关于闭包的定义
闭包是指有权限访问另一个函数作用域中变量的函数
执行完的执行上下文被弹出栈,其词法环境处于失联状态,外部不能直接访问,在这种情况下还保留了其词法环境的引用,通过引用能访问其词法环境,这个引用就是闭包
一个函数和对其周围状态(词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包.也就是说,闭包可以让你在一个内层函数访问其外层函数的作用域.(出自MDN)
例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function foo ( ) { var a = 2 ; function bar ( ) { console .log(a); } return bar; } var baz = foo();baz(); foo()()
正常情况下,foo执行结束,出栈,其词法环境失联,不能访问. 每个函数创建执行上下文,都会创建新的词法环境,其中包括一个scope(对外层词法环境的引用)会保存对上层词法环境的引用。bar创建时,词法环境中就会有scope指向foo词法环境.因此baz能通过引用访问foo词法环境,拿到变量.
闭包应用 结合工厂函数完成一些业务逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function makeAdder (x ) { return function (y ) { return x + y } } let add5 = makeAdder(5 )let add10 = makeAdder(10 )add5(1 ) add10(1 ) function changeSize (size ) { return function ( ) { document .body.style.fontSize = size + 'px' } } let size12 = changeSize(12 )let size14 = changeSize(14 )let size18 = changeSize(18 )document .getElementById('size-12' ).onclick = size12;document .getElementById('size-14' ).onclick = size14;document .getElementById('size-16' ).onclick = size16;
模拟实现私有方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var count = (function ( ) { var num = 0 function changeNum (val ) { num += val } return { add: function (v ) { changeNum(v) }, dec: function (v ) { changeNum(-v) }, val: function ( ) { return num } } })() count.add(2 ) count.val() count.dec(1 ) count.val()
循环中的闭包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 var fn = []for (var i=0 ; i<3 ; i++) { fn[i] = function ( ) { console .log(i) } } fn[0 ]() fn[1 ]() fn[2 ]() for (var i=0 ; i<3 ; i++) { fn[i] = (function (num ) { return function ( ) { console .log(num) } })(i) } fn[0 ]() fn[1 ]() fn[2 ]()
作用域&链 作用域 决定了一段代码能够访问到哪些数据,这些数据存放在词法环境对象内。
全局,函数,块级作用域 es6之前,作用域只有两个,不存在块级作用域
es6之前,只有两种声明变量的方法
var,function存在变量提升并且能覆盖同名变量,造成很多不合理场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 var a = 1 function fn ( ) { a = 2 console .log(a) } fn() console .log(a) var a = 1 function fn ( ) { console .log(a) if (false ) { var a = 2 } } fn() console .log(a)
es6出现块级作用域 声明方式增加了let,const,class,import,这些声明方式都会形成块级作用域,不会绑定到全局,同时支持{}形式,形成块级作用域(函数{}不算) 现代浏览器var,function声明不受{}块级限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 let a = 1 { let a = 2 console .log(a) } console .log(a) console .log(window .a) { var a = 1 let b = 1 } console .log(a) console .log(b) function test ( ) { var foo = 33 ; { let foo = (foo + 55 ); } } test();
作用域链 当执行一个函数时,引擎会将函数的执行环境推入执行栈并生成作用域链。
作用域链的顶部永远是当前环境的词法环境对象,下一个词法环境对象来自于包含环境,下下个词法环境对象来自于包含环境的包含环境。作用域链的尾部是全局词法环境对象。
作用域链是一个链表的形式。当我们在一个函数内部调用某一个变量时,会从作用域链顶部开始查找。如果在当前执行环境内存在定义则返回,否则会在当前环境的词法环境组件中引用的外部环境引用中查找。然后重复前面的逻辑,直到找到全局词法环境对象(外部引用环境为null)如果仍然未找到会抛出异常。