JavaScript中的函式過載(Function overloading)

FEWY發表於2019-03-02

說明

JavaScript 中沒有真正意義上的函式過載。

函式過載

函式名相同,函式的引數列表不同(包括引數個數和引數型別),根據引數的不同去執行不同的操作。

我們舉個例子看看

function overload(a){
	console.log(`一個引數`)
}

function overload(a,b){
	console.log(`兩個引數`)
}

// 在支援過載的程式語言中,比如 java
overload(1);   	  //一個引數
overload(1,2);    //兩個引數


// 在 JavaScript 中
overload(1);   	  //兩個引數
overload(1,2);    //兩個引數
複製程式碼

在JavaScript中,同一個作用域,出現兩個名字一樣的函式,後面的會覆蓋前面的,所以 JavaScript 沒有真正意義的過載。

但是有各種辦法,能在 JavaScript 中模擬實現過載的效果。

先看第一種辦法,通過 arguments 物件來實現

arguments 物件,是函式內部的一個類陣列物件,它裡面儲存著呼叫函式時,傳遞給函式的所有引數。

function overload () {
  if (arguments.length === 1) {
    console.log(`一個引數`)
  }
  if (arguments.length === 2) {
    console.log(`兩個引數`)
  }
}

overload(1);      //一個引數
overload(1, 2);  //兩個引數
複製程式碼

這個例子非常簡單,就是通過判斷 arguments 物件的 length 屬性來確定有幾個引數,然後執行什麼操作。

但是引數少的情況下,還好,如果引數多一些,if 判斷就需要寫好多,就麻煩了。

所以,我們再來看一個經典的例子
在看這個例子之前,我們先來看一個需求,我們有一個 users 物件,users 物件的values 屬性中存著一些名字。
一個名字由兩部分組成,空格左邊的是 first-name ,空格右邊的是 last-name,像下面這樣。

var users = {
  values: ["Dean Edwards", "Alex Russell", "Dean Tom"]
};
複製程式碼

我們要在 users 物件 中新增一個 find 方法,

當不傳任何引數時, 返回整個users .values
當傳一個引數時,就把 first-name 跟這個引數匹配的元素返回;
當傳兩個引數時,則把 first-name 和 last-name 都匹配的返回。

這個需求中 find方法 需要根據引數的個數不同而執行不同的操作,下來我們通過一個 addMethod 函式,來在 users 物件中新增這個 find 方法。

function addMethod (object, name, fn) {
  // 先把原來的object[name] 方法,儲存在old中
  var old = object[name];

  // 重新定義 object[name] 方法
  object[name] = function () {
    // 如果函式需要的引數 和 實際傳入的引數 的個數相同,就直接呼叫fn
    if (fn.length === arguments.length) {
      return fn.apply(this, arguments);

      // 如果不相同,判斷old 是不是函式,
      // 如果是就呼叫old,也就是剛才儲存的 object[name] 方法
    } else if (typeof old === "function") {
      return old.apply(this, arguments);
    }
  }
}
複製程式碼

addMethod 函式,它接收3個引數
第一個:要繫結方法的物件,
第二個:繫結的方法名稱,
第三個:需要繫結的方法

這個 addMethod 函式在判斷引數個數的時候,除了用 arguments 物件,還用了函式的 length 屬性。

函式的 length 屬性,返回的是函式定義時形參的個數。

簡單說 函式的 length 是,函式需要幾個引數,而 arguments.length 是呼叫函式時,真的給了函式幾個引數

function fn (a, b) {
  console.log(arguments.length)
}
console.log(fn.length);  // 2
fn(`a`);	// 1
複製程式碼

下來我們來使用這個 addMethod 函式

// 不傳引數時,返回整個values陣列
function find0 () {
  return this.values;
}
// 傳一個引數時,返回firstName匹配的陣列元素
function find1 (firstName) {
  var ret = [];
  for (var i = 0; i < this.values.length; i++) {
    if (this.values[i].indexOf(firstName) === 0) {
      ret.push(this.values[i
      ]);
    }
  }
  return ret;
}
// 傳兩個引數時,返回firstName和lastName都匹配的陣列元素
function find2 (firstName, lastName) {
  var ret = [];
  for (var i = 0; i < this.values.length; i++) {
    if (this.values[i
    ] === (firstName + " " + lastName)) {
      ret.push(this.values[i
      ]);
    }
  }
  return ret;
}
// 給 users 物件新增處理 沒有引數 的方法
addMethod(users, "find", find0);

// 給 users 物件新增處理 一個引數 的方法
addMethod(users, "find", find1);

// 給 users 物件新增處理 兩個引數 的方法
addMethod(users, "find", find2);

// 測試:
console.log(users.find()); //["Dean Edwards", "Alex Russell", "Dean Tom"]
console.log(users.find("Dean")); //["Dean Edwards", "Dean Tom"]
console.log(users.find("Dean Edwards")); //["Dean Edwards"]
複製程式碼

addMethod 函式是利用了閉包的特性,通過變數 old 將每個函式連線了起來,讓所有的函式都留在記憶體中。

每呼叫一次 addMethod 函式,就會產生一個 old,形成一個閉包。
我們可以通過 console.dir(users.find) ,把 find 方法列印到控制檯看看。

JavaScript中的函式過載(Function overloading)

上面這個例子是 jQuery 之父 John Resig 寫的,他在他的部落格和他寫的書 《secrets of the JavaScript ninja》第一版中都有提到過,在書中的第4章中也有講解 Function overloading,文中的 addMethod 函式 就是書中的例子 4.15,感興趣的朋友可以去看看。

上面的例子,本質都是在判斷引數的個數,根據不同的個數,執行不同的操作,而下來舉的例子是通過判斷引數的型別,來執行不同的操作。

我們看看 jQuery 中的 css( ) 方法

css( ) 方法返回或設定匹配的元素的一個或多個樣式屬性。

css(name|pro|[,val|fn])

JavaScript中的函式過載(Function overloading)

我們可以看到 css( ) 方法,有5種 引數情況,其中3種是一個引數,另外兩種是兩個引數。
而在只有一個引數的情況下,如果引數型別是字串或者陣列就是獲取屬性值,而如果引數是物件,就是是設定屬性值。

jQuery 的 css( ) 方法就是通過判斷引數的型別,來確定執行什麼操作。

我們來看看jQuery 3.3.1中的原始碼

// name 表示屬性名
// value 表示屬性值
css: function( name, value ) {
	return access( this, function( elem, name, value ) {
		var styles, len,
			map = {},
			i = 0;

		// 判斷屬性名是不是陣列
		// 是陣列就遍歷,呼叫jQuery.css 方法傳入每個屬性名,獲取樣式
		if ( Array.isArray( name ) ) {
			styles = getStyles( elem );
			len = name.length;

			for ( ; i < len; i++ ) {
				map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
			}

			return map;
		}

		// 如果value 不等於 undefined 就呼叫jQuery.style 方法設定樣式
		// 如果value 等於 undefined 就呼叫jQuery.css 方法獲取樣式
		return value !== undefined ?
			jQuery.style( elem, name, value ) :
			jQuery.css( elem, name );
	}, name, value, arguments.length > 1 );
}
複製程式碼

css( ) 方法依賴於三個方法:

1、jQuery.access( ) 方法,這個方法可以獲取 或 設定,一個或者多個屬性值

jQuery.access( ) 方法裡有這樣的程式碼

// 設定多個屬性值
// 如果屬性名(key)的型別是 object,就遍歷這個物件
// 遍歷一次就呼叫一次 access()方法,並傳入這次的屬性名和屬性值
if ( jQuery.type( key ) === "object" ) {
	chainable = true;
	for ( i in key ) {
		jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
	}

// 設定一個值
} else if ( value !== undefined ) {
	......
}
複製程式碼

也就是這個方法,在幫 css( ) 方法判斷第一個引數是字串還是物件的。

2、jQuery.style( ) 方法:在DOM節點上讀取或設定樣式屬性

在css( )方法中,如果有傳第二個引數,也就是有要設定的屬性值時,那就會呼叫 jQuery.style( ) 方法設定樣式

3、jQuery.css( ) :在DOM元素上讀取DOM樣式值

這裡的 jQuery.css( ) 是通過 jQuery.extend( ) 新增的方法,而我們最開始提到的 css( )方法,是通過 jQuery.fn.extend( ) 新增的方法,他們不是同一個方法。

jQuery.extend( )與 jQuery.fn.extend( )的區別

jQuery.extend( )是為jQuery類新增類方法(靜態方法),需要通過jQuery類來呼叫(直接使用 $.xxx 呼叫);

jQuery.fn.extend( )是為jQuery類新增成員數(例項方法),所有jQuery例項都可以直接呼叫(需要使用 $().xxx 呼叫)。

過載的好處

過載其實是把多個功能相近的函式合併為一個函式,重複利用了函式名。
假如jQuery中的css( )方法不使用 過載,那麼就要有5個不同的函式,來完成功能,那我們就需要記住5個不同的函式名,和各個函式相對應的引數的個數和型別,顯然就麻煩多了。

總結

雖然 JavaScript 並沒有真正意義上的過載,但是過載的效果在JavaScript中卻非常常見,比如 陣列的 splice( )方法,一個引數可以刪除,兩個引數可以刪除一部分,三個引數可以刪除完了,再新增新元素。
再比如 parseInt( )方法 ,傳入一個引數,就判斷是用十六進位制解析,還是用十進位制解析,如果傳入兩個引數,就用第二個引數作為數字的基數,來進行解析。

文中提到的實現過載效果的方法,本質都是對引數進行判斷,不管是判斷引數個數,還是判斷引數型別,都是根據引數的不同,來決定執行什麼操作的。

雖然,過載能為我們帶來許多的便利,但是也不能濫用,不要把一些根本不相關的函式合為一個函式,那樣並沒有什麼意義。

參考

淺談JavaScript函式過載

js如何實現過載

JavaScript函式過載

這裡寫圖片描述

相關文章