闭包是指有权访问另外一个函数做用域中的变量的函数。一般是在一个函数的内部建立另外一个函数,那个函数则被称为闭包。这个概念很难弄懂,我到如今也是只知其一;不知其二,可是有几点是确定的:想要理解什么是闭包咱们就要知道建立函数开始到函数被执行,这一过程当中到底发生了什么?这之中又涉及到词法做用域。git
词法做用域是一个比较好理解的概念。它是由编译器在编译阶段就能够肯定的事情。全局有个全局做用域,每定义一个函数,编译过程当中就建立一个函数做用域。 而咱们熟知的做用域链就是一层套一层的做用域构成的。这里咱们须要知道一点:做用域(链)的做用是在运行阶段为引擎找到存在的变量(等,以上是我本身的理解)github
function bar (name) {
var age = 23
return function () {
alert(name, age)
}
}
bar('Nicholas') // Nicholas 23
复制代码
以上代码,bar 函数中有一个 匿名函数 这个匿名函数能够访问到 bar 函数中的变量。其实更明显的作法是下面这样:缓存
function bar ...
var fn = bar('Nicholas')
fn() // Nicholas 23
复制代码
上面这段代码,咱们取得 bar 函数中匿名函数的引用后,垃圾处理机制并未销毁 bar 函数的活动对象,而让匿名函数获取到了 bar 函数的做用域,这就是闭包的用处安全
值得注意的是,闭包无处不在,同时闭包与词法做用域息息相关,而次发做用域是在为引擎执行阶段 获取变量引用,要理解这个意思须要理解下面这段经典的一批的代码:bash
function createFn () {
var fns = []
for (var i = 0; i < 10; i++) {
fns.push(function () {
console.log(i)
})
}
return fns
}
var fns = createFn()
fns.forEach(function (fn) {
fn()
}) // 10 个 10 啦
复制代码
答案也没啥稀奇,可是这中间到底发生了什么?涉及到做用域的概念:做用域其实包含了执行环境的变量对象,而函数的做用域的变量对象就是函数的活动对象,而活动对象就是包含了函数的参数、定义的变量等。 而咱们又知道 js是不存在块级做用域 的,所以在 createFn 函数中 for 循环中定义的 i 变量,其实保存在 createFn 的函数做用域的变量对象中,所以当定义完了10个函数以后,i 也变成了 10,再调用 函数的时候,会访问 i,而匿名函数中没有 i,就向上搜索到 createFn 的做用域,这里有 i,输出 i 就输出了 10。闭包
做用域链,就是一堆做用域连起来啦,没啥好稀奇的。在下一节以前我要强调的是:词法做用域是词法做用域,this 是 this,不要试图在理解 this 的时候,带入词法做用域的知识app
以上代码如何输出 0 ~ 9?咱们再建立一个闭包,缓存 i 的值而后让闭包访问到就能够了。理解了这个,闭包就差很少理解了。函数
function createFn () {
var fns = []
for (var i = 0; i < 10; i++) {
fns.push(function (i) {
return function () {
console.log(i)
}
}(i))
}
return fns
}
var fns = createFn()
fns.forEach(function (fn) {
fn()
})
复制代码
闭包能够用来作什么?其实能够作不少事情。从事情的最开始来说吧。ui
- 若是全部的变量都在全局定义,会怎么样?会没办法定义同名变量,会让代码变得杂乱无章,会不知道代码在作什么。因而有了函数,咱们使用函数包装起一段代码,这样定义在这段代码里的变量,根据词法做用域的规则,全局是没办法访问到函数里的变量的。
- 既然定义了一个函数,为何会有闭包出现?其实闭包彻底能够定义成另外一个函数扔在全局中,可是颇有可能一个函数会改变另外一个函数之中值(经过调用函数的方式,而非直接改变,由于词法做用域的关系,一个函数若是不是闭包绝对没有办法访问到另外一个函数的做用域的), 这就很危险了,咱们不肯意让一些咱们不知道的方式引用了一个函数,而改变另外一个函数,因而咱们把一个函数从全局定义扔到了另外一个函数中去,这样既减小了全局做用域中的变量个数,又更加安全。这就是闭包的做用。
- 闭包的好处:能够访问另外一个函数的做用域!这看来好像没什么特别的,可是这偏偏就是闭包最厉害的地方:若是一个函数须要依赖一些其余函数怎么办?咱们彻底能够将全部的依赖函数统一管理。因而咱们能够经过闭包写一个本身的模块依赖机制!
function ModuleManager () {
// 存放定义的模块
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 {
define: define,
get: get
}
}
var myModules = ModuleManager()
myModules.define('bar', [], function () {
return {
hello: function (who) {
return 'Hello, I am ' + who
}
}
})
myModules.define('foo', ['bar'], function (bar) {
var name = 'Nicholas'
return {
awesome: function () {
console.log(bar.hello(name).toUpperCase())
}
}
})
var bar = myModules.get('bar')
var foo = myModules.get('foo')
console.log(bar.hello('Bob'))
foo.awesome()
复制代码
为何以上代码没有使用原型对象,类的方式去写,就是为了展现闭包的魔力:modules 能被 define 函数和 get 函数时刻访问到,因而咱们就获得了一个模块管理的入口,这就是闭包的一个做用,咱们能够本身写出不少关于闭包的东西,咱们要拥抱闭包,而不是害怕它原理它。this
持续跟新在github