JS有一个全局对象,window,在全局声明的变量都属于window的属性,未使用声明符声明的属性也是window的属性。
var a = 10;
b = 10;
function fun(){
c = 10;
var d = 10;
}
fun();
console.log("window.a",window.a); //10
console.log("window.b",window.b); //10
console.log("window.c",window.c); //10
console.log("window.d",window.d); //undefined
我们在定义函数的时候,函数会默认存在一个叫scope的隐式属性,即域,保存着函数定义时的信息,这个属性指向一个数组,数组中存的则是一组链式的函数执行上下文。数组的第一项就是函数自身的作用域。
假如我们要访问一个属性,就在这个域中按顺序寻找。所以下面的代码只能打印出b的值,因为a在函数定义的时候并未定义。
var b = 2;
function foo() {
console.log(b);
console.log(a);
}
(function () {
var a = 1;
foo();
})();
//2
//error
上面说到函数有一个属性指向一个链式的执行上下文,最下层是函数自己的作用域,而再往上就是父级作用域,最后到达window作用域。
因为在函数被定义的时候,会直接拥有父级模块的作用域,比如在window中被定义的函数,会直接拥有window的作用域。
在函数执行的时候,假如访问某个属性在当前函数中没有,就会在链式的执行上下文中寻找。
看一下例子
var a=10
function fun1(){
var b=20;
function fun2(){
//...
}
fun2();
}
fun1();
如上,通过预编译和作用域链来解读一下代码运行的具体步骤
1.首先,全局存在作用域GO,归window所有
2.定义函数fun1时,会继承window的作用域,其scope属性被创建,指向一个链表,其第一项为GO,
即scope(fun1):GO -->
2执行函数fun1时,会生成一个属于fun1的函数执行上下文AO,这是scope第一项为这个AO对象,
即scope(fun1):AO(fun1) --> GO -->
3.执行函数fun1时,在fun1函数体中,由于定义了函数fun2,所以创建fun2的scope属性,直接继承自fun1,
即scope(fun2): AO(fun1) --> GO -->
4.之后在执行函数fun2时,会创建一个专属于fun2的执行上下文,放入,fun2的scope属性的最顶端,
即scope(fun2): AO(fun2) --> AO(fun1) --> GO -->
5,执行完函数fun2后,销毁其作用域
6,执行完函数fun1后,销毁其作用域
假如现在要在函数b中访问一个变量,系统则会到函数b的scope中去寻找,scope是一个数组,它从第0位开始访问,第一位是函数b的作用域,找不到的话会继续想下寻找,即函数a的作用域,
再找不到,便会继续向下,即在window的作用域中寻找,最后也无法找的变量的话,则会抛出错误。
var cc = 123;
function a(){
function b(){
var bb = 234;
aa = 0;
console.log(cc);
}
var aa = 123;
var cc = 111;
b();
console.log(aa);
}
a();
所以函数执行结果为
111
0
当内部函数被保存到外部时,将会生成闭包。生成闭包后,内部函数依旧可以访问其所在的外部函数的作用域。
在内部函数被定义的时候会创建一个属于内部函数的scope属性保存着的作用域链,它会直接继承父函数的作用域链.
当它有对父级函数的变量的访问时,这个作用域链在父级函数销毁时不会被销毁,此时内部函数依旧可以访问父级函数的变量。
(function(){
var a;
//code
}());
(function(){
var a;
//code
})();
但是,括号有个缺点,那就是如果上一行代码不写分号,括号会被解释为上一行代码最末的函数调用,产生完全不符合预期,并且难以调试的行为,加号等运算符也有类似的问题。
另一种写法
void function () {
var a = 100;
console.log(a);
}();
语义上 void 运算表示忽略后面表达式的值,变成 undefined
使用闭包实现一个计数器
function counterCreate(){
var count = 0;
return function(){
count++;
console.log(`计数${count}次`);
}
}
var addCount = counterCreate();
addCount();//计数1次
addCount();//计数2次
addCount();//计数3次
addCount();//计数4次
这里内部函数使用了外部函数的count属性,所以再外部函数销毁之后,count依然可以被内部函数使用,但无法再外部被访问。
容易造成内存泄漏
var Person = (function () {
var privateData = {},
privateId = 0;
function Person(name) {
Object.defineProperty(this, "_id", { value: privateId++ });
privateData[this._id] = {
name: name,
};
}
Person.prototype.getName = function () {
return privateData[this._id].name;
};
return Person;
})();
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有