functiona(){ var n = 0; functioninc() { n++; console.log(n); } inc(); inc(); } a();
例子3
1 2 3 4 5 6 7 8 9 10 11
functiona(){ var n = 0; functioninc(){ n++; console.log(n); } return inc; } var c = a(); c(); //控制台输出1 c(); //控制台输出2
看看是怎么执行的:
var c = a(),这一句 a()返回的是函数 inc,那这句等同于 var c = inc; c(),这一句等同于 inc(); 注意,函数名只是一个标识(指向函数的指针),而()才是执行函数。 后面三句翻译过来就是: var c = inc; inc(); inc();,跟第一段代码有区别吗? 没有。
为啥要这样写? 我们知道,js的每个函数都是一个个小黑屋,它可以获取外界信息,但是外界却无法直接看到里面的内容。将变量 n 放进小黑屋里,除了 inc 函数之外,没有其他办法能接触到变量 n,而且在函数 a 外定义同名的变量 n 也是互不影响的,这就是所谓的增强“封装性”。
functionouter() { var result = newArray(); for (var i = 0; i < 10; i++) { result[i] = function() { return i; }; } return result; }
看样子result每个闭包函数对打印对应数字,1,2,3,4,…,10, 实际不是,因为每个闭包函数访问变量i是outer执行环境下的变量i,随着循环的结束,i已经变成10了,所以每个函数内部的 i 值都是10, 怎么解决这个问题呢?
1 2 3 4 5 6 7 8 9 10 11 12
functionouter() { var result = [] for (var i = 0; i < 10; i++) { result[i] = (function(num) { returnfunction() { return num; // 此时访问的num,是上层函数执行环境的num,数组有10个函数对象,每个对象的执行环境下的number都不一样 }(); })(i); } return result } console.log(outer()); //0,1,2,3,4,5,6,7,8,9
`我们没有直接把闭包赋值给数组,而是定义一个匿名函数,并立即执行该匿名函数的结果赋值给数组。我们在调用匿名函数时,我们传入的变量 i 。由于函数是按值传递的,所以将变量 i 的当前值复制给num。而这个匿名函数的内部,又创建并返回一个访问num的闭包。这样一来result数组中的每个函数都有自己的num变量的一个副本,因此返回不同的数值。
坑点2: this指向问题
1 2 3 4 5 6 7 8 9 10
var name = "The Window"; var object = { name: "My Object", getNameFuce: function() { returnfunction() { returnthis.name; }; } }; console.log(object.getNameFuce()()); // The Window
this和arguments。内部函数在搜索这两个对象时,只会搜索到其活动对象为止,因此永远不能直接访问外部函数中的这两个变量。如果想访问作用域中的 this 和 arguments 对象,必须将该对象的引用保存到另一个闭包能够访问的变量之中。
我们可以把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。
1 2 3 4 5 6 7 8 9 10 11
var name = "The Window"; var object = { name: "My Object", getNameFuce: function() { var that = this; returnfunction() { return that.name; }; } }; console.log(object.getNameFuce()()); // My Object
在定义匿名函数之前,我们把 this 对象赋值给一个名叫that的变量。而定义了闭包之后,闭包可以访问这变量,因为它是我们在包含函数中特意声明的变量。即使在函数返回之后,that 也仍然引用着 object ,所以调用object.getNameFuce()() 就返回了 “My Object”。
下面几种特殊情况,this 的值也可能发生意外的改变。
1 2 3 4 5 6 7 8 9 10
var name = "The Window"; object = { name: "My Object", getName: function() { returnthis.name; } }; console.log(object.getName()); // My Object console.log((object.getName)()); // My Object console.log((object.getName = object.getName)()); // The Window 非严格模式下
第一行是代码正常调用。第二行(object.getName)()调用 和object.getName() 是一样的。我们看一下第三行,先执行一条赋值语句,再调用赋值后的结果。因为这个赋值表达式的值是函数本身所有 this 的值不能得到维持,结果就返回了”The Window”。 当然我们在开发的时候,不会用第二行和第三行代码,不过,这个例子有助于说明即使是语法的细微变化,都有可能意外的改变 this 值。
坑点3:内存泄露问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
functionshowId() { var el = document.getElementById("app") el.onclick = function(){ aler(el.id) // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放 } }
// 改成下面 functionshowId() { var el = document.getElementById("app") var id = el.id el.onclick = function(){ aler(id) } el = null// 主动释放el }
functionfactorial(num) { if (num <= 1) { return1; } else { return num * factorial(num - 1); //会产生耦合 } } var f = factorial; factorial = null; console.log(f(3)); //报错 1.html:2030 Uncaught TypeError: factorial is not a function
以上代码先把 factorial() 函数保存到变量 f 中,然后将 factorial 变量设置为null。结果指向原始函数的引用就只有一个 f ,但接下来调用 f() 时,由于必须执行 factorial() ,而 factorial 已经不再是函数了,所以就会导致错误,在这种情况下可以使用 arguments.callee() 可以解决这个问题。
1 2 3 4 5 6 7 8 9 10
functionfactorial(num) { if (num <= 1) { return1; } else { return num * arguments.callee(num - 1); } } var f = factorial; factorial = null; console.log(f(3)); //6
functionoutputNumber(count) { (function() { for (var i = 0; i < count; i++) { console.log(i); } })(); console.log(i); // 报错 Uncaught ReferenceError: i is not defined } outputNumber(10);