本章節提供建議,說明如何正確使用您可以在 ES6 中呼叫的實體 (透過函式呼叫、方法呼叫等)。
super
的呼叫限制在特定位置Object.prototype
和 Array.prototype
的縮寫
name
屬性
name
new
呼叫的?在 ES5 中,單一建構函式(傳統)函式扮演了三個角色
在 ES6 中,有更多專門化。三個職責現在處理如下。就函式定義和類別定義而言,定義可以是宣告或表達式。
特別是對於回呼函式,箭頭函式很方便,因為它們不會遮蔽周圍範圍的 this
。
對於較長的回呼函式和獨立函式,傳統函式可以使用。一些 API 使用 this
作為隱含參數。在這種情況下,你別無選擇,只能使用傳統函式。
請注意,我區分
儘管它們的行為不同(如下所述),但所有這些實體都是函式。例如
> typeof (() => {}) // arrow function
'function'
> typeof function* () {} // generator function
'function'
> typeof class {} // class
'function'
有些呼叫可以在任何地方進行,而另一些呼叫則限於特定位置。
在 ES6 中,三種類型的呼叫可以在任何地方進行
func(3, 1)
obj.method('abc')
new Constr(8)
super
的呼叫限於特定位置 兩種呼叫可以透過 super
關鍵字進行;它們的使用限於特定位置
super.method('abc')
super(8)
constructor()
內可用。非方法函式與方法之間的差異在 ECMAScript 6 中變得更加明顯。現在針對兩者都有特殊實體,而且只有它們才能執行某些動作
this
」)擷取 this
。super
的支援,用於參考超級屬性和執行超級方法呼叫。本節提供使用可呼叫實體的秘訣:何時最好使用哪個實體等。
作為回呼,箭頭函式相較於傳統函式有兩個優點
this
是詞彙的,因此使用起來更安全。this
作為隱含參數 唉,一些 JavaScript API 將 this
用作其回呼的隱含引數,這會阻止您使用箭頭函式。例如:B 行中的 this
是 A 行中函式的隱含引數。
beforeEach
(
function
()
{
// (A)
this
.
addMatchers
({
// (B)
toBeInRange
:
function
(
start
,
end
)
{
···
}
});
});
這種模式較不明確,而且會阻止您使用箭頭函式。
這很容易修復,但需要變更 API
beforeEach
(
api
=>
{
api
.
addMatchers
({
toBeInRange
(
start
,
end
)
{
···
}
});
});
我們已將 API 從隱含參數 this
變成明確參數 api
。我喜歡這種明確性。
this
的值 在某些 API 中,有其他方式可以取得 this
的值。例如,以下程式碼使用 this
。
var
$button
=
$
(
'#myButton'
);
$button
.
on
(
'click'
,
function
()
{
this
.
classList
.
toggle
(
'clicked'
);
});
但也可以透過 event.target
存取事件的目標
var
$button
=
$
(
'#myButton'
);
$button
.
on
(
'click'
,
event
=>
{
event
.
target
.
classList
.
toggle
(
'clicked'
);
});
作為獨立函式(相對於回呼函式),我偏好函式宣告
function
foo
(
arg1
,
arg2
)
{
···
}
其優點為
function
是一個優點,你希望該建構顯得突出。有一個注意事項:通常,你不需要在獨立函式中使用 this
。如果你使用它,你希望存取周圍範圍的 this
(例如,包含獨立函式的函式)。唉,函式宣告不允許你這麼做,它們有自己的 this
,會遮蔽周圍範圍的 this
。因此,你可能希望讓程式碼檢查器警告你函式宣告中的 this
。
獨立函式的另一個選項是將箭頭函式指定給變數。因為它是字面上的,所以避免了 this
的問題。
const
foo
=
(
arg1
,
arg2
)
=>
{
···
};
函式定義是建立使用 super
函式的唯一方法。它們是物件文字和類別的明顯選擇(在這些情況下,它們是定義函式的唯一方法),但要如何將函式新增到現有的物件?例如
MyClass
.
prototype
.
foo
=
function
(
arg1
,
arg2
)
{
···
};
以下是在 ES6 中執行相同動作的快速方法(注意事項:Object.assign()
無法正確移動使用 super
的函式)。
Object
.
assign
(
MyClass
.
prototype
,
{
foo
(
arg1
,
arg2
)
{
···
}
});
有關更多資訊和注意事項,請參閱 Object.assign()
部分。
通常,函式值屬性應該透過函式定義建立。不過,有時箭頭函式是更好的選擇。以下兩個小節說明何時使用哪一種方法:前一種方法較適合有函式的物件,後一種方法較適合有回呼函式的物件。
如果函式值屬性真的是函式,則透過函式定義建立這些屬性。如果屬性值與物件(以下範例中的 obj
)及其同層函式密切相關,而不是與周圍範圍(範例中的 surroundingMethod()
)相關,則情況就是如此。
使用函式定義時,屬性值的 this
是函式呼叫的接收者(例如,如果函式呼叫為 obj.m(···)
,則為 obj
)。
例如,您可以使用 WHATWG 串流 API,如下所示
const
surroundingObject
=
{
surroundingMethod
()
{
const
obj
=
{
data
:
'abc'
,
start
(
controller
)
{
···
console
.
log
(
this
.
data
);
// abc (*)
this
.
pull
();
// (**)
···
},
pull
()
{
···
},
cancel
()
{
···
},
};
const
stream
=
new
ReadableStream
(
obj
);
},
};
obj
是其屬性 start
、pull
和 cancel
為實際函式的物件。因此,這些函式可以使用 this
來存取物件本機狀態(第 * 行)並互相呼叫(第 ** 行)。
如果屬性值是回呼,請透過箭頭函式建立函式值屬性。此類回呼通常與其周圍範圍(以下範例中的 surroundingMethod()
)密切相關,而不是與其儲存的物件(範例中的 obj
)相關。
箭頭函式的 this
是周圍範圍的 this
(詞彙 this
)。箭頭函式是絕佳的回呼,因為這是您通常希望回呼(實際非函式函式)的行為。回呼不應有自己的 this
,會遮蔽周圍範圍的 this
。
如果屬性 start
、pull
和 cancel
是箭頭函式,則它們會選取 surroundingMethod()
(其周圍範圍)的 this
const
surroundingObject
=
{
surroundingData
:
'xyz'
,
surroundingMethod
()
{
const
obj
=
{
start
:
controller
=>
{
···
console
.
log
(
this
.
surroundingData
);
// xyz (*)
···
},
pull
:
()
=>
{
···
},
cancel
:
()
=>
{
···
},
};
const
stream
=
new
ReadableStream
(
obj
);
},
};
const
stream
=
new
ReadableStream
();
如果第 * 行的輸出讓您感到驚訝,請考慮以下程式碼
const
obj
=
{
foo
:
123
,
bar
()
{
const
f
=
()
=>
console
.
log
(
this
.
foo
);
// 123
const
o
=
{
p
:
()
=>
console
.
log
(
this
.
foo
),
// 123
};
},
}
在 bar()
函式中,f
的行為應立即合理。o.p
的行為較不顯而易見,但與 f
相同。兩個箭頭函式具有相同的周圍詞彙範圍 bar()
。後者箭頭函式被物件文字包圍並不會改變這一點。
本節提供在 ES6 中避免使用 IIFE 的提示。
在 ES5 中,如果您想保留變數為本機變數,則必須使用 IIFE
(
function
()
{
// open IIFE
var
tmp
=
···
;
···
}());
// close IIFE
console
.
log
(
tmp
);
// ReferenceError
在 ECMAScript 6 中,您可以簡單地使用區塊和 let
或 const
宣告
{
// open block
let
tmp
=
···
;
···
}
// close block
console
.
log
(
tmp
);
// ReferenceError
在不透過函式庫(例如 RequireJS、browserify 或 webpack)使用模組的 ECMAScript 5 程式碼中,公開模組模式很受歡迎,且基於 IIFE。其優點是它清楚區分公開和私人內容
var
my_module
=
(
function
()
{
// Module-private variable:
var
countInvocations
=
0
;
function
myFunc
(
x
)
{
countInvocations
++
;
···
}
// Exported by module:
return
{
myFunc
:
myFunc
};
}());
此模組模式產生全域變數,並使用如下方式
my_module
.
myFunc
(
33
);
在 ECMAScript 6 中,模組是內建的,因此採用它們的障礙很低
// my_module.js
// Module-private variable:
let
countInvocations
=
0
;
export
function
myFunc
(
x
)
{
countInvocations
++
;
···
}
此模組不會產生全域變數,並使用如下方式
import
{
myFunc
}
from
'my_module.js'
;
myFunc
(
33
);
在 ES6 中,您仍然需要立即調用的函式,有一種使用案例:有時您只能透過一系列陳述式產生結果,而非透過單一表達式。如果您想要內嵌那些陳述式,您必須立即呼叫函式。在 ES6 中,您可以透過立即呼叫箭頭函式來儲存幾個字元
const
SENTENCE
=
'How are you?'
;
const
REVERSED_SENTENCE
=
(()
=>
{
// Iteration over the string gives us code points
// (better for reversal than characters)
const
arr
=
[...
SENTENCE
];
arr
.
reverse
();
return
arr
.
join
(
''
);
})();
請注意,您必須像顯示的那樣加括號(括號在箭頭函式周圍,而不是在完整的函式呼叫周圍)。詳細說明請參閱箭頭函式章節。
在 ES5 中,建構函式是建立物件工廠的主流方式(但也有許多其他技術,有些可以說是更優雅)。在 ES6 中,類別是實作建構函式的主流方式。幾個架構支援它們作為其自訂繼承 API 的替代方案。
本節從秘笈開始,然後詳細描述每個 ES6 可呼叫實體。
實體產生的值的特性
函式宣告/函式表達式 | 箭頭 | 類別 | 方法 | |
---|---|---|---|---|
可呼叫函式 | ✔ | ✔ | × | ✔ |
可呼叫建構函式 | ✔ | × | ✔ | × |
原型 | F.p |
F.p |
SC | F.p |
屬性 prototype
|
✔ | × | ✔ | × |
整個實體的特性
函式宣告 | 函式表達式 | 箭頭 | 類別 | 方法 | |
---|---|---|---|---|---|
提升 | ✔ | × | |||
建立 window 屬性 (1) |
✔ | × | |||
內部名稱 (2) | × | ✔ | ✔ | × |
實體主體的特性
函式宣告 | 函式表達式 | 箭頭 | 類別 (3) | 方法 | |
---|---|---|---|---|---|
this |
✔ | ✔ | lex | ✔ | ✔ |
new.target |
✔ | ✔ | lex | ✔ | ✔ |
super.prop |
× | × | lex | ✔ | ✔ |
super() |
× | × | × | ✔ | × |
圖例 – 表格儲存格
F.p
:Function.prototype
Function.prototype
。詳細說明請參閱類別章節。圖例 – 註腳
關於產生器函式和方法呢?這些函式和方法就像它們的非產生器對應項目一樣,有兩個例外
(GeneratorFunction).prototype
((GeneratorFunction)
是內部物件,請參閱「反覆運算 API 內的繼承(包括產生器)」一節中的圖表)。this
的規則 函式呼叫 | 方法呼叫 | new |
|
---|---|---|---|
傳統函式(嚴格) | 未定義 |
接收器 | 執行個體 |
傳統函式(寬鬆) | window |
接收器 | 執行個體 |
產生器函式(嚴格) | 未定義 |
接收器 | TypeError |
產生器函式(寬鬆) | window |
接收器 | TypeError |
方法(嚴格) | 未定義 |
接收器 | TypeError |
方法(寬鬆) | window |
接收器 | TypeError |
產生器方法(嚴格) | 未定義 |
接收器 | TypeError |
產生器方法(寬鬆) | window |
接收器 | TypeError |
箭頭函式(嚴格和寬鬆) | 字彙 | 字彙 | TypeError |
類別(隱含嚴格) | TypeError |
TypeError |
SC 協定 |
圖例 – 表格儲存格
this
接收新的執行個體。衍生類別從其超類別取得其執行個體。詳細資訊說明於 類別章節 中。這些是您從 ES5 認識的函式。有兩種方法可以建立它們
const
foo
=
function
(
x
)
{
···
};
function
foo
(
x
)
{
···
}
this
的規則
this
在嚴格模式函式中為 undefined
,在寬鬆模式中為全域物件。this
為方法呼叫的接收器(或 call
/apply
的第一個引數)。this
為新建立的執行個體。產生器函式說明於 產生器章節 中。它們的語法類似於傳統函式,但它們有一個額外的星號
const
foo
=
function
*
(
x
)
{
···
};
function
*
foo
(
x
)
{
···
}
this
的規則如下。請注意,this
永遠不會指產生器物件。
this
的處理方式與傳統函式相同。此類呼叫的結果是產生器物件。TypeError
。方法定義可以出現在 物件文字 中
const
obj
=
{
add
(
x
,
y
)
{
return
x
+
y
;
},
// comma is required
sub
(
x
,
y
)
{
return
x
-
y
;
},
// comma is optional
};
以及 類別定義 中
class
AddSub
{
add
(
x
,
y
)
{
return
x
+
y
;
}
// no comma
sub
(
x
,
y
)
{
return
x
-
y
;
}
// no comma
}
如你所見,你必須使用逗號來分隔物件文字中的方法定義,但在類別定義中它們之間沒有分隔符號。前者是為了保持語法一致性,特別是關於 getter 和 setter。
方法定義是你唯一可以使用 super
來參照超屬性的位置。只有使用 super
的方法定義會產生具有內部屬性 [[HomeObject]]
的函式,這是該功能所必需的(詳細資訊說明於 類別章節 中)。
規則
super
。TypeError
。在類別定義中,名稱為 constructor
的方法很特別,如本章稍後所述。
產生器方法說明於 產生器章節 中。它們的語法類似於方法定義,但它們有一個額外的星號
const
obj
=
{
*
generatorMethod
(
···
)
{
···
},
};
class
MyClass
{
*
generatorMethod
(
···
)
{
···
}
}
規則
this
和 super
。箭頭函式說明於 它們自己的章節 中
const
squares
=
[
1
,
2
,
3
].
map
(
x
=>
x
*
x
);
下列變數在箭頭函式中是詞彙的(從周圍範圍中擷取)
arguments
super
this
new.target
規則
this
等。this
仍然是詞彙的,並且不會參照方法呼叫的接收者。TypeError
。類別說明於 它們自己的章節 中。
// Base class: no `extends`
class
Point
{
constructor
(
x
,
y
)
{
this
.
x
=
x
;
this
.
y
=
y
;
}
toString
()
{
return
`(
${
this
.
x
}
,
${
this
.
y
}
)`
;
}
}
// This class is derived from `Point`
class
ColorPoint
extends
Point
{
constructor
(
x
,
y
,
color
)
{
super
(
x
,
y
);
this
.
color
=
color
;
}
toString
()
{
return
super
.
toString
()
+
' in '
+
this
.
color
;
}
}
方法 constructor
很特別,因為它「變成」類別。也就是說,類別與建構函式非常相似
> Point.prototype.constructor === Point
true
規則
this
參照它。衍生類別從其超類別接收其執行個體,這就是為什麼它需要在存取 this
之前呼叫 super()
。在 JavaScript 中呼叫方法有兩種方式
arr.slice(1)
):在 arr
的原型鏈中搜尋屬性 slice
。其結果會呼叫 this
設定為 arr
。Array.prototype.slice.call(arr, 1)
):直接呼叫 slice
,並將 this
設定為 arr
(call()
的第一個引數)。本節說明這兩種方式如何運作,以及為什麼你很少會在 ECMAScript 6 中直接呼叫方法。在我們開始之前,讓我們複習一下我們對原型鏈的了解。
請記住,JavaScript 中的每個物件實際上都是一個由一個或多個物件組成的鏈。第一個物件繼承後續物件的屬性。例如,陣列 ['a', 'b']
的原型鏈如下所示
'a'
和 'b'
的實例
Array.prototype
,由 Array
建構函式提供的屬性Object.prototype
,由 Object
建構函式提供的屬性null
(鏈的結尾,因此實際上不是成員)你可以透過 Object.getPrototypeOf()
檢查鏈。
> var arr = ['a', 'b'];
> var p = Object.getPrototypeOf;
> p(arr) === Array.prototype
true
> p(p(arr)) === Object.prototype
true
> p(p(p(arr)))
null
「較早」物件中的屬性會覆寫「較晚」物件中的屬性。例如,Array.prototype
提供 toString()
方法的陣列特定版本,覆寫 Object.prototype.toString()
。
> var arr = ['a', 'b'];
> Object.getOwnPropertyNames(Array.prototype)
[ 'toString', 'join', 'pop', ··· ]
> arr.toString()
'a,b'
如果你查看方法呼叫 arr.toString()
,你會看到它實際上執行兩個步驟
arr
的原型鏈中,擷取名稱為 toString
的第一個屬性的值。this
設定為方法呼叫的接收者 arr
。你可以使用函式的 call()
方法讓這兩個步驟明確化
> var func = arr.toString; // dispatch
> func.call(arr) // direct call, providing a value for `this`
'a,b'
在 JavaScript 中有兩種方法可以進行直接方法呼叫
Function.prototype.call(thisValue, arg0?, arg1?, ···)
Function.prototype.apply(thisValue, argArray)
call
方法和 apply
方法都是針對函式呼叫的。它們與一般函式呼叫不同,在於你可以為 this
指定一個值。call
透過個別參數提供方法呼叫的引數,apply
則透過陣列提供它們。
使用派送方法呼叫時,接收者扮演兩個角色:用於尋找方法,以及是一個隱含參數。第一個角色的問題在於,如果你想要呼叫一個方法,該方法必須在一個物件的原型鏈中。使用直接方法呼叫時,該方法可以來自任何地方。這允許你從另一個物件借用一個方法。例如,你可以借用 Object.prototype.toString
,並因此將 toString
的原始未覆寫實作套用至陣列 arr
>
const
arr
=
[
'a'
,
'b'
,
'c'
];
>
Object
.
prototype
.
toString
.
call
(
arr
)
'
[
object
Array
]
'
toString()
的陣列版本會產生不同的結果
> arr.toString() // dispatched
'a,b,c'
> Array.prototype.toString.call(arr); // direct
'a,b,c'
與各種物件一起運作的方法(不只與「它們」建構函式的執行個體一起運作)稱為通用。Speaking JavaScript 有一個清單,列出所有通用的方法。清單包含大多數陣列方法和 Object.prototype
的所有方法(必須與所有物件一起運作,因此隱含地是通用的)。
本節涵蓋直接方法呼叫的用例。每次,我都會先描述 ES5 中的用例,然後說明它在 ES6 中如何改變(在 ES6 中,你很少需要直接方法呼叫)。
有些函式會接受多個值,但每個參數只有一個值。如果你想要透過陣列傳遞這些值,該怎麼辦?
例如,push()
允許你破壞性地將多個值附加至陣列
> var arr = ['a', 'b'];
> arr.push('c', 'd')
4
> arr
[ 'a', 'b', 'c', 'd' ]
但是你無法破壞性地附加整個陣列。你可以透過使用 apply()
來解決這個限制
> var arr = ['a', 'b'];
> Array.prototype.push.apply(arr, ['c', 'd'])
4
> arr
[ 'a', 'b', 'c', 'd' ]
類似地,Math.max()
和 Math.min()
只對單一值運作
> Math.max(-1, 7, 2)
7
使用 apply()
,你可以將它們用於陣列
> Math.max.apply(null, [-1, 7, 2])
7
...
) 大多取代了 apply()
透過 apply()
進行直接方法呼叫,只是因為你想要將陣列轉換為參數,這很笨拙,這就是 ECMAScript 6 有散佈運算子 (...
) 的原因。它甚至在派送方法呼叫中提供此功能。
> Math.max(...[-1, 7, 2])
7
另一個範例
> const arr = ['a', 'b'];
> arr.push(...['c', 'd'])
4
> arr
[ 'a', 'b', 'c', 'd' ]
作為額外功能,散佈也適用於 new
運算子
> new Date(...[2011, 11, 24])
Sat Dec 24 2011 00:00:00 GMT+0100 (CET)
請注意,apply()
無法與 new
一起使用 - 上述功能只能透過一個複雜的解決方法在 ECMAScript 5 中達成。
JavaScript 中的某些物件是類陣列,它們幾乎是陣列,但沒有任何陣列方法。我們來看兩個範例。
首先,函式的特殊變數 arguments
類似陣列。它有一個 length
和索引存取元素。
>
var
args
=
function
()
{
return
arguments
}(
'a'
,
'b'
);
>
args
.
length
2
>
args
[
0
]
'a'
但 arguments
不是 Array
的實例,也沒有 map()
方法。
>
args
instanceof
Array
false
>
args
.
map
undefined
其次,DOM 方法 document.querySelectorAll()
會傳回 NodeList
的實例。
> document.querySelectorAll('a[href]') instanceof NodeList
true
> document.querySelectorAll('a[href]').map // no Array methods!
undefined
因此,對於許多複雜的操作,您需要先將類陣列物件轉換成陣列。這可透過 Array.prototype.slice()
來達成。此方法會將接收者的元素複製到一個新的陣列中
> var arr = ['a', 'b'];
> arr.slice()
[ 'a', 'b' ]
> arr.slice() === arr
false
如果您直接呼叫 slice()
,您可以將 NodeList
轉換成陣列
var
domLinks
=
document
.
querySelectorAll
(
'a[href]'
);
var
links
=
Array
.
prototype
.
slice
.
call
(
domLinks
);
links
.
map
(
function
(
link
)
{
return
link
.
href
;
});
您也可以將 arguments
轉換成陣列
function
format
(
pattern
)
{
// params start at arguments[1], skipping `pattern`
var
params
=
Array
.
prototype
.
slice
.
call
(
arguments
,
1
);
return
params
;
}
console
.
log
(
format
(
'a'
,
'b'
,
'c'
));
// ['b', 'c']
一方面,ECMAScript 6 有 Array.from()
,一種將類陣列物件轉換成陣列的更簡單方式
const
domLinks
=
document
.
querySelectorAll
(
'a[href]'
);
const
links
=
Array
.
from
(
domLinks
);
links
.
map
(
link
=>
link
.
href
);
另一方面,您不需要類陣列的 arguments
,因為 ECMAScript 6 有剩餘參數(透過三個點宣告)
function
format
(
pattern
,
...
params
)
{
return
params
;
}
console
.
log
(
format
(
'a'
,
'b'
,
'c'
));
// ['b', 'c']
hasOwnProperty()
obj.hasOwnProperty('prop')
會告訴您 obj
是否有自己的(非繼承的)屬性 prop
。
>
var
obj
=
{
prop
:
123
}
;
>
obj
.
hasOwnProperty
(
'prop'
)
true
>
'toString'
in
obj
//
inherited
true
>
obj
.
hasOwnProperty
(
'toString'
)
//
own
false
然而,如果覆寫了 Object.prototype.hasOwnProperty
,透過調度呼叫 hasOwnProperty
可能會無法正常運作。
>
var
obj1
=
{
hasOwnProperty
:
123
}
;
>
obj1
.
hasOwnProperty
(
'toString'
)
TypeError
:
Property
'hasOwnProperty'
is
not
a
function
如果 Object.prototype
不在物件的原型鏈中,hasOwnProperty
也可能無法透過調度使用。
> var obj2 = Object.create(null);
> obj2.hasOwnProperty('toString')
TypeError: Object has no method 'hasOwnProperty'
這兩種情況的解決方案都是直接呼叫 hasOwnProperty
>
var
obj1
=
{
hasOwnProperty
:
123
}
;
>
Object
.
prototype
.
hasOwnProperty
.
call
(
obj1
,
'hasOwnProperty'
)
true
>
var
obj2
=
Object
.
create
(
null
);
>
Object
.
prototype
.
hasOwnProperty
.
call
(
obj2
,
'toString'
)
false
hasOwnProperty()
hasOwnProperty()
主要用於透過物件實作映射。感謝的是,ECMAScript 6 有內建的 Map
資料結構,這表示您較少需要 hasOwnProperty()
。
Object.prototype
和 Array.prototype
的縮寫 您可以透過一個空的物件字面(其原型是 Object.prototype
)存取 Object.prototype
的方法。例如,以下兩個直接方法呼叫是等效的
Object
.
prototype
.
hasOwnProperty
.
call
(
obj
,
'propKey'
)
{}.
hasOwnProperty
.
call
(
obj
,
'propKey'
)
相同的技巧也適用於 Array.prototype
Array
.
prototype
.
slice
.
call
(
arguments
)
[].
slice
.
call
(
arguments
)
此模式已經變得相當普遍。它沒有像較長的版本那樣清楚地反映作者的意圖,但它簡潔多了。就速度而言,這兩個版本之間沒有太大的差異。
name
屬性 函式的 name
屬性包含函式的名稱
> function foo() {}
> foo.name
'foo'
此屬性對於偵錯(它的值會顯示在堆疊追蹤中)和一些元程式設計任務(透過名稱挑選函式等)很有用。
在 ECMAScript 6 之前,此屬性已獲得大多數引擎支援。在 ES6 中,它成為語言標準的一部分,並經常自動填寫。
以下各節說明如何自動為各種程式設計結構設定 name
。
如果函數是透過變數宣告建立,則會選取名稱
let
func1
=
function
()
{};
console
.
log
(
func1
.
name
);
// func1
const
func2
=
function
()
{};
console
.
log
(
func2
.
name
);
// func2
var
func3
=
function
()
{};
console
.
log
(
func3
.
name
);
// func3
但即使使用一般指定,name
仍會正確設定
let
func4
;
func4
=
function
()
{};
console
.
log
(
func4
.
name
);
// func4
var
func5
;
func5
=
function
()
{};
console
.
log
(
func5
.
name
);
// func5
關於名稱,箭頭函數就像匿名函數運算式
const
func
=
()
=>
{};
console
.
log
(
func
.
name
);
// func
從現在開始,每當您看到匿名函數運算式時,您可以假設箭頭函數以相同方式運作。
如果函數是預設值,則會從其變數或參數取得名稱
let
[
func1
=
function
()
{}]
=
[];
console
.
log
(
func1
.
name
);
// func1
let
{
f2
:
func2
=
function
()
{}
}
=
{};
console
.
log
(
func2
.
name
);
// func2
function
g
(
func3
=
function
()
{})
{
return
func3
.
name
;
}
console
.
log
(
g
());
// func3
函數宣告和函數運算式是函數定義。此情境已獲得支援很長一段時間:具有名稱的函數定義會將其傳遞給 name
屬性。
例如,函數宣告
function
foo
()
{}
console
.
log
(
foo
.
name
);
// foo
命名函數運算式的名稱也會設定 name
屬性。
const
bar
=
function
baz
()
{};
console
.
log
(
bar
.
name
);
// baz
由於函數運算式的名稱 baz
出現在前面,因此它優先於其他名稱(例如透過變數宣告提供的名稱 bar
)
然而,如同在 ES5 中,函數運算式的名稱僅為函數運算式內部的變數
const
bar
=
function
baz
()
{
console
.
log
(
baz
.
name
);
// baz
};
bar
();
console
.
log
(
baz
);
// ReferenceError
如果函式是屬性的值,則其名稱會從該屬性取得。不論是透過方法定義 (A 行)、傳統屬性定義 (B 行)、具有計算屬性金鑰的屬性定義 (C 行) 或屬性值簡寫 (D 行) 進行,都沒有關係。
function
func
()
{}
let
obj
=
{
m1
()
{},
// (A)
m2
:
function
()
{},
// (B)
[
'm'
+
'3'
]
:
function
()
{},
// (C)
func
,
// (D)
};
console
.
log
(
obj
.
m1
.
name
);
// m1
console
.
log
(
obj
.
m2
.
name
);
// m2
console
.
log
(
obj
.
m3
.
name
);
// m3
console
.
log
(
obj
.
func
.
name
);
// func
取得器的名稱會加上前置詞 'get'
,設定器的名稱會加上前置詞 'set'
let
obj
=
{
get
foo
()
{},
set
bar
(
value
)
{},
};
let
getter
=
Object
.
getOwnPropertyDescriptor
(
obj
,
'foo'
).
get
;
console
.
log
(
getter
.
name
);
// 'get foo'
let
setter
=
Object
.
getOwnPropertyDescriptor
(
obj
,
'bar'
).
set
;
console
.
log
(
setter
.
name
);
// 'set bar'
類別定義中方法的命名方式類似於物件文字
class
C
{
m1
()
{}
[
'm'
+
'2'
]()
{}
// computed property key
static
classMethod
()
{}
}
console
.
log
(
C
.
prototype
.
m1
.
name
);
// m1
console
.
log
(
new
C
().
m1
.
name
);
// m1
console
.
log
(
C
.
prototype
.
m2
.
name
);
// m2
console
.
log
(
C
.
classMethod
.
name
);
// classMethod
取得器和設定器再次具有名稱前置詞 'get'
和 'set'
class
C
{
get
foo
()
{}
set
bar
(
value
)
{}
}
let
getter
=
Object
.
getOwnPropertyDescriptor
(
C
.
prototype
,
'foo'
).
get
;
console
.
log
(
getter
.
name
);
// 'get foo'
let
setter
=
Object
.
getOwnPropertyDescriptor
(
C
.
prototype
,
'bar'
).
set
;
console
.
log
(
setter
.
name
);
// 'set bar'
在 ES6 中,方法的金鑰可以是符號。此類方法的 name
屬性仍然是字串
''
)。
const
key1
=
Symbol
(
'description'
);
const
key2
=
Symbol
();
let
obj
=
{
[
key1
]()
{},
[
key2
]()
{},
};
console
.
log
(
obj
[
key1
].
name
);
// '[description]'
console
.
log
(
obj
[
key2
].
name
);
// ''
請記住,類別定義會建立函式。這些函式也具有正確設定的屬性 name
class
Foo
{}
console
.
log
(
Foo
.
name
);
// Foo
const
Bar
=
class
{};
console
.
log
(
Bar
.
name
);
// Bar
下列所有陳述式都會將 name
設定為 'default'
export
default
function
()
{}
export
default
(
function
()
{});
export
default
class
{}
export
default
(
class
{});
export
default
()
=>
{};
new Function()
會產生名稱為 'anonymous'
的函式。WebKit 錯誤 說明了為何在網路上需要這樣做。func.bind()
會產生名稱為 'bound '+func.name
的函式
function
foo
(
x
)
{
return
x
}
const
bound
=
foo
.
bind
(
undefined
,
123
);
console
.
log
(
bound
.
name
);
// 'bound foo'
函數名稱在建立時會永遠指派,之後不會再變更。也就是說,JavaScript 引擎會偵測先前提到的模式,並建立函數,其名稱會在建立時就正確指派。以下程式碼示範由 functionFactory()
建立的函數名稱會在 A 行指派,不會因為 B 行的宣告而變更。
function
functionFactory
()
{
return
function
()
{};
// (A)
}
const
foo
=
functionFactory
();
// (B)
console
.
log
(
foo
.
name
.
length
);
// 0 (anonymous)
理論上,可以檢查每個指派,看看右手邊是否評估為函數,以及該函數是否尚未有名稱。但是這樣會大幅降低效能。
函數名稱會受到壓縮,這表示壓縮後的程式碼中的名稱通常會變更。視您要執行的動作而定,您可能必須透過字串 (不會壓縮) 來管理函數名稱,或者您可能必須告知壓縮器不要壓縮哪些名稱。
這些是屬性 name
的屬性
> let func = function () {}
> Object.getOwnPropertyDescriptor(func, 'name')
{ value: 'func',
writable: false,
enumerable: false,
configurable: true }
屬性不可寫入,表示您無法透過指派變更其值
> func.name = 'foo';
> func.name
'func'
不過,屬性是可以設定的,表示您可以透過重新定義來變更它
>
Object
.
defineProperty
(
func
,
'name'
,
{
value
:
'foo'
,
configurable
:
true
}
);
>
func
.
name
'foo'
如果屬性 name
已存在,則您可以省略描述符屬性 configurable
,因為遺失的描述符屬性表示對應的屬性不會變更。
如果屬性 name
尚未存在,則描述符屬性 configurable
會確保 name
保持可設定 (預設屬性值都是 false
或 undefined
)。
name
SetFunctionName()
會設定屬性 name
。在規格中搜尋其名稱,找出發生此情況的地方。
'get'
和 'set'
)Function.prototype.bind()
(前綴 'bound'
)name
屬性的匿名函式表達式,可透過檢視 其執行時期語意 來查看
SetFunctionName()
設定的。此操作不會呼叫匿名函式表達式。SetFunctionName()
沒有呼叫)。new
呼叫? ES6 有新的子類別化協定,在 類別章節 中有說明。該協定的一部分是 元屬性 new.target
,它指的是建構式呼叫鏈中的第一個元素(類似於超方法呼叫鏈中的 this
)。如果沒有建構式呼叫,它會是 undefined
。我們可以使用它來強制執行函式必須透過 new
呼叫,或不得透過它呼叫。以下是後者的範例
function
realFunction
()
{
if
(
new
.
target
!==
undefined
)
{
throw
new
Error
(
'Can’t be invoked via `new`'
);
}
···
}
在 ES5 中,通常會這樣檢查
function
realFunction
()
{
"use strict"
;
if
(
this
!==
undefined
)
{
throw
new
Error
(
'Can’t be invoked via `new`'
);
}
···
}