I figure life is a gift and I dont intend on wasting it. You never know what hand youre going to get dealt next. You learn to take life as it comes at you.
I figure life is a gift and I dont intend on wasting it. You never know what hand youre going to get dealt next. You learn to take life as it comes at you.
对于那些有一点JavasScript使用经验但从未真正理解闭包概念的人来说,理解闭包可以看做某种意义上的重生,但是需要付出非常多的努力和牺牲才能理解这个概念。闭包是基于词法作用域书写代码时所产生的自然结果,你甚至不需要为了利用他们而有意识地创建闭包。闭包的创建和使用在你的代码中随处可见。你缺少的是根据你自己的意愿来识别、拥抱和影响闭包的思维环境。
JavaScript引擎通过执行上下文来跟踪代码,基于单线程的执行模式,通过执行上下文栈,来确定执行顺序。
JavaScript代码有两种类型:
全局代码 在所有函数外部定义,对应全局执行上下文,只有一个;
函数代码 在函数内部定义,对应函数执行上下文,每次调用函数,就会创建一个新的。
作用域就是变量的合法使用的范围。作用域是JavaScript引擎内部用来跟踪标识符与特定变量之间的映射关系,也叫词法环境。
let a = 0
function fn1(){
let a1 = 100
function fn2(){
let a2 = 200
function fn3() {
let a3 = 300
return a + a1 + a3
}
fn3()
}
fn2()
}
自由变量 一个变量在当前作用域没有定义,但被使用了,就会成为自由变量。
寻找自由变量就要去父级作用域中去寻找,直到找到全局作用域,如果没有就报错xx is not defined
。层层嵌套的作用域就会形成作用域链。作用域应用的特殊情况,有两种表现:
// 函数作为返回值
function create() {
const a = 100
return function () {
console.log(a)
}
}
const fn = create()
const a = 200
fn()
```
调用fn时,执行create函数,打印a,此时在crate作用域内寻找到a,输出100。
// 函数作为参数被传递
function print(fn) {
const a = 200
fn()
}
const a = 100
function fn() {
console.log(a)
}
print(fn)
当调用print是,输入fn,执行fn()打印a,这时候应该去定义fn的作用域去寻找a,此时a没有,则往父级作用域找到a,输出100。
总结:所有自由变量的查找,都是在函数定义的地方开始查找,不是在执行的地方。
闭包是有权访问另一个函数作用域中的变量的函数。当函数可以记住并访问所在的词法作用域是,就产生了闭包,即使函数是在当前词法作用域之外执行的。闭包封闭的是外部状态,当外部状态的cope消失的时候,通过闭包还能访问到原始作用域(我自己感觉闭包更像一个空间,里面包括函数和变量)。
闭包三个条件:
闭包存在于定义该变量的作用域中。
var outerValue = 'ninja'
function outerFunction(){
assert(outerValue === 'ninja', 'I can see the ninja')
}
outerFunction()
在同一作用域中声明outerFunction
和outerValue
,outerFunction
可以看见并访问outerValue
,这个闭包的例子优势不明显。 每一个通过闭包访问变量的函数都具有一个作用域链,作用域链包含闭包的所有信息。
使用闭包时,所有的信息都会存储在内存中,直到JavaScript引擎确保这些信息不再使用或页面卸载时,才会清理这些信息,所以不能滥用闭包,否则会影响网页性能,造成内存泄漏。
当不需要使用闭包的时候,要及时释放内存,可将内层函数中对象的变量赋值为null
。
使用闭包可以:
原生JavaScript不支持私有变量,通过使用闭包可以实现很接近的、可接受的私有变量。
// 闭包隐藏数据,只提供 API
// 操作数据只能通过set和get来操作。
function createCache() {
const data = {} // 闭包中的数据,被隐藏,不被外界访问
return {
set: function (key, val) {
data[key] = val
},
get: function (key) {
return data[key]
}
}
}
const c = createCache()
c.set('a', 100)
console.log( c.get('a') )
回调函数是需要在将来不确定的某一时刻异步调用的函数。回调函数需要频繁地访问外部数据。 使用闭包可以减少全局变量,减少传递函数的参数量。
for (var i = 0; i < 10; i++) {
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
}
我们的预期目标是点击哪一个数字弹出相应的数字,现在是无论点击哪一个数字都会弹出10。
原因是因为鼠标点击事件的回调会在循环结束时才执行,此时i是10
当使用var的时候i是定义在全局作用域的,每次循环时捕获的i都是一个i。所以我们需要每次循环时都有一个闭包作用域,就把i定义到每次循环的里面。
正确的方式是:把var i改变为let i。
for (let i = 0; i < 10; i++) {
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
}
评论没有加载,检查你的局域网
Cannot load comments. Check you network.