this關鍵字是javascript中最複雜的機制之一。它是一個很特別的關鍵字,被自動定義在所有函數的作用域中。
this既不指向函數本身也不指向函數的語法作用域。
this是在函數被調用時發生的綁定,this的綁定和函數聲明的位置沒有任何關係,它指向什麼完全取決於函數在哪裏被調用。
調用位置:是函數在代碼中被調用的位置,而不是聲明的位置。
this的4條綁定規則
* 默認綁定
* 隱式綁定
* 顯式綁定
* new綁定
優先級排序:new綁定 > 顯式綁定 > 隱式綁定 > 默認綁定
默認綁定
無法應用其它規則時的默認規則
如果使用嚴格模式(strict mode),則不能將全局對象用於默認綁定,因此this會綁定到undefined。
最常用的函數調用類型:獨立函數調用。
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綁定到這個上下文對象。
當函數引用上下文對象時,對象屬性引用鏈中只有上一層或者説最後一層在調用位置中起作用。
//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(...))
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調用函數,或者説發生構造函數調用時,會自動執行下面的操作:
1、創建(或者説構造)一個全新的對象
2、這個新對象會被執行[[Prototype]]連接
3、這個新對象會綁定到函數調用的this
4、如果函數沒有返回其他對象,那麼new表達式中的函數調用會自動返回這個新對象
綁定例外
1、如果你把null或者undefined作為this的綁定對象傳入call、apply或者bind,這些值在調用時會被忽略,實際應用的是默認綁定規則。
一種安全的做法是傳入一個特殊對象,把this綁定到這個對象不會對你的程序產生任何副作用。
在Javascript中創建一個空對象最簡單的方法都是Object。create(null)。Object。create(null)和{}很像,但是並不會創建Object.prototype這個委託,所以它比{}更空。
2、有可能創建一個函數的“間接引用”,在這中情況下,調用這個函數會應用默認綁定規則。
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()
3、軟綁定 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基礎--作用域與變量提升