【譯文】this全解

一. 全局 this

1.在瀏覽器中,在一個全局環境中,this就是window對象。javascript

<script type="text/javascript">
console.log(this === window); //true
</script>

2.在瀏覽器中,在全局中使用var至關於分配給this或者windowjava

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

3.假如你建立一個新的變量,不使用var或者let(ECMAScript6),你是添加或者改變全局this的屬性node

<script type="text/javascript">
    foo = "bar";

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

    console.log(this.foo); //logs "bar"
    testThis();
    console.log(this.foo); //logs "foo"
</script>

4.在node中使用repl,this是最頂級的命名空間,你能夠認爲是global數組

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

5.在node中執行腳本,在全局中this是一個空對象,而不與global相等瀏覽器

test.js:
console.log(this);
console.log(this === global);
$ node test.js
{}
false

6.在node中,全局環境中的var並不是像在瀏覽器中執行腳本同樣,分配給this安全

test.js:
var foo = "bar";
console.log(this.foo);
$ node test.js
undefined

可是在repl中是同樣的app

> var foo = "bar";
> this.foo
bar
> global.foo
bar

7.在node中,使用腳本執行,不用var或者let建立的變量會添加到global而不是this.函數

test.js
foo = "bar";
console.log(this.foo);
console.log(global.foo);
$ node test.js
undefined
bar

在repl中,它是分配到這兩個上的。this

二. 函數中的this

除了DOM事件處理程序或者一個thisArg已經設置的狀況外,在node和瀏覽器中,函數中(不實例化new)的this是全局範圍的。prototype

<script type="text/javascript">
    foo = "bar";

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

    console.log(this.foo); //logs "bar"
    testThis();
    console.log(this.foo); //logs "foo"
</script>
test.js:
foo = "bar";

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

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

除非你使用user strictthis會變爲underfined

<script type="text/javascript">
    foo = "bar";

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

    console.log(this.foo); //logs "bar"
    testThis();  //Uncaught TypeError: Cannot set property 'foo' of undefined 
</script>

當你new一個函數的時候,this會成爲一個新的上下文,不等同於全局this

<script type="text/javascript">
    foo = "bar";

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

    console.log(this.foo); //logs "bar"
    new testThis();
    console.log(this.foo); //logs "bar"

    console.log(new testThis().foo); //logs "foo"
</script>

三. 原型中的this

函數對象有一個特殊的屬性prototype,當你建立一個函數實例,能夠訪問prototype屬性,可使用this進行訪問

function Thing() {
      console.log(this.foo);
    }

    Thing.prototype.foo = "bar";

    var thing = new Thing(); //logs "bar"
    console.log(thing.foo);  //logs "bar"

加入建立多個實例化,它們共享原型上的值,this.foo都會返回相同的值,除非你在實例化函數上進行覆蓋。

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

var thing1 = new Thing();
var thing2 = new Thing();

thing1.logFoo(); //logs "bar"
thing2.logFoo(); //logs "bar"

thing1.setFoo("foo");
thing1.logFoo(); //logs "foo";
thing2.logFoo(); //logs "bar";

thing2.foo = "foobar";
thing1.logFoo(); //logs "foo";
thing2.logFoo(); //logs "foobar";

this在一個實例中是一個特殊的對象,this實際是一個關鍵字,能夠認爲this做爲一種方法去訪問prototype,直接分配給this,將會覆蓋原來prototype上的方法。你能夠刪除this掛接的方法,從而恢復訪問默認prototype

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    console.log(this.foo);
}
Thing.prototype.setFoo = function (newFoo) {
    this.foo = newFoo;
}
Thing.prototype.deleteFoo = function () {
    delete this.foo;
}

var thing = new Thing();
thing.setFoo("foo");
thing.logFoo(); //logs "foo";
thing.deleteFoo();
thing.logFoo(); //logs "bar";
thing.foo = "foobar";
thing.logFoo(); //logs "foobar";
delete thing.foo;
thing.logFoo(); //logs "bar";

或者直接引用函數對象的原型。

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

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

建立的實例都共享相同的屬性和方法,若是給prototype分配一個數組,全部實例都可以訪問。

function Thing() {
}
Thing.prototype.things = [];


var thing1 = new Thing();
var thing2 = new Thing();
thing1.things.push("foo");
console.log(thing2.things); //logs ["foo"]

prototype上分配一個數組一般是一個錯誤,若是但願每一個實例都有本身的數組,那在函數中建立。

function Thing() {
    this.things = [];
}


var thing1 = new Thing();
var thing2 = new Thing();
thing1.things.push("foo");
console.log(thing1.things); //logs ["foo"]
console.log(thing2.things); //logs []

this能夠經過原型鏈找到相應的方法。

function Thing1() {
}
Thing1.prototype.foo = "bar";

function Thing2() {
}
Thing2.prototype = new Thing1();


var thing = new Thing2();
console.log(thing.foo); //logs "bar"

在javascript中可使用原型鏈模擬傳統面向對象繼承。
使用函數內含有綁定this的方法或者屬性去建立原型鏈,將會隱藏上層原型鏈定義的內容。

function Thing1() {
}
Thing1.prototype.foo = "bar";

function Thing2() {
    this.foo = "foo";
}
Thing2.prototype = new Thing1();

function Thing3() {
}
Thing3.prototype = new Thing2();


var thing = new Thing3();
console.log(thing.foo); //logs "foo"

我喜歡叫綁定在原型上的函數爲methods.在methods中使用this綁定某個值,將會覆蓋原型上的相關定義。

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

function Thing2() {
    this.foo = "foo";
}
Thing2.prototype = new Thing1();


var thing = new Thing2();
thing.logFoo(); //logs "foo";

在JavaScript嵌套函數中,雖然能夠捕獲到父函數中的變量,可是不繼承this

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    var info = "attempting to log this.foo:";
    function doIt() {
        console.log(info, this.foo);
    }
    doIt();
}


var thing = new Thing();
thing.logFoo();  //logs "attempting to log this.foo: undefined"

函數doIt中的this指向global,在use strict下則爲undefined,這是不少不熟悉this用法的人痛苦的根源之一。
更壞的狀況是,將一個實例方法做爲參數傳入函數。this將指向global,在use strict下則爲undefined

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

function doIt(method) {
    method();
}

var thing = new Thing();
thing.logFoo(); //logs "bar"
doIt(thing.logFoo); //logs undefined

一些人把this賦值給一個變量,一般叫self,可以避免this指向global這個問題。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    var self = this;
    var info = "attempting to log this.foo:";
    function doIt() {
        console.log(info, self.foo);
    }
    doIt();
}

var thing = new Thing();
thing.logFoo();  //logs "attempting to log this.foo: bar"

可是這種方法在將一個實例方法做爲參數傳入函數狀況下,不起做用

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () { 
    var self = this;
    function doIt() {
        console.log(self.foo);
    }
    doIt();
}

function doItIndirectly(method) {
    method();
}


var thing = new Thing();
thing.logFoo(); //logs "bar"
doItIndirectly(thing.logFoo); //logs undefined

解決這個方法,可使用函數綁定的方法bind

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

function doIt(method) {
    method();
}


var thing = new Thing();
doIt(thing.logFoo.bind(thing)); //logs bar

你也可使用apply或者call在新的上下文環境中調用方法或者函數。

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

function doItIndirectly(method) {
    method();
}

var thing = new Thing();
doItIndirectly(thing.logFoo.bind(thing)); //logs bar

可使用bind替換this,適用於任何函數或方法,即便沒有在實例原型上定義。

function Thing() {
}
Thing.prototype.foo = "bar";


function logFoo(aStr) {
    console.log(aStr, this.foo);
}


var thing = new Thing();
logFoo.bind(thing)("using bind"); //logs "using bind bar"
logFoo.apply(thing, ["using apply"]); //logs "using apply bar"
logFoo.call(thing, "using call"); //logs "using call bar"
logFoo("using nothing"); //logs "using nothing undefined"

避免從構造函數返回任何東西,由於它可能會替換所產生的實例。

function Thing() {
    return {};
}
Thing.prototype.foo = "bar";


Thing.prototype.logFoo = function () {
    console.log(this.foo);
}


var thing = new Thing();
thing.logFoo(); //Uncaught TypeError: undefined is not a function

奇怪的是,假如你返回的是原始值(string或者number),返回語句將會被忽略。最好不要從你打算調用的構造函數中返回任何東西,即便你知道你在作什麼。若是你想建立一個工廠模式,使用一個函數來建立實例,不要用new的。固然,這只是我的觀點。
避免使用new`,使用Object.create也能建立一個實例

function Thing() {
}
Thing.prototype.foo = "bar";


Thing.prototype.logFoo = function () {
    console.log(this.foo);
}


var thing =  Object.create(Thing.prototype);
thing.logFoo(); //logs "bar"

然而這不會調用構造函數。

function Thing() {
    this.foo = "foo";
}
Thing.prototype.foo = "bar";


Thing.prototype.logFoo = function () {
    console.log(this.foo);
}


var thing =  Object.create(Thing.prototype);
thing.logFoo(); //logs "bar"

由於Object.create不會調用構造函數,因此這是一個有效的建立繼承模式的方法,可以重寫原型鏈上的構造函數。

function Thing1() {
    this.foo = "foo";
}
Thing1.prototype.foo = "bar";

function Thing2() {
    this.logFoo(); //logs "bar"
    Thing1.apply(this);
    this.logFoo(); //logs "foo"
}
Thing2.prototype = Object.create(Thing1.prototype);
Thing2.prototype.logFoo = function () {
    console.log(this.foo);
}

var thing = new Thing2();

四. 對象中的this

能夠在對象的任何函數中使用this來引用該對象上的其餘屬性。這與使用new實例不一樣。

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

obj.logFoo(); //logs "bar"

不使用new,Object.create ,function 去建立一個對象,也能夠像實例化同樣綁定到對象上。

var obj = {
    foo: "bar"
};

function logFoo() {
    console.log(this.foo);
}

logFoo.apply(obj); //logs "bar"

當你像下面使用this時,沒有順着對象的層次結構。只有直接父對象上的屬性才能經過this進行訪問

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

obj.deeper.logFoo(); //logs undefined

你能夠直接使用你想要的屬性。

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

obj.deeper.logFoo(); //logs "bar"

五. DOM event中的this

在一個HTML DOM event處理程序中,this一般是指DOM element event綁定的對象

function Listener() {
    document.getElementById("foo").addEventListener("click",
       this.handleClick);
}
Listener.prototype.handleClick = function (event) {
    console.log(this); //logs "<div id="foo"></div>"
}

var listener = new Listener();
document.getElementById("foo").click();

除非你綁定新的上下文

function Listener() {
    document.getElementById("foo").addEventListener("click", 
        this.handleClick.bind(this));
}
Listener.prototype.handleClick = function (event) {
    console.log(this); //logs Listener {handleClick: function}
}

var listener = new Listener();
document.getElementById("foo").click();

六. HTML中的this

在HTML屬性中能夠放js代碼,this指向當前的元素

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

this的覆蓋
你不可以複寫this,由於它是一個關鍵詞

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

七. eavl中的this

可使用eavl訪問this

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

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

這種作法有安全隱患,可使用Function訪問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

可使用withthis添加到當前的範圍來讀取和寫入值,而不用顯式調用。

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

var thing = new Thing();
thing.logFoo(); // logs "bar"
console.log(thing.foo); // logs "foo"

不少人認爲這是錯的作法,鑑於with引發的歧義。

九. jQuery中的this

像HTML DOM elements的事件處理程序同樣,jQuery在不少地方使用this指向DOM元素。好比$.each

<div class="foo bar1"></div>
<div class="foo bar2"></div>
<script type="text/javascript">
$(".foo").each(function () {
    console.log(this); //logs <div class="foo...
});
$(".foo").on("click", function () {
    console.log(this); //logs <div class="foo...
});
$(".foo").each(function () {
    this.click();
});
</script>

鑑於筆者翻譯水平有限,有什麼問題歡迎提出指教。

十.參考資料

原文地址:all this

相關文章
相關標籤/搜索