为什么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、微信小程序、支付宝小程序线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了80亿+错误事件。欢迎大家免费试用

版权声明

转载时请注明作者 Fundebug以及本文地址:
https://blog.fundebug.com/2018/08/06/why-this-is-so-powerful/

您的用户遇到BUG了吗?

体验Demo 免费使用