为什么JavaScript中的this如此强大?

译者按: JavaScript中的this让开发者犯迷糊,但是也很灵活很强大。

为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

小编推荐:Fundebug专注于JavaScript、微信小程序、微信小游戏,Node.js和Java线上bug实时监控。真的是一个很好用的bug监控服务,众多大佬公司都在使用。

“this”到底是什么?
在Vanilla JavaScript中,是一个让新手很困扰的问题。

特别是对于我们这些没有编程背景的初学者。

我曾经从事会计工作,对于JIB、AP、AR这些专业词很熟悉。因为词典会给它们一个清晰的定义。

但是,“this”的定义却一点都不清晰。

我记得第一次学习对象的时候,”this“在构造函数中被使用,但是从来没有清楚地解释。

到底什么是this?

这里是我个人给出的非官方的定义:

“this”是JavaScript的一个关键字,它具体的含义依赖于使用的环境。

至于在学习JavaScript的时候,”this”如此让人迷惑就是因为它的环境又基于你如何使用它。

你甚至可以把它理解成为一个动态的关键字。

我很喜欢Ryan Morr在 Understanding Scope and Context in JavaScript中的一句话:

Context is always the value of the this keyword which is a reference to the object that “owns” the currently executing code.

但是,“this”的环境不一定和它的执行环境相同。

因此,当我们使用“this”关键字的时候,我们实际上用来指向一个对象,那么到底是哪个对象?

我们来看一些示例

  1. “this”指向Window对象
  2. 对象的方法
    1. this作用于对象
    2. this作用于嵌套的对象
    3. this作用于对象,但是用了箭头(arrow function)函数
  3. 函数环境
  4. new关键字

1. “this”指向Window对象

如果你尝试在函数外使用“this”,它会指向全局,也就是Window对象。

在全局的函数中使用的this也指向Window对象。

你可以自己试一试:

console.log(this);

// returns the Window object
// Window { postMessage: ƒ,
// blur: ƒ,
// focus: ƒ,
// close: ƒ,
// frames: Window, …}

function myFunction() {
console.log(this);
}

// Call the function
myFunction();

// returns the Window object!
// Window { postMessage: ƒ,
// blur: ƒ,
// focus: ƒ,
// close: ƒ,
// frames: Window, …}

2. 对象方法

当“this”在对象内部,那么指向对象本身。

如下所示,创建一个对象,intro函数中使用了this,那么this指向dog对象。

var dog = {
name: 'Chester',
breed: 'beagle',
intro: function(){
console.log(this);
}
};

dog.intro();

// returns the dog object and all of it's properties and methods
// {name: "Chester", breed: "beagle", intro: ƒ}
// breed:"beagle"
// intro:ƒ ()
// name:"Chester"
// __proto__:Object

对象嵌套

如果使用了对象嵌套,那么this的值如何确定就变得错综复杂了。

任何时候,如果你在对象中嵌套对象,那么”this”的使用取决于函数基于哪个对象定义。

例子:

var obj1 = {
hello: function() {
console.log('Hello world');
return this;
},
obj2: {
breed: 'dog',
speak: function(){
console.log('woof!');
return this;
}
}
};

console.log(obj1);
console.log(obj1.hello()); // logs 'Hello world' and returns obj1
console.log(obj1.obj2);
console.log(obj1.obj2.speak()); // logs 'woof!' and returns obj2

但是,要注意箭头函数并不遵守规则

在箭头函数中,“this”指向Window或则全局对象!

你可以试试下面的代码:

var objReg = {
hello: function() {
return this;
}
};

var objArrow = {
hello: () => this
};

objReg.hello(); // returns the objReg object that we expect
objArrow.hello(); // returns the Window object!

根据MDN的定义:箭头函数有更加简洁的语法,并且没有自己的this绑定。

所以,不要在定义对象方法的事或使用它。

3. “this”用于一般函数情况

函数在全局定义,那么this指向全局对象,即window。

function test() {
console.log('hello world');
console.log(this);
}

test();

// hello world
// Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

但是,如果在严格模式下,“this”会返回undefined。因为严格模式下,不允许默认绑定。就算是window对象也不行。

function test() {
'use strict';
return this;
}

console.log( test() );
// returns undefined, not the Window object … interesting

当在对象外调用函数,而函数中使用了this

dog对象在定义的时候没有定义方法,知道我创建dog.foo,将chase函数绑定给它。

调用foo函数,那么chase函数中的this会指向dog对象。如果没有将chase函数和某个对象绑定,那么会报错或则返回未定义。

var dog = {
breed: 'Beagles',
lovesToChase: 'rabbits'
};

function chase() {
console.log(this.breed + ' loves chasing ' + this.lovesToChase + '.');
// console.log(this);
}

dog.foo = chase;
dog.foo(); // returns Beagles loves chasing rabbits.

chase(); // returns undefined because when run in the global context, window object does not have these properties defined

直接调用chase函数,返回未定义,因为在全局环境下,this指向window对象。

4. “new”关键字

function Dog(breed, name, friends){
this.breed = breed;
this.name = name;
this.friends = friends;
this.intro = function() {
console.log(`Hi, my name is ${this.name} and I’m a ${this.breed}`);
return this;
};
}

注意,在定义Dog函数的时候,其this没有特定的值。

每一次使用new关键字创建一个新的Dog实例,this就会指向该对象。

原则上和下面的语法是一致的:

var str = new String('Hello world');
/*******
You could do the above, but it's best to avoid it (instead do like the variable str2 below)
(because JavaScript knows that anything inside single or double quotes has the type of String)
Same goes for other primitives. This is for example purposes only.
NOTE: To clarify -- the only time I ever use the new keyword in practice is when I use a function constructor and create my own object type.
*******/

var str2 = 'Hello world';
// both have the prototype of String and inherit all the String methods and properties

// Using the Dog prototype, create a new instance 
var chester = new Dog('beagle', 'Chester', ['Gracie', 'Josey', 'Barkley']);
chester.intro(); // returns Hi, my name is Chester and I'm a beagle
console.log(chester); // returns Dog {breed: "beagle", name: "Chester", friends: Array(3), intro: ƒ}

// Here's another example:
var City = function(city, state) {
this.city = city || "Phoenix";
this.state = state || "AZ";
this.sentence = function() {
console.log(`I live in ${this.city}, ${this.state}.`);
};
};

var phoenix = new City(); // use the default parameters
console.log(phoenix); // returns the phoenix object (an instance of the City prototype)
phoenix.sentence(); // returns "I live in Phoenix, AZ."

var spokane = new City('Spokane', 'WA');
console.log(spokane); // returns the spokane object (another instance of the City prototype)
spokane.sentence(); // returns "I live in Spokane, WA."

new关键字很重要

它可以将this指向到新定义的对象实例。这样的好处在于,我们可以有一个定义,来声明多个实例。

想象一下你的社交账号,我们可以定义每一个社交账号叫“Friend”,每一次创建一个新的账户,它们都会继承所有的属性和方法,但是传入个性化的名字,密码,兴趣,工作等数据。

// Constructor Function
var Friend = function(name, password, interests, job){
this.fullName = name;
this.password = password;
this.interests = interests;
this.job = job;
};

function sayHello(){
// uncomment the console.log to see the object that 'this' points to
// console.log(this);
return `Hi, my name is ${this.fullName} and I'm a ${this.job}. Let's be friends!`;
}

// Create one or multiple instances of the Friend prototype with the keyword 'new'
var john = new Friend('John Smith', 'badpassword', ['hiking', 'biking', 'skiing'], 'teacher');

console.log(john);

// Assign the function to the greeting key on the john object
john.greeting = sayHello;

// Call the the new method
console.log( john.greeting() );

// Remember, you can't call sayHello() as a function; it will return "Hi, my name is undefined and I'm a undefined. Let's be friends!"
// Because the function's context is global and the window object does NOT have the keys that belong to the Friend prototype
console.log( sayHello() ) ;

结论

还有一种方式可以传递this值,那就是call,apply,和bind函数。
不过本文不再赘述。

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java实时BUG监控。
自从2016年双十一正式上线,Fundebug累计处理了6亿+错误事件,得到了Google、360、金山软件等众多知名用户的认可。




您的用户遇到BUG了吗?

体验Demo 免费使用