const calc = { num: 0, increment: function() { console.log(this === calc); // => true this.num += 1; return this.num; } }; // method invocation. this is calc calc.increment(); // => 1 calc.increment(); // => 2
调用calc.increment()使increment函数的上下文成为calc对象。所以使用this.num来增加num属性是有效的。
再来看看另一个例子。JS对象从原型继承一个方法,当在对象上调用继承的方法时,调用的上下文仍然是对象本身
const myDog = Object.create({ sayName: function() { console.log(this === myDog); // => true return this.name; } }); myDog.name = 'Milo'; // 方法调用 this 指向 myDog myDog.sayName(); // => 'Milo'
Object.create()创建一个新对象myDog,并根据第一个参数设置其原型。myDog对象继承sayName方法。
执行myDog. sayname()时,myDog是调用的上下文。
在EC6 class 语法中,方法调用上下文也是实例本身
class Planet { constructor(name) { this.name = name; } getName() { console.log(this === earth); // => true return this.name; } } var earth = new Planet('Earth'); // method invocation. the context is earth earth.getName(); // => 'Earth'
3.2 陷阱:将方法与其对象分离
方法可以从对象中提取到一个单独的变量const alone = myObj.myMethod。当方法单独调用时,与原始对象alone()分离,你可能认为当前的this就是定义方法的对象myObject。
如果方法在没有对象的情况下调用,那么函数调用就会发生,此时的this指向全局对象window严格模式下是undefined。
下面的示例定义了Animal构造函数并创建了它的一个实例:myCat。然后setTimout()在1秒后打印myCat对象信息
function Animal(type, legs) { this.type = type; this.legs = legs; this.logInfo = function() { console.log(this === myCat); // => false console.log('The ' + this.type + ' has ' + this.legs + ' legs'); } } const myCat = new Animal('Cat', 4); // The undefined has undefined legs setTimeout(myCat.logInfo, 1000);
你可能认为setTimout调用myCat.loginfo()时,它应该打印关于myCat对象的信息。
不幸的是,方法在作为参数传递时与对象是分离,setTimout(myCat.logInfo)以下情况是等效的:
setTimout(myCat.logInfo); // 等价于 const extractedLogInfo = myCat.logInfo; setTimout(extractedLogInfo);
将分离的logInfo作为函数调用时,this是全局 window,所以对象信息没有正确地打印。
函数可以使用.bind()方法与对象绑定,就可以解决 this 指向的问题。
function Animal(type, legs) { this.type = type; this.legs = legs; this.logInfo = function() { console.log(this === myCat); // => true console.log('The ' + this.type + ' has ' + this.legs + ' legs'); }; } const myCat = new Animal('Cat', 4); // logs "The Cat has 4 legs" setTimeout(myCat.logInfo.bind(myCat), 1000);
myCat.logInfo.bind(myCat)返回一个新函数,它的执行方式与logInfo完全相同,但是此时的 this 指向 myCat,即使在函数调用中也是如此。
另一种解决方案是将logInfo()方法定义为一个箭头函数:
function Animal(type, legs) { this.type = type; this.legs = legs; this.logInfo = () => { console.log(this === myCat); // => true console.log('The ' + this.type + ' has ' + this.legs + ' legs'); }; } const myCat = new Animal('Cat', 4); // logs "The Cat has 4 legs" setTimeout(myCat.logInfo, 1000);
4. 构造函数调用
当new关键词紧接着函数对象,(,一组逗号分隔的参数以及)时被调用,执行的是构造函数调用如new RegExp('\\d')。
声明了一个Country函数,并且将它作为一个构造函数调用:
function Country(name, traveled) { this.name = name ? name : 'United Kingdom'; this.traveled = Boolean(traveled); } Country.prototype.travel = function() { this.traveled = true; }; // 构造函数调用 const france = new Country('France', false); // 构造函数调用 const unitedKingdom = new Country; france.travel(); // Travel to France
new Country('France', false)是Country函数的构造函数调用。它的执行结果是一个name属性为'France'的新的对象。 如果这个构造函数调用时不需要参数,那么括号可以省略:new Country。
从ES6开始,JS 允许用class关键词来定义构造函数
class City { constructor(name, traveled) { this.name = name; this.traveled = false; } travel() { this.traveled = true; } } // Constructor invocation const paris = new City('Paris', false); paris.travel();
new City('Paris')是构造函数调用。这个对象的初始化由这个类中一个特殊的方法constructor来处理。其中,this指向新创建的对象。