javascript忍者祕籍-第四章 理解函式呼叫

smiler2018發表於2018-12-03

呼叫函式時,隱式的函式引數 this 和 arguments 會被靜默的傳遞給函式

this 表示呼叫函式的上下文物件

arguments 表示函式呼叫過程中傳遞的所有引數。通過 arguments 引數可以訪問 函式呼叫過程中傳遞的實際引數。

函式呼叫的方式 對 函式的隱式引數有很大的影響

4.1 隱式的函式引數

arguments 和 this

arguments 引數

arguments 引數是 傳遞給函式的所有引數的集合

//arguments 是一個類陣列物件 
//length 屬性 表示實參的確切個數
arguments[2]  

function whatever(a,b,c){
    arguments.length  //實際傳入的引數個數
}

//arguments 物件是函式引數的別名 所以改變了 arguments 物件的值,同時也會影響到對應的函式引數
複製程式碼

在嚴格模式下,不能改變 arguments[index] 的值。

"use strict"

function infiltrate(person){
    arguments[0] = 'ninja';  //嚴格模式下 報錯
}
複製程式碼

this 引數:函式上下文

呼叫函式時,除了顯示提供的引數外,this 引數也會預設的傳遞給函式。 => 函式上下文

this 引數的指向 不僅是由 定義函式的方式 和 位置決定的,還受到 函式呼叫方式的影響。

4.2 函式呼叫

函式呼叫的4種方式

//1.作為一個函式直接呼叫
function skulk(name){}
shulk('Hattori');

//2.作為一個方法,關聯到物件上呼叫
var ninja = {
    shulk: function(){}
};
ninja.shulk('Hattori');

//3.作為建構函式呼叫
function Ninja(name){}
ninja = new Ninja('Hattori');

//4.通過apply 和 call呼叫
skulk.apply(ninja,['Hattori']);
skulk.call(ninja,'Hattori');
複製程式碼

作為函式直接呼叫

函式上下文 this 有兩種可能性:

1.在非嚴格模式下,this是全域性上下文(window物件)

2.在嚴格模式下,this是undefined

//函式定義 作為函式呼叫
function ninja(){};
ninja();

//函式表示式 作為函式呼叫
var samurai = function(){};
samurai();

(function(){})();  //立即呼叫的函式表示式,作為函式被呼叫
複製程式碼
//非嚴格模式下的函式呼叫
function ninja(){
    return this;  //window
}

//嚴格模式下的函式呼叫
function ninja(){
    "use strict";
    return this;  //undefined
}
複製程式碼

作為方法被呼叫

當一個函式被賦值給一個物件的屬性,並且通過物件屬性引用的方式呼叫函式,函式會被作為 物件的方法 呼叫

當函式作為某個物件的方法被呼叫時,該物件會成為函式的上下文,並且在函式內部可以通過引數(this)訪問到

var ninja = {};
ninja.skulk = function(){};
ninja.skulk();
複製程式碼
function whatsMyContext(){
    return this;
}

whatsMyContext();  //當作函式呼叫 this == window

var getMyThis = whatsMyContext();
getMyThis();  //建立原函式的引用 當作函式呼叫 this == window

var ninja1 = {
    getMyThis: whatsMyContext
}
ninja1.getMyThis == ninja1  //this 返回函式上下文 == 該方法所在的物件
複製程式碼

作為建構函式呼叫

//通過建構函式的方式呼叫,需要在函式呼叫之前使用關鍵字new
new MyObject();

function Ninja(){
    this.skulk = function(){
        return this;
    }
}

var ninja1 = new Ninja();
ninja1.skulk() === ninjia1  //true
複製程式碼

當用 new 呼叫建構函式時,觸發以下幾個動作

  1. 建立一個新的空物件
  2. 該物件作為 this 引數傳遞給建構函式,從而成為建構函式的函式上下文
  3. 新構造的物件作為 new 運算子的返回值

建構函式的目的:建立一個新物件,並進行初始化設定,然後將其作為建構函式的返回值。

建構函式的返回值

function Ninja(){
    this.skulk = function(){
        return true;
    };
    return 1;  //返回一個基本資料型別
}

Ninja()   //返回值為1

//返回值1被忽略了,一個新的初始化物件返回
var ninja = new Ninja();
typeof ninja === "object"  //true
typeof ninja.skulk === "function"  //true

var puppet = {rules:false};
function Ninja(){
    this.skulk = function(){
        return true;
    }
    return puppet;  //返回一個新物件
}
複製程式碼
  • 如果建構函式返回了一個物件,那麼該物件作為整個表示式的值返回,傳入建構函式的 this 被丟棄
  • 如果建構函式返回了 非物件型別,則忽略返回值,返回新建立的物件

apply/call方法呼叫

不同型別函式呼叫之間的區別:最終作為 函式上下文(this) 傳遞給 執行函式的物件不同。

直接函式:window undefined

方法:方法所在的物件

建構函式:新建立的物件例項

==每個函式都存在這兩個方法==

改變函式的上下文,顯示指定函式的上下文物件

事件回撥函式的上下文 是 觸發事件的物件 => 呼叫的位置

function Button(){
    this.clicked = false;
    this.click = function(){
        this.clicked = true;
        assert(button.clicked,"The button has been clicked");
    }
}

var button = new Button();
var elem = document.getElementById("test");
elem.addEventListener("click",button.click);
複製程式碼
//apply和call方法來設定函式上下文
function juggle(){
    var result = 0;
    for (var n=0;n<arguments.length;n++){
        result += arguments[n];
    }
    this.result = result;
}

var ninja1 = {};
var ninja2 = {};

juggle.apply(ninja1,[1,2,3,4]);
juggle.call(ninja2,5,6,7,8);

ninja1.result == 10
ninja2.result == 26
複製程式碼

手動設定call和apply

強制指定回撥函式的函式上下文

forEach 遍歷函式將每個元素傳給回撥函式,將當前元素作為回撥函式的上下文

function forEach(list,callback){
    for(var n=0;n<list.length;n++){
        callback.call(list[n],n);  //當前元素作為函式上下文,迴圈索引作為回撥函式的引數
    }
}

var weapons = [{type:'abc'},{type:'bcd'},{type:'efg'}];
forEach(weapons,function(index){
    this === weapons[index]  //list[n]
})
複製程式碼

4.3 解決函式上下文的問題

上下文不一致可以考慮以下幾種辦法:

1.call 和 apply 顯示指定 this 上下文

2.箭頭函式

3.bind 方法

箭頭函式繞過函式上下文

箭頭函式沒有單獨的this值.

箭頭函式的 this 與宣告所在的上下文相同

//箭頭函式自身不含上下文,從定義時所在的函式繼承上下文
//呼叫箭頭函式時,不會隱式傳入this引數,而是從定義時的函式繼承上下文
function Button(){
    this.clicked = false;
    this.click = () => {
        this.clicked = true;
        button.clicked == true;  //button == this
    }
}

var button = new Button();
var elem = document.getElementById("test");
elem.addEventListener("click",button.click);
複製程式碼

箭頭函式與物件字面量

只有一個按鈕,不需要使用建構函式。直接使用物件字面量。單例模式

//箭頭函式與物件字面量
//物件字面量在全域性程式碼中定義,所以this與全域性程式碼的this相同
var button = {
    clicked: false,
    click: () => {  //箭頭函式在建立時確定了this的指向
        this.clicked = true;
        button.clicked;   //false
        this === window;  //true
        window.clicked;   //true
    }
}

var elem = document.getElementById("test");
elem.addEventListener("click",button.click);
複製程式碼

bind 方法

函式可以訪問 bind 方法建立新函式,建立的的新函式與 **原始函式的函式體 **相同,新函式被繫結到指定的物件上,this 被設定為物件本身

呼叫 bind 方法不會修改 原始函式,而是建立了一個全新的函式

var button = {
    clicked : false,
    click : function(){
        this.clicked = true;
        button.clicked;  //true
    }
}

var elem = document.getElementById("test");
elem.addEventListener("click",button.click.bind(button));

//bind方法會新建立一個全新的函式
var boundFunction = button.click.bind(button);
boundFunction != button.click   //true
複製程式碼

4.4 總結

  • 當呼叫函式時,除了傳入在函式定義中顯式宣告的引數之外,同時還傳入兩個隱式引數:arguments 與 this。

    • arguments 引數是傳入函式的所有引數的集合。具有 length 屬性,表示傳入引數的個數,通過 arguments 引數還可獲取那些與函式形參不匹配的引數。 在非嚴格模式下,arguments 物件是函式引數的別名,修改 arguments 物件 會修改函式實參,可以通過嚴格模式避免修改函式實參。

    • this 表示函式上下文,即與函式呼叫相關聯的物件。函式的定義方式和呼叫 方式決定了 this 的取值。

  • 函式的呼叫方式有 4 種。

    • 作為函式呼叫:skulk()。

    • 作為方法呼叫:ninja.skulk()。

    • 作為建構函式呼叫:new Ninja()。

    • 通過 apply 與 call 方法呼叫:skulk.apply(ninja)或 skulk.call(ninja)。

  • 函式的呼叫方式影響 this 的取值。

    • 如果作為函式呼叫,在非嚴格模式下,this 指向全域性 window 物件;在嚴格 模式下,this 指向 undefined。

    • 作為方法呼叫,this 通常指向呼叫的對像。

    • 作為建構函式呼叫,this 指向新建立的物件。

    • 通過 call 或 apply 呼叫,this 指向 call 或 apply 的第一個引數。

  • 箭頭函式沒有單獨的 this 值,this 在箭頭函式建立時確定。

  • 所有函式均可使用 bind 方法,建立新函式,並繫結到 bind 方法傳入的引數上。被繫結的函式與原始函式具有一致的行為。

相關文章