作用域闭包
《你不知道的js 上卷》
当函数可以记住并访问所在的词法作用域时,就产生了闭包,也就是将内部函数放到了当前的词法作用域之外执行
function func () {
var a = 2;
function foo () {
console.log(a); // 2
}
foo();
}
func();
对于输出2的结果,我们分析可以得出,这是基于词法作用域的查找规则的结果,函数foo可以访问外部作用域中的变量a(RHS引用查询)
而这些规则来说只是闭包的一部分,将上面的代码做简单的修改
function func () {
var a = 2;
function foo () {
console.log(a);
}
return foo;
}
var fn = func();
fn(); // 2
上面的输出效果就是闭包的实现效果
foo()函数可以访问func()函数的词法作用域,又将foo函数作为func()函数执行后的结果返回,将其保存到了全局作用域下,因此,通过fn函数我们就可以在全局作用域下,实现在外部访问内部函数的词法作用域
通常情况下,在func函数执行后,他内部的整个作用域就会被销毁(javascript引擎会通过垃圾回收机制将不再使用的内存空间释放掉),而闭包的特点就是可以这件事情的发生,他的内部作用域依然存在(作用域链),是被foo函数所使用,他持有对该作用域的引用,不会被垃圾回收机制回收释放掉,而这个引用就叫做闭包
function func() {
var a = 2;
function foo () {
console.log(a);
}
fn(foo);
}
function fn (fnc) {
fnc(); // 此处形成闭包
}
将内部函数foo传递给fn,当调用这个内部函数的时候,她所包含的func的词法作用域就可以在fn函数内部使用,也就可以访问到a变量。
模块
JavaScript语言中有许多代码模式使用了闭包的强大威力,例如,模块
function foo () {
var something = "cool";
var another = [1,2,3];
function doSomething () {
console.log(something);
}
function doAnother () {
console.log(another.join("!"));
}
return {
doSomething:doSomething,
doAnother:doAnother
};
}
var func = foo();
func.doSomething(); // cool
func.doAnother(); // 1!2!3
这就是JavaScript中的模块
从上面的代码可以看出,首先需要通过函数foo来创建一个模块的实例,如果没有执行该函数,则内部作用域和闭包都无法创建
函数foo返回的是一个对象字面量语法表示的对象,它包含了内部函数的引用,没有引用内部的数据变量,这可以保证内部的数据变量是一个私有的状态,也可以将这个对象看作本制上是模块的公共API。
将这个对象的返回值赋值给func,此处也就形成了闭包,就可以通过它来调用函数内部的属性和方法
通过返回一个含有属性引用的对象的方式来将函数传递到词法作用域外部,通过函数对内部属性就行相关的操作,形成闭包
模式模块必须具备两个条件
1、必须由外部的封闭函数,且至少被调用一次(每次调用都会创建一个新的实例)
2、封闭函数必须返回至少一个内部函数,这样才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态
可以对上面的模块进行修改,实现单例模式:
var func = (function foo () {
var something = "cool";
var another = [1,2,3];
function doSomething () {
console.log(something);
}
function doAnother () {
console.log(another.join("!"));
}
return {
doSomething:doSomething,
doAnother:doAnother
};
})();
模块也是普通的参数,也可以传递参数
模式的另一个简单但强大的变化用法是,命名将要作为公共API返回对象:
var foo = (function func (id) {
function change () {
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对象的内部引用,可以从内部对模块实例进行修改,包括添加或者删除属性和方法,以及修改他们的值