JS面向對象函數的四種調用模式


函數的四種調用模式

概念

  1. 在 js 中,無論是函數, 還是方法, 還是事件, 還是構造器,...這些東西的本質都是函數
  2. 函數, 方法, 事件, 構造器,...只是所處的位置不同
  3. 這四種模式分別是
    • 函數模式
    • 方法模式
    • 構造器模式
    • 上下文模式

函數模式

特征: 簡單的函數調用, 函數名前面沒有任何引導內容

    function foo(){}
    var fn = function(){};
    ...
    foo();
    fn();
    (function(){})();
    // 上面的三種都是簡單的函數調用

this的含義

  1. 在函數中 this 表示全局對象
  2. 在瀏覽器中 this 表示 window(js 中的 global)

方法模式

特征: 方法一定是依附於一個對象, 將函數賦值給對象的一個屬性, 那么就成為方法.就是函數前面必須有引導對象

    function foo(){
        this.method = function(){};
    }
    var o = {
        method : function(){}
    }

this的含義

  • 這個依附的對象(引導函數的對象)

注意點
在 arguments 這種偽數組, 或者 [] 數組這樣的對象中, 這樣調用函數也是方法調用, this 會只指向對象

構造器模式

構造函數在創建對象的時候, 做了些什么

  1. 使用 new 引導構造函數, 創建了一個實例對象
  2. 在創建對象的同時, 將this指向這個剛剛創建的對象
  3. 在構造函數中, 不需要 return , 會默認的 return this

分析:
由於構造函數只是給 this 添加成員, 而方法也可以完成這個操作,對與 this 來說, 構造函數和方法沒有本質區別

關於return的補充, 在構造函數中

普通情況, 可以理解為構造函數已經默認進行了 return this, 添加在后面的都不會執行

  1. 如果手動的添加 return ,就相當於 return this.
  2. 如果手動的添加 return 基本類型(字符串, 數字, 布爾), 無效, 還是 return this
  3. 如果手動的添加 return null 或 return undefined, 無效, 還是 return this

特殊情況, return 對象, 最終返回對象

  • 手動添加 return 對象類型, 那么原來創建的 this 會被丟掉, 返回 return 后面的對象

上下文模式

概念: 上下文就是環境, 就是自定義this的含義

語法:

  1. 函數名.apply( 對象, [參數]);
    • 這個參數可以是數組, 也可以是偽數組
  2. 函數名.call( 對象, 參數);
    • 多個參數可以通過,進行隔離

描述:

  1. 函數名表示的是函數本身, 使用函數進行調用的時候,默認this指的是全局變量
  2. 函數名也可以是方法提供, 使用方法調用的時候, this指的是當前對象
  3. 使用 apply 或者 call 進行調用后, 無論是函數, 還是方法的 this 指向全部無效了, this 的指向由 apply 或者 call 的第一個參數決定

注意:

  1. 如果函數或方法中沒有this的操作, 那么無論是哪一種函數調用模式, 其實都一樣
  2. 如果是函數調用 foo(), 其實和 foo.apply(window) 類似
  3. 如果是方法調用 o.method(), 其實和 o.method.apply(o)

無論是 call 還是 apply 在沒有后面參數的情況下(函數無參數, 方法無參數), 兩者一致

    function foo(){
        console.log(this);  // this => window
    }
    var obj = {};
    foo.apply( obj );   // this => obj
    foo.call( obj );    // this => obj

apply 和 call 第一個參數的使用規則

  1. 如果傳入的是一個對象, 就相當於設置該函數中的this為參數
  2. 如果不傳參數, 或者傳入 null, undefined 等,那么this就默認是 window
    foo();
    foo.apply();
    foo.apply(null);
    foo.apply(undefined);
    foo.call();
    foo.call(null);
    foo.call(undefined);
    // 上面都this都指向window
  1. 如果傳入的是基本類型, 那么this指向的就是基本類型的包裝類型的引用
    • number => Number
    • boolean => Boolean
    • string => String

除 this 外的其他參數

再使用上下文調用的時候, 原函數(方法)可能會帶有參數, 那么要讓這些參數在上下文中調用, 就需要這個第二, ( n )個參數來表示

    function foo(num){
        console.log(num);
    }
    foo.apply(null, [123]);
    // 相當於
    foo(123);

應用

上下文調用只是修改this, 但是使用最多的地方是借用函數調用

  1. 將偽數組轉換為數組
    • 傳統方法
        var a = {};
        a[0] = 'a';
        a[1] = 'b';
        a.length = 2;
        // 使用數組自帶方法 concat();
        // 不修改原數組
        var arr = [];
        var newArr = arr.concat(a);
    

    分析
    由於 a 是偽數組, 並不是真正的數組, 不能使用數組的方法, concat 會將 a 作為一個整體 Object 加入數組
    apply 方法有一個特性, 可以將數組或者偽數組作為參數

        foo.apply( obj, 偽數組 ); // IE8 不支持
    

    將 a 作為 apply 的第二個參數

        var arr = [];
        var newArr = Array.prototype.concat.apply( arr, a);
    

    由上面的數組轉換, 我們可以得到結論, 應該涉及到數組操作的方法理論上都應該可以
    push, pop, unshift, shift
    slice
    splice

  2. 讓偽數組使用 push 方法
    小技巧, 偽數組添加元素
        var a = {length : 0};   // 設置偽數組的長度
        a[ a.length++   ] = 'a';
        a[ a.length++   ] = 'b';
        // 在給偽數組的元素賦值時, 同時修改偽數組的 length 屬性
        // a[0] = 'a'; a.length++;
    
    偽數組使用 push 方法
        var arr = [];
        arr.push.apply( arr, a );
        // 利用 apply 可以處理偽數組的特性, 進行傳參
        // 相當於 arr.push(a[0], a[1])
    
  3. 讓偽數組使用 slice 方法
    數組的 slice 語法
    • arr.slice( index, endIndex ), 不包含 endIndex
    • 如果第二個參數不傳參, 那么截取從 index 一直到數組結尾
    • slice 方法不會修改原數組

    通過 apply 實現偽數組使用 slice 方法

        var a = { length : 0 };
        a[a.length++] = 'a';
        a[a.length++] = 'b';
        var arr =[];
        var newArr = arr.slice.apply(a ,[0])
    

獲取數組中的最大值

傳統方法

    var arr = [1,2,3,4,5,6,7,8,9];
    var max = arr[0];
    for(var i = 0;i < arr.length;i++){
        if(max < arr[i]){
            max = arr[i]
        }
    }

使用 apply 借用 Math.max 獲取數組中最大值
利用 apply 可以傳入參數可以是數組或是偽數組的特性

    var arr = [1,2,3,4,5,6,7,8,9];
    Math.max.apply(null, arr);

創建對象的幾種模式

了解了四種函數調用模式, 我們可以深一步了解創建對象的幾種方式, 對象是通過構造函數創建的

  1. 工廠模式
    特點:
    1. 大量重復執行的代碼, 解決重復實例化的問題
    2. 函數創建對象並返回
    3. 最典型的工廠模式就是 document.createElement()
    4. 無法知道是誰創建了這個實例對象
        function createPerson(name, age, gender){
            var o = {};
            o.name = name;
            o.age = age;
            o.gender = gender;
            return o;
        }
    
  2. 構造方法
    特點:
    1. 解決了重復實例化問題
    2. 能夠知道是誰創建了這個對象(constructor)
    3. 需要通過 new 運算符穿件對象
        function Person(name,age,gender){
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
    
  3. 寄生式構造函數創建對象
    特點:
    1. 外表看起來就是構造犯法, 但本質不是通過構造方法創建對象
    2. 工廠模式 + 構造函數模式
    3. 不能確定對象的關系, 不推薦使用
        function createPerson(name,age,gender){
            var o = {};
            o.name = name;
            o.age = age;
            o.gender = gender;
            return o;
        }
        var p = new createPerson('Bob',19,'male')
    
  4. 混合式創建
    1. 構造函數 + 原型
    2. 解決了構造函數傳參和共享的問題
    3. 不共享的參數使用構造函數
    4. 共享的使用原型
    5. 這種混合模式很好的解決了傳參引用共享的難題
        function createPerson(name,age,gender){
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
        createPerson.prototype = {
            constructor : createPerson,
            wife : '高圓圓'
        }
    
  5. 借用構造函數繼承(對象冒充)
    特點:
    1. 借用構造函數(對象冒充)只能繼承構造函數的成員, 無法繼承原型的成員
        function Person(name,age,gender){
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
        function Studner(name,age,gender,course){
            // 借用構造函數Person, 創建 Student 對象
            Person.call(this,name,age,gender);
            this.course = course;
        }
        var boy = new Student('Bob',19,'male',;Math);
    
  6. 寄生式繼承
    特點:
    1. 原型 + 工廠模式
    2. 通過臨時中轉
        // 臨時中轉
        function person(o){
            function Foo(){};
            F.prototype = o;
            return new F();
        }
        // 寄生函數
        function create(o){
            var fn = person(o);
            fn.move = function(){};
            fn.eat = function(){};
            fn.sleep = function(){};
            return fn;
        }
        var boy = {
            name : 'Bob',
            age : 19,
            famliy : ['father','mother','wife']
        }
        var boy1 = create(boy);
        console.log(boy1.name);
        console.log(boy1.family);
        // 此時 boy1 有了 boy 的成員
    

經典例題

例題 1

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

    getName = function(){ alert(4); };

    Foo.getName();        		// 2
    getName();                	// 4
    Foo().getName();			// 4  1

    getName();         		    // 4  1
    new Foo.getName();          // 2
    new Foo().getName();      	// 3

    new new Foo().getName();    // 3

分析

  1. 預解析
  • 函數名 Foo 和函數名 getName 聲明提升,函數名和函數體綁定
  1. 執行代碼
  • 執行 Foo.getName();
    • 輸出 2
  • 執行 getName();
    • 此時 getName 是在全局中, 未被修改, 輸出4
  • Foo().getName();
    • 此事 Foo() 只是一個函數, 執行完成后 getName 被重新賦值
    • getName 因為被重新賦值為 1, 輸出1
  • getName()
    • 由於 getName 被重新賦值, 所以輸出 1
  • new Foo.getName();
    • Foo.getName 並未被修改
    • new 沒有起任何作用
    • 輸出2
  • new Foo().getName();
    • 構造函數創建了實例對象
    • 對象中沒有 getName 方法, 要從對象的構造函數中的原型中尋找
    • 在 Foo.prototype 中得到 getName 輸出為 3
  • new new Foo().getName();
  • 構造函數創建了實例對象
  • 對象中沒有 getName 方法, 要從對象的構造函數中的原型中尋找
  • new 沒有起作用
  • 在 Foo.prototype 中得到 getName 輸出為 3

例題 2

var length = 10;
function fn() {
    console.log( this.length );
}

var obj = {
    length: 5,
    method: function ( fn ) {
        fn();
        arguments[ 0 ]();  // [ fn, 1, 2, 3, length: 4]
    }
};
obj.method( fn, 1, 2, 3 );

分析

  1. 預解析
    • 變量名 length, obj 和 函數名fn 聲明提升, 函數名和函數體綁定
  2. 執行代碼
    • length = 10, 此時 length 可以看作是 window 下的屬性
    • obj = {}, 進行賦值
    • 執行 obj 中的 method 方法
    • 將 函數體 fn 進行傳參
    • 跳進函數 fn,執行函數 fn(), 函數中的 this 指的是 window 下的 length, 為10
    • 明確argument是一個對象, argument[0] 指的是 fn
    • 使用對象調用函數, 這里的 this 指的是 argument 這個對象
    • 得到 argument.length 為 4
    • 最后輸出結果為 10 4

例題 3

    var o = {
        method : function(){
            console.log(this);
        }
    };
    o.method();
    var f = o.method;
    f();
    (o.method)();
    var obj = {};
    (obj.fn = o.method)();
    (obj.fn)();

分析

  1. 預解析
    • 變量名 o, f ,obj 聲明提升
  2. 執行函數
    • o = {},進行賦值
    • 執行o.method方法, 這里的 this 指的是 o 這個對象
    • 將 o.method 函數體,賦值給f
    • 執行 f(), 這里的 this 指的是window
    • (o.method)是一個函數表達式, 和 o.method() 的結果一致
    • obj = {}, obj進行賦值
    • o.method 的函數體, 賦值給obj.fn, 執行之后, 這里的 this 指的是window
    • (obj.fn)是一個函數表達式, 和 obj.fn() 的結果一致, tshi 指向 obj


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM