JavaScript基礎--this解析

this關鍵字是javascript中最複雜的機制之一。它是一個很特別的關鍵字,被自動定義在全部函數的做用域中。
this既不指向函數自己也不指向函數的語法做用域。
this是在函數被調用時發生的綁定,this的綁定和函數聲明的位置沒有任何關係,它指向什麼徹底取決於函數在哪裏被調用。javascript

調用位置:是函數在代碼中被調用的位置,而不是聲明的位置。

this的4條綁定規則

* 默認綁定
* 隱式綁定
* 顯式綁定
* new綁定

優先級排序:new綁定 > 顯式綁定 > 隱式綁定 > 默認綁定
默認綁定

沒法應用其它規則時的默認規則
若是使用嚴格模式(strict mode),則不能將全局對象用於默認綁定,所以this會綁定到undefined。
最經常使用的函數調用類型:獨立函數調用。java

function foo(){
    console.log(this.a)
}
var a = 2;
foo() //2  >函數調用時應用了this的默認綁定,所以this指向全局對象

'tips: 聲明在全局做用域中的變量就是全局對象的同名屬性。'

//嚴格模式 example 1
function (){
    "use strict";
    console.log(this.a)
}
var a = 2;
foo(); //TypeError:this is undefined

//嚴格模式 example 2
function foo(){
    console.log(this.a)
}
var a = 2;
(function(){
    "use strict";
    foo(); //2
})();
隱式綁定

隱式綁定規則會把函數調用中的this綁定到這個上下文對象。
當函數引用上下文對象時,對象屬性引用鏈中只有上一層或者說最後一層在調用位置中起做用。es6

//example 1:
function foo(){
    console.log(this.a)
}
var obj = {
    a: 2,
    foo: foo
}
obj.foo();//2

//example 2:
var obj2 = {
    a: 42,
    foo: foo
}
var obj1 = {
    a: 2,
    obj2: obj2
}
obj1.obj2.foo();//42
顯式綁定

直接指定this的綁定對象稱爲顯式綁定
常見的顯式綁定方法有 call()apply()編程

call和apply的使用方法:

function.call(thisArg, arg1, arg2, ...)

//thisArg           在 f unction 函數運行時使用的 this 值

//arg1, arg2, ...   指定的參數列表。


func.apply(thisArg, [argsArray]) 

//thisArg    在 f unc 函數運行時使用的this值,
             //若是這個函數處於非嚴格模式下,則指定爲 null 或 undefined 時會自動替換爲指向全局對象,原始值會被包裝。

//argsArray  一個數組或者類數組對象,其中的數組元素將做爲單獨的參數傳給'func'函數,
            //若是該參數的值爲 null 或 undefined,則表示不須要傳入任何參數。
            
            
兩者區別
call()方法接受的是一個參數列表,而apply()方法接受的是一個包含多個參數的數組。
function foo(){
    console.log(this.a)
}
var obj={
    a: 2
}
foo.call(obj); //2 調用foo時強制把它的this綁定到obj上

裝箱:若是你傳入了一個原始值(字符串類型、布爾類型或數字類型)來看成this的綁定對象,這個原始值會被轉換成它的對象形式(也就是new String(...)、new Boolean(...)或new Number(...))segmentfault

1>硬綁定
//硬綁定--顯式的強制綁定
function foo(){
    console.log(this.a)
}
var obj = {
    a:2
}
var bar = function(){
    foo.call(obj)
}
bar(); //2

//硬綁定--硬綁定的使用場景就是建立一個包裹函數,負責接收參數並返回值
function foo(something){
    console.log(this.a,something);
    return this.a + something;
}
var obj={
    a: 2
}
var bar=function(){
    return foo.apply(obj,arguments)
}
var b = bar(3); //2 3
console.log(b); //5

//硬綁定--另外一種使用方法是建立一個能夠重複使用的輔助函數
function foo(something){
    console.log(this.a,something)
    return this.a + something
}
//簡單的輔助綁定函數
function bind(fn,obj){
    return function(){
        return fn.apply(obj,arguments)
    }
}
var obj = {
    a:2
}
var bar = bind(foo,obj);
var b = bar(3) //2 3
console.log(b) //5

//因爲硬綁定是一種很是經常使用的模式,因此ES5提供了內置的方法Function.prototype.bind
function foo(something){
    console.log(this.a,something)
    return this.a+something
}
var obj={
    a:2
}
var bar = foo.bind(obj); //bind(...)會返回一個硬編碼的新函數,它會把你指定的參數設置爲this的上下文並調用原始函數
var b=bar(3); //2 3
console.log(b); //5

bind(...)的功能之一就是能夠把除了第一個參數(第一個參數用於綁定this)以外的其餘參數都傳給下一層的函數


2>API調用的「上下文」
第三方庫的許多函數,以及Javascript語言和宿主環境中許多新的內置函數,都提供了一個可選的參數,一般被稱爲「上下文」(context),其做用和bind(...)同樣,確保你的回調函數使用指定的this。
new綁定

在Javascript中,構造函數只是一些使用new操做符時被調用的函數。它們並不會屬於某個類,也不會實例化一個類。它們只是被new操做符調用的普通函數。
實際上並不存在所謂的「構造函數」,只有對於函數的「構造調用」。數組

function foo(a){
    this.a = a
}
var bar = new foo(2)
console.log(bar); //2

使用new調用函數,或者說發生構造函數調用時,會自動執行下面的操做:
一、建立(或者說構造)一個全新的對象
二、這個新對象會被執行[[Prototype]]鏈接
三、這個新對象會綁定到函數調用的this
四、若是函數沒有返回其餘對象,那麼new表達式中的函數調用會自動返回這個新對象安全

綁定例外

一、若是你把null或者undefined做爲this的綁定對象傳入call、apply或者bind,這些值在調用時會被忽略,實際應用的是默認綁定規則。
一種安全的作法是傳入一個特殊對象,把this綁定到這個對象不會對你的程序產生任何反作用。
在Javascript中建立一個空對象最簡單的方法都是Object。create(null)。Object。create(null)和{}很像,可是並不會建立Object.prototype這個委託,因此它比{}更空。app

二、有可能建立一個函數的「間接引用」,在這中狀況下,調用這個函數會應用默認綁定規則。編程語言

function foo(){
    console.log(this.a)
}
var a =2
var o = {
    a: 3,
    foo: foo 
}
var p = {
    a: 4
}
0.foo(); //3
(p.foo = o.foo)(); //2   >>p.foo = o.foo的返回是目標函數的引用,所以調用位置是foo()而不是p.foo()或者o.foo()

三、軟綁定 Function.prototype.softBind函數

if(!Function.prototype.softBind){
    Function.prototype.softBind = function(obj){
        var fn = this;
        //捕獲全部的 curried 參數
        var curried = [].slice.call(arguments,1);
        var bound = function(){
            return fn.apply(
                (!this || this === (window || global)) ? 
                    obj : this,
                curried.concat.apply(curried,arguments)
            )
        }
        bound.prototype = Object.create(fn.prototype)
        return bound
    }
}

箭頭函數

以前介紹的4條規則能夠包含全部正常的函數,可是在es6中介紹了一種沒法使用這些規則的特殊函數:箭頭函數。

  • 箭頭函數不使用this的四種標準規則,而是根據外層(函數或者全局)做用域來決定this。
  • 箭頭函數的綁定沒法被修改。(new也不行)
  • 箭頭函數會繼承外層函數調用的this綁定(不管this綁定到什麼)。這其實和ES6以前代碼中的self=this機制同樣。

小知識點

柯里化

即Currying的音譯。Currying是編譯原理層面實現多參函數的一個技術。
Currying 爲實現多參函數提供了一個遞歸降解的實現思路——把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數,在某些編程語言中(如 Haskell),是經過 Currying 技術支持多參函數這一語言特性的。

判斷this綁定

先找到這個函數的直接調用位置,而後順序應用下面這4條規則判斷this的綁定對象:

  • 由new調用?綁定到新建立的對象。
  • 由call或apply或bind調用?綁定到指定的對象。
  • 由上下文對象調用?綁定到那個上下文對象。
  • 默認:在嚴格模式下綁定到undefined,不然綁定到全局對象。

文章摘取來源:《你不知道的JavaScript上卷》
 
 

更多文章
JavaScript基礎--做用域與變量提高

相關文章
相關標籤/搜索