4.1 函式隱含引數
當我們在使用函式時,會傳遞 arguments 與 this 這兩個引數
arguments
:函式隱含參數 ( Implicit function parameter )
this
:代表這個函式所處的背景空間 ( function context )
4.1.1 arguments 參數
- arguments 是傳遞給 function 的所有 parameter 之集合
- arguments 並不是陣列
function sum() { console.log(arguments); } sum(1, 2, 3);
可實現 function overloading
函式名相同,函式的引數列表不同(包括引數個數和引數型別),根據引數的不同去執行不同的操作。
function overload () {
if (arguments.length === 1) {
console.log('一個參數')
}
if (arguments.length === 2) {
console.log('兩個參數')
}
}
overload(1); // 傳入一個引數
overload(1, 2); // 傳入兩個引數
Ref: JavaScript中的函式過載(Function overloading) - IT閱讀
jQuery 的 css function
jQuery.fn.extend( {
css: function( name, value ) {
return access( this, function( elem, name, value ) {
var styles, len,
map = {},
i = 0;
if ( Array.isArray( name ) ) { // 判斷參數 name 是否為 array
styles = getStyles( elem );
len = name.length;
for ( ; i < len; i++ ) {
map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
}
return map;
}
return value !== undefined ? // 判斷 value 是否為 undefined
jQuery.style( elem, name, value ) :
jQuery.css( elem, name );
}, name, value, arguments.length > 1 );
}
} );
Ref: jquery/css.js at 39c5778c649ad387dac834832799c0087b11d5fe · jquery/jquery · GitHub
array 的 slice 方法也有 overloading
我們可以使用 rest parameter 取代 arguments parameter
ref: prefer-rest-params - Rules - ESLint - Pluggable JavaScript linter
不訂參數是一個真正的陣列
Arguments 物件可作為函式參數的別名
function infiltrate(person) {
arguments[0] = 'ninja'; // 改動物件會造成相對應的參數被異動
console.log(person); // 'ninja'
person = 'gardener';
console.log(arguments[0]); // 'gardener'
}
infiltrate("gardener");
避免別名,容易造成混淆的風險,因此可設定嚴格模式禁止變動的行為
4.1.2 this 參數:介紹函式背景空間 context
- 與函式呼叫相關聯的物件,因此通常被稱為 function context
- this 所指向的東西會受到
函式定義的方式
與呼叫的方式
影響
4.2 呼叫函式 Invoking functions
呼叫函式的方式
- 作為函式:
foo()
,一般呼叫函式的形式 - 作為一個方法:
foo.bar()
,把呼叫綁到一個物件上 - 作為一個建構器函式:
new Foo()
,創建新的物件 - 經由函式的 apply 與 call 方法:
foo.alppy(bar) or foo.call(bar)
4.2.1 作為函式呼叫
function ninja() {
return this;
}
function samurai() {
"use strict";
return this;
}
ninja(); // window,function context 為 window
samurai(); // undefined, 在嚴格模式下 function context 為 undefined
4.2.2 作為方法呼叫
function whatsMyContext() {
return this;
}
var getMyThis = whatsMyContext;
getMyThis(); // function context is window
var ninja1 = {
getMyThis: whatsMyContext
};
var ninja2 = {
getMyThis: whatsMyContext
};
ninja1.getMyThis(); // function context 為 ninja1
ninja2.getMyThis(); // function context 為 ninja2
函式作為方法來呼叫,可以在任何方法中引用該方法所擁有的物件,這是物件導向程式設計中的一項基本概念
4.2.3 作為建構器來呼叫 Invocation as a constructor
function Ninja() {
this.skulk = function() {
return this;
};
}
var ninja1 = new Ninja();
var ninja2 = new Ninja();
assert(ninja1.skulk() === ninja1, "The 1st ninja is skulking");
assert(ninja2.skulk() === ninja2, "The 2nd ninja is skulking");
使用關鍵字 new 來呼叫函式,會觸發以下步驟
- 建立一個新的空物件
- 物件被當成 this 參數傳遞給建構器,因此成為建構器的函式背景空間
- new 運算子會回傳新建立的物件 (但會有例外)
constructor 的目的是建立一個新物件,設置好它,然後將物件作為建構器的回傳值。
任何會干擾這個目的的事物都不應該出現在建構器裡面。
因此要作為 constructor 的函式內不應該定義回傳值 ( return xxx )。
建構器的回傳值
// 4.8 Constructors returning primitive values, 回傳基礎型值的建構器函式
function Ninja() {
this.skulk = function () {
return true;
};
return 1;
}
// 作為 function 呼叫如期回傳 1
assert(Ninja() === 1,
"Return value honored when not called as a constructor");
// 用 new 運算子呼叫,回傳值會被忽略
var ninja = new Ninja();
assert(typeof ninja === "object",
"Object returned when called as a constructor");
assert(typeof ninja.skulk === "function",
"ninja object has a skulk method");
var puppet = {
rules: false
};
function Emperor() {
this.rules = true;
return puppet;
}
// 如果 constructor 回傳一個物件, 該物件將作為 new 表達式的回傳值,而一開始被作為 this 傳遞給建構器的新物件將被丟棄
var emperor = new Emperor();
assert(emperor === puppet,
"The emperor is merely a puppet!");
assert(emperor.rules === false,
"The puppet does not know how to rule!");
如果 constructor 回傳的不是物件,則回傳的值會被忽略,會回傳新建立的物件(this)
建構器的設計考量
函式和方法通常以一個描述他們在做什麼的動詞開始,並以小寫開頭
sum, add, sort, max, min, createObject
建構器通常用物件的名詞來命名,並大寫開頭
Car, Cat, Ball, House
4.3 修復函式背景空間的問題
- 在建構器函式建立,this 為新建立的物件
- 在建立物件時建立,this 指向當前的 function context
function Foo() {
this.age = 20;
this.showAgeUseArrowFun = () => { // 在建構器函式建立,this 為新建立的物件
console.log('showAgeUseArrowFun', this.age);
}
this.showAgeUseFun = function() {
console.log('showAgeUseFun', this.age);
}
}
var age = 100;
var foo = new Foo();
foo.showAgeUseArrowFun(); // 20
var showAgeUseArrowFun = foo.showAgeUseArrowFun;
showAgeUseArrowFun(); // 20
foo.showAgeUseFun(); // 20
var showAgeUseFun = foo.showAgeUseFun;
showAgeUseFun(); // 100
var bar = {
age: 999,
showAgeUseArrowFun: () => { // 在建立物件時建立,this 指向當前的 function context
console.log('showAgeUseArrowFun', this.age);
},
showAgeUseFun: function() {
console.log('showAgeUseFun', this.age);
}
}
bar.showAgeUseArrowFun(); // 100
bar.showAgeUseFun(); // 999
4.3.2. Using the bind method
可使用 bind 方法建立一個新函式,function context 會綁定到指定的物件上
<button id="test">Click Me!</button>
<script>
var button = {
clicked: false,
click: function(){
this.clicked = true;
assert(button.clicked,"The button has been clicked");
}
};
var elem = document.getElementById("test");
elem.addEventListener("click", button.click.bind(button));
var boundFunction = button.click.bind(button);
assert(boundFunction != button.click,
"Calling bind creates a completly new function");
</script>
呼叫 bind 不會修改原始的函式,而是建立一個全新的函式。
寫 React Component 時,使用到 bind 的情境
import React from "react";
class Button extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
// 將 function context 綁定到目前的 this 上
this.handleClick = this.handleClick.bind(this);
console.log(this); // Button
}
handleClick() {
this.setState(state => ({
count: state.count + 1
}));
}
render() {
return (
<div>
<button onClick={this.handleClick}>click me</button> count:
{this.state.count}
</div>
);
}
}
export default Button;
如果不使用 bind,handleClick 裡面的 this 會指向 window 物件
bind example