該文章是直接翻譯國外一篇文章,關於JS函數調用和"this"的處理。
都是基於原文處理的,其餘的都是直接進行翻譯可能有些生硬,因此爲了行文方便,就作了一些簡單的本地化處理。
若是想直接根據原文學習,能夠忽略此文。javascript
關於JS函數是如何調用的困惑了不少年,尤爲是在JS函數中this
的語法機制很讓人頭疼。java
在我看來,若是理解核心函數的調用機制,同時驗證一些以核心函數爲基礎的其餘實現方式的運行機制,關於上述所說的問題就會迎刃而解。bash
首先,讓咱們來解析一些核心函數的調用機制的重點---Function
對象的call
方法。 call
函數的調用過程以下:app
parameters
第二個值到最後一個剝離出來並從新構建一個新的參數列表(argList
)thisValue
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)。學習
很顯然,利用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])
在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
的定義
func
不是一個函數,直接拋出錯誤。argList
arg1
到參數結尾的全部參數的值做爲一個新值,賦值給argList
.[[Call]]
internal method of func, providing thisArg as the this value and argList as the list of arguments.(這個話真的很差翻譯,感受仍是原文的語句更加貼切)正如上面的定義所知,func.call
的內部實現,都是基於[[Call]]
的操做來實現。也就是說,函數調用的核心就是**[[Call]]**的實現。可是這個方法的實現方式和func.call
的處理過程是同樣的。因此,經過類比來模擬出函數的調用過程。