[译] Javascript 中多样的 this

本文讲的是[译] Javascript 中多样的 this,




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

我们可以像平常说话一样来使用 this。例如:我会说“我妈很不爽,这(this)太糟糕了”,而不会说“我妈很不爽,我妈很不爽这件事太糟糕了”。理解了 this 的上下文,才会理解我们为什么觉得很糟糕。

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

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

全局上下文

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

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

var a = 15;
console.log(this.a);
// => 15
console.log(window.a);
// => 15

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

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

函数上下文

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

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

在严格模式下,全局作用域的函数在全局作用域被调用时,this 为 undefined

例 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

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

相关阅读





原文发布时间为:2017年10月13日


本文来自合作伙伴掘金,了解相关信息可以关注掘金网站。

时间: 2024-11-10 01:01:57

[译] Javascript 中多样的 this的相关文章

[译] JavaScript 中的 CSS:基于组件的样式的未来

本文讲的是[译] JavaScript 中的 CSS:基于组件的样式的未来, 原文地址:CSS in JavaScript: The future of component-based styling 原文作者:Jonathan Z. White 译文出自:掘金翻译计划 译者:bambooom 校对者:Aladdin-ADD.reid3290 JavaScript 中的 CSS:基于组件的样式的未来 使用行内样式使我们可以获得 JavaScript 的所有编程支持.这让我们获得类似 CSS 预处

[译] ES6+ 中的 JavaScript 工厂函数(第八部分)

本文讲的是[译] ES6+ 中的 JavaScript 工厂函数(第八部分), 原文地址:JavaScript Factory Functions with ES6+ 原文作者:Eric Elliott 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m- 译者:lampui 校对者:IridescentMia.sunui Smoke Art Cubes to Smoke - MattysFlicks - (CC BY 2.0) 注意:这是"软件编写"系

JavaScript中的稀疏数组与密集数组[译]_javascript技巧

1.稀疏数组 创建一个指定长度的稀疏数组很简单: 复制代码 代码如下: > var a = new Array(3); > a [ , , ] > a.length 3 > a[0] undefined 当你遍历它时,你会发现,它并没有元素.JavaScript会跳过这些缝隙. 复制代码 代码如下: > a.forEach(function (x, i) { console.log(i+". "+x) }); > a.map(function (x,

JavaScript中:表达式和语句的区别[译]_javascript技巧

1.语句和表达式 JavaScript中的表达式和语句是有区别的.一个表达式会产生一个值,它可以放在任何需要一个值的地方,比如,作为一个函数调用的参数.下面的每行代码都是一个表达式: myvar3 + xmyfunc("a", "b")语句可以理解成一个行为.循环语句和if语句就是典型的语句.一个程序是由一系列语句组成的.JavaScript中某些需要语句的地方,你可以使用一个表达式来代替.这样的语句称之为表达式语句.但反过来不可以:你不能在一个需要表达式的地方放一

JavaScript中的function使用方法

JavaScript入门易,可深究起来,竟搞得我如此混乱,这恐怕就是弱类型语言的特点吧?写惯了C++,还真是不适应.近日在google上搜来搜去,学习了半天function.this和prototype,这就总结一下,但愿能把它们理清楚.这是第一篇,关于JavaScript中的function. 参考了一些文章,我认为JavaScript中的function可以有以下两种用法:一是做"普通逻辑代码容器",也就是我们通常意义上的函数.方法,和我们C/C++里的函数没什么大分别,只是写法稍

JavaScript中的函数声明和函数表达式区别浅析

 这篇文章主要介绍了JavaScript中的函数声明和函数表达式区别浅析,本文总结的浅显易懂,非常好的一篇技术文章,需要的朋友可以参考下     记得在面试腾讯实习生的时候,面试官问了我这样一道问题. 代码如下: //下述两种声明方式有什么不同 function foo(){}; var bar = function foo(){};   当初只知道两种声明方式一个是函数声明一个是函数表达式,具体有什么不同没能说得很好.最近正好看到这方面的书籍,就想好好总结一番. 在ECMAScript中,有两

[译] JavaScript 的函数式编程是一种反模式

本文讲的是[译] JavaScript 的函数式编程是一种反模式, 原文地址:Functional programming in JavaScript is an antipattern 原文作者:Alex Dixon 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m- 译者:sunui 校对者:LeviDing.xekri 其实 Clojure 更简单些 写了几个月 Clojure 之后我再次开始写 JavaScript.就在我试着写一些很普通的东西的时候,我

十个JavaScript中易犯的小错误,你中了几枪?

在今天,JavaScript已经成为了网页编辑的核心.尤其是过去的几年,互联网见-证了在SPA开发.图形处理.交互等方面大量JS库的出现. 如果初次打交道,很多人会觉得js很简单.确实,对于很多有经验的工程师,或者甚至是初学者而言,实现基本的js功能几乎 毫无障碍.但是JS的真实功能却比很多人想象的要更加多样.复杂.JavaScript的许多细节规定会让你的网页出现很多意想不到的bug,搞懂这些 bug,对于成为一位有经验的JS开发者很重要. 常见错误一:对于this关键词的不正确引用 我曾经听

[译] 同中有异的 Webpack 与 Rollup

本文讲的是[译] 同中有异的 Webpack 与 Rollup, 原文地址:Webpack and Rollup: the same but different 原文作者:Rich Harris 译文出自:掘金翻译计划 译者:lsvih 校对者:avocadowang,Aladdin-ADD 同中有异的 Webpack 与 Rollup 本周,Facebook 将一个非常大的 pull request 合并到了 React 主分支.这个 PR 将 React 当前使用的构建工具替换成了 Roll