當有人問起你JavaScript有什麼特色的時候,你可能立馬就想到了單線程、事件驅動、面向對象等一堆詞語,可是若是真的讓你解釋一下這些概念,可能真解釋不清楚。有句話這麼說:若是你不能向一個6歲小孩解釋清楚一個東西,那麼你本身也不懂這個東西。這句話或許有點誇張,可是極其有道理。我的以爲,若是須要掌握一門語言,掌握它的API只是學了皮毛,理解這門語言的精髓纔是重點。說起JavaScript的精髓,this、閉包、做用域鏈、函數是當之無愧的。這門語言正式由於這幾個東西而變得魅力無窮。javascript
博客的標題是《JavaScript中的this陷阱的最全收集--沒有之一》,很顯然這篇博客闡述的是this。相信作過JavaScript開發的人都遇到過很多this的陷阱,我本身自己也遇到過很多坑,可是若是非要給出一個系統的總結的話,尚未足夠的底蘊。很是幸運的是,今天早上起來看《Hacker News》的時候,恰巧看到了一篇有關於JavaScript this的解析:all this。因而,本着學習和共享的精神,決定將它翻譯成中文。翻譯的目的絕對不是爲了當大天然的搬運工,在這個過程當中會徹底弄明白別人的著做,加深認識,同時將好東西分享給別人,才能讓更多的學習者站在巨人的肩膀上前進。按照我本身的習慣,會翻譯的過程當中加上一些本身解釋(引用部分),畢竟中西方人的思考方式是有差別的。固然文章標題所述的最全也不是吹的,文章很是長。java
原文翻譯:node
JavaScript來自一門健全的語言,因此你可能以爲JavaScript中的this和其餘面向對象的語言如java的this同樣,是指存儲在實例屬性中的值。事實並不是如此,在JavaScript中,最好把this當成哈利波特中的博格特的揹包,有着深不可測的魔力。web
下面的部分是我但願個人同事在使用JavaScript的this的時候應當知道的。內容不少,是我學習好幾年總結出來的。算法
JavaScript中不少時候會用到this,下面詳細介紹每一種狀況。在這裏我想首先介紹一下宿主環境這個概念。一門語言在運行的時候,須要一個
環境
,叫作宿主環境
。對於JavaScript,宿主環境最多見的是web瀏覽器
,瀏覽器提供了一個JavaScript運行的環境,這個環境裏面,須要提供一些接口
,好讓JavaScript引擎
可以和宿主環境
對接。JavaScript引擎纔是真正執行JavaScript代碼的地方,常見的引擎有V8
(目前最快JavaScript引擎、Google生產)、JavaScript core
。JavaScript引擎主要作了下面幾件事情:編程
- 一套與宿主環境相聯繫的規則;
- JavaScript引擎內核(基本語法規範、邏輯、命令和算法);
- 一組內置對象和API;
- 其餘約定。
可是環境不是惟一的,也就是JavaScript不只僅可以在瀏覽器裏面跑,也能在其餘提供了宿主環境的程序裏面跑,最多見的就是nodejs。一樣做爲一個宿主環境,nodejs也有本身的JavaScript引擎--V8。根據官方的定義:
Node.js is a platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applicationssegmentfault
global this瀏覽器
1 <script type="text/javascript"> 2 console.log(this === window); //true 3 </script>
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>
var
或者let
(ECMAScript 6),你就是在給全局的this添加或者改變屬性值。 1 <script type="text/javascript"> 2 foo = "bar"; 3 4 function testThis() { 5 foo = "foo"; 6 } 7 8 console.log(this.foo); //logs "bar" 9 testThis(); 10 console.log(this.foo); //logs "foo" 11 </script>
> this { ArrayBuffer: [Function: ArrayBuffer], Int8Array: { [Function: Int8Array] BYTES_PER_ELEMENT: 1 }, Uint8Array: { [Function: Uint8Array] BYTES_PER_ELEMENT: 1 }, ... > global === this true
1 test.js腳本內容: 2 3 console.log(this); 4 console.log(this === global); 5 6 REPL運行腳本: 7 8 $ node test.js 9 {} 10 false
1 test.js: 2 3 var foo = "bar"; 4 console.log(this.foo); 5 6 $ node test.js 7 undefined
1 > var foo = "bar"; 2 > this.foo 3 bar 4 > global.foo 5 bar
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
上面的八種狀況可能你們已經繞暈了,總結起來就是:在
瀏覽器
裏面this是老大,它等價於window對象,若是你聲明一些全局變量(無論在任何地方),這些變量都會做爲this的屬性。在node裏面,有兩種
執行JavaScript代碼的方式,一種是直接執行寫好的JavaScript文件
,另一種是直接在裏面執行一行行代碼
。對於直接運行一行行JavaScript代碼的方式,global纔是老大,this和它是等價的。在這種狀況下,和瀏覽器比較類似,也就是聲明一些全局變量會自動添加給老大global,順帶也會添加給this。可是在node裏面直接腳本文件就不同了,你聲明的全局變量不會自動添加到this,可是會添加到global對象。因此相同點是,在全局範圍內,全局變量終究是屬於老大的。安全
function 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
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>
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>
我更喜歡把新的值稱做一個實例。
函數裏面的this其實相對比較好理解,若是咱們在一個函數裏面使用this,須要注意的就是咱們調用函數的方式,若是是正常的方式調用函數,this指代全局的this,若是咱們加一個new,這個函數就變成了一個構造函數,咱們就建立了一個實例,this指代這個實例,這個和其餘面向對象的語言很像。另外,寫JavaScript很常作的一件事就是綁定事件處理程序,也就是諸如
button.addEventListener
(‘click
’,fn
,false
)之類的,若是在fn裏面須要使用this,this指代事件處理程序對應的對象,也就是button。
prototype this
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"
this.foo
的時候,都會返回相同的值,除非你在某個實例裏面重寫了本身的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";
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";
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";
經過一個函數建立的實例會共享這個函數的prototype屬性的值,若是你給這個函數的prototype賦值一個Array,那麼全部的實例都會共享這個Array,除非你在實例裏面重寫了這個Array,這種狀況下,函數的prototype的Array就會被隱藏掉。
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"]
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 []
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"
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"
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";
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"
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
博主很是喜歡用這種方式
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"
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
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
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
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"
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
奇怪的是,若是你在構造函數裏面返回了一個原始值,上面所述的狀況並不會發生而且返回語句被忽略了。最好不要在你將經過new調用的構造函數裏面返回任何類型的數據,即使你知道本身正在作什麼。若是你想建立一個工廠模式,經過一個函數來建立一個實例,這個時候不要使用new來調用函數。固然這個建議是可選的。
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"
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"
由於Object.create不會調用構造函數的特性在你繼承模式下你想經過原型鏈重寫構造函數的時候很是有用。
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
1 var obj = { 2 foo: "bar", 3 logFoo: function () { 4 console.log(this.foo); 5 } 6 }; 7 8 obj.logFoo(); //logs "bar"
1 var obj = { 2 foo: "bar" 3 }; 4 5 function logFoo() { 6 console.log(this.foo); 7 } 8 9 logFoo.apply(obj); //logs "bar"
當你用這種方式使用this的時候,並不會越出當前的對象。只有有相同直接父元素的屬性才能經過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
var obj = { foo: "bar", deeper: { logFoo: function () { console.log(obj.foo); } } }; obj.deeper.logFoo(); //logs "bar"
DOM event this
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();
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
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
1 function test () { 2 var this = {}; // Uncaught SyntaxError: Unexpected token this 3 } 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();
這會形成一個安全問題,除非不用eval,沒有其餘方式來避免這個問題。
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
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"
許多人認爲這樣使用是很差的由於with自己就飽受爭議。
jQuery this
$.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
你可能知道許多類庫的方法能夠經過一個叫作thisArg
的函數參數來傳遞實例,這個函數參數會做爲this的上下文。舉個例子,這適用於_.each
。原生的JavaScript在ECMAScript 5的時候也容許函數傳遞一個thisArg參數了,好比forEach。事實上,以前闡述的bind,apply和call的使用已經給你創造了傳遞thisArg參數給函數的機會。這個參數將this綁定爲你所傳遞的對象。
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"]);
這使得代碼變得更加簡介,由於避免了一大堆bind語句、函數嵌套和this暫存的使用。
做者:yuanzm
文章源自:http://segmentfault.com/a/1190000002640298
做者:連接:https://www.imooc.com/article/1379來源:慕課網