理解JS函數調用和"this"

該文章是直接翻譯國外一篇文章,關於JS函數調用和"this"的處理。
都是基於原文處理的,其餘的都是直接進行翻譯可能有些生硬,因此爲了行文方便,就作了一些簡單的本地化處理。
若是想直接根據原文學習,能夠忽略此文。javascript

關於JS函數是如何調用的困惑了不少年,尤爲是在JS函數中this的語法機制很讓人頭疼。java

在我看來,若是理解核心函數的調用機制,同時驗證一些以核心函數爲基礎的其餘實現方式的運行機制,關於上述所說的問題就會迎刃而解。bash

核心機制(The Core Primitive)

首先,讓咱們來解析一些核心函數的調用機制的重點---Function對象的call方法。 call函數的調用過程以下:app

  1. parameters第二個值到最後一個剝離出來並從新構建一個新的參數列表(argList)
  2. 傳入函數的第一個值賦值給thisValue
  3. 調用函數,在此過程當中,將thisValue賦值給this,argList做爲函數的參數列表(argument list)

示例以下:函數

function hello(thing){
    console.log(this+ "says hello"+ thing);
}

hello.call("北宸南蓁","world");
//輸出結果:北宸南蓁 says hello world

複製代碼

正如實踐以後所獲得的結果,咱們調用hello()的時候,將this的值賦值爲北宸南蓁同時將world做爲hello運行時的參數list。上述的處理流程就是JS函數調用的核心機制。你能夠這樣粗略的認爲:其餘函數的調用機制就是在核心機制的基礎上進行了封裝/簡化處理(desugar)。學習

簡單函數調用(Simple Function Invocation)

很顯然,利用call調用函數看起來,不是一個很聰明的亞子。因此,JS運行利用簡單語法 hello("world")直接調用函數。ui

function hello(thing){
    console.log("Hello" + thing);
}

//簡化的語法(封裝以後的語法)
hello("world")
//核心語法
hello.call(window,"world");
複製代碼

NOTE:在ECMAScript 5的嚴格模式下有些許的不一樣:this

//簡化語法
hello("world");
//核心語法
hello(undefined,"world");
複製代碼

綜上所述:能夠將簡單函數fn(...args)的調用匯總爲fn.call(window [ES5-strict:undefined],...args)spa

NOTEprototype

上述的調用公式一樣也適應於:(funciton(){})() ==>(function(){}).call(window [ES5-strict:undefined])

成員函數(Member Functions)

在js的應用場景中,函數做爲對象的屬性也是很常見的情景。在這種情景下,會發生以下的簡化處理:

var person ={
    name:"北宸南蓁",
    hello:function(){
        console.log(this + "says hello" + thing);
    }
}
//簡化的語法
person.hello("world");
//核心語法/
person.hello.call(person,"world");

複製代碼

Note: 上述的簡化過程不受成員函數的賦值和定義方式的影響的。例如上面的例子中,成員函數是直接定義在對象中。若是動態的對成員函數進行賦值,最後的簡化結果也是同樣的。

function hello(thing){
    console.log(this + "says hello" + thing);
}

person = { name:"北宸南蓁"};
person.hello= hello;
//簡化語法
person.hello("world") //該種的核心語法也是 `person.hello.call(person,'world')`

hello("world") //"[object DOMWindow]world"
複製代碼

Note:上述全部的函數中this的值都不是肯定的。this的值由調用函數的所在的 做用域決定。

Function.prototype.bind對做用域進行綁定

在某些應用場景中,須要將函數中的this值進行綁定到指定的環境中。就須要額外的藉助一個函數進行特定環境的綁定。

var person ={
    name:"北宸南蓁",
    hello:function(thing){
        console.log(this + "says hello " + thing);
    }
}

var boundHello = function(thing){
    return person.hello.call(person,thing);
}

boundHello("world");

複製代碼

雖然在boundHello("world")調用的時候,被脫糖(desugar)boundHello.call(window,"world")。可是在函數中,是將person對象的成員函數hello進行this值的處理,指向hello函數應該在的做用域中。

或者咱們能夠將boundXX函數變得更加通用。

var bind = function(func,thisValue){
    return function(){
        return func.apply(thisValue,arguments);
    }
}

var boundHello = bind(person.hello,person);
boundHello("world");
複製代碼

其中實現的原理這裏就再也不贅述了。

因爲這種場景不少,ES5在Function對象中新增了bind方法,用於對一個函數指定特定的this值。

var boundHello = person.hello.bind(person);
boundHello("world");

複製代碼

這種處理方式頗有用,好比,你將一個成員函數做爲callback:

var person = {
  name: "北宸南蓁",
  hello: function() { console.log(this.name + " says hello world"); }
}

$("#some-div").click(person.hello.bind(person));

複製代碼

撥開雲霧見月明

在上文中,爲了可以在現有的規範語法中解釋清楚函數調用的核心機制。經過func.call來闡述函數底層是如何實現一系列的數據操做的。實際上,實現函數調用的核心語法另有其人[[Call]](這是一個內部屬性),可是他是func.call[obj.]func()實現的基礎零件

咱們來了解一下func.call的定義

  1. 若是func不是一個函數,直接拋出錯誤。
  2. 定義一個長度爲0的argList
  3. 若是傳入函數的參數大於1個,從第一個參數arg1到參數結尾的全部參數的值做爲一個新值,賦值argList.
  4. Return the result of calling the [[Call]] internal method of func, providing thisArg as the this value and argList as the list of arguments.(這個話真的很差翻譯,感受仍是原文的語句更加貼切)

正如上面的定義所知,func.call的內部實現,都是基於[[Call]]的操做來實現。也就是說,函數調用的核心就是**[[Call]]**的實現。可是這個方法的實現方式和func.call的處理過程是同樣的。因此,經過類比來模擬出函數的調用過程。

相關文章
相關標籤/搜索