Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >重学JS基础-作用域链和闭包

重学JS基础-作用域链和闭包

作者头像
Jou
发布于 2022-08-10 12:55:05
发布于 2022-08-10 12:55:05
61600
代码可运行
举报
文章被收录于专栏:前端技术归纳前端技术归纳
运行总次数:0
代码可运行

一,作用域和作用域链

1.全局作用域

JS有一个全局对象,window,在全局声明的变量都属于window的属性,未使用声明符声明的属性也是window的属性。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

2.函数作用域

我们在定义函数的时候,函数会默认存在一个叫scope的隐式属性,即,保存着函数定义时的信息,这个属性指向一个数组,数组中存的则是一组链式的函数执行上下文。数组的第一项就是函数自身的作用域。

假如我们要访问一个属性,就在这个域中按顺序寻找。所以下面的代码只能打印出b的值,因为a在函数定义的时候并未定义。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var b = 2; 
function foo() { 
    console.log(b); 
    console.log(a); 
} 
(function () { 
    var a = 1; 
    foo(); 
})();

//2
//error

3.作用域链

上面说到函数有一个属性指向一个链式的执行上下文,最下层是函数自己的作用域,而再往上就是父级作用域,最后到达window作用域。

因为在函数被定义的时候,会直接拥有父级模块的作用域,比如在window中被定义的函数,会直接拥有window的作用域。

在函数执行的时候,假如访问某个属性在当前函数中没有,就会在链式的执行上下文中寻找。

看一下例子

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var a=10
function fun1(){
    var b=20;
    function fun2(){
        //...
    }
    fun2();
}
fun1();

如上,通过预编译和作用域链来解读一下代码运行的具体步骤

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的作用域中寻找,最后也无法找的变量的话,则会抛出错误。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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();

所以函数执行结果为

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
111
0

二.闭包

当内部函数被保存到外部时,将会生成闭包。生成闭包后,内部函数依旧可以访问其所在的外部函数的作用域。

1.原理

在内部函数被定义的时候会创建一个属于内部函数的scope属性保存着的作用域链,它会直接继承父函数的作用域链.

当它有对父级函数的变量的访问时,这个作用域链在父级函数销毁时不会被销毁,此时内部函数依旧可以访问父级函数的变量。

2.避免闭包的方法

(1)立即执行函数
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(function(){ 
    var a; 
    //code
}());

(function(){
    var a; 
    //code
})();

但是,括号有个缺点,那就是如果上一行代码不写分号,括号会被解释为上一行代码最末的函数调用,产生完全不符合预期,并且难以调试的行为,加号等运算符也有类似的问题。

另一种写法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void function () {
    var a = 100;
    console.log(a);

}();

语义上 void 运算表示忽略后面表达式的值,变成 undefined

(2)使用const和let

3.闭包的使用

使用闭包实现一个计数器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function counterCreate(){
    var count = 0;
    return function(){
        count++;
        console.log(`计数${count}`);
    }
}
var addCount = counterCreate();
addCount();//计数1次
addCount();//计数2次
addCount();//计数3次
addCount();//计数4次

这里内部函数使用了外部函数的count属性,所以再外部函数销毁之后,count依然可以被内部函数使用,但无法再外部被访问。

4.闭包的优缺点

闭包的好处
  1. 希望一个变量长期存储在内存中
  2. 避免全局变量的污染
  3. 私有成员的存在
  4. 用于缓存闭包的坏处

容易造成内存泄漏

使用闭包定义对象的私有变量
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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;
})();
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-07-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验