JavaScript中的调用对象 作用域 闭包
2013-03-13 00:00
441 查看
在JavaScript中,在所有函数体之外声明的变量为全局变量,而在函数体内声明的变量(通过var关键字)为局部变量。事实上,全局变量是全局对象的属性而已,比如在客户端的JavaScript中,我们声明的变量其实是window对象的属性,如此而已。
那么,局部变量又隶属于什么对象呢?就是我们要讨论的调用对象。在执行一个函数时,函数的参数和其局部变量会作为调用对象的属性进行存储。同时,解释器会为函数创建一个执行器上下文(Execution Context),与上下文对应起来的是一个作用域链。顾名思义,作用域链是关于作用域的链,通常实现为一个链表,链表的每个项都是一个对象,在全局作用域中,该链中有且只有一个对象,即全局对象。对应的,在一个函数中,作用域链上会有两个对象,第一个(首先被访问到的)为调用对象,第二个为全局对象。
如果函数需要用到某个变量,则解释器会遍历作用域链,比如:
当解释器进入scopeTest函数的时候,一个调用对象就被创建了,其中包含了str变量作为其中的一个属性并被初始化为undefined,当执行到第一个print(str)时,解释器会在作用域链中查找str,找到之后,打印其值为undefined,然后执行赋值语句,此时调用对象的属性 str会被赋值为local,因此第二个print(str)语句会打印local。
应该注意的是,作用域链随着嵌套函数的层次会变的很长,但是查找变量的过程依旧是遍历作用域链(链表),一直向上查找,直到找出该值,如果遍历完作用域链仍然没有找到对应的属性,则变量未定义。(定义未赋值返回undefined,未定义会引起运行时的错误not defined)。
尝试读一个未声明的变量的值,Javascript会生成一个错误。如果尝试给一个味用var声明的变量赋值,Javascript会隐式声明该变量,注意,隐式声明的变量总是被创建为全局变量,即使该变量只是在一个函数体内使用。防止在创建局部变量时创建全局变量,必须在函数体内部使用var语句。
一个函数的调用对象是动态的,它是在这个函数被调用时才被实例化的。我们已经知道,当一个函数被定义的时候,已经确定了它的作用域链。当 Javascript 解释器调用一个函数的时候,它会添加一个新的对象(调用对象)到这个作用域链的前面。这个调用对象的一个属性被初始化成一个名叫 arguments 的属性,它引用了这个函数的 Arguments 对象,Arguments 对象是函数的实际参数。所有用 var 语句声明的本地变量也被定义在这个调用对象里。这个时候,调用对象处在作用域链的头部,本地变量、函数形式参数和 Arguments 对象全部都在这个函数的范围里了。当然,这个时候本地变量、函数形式参数和 Arguments 对象就覆盖了作用域链里同名的属性。
全局的大匿名函数被定义的时候,它没有外层,所以它的作用域链是空的。
全局的大匿名函数直接被执行,全局的作用域链里只有一个 '全局调用对象'。
函数 f 被定义,此时函数 f 的作用域链是它外层的作用域链,即 '全局调用对象'。
函数 f(1) 被执行,它的作用域链是新的 f(1) 调用对象加上函数 f 被定义的时候的作用域链,即 'f(1) 调用对象->全局调用对象'。
函数 g (它要被返回给 g1,就命名为 g1吧)在 f(1) 中被定义,它的作用域链是它外层的函数 f(1) 的作用域链,即 'f(1) 调用对象->全局调用对象'。
函数 f(1) 返回函数 g 的定义给 g1。
函数 g1 被执行,它的作用域链是新的 g(1) 调用对象加上外层 f(1) 的作用域链,即 'g1 调用对象->f(1)调用对象->全局调用对象'。
之前的那个例子其实就是一个闭包。g1 是在 f(1) 内部定义的,却在 f(1) 返回后才被执行。可以看出,闭包的一个效果就是被嵌套函数 f 返回后,它内部的资源不会被释放。在外部调用 g 函数时,g 可以访问 f 的内部变量。根据这个特性,可以写出很多优雅的代码。
例如要在一个页面上作一个统一的计数器,如果用闭包的写法,可以这么写:
这样,在内存中就维持了一个变量 i,整个程序中的其它地方都无法直接操作 i 的值,只能通过 counter 的两个操作。
在 setTimeout(fn, delay) 的时候,我们不能给 fn 这个函数句柄传参数,但可以通过闭包的方法把需要的参数绑定到 fn 内部。
这样,打印出来的值都是
改用闭包的方式可以很容易绑定要传进去的参数:
输出:
闭包还有一个很常用的地方,就是在绑定事件的回调函数的时候。也是同样的道理,绑定的函数句柄不能做参数,但可以通过闭包的形式把参数绑定进去。
函数在被定义的时候,同时也是它外层的函数在被执行的时候。
函数在被定义的时候它的词法作用域就已经确定了,但它仍然是抽象的概念,没有也不能被实例化。
函数在被定义的时候还确定了一个东西,就是它外层函数的作用域链,这个是实例化的东西。
函数在被多次调用的时候,它的作用域链都是不同的。
闭包很强大。犀牛书说得对,理解了这些东西,你就可以自称是高级 Javascript 程序员了。因为利用好这些概念,可以玩转 Javascript 的很多设计模式。
那么,局部变量又隶属于什么对象呢?就是我们要讨论的调用对象。在执行一个函数时,函数的参数和其局部变量会作为调用对象的属性进行存储。同时,解释器会为函数创建一个执行器上下文(Execution Context),与上下文对应起来的是一个作用域链。顾名思义,作用域链是关于作用域的链,通常实现为一个链表,链表的每个项都是一个对象,在全局作用域中,该链中有且只有一个对象,即全局对象。对应的,在一个函数中,作用域链上会有两个对象,第一个(首先被访问到的)为调用对象,第二个为全局对象。
如果函数需要用到某个变量,则解释器会遍历作用域链,比如:
var str = "global"; function scopeTest(){ print(str); var str = "local"; print(str); }
当解释器进入scopeTest函数的时候,一个调用对象就被创建了,其中包含了str变量作为其中的一个属性并被初始化为undefined,当执行到第一个print(str)时,解释器会在作用域链中查找str,找到之后,打印其值为undefined,然后执行赋值语句,此时调用对象的属性 str会被赋值为local,因此第二个print(str)语句会打印local。
应该注意的是,作用域链随着嵌套函数的层次会变的很长,但是查找变量的过程依旧是遍历作用域链(链表),一直向上查找,直到找出该值,如果遍历完作用域链仍然没有找到对应的属性,则变量未定义。(定义未赋值返回undefined,未定义会引起运行时的错误not defined)。
尝试读一个未声明的变量的值,Javascript会生成一个错误。如果尝试给一个味用var声明的变量赋值,Javascript会隐式声明该变量,注意,隐式声明的变量总是被创建为全局变量,即使该变量只是在一个函数体内使用。防止在创建局部变量时创建全局变量,必须在函数体内部使用var语句。
一个函数的调用对象是动态的,它是在这个函数被调用时才被实例化的。我们已经知道,当一个函数被定义的时候,已经确定了它的作用域链。当 Javascript 解释器调用一个函数的时候,它会添加一个新的对象(调用对象)到这个作用域链的前面。这个调用对象的一个属性被初始化成一个名叫 arguments 的属性,它引用了这个函数的 Arguments 对象,Arguments 对象是函数的实际参数。所有用 var 语句声明的本地变量也被定义在这个调用对象里。这个时候,调用对象处在作用域链的头部,本地变量、函数形式参数和 Arguments 对象全部都在这个函数的范围里了。当然,这个时候本地变量、函数形式参数和 Arguments 对象就覆盖了作用域链里同名的属性。
作用域、作用域链和调用对象之间的关系
在函数被定义的时候,实际上也是它外层函数执行的时候,它确定的作用域链实际上是它外层函数的调用对象链;当函数被调用时,它的作用域链是根据定义的时候 确定的作用域链(它外层函数的调用对象链)加上一个实例化的调用对象。所以函数的作用域链实际上是调用对象链。在一个函数被调用的时候,它的作用域链(或 者称调用对象链)实际上是它在被定义的时候确定的作用域链的一个超集。它们之间的关系可以表示成:作用域⊃作用域链⊇调用对象。function f(x) { var g = function () { return x; } return g; } var g1 = f(1); alert(g1()); //输出 1假设我们把全局看成类似以下这样的一个大匿名函数:
(function() { //这里是全局范围 })();那么例子就可以看成是:
(function() {
function f(x) { var g = function () { return x; } return g; } var g1 = f(1); alert(g1()); //输出 1
})();
全局的大匿名函数被定义的时候,它没有外层,所以它的作用域链是空的。
全局的大匿名函数直接被执行,全局的作用域链里只有一个 '全局调用对象'。
函数 f 被定义,此时函数 f 的作用域链是它外层的作用域链,即 '全局调用对象'。
函数 f(1) 被执行,它的作用域链是新的 f(1) 调用对象加上函数 f 被定义的时候的作用域链,即 'f(1) 调用对象->全局调用对象'。
函数 g (它要被返回给 g1,就命名为 g1吧)在 f(1) 中被定义,它的作用域链是它外层的函数 f(1) 的作用域链,即 'f(1) 调用对象->全局调用对象'。
函数 f(1) 返回函数 g 的定义给 g1。
函数 g1 被执行,它的作用域链是新的 g(1) 调用对象加上外层 f(1) 的作用域链,即 'g1 调用对象->f(1)调用对象->全局调用对象'。
闭包 Closuer
闭包的一个简单的说法是,当嵌套函数在被嵌套函数之外调用的时候,就形成了闭包。之前的那个例子其实就是一个闭包。g1 是在 f(1) 内部定义的,却在 f(1) 返回后才被执行。可以看出,闭包的一个效果就是被嵌套函数 f 返回后,它内部的资源不会被释放。在外部调用 g 函数时,g 可以访问 f 的内部变量。根据这个特性,可以写出很多优雅的代码。
例如要在一个页面上作一个统一的计数器,如果用闭包的写法,可以这么写:
var counter = (function() { var i = 0; var fns = {"get": function() {return i;}, "inc": function() {return ++i;}}; return fns; })(); //do something counter.inc(); //do something else counter.inc(); var c_value = counter.get(); //now c_value is 2
这样,在内存中就维持了一个变量 i,整个程序中的其它地方都无法直接操作 i 的值,只能通过 counter 的两个操作。
在 setTimeout(fn, delay) 的时候,我们不能给 fn 这个函数句柄传参数,但可以通过闭包的方法把需要的参数绑定到 fn 内部。
for(var i=0,delay=1000; i< 5; i++, delay +=1000) { setTimeout(function() { console.log('i:' + i + " delay:" + delay); }, delay); }
这样,打印出来的值都是
i:5 delay:6000 i:5 delay:6000 i:5 delay:6000 i:5 delay:6000 i:5 delay:6000
改用闭包的方式可以很容易绑定要传进去的参数:
for(var i=0, delay=1000; i < 5; i++, delay += 1000) { (function(a, _delay) { setTimeout(function() { console.log('i:'+a+" delay:"+_delay); }, _delay); })(i, delay); }
输出:
i:0 delay:1000 i:1 delay:2000 i:2 delay:3000 i:3 delay:4000 i:4 delay:5000
闭包还有一个很常用的地方,就是在绑定事件的回调函数的时候。也是同样的道理,绑定的函数句柄不能做参数,但可以通过闭包的形式把参数绑定进去。
总结
函数的词法作用域和作用域链是不同的东西,词法作用域是抽象概念,作用域链是实例化的调用对象链。函数在被定义的时候,同时也是它外层的函数在被执行的时候。
函数在被定义的时候它的词法作用域就已经确定了,但它仍然是抽象的概念,没有也不能被实例化。
函数在被定义的时候还确定了一个东西,就是它外层函数的作用域链,这个是实例化的东西。
函数在被多次调用的时候,它的作用域链都是不同的。
闭包很强大。犀牛书说得对,理解了这些东西,你就可以自称是高级 Javascript 程序员了。因为利用好这些概念,可以玩转 Javascript 的很多设计模式。
相关文章推荐
- JavaScript作用域、上下文环境、函数对象的定义与调用、匿名函数的定义与调用、闭包
- JavaScript作用域、上下文环境、函数对象的定义与调用、匿名函数的定义与调用、闭包
- javascript闭包,作用域,自调用匿名函数
- JavaScript作用域、闭包、对象与原型链概念及用法实例总结
- 【转】Javascript 的词法作用域、调用对象和闭包
- [label][JavaScript]读nowmagic - js词法作用域、调用对象与闭包
- javascript(面向对象,作用域,闭包,设计模式等)
- javascript作用域、闭包、对象与原型链
- python基础----函数的定义和调用、return语句、变量作用域、传参、函数嵌套、函数对象、闭包、递归函数
- Javascript 的词法作用域、调用对象和闭包
- 【转】Javascript 的词法作用域、调用对象和闭包
- 【转】Javascript 的词法作用域、调用对象和闭包
- 深入理解JavaScript作用域、变量对象、闭包
- javascript面向对象基础——作用域、闭包、模拟私有属性
- 举例详细说明javascript作用域、闭包原理以及性能问题
- 在JavaScript中this对象上下文作用域
- ?一个XML对象在javascript中调用的代码错误
- javascript调用COM组件,"错误: 对象不支持此属性或方法"
- COM组件封装与javascript调用返回未知对象
- 后台调用前台javascript方法报错:“缺少对象”的解决方法