概念
匿名函数经常被人误认为是闭包(closure)。闭包指的是那些引用了另一个函数作用域中变量的函数,通常是嵌套函数中使用的。
function createComparisonFunction(propertyName) {
return function(object1, object2) {
let value1 = object1[propertyName];
let value2 = object2[propertyName];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
};
}
在这个内部函数被返回并在其他地方被使用后,它仍然引用着那个变量。这是因为内部函数的作用域链包含createComparisonFunction()函数的作用域。
this对象
在闭包中使用
this
会让代码变复杂。如果内部函数没有使用箭头函数定义,则
this
对象会在运
行时绑定到执行函数的上下文。如果在全局函数中调用,则
this
在非严格模式下等于
window
,在
格模式下等于
undefined
。如果作为某个对象的方法调用,则
this
等于这个对象。匿名函数在这种
况下不会绑定到某个对象,这就意味着
this
会指向
window
,除非在严格模式下
this
是
undefined
不过,由于闭包的写法所致,这个事实有时候没有那么容易看出来。来看下面的例子:
window.identity = 'The Window';
let object = {
identity: 'My Object',
getIdentityFunc() {
return function() {
return this.identity;
};
}
};
console.log(object.getIdentityFunc()()); // 'The Window'
这里先创建了一个全局变量
identity
,之后又创建一个包含
identity
属性的对象。这个对象还
包含一个
getIdentityFunc()
方法,返回一个匿名函数。这个匿名函数返回
this.identity
。因为
getIdentityFunc()
返回函数,所以
object.getIdentityFunc()()
会立即调用这个返回的函数,从而得到一个字符串。可是,此时返回的字符串是"The Winodw"
,即全局变量
identity 的值。为什
么匿名函数没有使用其包含作用域(
getIdentityFunc()
)的
this
对象呢前面介绍过,每个函数在被调用时都会自动创建两个特殊变量:this
和
arguments
。内部函数永远不可能直接访问外部函数这两个变量。但是,如果把 this
保存到闭包可以访问的另一个变量中,则是行得通的。比如:
window.identity = 'The Window';
let object = {
identity: 'My Object',
getIdentityFunc() {
let that = this;
return function() {
return that.identity;
};
}
};
console.log(object.getIdentityFunc()()); // 'My Object'
在定义匿名函数之前,先把外部函数的
this
保存 到变量 that
中。然后在定义闭包时,就可以让它访问
that
,因为这是包含函数中名称没有任何冲突的 一个变量。即使在外部函数返回之后,that
仍然指向
object
,所以调
object.getIdentityFunc()
就会返回
"My Object"
。
注意:
this 和 arguments 都是不能直接在内部函数中访问的。如果想访问包含作用域中的 arguments 对象,则同样需要将其引用先保存到闭包能访问的另一个变量中。
在一些特殊情况下,
this
值可能并不是我们所期待的值。比如下面这个修改后的例子:
window.identity = 'The Window';
let object = {
identity: 'My Object',
getIdentity () {
return this.identity;
}
};
getIdentity()
方法就是返回
this.identity
的值。以下是几种调用
object.getIdentity()
的方式及返回值:
object.getIdentity(); // 'My Object'
(object.getIdentity)(); // 'My Object'
(object.getIdentity = object.getIdentity)(); // 'The Window'
第一行调用
object.getIdentity()
是正常调用,会返回
"My Object"
,因为
this.identity 就是 object.identity
。第二行在调用时把
object.getIdentity
放在了括号里。虽然加了括号之
后看起来是对一个函数的引用,但
this
值并没有变。这是因为按照规范,
object.getIdentity
和
(object.getIdentity)
是相等的。第三行执行了一次赋值,然后再调用赋值后的结果。因为赋值表
达式的值是函数本身,
this
值不再与任何对象绑定,所以返回的是
"The Window"。一般情况下,不大可能像第二行和第三行这样调用对象上的方法。但通过这个例子,我们可以知道
即使语法稍有不同,也可能影响
this
的值。
静态私有变量
特权方法也可以通过使用私有作用域定义私有变量和函数来实现。这个模式如下所示:
(function() {
// 私有变量和私有函数
let privateVariable = 10;
function privateFunction() {
return false;
}
// 构造函数
MyObject = function() {};
// 公有和特权方法
MyObject.prototype.publicMethod = function() {
privateVariable++;
return privateFunction();
};
})();
在这个模式中,匿名函数表达式创建了一个包含构造函数及其方法的私有作用域。首先定义的是私
有变量和私有函数,然后又定义了构造函数和公有方法。公有方法定义在构造函数的原型上,与典型的原型模式一样。注意,这个模式定义的构造函数没有使用函数声明,使用的是函数表达式。函数声明会创建内部函数,在这里并不是必需的。基于同样的原因(但操作相反),这里声明MyObject 并没有使用任何关键字。因为不使用关键字声明的变量会创建在全局作用域中,所以MyObject 变成了全局变量,可以在这个私有作用域外部被访问。注意在严格模式下给未声明的变量赋值会导致错误。
这个模式与前一个模式的主要区别就是,私有变量和私有函数是由实例共享的。因为特权方法定义
在原型上,所以同样是由实例共享的。特权方法作为一个闭包,始终引用着包含它的作用域。来看下面的例子:
(function() {
let name = '';
Person = function(value) {
name = value;
};
Person.prototype.getName = function() {
return name;
};
Person.prototype.setName = function(value) {
name = value;
};
})();
let person1 = new Person('Nicholas');
console.log(person1.getName()); // 'Nicholas'
person1.setName('Matt');
console.log(person1.getName()); // 'Matt'
let person2 = new Person('Michael');
console.log(person1.getName()); // 'Michael'
console.log(person2.getName()); // 'Michael'
这里的
Person
构造函数可以访问私有变量
name
,跟
getName()
和
setName()
方法一样。使用这
种模式,
name
变成了静态变量,可供所有实例使用。这意味着在任何实例上调用
setName()
修改这个 变量都会影响其他实例。调用 setName()
或创建新的
Person
实例都要把
name
变量设置为一个新值而所有实例都会返回相同的值。像这样创建静态私有变量可以利用原型更好地重用代码,只是每个实例没有了自己的私有变量最终,到底是把私有变量放在实例中,还是作为静态私有变量,都需要根据自己的需求来确定。