[译] Javascript 中多样的 this

Javascript 中多样的 this

本文将尽可能解释清楚 JavaScript 中最基础的部分之一:执行上下文(execution context)。若是你常常使用 JS 框架,那理解 this 更是锦上添花。但若是你想更加认真地对待编程的话,理解上下文无疑是很是重要的。javascript

咱们能够像日常说话同样来使用 this。例如:我会说“我妈很不爽,这(this)太糟糕了”,而不会说“我妈很不爽,我妈很不爽这件事太糟糕了”。理解了 this 的上下文,才会理解咱们为何以为很糟糕。html

如今试着把这个例子与编程语言联系起来。在 Javascript 中,咱们将 this 做为一个快捷方式,一个引用。它指向其所在上下文的某个对象或变量。前端

如今这么说可能会让人不解,不过很快你就能理解它们了。java

全局上下文

若是你和某人聊天,在刚开始对话、没有作介绍、没有任何上下文时,他对你说:“这(this)太糟糕了”,你会怎么想?大多数状况人们会试图将“这(this)”与周围的事物、最近发生的事情联系起来。react

对于浏览器来讲也是如此。成千上万的开发者在没有上下文的状况下使用了 this。咱们可怜的浏览器只能将 this 指向一个全局对象(大多数状况下是 window)。android

var a = 15;
console.log(this.a);
// => 15
console.log(window.a);
// => 15复制代码

[以上代码需在浏览器中执行]ios

函数外部的任何地方都为全局上下文,this 始终指向全局上下文(window 对象)。git

函数上下文

以真实世界来类比,函数上下文能够当作句子的上下文。“我妈很不爽,这(this)很不妙。”咱们都知道这句话中的 this 是什么意思。其它句子中一样能够使用 this,可是因为其处于所处上下文不一样于是意思全然不一样。例如,“风暴来袭,这(this)太糟糕了。”github

JavaScript 的上下文与对象有关,它取决于函数被执行时所在的对象。所以 this 会指向被执行函数所在的对象。编程

var a = 20;

function gx () {
    return this;
}

function fx () {
    return this.a;
}

function fy () {
    return window.a;
}

console.log(gx() === window);
// => True
console.log(fx());
// => 20
console.log(fy());
// => 20复制代码

this 由函数被调用的方式决定。如你所见,上面的全部函数都是在全局上下文中被调用。

var o = {
  prop: 37,
  f: function() {
    return this.prop;
  }
};

console.log(o.f());
// => 37复制代码

当一个函数是做为某个对象的方法被调用时,它的 this 指向的就是这个方法所在的对象。

function fx () {
    return this;
}

var obj = {
    method: function () {
        return this;
    }
};

var x_obj = {
    y_obj: {
        method: function () {
            return this;
        }
    }
};

console.log(fx() === window);
// => True — 咱们仍处于全局上下文中。
console.log(obj.method() === window);
// => False — 函数做为一个对象的方法被调用。
console.log(obj.method() === obj);
// => True — 函数做为一个对象的方法被调用。
console.log(x_obj.y_obj.method() === x_obj)
// => False — 函数做为 y_obj 对象的方法被调用,所以 `this` 指向的是 y_obj 的上下文。复制代码

例 4

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

console.log(f2() === undefined);
// => True复制代码

在严格模式下,全局做用域的函数在全局做用域被调用时,thisundefined

例 5

function fx () {
    return this;
}

var obj = {
    method: fx
};

console.log(obj.method() === window);
// => False
console.log(obj.method() === obj);
// => True复制代码

与前面的例子同样,不管函数是如何被定义的,在这儿它都是做为一个对象方法被调用。

例 6

var obj = {
    method: function () {
        return this;
    }
};

var sec_obj = {
    method: obj.method
};

console.log(sec_obj.method() === obj);
// => False
console.log(sec_obj.method() === sec_obj);
// => True复制代码

this 是动态的,它能够由一个对象指向另外一个对象。

例 7

var shop = {
  fruit: "Apple",
  sellMe: function() {
    console.log("this ", this.fruit);
// => this Apple
    console.log("shop ", shop.fruit);
// => shop Apple
  }
}

shop.sellMe()复制代码

咱们既能经过 shop 对象也能经过 this 来访问 fruit 属性。

例 8

var Foo = function () {
    this.bar = "baz"; 
};

var foo = new Foo();

console.log(foo.bar); 
// => baz
console.log(window.bar);
// => undefined复制代码

如今状况不一样了。new 操做符建立了一个对象的实例。所以函数的上下文设置为这个被建立的对象实例。

Call、apply、bind

依旧以真实世界举例:“这(this)太糟糕了,由于我妈开始不爽了。”

这三个方法可让咱们在任何期许的上下文中执行函数。让咱们举几个例子看看它们的用法:

例 1

var bar = "xo xo";

var foo = {
    bar: "lorem ipsum"
};

function test () {
    return this.bar;
}

console.log(test());
// => xo xo — 咱们在全局上下文中调用了 test 函数。
console.log(test.call(foo)); 
// => lorem ipsum — 经过使用 `call`,咱们在 foo 对象的上下文中调用了 test 函数。
console.log(test.apply(foo));
// => lorem ipsum — 经过使用 `apply`,咱们在 foo 对象的上下文中调用了 test 函数。复制代码

这两种方法都能让你在任何须要的上下文中执行函数。

apply 可让你在调用函数时将参数以不定长数组的形式传入,而 call 则须要你明确参数。

例 2

var a = 5;

function test () {
    return this.a;
}

var bound = test.bind(document);

console.log(bound()); 
// => undefined — 在 document 对象中没有 a 这个变量。
console.log(bound.call(window)); 
// => undefined — 在 document 对象中没有 a 这个变量。在这个状况中,call 不能改变上下文。

var sec_bound = test.bind({a: 15})

console.log(sec_bound())
// => 15 — 咱们建立了一个新对象 {a:15},并在此上下文中调用了 test 函数。复制代码

bind 方法返回的函数的下上文会被永久改变。
在使用 bind 以后,其上下文就固定了,不管你再使用 call、apply 或者 bind 都没法再改变其上下文。

箭头函数(ES6)

箭头函数是 ES6 中的一个新语法。它是一个很是方便的工具,不过你须要知道,在箭头函数中的上下文与普通函数中的上下文的定义是不一样的。让咱们举例看看。

例 1

var foo = (() => this);
console.log(foo() === window); 
// => True复制代码

当咱们使用箭头函数时,this 会保留其封闭范围的上下文。

例 2

var obj = {method: () => this};

var sec_obj = {
  method: function() {
    return this;
  }
};

console.log(obj.method() === obj);
// => False
console.log(obj.method() === window);
// => True
console.log(sec_obj.method() === sec_obj);
// => True复制代码

请注意箭头函数与普通函数的不一样点。在这个例子中使用箭头函数时,咱们仍然处于 window 上下文中。
咱们能够这么看:

x => this.y equals function (x) { return this.y }.bind(this)

能够将箭头函数看作其始终 bind 了函数外层上下文的 this,所以不能将它做为构造函数使用。下面的例子也说明了其不一样之处。

例 3

var a = "global";

var obj = {
 method: function () {
   return {
     a: "inside method",
     normal: function() {
       return this.a;
     },
     arrowFunction: () => this.a
   };
 },
 a: "inside obj"
};

console.log(obj.method().normal());
// => inside method
console.log(obj.method().arrowFunction());
// => inside obj复制代码

当你了解了函数中动态(dynamic) this 与词法(lexical)this ,在定义新函数的时候请三思。若是函数将做为一个方法被调用,那么使用动态 this;若是它做为一个子程序(subroutine)被调用,则使用词法 this

译注:了解动态做用域与词法做用域可阅读此文章

相关阅读


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOSReact前端后端产品设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索