Know this, use this! (總結 this 的常見用法)

this應該是一個討論了好久的話題了。其中,關於this的文章,在不少的博客當中也有不少介紹,可是,之前我都是隻知其一;不知其二的去了解它,就是看博客當中,只介紹了一些狀況下的 this 的使用方式,可是也並無本身去作過總結。恰好是在掘金當中有看到一篇關於this的一些詳細文章,文末會附上連接以及英文原文,這裏純粹是本身進行一個總結,之後方便本身進行回顧以及加深印象。但願這篇文章對於你瞭解this有必定的幫助,文末還有一些練習題噢~但願真的對大家有幫助。(由於寫項目過程當中,一直被 this 坑過,卻找了好久的 bug ,我真是 樂了狗)javascript

在瞭解this以前,相信你們都應該會知道做用域這個知識點的存在,函數在建立以後,會構建本身的執行環境以及做用域,這是一開始就肯定了。可是實際的上下文(context)環境,也能夠理解爲就是this,它是動態肯定的,即在函數運行時才肯定this所指向的對象,而非聲明時所指向的對象。java

關於this,總結起來,主要有如下幾個途徑可以被運用到。閉包

1 對象方法中調用this

若是函數被當中對象的一個方法進行調用,則this值指向該對象。app

var person = {
    name: 'Alice',
    sayName: function() {
        alert('welcome ' + this.name);
    }
}

person.sayName();    // this == person, alert: 'welcome Alice'

在這裏,函數的this指向該對象(即 person);可是有一點須要注意,就是當對象的方法被賦予給一個變量時,其則變爲了函數觸發,此時的this爲 window 或者 undefined(嚴格模式下),以下:函數

var name = 'Bob';
var person;    // 即上面的定義,此不拓展詳細,直接使用

var say = person.sayName;    // this == window || undefined
say();    // 'welcome Bob' || throw an error: Cannot read property 'name' of undefined(...)

2 函數內部使用

在函數內部當中使用了 this,即函數被當作方法使用,不一樣於 1 當中做爲對象的方法使用,此時調用,是在全局做用域下進行調用,即在window下進行調用,由定義能夠知道,在全局做用域下聲明一個函數,其自動加爲window的一個屬性。this此時名正言順的會指向window,嚴格模式下爲 undefinedthis

function sayThis() {
    alert(this == window);    // true
}

結合第一點,函數做爲對象的一個方法使用,這裏存在一個小坑,即閉包,啥是閉包,這個在這裏就不扯開了,最簡單的理解就是 Function that returns function,若是不理解什麼是閉包的話,能夠去翻翻 《JavaScript 高級程序設計》第七章關於閉包的相關內容。第一點當中存在一個小坑,就是將對象的方法賦予給一個變量的時候,其變爲函數觸發,此時的 this 其實是指向 window(非嚴格模式)。prototype

那麼,當函數中返回一個函數,此時在對象當中調用該方法,其就至關因而函數觸發,此時的 this,在不作任何上下文綁定的前提之下,其指向 window(非嚴格模式)。設計

var name = 'Bob',
    person = {
        name: 'Alice',
        sayName: function() {
            console.log(this === person);    // true
            return function() {
                console.log(this === person);    // false
                console.log(this === window);    // true
                console.log(this.name);          // Bob
            };
        }
    };

person.sayName()();

固然,要解決這個問題的方法,很簡單,就是給他綁定一個上下文。code

var name = 'Bob',
    person = {
        name: 'Alice',
        sayName: function() {
            console.log(this === person);    // true
            return function() {
                console.log(this === person);    // true
                console.log(this === window);    // false
                console.log(this.name);          // Alice
            }.bind(this);
        }
    };

person.sayName()();

3 new 當中進行使用

咱們知道在使用 new 方法建立對象的時候,會通過以下這些個過程:orm

  • 建立對象,將 this 值賦予新的對象

  • 調用構造函數,爲 this 添加屬性和方法

  • 返回 this 給當前的對象

function Person(name, age) {
    this.name = name;
    this.age = age;
}

var person1 = new Person('Alice', 29);
console.log(person1.name);    // Alice

這裏要記得使用 new 運算符,不然,其只能算是普通的調用,而不是建立一個新的實例對象。而當作普通函數調用的話,實際上即 第 2 種狀況下,對函數普通調用,此時的 this 指向 window

function Person(name, age) {
    this.name = name;
    this.age = age;
    return this;
}

var person1 = Person('Alice', 29);
console.log(person1.name);    // Alice
console.log(window.name);     // Alice
console.log(person1 === window);    // true

這是正常狀況下,this 會正確返回而且指向該對象,可是在構造函數當中,若是返回了一個對象,那麼 this 會指向返回的那個對象。

function Person(name, age) {
    this.name = name;
    this.age = age;
    return {
        name: 'Bob'
    };
}

var person1 = new Person('Alice');
console.log(person1.name);    // Bob
console.log(person1.age);     // undefined

題外話,相似的,聯想到 var a = new Person(),則 a instanceof Person必定返回 true嗎?留給大家想想咯。

4 使用 callapplybind 改變 this

在引用類型 Function當中,函數存在兩個方法屬性,callapply,在 ECMAScript5當中,加入了 bind 方法。題外話,他們三者區別,應該都知道了吧,不知道的加緊補習呀。

var name = 'Bob';
var person = {
    name: 'Alice',
    age: 29
}

function sayName() {
    console.log(this.name);
}

sayName.call(person);    // Alice

這裏是使用了 call 方法來改變了 this的執行環境,至於使用 apply,效果同樣,只是兩者差異在於傳入參數的不一樣。

func.call(context, arg1, arg2, ...)
func.apply(context, [arg1, arg2, ...])

使用 bind 方法進行上下文的改變,bind 方法與 callapply有着本質的不一樣,其不一樣點是,bind()函數返回的是一個新的函數,即方法,然後二者則都是當即執行函數,使用的時候即調用了該函數,返回方法操做的結果。

而且,使用 bind()方法建立的 上下文,其爲永久的上下文環境,不可修改,即便是使用 call 或者 apply方法,也沒法修改 this 所指向的值。

var name = 'Bob';
var person = {
    name: 'Alice',
    age: 29
}

function sayName() {
    console.log(this.name);
}

var say = sayName.bind(person);
say();        // Alice
sayName();    // Bob

5 箭頭函數

箭頭函數並不建立其自身的上下文,其上下文 this,取決於其在定義時的外部函數。

而且,箭頭函數擁有靜態的上下文,即一次綁定以後,便不可再修改,即便是用了 第 4 種用途當中的改變上下文的方法,也不爲之動容。

var num = [1, 2, 3];

(function() {
    var showNumber = () => {
        console.log(this === num);    // true
        console.log(this);            // [1, 2, 3]
    }
    console.log(this === num);        // true
    showNumber();                     // true && [1, 2, 3]
    showNumber.call([1, 2]);          // true && [1, 2, 3]
    showNumber.apply([1, 2]);         // true && [1, 2, 3]
    showNumber.bind([1, 2])();        // true && [1, 2, 3]
}).call(num);

因爲箭頭函數的外部決定上下文以及靜態上下文等的特性,不太建議使用箭頭函數在全局環境下來定義方法,由於不能經過其餘方法改變其上下文。這很蛋疼。

function Period (hours, minutes) {  
    this.hours = hours;
    this.minutes = minutes;
}
Period.prototype.format = () => {  
    console.log(this === window);    // => true
    return this.hours + ' hours and ' + this.minutes + ' minutes';
};
var walkPeriod = new Period(2, 30);  
console.log(walkPeriod.hours);
walkPeriod.format();    // => 'undefined hours and undefined minutes'

此時的 this 其實是指向了 window,因此 this.hours 和 this.minutes實際上沒有聲明的,故爲 undefined

在全局環境下,仍是選用 函數表達式 來進行函數的定義,能夠保證正確的上下文環境

function Period (hours, minutes) {  
    this.hours = hours;
    this.minutes = minutes;
}
Period.prototype.format = function() {  
    console.log(this === walkPeriod);    // => true
    return this.hours + ' hours and ' + this.minutes + ' minutes';
};
var walkPeriod = new Period(2, 30);  
walkPeriod.format(); // '2 hours and 30 minutes'

練習

// 練習1
var func = (function(a) {
    this.a = a;
    return function(a) {
        a += this.a;
        return a;
    }
})(function(a, b) {
    return a;
}(1, 2))

func(4) // ?

// 練習2
var x = 10,
    foo = {
        x: 20,
        bar: function() {
            var x = 30;
            return this.x;
        }
    }

console.log(foo.bar());
console.log((foo.bar)());
console.log((foo.bar = foo.bar)());
console.log((foo.bar, foo.bar)());

但願看完這篇文章,這兩個練習題,你是可以作出來的呀~ 好好分析一下唄。若是不肯定的話,能夠在留言板上,我們相互討論一下呀~

參考

相關文章
相關標籤/搜索