13.2 因为 this
,传统的函数都是糟糕的非方法函数
在 JavaScript 中,传统的函数可以被用作:
- 1、非方法函数
- 2、方法
- 3、构造器
它们之间是冲突的:因为在第二种和第三种中,函数总是有自己的 this
变量。但是,有时会拿不到想要的那个 this
,比如从一个回调函数(第一种)中获取外面方法中的 this
。
可以在下面的 ES5 代码中看到这个现象:
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) { // (A)
'use strict';
return arr.map(function (x) { // (B)
// Doesn’t work:
return this.prefix + x; // (C)
});
};
在 C 行,我们想访问 this.name
,但是由于行 B 的函数在作用域链上覆盖了来自于行 A 方法的 this
,因此是无法访问的。在严格模式下, 非方法函数中的 this
是 undefined
,这就是为什么在使用 Prefixer
的时候抛出错误:
> var pre = new Prefixer('Hi ');
> pre.prefixArray(['Joe', 'Alex'])
TypeError: Cannot read property 'prefix' of undefined
在 ECMAScript 5 中有三种方法解决这个问题。
13.2.1 方法1: that = this
你可以将 this
赋值给一个不会被隐藏的变量。这就是下面行 A 所做的:
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
var that = this; // (A)
return arr.map(function (x) {
return that.prefix + x;
});
};
现在 Prefixer
像预期一样运作了:
> var pre = new Prefixer('Hi ');
> pre.prefixArray(['Joe', 'Alex'])
[ 'Hi Joe', 'Hi Alex' ]
13.2.2 方法2:为 this
指定一个值
有几个数组方法有一个额外的参数用于指定在调用回调函数的时候的 this
值。这就是下面行 A 最后一个参数:
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
return arr.map(function (x) {
return this.prefix + x;
}, this); // (A)
};
13.2.3 方法3: bind(this)
可以使用方法 bind()
转换一下在调用的时候才决定的 this
值(通过 call()
,函数调用,方法调用,等等),将其变为固定的值。这就是下面行 A 所做的。
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
return arr.map(function (x) {
return this.prefix + x;
}.bind(this)); // (A)
};
13.2.4 ECMAScript 6 的解决方案:箭头函数
箭头函数基于方法3,用一种更加方便的语法。使用箭头函数,代码看起来像下面这样。
function Prefixer(prefix) {
this.prefix = prefix;
}
Prefixer.prototype.prefixArray = function (arr) {
return arr.map((x) => {
return this.prefix + x;
});
};
如果要把上述代码完全 ES6 化,就要使用类和更加紧凑多样的箭头函数:
class Prefixer {
constructor(prefix) {
this.prefix = prefix;
}
prefixArray(arr) {
return arr.map(x => this.prefix + x); // (A)
}
}
在行 A ,调整了一下箭头函数两边的部分,节省了几个字符:
- 如果只有一个参数,并且这个参数是一个标识符,那么可以省略小括号。
- 在箭头后面跟一个表达式会使表达式的值被直接返回。
在上面的代码中,也可以看到方法 constructor
和 prefixArray
使用新的,更加紧凑的 ES6 语法定义,该种写法对对象字面量也同样有效。