JavaScript中的this陷阱收集

當(dāng)有人問起你JavaScript有什么特點(diǎn)的時(shí)候,你可能立馬就想到了單線程利花、事件驅(qū)動(dòng)、面向?qū)ο蟮纫欢言~語(yǔ)载佳,但是如果真的讓你解釋一下這些概念炒事,可能真解釋不清楚。有句話這么說:如果你不能向一個(gè)6歲小孩解釋清楚一個(gè)東西蔫慧,那么你自己也不懂這個(gè)東西挠乳。這句話或許有點(diǎn)夸張,但是極其有道理姑躲。個(gè)人覺得睡扬,如果需要掌握一門語(yǔ)言,掌握它的API只是學(xué)了皮毛黍析,理解這門語(yǔ)言的精髓才是重點(diǎn)卖怜。提及JavaScript的精髓,this阐枣、閉包马靠、作用域鏈函數(shù)是當(dāng)之無愧的蔼两。這門語(yǔ)言正式因?yàn)檫@幾個(gè)東西而變得魅力無窮甩鳄。


原文翻譯:

JavaScript來自一門健全的語(yǔ)言,所以你可能覺得JavaScript中的this和其他面向?qū)ο蟮恼Z(yǔ)言如java的this一樣宪哩,是指存儲(chǔ)在實(shí)例屬性中的值娩贷。事實(shí)并非如此,在JavaScript中锁孟,最好把this當(dāng)成哈利波特中的博格特的背包彬祖,有著深不可測(cè)的魔力。
下面的部分是我希望我的同事在使用JavaScript的this的時(shí)候應(yīng)當(dāng)知道的品抽。內(nèi)容很多储笑,是我學(xué)習(xí)好幾年總結(jié)出來的。

JavaScript中很多時(shí)候會(huì)用到this圆恤,下面詳細(xì)介紹每一種情況突倍。在這里我想首先介紹一下宿主環(huán)境這個(gè)概念。一門語(yǔ)言在運(yùn)行的時(shí)候,需要一個(gè)環(huán)境羽历,叫做宿主環(huán)境焊虏。對(duì)于JavaScript,宿主環(huán)境最常見的是web瀏覽器秕磷,瀏覽器提供了一個(gè)JavaScript運(yùn)行的環(huán)境诵闭,這個(gè)環(huán)境里面,需要提供一些接口澎嚣,好讓JavaScript引擎能夠和宿主環(huán)境對(duì)接疏尿。JavaScript引擎才是真正執(zhí)行JavaScript代碼的地方,常見的引擎有V8(目前最快JavaScript引擎易桃、Google生產(chǎn))褥琐、JavaScript core。JavaScript引擎主要做了下面幾件事情:

  • 一套與宿主環(huán)境相聯(lián)系的規(guī)則;
  • JavaScript引擎內(nèi)核(基本語(yǔ)法規(guī)范晤郑、邏輯敌呈、命令和算法);
  • 一組內(nèi)置對(duì)象和API;
  • 其他約定。

但是環(huán)境不是唯一的贩汉,也就是JavaScript不僅僅能夠在瀏覽器里面跑驱富,也能在其他提供了宿主環(huán)境的程序里面跑,最常見的就是nodejs匹舞。同樣作為一個(gè)宿主環(huán)境褐鸥,nodejs也有自己的JavaScript引擎--V8。
<small>根據(jù)官方的定義:Node.js is a platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applications</small>

global this

在瀏覽器里赐稽,在全局范圍內(nèi)叫榕,this等價(jià)于window對(duì)象。

1 <script type="text/javascript">
2   console.log(this === window); //true3 
3 </script>

在瀏覽器里姊舵,在全局范圍內(nèi)晰绎,用var聲明一個(gè)變量和給this或者window添加屬性是等價(jià)的。

1 <script type="text/javascript">
2   var foo = "bar";
3   console.log(this.foo); //logs "bar"
4   console.log(window.foo); //logs "bar"
5 </script>

如果你在聲明一個(gè)變量的時(shí)候沒有使用var或者let(ECMAScript 6),你就是在給全局的this添加或者改變屬性值括丁。

1 <script type="text/javascript">
2   foo = "bar";
3   function testThis() { 
4     foo = "foo";
5   }
6   console.log(this.foo); //logs "bar"
7   testThis();
8   console.log(this.foo); //logs "foo"
9 </script>

在node環(huán)境里荞下,如果使用REPL(Read-Eval-Print Loop,簡(jiǎn)稱REPL:讀取-求值-輸出,是一個(gè)簡(jiǎn)單的史飞,交互式的編程環(huán)境)來執(zhí)行程序,this并不是最高級(jí)的命名空間尖昏,最高級(jí)的是global.

> this
{ ArrayBuffer: [Function: ArrayBuffer], 
  Int8Array: { [Function: Int8Array] BYTES_PER_ELEMENT: 1 }, 
  Uint8Array: { [Function: Uint8Array] BYTES_PER_ELEMENT: 1 }, 
  ...
> global === this
true

在node環(huán)境里,如果執(zhí)行一個(gè)js腳本构资,在全局范圍內(nèi)抽诉,this以一個(gè)空對(duì)象開始作為最高級(jí)的命名空間,這個(gè)時(shí)候吐绵,它和global不是等價(jià)的迹淌。

 1 test.js腳本內(nèi)容:
 2 
 3 console.log(this);
 4 console.log(this === global);
 5 
 6 REPL運(yùn)行腳本:
 7 
 8 $ node test.js
 9 {}
10 false

在node環(huán)境里河绽,在全局范圍內(nèi),如果你用REPL執(zhí)行一個(gè)腳本文件唉窃,用var聲明一個(gè)變量并不會(huì)和在瀏覽器里面一樣將這個(gè)變量添加給this耙饰。

1 test.js:
2 
3 var foo = "bar";
4 console.log(this.foo);
5 
6 $ node test.js
7 undefined

但是如果你不是用REPL執(zhí)行腳本文件,而是直接執(zhí)行代碼句携,結(jié)果和在瀏覽器里面是一樣的(神坑)

1 > var foo = "bar";
2 > this.foo
3 bar
4 > global.foo
5 bar

在node環(huán)境里榔幸,用REPL運(yùn)行腳本文件的時(shí)候,如果在聲明變量的時(shí)候沒有使用var或者let矮嫉,這個(gè)變量會(huì)自動(dòng)添加到global對(duì)象,但是不會(huì)自動(dòng)添加給this對(duì)象牍疏。如果是直接執(zhí)行代碼蠢笋,則會(huì)同時(shí)添加給global和this

1 test.js
2 
3 foo = "bar";
4 console.log(this.foo);
5 console.log(global.foo);
6 
7 $ node test.js
8 undefined
9 bar

上面的八種情況可能大家已經(jīng)繞暈了,總結(jié)起來就是:

在瀏覽器里面this是老大鳞陨,它等價(jià)于window對(duì)象昨寞,如果你聲明一些全局變量(不管在任何地方),這些變量都會(huì)作為this的屬性厦滤。在node里面援岩,有兩種執(zhí)行JavaScript代碼的方式,一種是直接執(zhí)行寫好的JavaScript文件掏导,另外一種是直接在里面執(zhí)行一行行代碼享怀。對(duì)于直接運(yùn)行一行行JavaScript代碼的方式,global才是老大趟咆,this和它是等價(jià)的添瓷。在這種情況下,和瀏覽器比較相似值纱,也就是聲明一些全局變量會(huì)自動(dòng)添加給老大global鳞贷,順帶也會(huì)添加給this。但是在node里面直接腳本文件就不一樣了虐唠,你聲明的全局變量不會(huì)自動(dòng)添加到this搀愧,但是會(huì)添加到global對(duì)象。所以相同點(diǎn)是疆偿,在全局范圍內(nèi)咱筛,全局變量終究是屬于老大的。

function this

無論是在瀏覽器環(huán)境還是node環(huán)境翁脆, 除了在DOM事件處理程序里或者給出了thisArg(接下來會(huì)講到)外眷蚓,如果不是用new調(diào)用,在函數(shù)里面使用this都是指代全局范圍的this反番。

 1 <script type="text/javascript">
 2     foo = "bar";
 3 
 4     function testThis() {
 5       this.foo = "foo";
 6     }
 7 
 8     console.log(this.foo); //logs "bar"
 9     testThis();
10     console.log(this.foo); //logs "foo"
11 </script>
test.js

foo = "bar";

function testThis () {
  this.foo = "foo";
}

console.log(global.foo);
testThis();
console.log(global.foo);
$ node test.js
bar
foo

除非你使用嚴(yán)格模式沙热,這時(shí)候this就會(huì)變成undefined叉钥。

 1 <script type="text/javascript">
 2     foo = "bar";
 3 
 4     function testThis() {
 5       "use strict";
 6       this.foo = "foo";
 7     }
 8 
 9     console.log(this.foo); //logs "bar"
10     testThis();  //Uncaught TypeError: Cannot set property 'foo' of undefined 
11 </script>

如果你在調(diào)用函數(shù)的時(shí)候在前面使用了new,this就會(huì)變成一個(gè)新的值篙贸,和global的this脫離干系投队。

 1 <script type="text/javascript">
 2     foo = "bar";
 3 
 4     function testThis() {
 5       this.foo = "foo";
 6     }
 7 
 8     console.log(this.foo); //logs "bar"
 9     new testThis();
10     console.log(this.foo); //logs "bar"
11 
12     console.log(new testThis().foo); //logs "foo"
13 </script>

我更喜歡把新的值稱作一個(gè)實(shí)例。

函數(shù)里面的this其實(shí)相對(duì)比較好理解爵川,如果我們?cè)谝粋€(gè)函數(shù)里面使用this敷鸦,需要注意的就是我們調(diào)用函數(shù)的方式,如果是正常的方式調(diào)用函數(shù)寝贡,this指代全局的this扒披,如果我們加一個(gè)new,這個(gè)函數(shù)就變成了一個(gè)構(gòu)造函數(shù)圃泡,我們就創(chuàng)建了一個(gè)實(shí)例碟案,this指代這個(gè)實(shí)例,這個(gè)和其他面向?qū)ο蟮恼Z(yǔ)言很像颇蜡。另外价说,寫JavaScript很常做的一件事就是綁定事件處理程序,也就是諸如button.addEventListener(‘click’, fn, false)之類的风秤,如果在fn里面需要使用this鳖目,this指代事件處理程序?qū)?yīng)的對(duì)象,也就是button缤弦。

prototype this

你創(chuàng)建的每一個(gè)函數(shù)都是函數(shù)對(duì)象领迈。它們會(huì)自動(dòng)獲得一個(gè)特殊的屬性prototype,你可以給這個(gè)屬性賦值甸鸟。當(dāng)你用new的方式調(diào)用一個(gè)函數(shù)的時(shí)候惦费,你就能通過this訪問你給prototype賦的值了。

1 function Thing() {
2   console.log(this.foo);
3 }
4 
5 Thing.prototype.foo = "bar";
6 
7 var thing = new Thing(); //logs "bar"
8 console.log(thing.foo);  //logs "bar"

當(dāng)你使用new為你的函數(shù)創(chuàng)建多個(gè)實(shí)例的時(shí)候抢韭,這些實(shí)例會(huì)共享你給prototype設(shè)定的值薪贫。對(duì)于下面的例子,當(dāng)你調(diào)用this.foo的時(shí)候刻恭,都會(huì)返回相同的值瞧省,除非你在某個(gè)實(shí)例里面重寫了自己的this.foo

 1 function Thing() {
 2 }
 3 Thing.prototype.foo = "bar";
 4 Thing.prototype.logFoo = function () {
 5     console.log(this.foo);
 6 }
 7 Thing.prototype.setFoo = function (newFoo) {
 8     this.foo = newFoo;
 9 }
10 
11 var thing1 = new Thing();
12 var thing2 = new Thing();
13 
14 thing1.logFoo(); //logs "bar"
15 thing2.logFoo(); //logs "bar"
16 
17 thing1.setFoo("foo");
18 thing1.logFoo(); //logs "foo";
19 thing2.logFoo(); //logs "bar";
20 
21 thing2.foo = "foobar";
22 thing1.logFoo(); //logs "foo";
23 thing2.logFoo(); //logs "foobar";

實(shí)例里面的this是一個(gè)特殊的對(duì)象。你可以把this想成一種獲取prototype的值的一種方式鳍贾。當(dāng)你在一個(gè)實(shí)例里面直接給this添加屬性的時(shí)候鞍匾,會(huì)隱藏prototype中與之同名的屬性。如果你想訪問prototype中的這個(gè)屬性值而不是你自己設(shè)定的屬性值骑科,你可以通過在實(shí)例里面刪除你自己添加的屬性的方式來實(shí)現(xiàn)橡淑。

 1 function Thing() {
 2 }
 3 Thing.prototype.foo = "bar";
 4 Thing.prototype.logFoo = function () {
 5     console.log(this.foo);
 6 }
 7 Thing.prototype.setFoo = function (newFoo) {
 8     this.foo = newFoo;
 9 }
10 Thing.prototype.deleteFoo = function () {
11     delete this.foo;
12 }
13 var thing = new Thing();
14 thing.setFoo("foo");
15 thing.logFoo(); //logs "foo";
16 thing.deleteFoo();
17 thing.logFoo(); //logs "bar";
18 thing.foo = "foobar";
19 thing.logFoo(); //logs "foobar";
20 delete thing.foo;
21 thing.logFoo(); //logs "bar";

或者你也能直接通過引用函數(shù)對(duì)象的prototype 來獲得你需要的值。

 1 function Thing() {
 2 }
 3 Thing.prototype.foo = "bar";
 4 Thing.prototype.logFoo = function () {
 5     console.log(this.foo, Thing.prototype.foo);
 6 }
 7 
 8 var thing = new Thing();
 9 thing.foo = "foo";
10 thing.logFoo(); //logs "foo bar";

通過一個(gè)函數(shù)創(chuàng)建的實(shí)例會(huì)共享這個(gè)函數(shù)的prototype屬性的值咆爽,如果你給這個(gè)函數(shù)的prototype賦值一個(gè)Array梁棠,那么所有的實(shí)例都會(huì)共享這個(gè)Array置森,除非你在實(shí)例里面重寫了這個(gè)Array,這種情況下符糊,函數(shù)的prototype的Array就會(huì)被隱藏掉凫海。

1 function Thing() {
2 }
3 Thing.prototype.things = [];
4 
5 
6 var thing1 = new Thing();
7 var thing2 = new Thing();
8 thing1.things.push("foo");
9 console.log(thing2.things); //logs ["foo"]

給一個(gè)函數(shù)的prototype賦值一個(gè)Array通常是一個(gè)錯(cuò)誤的做法。如果你想每一個(gè)實(shí)例有他們專屬的Array男娄,你應(yīng)該在函數(shù)里面創(chuàng)建而不是在prototype里面創(chuàng)建行贪。

 1 function Thing() {
 2     this.things = [];
 3 }
 4 
 5 
 6 var thing1 = new Thing();
 7 var thing2 = new Thing();
 8 thing1.things.push("foo");
 9 console.log(thing1.things); //logs ["foo"]
10 console.log(thing2.things); //logs []

實(shí)際上你可以通過把多個(gè)函數(shù)的prototype鏈接起來的從而形成一個(gè)原型鏈,因此this就會(huì)魔法般地沿著這條原型鏈往上查找直到找你你需要引用的值模闲。

 1 function Thing1() {
 2 }
 3 Thing1.prototype.foo = "bar";
 4 
 5 function Thing2() {
 6 }
 7 Thing2.prototype = new Thing1();
 8 
 9 
10 var thing = new Thing2();
11 console.log(thing.foo); //logs "bar"

一些人利用原型鏈的特性來在JavaScript模仿經(jīng)典的面向?qū)ο蟮睦^承方式建瘫。任何給用于構(gòu)建原型鏈的函數(shù)的this的賦值的語(yǔ)句都會(huì)隱藏原型鏈上游的相同的屬性。

 1 function Thing1() {
 2 }
 3 Thing1.prototype.foo = "bar";
 4 
 5 function Thing2() {
 6     this.foo = "foo";
 7 }
 8 Thing2.prototype = new Thing1();
 9 
10 function Thing3() {
11 }
12 Thing3.prototype = new Thing2();
13 
14 
15 var thing = new Thing3();
16 console.log(thing.foo); //logs "foo"

我喜歡把被賦值給prototype的函數(shù)叫做方法尸折。在上面的例子中暖混,我已經(jīng)使用過方法了,如logFoo翁授。這些方法有著相同的prototype,即創(chuàng)建這些實(shí)力的原始函數(shù)晾咪。我通常把這些原始函數(shù)叫做構(gòu)造函數(shù)收擦。在prototype里面定義的方法里面使用this會(huì)影響到當(dāng)前實(shí)例的原型鏈的上游的this。這意味著你直接給this賦值的時(shí)候谍倦,隱藏了原型鏈上游的相同的屬性值塞赂。這個(gè)實(shí)例的任何方法都會(huì)使用這個(gè)最新的值而不是原型里面定義的這個(gè)相同的值。

 1 function Thing1() {
 2 }
 3 Thing1.prototype.foo = "bar";
 4 Thing1.prototype.logFoo = function () {
 5     console.log(this.foo);
 6 }
 7 
 8 function Thing2() {
 9     this.foo = "foo";
10 }
11 Thing2.prototype = new Thing1();
12 
13 
14 var thing = new Thing2();
15 thing.logFoo(); //logs "foo";

在JavaScript里面你可以嵌套函數(shù)昼蛀,也就是你可以在函數(shù)里面定義函數(shù)宴猾。嵌套函數(shù)可以通過閉包捕獲父函數(shù)的變量,但是這個(gè)函數(shù)沒有繼承this

 1 function Thing() {
 2 }
 3 Thing.prototype.foo = "bar";
 4 Thing.prototype.logFoo = function () {
 5     var info = "attempting to log this.foo:";
 6     function doIt() {
 7         console.log(info, this.foo);
 8     }
 9     doIt();
10 }
11 
12 
13 var thing = new Thing();
14 thing.logFoo();  //logs "attempting to log this.foo: undefined"

在doIt里面的this是global對(duì)象或者在嚴(yán)格模式下面是undefined叼旋。這是造成很多不熟悉JavaScript的人深陷 this陷阱的根源仇哆。在這種情況下事情變得非常糟糕,就像你把一個(gè)實(shí)例的方法當(dāng)作一個(gè)值夫植,把這個(gè)值當(dāng)作函數(shù)參數(shù)傳遞給另外一個(gè)函數(shù)但是卻不把這個(gè)實(shí)例傳遞給這個(gè)函數(shù)一樣讹剔。在這種情況下,一個(gè)方法里面的環(huán)境變成了全局范圍详民,或者在嚴(yán)格模式下面的undefined延欠。

 1 function Thing() {
 2 }
 3 Thing.prototype.foo = "bar";
 4 Thing.prototype.logFoo = function () {  
 5     console.log(this.foo);   
 6 }
 7 
 8 function doIt(method) {
 9     method();
10 }
11 
12 
13 var thing = new Thing();
14 thing.logFoo(); //logs "bar"
15 doIt(thing.logFoo); //logs undefined

一些人喜歡先把this捕獲到一個(gè)變量里面,通常這個(gè)變量叫做self沈跨,來避免上面這種情況的發(fā)生由捎。

 1 function Thing() {
 2 }
 3 Thing.prototype.foo = "bar";
 4 Thing.prototype.logFoo = function () {
 5     var self = this;
 6     var info = "attempting to log this.foo:";
 7     function doIt() {
 8         console.log(info, self.foo);
 9     }
10     doIt();
11 }
12 
13 
14 var thing = new Thing();
15 thing.logFoo();  //logs "attempting to log this.foo: bar"

但是當(dāng)你需要把一個(gè)方法作為一個(gè)值傳遞給一個(gè)函數(shù)的時(shí)候并不管用。

 1 function Thing() {
 2 }
 3 Thing.prototype.foo = "bar";
 4 Thing.prototype.logFoo = function () { 
 5     var self = this;
 6     function doIt() {
 7         console.log(self.foo);
 8     }
 9     doIt();
10 }
11 
12 function doItIndirectly(method) {
13     method();
14 }
15 
16 
17 var thing = new Thing();
18 thing.logFoo(); //logs "bar"
19 doItIndirectly(thing.logFoo); //logs undefined

你可以通過bind將實(shí)例和方法一切傳遞給函數(shù)來解決這個(gè)問題饿凛,bind是一個(gè)函數(shù)定義在所有函數(shù)和方法的函數(shù)對(duì)象上面

 1 function Thing() {
 2 }
 3 Thing.prototype.foo = "bar";
 4 Thing.prototype.logFoo = function () { 
 5     console.log(this.foo);
 6 }
 7 
 8 function doIt(method) {
 9     method();
10 }
11 
12 
13 var thing = new Thing();
14 doIt(thing.logFoo.bind(thing)); //logs bar

你同樣可以使用apply和call來在新的上下文中調(diào)用方法或函數(shù)狞玛。

 1 function Thing() {
 2 }
 3 Thing.prototype.foo = "bar";
 4 Thing.prototype.logFoo = function () { 
 5     function doIt() {
 6         console.log(this.foo);
 7     }
 8     doIt.apply(this);
 9 }
10 
11 function doItIndirectly(method) {
12     method();
13 }
14 
15 
16 var thing = new Thing();
17 doItIndirectly(thing.logFoo.bind(thing)); //logs bar

你可以用bind來代替任何一個(gè)函數(shù)或者方法的this软驰,即便它沒有賦值給實(shí)例的初始prototype。

 1 function Thing() {
 2 }
 3 Thing.prototype.foo = "bar";
 4 
 5 
 6 function logFoo(aStr) {
 7     console.log(aStr, this.foo);
 8 }
 9 
10 
11 var thing = new Thing();
12 logFoo.bind(thing)("using bind"); //logs "using bind bar"
13 logFoo.apply(thing, ["using apply"]); //logs "using apply bar"
14 logFoo.call(thing, "using call"); //logs "using call bar"
15 logFoo("using nothing"); //logs "using nothing undefined"

你應(yīng)該避免在構(gòu)造函數(shù)里面返回任何東西为居,因?yàn)檫@可能代替本來應(yīng)該返回的實(shí)例碌宴。

 1 function Thing() {
 2     return {};
 3 }
 4 Thing.prototype.foo = "bar";
 5 
 6 
 7 Thing.prototype.logFoo = function () {
 8     console.log(this.foo);
 9 }
10 
11 
12 var thing = new Thing();
13 thing.logFoo(); //Uncaught TypeError: undefined is not a function

奇怪的是,如果你在構(gòu)造函數(shù)里面返回了一個(gè)原始值蒙畴,上面所述的情況并不會(huì)發(fā)生并且返回語(yǔ)句被忽略了贰镣。最好不要在你將通過new調(diào)用的構(gòu)造函數(shù)里面返回任何類型的數(shù)據(jù),即便你知道自己正在做什么膳凝。如果你想創(chuàng)建一個(gè)工廠模式碑隆,通過一個(gè)函數(shù)來創(chuàng)建一個(gè)實(shí)例,這個(gè)時(shí)候不要使用new來調(diào)用函數(shù)蹬音。當(dāng)然這個(gè)建議是可選的上煤。

你可以通過使用Object.create來避免使用new,這樣同樣能夠創(chuàng)建一個(gè)實(shí)例著淆。

 1 function Thing() {
 2 }
 3 Thing.prototype.foo = "bar";
 4 
 5 
 6 Thing.prototype.logFoo = function () {
 7     console.log(this.foo);
 8 }
 9 
10 
11 var thing =  Object.create(Thing.prototype);
12 thing.logFoo(); //logs "bar"

在這種情況下并不會(huì)調(diào)用構(gòu)造函數(shù)

 1 function Thing() {
 2     this.foo = "foo";
 3 }
 4 Thing.prototype.foo = "bar";
 5 
 6 
 7 Thing.prototype.logFoo = function () {
 8     console.log(this.foo);
 9 }
10 
11 
12 var thing =  Object.create(Thing.prototype);
13 thing.logFoo(); //logs "bar"

因?yàn)镺bject.create不會(huì)調(diào)用構(gòu)造函數(shù)的特性在你繼承模式下你想通過原型鏈重寫構(gòu)造函數(shù)的時(shí)候非常有用劫狠。

 1 function Thing1() {
 2     this.foo = "foo";
 3 }
 4 Thing1.prototype.foo = "bar";
 5 
 6 function Thing2() {
 7     this.logFoo(); //logs "bar"
 8     Thing1.apply(this);
 9     this.logFoo(); //logs "foo"
10 }
11 Thing2.prototype = Object.create(Thing1.prototype);
12 Thing2.prototype.logFoo = function () {
13     console.log(this.foo);
14 }
15 
16 var thing = new Thing2();
object this

在一個(gè)對(duì)象的一個(gè)函數(shù)里,你可以通過this來引用這個(gè)對(duì)象的其他屬性永部。這個(gè)用new來新建一個(gè)實(shí)例是不一樣的独泞。

1 var obj = {
2     foo: "bar",
3     logFoo: function () {
4         console.log(this.foo);
5     }
6 };
7 
8 obj.logFoo(); //logs "bar"

注意,沒有使用new苔埋,沒有使用Object.create懦砂,也沒有使用函數(shù)調(diào)用創(chuàng)建一個(gè)對(duì)象。你也可以將對(duì)象當(dāng)作一個(gè)實(shí)例將函數(shù)綁定到上面组橄。

1 var obj = {
2     foo: "bar"
3 };
4 
5 function logFoo() {
6     console.log(this.foo);
7 }
8 
9 logFoo.apply(obj); //logs "bar"

當(dāng)你用這種方式使用this的時(shí)候荞膘,并不會(huì)越出當(dāng)前的對(duì)象。只有有相同直接父元素的屬性才能通過this共享變量

 1 var obj = {
 2     foo: "bar",
 3     deeper: {
 4         logFoo: function () {
 5             console.log(this.foo);
 6         }
 7     }
 8 };
 9 
10 obj.deeper.logFoo(); //logs undefined

你可以直接通過對(duì)象引用你需要的屬性

var obj = {
    foo: "bar",
    deeper: {
        logFoo: function () {
            console.log(obj.foo);
        }
    }
};

obj.deeper.logFoo(); //logs "bar"
DOM event this

在一個(gè)HTML DOM事件處理程序里面玉工,this始終指向這個(gè)處理程序被所綁定到的HTML DOM節(jié)點(diǎn)

 1 function Listener() {
 2     document.getElementById("foo").addEventListener("click",
 3        this.handleClick);
 4 }
 5 Listener.prototype.handleClick = function (event) {
 6     console.log(this); //logs "<div id="foo"></div>"
 7 }
 8 
 9 var listener = new Listener();
10 document.getElementById("foo").click();

除非你自己通過bind切換了上下文

 1 function Listener() {
 2     document.getElementById("foo").addEventListener("click", 
 3         this.handleClick.bind(this));
 4 }
 5 Listener.prototype.handleClick = function (event) {
 6     console.log(this); //logs Listener {handleClick: function}
 7 }
 8 
 9 var listener = new Listener();
10 document.getElementById("foo").click();
HTML this

在HTML節(jié)點(diǎn)的屬性里面羽资,你可以放置JavaScript代碼,this指向了這個(gè)元素

1 <div id="foo" onclick="console.log(this);"></div>
2 <script type="text/javascript">
3 document.getElementById("foo").click(); //logs <div id="foo"...
4 </script>
override this

你不能重寫this瓮栗,因?yàn)樗潜A糇帧?/p>

1 function test () {
2     var this = {};  // Uncaught SyntaxError: Unexpected token this 
3 }
eval this

你可以通過eval來訪問this

function Thing () {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    eval("console.log(this.foo)"); //logs "bar"
}

var thing = new Thing();
thing.logFoo();

這會(huì)造成一個(gè)安全問題削罩,除非不用eval,沒有其他方式來避免這個(gè)問題费奸。

在通過Function來創(chuàng)建一個(gè)函數(shù)的時(shí)候弥激,同樣能夠訪問this

function Thing () {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = new Function("console.log(this.foo);");

var thing = new Thing();
thing.logFoo(); //logs "bar"
with this

你可以通過with來將this添加到當(dāng)前的執(zhí)行環(huán)境,并且讀寫this的屬性的時(shí)候不需要通過this

 1 function Thing () {
 2 }
 3 Thing.prototype.foo = "bar";
 4 Thing.prototype.logFoo = function () {
 5     with (this) {
 6         console.log(foo);
 7         foo = "foo";
 8     }
 9 }
10 
11 var thing = new Thing();
12 thing.logFoo(); // logs "bar"
13 console.log(thing.foo); // logs "foo"

<small>許多人認(rèn)為這樣使用是不好的因?yàn)閣ith本身就飽受爭(zhēng)議愿阐。</small>

jQuery this

和HTML DOM元素節(jié)點(diǎn)的事件處理程序一樣微服,在許多情況下JQuery的this都指向HTML元素節(jié)點(diǎn)。這在事件處理程序和一些方便的方法中都是管用的缨历,比如$.each

 1 <div class="foo bar1"></div>
 2 <div class="foo bar2"></div>
 3 <script type="text/javascript">
 4 $(".foo").each(function () {
 5     console.log(this); //logs <div class="foo...
 6 });
 7 $(".foo").on("click", function () {
 8     console.log(this); //logs <div class="foo...
 9 });
10 $(".foo").each(function () {
11     this.click();
12 });
13 </script>
thisArg this

如果你用過underscore.js 或者 lo-dash 你可能知道許多類庫(kù)的方法可以通過一個(gè)叫做thisArg 的函數(shù)參數(shù)來傳遞實(shí)例以蕴,這個(gè)函數(shù)參數(shù)會(huì)作為this的上下文糙麦。舉個(gè)例子,這適用于_.each丛肮。原生的JavaScript在ECMAScript 5的時(shí)候也允許函數(shù)傳遞一個(gè)thisArg參數(shù)了赡磅,比如forEach。事實(shí)上宝与,之前闡述的bind焚廊,apply和call的使用已經(jīng)給你創(chuàng)造了傳遞thisArg參數(shù)給函數(shù)的機(jī)會(huì)。這個(gè)參數(shù)將this綁定為你所傳遞的對(duì)象习劫。

 1 function Thing(type) {
 2     this.type = type;
 3 }
 4 Thing.prototype.log = function (thing) {
 5     console.log(this.type, thing);
 6 }
 7 Thing.prototype.logThings = function (arr) {
 8    arr.forEach(this.log, this); // logs "fruit apples..."
 9    _.each(arr, this.log, this); //logs "fruit apples..."
10 }
11 
12 var thing = new Thing("fruit");
13 thing.logThings(["apples", "oranges", "strawberries", "bananas"]);

這使得代碼變得更加簡(jiǎn)介咆瘟,因?yàn)楸苊饬艘淮蠖裝ind語(yǔ)句、函數(shù)嵌套和this暫存的使用诽里。

原文鏈接:http://bjorn.tipling.com/all-this
文章翻譯轉(zhuǎn)載于 yuanzm 的專欄袒餐,鏈接:https://segmentfault.com/a/1190000002640298

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市谤狡,隨后出現(xiàn)的幾起案子灸眼,更是在濱河造成了極大的恐慌,老刑警劉巖墓懂,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幢炸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡拒贱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門佛嬉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逻澳,“玉大人,你說我怎么就攤上這事暖呕⌒弊觯” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵湾揽,是天一觀的道長(zhǎng)瓤逼。 經(jīng)常有香客問我,道長(zhǎng)库物,這世上最難降的妖魔是什么霸旗? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮戚揭,結(jié)果婚禮上诱告,老公的妹妹穿的比我還像新娘。我一直安慰自己民晒,他們只是感情好精居,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布锄禽。 她就那樣靜靜地躺著,像睡著了一般靴姿。 火紅的嫁衣襯著肌膚如雪沃但。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天佛吓,我揣著相機(jī)與錄音宵晚,去河邊找鬼。 笑死辈毯,一個(gè)胖子當(dāng)著我的面吹牛坝疼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谆沃,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼钝凶,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了唁影?” 一聲冷哼從身側(cè)響起耕陷,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎据沈,沒想到半個(gè)月后哟沫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锌介,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年嗜诀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孔祸。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡隆敢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出崔慧,到底是詐尸還是另有隱情拂蝎,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布惶室,位于F島的核電站温自,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏皇钞。R本人自食惡果不足惜悼泌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望夹界。 院中可真熱鬧券躁,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至慢哈,卻和暖如春蔓钟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卵贱。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工滥沫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人键俱。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓兰绣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親编振。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缀辩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容