js|执行上下文/作用域链/闭包☂️☂️

345 阅读4分钟

一、闭包

1、概念

闭包是指有权利访问另一个函数作用域中变量的函数,创建闭包的一般的形式就是在一个函数内创建另一个函数

标准回答

闭包 一个函数和词法环境的引用捆绑在一起,这样的组合就是闭包(closure)。一般就是一个函数A,return其内部的函数B,被return出去的B函数能够在外部访问A函数内部的变量,这时候就形成了一个B函数的闭包,A函数执行结束后这个A函数的变量也不会被销毁,并且这个变量在A函数外部只能通过B函数访问。

闭包形成的原理:

作用域链,当前作用域可以访问上级作用域中的变量

闭包解决的问题:

能够让函数作用域中的变量在函数执行结束之后不被销毁,同时也能在函数外部可以访问函数内部的局部变量。

闭包带来的问题:

由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出。

加分回答

闭包的应用,能够模仿块级作用域,能够实现柯里化,在构造函数中定义特权方法、Vue中数据响应式Observer中使用闭包等。

2、用途

(1)通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这个方法创建私有变量

(2)使已经运行结束的函数上下文中的变量继续留在内存中。保存变量对象的引用,不会被垃圾回收机制回收

3、经典面试题1

1、面试题

for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}

每隔一秒输出6 ,因为setTimeout是异步函数,setTimeout 执行时,for循环已经结束了,i的值是6

2、优化:使用立即执行函数

for (var i = 1; i <= 5; i++) {
  (function(j) {
    setTimeout(function timer() {
      console.log(j)
    }, j * 1000)
  })(i)
}

使用立即执行函数构建自己的作用域,然后将i值传入立即执行函数,用j保存传入的值,此时值是不变的,然后setTimeout使用j

3、优化:使用setTimeout的第三个参数

for (var i = 1; i <= 5; i++) {
  setTimeout(function timer(j) {
      console.log(j)
    }, i * 1000,i)
}

使用 setTimeout 的第三个参数,这个参数会被当成 timer 函数的参数传入。

4、优化:使用let

for (let  i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}

4、经典面试题2

function test() {
    var n = 4399;
    function add() {
        n++;
        console.log(n);
    }
    return { n: n, add: add }
}
var result = test(); // 设置result的属性{n:4399,add:add()}
var result2 = test(); // 设置result2的属性{n:4399,add:add()}
result.add();  // 调用result的add方法,n是4399,进行n++,n等于4400
result.add(); // 调用result的add方法,进行n++,n是4400,n等于4401
console.log(result.n); // 调用result的属性,n,值是4399
result2.add(); // 调用result2的add方法,n是4399,n++之后,n的值是4400
// 闭包的定义:在A函数中定义B函数,在B函数中引用了A中的变量,就会产生闭包。

5、几种闭包形式

1、返回值是函数

/**
 * 闭包
 */
function makeFun() {
    var name = "tom";
    function displayName() {
        console.log(name);
    }
    return displayName;
}
var myFunc = makeFun();
myFunc();

2、立即执行函数

/**
 * 立即执行闭包(2次立即执行函数的写法)
 */
(function fout() {
    const a = 1;
    function fin() {
        console.log(a);
    }
    return fin;
})()(); // 1

3、闭包实现加法

/**
 * 闭包
 */
function makeAdder(x) {
    return function (y) {
        return x + y;
    }
}
var makeA = makeAdder(5);
var makeB = makeAdder(7);
console.log(makeA(2));
console.log(makeB(2));

6、闭包的使用案例

1、闭包,通过点击链接,实现改变body的字体大小

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <a href="#" id="size-12">12</a>
    <a href="#" id="size-14">14</a>
    <a href="#" id="size-16">16</a>
    <script>
        function makeSize(size) {
            return function () {
                document.body.style.fontSize = size + 'px';
            }
        }
        document.getElementById('size-12').onclick = makeSize(12);
        document.getElementById('size-14').onclick = makeSize(14);
        document.getElementById('size-16').onclick = makeSize(16);
    </script>
</body>

</html>

20210828174708.gif

2、闭包模拟私有方法

/**
 * 闭包模拟私有方法
 */
var makeCounter = function () {
    var privateCounter = 0; //私有变量
    function changeBy(val) { //私有变量
        privateCounter += val;
    }
    return {
        increment: function () {
            changeBy(1);
        },
        decrement: function () {
            changeBy(-1);
        },
        value: function () {
            return privateCounter;
        }
    }
}
// console.log(makeCounter().value()); // 0
var count1 = makeCounter();
var count2 = makeCounter();
console.log(count1.value()); //0
count1.increment();
console.log(count1.value()); //1
console.log(count2.value()); // 0
count2.increment();
count2.increment();
console.log(count2.value()); //2

匿名函数创建了2个私有属性:变量privateCounter和函数changeBy 这两项无法在外部环境中进行访问,所以通过匿名函数返回3个公共函数访问 这3个函数共享同一环境的闭包 2个计数器count1和count2的环境是相互独立的 在count1的闭包环境中对变量做的改变不会影响到另一个闭包环境中的变量

二、对作用域、作用域链的理解

一、作用域

1、全局作用域

最外层函数和最外层函数外面变量拥有全局作用域

未声明就赋值的变量

window对象的所有属性

2、函数作用域

函数内部定义的变量是函数作用域

3、块级作用域

let const 定义的变量是块级作用域

二、作用域链

访问一个变量,当前作用域找不到会去上一层作用域查找,找的到会返回,到全局作用域都没有找到就会报错 作用域链是分层的,内层的作用域可以访问外层的作用域的变量、方法;外层的作用域无法访问内层的

三、执行上下文的类型

1、全局执行上下文

不在函数内部的都是全局执行上下文,首先会创建一个window对象,设置this等于window,一个程序只有一个全局上下文

2、函数执行上下文

当一个函数被调用的时候,会为该函数创建一个函数执行上下文,可以有多个

3、eval执行上下文

没用过

四、执行上下文栈

js引擎使用执行上下文栈管理执行上下文,js执行代码的时候,首先遇到全局代码,创建全局执行上下文并压入栈,遇到调用函数,会创建函数执行上下文压入栈顶,js引擎会执行栈顶函数,执行完弹出,继续执行下一个函数上下文,当所有代码执行完,弹出全局执行上下文

五、执行上下文过程

1、创建阶段

将代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好。编译阶段完成好,才会进入执行阶段

2、执行阶段

对执行上下文的运行