你不知道的JS(上卷)笔记——作用域,作用域链,调用栈,闭包总结

2019-11-13  本文已影响0人  李牧敲代码

作用域

1. 定义

可访问变量规则的集合

2. 作用
  1. 解决命名冲突的问题。
  2. 隐藏私有属性。
3. 分类

1.1 按照作用的范围可以分为全局作用域局部作用域(函数作用域)块级作用域(由const和let等形成)。
1.2 eval和with等欺骗词法作用域的手段不是最佳实践。

4. 其他
(function f1() {
    return 123;
})()
//或者
(function() {
    return 123
}())

其中函数可以使具名函数也可以是匿名函数
使函数声明转变为函数表达式可以在 function 前面加上!,+,-,=等符号

var f1 = function() {....}
        'use strict';

        function foo() {
              //'use strict';//或者写这里
            console.log(this.a)
        }

ps: 这里顺带说个js一个执行机制:

作用域链

自由变量:

定义:当我们访问一个在当前作用域没有申明的变量时,该变量就是一个自由变量。

作用域链:

定义: 当我们在当前作用域查找不到一个自由变量的定义时,我们会从其父级作用域查找,直到全局作用域。我们将这条查找的链路称为作用域链。

这里要注意的是js的作用域指的是词法作用域也就是静态作用域,即作用域在函数定义的时候就确定了,而不是执行的时候确定。


var scope = 'global scope'

function fn() {
    var scope = 'local scope';
    
    return function() {        
        console.log(scope)
    }
}


fn()()//local scope

这里穿插一个有意思的操作符()

let obj = {
    a: function() {
        console.log(this.a)
    }
};

obj.a();//[Function a]

(false || obj.a)();//undefined,因为()返回一个表达式,相当于,obj.a赋值给了圆括号。


console.log((1,2,3))//3。圆括号将最后一个表达式的值返回。

console.log([1,2,3][1,2])//3。相当于[1,2,3][(1,2)] => [1,2,3][2]

调用栈

JavaScript 是解释型语言。js的执行分为解释和执行阶段:

解释阶段:

执行阶段:

解释阶段我们前面讲过了,这里说下执行阶段创建上下文的时候就会涉及到调用栈。这里注意一下顺序:
确定作用域 => 创建执行上下文=>执行函数代码

结合具体例子:


var scope = "global scope";
function checkscope(){
    var scope2 = 'local scope';
    return scope2;
}
checkscope();




// 1. 创建函数 checkscope,保存作用域链到内部[[scope]]

// checkscope.[[scope]] = [

//     globalContext
// ]

// 2.  创建checkscope的执行上下文,这个执行上下文是一个对象,里面包含作用域链(scope), 活动对象(active-object);而活动对象是用arguments创建的。

// checkscope-context = {
//     ['active-object']: {
//         arguments: {
//             length: 0
//         },
//         scope2: undefined
//     }
//     [[scope]]: [ checkscope-context['active-object'], checkscope.[[scope]]]
// }

// 3. 将创建的checkscope执行上下文压入js上下文执行栈。

// EsStack = [
//     checkscope-context,
//     global-context
// ]

// 4. 开始执行js上下文执行栈
// checkscope-context = {
//     ['active-object']: {
//         arguments: {
//             length: 0
//         },
//         scope2: 'local scope';
//     }
//     [[scope]]: [ checkscope-context['active-object'], checkscope.[[scope]]]
// }

// 5. 函数出栈(垃圾回收)
// EsStack = [
//     global-context
// ]


image

闭包

1. 定义

当一个函数的词法作用域覆盖了另外一个函数的词法作用域的时候就形成了闭包,闭包是一种状态。
简而言之就是一个函数可以读取另外一个函数内部变量的状态就是闭包。

2. 作用(优点):
  1. 封装私有属性
  2. 变量常驻内存,随时访问(比如用于构造模块)
  3. 避免命名冲突
3. 缺点:
  1. 消耗内存
  2. 容易造成内存泄漏(比如在mvvm框架构建的单页应用中如果在某个页面初始化了一个第三方库比如jquery,如果在组件的生命周期的销毁阶段没有销毁初始化的第三方库 就造成了内存泄漏导致页面变卡)

下面的一段代码内存泄漏和误修改父函数内部变量的一个例子(可以加深理解)

        (function f1(window) {
            var a = 1;

            function f2() {
                a = a + 1;
                console.log(a)
            }

            function f3() {
                a = a - 1;
                console.log(a)
            }
            window.f2 = f2;
            window.f3 = f3;
        })(window)
        setInterval(function() {
            f2()
        }, 1000)
        setInterval(function() {
            f3()
        }, 1000)

        function f1(window) {
            var a = 1;

            function f2() {
                a = a + 1;
                console.log(a)
            }

            function f3() {
                a = a - 1;
                console.log(a)
            }
            return {
                f2,
                f3
            }
        }
        let f2 = f1().f2; //新分配了一片内存
        let f3 = f1().f3 //新分配了一片内存

        setInterval(function() {
            f2()
        }, 1000)
        setInterval(function() {
            f3()
        }, 1000)

第一段代码中,f2和f3分别都对内存中同一个a进行了操作,
第二段代码中, f2,f3声明时同时分配了一片一样的内存,但是实际上我们可能不需要。

(完)

参考文献

深入理解JavaScript作用域和作用域链
JavaScript深入之作用域链

上一篇下一篇

猜你喜欢

热点阅读