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

函數的四種調用模式

概念

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

函數模式

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

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

this的含義瀏覽器

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

方法模式

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

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

this的含義函數

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

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

構造器模式

構造函數在建立對象的時候, 作了些什麼

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

分析:
因爲構造函數只是給 this 添加成員, 而方法也能夠完成這個操做,對與 this 來講, 構造函數和方法沒有本質區別prototype

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

普通狀況, 能夠理解爲構造函數已經默認進行了 return this, 添加在後面的都不會執行code

  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
相關文章
相關標籤/搜索