函數(shù)
函數(shù)形參的默認(rèn)值
在ES5中模擬默認(rèn)參數(shù)
- 第一種方式:
- 缺陷: 如果給num傳入值為0房资, 那么因?yàn)楸灰暈閒alse队塘,所以num在函數(shù)內(nèi)為100喳资。
function aa(num, callback) {
num = num || 100;
callback = callback = function() {};
}
- 第二種方式:
- 常見(jiàn)于流行的JS庫(kù)中
function aa(num, callback) {
num = (typeof num !== "undefined") ? num : 100;
callback = (typeof callback !== "undefined") ? callback : function() {};
}
ES6中的默認(rèn)參數(shù)
- 提供一個(gè)初始值
- 例如:
function aa(num = 100, callback = function() {}) {
// ...
}
- 聲明函數(shù)時(shí)觉吭,可以為任意參數(shù)設(shè)置默認(rèn)值
- 在已指定默認(rèn)值的參數(shù)后可以繼續(xù)聲明無(wú)默認(rèn)值的參數(shù)
- 如:
function aa(num = 100, callback) {}
- 此時(shí),只有當(dāng) 不為第二個(gè)參數(shù)傳值 或 主動(dòng)為第二個(gè)參數(shù)傳入“undefined”時(shí)仆邓,第二個(gè)參數(shù)才會(huì)使用默認(rèn)值
- 傳入null鲜滩,不會(huì)使用第二個(gè)參數(shù)的默認(rèn)值,其值最終為null
- 如:
默認(rèn)參數(shù)對(duì)arguments對(duì)象的影響
- ES5中 非嚴(yán)格模式下节值, 命名參數(shù)的變化會(huì)被同步更新到arguments對(duì)象中
- ES5的嚴(yán)格模式下徙硅,無(wú)論參數(shù)在函數(shù)體內(nèi)如何變化,都不會(huì)影響到arguments對(duì)象
- ES6中察署,如果一個(gè)函數(shù)使用了默認(rèn)參數(shù)闷游,那么如論何種模式下,都與嚴(yán)格模式保持一致
- 默認(rèn)參數(shù)的存在使得arguments對(duì)象與命名參數(shù)保持分離
function aa(x, y ="y") {
console.log(arguments.length);
console.log(x === arguments[0]);
console.log(y === arguments[1]);
x = "x";
y = "000"
console.log(x === arguments[0]);
console.log(y === arguments[1]);
}
+ 輸出結(jié)果為:```1, true, false, false, false```
+ 這種特性可以讓我們通過(guò)arguments對(duì)象將參數(shù)回復(fù)為初始值
默認(rèn)參數(shù)表達(dá)式
- 非原始值傳參
- 只有調(diào)用add()函數(shù)且不傳入第二個(gè)參數(shù)時(shí)才會(huì)調(diào)用getValue()
function getValue() {
return 5
}
function add(x, y = getValue()) {
return x + y;
}
console.log(add(1, 1));
// 2
console.log(add(1));
// 6
+ 當(dāng)使用函數(shù)調(diào)用結(jié)果作為默認(rèn)參數(shù)值時(shí)贴汪,如果忘記寫小括號(hào)脐往,最終傳入的是函數(shù)的引用,而不是函數(shù)調(diào)用的結(jié)果
- 可以使用先定義的參數(shù)作為后定義參數(shù)的默認(rèn)值扳埂,但不能用后定義的參數(shù)作為先定義參數(shù)的默認(rèn)值
- 臨時(shí)死區(qū)(TDZ)
function add(x = y, y) {
return x + y;
}
console.log(add(1, 1));
// 2
console.log(add(1));
// 拋出錯(cuò)誤
// 表示調(diào)用add(undefined, 1), 即:
// let x = y;
// let y = 1;
// 此時(shí)會(huì)報(bào)錯(cuò)
默認(rèn)參數(shù)的臨時(shí)死區(qū)
- 與let聲明類似
- 定義參數(shù)時(shí)會(huì)為每個(gè)參數(shù)創(chuàng)建一個(gè)新的標(biāo)識(shí)符綁定
- 該綁定在初始化之前不可被引用
- 如果試圖訪問(wèn)业簿,會(huì)導(dǎo)致程序拋出錯(cuò)誤
處理無(wú)命名參數(shù)
- 無(wú)論函數(shù)已定義的命名參數(shù)有多少,調(diào)用時(shí)都可以傳入任意數(shù)量的參數(shù)
- 當(dāng)傳入更少的參數(shù)時(shí)阳懂,默認(rèn)參數(shù)值的特性可以有效簡(jiǎn)化函數(shù)聲明的代碼
- 當(dāng)傳入更多數(shù)量的參數(shù)時(shí)梅尤,需要用到以下ES6的新特性
ES5中的無(wú)命名參數(shù)
- 實(shí)例:返回一個(gè)給定對(duì)象的副本柜思,包含院士對(duì)象屬性的特定子集
- 模仿了Underscore.js中的pick()方法
function pick(obj) {
let result = Object.create(null);
// 從第二個(gè)參數(shù)開始
for (let i = 1, len = arguments.length; i < len; i++) {
result[arguments[i]] = obj[arguments[i]];
}
return result;
}
let book = {
author: "shaun",
age: 20
};
let data = pick(book, "author", "age");
// shaun
console.log(data.author);
// 20
console.log(data.age);
- 不足:
- 不容易發(fā)現(xiàn)這個(gè)函數(shù)可以接受任意數(shù)量的參數(shù)
- 因?yàn)榈谝粋€(gè)參數(shù)為命名參數(shù)且已經(jīng)被占用,當(dāng)需要查找需要拷貝的屬性名稱時(shí)巷燥,需要從索引1開始遍歷arguments對(duì)象
- 可以用ES6的不定參數(shù)特性解決
不定參數(shù)
- 在函數(shù)的命名參數(shù)前加三個(gè)點(diǎn)(...)就表明這是一個(gè)不定參數(shù)
- 該參數(shù)為一個(gè)數(shù)組赡盘,包含"自它之后"傳入的所有參數(shù)
- 這個(gè)特性使得可以放心遍歷keys對(duì)象了,沒(méi)有索引的特殊性
- 另一個(gè)好處是只需看一眼缰揪,就能知道函數(shù)可以處理的參數(shù)數(shù)量
- 通過(guò)這個(gè)數(shù)組名陨享,即可訪問(wèn)到里面的參數(shù)
- 重寫pick函數(shù)
function pick(obj, ...keys) {
let result = Object.create(null);
for (let i = 1, len = arguments.length; i < len; i++) {
result[keys[i]] = obj[keys[i]];
}
return result;
}
- 不定參數(shù)的使用限制
- 每個(gè)函數(shù)只能聲明一個(gè)不定參數(shù),而且只能放在所有參數(shù)的末尾
- 不定參數(shù)不能用于對(duì)象字面量stter之中
- 因?yàn)閟etter的參數(shù)有且只能有一個(gè)
let obj = {
// 會(huì)報(bào)錯(cuò)钝腺,不可以在setter中使用不定參數(shù)
set name(...value) {
// to do...
}
}
- 不定參數(shù)對(duì)arguments的影響
- 無(wú)論是否使用不定參數(shù)抛姑,當(dāng)函數(shù)被調(diào)用時(shí),arguments對(duì)象依然包含了所有傳入的參數(shù)
function check(...args) {
console.log(args.length);
console.log(arguments.length);
console.log(args.[0], arguments[0]);
console.log(args.[1], arguments[1]);
}
check("a", "b");
// 輸出:
// 2
// 2
// a a
// b b
增強(qiáng)的Function構(gòu)造函數(shù)
- Function構(gòu)造函數(shù)是來(lái)動(dòng)態(tài)創(chuàng)建新函數(shù)的方法
- 接受字符串形式的參數(shù)作為函數(shù)的參數(shù)和函數(shù)體(最后一項(xiàng)默認(rèn)為函數(shù)體)
var add = new Function("x", "y", "return x + y");
// 2
console.log(add(1, 1));
- ES6中支持定義默認(rèn)參數(shù)和不定參數(shù)
- 默認(rèn)參數(shù)
var add = new Function("x", "y = 2", "return x + y");
// 2
console.log(add(1, 1));
// 3
console.log(add(1));
+ 不定參數(shù)(只能在最后一個(gè)參數(shù)前加...)
var pick = new Function("...args", "return args[0]");
// 1
console.log(pick(1, 2));
展開運(yùn)算符
- 與不定參數(shù)的區(qū)別
- 不定參數(shù)可以讓你指定多個(gè)各自獨(dú)立的參數(shù)艳狐,并通過(guò)數(shù)組來(lái)訪問(wèn)
- 展開運(yùn)算符可以讓你指定一個(gè)數(shù)組定硝,將它們打散后作為各自獨(dú)立的參數(shù)傳入函數(shù)
- ES5 實(shí)例
let value = [25, 50, 75, 100];
// 100
console.log(Math.max.apply(Math, value));
- ES6 實(shí)例
let value = [25, 50, 75, 100];
// 等價(jià)于
// console.log(Math.max(25, 50, 75, 100));
// 100
console.log(Math.max(...value));
- 可以將展開運(yùn)算符與其他正常傳入的參數(shù)混合使用
let value = [25, -50, -75, -100];
let value2 = [-25, -50, -75, -100];
// 25
console.log(Math.max(...value, 0));
// 0
console.log(Math.max(...value2, 0));
- 大多數(shù)使用apply()方法的情況下,展開運(yùn)算符都可能是一個(gè)更適合的方案
name屬性
- 辨別函數(shù)對(duì)調(diào)試和追蹤難以解讀的棧記錄
- ES6 為所有函數(shù)新增了name屬性
如何選擇合適的名稱
- name屬性都有一個(gè)合適的值
function dosth() {
//
}
var doelse function() {
//
};
// dosth
dosth.name;
// doelse
doelse.name;
name屬性的特殊情況
- 特殊情況
var dosth = function doelse() {
//
};
var person = {
get firstName() {
return "shaun";
},
sayName: function() {
console.log(this.name);
}
}
// 由于函數(shù)表達(dá)式的名字比函數(shù)本身被賦值的變量的權(quán)重高
// doelse
dosth.name;
// 取值對(duì)象字面量
// sayName
person.sayName.name;
// getter和setter毫目,都會(huì)有前綴
// get firstName
person.firstName.name;
+ 其他兩種前綴
var dosth = function() {
//
};
// bound dosth
dosth.bind().name;
// anonymous
(new Function()).name;
- 函數(shù)的name屬性的值不一定引用同名變量蔬啡,只是協(xié)助調(diào)試用的額外信息
- 所以不能使用name屬性的值來(lái)獲取對(duì)于函數(shù)的引用
明確函數(shù)的多重用途
- 當(dāng)通過(guò)new關(guān)鍵字調(diào)用函數(shù)時(shí),執(zhí)行的是[[construct]]函數(shù)
- 負(fù)責(zé)創(chuàng)建一個(gè)實(shí)例新對(duì)象
- 然后再執(zhí)行函數(shù)體
- 將this綁定到實(shí)例上
- 不通過(guò)new調(diào)用時(shí)镀虐,執(zhí)行的是[[call]]函數(shù)
- 直接執(zhí)行函數(shù)體
- 具有[[construct]]方法的函數(shù)被統(tǒng)稱為構(gòu)造函數(shù)
- 沒(méi)有[[construct]]方法的函數(shù)不能通過(guò)new來(lái)調(diào)用
在ES5中判斷函數(shù)被調(diào)用的方法
- ES5 確定一個(gè)函數(shù)是否通過(guò)new關(guān)鍵字被調(diào)用星爪,最流行用instanceof
- 缺點(diǎn):不完全可靠,例如call
function person() {
if (this instanceof person) {
this.name = name;
} else {
throw new Error("msg")
}
}
// 成功執(zhí)行
var person = new Preson("shaun");
// 拋錯(cuò)
var notPerson = Preson("shaun");
// 也被成功執(zhí)行
var notPerson2 = Preson.call(person, "shaun");
元屬性(Metaproperty) new.target
- 此特性可以解決判斷是否通過(guò)new調(diào)用的問(wèn)題
- 當(dāng)調(diào)用函數(shù)的[[construct]]方法時(shí)粉私,new.target被賦值為new操作符的目標(biāo)
- 當(dāng)調(diào)用函數(shù)的[[call]]方法時(shí)顽腾,new.target被賦值為undefined
- 在函數(shù)體外使用new.target是一個(gè)語(yǔ)法錯(cuò)誤
塊級(jí)函數(shù)
- ES5 中不能在例如if語(yǔ)句中創(chuàng)建函數(shù)
- ES6 中可以,但是只能在代碼塊中調(diào)用此函數(shù)诺核,代碼塊結(jié)束執(zhí)行后抄肖,此函數(shù)將不再存在
塊級(jí)函數(shù)的使用場(chǎng)景
- 嚴(yán)格模式下
- let 聲明的變量不會(huì)提升到代碼塊頂部
- 聲明的函數(shù)會(huì)提升到代碼塊頂部
- 非嚴(yán)格模式下
- 函數(shù)直接提升到外圍函數(shù)或全局作用域的頂部
箭頭函數(shù)
- 沒(méi)有this, super, arguments, new.target的綁定
- 這些值由外圍最近一層非箭頭函數(shù)決定
- 不能通過(guò)new調(diào)用
- 因?yàn)闆](méi)有[[construct]]方法
- 沒(méi)有原型
- 不可以改變this的綁定
- 在函數(shù)生命周期內(nèi)都不會(huì)變
- 不支持重復(fù)的命名參數(shù)
- 也有一個(gè)name屬性
箭頭函數(shù)語(yǔ)法
- 傳入一個(gè)參數(shù)
let fn = val => val;
- 傳入2個(gè)以上的參數(shù)
let sum = (x, y) => x + y;
- 不傳參數(shù)
let name = () => "shaun";
- 由多個(gè)表達(dá)式組成的函數(shù)體要用{}包裹,并顯式定義一個(gè)返回值
let sum = (x, y) => { return x + y; };
- 除了arguments對(duì)象不可用外窖杀,某種程度上都可以將花括號(hào)內(nèi)的代碼視作傳統(tǒng)的函數(shù)體
- 創(chuàng)建一個(gè)空函數(shù)漓摩,仍需要寫一對(duì)沒(méi)有內(nèi)容的花括號(hào)
- 如果想在箭頭函數(shù)外返回一個(gè)對(duì)象字面量,需要將該對(duì)象字面量包裹在小括號(hào)內(nèi)(為了將其與函數(shù)體區(qū)分開)
let getId = id => ({ id: id, name: "shaun" });
// 相當(dāng)于
let getId = function(id) {
return {
id: id,
name: "shaun"
};
};
創(chuàng)建立即執(zhí)行函數(shù)表達(dá)式
- 函數(shù)的一個(gè)流行使用方式是創(chuàng)建IIFE
- 定義一個(gè)匿名函數(shù)并立即調(diào)用
- 自始至終不保存對(duì)該函數(shù)的引用
- 可以用作創(chuàng)建一個(gè)與其他程序隔離的作用域
let person = function(name) {
return {
getName: function() {
return name;
}
};
}("shaun");
// shaun
console.log( person.getName());
- 將箭頭函數(shù)包裹在小括號(hào)內(nèi)入客,但不把("shaun")包裹在內(nèi)
let person = ((name) => {
return {
getName: function() {
return name;
}
};
})("shaun");
// shaun
console.log( person.getName());
箭頭函數(shù)沒(méi)有this綁定
- 箭頭函數(shù)的this值取決于該函數(shù)外部非箭頭函數(shù)的this值
- 箭頭函數(shù)內(nèi)沒(méi)有this綁定管毙,需要通過(guò)查找作用域鏈來(lái)決定其值
- 否則會(huì)被設(shè)置為undefined
箭頭函數(shù)和數(shù)組
- 箭頭函數(shù)適用于數(shù)組處理
- 實(shí)例:排序
var value = [1, 2, 3, 9, 8];
// 老辦法
var result = value.sort(function(a, b) {
return a - b;
})
// 新辦法
var result2 = value.sort((a, b) => a - b);
- 諸如sort(), map(), reduce()這些可以接受回掉函數(shù)的數(shù)組辦法
- 都可以通過(guò)箭頭函數(shù)語(yǔ)法簡(jiǎn)化編碼過(guò)程,減少編碼量
箭頭函數(shù)沒(méi)有argument綁定
- 箭頭函數(shù)沒(méi)有自己的arguments對(duì)象
- 但是無(wú)論在哪個(gè)上下文中執(zhí)行桌硫,箭頭函數(shù)始終可以訪問(wèn)外圍函數(shù)的arguments對(duì)象
箭頭函數(shù)的辨識(shí)方法
- 同樣可以辨識(shí)出來(lái)
var result = (a, b) => a - b;
// function
console.log(typeof result);
// true
console.log(result instanceof Function);
- 仍然可以在箭頭函數(shù)上調(diào)用call(), apply(), bind()方法夭咬,但有區(qū)別:
var result = (a, b) => a + b;
// 3
console.log(result.call(null, 1, 2));
// 3
console.log(result.call(null, 1, 2));
var boundResult = result.bind(null, 1, 2);
// bind后的方法不用傳參
// 3
console.log(boundResult());
尾調(diào)用優(yōu)化
- 尾調(diào)用指的是函數(shù)作為另一個(gè)函數(shù)的最后一條語(yǔ)句被調(diào)用
- 如此會(huì)創(chuàng)建一個(gè)新的棧幀:stack frame
- 在循環(huán)調(diào)用中,每一個(gè)未用完的棧幀都會(huì)被保存在內(nèi)存中
function dosth() {
// 尾調(diào)用
return doelse();
}
ES6中的尾調(diào)用優(yōu)化
- ES6 縮減了嚴(yán)格模式下尾調(diào)用棧的大小
- 非嚴(yán)格模式下不受影響
- 滿足以下條件铆隘,尾調(diào)用不再創(chuàng)建新的棧幀卓舵,而是清除并重用當(dāng)前棧幀
- 尾調(diào)用不訪問(wèn)當(dāng)前棧幀的變量(即函數(shù)不是閉包)
- 在函數(shù)內(nèi)部,尾調(diào)用是最后一條語(yǔ)句
- 偉嗲用的結(jié)果作為函數(shù)值返回
如何利用尾調(diào)用優(yōu)化
- 遞歸函數(shù)是其應(yīng)用最常見(jiàn)的場(chǎng)景膀钠,尾調(diào)用優(yōu)化效果最顯著
function fn(n) {
if (n <= 1) {
return 1;
} else {
return n * fn(n - 1);
}
}
上面的代碼中掏湾,遞歸調(diào)用前執(zhí)行了乘法裹虫,因而當(dāng)前版本的階乘函數(shù)不能被引擎優(yōu)化
當(dāng)n是一個(gè)非常大的數(shù)時(shí),調(diào)用棧的尺寸就會(huì)不斷增長(zhǎng)并存在最終導(dǎo)致溢出的風(fēng)險(xiǎn)
-
優(yōu)化方法:
- 首先確保乘法不在函數(shù)調(diào)用后執(zhí)行
- 這里使用默認(rèn)參數(shù)來(lái)將乘法移除return語(yǔ)句
- 首先確保乘法不在函數(shù)調(diào)用后執(zhí)行
function fn(n, p = 1) {
if (n <= 1) {
return 1 * p;
} else {
let result = n * p;
// 優(yōu)化后
return fn(n - 1, result);
}
}