函數(shù)的拓展
函數(shù)默認(rèn)值
到了ES6轻庆,函數(shù)終于有默認(rèn)值了市栗,在ES5中其實(shí)是可以用一些變通的方法來(lái)完成參數(shù)默認(rèn)的鲫骗,但是有一些缺點(diǎn),比如說(shuō)如果參數(shù)賦值了杠人,但是對(duì)應(yīng)的布爾值為false勋乾,則該賦值不起作用。ES6就解決了這個(gè)問(wèn)題嗡善。
// ES5
function log(x, y) {
y = y || 'World';
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World 出現(xiàn)問(wèn)題
// ES6
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello 沒(méi)毛病
參數(shù)變量是默認(rèn)聲明的辑莫,所以不能用let或者const再次聲明
function foo(x = 5) {
let x = 1; // error
const x = 2; // error
}
使用參數(shù)默認(rèn)值時(shí),函數(shù)不能有同名參數(shù)
// 不報(bào)錯(cuò)
function foo(x, x, y) {
// ...
}
// 報(bào)錯(cuò)
function foo(x, x, y = 1) {
// ...
}
// SyntaxError: Duplicate parameter name not allowed in this context
參數(shù)默認(rèn)值不是傳值的罩引,而是每次都重新計(jì)算默認(rèn)值表達(dá)式的值各吨。也就是說(shuō),參數(shù)默認(rèn)值是惰性求值的
let x = 99;
function foo(p = x + 1) {
console.log(p);
}
foo() // 100
x = 100;
foo() // 101
每次調(diào)用都重新計(jì)算
與解構(gòu)賦值默認(rèn)值結(jié)合使用
參數(shù)默認(rèn)值可以和解構(gòu)賦值的默認(rèn)值袁铐,結(jié)合起來(lái)使用揭蜒。
更騷氣的是如果參數(shù)時(shí)一個(gè)對(duì)象,對(duì)象里面做了默認(rèn)值剔桨,比如{x, y=5}屉更,這個(gè)時(shí)候如果啥也不傳就會(huì)報(bào)錯(cuò),這個(gè)時(shí)候可以通過(guò)提供函數(shù)參數(shù)的默認(rèn)值洒缀,避免報(bào)錯(cuò)瑰谜。
function foo({x, y = 5} = {}) {
console.log(x, y);
}
foo() // undefined 5
function fetch(url, { body = '', method = 'GET', headers = {} }) {
console.log(method);
}
fetch('http://example.com', {})
// "GET"
fetch('http://example.com')
function fetch(url, { method = 'GET' } = {}) {
console.log(method);
}
fetch('http://example.com')
// "GET"
出現(xiàn)了雙重默認(rèn)值。
參數(shù)默認(rèn)值的位置
通常情況下树绩,定義了默認(rèn)值的參數(shù)萨脑,應(yīng)該是函數(shù)的尾參數(shù)。因?yàn)檫@樣比較容易看出來(lái)饺饭,到底省略了哪些參數(shù)渤早。如果非尾部的參數(shù)設(shè)置默認(rèn)值,實(shí)際上這個(gè)參數(shù)是沒(méi)法省略的瘫俊。
// 例一
function f(x = 1, y) {
return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined])
f(, 1) // 報(bào)錯(cuò)
f(undefined, 1) // [1, 1]
// 例二
function f(x, y = 5, z) {
return [x, y, z];
}
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 報(bào)錯(cuò)
f(1, undefined, 2) // [1, 5, 2]
上述情況都不能省略參數(shù)鹊杖,只能設(shè)置undefined來(lái)觸發(fā)等于默認(rèn)值提鸟。但是null沒(méi)有這個(gè)效果。
函數(shù)的長(zhǎng)度(length)
函數(shù)的屬性length返回函數(shù)沒(méi)有指定默認(rèn)值的參數(shù)的個(gè)數(shù)仅淑,如果指定了默認(rèn)參數(shù),那么將不計(jì)入length胸哥,length永遠(yuǎn)返回沒(méi)有默認(rèn)參數(shù)的傳入?yún)?shù)個(gè)數(shù)涯竟。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
有趣的是這個(gè)length的計(jì)數(shù)是根據(jù)默認(rèn)參數(shù)的位置來(lái)計(jì)算的,果設(shè)置了默認(rèn)值的參數(shù)不是尾參數(shù)空厌,那么length屬性也不再計(jì)入后面的參數(shù)了庐船。
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
作用域
一旦設(shè)置了參數(shù)的默認(rèn)值,函數(shù)進(jìn)行聲明初始化時(shí)嘲更,參數(shù)會(huì)形成一個(gè)單獨(dú)的作用域筐钟。等到初始化結(jié)束,這個(gè)作用域也就消失了赋朦。
var x = 1;
function f(x, y = x) {
console.log(y);
}
f(2) // 2
let x = 1;
function f(y = x) {
let x = 2;
console.log(y);
}
f() // 1
上面兩種情況篓冲,第一種就是指向第一個(gè)x,外面全局的x不會(huì)影響y的賦值宠哄。但是第二種情況因?yàn)閤本身沒(méi)有定義壹将,所以會(huì)指向外部全局的x,但是在內(nèi)部x的操作毛嫉,并不會(huì)影響到y(tǒng)的值诽俯。
應(yīng)用
利用參數(shù)默認(rèn)值,可以指定某一個(gè)參數(shù)不得省略承粤,如果省略就拋出錯(cuò)誤暴区。
function throwIfMissing() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
}
foo()
// Error: Missing parameter
rest參數(shù)
ES5中函數(shù)傳參可以用arguments,在ES6加入了rest參數(shù)來(lái)使得傳參更加地優(yōu)雅辛臊,用法為...變量名仙粱。
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
注意,rest參數(shù)必須是最后一個(gè)參數(shù)浪讳,否則會(huì)報(bào)錯(cuò)
嚴(yán)格模式
ES5開(kāi)始函數(shù)內(nèi)部可以設(shè)定為嚴(yán)格模式缰盏,但是ES6引入默認(rèn)值、解構(gòu)賦值淹遵、擴(kuò)展運(yùn)算符之后口猜,函數(shù)內(nèi)部就不可以使用嚴(yán)格模式了,否則就會(huì)報(bào)錯(cuò)透揣。
因?yàn)楹瘮?shù)內(nèi)部的嚴(yán)格模式济炎,同樣適用于函數(shù)體和函數(shù)參數(shù),但是函數(shù)執(zhí)行的時(shí)候辐真,先執(zhí)行函數(shù)參數(shù)须尚,然后執(zhí)行函數(shù)體崖堤,這就不合理了,只有從函數(shù)體之中耐床,才能知道參數(shù)是否應(yīng)該以嚴(yán)格模式執(zhí)行密幔,但是參數(shù)應(yīng)該先初始化。
當(dāng)然了撩轰,你可以吧“use strict”放在外面或者用一個(gè)立即執(zhí)行的函數(shù)包住函數(shù)胯甩。
'use strict';
function doSomething(a, b = a) {
// code
}
const doSomething = (function () {
'use strict';
return function(value = 42) {
return value;
};
}());
name屬性
函數(shù)的name屬性,直接返回函數(shù)的名字堪嫂,有很多種情況偎箫,比如匿名函數(shù)還有bind的函數(shù)等,詳見(jiàn)代碼皆串。
var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"
const bar = function baz() {};
// ES5
bar.name // "baz"
// ES6
bar.name // "baz"
(new Function).name // "anonymous"
function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "
箭頭函數(shù)
ES6的箭頭表達(dá)式是非常有用的一個(gè)東西淹办。哇,真的方便恶复。
如果箭頭函數(shù)不需要參數(shù)或需要多個(gè)參數(shù)怜森,就使用一個(gè)圓括號(hào)代表參數(shù)部分。
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
如果箭頭函數(shù)的代碼塊部分多于一條語(yǔ)句寂玲,就要使用大括號(hào)將它們括起來(lái)塔插,并且使用return語(yǔ)句返回。
var sum = (num1, num2) => { return num1 + num2; }
由于大括號(hào)被解釋為代碼塊拓哟,所以如果箭頭函數(shù)直接返回一個(gè)對(duì)象想许,必須在對(duì)象外面加上括號(hào),否則會(huì)報(bào)錯(cuò)断序。
// 報(bào)錯(cuò)
let getTempItem = id => { id: id, name: "Temp" };
// 不報(bào)錯(cuò)
let getTempItem = id => ({ id: id, name: "Temp" });
如果箭頭函數(shù)只有一行語(yǔ)句流纹,且不需要返回值,可以采用下面的寫法违诗,就不用寫大括號(hào)了漱凝。
let fn = () => void doesNotReturn();
箭頭函數(shù)可以與變量解構(gòu)結(jié)合使用。
const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {
return person.first + ' ' + person.last;
}
箭頭函數(shù)使得表達(dá)更加簡(jiǎn)潔诸迟。
箭頭函數(shù)的一個(gè)用處是簡(jiǎn)化回調(diào)函數(shù)茸炒。
// 正常函數(shù)寫法
[1,2,3].map(function (x) {
return x * x;
});
// 箭頭函數(shù)寫法
[1,2,3].map(x => x * x);
另一個(gè)例子是
// 正常函數(shù)寫法
var result = values.sort(function (a, b) {
return a - b;
});
// 箭頭函數(shù)寫法
var result = values.sort((a, b) => a - b);
下面是 rest 參數(shù)與箭頭函數(shù)結(jié)合的例子。
const numbers = (...nums) => nums;
numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]
const headAndTail = (head, ...tail) => [head, tail];
headAndTail(1, 2, 3, 4, 5)
// [1,[2,3,4,5]]
注意點(diǎn)
- 函數(shù)體內(nèi)的this對(duì)象阵苇,就是定義時(shí)所在的對(duì)象壁公,而不是使用時(shí)所在的對(duì)象
- 不可以當(dāng)作構(gòu)造函數(shù),也就是說(shuō)绅项,不可以使用new命令紊册,否則會(huì)拋出一個(gè)錯(cuò)誤
- 不可以使用arguments對(duì)象,該對(duì)象在函數(shù)體內(nèi)不存在快耿。如果要用囊陡,可以用 rest 參數(shù)代替
- 不可以使用yield命令芳绩,因此箭頭函數(shù)不能用作 Generator 函數(shù)
第一點(diǎn)尤其值得注意。this對(duì)象的指向是可變的撞反,但是在箭頭函數(shù)中妥色,它是固定的
綁定this
箭頭函數(shù)可以綁定this對(duì)象,這就可以減少bind顯式的調(diào)用遏片。
函數(shù)綁定運(yùn)算符是并排的兩個(gè)冒號(hào)(::)垛膝,雙冒號(hào)左邊是一個(gè)對(duì)象,右邊是一個(gè)函數(shù)丁稀。該運(yùn)算符會(huì)自動(dòng)將左邊的對(duì)象,作為上下文環(huán)境(即this對(duì)象)倚聚,綁定到右邊的函數(shù)上面线衫。
如果雙冒號(hào)左邊為空,右邊是一個(gè)對(duì)象的方法惑折,則等于將該方法綁定在該對(duì)象上面授账。
由于雙冒號(hào)運(yùn)算符返回的還是原對(duì)象,因此可以采用鏈?zhǔn)綄懛ā?/p>
foo::bar;
// 等同于
bar.bind(foo);
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
return obj::hasOwnProperty(key);
}
var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;
let log = ::console.log;
// 等同于
var log = console.log.bind(console);
// 例一
import { map, takeWhile, forEach } from "iterlib";
getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x));
// 例二
let { find, html } = jake;
document.querySelectorAll("div.myClass")
::find("p")
::html("hahaha");
尾逗號(hào)
ES6支持參數(shù)的最后一個(gè)后面可以接一個(gè)逗號(hào)
function clownsEverywhere(
param1,
param2,
) { /* ... */ }
clownsEverywhere(
'foo',
'bar