作用域链与闭包

本文最后更新于:2022年5月31日 上午

作用域与作用域链

作用域:变量可以起作用的范围

  • 全局作用域

    变量在函数外的当前global顶层声明

    全局变量:在任何地方都可以访问到的变量,关闭网页或浏览器才会销毁。顶层

    听过var声明的全局变量会直接挂载在Global对象上,let和const则不会,但他们效果是相同的。

  • 函数作用域(局部作用域)

    变量在函数内声明

    局部变量:只在固定的代码片段内可访问到的变量,在函数开始执行时创建,函数执行完后局部变量会自动销毁。

  • 块级作用域(es5)

    任何一对花括号{}也会生成一个独立的作用域。

  • 词法作用域

    js的作用域范围在书写时就已经确定,不受代码执行的动态影响。(静态作用域)

  • 自由变量

    当前作用域没有定义的变量,这成为自由变量(既不是局部变量也不是函数参数)

    • 自由变量会向父级作用域中寻找
    • 自由变量从作用域链中去寻找,依据的是函数定义时的作用域链,而不是函数执行时的作用域

作用域链

  • js中的函数在创建时会创建一个变量对象[scope]保存当前作用域中的所有函数与变量;

  • js中的代码在执行时会创建执行上下文,而执行上下文中的[scope chain] 保存了所有父级作用域的中变量对象的引用,随着函数的执行,会将当前的活动对象链接到[scope chain]的最前端。

  • 函数的活动对象初始只有arguments,变量的查找会从arguments开始循着scope chain的顺序依次往外部查找变量标识符号,直到全局作用域。

闭包

  • 函数与其词法环境共同构成 闭包(closure)
  • lexical environment词法环境 = 代码内变量标识符与值之间的关联关系(环境记录[[Environment]])+ 对外部词法环境的引用
  • new操作函数的[[Environment]]指向全局对象,所以只能显式传递参数
  • js中的函数声明时天然闭包(可以访问外部外部词法环境)

闭包的用途

  • 实现数据(变量和方法)私有化

  • 函数柯里化

    function curry(func,...args1){
      const fnLen = func.length
      const argsArr = [...args1]
      function _curry(...args2){
        argsArr = [...argsArr,...args2]
        return argsArr.length >= fnLen ? func.apply(func,argsArr) : _curry
      }
      return _curry
    }
    
    // 传世经典累加函数add
    function add(...args1){
      let res = [...args1].reduce((a,b)=>a+b)
      function innerAdd(...args2){
        res = [...res,...args2].reduce((a,b)=>a+b)
        return innerAdd
      }
      innerAdd.toString = () => res
      // 打印方法会调用toString
      return innerAdd
    }

效果

function addCounter() {
  let counter = 0; // 执行结束后不会被清除
  const myFunction = function () {
    counter = counter + 1; // myFunction函数可以读取add函数内部的变量
    return counter;
  };
  return myFunction;
}

const increment = addCounter();
const c1 = increment();
const c2 = increment();
const c3 = increment();
console.log("increment:", c1, c2, c3);
// increment: 1 2 3

面试题

  • 老生常谈的for循环setTimeout打印问题
    for (var i = 1; i < 7; i++) {
      setTimeout(function () {
        console.log(i)
      }, 1000 * i)
    }
    // 6 6 6 6 6 6
    // 创建的6个setTimeout闭包共享一个词法作用域
    
    **闭包只能取得包含函数中任何变量赋值最后一个值** // 6
    
    
    for (var i = 1; i < 7; i++) {
      ((j) => {
        setTimeout(function(){
          console.log(j)
        }, 1000 * j)
      })(i)
    }
    // 1 2 3 4 5 6
    // 6个setTimeout闭包有自己独立的词法环境
    // 闭包读取到不同的i值
    
    
    for(var i = 1 ;i<7;i++){
      setTimeout(function(j){
        console.log(j);
      },1000*i,i)
    }
    // setTimeout 可以接受一个参数当函数调用时传入
    
    for(let i = 1 ;i<7;i++){
      setTimeout(function(){
        console.log(i);
      },1000*i)
    }
  • function fn(num2) {
      var num = 15;
      function abc() { // 函数作为返回值
        num++;
        return(num + num2);
      }
      return abc;
    }
    var aa = fn(20); //  没有引用??
    aa(); //16+20
    aa(); //17+20
    fn(20)(); //36
    fn(20)(); //36

作用域链与闭包
http://yoursite.com/2022/02/24/作用域链与闭包/
作者
tatekii
发布于
2022年2月24日
许可协议