javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript技巧也是围绕作用域进行的,今天我要总结一下关于javascript作用域的相关知识。

  很多人使用javascript时候会把{}作为作用域的边界,所以我们可以看看下面的代码:

function ftn01(){
var i = 1;
if (i == 1){
var a = "ok";
}
console.log("a = " + a);// a = ok
{var b = "bok";}
console.log("b = " + b);// b = bok
}
ftn01();

  我们发现变量a和b都能被打印出来,这就说明if下的{}和单独的{}并不能保护变量a和b的作用域范围,我们将上面的代码修改下:

function ftn01(){
var i = 1;
if (i == 1){
var a = "ok";
}
console.log("a = " + a);// a = ok
{var b = "bok";}
console.log("b = " + b);// b = bok
}
console.log(a);
ftn01();

  在firebug里会报出如下的错误:

  可见函数的{}是可以保护变量的作用域的。

  其实在javascript语言里只有函数是可以提供作用域,换句话说javascript里有且只有函数作用域,没有其他的作用域。因此要理解作用域必须从函数讲起,javascript里的函数同时也是一个对象,函数同时也是对象这句话让很多初学者误解,javascript这个特性和其他很多语言不太一样,要解释这个问题就必须从javascript创建函数的机制,javascript语言里有一个对象叫做Function,所有的函数都是该对象的实例,下面我们看看javascript里创建函数的三种方式:

var add01 = function add(a,b){
return a + b;
}
var add02 = function(a,b){
return a+ b;
}
var add03 = new Function("a","b","return a + b");
console.log(add01(1,2));//
console.log(add02(1,2));//
console.log(add03(1,2));//
console.log(typeof add01);//function
console.log(typeof add02);//function
console.log(typeof add03);//function

  第一种方式叫做命名函数方式,第二种叫做匿名函数方式,第三种是直接使用Function对象来定义函数,三种方式是等价的,并且都可以用一个变量保存该函数。其实前两种创建函数的方式是第三种的变种,由此可以看到所有函数都是Function对象的实例。

  函数的作用域功能是由函数内置的一个属性scope体现的,scope属性是一个类数组的集合,这个类数组的集合叫做函数的作用域链,作用域是函数的一个属性,作用域链其实就是该属性的数据类型。当我们创建一个函数时候,就是上面定义函数add01时候,函数的scope属性就会被同步创建,并且作用域链也会被构建,上面的例子里创建的函数都是属于全局的,因此作用域链只有一个元素即该类数组的length是1,类数组所包含的元素也是一个键值对类型,该元素包含所有全局变量例如:window,document,add01等等。

  上面的例子里add01是函数的标识符,当add01(1,2)加上了小括号的时候就是执行该函数了,执行一个函数时候,函数会创建一个运行期上下文,英文全称是:execution context,运行期上下文是函数的一个内部对象和作用域一样也是不能被外部访问的,每个运行期的函数都会有一个自己独有的运行上下文,当函数执行完毕后,该函数的运行上下文也会被销毁。当函数执行的时候,首先会初始化函数自带的scope属性,然后将函数自带的scope里的作用域链复制到运行期上下文里,运行期上下文里也包含一个作用域的属性,该属性也是用一个作用域链的类数组表示,复制出来的函数的作用域链会放到运行期上下文的作用域链里,这一步做好后,运行期上下文会初始化函数内部的局部变量和命名参数例如add01函数里的a,b,把这些变量存储到一个活动对象的变量里,初始化完成后这个活动对象也会加入到运行期上下文的作用域链里,这个活动对象会放到运行期上下文作用域链的最前端。其实在函数执行完毕销毁的对象就是这个活动对象,活动对象被销毁了对应的运行期上下文也就被销毁,但是原来存储在函数里的作用域还是保留的。

  作用域链的作用是对变量标识符进行解析,标识符就是变量的名字,例如add01、add02这些就是标识符,标识符的解析就是获取数据的位置或是如何存储数据。当函数执行时候,遇到每一个变量就会搜索运行期上下文的作用域链,这个过程都是从作用域链的头开始查找,也就是我上面说的从活动对象开始找起,如果找到与之对应的标识符,那么搜索过程便会停止,如果没有找到那么接着就会搜索作用域链的下一个对象,直到找到为止。不管是函数的作用域链还是运行期上下文的作用域链,链条的最后一个都是全局对象,其实全局对象是一个特殊的对象,如果我还是套用前面函数作用域的方式去理解全局对象,把一个网页当做一个最大的函数,这个函数对象就是window,网页的打开时这个window函数生命周期的开始,网页的关闭就是window函数的销毁,与window函数对应的还有一个全局的运行期上下文,那么用上面的理论理解全局变量应该就会简单多了。全局变量可以当做所有函数作用域的父作用域,当标识符解析到了全局变量时候,前面一定经过了n多个的作用域链中元素的遍历,因此到了遍历全局变量其实程序执行的效率是很低了,所以所有写高性能javascript代码的建议里都是要尽量减少全局变量的使用,如果非要使用全局变量也要在函数作用域内用一个局部变量替代全局变量,这是高性能javascript代码的一个基本要求,不过时下最新版的浏览器几乎都对这个特性进行了优化,访问全局变量不再像以前那么消耗性能了,不过ie的老版本的市场还是很大,而老版本的ie对全局访变量的访问那就是代码效率的毒瘤了。

  下面我要讲讲闭包和作用域的关系,有很多人把闭包等价于作用域或者是作用域链,这个有一定的道理,但是如果真的以为闭包就是等价于的这些的话,这个等价于就是错误的,闭包在javascript语言里是一个特殊的函数,闭包产生于函数执行的时候,也就是函数的名字加上了小括号使用的时候,这个时候就会创建闭包或者叫做定义闭包,这个过程和函数创建作用域的过程一样,而闭包的作用域链就是函数的运行期上下文,当执行闭包或者说使用闭包的时候,那么就会构建出闭包的运行期上下文,这个时候闭包也会构建一个活动对象,这个活动对象被置于它的作用域链的最前端,同时闭包还包含执行函数的活动对象,但这个活动变量是在闭包活动变量的后面,这样就会导致函数销毁时候内存无法及时回收,造成大量的内存资源的占用,因此使用闭包是个十分消耗计算资源的应用,前面我讲到要尽量把全局变量用局部变量替换,其实碰到跨作用域的变量引用,都要将其用局部变量替代,这样代码的效率和安全性会更好。

  还有很多童鞋认为this指针和作用域链没什么联系,这种理解是不正确的,其实this指针就是指向作用域链的上一级对象,但这种理解常常会误导某些人对this的理解,因为很多人在函数内部调用函数时候,发现被调用的函数this指针指向了全局变量,我们在实际开发里使用this指针最好是换个角度,this指针是调用某个函数上一级对象,例如obj.ftn(),那么ftn函数内部的this指针就是指向的obj,如果有个函数直接是ftn(),前面没有点,那么ftn函数里的内部指针就是window对象,this指针都是指向点号前面的对象,如果没有点号就是window对象,通过点号理解this指针比较方便,也不容易出错,但是通过作用域理解this为什么会有偏差了,原因就是全局作用域在作祟,在javascript里不管哪里直接调用函数,前面没有点的时候this都是指向全局的,我们不应该看这个方法是放到哪个函数里执行,其实this和作用域关系是紧密的,大家千万别怀疑这点。此外在作用域链的数组型的数据结构里,数组的每一个元素都含有一个this指针,this都是作为一个键值对预先定义好的,它的取值不由作用域链的创建所决定。因此有人认为this指针的使用其实也是跨域访问的观点也是不对,this指针的使用不存在跨域,它的效率也是非常高的。

  理解了上面这个问题,那么我们对函数的apply和call方法深入理解就比较容易了,这两个方法的本质作用就是在特定的作用域里调用函数,这个解释比较抽象,这样我们先看下面的例子:

function test(){
console.log(this);
}
test.call(window);// window
var obj = {};
test.call(obj);// object
function ftn01(){
test.call(this);
}
ftn01();// window

  这两个方法的第一个参数就是改变当前活动对象里this指针指向哪个对象,第一个参数就是函数this指针指向的对象,其本质也是改变作用域的一种方式,改变作用域的方法可以扩展方法的使用范围,因此调用对象和方法解耦,这样可以精简代码,提高代码的复用程度。

  尽量少使用全局变量,多用局部变量是写高效javascript程序的一个基本要求,大家不要怀疑它会过时,不管浏览器如何演进,这点规则都是一条黄金规则。记住最优秀的javascript代码里全局变量最好只有一个。

  最后我还要插一句,做过javascript都知道javascript代码压缩是一个提升网站效率的重要手段,我们使用雅虎或者谷歌的压缩代码的工具时候,会发现很多变量都会被A,B这样简单的代码所替换,这个替换规则都是对局部变量进行的,因此多使用局部变量会javascript压缩效果更好。

javascript笔记:javascript的关键所在---作用域链的更多相关文章

  1. javascript的关键所在---作用域链

    javascript的关键所在---作用域链 javascript里的作用域是理解javascript语言的关键所在,正确使用作用域原理才能写出高效的javascript代码,很多javascript ...

  2. JavaScript 中的闭包和作用域链(读书笔记)

    要想理解闭包,应当先理解JavaScript的作用域和作用域链. JavaScript有一个特性被称之为“声明提前(hoisting)”,即JavaScript函数里声明的所有变量(但不涉及赋值)都被 ...

  3. JavaScript闭包理解的关键 - 作用域链

    阮一峰的一篇文章已经对闭包的用途.概念讲解地相对清晰了. 闭包就是能够读取其他函数内部变量的函数. 但我认为里面对于作用域链的解释还不够清晰,这里作一些补充. 闭包之所以可以读取外部函数的内部变量,即 ...

  4. javascript笔记整理(变量作用域)

    变量的作用域(作用域:指的就是一段代码的作用范围) 一.变量的作用域 1.全局变量(在代码中任何地方都能够访问得到的变量,拥有全局的作用域) A.最外层函数外面定义的变量 var a=1; funct ...

  5. JavaScript中的闭包和作用域链

    这部分几乎是JavaScript中最难的部分,也是面试官最爱问的地方. 下面的内容是我以前写的<JavaScript学习手册>中被客户删除的部分,理由听起来有点诡异:太难.

  6. Javascript 笔记:原型和原型链

    一.函数对象和普通对象 凡是通过new Function()创建的都是函数对象,其它的都是普通对象.Function,Object,Array,Number,String,Boolean,Date是J ...

  7. javascript笔记——JavaScript经典实例

    转载自百度文库 http://wenku.baidu.com/view/9a703522bcd126fff7050bfa.html 1. oncontextmenu="window.even ...

  8. 理解JavaScript的作用域链

    上一篇文章中介绍了Execution Context中的三个重要部分:VO/AO,scope chain和this,并详细的介绍了VO/AO在JavaScript代码执行中的表现. 本文就看看Exec ...

  9. [译]JavaScript:函数的作用域链

    原文:http://blogs.msdn.com/b/jscript/archive/2007/07/26/scope-chain-of-jscript-functions.aspx 在JavaScr ...

随机推荐

  1. Flashback for MySQL 5.7

    实现原理 flashback的概念最早出现于Oracle数据库,用于快速恢复用户的误操作. flashback for MySQL用于恢复由DML语句引起的误操作,目前不支持DDL语句.例如下面的语句 ...

  2. jQuery的ajax问题

    Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to th ...

  3. kendo chart label position 图表的值标签位置及显示模板

    1.不显示0 seriesDefaults: { type: "column", labels: { visible: true, position:'' background: ...

  4. 向NFV过渡 这三个坑我替你趟了

    网络功能虚拟化(NFV)在最近几年不断发展,并开始现实商业部署.通信服务商也必须解决自身所面临的挑战,包括安全.性能.投入产出比.与现有网络实现电信级交互.运营支撑系统(OSS)和业务支撑系统(BSS ...

  5. fuse入门

    参考1 http://www.cs.nmsu.edu/~pfeiffer/fuse-tutorial/html/running.html 参考2 http://www.maastaar.net/fus ...

  6. C#窗口应用如何居中

    在form的属性设置里面有一个初始位置的设置(startposion)设置成centerscreen(屏幕居中即可)

  7. 高性能 CSS3 动画

    注:本文出自腾讯AlloyTeam的元彦,文章也可以在github上浏览.请尊重版权,转载请注明来源,多谢-- 高性能移动Web相较PC的场景需要考虑的因素也相对更多更复杂,我们总结为以下几点: 流量 ...

  8. apache安装mod_deflate配置支持gzip

    apache 配置支持gzip apache使用gzip压缩能够大幅提高网站访问速度并节省网络流量,在网页响应头信息中可以判断是否支持压缩. HTTP/1.1 200 OK Date: Wed, 14 ...

  9. 通过统计用户DNS解析记录,实现监控用户上网行为

    上次通过扫描抓包分析TTL的方式检测公司网络开放的端口,发现没有开放53端口(DNS),也就是在公司内部的主机只能用服务器自动分配的DNS,并且发现这是台内部服务器.今天发现bing上不去,检测后发现 ...

  10. Java学习笔记 07 接口、继承与多态

    一.类的继承 继承的好处 >>使整个程序架构具有一定的弹性,在程序中复用一些已经定义完善的类不仅可以减少软件开发周期,也可以提高软件的可维护性和可扩展性 继承的基本思想 >>基 ...