继续整理。
闭包,指有权访问另一个函数作用域内变量的函数。创建闭包的常见方式就是在一个函数内部创建另一个函数。
1 2 3 4 5 6 7 8
| function a(A){ return function(B){ return A+B; }; } var be = a(5); console.log(be(10));
|
例子中匿名函数访问了外部函数中的变量A。即使这个匿名函数被返回了,或者在其他地方被调用了,它依然可以访问变量A,因为匿名函数的作用域链中包含a()的作用域。
当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后,使用arguments和其它命名参数的值来初始化函数的活动对象。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,直至作为作用域链终点的全局执行环境。
后台的每个执行环境都有一个表示变量的对象——变量对象。全局的变量对象始终存在,而像a()函数这样的局部环境的变量对象只在函数执行的过程中存在。一般来讲当函数执行完毕时,局部活动变量就会被销毁,内存中仅保存全局作用域。但是闭包的情况又有所不同。如刚才的例子,a()函数在执行完毕之后,其活动变量也不会呗销毁,因为匿名函数的作用域链依然在引用着这个活动对象。实际上,当a()函数在执行完毕后,其作用域链会被销毁,但它的活动对象仍然会留在内存中。直到匿名函数被销毁后,a()的活动对象才会被销毁。
解除对匿名函数的引用。
因为闭包保存的是整个变量对象而不是某个特殊的变量,所以闭包只能取得包含函数中任何变量的最后一个值。
1 2 3 4 5 6 7 8
| function a(){ var result = []; for(var i=0;i<10;i++){ result[i] = function(){ return i; } } }
|
这个函数会返回一个函数数组。表面上看应该每个函数都应该返回自己的索引值,然而10个函数返回的均为10。这就因为每个函数的作用域链保存的不是i这个变量的值,而是a()函数的活动对象。每个函数都引用着保存变量i最后一个值的变量对象,所以每个函数内部i都是10。这种情况下,我们可以创建另一个匿名函数强制让闭包的行为符合预期:
1 2 3 4 5 6 7 8 9 10
| function a(){ var result = []; for(var i=0;i<10;i++){ result[i] = function(num){ return function(){ return num; }; }(i); } }
|
这样定义了一个匿名函数,并将立即执行该匿名函数的结果赋值给数组。由于函数参数是按值传递的,所以会将变量i的当前值赋值给num。而在这个匿名函数的内部又创建并返回了一个访问num的闭包。这样一来,result数组的每个函数都有自己num变量的一个副本,因此就可以返回各自不同的数值了。
闭包与this
在全局函数中,this指向window;而当函数被作为某个对象的方法调用时,this指向那个对象。匿名函数的执行环境具有全局性,因此其this对象通常指向window。
1 2 3 4 5 6 7
| var a = { Name: 'hello', getName: function(){ console.log(this.Name); } }; a.getName();
|
getName作为a对象的方法调用,this指向a,输出’hello’。如果getName内加入闭包函数会怎样呢?
1 2 3 4 5 6 7 8 9
| var a = { Name: 'hello', getName: function(){ return function(){ console.log(this.Name); }; } }; a.getName()();
|
因为匿名函数的this指向了window,所以输出undefined。那如果闭包想要访问Name属性要怎么做呢?
1 2 3 4 5 6 7 8 9 10
| var a = { Name: 'hello', getName: function(){ var that = this; return function(){ console.log(that.Name); }; } }; a.getName()();
|
定义匿名函数之前,我们把this变量赋值给that,闭包也可以访问that,以此为桥梁进行访问。
关于块级作用域
js没有块级作用域,只有函数作用域。在块语句中定义的变量,实际上是在包含函数中创建的。
1 2 3 4 5 6
| function a(){ for(var i = 0;i < 5; i++){ .... } alert(i); }
|
如果在c、java等其他语言中,i只在for循环中有定义,循环结束后i就会销毁。而js没有块级作用域只有函数作用域,所以i在a()函数内都可以访问到。
如果需要块级作用域的话, 那么可以通过立即执行的匿名函数来实现。
1 2 3
| (function(){ ....块级作用域 })()
|
1 2 3 4 5 6 7 8
| function a(){ (function(){ for(var i = 0;i < 5; i++){ .... } })(); alert(i); }
|
无论是在什么地方,只要临时需要一些变量,都可以使用私用作用域。
这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。
实现私有变量
任何在函数内部定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。我们把有权访问私有变量和私有函数的公有方法叫做特权方法。
在构造函数中实现私有变量:
使用构造函数主要是为了自定义类型创建私有变量和私有方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function MyObject(name){ var privateVariable = "10"; function privateFunction(){ return false; } this.getName = function(){ return name; } this.setName = function(newName){ name = newName; } this.publicMethod = function(){ privateVariable++; return pricateFunction(); } }
|
不使用特权方法的话,没有其他办法可以访问name、privateVariable、privateFunction。将私有变量和函数写在构造函数内部的问题是,每创建一个示例都会创建同样一组新方法。使用静态私有变量可以避免这个问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| (function(){ var privateVariable = "10"; function privateFunction(){ return false; } MyObject = function(){}; MyObject.prototype.publicMethod = function(){ privateVariable++; return pricateFunction(); } })();
|
使用原型,所有实例引用相同的私有变量和私有函数。
模块模式
模块模式是为单例创建私有变量和私有方法。单例指的就是只有一个实例的对象。
普通方式创建单例对象:
1 2 3 4 5 6
| var single = { pro : "str..", method: function(){ } }
|
使用模块方式创建单例对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| var single = function(){ var privateVariable = "10"; function privateFunction(){ return false; } return { publicVariable: true, publicMethod: function(){ privateVariable++; return pricateFunction(); } } }
|
在匿名函数的内部定义私有变量和私有方法,在返回的对象字面量中定义公有属性/方法和特权方法。由于这个对象是在匿名函数内定义的,所以其公有方法可以访问私有变量和私有方法。
还有一种叫“增强的模块方式”,理解起来很简单:如果要求返回的对象属于某种类型,比如Person类型,就这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| var single = function(){ var privateVariable = "10"; function privateFunction(){ return false; } var obj = new Person(); Person.publicVariable = true; Person.publicMethod: function(){ privateVariable++; return pricateFunction(); } return obj; }
|
接下来打算整理下坑坑哒变量提示和函数声明提升:)