QQ群招募中:646258285(招募中,没几个人说话),
需要交流的朋友可以直接加我微信( DntBeliv )或QQ( 1121864253 )
作用域链
和函数的原型一样,当创建一个函数之后,也会为函数对象添加一个属性[[Scopes]]记录他的作用域。该属性时一个栈,应该不可以修改,但是我们可以在调试模式中看到。
[[Scopes]]属性记录了当前作用域与活动对象,在函数运行时会向栈内压入一个元素,标识当前的作用域,等函数运行结束后,会将栈顶的元素弹出,销毁掉当前的作用域。
此外,当我们讨论闭包的时候,[[Scopes]]属性有一个非常重要的特点,那就是他会存储调用函数的作用域。举个例子,A函数内创建了函数B,那么B的[[Scopes]]就会有A函数作用域。此外,所有函数都会包括global作用域。我们也可以猜到,表示函数运行时作用域的是栈顶元素,而栈顶的属性会屏蔽掉非栈顶相同属性。
我们看个例子,因为无法看到函数运行时的情况,所有这里看不到函数运行时的作用域。这里主要看一下单个函数和函数内创建函数的不同。
function createComparisonFuntion(propertyName) {
return function (obj1, obj2) {
var value1 = obj1[propertyName];
var value2 = obj2[propertyName];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
}
var compareNames = createComparisonFuntion("name");
var result = compareNames({
name: "Zachary",
age: 28
}, {
name: "Nicholas",
age: 29
});
compareNames = null;
这里,compareNames 就是createComparisonFuntion函数内创建的函数,compareNames 的作用域应该包括了reateComparisonFuntion作用域,我们来看一下是不是这样。
可以看到compareNames包括了许多属性,其中有我们之前提到的arguments、caller等,这里主要关注[[Scopes]],可以看到里面有两个元素,第一个元素(也即是栈顶元素)就是它的调用函数createComparisonFuntion,第二个元素是全局作用域。因为函数还没有被调用,所以函数运行时的作用域还没有被创建。最后我们再来看一下createComparisonFuntion的情况。
最后给一个完整的作用域链的图:
闭包
闭包是指有权访问另一个函数作用域中变量的函数。我们利用[[Scopes]]就可以实现闭包。
还是上面那个例子,为什么createComparisonFuntion已经销毁了我们还可以访问到"name"这个参数?这就是因为compareNames的[[Scopes]]还引用着这个参数,因为该部分内存还在使用中,因此不会被垃圾回收机制回收。看下面的证明:
现在来看几个例子:
function createFunction() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
};
}
return result;
}
这段代码希望创建10个不同i值的函数,但实际上所有的i返回都是10。为什么会这样呢?因为返回的10个函数访问的i实际上都是通过闭包访问的createFunction作用域中的活动对象i,只有这一个变量,当createFunction()调用结束后,i就等于10。
var name = "The window";
var obj = {
name: "my object",
getName: function () {
var that = this;
return function () {
return that.name;
};
}
};
//实际上写成console.log(obj.getName()());就行了,为了便于理解,写成这个
console.log((obj.getName())());//"my object"
在使用vue的回调时经常用到that = this,一直不知道为啥,现在清楚了吧,如果直接返回this.name,那么这个this指的是当前作用域,那么它的值取决于这个(被返回的)函数的调用对象。而如果是that,则就是通过闭包访问的前一个函数的作用域。
再来看一个内存泄露的问题:
//element较大,会占用很大内存,不建议这样写
function assignHandler(){
var element = document.getElementById("someEle");
element.onclick = function(){
console.log(element.id);
};
}
//建议这样写
function assignHandler(){
var element = document.getElementById("someEle");
var id = element.id;
element.onclick = function(){
console.log(id);
};
element = null;
}
模仿块级作用域与私有变量
JavaScript中没有块级作用域,看下面这个例子:
for (var i = 0; i < 10; i++) {}
var i;
console.log(i);//10
但是函数类似于块级作用域,我们可以通过闭包模仿块级作用域:
//函数声明后面不能跟圆括号
(function() {for (var i = 0; i < 10; i++) {}})();
console.log(i);//报错:ReferenceError: i is not defined
进一步,我们可以创建具有私有变量的对象:
//函数表达式后面可以跟圆括号
var dingleton = function () {
//私有变量和私有函数
var privateVar = 10;
function privateFun(){
return false;
}
//创建对象
var obj = new Object();
//添加特权/公有属性和方法
obj.publicProperty = true;
obj.publicMethod = function(){
privateVar++;
return privateFun();
}
//返回这个对象
return obj;
}();