作用域链与闭包
本文最后更新于: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/作用域链与闭包/