NO--11關於"this"你知道多少

爲了更好地理解 this,將 this 使用的場景分紅三類:api

 

  • 在函數內部 this 一個額外的,一般是隱含的參數。瀏覽器

  • 在函數外部(頂級做用域中): 這指的是瀏覽器中的全局對象或者 Node.js 中一個模塊的輸出。安全

  • 在傳遞給eval()的字符串中: eval() 或者獲取 this 當前值值,或者將其設置爲全局對象,取決於 this 是直接調用仍是間接調用。app

 

咱們來看看每一個類別。函數

this 在函數中

這是最經常使用的 this 使用方式,函數經過扮演三種不一樣的角色來表示 JavaScript 中的全部可調用結構體:oop

 

  • 普通函數(this 在非嚴格模式下爲全局對象,在嚴格模式下爲undefined)this

  • 構造函數(this 指向新建立的實例)spa

  • 方法(this 是指方法調用的接收者)prototype

  •  

在函數中,this 一般被認爲是一個額外的,隱含的參數。對象

this 在普通函數中

在普通函數中,this 的值取決於模式:

 

  • 非嚴格模式: this 是指向全局對象 (在瀏覽器中爲window對象)。

function sloppyFunc() {

    console.log(this === window); // true

}

sloppyFunc();

  • 嚴格模式: this 的值爲 undefined。

function strictFunc() {

    'use strict';

    console.log(this === undefined); // true

}

strictFunc();

也就是說,this 是一個設定了默認值(window或undefined)的隱式參數。 可是,能夠經過 call() 或 apply() 進行函數調用,並明確指定this的值:

function func(arg1, arg2) {

    console.log(this); // a

    console.log(arg1); // b

    console.log(arg2); // c

}

func.call('a', 'b', 'c'); // (this, arg1, arg2)

func.apply('a', ['b', 'c']); // (this, arrayWithArgs)

this 在構造函數中

若是經過new運算符調用函數,則函數將成爲構造函數。 該運算符建立一個新的對象,並經過this傳遞給構造函數:

var savedThis;

function Constr() {

    savedThis = this;

}

var inst = new Constr();

console.log(savedThis === inst); // true

在JavaScript中實現,new運算符大體以下所示(更精確的實現稍微複雜一點):

function newOperator(Constr, arrayWithArgs) {

    var thisValue = Object.create(Constr.prototype);

    Constr.apply(thisValue, arrayWithArgs);

    return thisValue;

}

this 在方法中

在方法中,相似於傳統的面向對象的語言:this指向接受者,方法被調用的對象。

var obj = {

    method: function () {

        console.log(this === obj); // true

    }

}

obj.method();

this 在頂級做用域中

在瀏覽器中,頂層做用域是全局做用域,它指向 global object(如window):

console.log(this === window); // true

在Node.js中,一般在模塊中執行代碼。 所以,頂級做用域是一個特殊的模塊做用域:

// `global` (不是 `window`) 指全局對象:

console.log(Math === global.Math); // true

 

// `this` 不指向全局對象:

console.log(this !== global); // true

// `this` refers to a module’s exports:

console.log(this === module.exports); // true

this 在 eval() 中

eval() 能夠被直接(經過真正的函數調用)或間接(經過其餘方式)調用。

若是間接調用evaleval() ,則this指向全局對象:

(0,eval)('this === window')

true

不然,若是直接調用eval() ,則this與eval()的環境中保持一致。 例如:

// 普通函數

function sloppyFunc() {

    console.log(eval('this') === window); // true

}

sloppyFunc();

 

function strictFunc() {

    'use strict';

    console.log(eval('this') === undefined); // true

}

strictFunc();

 

// 構造器

var savedThis;

function Constr() {

    savedThis = eval('this');

}

var inst = new Constr();

console.log(savedThis === inst); // true

 

// 方法

var obj = {

    method: function () {

        console.log(eval('this') === obj); // true

    }

}

obj.method();

與this相關的陷阱

有三個你須要知道的與this相關的陷阱。請注意,在各類狀況下,嚴格模式更安全,由於this在普通函數中爲undefined,而且會在出現問題時警告。

陷阱:忘記new操做符

若是你調用一個構造函數時忘記了new操做符,那麼你意外地將this用在一個普通的函數。this會沒有正確的值。 在非嚴格模式下,this指向window對象,你將建立全局變量:

function Point(x, y) {

    this.x = x;

    this.y = y;

}

var p = Point(7, 5); // 忘記new!

console.log(p === undefined); // true

 

// 建立了全局變量:

console.log(x); // 7

console.log(y); // 5

幸運的是,在嚴格模式下會獲得警告(this === undefined):

function Point(x, y) {

    'use strict';

    this.x = x;

    this.y = y;

}

var p = Point(7, 5);

// TypeError: Cannot set property 'x' of undefined

陷阱:不正確地提取方法

若是獲取方法的值(不是調用它),則能夠將該方法轉換爲函數。 調用該值將致使函數調用,而不是方法調用。 當將方法做爲函數或方法調用的參數傳遞時,可能會發生這種提取。 實際例子包括setTimeout()和事件註冊處理程序。 我將使用函數callItt() 來模擬此用例:

/**相似setTimeout() 和 setImmediate() */

function callIt(func) {

    func();

}

若是在非嚴格模式下把一個方法做爲函數來調用,那麼this將指向全局對象並建立全局變量:

var counter = {

    count: 0,

    // Sloppy-mode method

    inc: function () {

        this.count++;

    }

}

 

callIt(counter.inc);

 

// Didn’t work:

console.log(counter.count); // 0

 

// Instead, a global variable has been created

// (NaN is result of applying ++ to undefined):

console.log(count); // NaN

若是在嚴格模式下把一個方法做爲函數來調用,this爲undefined。 同時會獲得一個警告:

var counter = {

    count: 0,

    // Strict-mode method

    inc: function () {

        'use strict';

        this.count++;

    }

}

 

callIt(counter.inc);

 

// TypeError: Cannot read property 'count' of undefined

console.log(counter.count);

修正方法是使用bind():

var counter = {

    count: 0,

    inc: function () {

        this.count++;

    }

}

 

callIt(counter.inc.bind(counter));

 

// 成功了!

console.log(counter.count); // 1

bind()建立了一個新的函數,它老是能獲得一個指向counter的this。

陷阱:shadowing this

當在一個方法中使用普通函數時,很容易忘記前者具備其本身this(即便其不須要this)。 所以,你不能從前者引用該方法的this,由於該this會被遮蔽。 讓咱們看看出現問題的例子:

var obj = {

    name: 'Jane',

    friends: [ 'Tarzan', 'Cheeta' ],

    loop: function () {

        'use strict';

        this.friends.forEach(

            function (friend) {

                console.log(this.name+' knows '+friend);

            }

        );

    }

};

obj.loop();

// TypeError: Cannot read property 'name' of undefined

在前面的例子中,獲取this.name失敗,由於函數的this個是undefined,它與方法loop()的不一樣。 有三種方法能夠修正this。

 

修正1: that = this。 將它分配給一個沒有被遮蔽的變量(另外一個流行名稱是self)並使用該變量。

loop: function () {

    'use strict';

    var that = this;

    this.friends.forEach(function (friend) {

        console.log(that.name+' knows '+friend);

    });

}

修正2: bind()。 使用bind()來建立一個this老是指向正確值的函數(在下面的例子中該方法的this)。

loop: function () {

    'use strict';

    this.friends.forEach(function (friend) {

        console.log(this.name+' knows '+friend);

    }.bind(this));

}

修正3: forEach的第二個參數。 此方法具備第二個參數,this值將做爲此值傳遞給回調函數。

loop: function () {

    'use strict';

    this.friends.forEach(function (friend) {

        console.log(this.name+' knows '+friend);

    }, this);

}

最佳實踐

從概念上講,我認爲普通函數沒有它本身的this,而且想到上述修復是爲了保持這種想法。 ECMAScript 6經過箭頭函數支持這種方法 - 沒有它們本身的this。 在這樣的函數裏面,你能夠自由使用this,由於不會被屏蔽:

loop: function () {

    'use strict';

    // The parameter of forEach() is an arrow function

    this.friends.forEach(friend => {

        // `this` is loop’s `this`

        console.log(this.name+' knows '+friend);

    });

}

我不喜歡使用this做爲普通函數的附加參數的API:

beforeEach(function () {

    this.addMatchers({

        toBeInRange: function (start, end) {

            ...

        }

    });

});

將這樣的隱含參數變成明確的參數使得事情更加明顯,而且與箭頭函數兼容。

beforeEach(api => {

    api.addMatchers({

        toBeInRange(start, end) {

            ...

        }

    });

});

相關文章
相關標籤/搜索