函數的四種調用模式
在 js 中 無論是函數, 還是方法, 還是事件, 還是構造器, ... 其本質都是函數. 只是處在不同的位子而已.
四種:
- 函數模式
- 方法模式
- 構造器模式
- 上下文模式
函數模式
特征: 就是一個簡單的函數調用. 函數名的前面沒有任何引導內容.
function foo () {}
var func = function () {};
...
foo();
func();
(function (){})();
this 的含義: 在 函數中 this 表示全局對象, 在瀏覽器中是 window
方法模式
特征: 方法一定是依附於一個對象, 將函數賦值給對象的一個屬性, 那么就成為了方法.
function f() {
this.method = function () {};
}
var o = {
method: function () {}
}
this 的含義: 這個依附的對象.
構造器調用模式
創建對象的時候 構造函數做了什么?
由於構造函數只是給 this 添加成員. 沒有做其他事情. 而方法也可以完成這個操作, 就 this 而言,
構造函數與方法沒有本質區別.
特征:
- 使用 new 關鍵字, 來引導構造函數.
- 構造函數中發 this 與方法中一樣, 表示對象, 但是構造函數中的對象是剛剛創建出來的對象
- 構造函數中不需要 return, 就會默認的 return this
補充:
- 如果手動的添加 return, 就相當於 return this
- 如果手動的添加 return 基本類型; 無效, 還是保留原來 返回 this
- 如果手動添加 return null; 或 return undefiend, 無效
- 如果手動添加 return 對象類型; 那么原來創建的 this 就會被丟掉, 返回的是 return 后面的對象
創建對象的模式
- 工廠方法
// 工廠就是用來生產的, 因此如果函數創建對象並返回, 就稱該函數為工廠函數
function createPerson( name, age, gender ) {
var o = {};
o.name = name;
o.age = age;
o.gender = gender;
return o;
}
// document.createElement()
- 構造方法
- 寄生式創建對象
// 外表看起來就是構造方法, 但是本質不是的構造方法創建對象的方式
function createPerson( name, age, gender ) {
var o = {};
o.name = name;
o.age = age;
o.gender = gender;
return o;
}
var p = new createPerson( 'jim', 19, 'male' );
- 混合式創建
上下文調用模型
上下文 就是環境, 就是自定義設置 this 的含義
語法:
- 函數名.apply( 對象, [ 參數 ] );
- 函數名.call( 對象, 參數 );
描述:
- 函數名就是表示的函數本身, 使用函數進行調用的時候默認 this 是全局變量
- 函數名也可以是方法提供, 使用方法調用的時候, this 是指當前對象.
- 使用 apply 進行調用后, 無論是函數, 還是方法都無效了. 我們的 this, 由 apply 的第一個參數決定
注意:
- 如果函數或方法中沒有 this 的操作, 那么無論什么調用其實都一樣.
- 如果是函數調用 foo(), 那么有點像 foo.apply( window ).
- 如果是方法調用 o.method(), 那么有點像 o.method.apply( o ).
參數問題
無論是 call 還是 apply 在沒有后面的參數的情況下( 函數無參數, 方法無參數 ) 是完全一樣的.
function foo() {
console.log( this );
}
foo.apply( obj );
foo.call( obj );
第一個參數的使用也是有規則的
- 如果傳入的是一個對象, 那么就相當於設置該函數中的 this 為參數
- 如果不傳入參數, 或傳入 null. undefiend 等, 那么相當於 this 默認為 window
foo();
foo.apply();
foo.apply( null );
foo.call( undefined );
- 如果傳入的是基本類型, 那么 this 就是基本類型對應的包裝類型的引用
- number -> Number
- boolean -> Boolean
- string -> String
除了 this 的參數外的參數
在使用 上下文調用的 時候, 原函數(方法)可能會帶有參數, 那么這個參數在上下文調用中使用 第二個( 第 n 個 )參數來表示
function foo( num ) {
console.log( num );
}
foo.apply( null, [ 123 ] );
// 等價於
foo( 123 );
應用
上下文調用只是能修改 this, 但是使用的最多的地方上是借用函數調用.
將偽數組轉換為數組
傳統的做法
var a = {};
a[ 0 ] = 'a';
a[ 1 ] = 'b';
a.length = 2;
// 數組自帶的方法 concat
// 語法: arr.concat( 1, 2, 3, [ 4, [ 5 ] ] );
// 特點不修改原數組
var arr = [];
var newArr = arr.concat( a );
由於 a 是偽數組, 只是長得像數組. 所以這里不行, 但是 apply 方法有一個特性, 可以將數組或偽數組作為參數
foo.apply( obj, 偽數組 ); // IE8 不支持
將 a 作為 apply 的第二個參數
var newArr = Array.prototype.concat.apply( [], a )
處理數組轉換, 實際上就是將元素一個一個的取出來構成一個新數組, 凡是涉及到該操作的方法理論上都可以
- push, unshift
- slice
- splice
push 方法
用法: arr.push( 1 ); 將這個元素加到數組中, 並返回所加元素的個數
arr.push( 1, 2, 3 ); 將這三個元素依次加到數組中, 返回所加個數
var a = { length: 0 }; // 偽數組
a[ a.length++ ] = 'abc'; // a[ 0 ] = 'abc'; a.length++;
a[ a.length++ ] = 'def';
// 使用一個空數組, 將元素一個個放到數組中即可
var arr = [];
arr.push( a ); // 此時不會將元素展開, 而是將這個偽數組作為一個元素加到數組中
// 再次利用 apply 可以展開偽數組的特征
arr.push.apply( arr, a );
// 利用 apply 可以展開偽數組的特性, 這里就相當於 arr.push( a[0], a[1] )
slice
語法: arr.slice( index, endIndex )
如果第二個參數不傳, 那么就是 從 index 一致獲取到結尾
該方法不會修改原數組
var a = { length: 0 };
a[ a.length++ ] = 'abc';
a[ a.length++ ] = 'def';
// 假設他是一個數組, 就是應該 從 0 項開始截取到 最后
// 如果可以的話, 應該 a.slice( 0 )
// 但是他沒有該方法
// 借用 數組的 slice, 將 this 轉換成 這個偽數組
var arr = [];
var newArr = arr.slice.apply( a, [ 0 ] );