博客 / 詳情

返回

JavaScript基礎--this解析

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基礎--作用域與變量提升

 
 

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.