jS四種函數的調用方式

6- js 函數的四種調用方式
2016年11月04日 13:41:54
閱讀數:7559前端

  1. 函數的四種調用方式

函數有下列調用模式面試

函數調用模式
方法調用模式
構造器模式
上下文模式
  1. 函數調用 模式

要調用,就確定要先定義,函數的定義方式:數組

聲明式: function fuc() {}
表達式式: var func = function() {};
Function: new Function( ‘參數’,…,’函數體’ );

單獨獨立調用的,就是函數調用模式,即 函數名( 參數 ),不能加任何其餘的東西, 對象 o.fuc() 就不是了。app

在函數調用模式中, this 表示全局對象 windowide

任何自調用函數都是函數模式。函數

  1. 方法調用 模式 method

所謂方法調用,就是用對象的方法調用。方法是什麼,方法自己就是函數,可是,方法不是單獨獨立的,而是要經過一個對象引導來調用。學習

就是說方法對象必定要有宿主對象。優化

即 對象.方法(參數)this

this表示引導方法的對象,就是指宿主對象prototype

對比-函數調用模式:

方法調用模式是否是獨立的,須要宿主,而函數調用模式是獨立的
方法調用模式方式: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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

解析:

fn() 前面沒有引導對象,是函數調用模式, this是全局對象,輸出 10

arguments[ 0 ](),arguments是一個僞數組對象, 這裏調用至關於經過數組的索引來調用.

這裏引導對象即宿主就是 arguments對象。

因此,執行時,this 就是指 arguments,因爲傳了兩個參數,因此 輸出爲 arguments.length 就是 2
  1. 構造器模式(構造函數模式, 構造方法模式)

constructor

特色: 使用 new 關鍵字引導

執行步驟: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();
    1
    2
    3
    4
    5
    6
    7
    8
    9

執行完 new 進入構造函數時, p 對象的原型 就指向了 構造函數 Person

而 構造時,this 指的的是 p 對象,是經過對象動態添加屬性來構造的

小貼士:若是調用構造函數的時候, 構造函數沒有參數, 圓括號是能夠省略的。

    function Person() {
        this.name = 'jim';
    }
    var p = new Person; // 不傳參,能夠簡寫,不影響構造
    console.log( p );   // p 含有 name屬性
    1
    2
    3
    4
    5

↑ 不傳參,能夠簡寫,不影響構造

返回值

    不寫 return 語句, 那麼 構造函數 默認返回 this

    在構造函數 return 基本類型( return num, return 1223 ). 則忽略返回類型.

    在構造函數 return 引用類型, 那麼構造函數返回該引用類型數據, 而忽略 this

    function Person () {
        this.name = 'Jepson';
        return 123;
    }
    var p1 = new Person();
    console.log( p1 );
    1
    2
    3
    4
    5
    6

↑ 忽略了 123,返回 this 對象, 指向構建的實例

    function Person () {
        this.name = 'Jepson';
        return { 'peter': 'nihao' };
    }
    var p1 = new Person();
    console.log( p1 );
    1
    2
    3
    4
    5
    6

↑ 忽略了 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 ??

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

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

/* 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 -------

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

所有解析過程 ↓

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
  1. 上下文調用模式

就是 環境調用模式 => 在不一樣環境下的不一樣調用模式

簡單說就是統一一種格式, 能夠實現 函數模式與方法模式

-> 語法(區分)

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 對象

1
2
3
4
5
6
7
8
9
10

帶有參數的函數如何實現上下文調用?

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 );

1
2
3
4
5
6
7
8
9
10
11
12

使用 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

1
2
3
4
5
6
7
8
9

方法借用的案例

需求, 得到 div 與 p 標籤, 並添加邊框 border: 1px solid red

通常作法:

var p_list = document.getElementsByTagName('p');
var div_list = document.getElementsByTagName('div');

var i = 0;
for( ; i < p_list.length; i++ ) {
    p_list[ i ].style.border = "1px solid red";
}
for( i = 0; i < div_list.length; i++ ) {
    div_list[ i ].style.border = "1px solid red";
}

1
2
3
4
5
6
7
8
9
10

利用方法借用優化,元素獲取

var t = document.getElementsByTagName;
var p_list = t.apply( document, [ 'p' ] );  // 方法借用
var div_list = t.apply( document, [ 'div' ] ); // 方法借用

1
2
3

接下來考慮下面的優化,兩個for循環,只要將數組合並了,就能夠只用一個 for 循環

數組合並

    var arr1 = [ 1, 2, 3 ];
    var arr2 = [ 5, 6 ];
    arr1.push.apply( arr1, arr2 );  // 方法調用,第一個給上對象
    // 等價於 Array.prototype.push.apply( arr1, arr2 );
    1
    2
    3
    4

因此同理,利用 apply 方法借用,將兩個僞數組合併成同一個數組

    var arr = [];   // 僞數組沒有 push 方法,因此這裏要 聲明一個 數組
    arr.push.apply( arr, p_list );  // 將 p_list裏的內容,一個個當成參數放了進來,至關於不用遍歷了
    arr.push.apply( arr, div_list ); // 同上,方法借用
    console.log( arr );
    1
    2
    3
    4

將二者綜合, 使用forEach,最終 6 行就解決了

var t = document.getElementsByTagName, arr = [];
arr.push.apply( arr, t.apply( document, [ 'p' ] ) );
arr.push.apply( arr, t.apply( document, [ 'div'] ) );
arr.forEach( function( val, index, arr ) {
    val.style.border = '1px solid red';
});

1
2
3
4
5
6

call 調用

在使用 apply 調用的時候, 函數參數, 必須以數組的形式存在. 可是有些時候數組封裝比較複雜

因此引入 call 調用, call 調用與 apply 徹底相同, 惟一不一樣是 參數不須要使用數組

foo( 123, 567 );

foo.apply( null, [ 123, 567 ] );

foo.call( null, 123, 567 );

1
2
3
4
5

函數調用: 函數名.call( null, 參數1,參數2,參數3… );

方法調用: 函數名.call( obj, 參數1,參數2, 參數3… );

不傳參時,apply 和 call 徹底同樣
借用構造方法實現繼承

function Person ( name, age, gender ) {
    this.name = name;
    this.age = age;
    this.gender = gender;
}

function Student ( name, age, gender, course ) {
    // 原型鏈結構不會變化,同時實現了繼承的效果
    Person.call( this, name, age, gender );// 借用Person構造方法
    this.course = course;
}

var p = new Student ( 'jim', 19, 'male', '前端' );
console.log( p );

1
2
3
4
5
6
7
8
9
10
11
12
13
14

補充知識 1. 函數的 bind 方法 ( ES5 )

bind 就是 綁定

仍是上面那個案例 得到 div 與 p 標籤, 並添加邊框 border: 1px solid red

var t = document.getElementsByTagName, arr = [];
arr.push.apply( arr, t.call( document, 'p'  ) );
arr.push.apply( arr, t.call( document, 'div' ) );
arr.forEach( function( val, index, arr ) {
    val.style.border = '1px solid red';
});

1
2
3
4
5
6

咱們 讓 t 包含函數體(上面的方式),同時包含 對象,就能夠更精簡

var t = document.getElementsByTagName.bind( document ), arr = [];
arr.push.apply( arr, t('p') );
arr.push.apply( arr, t('div') );
arr.forEach( function( val, index, arr ) {
    val.style.border = '1px solid red';
});

1
2
3
4
5
6

bind : 就是讓函數綁定對象的一種用法

函數自己就是能夠調用, 可是其若是想要做爲方法調用, 就必須傳入宿主對象, 而且使用 call 或 apply 形式

可是 bind 使得個人函數能夠與某一個對象綁定起來, 那麼在調用函數的時候, 就好像是該對象在調用方法,就能夠直接傳參,而不須要傳宿主對象。

語法: 函數.bind( 對象 )

返回一個函數 foo,那麼調用 返回的函數 foo, 就好像 綁定的對象在調用 該方法同樣

t.call( document, 'p' );

 t( 'p' ); 綁定後,就不用傳宿主對象了,這裏調用時上下文已經變成了 document

 bind 是 ES 5 給出的函數調用方法

1
2
3
4
5

補充知識 2. Object.prototype 的成員

Object.prototype 的成員

constructor
hasOwnProperty 判斷該屬性是否爲本身提供
propertyIsEnumerable 判斷屬性是否能夠枚舉
isPrototypeOf 判斷是否爲原型對象
toString, toLocaleString, valueOf

function Person() {
    this.name = 'jim';
}
Person.prototype.age = 19;
var p = new Person();
console.log( p.hasOwnProperty( 'name' ) );  // true; p 是否含有 name 屬性,原型上無論
console.log( p.hasOwnProperty( 'age' ) ); // false;  p 是否含有 age 屬性

/* Person.prototype 是 p 的原型 */
console.log( p.isPrototypeOf( Person.prototype ) ); // false
console.log( Person.prototype.isPrototypeOf( p ) );  // true;

1
2
3
4
5
6
7
8
9
10
11

用途:通常把一個對象拷貝到另外一個對象時,能夠進行判斷,更加嚴謹,以防把原型中的屬性也拷貝過去了…
補充知識 3. 包裝類型

字符串 string 是基本類型, 理論上講不該該包含方法

那麼 charAt, substr, slice, …等等方法,理論上都不該該有,但確是有的

因此引入包裝對象的概念,在 js 中爲了更好的使用數據, 爲三個基本類型提供了對應的對象類型

Number
String
Boolean

在 開發中經常會使用基本數據類型, 可是基本數據類型沒有方法, 所以 js 引擎會在須要的時候自動的將基本類型轉換成對象類型, 就是包裝對象

「abc」.charAt( 1 )

「abc」 -> s = new String( 「abc」 )

s.charAt( 1 ) 返回結果之後 s 就被銷燬

當基本類型.方法 的時候. 解釋器首先將基本類型轉換成對應的對象類型, 而後調用方法.

方法執行結束後, 這個對象就被馬上回收

在 apply 和 call 調用的時候, 也會有轉換髮生. 上下文調用的第一個參數必須是對象. 若是傳遞的是數字就會自動轉換成對應的包裝類型
補充知識 4. getter 和 setter 的語法糖 ( ES5 )

語法糖: 爲了方便開發而給出的語法結構

自己實現:

var o = (function () {
    var num = 123;
    return {
        get_num: function () {
            return num;
        },
        set_num: function ( v ) {
            num = v;
        }
    };
})();

1
2
3
4
5
6
7
8
9
10
11

但願得到數據  以對象的形式
o.get_num();            => o.num 形式

但願設置數據  以對象的形式
o.set_num( 456 );       => o.num = 456 形式

1
2
3
4
5

因此 getter 和 setter 誕生 了

var o = (function () {
    var num = 123;
    return {
        // get 名字 () { 邏輯體 }
        get num () {
            console.log( '執行 getter 讀寫器了' );
            return num;
        },

        // set 名字 ( v ) { 邏輯體 }
        set num ( v ) {
            console.log( '執行 setter 讀寫器了' );
            num = v;
        }
    };
})();

console.log( o.num );   // 執行 getter 讀寫器了   123
o.num = 33; // 執行 setter 讀寫器了
console.log( o.num ); // 執行 getter 讀寫器了 33

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

爲何不直接用 對象呢 var o = { num : 123 } ,也能夠讀寫呀?

由於語法糖還能夠 限制其賦值的範圍,使用起來特別爽

var o = (function () {
    var num = 13;
    return {

        // get 名字 () { 邏輯體 }
        get num () {
            console.log( '執行 getter 讀寫器了' );
            return num;
        },

        // set 名字 ( v ) { 邏輯體 }
        set num ( v ) {
            console.log( '執行 setter 讀寫器了' );

            if ( v < 0 || v > 150 ) {
                console.log( '賦值超出範圍, 不成功 ' );
                return;
            }
            num = v;
        }
    };
})();
o.num = -1;         // 執行 setter 讀寫器了
                // 讀寫器賦值超出範圍, 不成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

補充知識 5. ES5 中引入的部分數組方法

forEach
map
filter
some
every
indexOf
lastIndexOf

forEach, 數組遍歷調用,遍歷arr,參數三個 1某項, 2索引, 3整個數組

     var arr = [ 'hello', ' js', {  }, function () {} ];
     // 遍歷 數組
     arr.forEach( function ( v, i, ary ) {
        console.log( i + '=====' + v );
        console.log( ary );
     });
    1
    2
    3
    4
    5
    6

map 映射

語法: 數組.map( fn )

返回一個數組, 數組的每個元素就是 map 函數中的 fn 的返回值

就是對每一項都進行操做,並返回

     var arr = [ 1, 2, 3, 4 ];
     // 數學中: x -> x * x
     var a = arr.map(function ( v, i ) {
        return v * v;
     });
     // a [1, 4, 9, 16]
    1
    2
    3
    4
    5
    6

filter 就是篩選, 函數執行結果是 false 就棄掉, true 就收着

語法: 數組.filter( function ( v, i ) { return true/false })

    var arr = [ 1, 2, 3, 4, 5, 6 ];
    // 篩選奇數
    var a = arr.filter( function ( v ) { return v % 2 === 1; });
    // a [ 1, 3, 5 ]
    1
    2
    3
    4

some 判斷數組中至少有一個數據複合要求 就返回 true, 不然返回 false

    var arr = [ '123', {}, function () {}, 123 ];
    // 判斷數組中至少有一個數字
    var isTrue = arr.some( function ( v ) { return typeof v === 'number'; } );  // true;
    1
    2
    3

every 必須知足全部元素都複合要求才會返回 true

    var arr = [ 1, 2, 3, 4, 5, '6' ];
    // 判斷數組中每個都是數字
    var isTrue = arr.every( function ( v ) { return typeof v === 'number'; } ); } ); // false;
    1
    2
    3

indexOf 在數組中查找元素, 若是含有該元素, 返回元素的須要( 索引 ), 不然返回 -1

     var arr = [ 1, 2, 3, 4, 5 ];
     var res = arr.indexOf( 4 );    // 要找 4
     console.log( res );    // 3     找 4 在 索引爲 3 找到

     var arr = [ 1, 2, 3, 4, 5, 4, 5, 6 ];
     var res = arr.indexOf( 4, 4 ); // 要找 4, 從索引 4開始找
     console.log( res );    // 找到了 索引爲 5
    1
    2
    3
    4
    5
    6
    7

lastIndexOf 從右往左找

     var arr = [ 1, 2, 3, 4, 5, 4, 5, 6 ];
     var res = arr.lastIndexOf( 4 );
     console.log( res );    // 索引爲 5, 從最後一項開始找,即從 length-1 項開始找
相關文章
相關標籤/搜索