|
1 | | -[](https://parg.co/bxN) |
| 1 | +[](https://parg.co/bxN) |
2 | 2 |
|
3 | | -# 函数声明与闭包 |
| 3 | +# 函数声明与闭包 |
| 4 | + |
| 5 | +# 参数 |
| 6 | + |
| 7 | +## arguments |
| 8 | + |
| 9 | +JavaScript 的函数里面的 arguments 对象有 .lengh 属性和可以通过 [] 访问,但是确实从 Object.prototype 继承的。很多时候都会用 Array.prototype.slice.call(arguments) 或者 Array.from(arguments) 转成数组。Brendan Eich 本人也承认 arguments 的设计是因为当时只花了十天所以整得太糙了。在正式规范化 JavaScript 的时候,Microsoft 曾经有人提出把 arguments 改成真正的 Array,BE 本人甚至都打算动手改实现了,但是 MS 那边回去商量了下又回来觉得多一事不如少一事,不改了。于是这个糟糕的设计就从此成为了规范 ... 这是 1997 年的第一版 ES 规范。 |
| 10 | + |
| 11 | +除了 arguments.callee 之外,还有一个神奇的 quirk,那就是 arguments 和实际的参数变量之间的迷之绑定。规范里是这么说的: |
| 12 | + |
| 13 | +In the case when iarg is less than the number of formal parameters for the function object, this property shares its value with the corresponding property of the activation object. This means that changing this property changes the corresponding property of the activation object and vice versa. The value sharing mechanism depends on the implementation. |
| 14 | + |
| 15 | +换言之,假设一个函数的第一个参数是 a,当你修改 a 的值的时候,arguments[0] 也会同步变化: |
| 16 | + |
| 17 | +```js |
| 18 | +(function(a) { |
| 19 | + console.log(arguments[0] === a); // -> true |
| 20 | + console.log(a); // -> 1 |
| 21 | + |
| 22 | + // 修改 arguments |
| 23 | + arguments[0] = 10; |
| 24 | + console.log(a); // -> 10 |
| 25 | + |
| 26 | + // 修改参数变量 |
| 27 | + a = 20; |
| 28 | + console.log(arguments[0]); // -> 20 |
| 29 | +})(1, 2); |
| 30 | +``` |
| 31 | + |
| 32 | +后面的事情你也知道了,ES 规范是要向后兼容的,而且上面的这个 quirk 使得它在引擎实现中需要很多特殊处理,一旦改动,兼容性影响巨大,所以它永远也改不了了。据说在 ES5 讨论时也有人提出要把 arguments 改成 Array 的 subclass,但是很快就不了了之,只是在 strict mode 下对 arguments.callee 和上面的绑定 quirk 进行了限制。直到 ES6 终于对 arguments 提供了一个替代品 - rest parameters: |
| 33 | + |
| 34 | +```js |
| 35 | +function foo(...args) { |
| 36 | + // 这里 args 终于是真正的 Array 了! |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +BE 本人并没有提到为什么一开始会把 arguments 设计成对象,因此我们也只能做猜测。但一个合理的推测是,ES1 里面的 Array.prototype 其实很弱,只有四个方法:toString, join, reverse 和 sort - 连 push, pop, shift, unshift, splice 都没有!而 forEach, filter, map, reduce 这些有用的方法更是 ES5 才添加进来的。所以当时 arguments 就算真的继承自 Array 貌似也没什么大用,所以就这样被放过了 ... 当然,这只是我们的猜测,估计 BE 自己今天也说不清自己当时为什么这么干的了吧。 |
| 41 | + |
| 42 | +# Closure: 闭包 |
| 43 | + |
| 44 | +* A closure is a function that has access to the parent scope, even after the scope has closed. |
| 45 | + |
| 46 | +* A closure is the combination of a function and the lexical environment within which that function was declared. |
| 47 | + |
| 48 | +# Closure |
| 49 | + |
| 50 | +闭包本身是含有自由变量的代码块,在 JavaScript 中我们常用的闭包则是本身的词法作用域与变量保留相结合的表现,首先回顾下一个基本的词法作用域的用法: |
| 51 | + |
| 52 | +```js |
| 53 | +function init() { |
| 54 | + var name = "Mozilla"; |
| 55 | + function displayName() { |
| 56 | + alert(name); |
| 57 | + } |
| 58 | + displayName(); |
| 59 | +} |
| 60 | +init(); |
| 61 | +``` |
| 62 | + |
| 63 | +函数 init() 创建了一个局部变量 name,然后定义了名为 displayName() 的函数。displayName() 是一个内部函数 —— 定义于 init() 之内且仅在该函数体内可用。displayName() 没有任何自己的局部变量,然而它可以访问到外部函数的变量,即可以使用父函数中声明的 name 变量。注意,这里是直接执行外部的 init 函数,下面看一个闭包的例子 : |
| 64 | + |
| 65 | +```js |
| 66 | +function makeFunc() { |
| 67 | + var name = "Mozilla"; |
| 68 | + function displayName() { |
| 69 | + alert(name); |
| 70 | + } |
| 71 | + return displayName; |
| 72 | +} |
| 73 | + |
| 74 | +var myFunc = makeFunc(); |
| 75 | +myFunc(); |
| 76 | +``` |
| 77 | + |
| 78 | +运行这段代码的效果和之前的 init() 示例完全一样:字符串 "Mozilla" 将被显示在一个 JavaScript 警告框中。其中的不同 — 也是有意思的地方 — 在于 displayName() 内部函数在执行前被从其外围函数中返回了。这段代码看起来别扭却能正常运行。通常,函数中的局部变量仅在函数的执行期间可用。一旦 makeFunc() 执行过后,我们会很合理的认为 name 变量将不再可用。虽然代码运行的没问题,但实际并不是这样的。这个谜题的答案是 myFunc 变成一个闭包了。 闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。在我们的例子中,myFunc 是一个闭包,由 displayName 函数和闭包创建时存在的 "Mozilla" 字符串形成。 |
| 79 | + |
| 80 | +# 避免闭包 |
| 81 | + |
| 82 | +在真实的开发中我们常常会使用闭包这一变量保留的特性来传递变量到异步函数中,不过闭包也往往会使程序出乎我们的控制,譬如在下面这个简单的循环中,我们本希望能够打印出 0~9 这几个数 : |
| 83 | + |
| 84 | +```js |
| 85 | +for (var i = 0; i < 10; i++) { |
| 86 | + setTimeout(() => { |
| 87 | + console.log(i), 1000; |
| 88 | + }); |
| 89 | +} |
| 90 | +``` |
| 91 | + |
| 92 | +不过所有输入的 i 的值都是 10,这与我们的期望产生了很大的偏差。因此我们在部分情况下需要破坏闭包而获取真实的变量值。 |
| 93 | + |
| 94 | +## 将异步获取值保留到新增的闭包中 |
| 95 | + |
| 96 | +我们可以考虑加一层闭包,将 i 以函数参数形式传递给内层函数: |
| 97 | + |
| 98 | +``` |
| 99 | + function init3() { |
| 100 | + var pAry = document.getElementsByTagName("p"); |
| 101 | + for( var i=0; i<pAry.length; i++ ) { |
| 102 | + (function(arg){ |
| 103 | + pAry[i].onclick = function() { |
| 104 | + alert(arg); |
| 105 | + }; |
| 106 | + })(i);//调用时参数 |
| 107 | + } |
| 108 | + } |
| 109 | +``` |
| 110 | + |
| 111 | +或者在新增的闭包中将`i`以局部变量形式传递给内部函数中 : |
| 112 | + |
| 113 | +```js |
| 114 | +function init4() { |
| 115 | + var pAry = document.getElementsByTagName("p"); |
| 116 | + for (var i = 0; i < pAry.length; i++) { |
| 117 | + (function() { |
| 118 | + var temp = i; //调用时局部变量 |
| 119 | + pAry[i].onclick = function() { |
| 120 | + alert(temp); |
| 121 | + }; |
| 122 | + })(); |
| 123 | + } |
| 124 | +} |
| 125 | +``` |
| 126 | + |
| 127 | +## 将变量值保留到作用域之外 |
| 128 | + |
| 129 | +在 DOM 环境中,我们可以将变量值存储到要操作的 DOM 对象中 : |
| 130 | + |
| 131 | +```js |
| 132 | +function init() { |
| 133 | + var pAry = document.getElementsByTagName("p"); |
| 134 | + for (var i = 0; i < pAry.length; i++) { |
| 135 | + pAry[i].i = i; |
| 136 | + pAry[i].onclick = function() { |
| 137 | + alert(this.i); |
| 138 | + }; |
| 139 | + } |
| 140 | +} |
| 141 | +``` |
| 142 | + |
| 143 | +也可以将变量`i`保存在匿名函数本身 : |
| 144 | + |
| 145 | +```js |
| 146 | +function init2() { |
| 147 | + var pAry = document.getElementsByTagName("p"); |
| 148 | + for (var i = 0; i < pAry.length; i++) { |
| 149 | + (pAry[i].onclick = function() { |
| 150 | + alert(arguments.callee.i); |
| 151 | + }).i = i; |
| 152 | + } |
| 153 | +} |
| 154 | +``` |
| 155 | + |
| 156 | +```js |
| 157 | +var b = fun(0) |
| 158 | + .fun(1) |
| 159 | + .fun(2) |
| 160 | + .fun(3); //undefined,0,1,2 |
| 161 | + |
| 162 | +/*一开始fun(n=undefined,o=undefined)*/ |
| 163 | +/* |
| 164 | + var b = fun(n=0,o=undefined); 执行后,相对应改变局部变量n和o. |
| 165 | + 执行console.log(undefined),返回: |
| 166 | + var b={ |
| 167 | + fun:执行fun(n=1,o=0);console.log(0),返回:{ |
| 168 | + fun:执行fun(n=2,o=1);console.log(1),返回:{ |
| 169 | + fun:执行fun(n=3,o=2);console.log(2),返回:{ |
| 170 | + fun:function(3){ |
| 171 | + return fun(m,3)由于没有执行所以n不会赋值为undefined,o也不会赋值为3 |
| 172 | + } |
| 173 | + } |
| 174 | + } |
| 175 | + } |
| 176 | + } |
| 177 | +*/ |
| 178 | +``` |
0 commit comments