for (var i = 0; i < 5; i++) {
// 利用 IIFE 创建一个函数作用域
(function() {
// 用一个变量把时下的 i 保存在函数作用域内
var j = i
setTimeout(function() {
console.log(j)
}, 0)
})()
}
利用 let 创建块级作用域
上述的方法是在不使用 let 关键词的情况下,通过创建一个函数作用域来将每一次的变量 i 捕获下来。
有了 let 关键词,可以免去使用 IIFE 创建函数作用域的过程:
for (var i = 0; i < 5; i++) {
// let 创建了一个块级作用域,用于回调函数捕获
let j = i
setTimeout(function() {
console.log(j)
}, 0)
}
let 关键词将变量 j 捕获在了一个块级作用域内部。
但是,上述的写法还可以继续简化成:
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, 0)
}
这种写法就能得到正确的输出结果,他的原理是:
for 循环头部的 let 声明有一个特殊行为:变量在循环过程中不止会被声明一次,而是每次迭代都会声明一次。每一次声明,都是用上次循环结束的值进行赋值。
用闭包封装模块
下面就是一种模块封装的基本形式:
function FooModule() {
var name = 'bar'
function sayHello() {
console.log(`Hello, ${name}`)
}
return {
sayHello
}
}
var foo = FooModule()
foo.sayHello() // Hello, bar
这种模块封装的方式就利用到了闭包的特性。他具备以下两个必要条件:
必须有一个外部封装函数,并且需要被调用一次(每调用一次就创建一个新的模块实例)
返回的内容中必须持有一个内部函数,这样才能保证对封装函数内部的作用域引用(形成闭包)
模块往往只需要被调用一次,因此可以采用 IIFE 的写法:
var fooModule = (function() {
var name = 'bar'
function sayHello() {
console.log(`Hello, ${name}`)
}
return {
sayHello
}
})()
fooModule.sayHello() // Hello, bar
模块机制
现代的模块机制使用类似于下面的结构:
var MyModules = (function () {
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,
get
}
})()
MyModules.define('foo', [], function() {
function hello(name) {
console.log(`Hello, ${name}`)
}
return {
hello
}
})
MyModules.define('bar', ['foo'], function(foo) {
function helloFromBar() {
foo.hello('bar')
}
return {
helloFromBar
}
})
var bar = MyModules.get('bar')
bar.helloFromBar() // Hello, bar