1. 程式人生 > >javascript之函式的四種呼叫方式

javascript之函式的四種呼叫方式

 函式的四種呼叫方式如下:

(1)函式呼叫模式
(2)方法呼叫模式
(3)構造器模式
(4)上下文模式
1. 函式呼叫 模式
要呼叫,就肯定要先定義,函式的定義方式:

宣告式: function fuc() {}
表示式式: var func = function() {};
Function: new Function( ‘引數’,…,’函式體’ );
單獨獨立呼叫的,就是函式呼叫模式,即 函式名( 引數 ),不能加任何其他的東西, 物件 o.fuc() 就不是了。

在函式呼叫模式中, this 表示全域性物件 window

任何自呼叫函式都是函式模式。

2. 方法呼叫 模式 method


所謂方法呼叫,就是用物件的方法呼叫。方法是什麼,方法本身就是函式,但是,方法不是單獨獨立的,而是要通過一個物件引導來呼叫。

就是說方法物件一定要有宿主物件。

即 物件.方法(引數)

this表示引導方法的物件,就是指宿主物件

對比-函式呼叫模式:

方法呼叫模式是不是獨立的,需要宿主,而函式呼叫模式是獨立的
方法呼叫模式方式:obj.fuc(); 函式呼叫模式方式: fuc();
方法呼叫模式中,this指宿主。而函式呼叫模式中 this 指 全域性物件window
美團的一道面試題
   

 var length = 10;
    function fn() {
        console.log( this.length ); // 10
    }
    var obj = {
        length: 5,
        method: function ( fn ) {
            fn();   // 10 前面沒有引導物件,是函式呼叫模式
            arguments[ 0 ](); // 2
            // arguments是一個偽陣列物件, 這裡呼叫相當於通過陣列的索引來呼叫.
            // 這裡 this 就是 指的這個偽陣列, 所以 this.length 為 2
        }
    };
    obj.method( fn, 1 );    // 列印 10 和 2
    //obj.method( fn, 1, 2, 3 );    // 列印 10 和 4


解析:

fn() 前面沒有引導物件,是函式呼叫模式, this是全域性物件,輸出 10

arguments[ 0 ](),arguments是一個偽陣列物件, 這裡呼叫相當於通過陣列的索引來呼叫.

這裡引導物件即宿主就是 arguments物件。

所以,執行時,this 就是指 arguments,由於傳了兩個引數,所以 輸出為 arguments.length 就是 2

3. 構造器模式(建構函式模式, 構造方法模式)
constructor

a.特點: 使用 new 關鍵字引導

b.執行步驟:var p = new Person();

new 是一個運算子, 專門用來申請建立物件, 創建出來的物件傳遞給建構函式的 this。然後利用建構函式對其初始化。

   

 function Person () {
        // new了 進入建構函式時, p 物件的原型 就指向了 建構函式 Person, 
        // p.__proto__.constructor = function Person() {};
        // 而 this 指的的是 p 物件
        this.name = 'jim',
        this.age = 19;
        this.gender = 'male';
    }
    var p = new Person();


執行完 new 進入建構函式時, p 物件的原型 就指向了 建構函式 Person

而 構造時,this 指的的是 p 物件,是通過物件動態新增屬性來構造的

小貼士:如果呼叫建構函式的時候, 建構函式沒有引數, 圓括號是可以省略的。

function Person() {
        this.name = 'jim';
    }
    var p = new Person; // 不傳參,可以簡寫,不影響構造
    console.log( p );   // p 含有 name屬性


↑ 不傳參,可以簡寫,不影響構造

c..返回值

不寫 return 語句, 那麼 建構函式 預設返回 this

若建構函式 返回基本型別( 例如:return num, return 1223 ). 則忽略返回型別,返回this.

若建構函式返回引用型別(object和Function), 那麼建構函式返回該引用型別資料, 而忽略 this.

 function Person () {
        this.name = 'Jepson';
        return 123;
    }
    var p1 = new Person();
    console.log( p1 );

↑ 忽略了 123,返回 this 物件, 指向構建的例項

 function Person () {
        this.name = 'Jepson';
        return { 'peter': 'nihao' };// 返回物件
    }
    var p1 = new Person();
    console.log( p1 );

↑ 忽略了 this,返回 { ‘peter’: ‘nihao’ } 物件

建構函式結合性
如果建構函式沒有引數, 可以省略 圓括號

var p = new Person;

如果希望建立物件並直接呼叫其方法

( new Person () ).sayHello()

-> 可以省略調整結核性的圓括號 new Person().sayHello()

-> 如果想要省略建構函式的圓括號, 就必須新增結核性的圓括號 (new Person).sayHello()

面試題
一道面試題,大家可以自己嘗試先做一下,再看下面的答案和解析

請問順序執行下面程式碼,會怎樣 alert   

 function Foo(){
        getName = function(){ alert(1); };
        return this;
    }
    Foo.getName = function(){ alert(2); };
    Foo.prototype.getName = function(){ alert(3); };
    var getName = function(){ alert(4); };
    function getName(){ alert(5); }

    Foo.getName();  // alert ??
    getName();  // alert ??
    Foo().getName(); // alert ??
    getName(); // alert ??
    new Foo.getName(); // alert ??
    new Foo().getName(); // alert ??
    new new Foo().getName(); // alert ??


預解析,簡化後的程式碼,以及答案 

  /* function getName(){ alert(5); } 執行到下面被覆蓋了,直接刪除 */
    function Foo() {
        getName = function () { alert(1); };
        return this;
    }
    Foo.getName = function () { alert(2); };
    Foo.prototype.getName = function () { alert(3); };
    var getName = function () { alert(4); };

    Foo.getName();  // ------- 輸出 2 -------
    getName();      // ------- 輸出 4 -------
    Foo().getName();    // ------- 輸出 1 -------
    getName();  // ------- 輸出 1 -------
    new Foo.getName();     // ------- 輸出 2 -------
    new Foo().getName();    // ------- 輸出 3 -------
    var p = new new Foo().getName();     // ------- 輸出 3 -------


全部解析過程 ↓ 

  function Foo() {
        getName = function () { alert(1); };
        return this;
    }
    Foo.getName = function () { alert(2); };
    Foo.prototype.getName = function () { alert(3); };
    var getName = function () { alert(4); };

    Foo.getName();  // ------- 輸出 2 -------
    // 呼叫 Foo函式 作為 物件 動態新增的屬性方法 getName
    // Foo.getName = function () { alert(2); };

    getName();      // ------- 輸出 4 -------
    // 這裡 Foo函式 還沒有執行,getName還沒有被覆蓋
    // 所以 這裡還是 最上面的 getName = function () { alert(4); };

    Foo().getName();    // ------- 輸出 1 -------
    // Foo()執行,先覆蓋全域性的 getName 再返回 this,
    // this 是 window, Foo().getName() 就是呼叫 window.getName
    // 此時 全域性的 getName已被覆蓋成 function () { alert(1); };
    // 所以 輸出 1
    /* 從這裡開始 window.getName 已被覆蓋 alert 1 */

    getName();  // -------- 輸出 1 --------
    // window.getName alert(1);

    new Foo.getName();     // ------- 輸出 2 -------
    // new 就是 找 建構函式(),由建構函式結合性,這裡即使 Foo無參,也不能省略 (),所以不是 Foo().getName()
    // 所以 Foo.getName 為一個整體,等價於 new (Foo.getName)();
    // 而 Foo.getName 其實就是函式 function () { alert(2); } 的引用
    // 那 new ( Foo.getName )(), 就是在以 Foo.getName 為建構函式 例項化物件。
    // 就 類似於 new Person(); Person 是一個建構函式

    // 總結來看 new ( Foo.getName )(); 就是在以 function () { alert(2); } 為建構函式來構造物件
    // 構造過程中 alert( 2 ),輸出 2

    new Foo().getName();    // ------- 輸出 3 -------
    // new 就是 找 建構函式(),等價於 ( new Foo() ).getName();
    // 執行 new Foo() => 以 Foo 為建構函式,例項化一個物件
    // ( new Foo() ).getName; 訪問這個例項化物件的 getName 屬性
    // 例項物件自己並沒有 getName 屬性,構造的時候也沒有 新增,找不到,就到原型中找
    // 發現 Foo.prototype.getName = function () { alert(3); };
    // 原型中有,找到了,所以 ( new Foo() ).getName(); 執行,alert(3)

    var p = new new Foo().getName();     // ------- 輸出 3 -------
    // new 就是 找 建構函式(),等價於 new ( ( new Foo() ).getName )() 輸出 3

    // 先看裡面的 ( new Foo() ).getName
    // new Foo() 以Foo為建構函式,例項化物件
    // new Foo().getName 找 例項物件的 getName屬性,自己沒有,去原型中找,
    // 發現 Foo.prototype.getName = function () { alert(3); }; 找到了

    // 所以裡層 ( new Foo() ).getName 就是 以Foo為建構函式例項出的物件的 一個原型屬性
    // 屬性值為一個函式 function () { alert(3); } 的引用

    // 所以外層 new ( (new Foo()).getName )()在以該函式 function () { alert(3); } 為建構函式,構造例項
    // 構造過程中 執行了 alert(3), 輸出 3


4. 上下文呼叫模式
就是 環境呼叫模式 => 在不同環境下的不同調用模式

簡單說就是統一一種格式, 可以實現 函式模式與方法模式

-> 語法(區分)

call 形式, 函式名.call( … )
apply 形式, 函式名.apply( … )

這兩種形式功能完全一樣, 唯一不同的是引數的形式. 先學習 apply, 再來看 call 形式

apply方法的呼叫形式


存在上下文呼叫的目的就是為了實現方法借用,且不會汙染物件。

如果需要讓函式以函式的形式呼叫, 可以使用

foo.apply( null ); // 上下文為 window

如果希望他是方法呼叫模式, 注意需要提供一個宿主物件

foo.apply( obj ); // 上下文 為 傳的 obj 物件

    function foo () {
        console.log( this );
    }
    var o = { name: 'jim' };

    // 如果需要讓函式以函式的形式呼叫, 可以使用
    foo.apply( null ); //  this => window  // 或 foo.apply()

    // 如果希望他是方法呼叫模式, 注意需要提供一個宿主物件
    foo.apply( o )  // this => o 物件

帶有引數的函式如何實現上下文呼叫?
 

  function foo ( num1, num2 ) {
        console.log( this );
        return num1 + num2;
    }

    // 函式呼叫模式
    var res1 = foo( 123, 567 );

    // 方法呼叫
    var o = { name: 'jim' };
    o.func = foo;
    var res2 = o.func( 123, 567 );


使用 apply 進行呼叫, 如果函式是帶有引數的. apply 的第一個引數要麼是 null 要麼是物件

如果是 null 就是函式呼叫

如果是 物件就是 方法呼叫, 該物件就是宿主物件, 後面緊跟一個數組引數, 將函式所有的引數依次放在陣列中.

    例如: 函式模式        foo( 123, 567 );
          apply         foo.apply( null, [ 123, 567 ] ) 以 window 為上下文執行 apply

    如果有一個函式呼叫:   func( '張三', 19, '男' ),
    將其修改成 apply 模式:  func.apply( null, [ '張三', 19, '男'] )

    方法模式:           o.func( 123, 567 )
    apply               var o = { name: 'jim' };
                        foo.apply( o, [ 123, 567 ] ); 以 o 為上下文執行 apply

call 呼叫

在使用 apply 呼叫的時候, 函式引數, 必須以陣列的形式存在. 但是有些時候陣列封裝比較複雜

所以引入 call 呼叫, call 呼叫與 apply 完全相同, 唯一不同是 引數不需要使用陣列

foo( 123, 567 );
foo.apply( null, [ 123, 567 ] );
foo.call( null, 123, 567 );
  1. 函式呼叫: 函式名.call( null, 引數1,引數2,引數3… );

  2. 方法呼叫: 函式名.call( obj, 引數1,引數2, 引數3… );

  3. 不傳參時,apply 和 call 完全一樣

該文轉自:https://blog.csdn.net/qq_16415157/article/details/53033953