产生闭包的条件
闭包的解释看这里
【要知道的】:
闭包是基于词法作用域书写代码时所产生的自然结果。因此闭包的创建和使用在我们的代码中随处可见。而我们只需要根据自己的意愿来识别并拥抱它就可以啦
词法作用域:它是定义在词法阶段的作用域。换句话说,它是由我们在写代码时,将变量和块作用域写在哪里的位置决定的
function foo(){
var a = 2
function bar(){
console.log(a)
}
return bar // 将内部函数保存到外部
}
var baz = foo()
baz() // 2 调用内部函数,产生了闭包
- 执行foo时:定义bar并将它保存到外部变量baz中
- 执行baz,相当于执行bar函数,因为bar函数中使用到了foo作用域中的变量a,因此它持有外层作用域的引用
- 当foo执行完之后,本应立即销毁他自己创建的作用域,但由于这个作用域同时也被baz这个外部变量所持有,因此不可以被销毁
function foo(){
var a = 2
function baz(){
console.log(a)
}
bar(baz) // 将内部函数保存到外部,并调用
}
function bar(fn){
fn(); // 呀呀,这就是闭包
}
foo()
var fn // 全局变量
function foo(){
var a = 2
function baz(){
console.log(a)
}
fn = baz // 将内部函数保存到全局变量上
}
function bar(fn){
fn(); // 呀呀,闭包
}
foo() // 主要是 fn = baz
bar() // 主要是 fn()
因为baz函数中使用到了外层作用域即foo中的数据。一旦在外部执行bar(),baz便具有覆盖foo作用域的闭包
什么是闭包
当多个函数之间发生嵌套时,如果内层函数引用到了外层函数内的数据。此时调用外层函数,并将内层函数的引用保存到外部便产生了闭包。此时内层函数便具由涵盖外层函数作用域的闭包
发生在外层函数执行导致内层函数的定义,且内层函数中还必须得使用到外层函数的数据
当然,如果想要生成的闭包有意义,还需要在外层函数执行的时候将内层函数的引用保存到外部。否则就算产生了闭包,他也是保存在外层函数的作用域里,一旦外层函数执行完,它的作用域没人引用就会成为垃圾被销毁,此时刚形成的闭包也跟着被销毁了
循环和闭包
for(var i = 1; i <= 5; i++) {
setTimeout(function timer(){
console.log(i)
},1000)
}
每秒一次,每次一个,输出6
for(var i = 1; i <= 5; i++) {
(function(){ // 立即执行函数,每次循环都会创建一个单独作用域
setTimeout(function timer(){
console.log(i)
},1000)
})()
}
通过声明并立即执行一个函数来创建一个新的作用域。但是、但是这个新创建的作用域其实并没有任何卵用,因为里面没有任何数据,而最后打印的i,最终找的还是全局作用域里面的i
for(var i = 1; i <= 5; i++) {
(function(){ // 立即执行函数,每次循环都会创建一个单独作用域
var j = i // 每次循环都会将i的值保存到自己的作用中的j里面
setTimeout(function timer(){
console.log(j) // 此时打印的j就是,自己作用域里面的j
},1000)
})()
}
实际上每次循环都形成了一个新的闭包。而timer函数里面使用的变量j就是新闭包中的变量
for(var i = 1; i <= 5; i++) {
(function(j){ // 立即执行函数,每次循环都会创建一个单独作用域
setTimeout(function timer(){
console.log(j) // 此时打印的j就是,自己作用域里面的j
},1000)
})(i)
}
块作用域和闭包
for (var i = 0; i <= 5; i++) {
let j = i // 闭包的块作用域
setTimeout(function timer(){
console.log(j)
}, 1000)
}
let可以劫持每一次循环时的块作用域,因此一个块就转换成一个可以被关闭的作用域。而timer覆盖了每次循环迭代生成的块作用域形成的闭包。也是生成了五个闭包
for (let i = 0; i <= 5; i++) {
setTimeout(function timer(){
console.log(i)
}, 1000)
}
即for循环头部的let声明还会有一个特殊的行为,也就是变量在循环的过程中不止被声明一次,每次迭代都会被声明,随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量
模块
模块可是利用了闭包的强大威力的典型例子
function foo(){
var something = "cool"
var another = [1,2,3]
function doSomething(){
console.log(something)
}
function doAnother(){
console.log(another.join("!"))
}
}
因此模块模式需要具备的两个必要条件
- 必须有外部封闭的函数,且该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)
- 封闭函数必须返回至少一个内部函数((将内部函数的引用保存到外部)),并且内部函数可以访问或修改私有状态,这样内部函数才可以在私有作用域中形成闭包
【一个具有函数属性的对象并不是真正的模块】
var obj = {
function doSomething(){
console.log(something)
}
function doAnother(){
console.log(another.join("!"))
}
}
【一个从函数调用所返回的,只有数据属性而没有闭包函数的对象也不是一个真正的模块】
function foo(){
var something = "cool"
var another = [1,2,3]
function doSomething(){
console.log(something)
}
function doAnother(){
console.log(another.join("!"))
}
return {
something: something
another: another
}
}
上一个示例代码中有一个叫做CoolModule()的独立的模块创建器,他可以被调用任意多次,且每次调用所返回的都会创建一个新的模块实例。当只需要一个实例时,我们可以对这个模式进行简单的改建来实现单例模式
var foo = (function CoolModule(){
var something = "cool"
var another = [1,2,3]
function doSomething(){
console.log(something)
}
function doAnother(){
console.log(another.join("!"))
}
return {
doSomething: doSomething,
doAnother: doAnother
}
})()
foo.doSomething() // cool
foo.doAnother() // 1!2!3
单例:通过立即执行函数,立即调用并将返回值直接赋值给单例的模块实例标识符foo
【模块的另一个强大的用法:给将要作为公共API返回的对象命名】
var foo = (function(id){
function change () {
// 修改公共的API
publicAPI.identify = identify2
}
function identify1 () {
console.log(id)
}
function identify2 () {
console.log(id.toUpperCase())
}
var publicAPI = {
change: change
identify: identify1
}
return publicAPI
})("foo module")
foo.identify() // foo module
foo.change() // 更改模块实例中的方法
foo.identify() // FOO 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] = imp.apply(impl, deps)
}
function get(name) {
return modules[name]
}
return {
define: define
get: get
}
})()
使用
MyModules.define("bar", [], function(){
function hello(who) {
return "let me introduce:" + who
}
return {
hello: hello
}
})
MyModules.define("foo", ["bar"], function(bar){
var hungry = "hippo"
function awesome(who) {
console.log(bar.hello(hungry).toUpperCase())
}
return {
awesome: awesome
}
})
var bar = MyModules.get("bar")
var foo = MyModules.get("foo")
console.log( bar.hello("hippo")) // let me introduce: hippo
foo.awesome() // LET ME INTRODUCE: HIPPO