本质与解析
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
function outter() {
var a = 1
function inner() {
console.log(a)
}
return inner
}
var accept = outter()
accept() // 1
在此例中,将内部函数inner作为返回值,当outter函数执行后,赋值给accept,并且调用accept;实际上调用了inner函数,它在自己定义的词法作用域以外的地方执行。
通常在outter函数执行完成之后,由于js的垃圾回收机制,其内部的作用域会被销毁回收,但是由于闭包的存在,accept持有对inner的引用,而inner声明的位置为outter的内部作用域,因此该作用能够一直存活
循环与闭包
面试常见的闭包考题就是循环闭包。例如:
for (var i=0; i<=8; i++) {
setTimeout(function () {
console.log(i)
},1000)
}
正常情况下期待每隔一秒输出1~8,但是实际每秒输出一个9,其实不难理解,从执行栈来看,延迟函数会在整个for循环执行完成之后才会执行,即使setTimeout设置的延迟时间是0,同时其被封闭在全局的作用域中的,因此每次得到的都是对全局i变量的引用,实际上只有一个i。
可以通过立即执行函数将作用域封闭起来,同时通过自己定义一个变量j在每次迭代中存储i的值来解决这个问题,代码如下:
for (var i=0; i<=8; i++) {
(function () {
var j = i
setTimeout(function () {
console.log(j)
},1000)
})(i)
}
还有更加简单的解决方案,可以通过let声明来劫持块作用域,并且在这个块作用域中声明一个变量,代码如下:
for (let i=0; i<=8; i++) {
setTimeout(function () {
console.log(i)
},1000)
}
for循环头部的let声明会有一个特殊行为,这个行为指出变量在循环的过程中不止被声明一次,每次迭代都会声明,随后每次迭代都会使用上一个迭代结束时的值来初始化这个变量,这就意味着每次循环都会声明一个新的i,且会将上一个迭代结束时的值赋给i
模块模式与闭包
模块模式实例:
function module() { //封闭函数
var arr = [1,2,3]
function one() {
console.log(arr.join(''))
}
function another() {
arr.push(4)
console.log(arr.join(''))
}
return {
one: one, //内部函数
another: another
}
}
var accept = module()
accept.one() //123 创建一个新的模块实例
accept.another() //1234 创建一个新的模块实例
解析:module是一个函数,需要通过调用它来创建一个模块实例。其返回一个{key:value}形式的对象,这个对象中包含内部函数的引用,将其暴露提供调用,且module内部数据变量是私有隐藏状态,这个对象返回值本质上是这个模块的公共API
分析得出模块模式需要两个必备条件:
- 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)
- 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态
模块机制
var MyModules = (function Manager() {
var modules = {};
function define(name, deps, impl) {
for (var i=0; i<deps.length; i++) {
deps [i] = modules[deps[i]]
}
modules [name] = impl.apply(impl, deps)
}
function get(name) {
return modules[name]
}
return { //公共API函数
define : define,
get : get
}
})()
MyModules.define("one", [], function () {
function hello(who) {
return "hello" + who
}
return {
hello : hello
}
})
MyModules.define("another",["one"], function (one) {
var name = 'world'
function output() {
console.log(one.hello(name))
}
return {
output : output
}
})
var another = MyModules.get('another')
another.output()
'one'和'another'两个模块都是通过公共API函数来定义,并且'another'接受'one'的实例作为参数注入,这就是模块之间可以存在依赖关系
以上内容是个人的一点总结,如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞收藏
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。