1. 函式引數的預設值
1.1 用法
在ES6之前是不能為函式的引數指定預設值的,要想實現預設值只能通過判斷賦值的方式來實現,在ES6中允許函式為引數設定預設值,主要是為了提高程式碼的可閱讀性,有利於程式碼的優化。另外注意的是在引數賦值的時候,該引數不能重複使用,不能使用let const 進行定義。
// ES6 之前實現
function log(x, y) {
y = y || 'World';
if (typeof y === 'undefined') {
y = 'World';
}
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World
// ES6 中實現
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
const p = new Point();
p // { x: 0, y: 0 }
function foo(x = 5,x) {
let x = 1; // 報錯,不能同名引數,不能對引數進行let const 定義
const x = 2;
}
複製程式碼
1.2 與解構賦值一起使用
如果函式在呼叫的時候沒有提供引數,內部變數就不會產生,就會產生錯誤,通過提供函式的預設值可以解決這種問題,如下:
function foo({x, y = 5}) {
console.log(x, y);
}
foo() // 報錯
foo({x:1}) // 1 5
foo({x:2,y:3) // 2 3
foo({}) // undefined 5
function foo ({x,y = 5} = {}){
console.log(x,y)
}
foo() // undefined 5
這樣就是如果沒有在呼叫的時候傳值 就預設賦空物件。
複製程式碼
如下例子:
function post(url, {b = '',type='get',h={}}){
console.log(type)
}
post('w.b.c',{}) // get
post('w.b.c') // 報錯
// 改成這樣就可以了
function post(url, {b = '',type='get',h={}} = {}){
console.log(type)
}
post('w.b.c',{}) // get
post('w.b.c') // get
複製程式碼
下面例子的區別
// 寫法一
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
// 寫法二
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
兩個都是有預設值在呼叫的時候都傳值或者都不傳值的時候情況是一樣的。
但是如果傳空值,或者不傳值的情況會有差異如下:
m1({}) // 因為本身有預設值 所以為 [0,0]
m2({}) // 預設值為空 解構賦值沒有傳值 所以 [undefined,undefined]
// 其他情況同上
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]
m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]
複製程式碼
1.3 引數預設值的位置
如果定義了預設值的引數,應該是函式的尾引數。而且這個引數是無法省略的,除非輸入undefined
1.4 函式的 length 屬性
函式引數指定了預設值之後,函式的length屬性將會減去指定了預設值的引數個數。因為該屬性認為,指定了預設值的引數將不包含在預期引數個數中。如下:
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
複製程式碼
1.5 作用域
如果函式中的引數設定了預設值,那麼函式在宣告初始化的時候,引數會形成一個單獨的作用域,初始化完成後這個作用域就會消失,這種情況只在引數設定了預設值的情況下。如下:
var x = 1;
function f(x, y = x) {
console.log(y);
}
f(2) // 2
// 因為 設定了預設值 所以在呼叫 f 的時候就形成了作用域,這時候因為將x賦值給y
傳入的x 為 2 所以y是2,如果這時候 呼叫的時候不傳值,
那麼x將指向全域性,所以y = 1
複製程式碼
1.6 應用
利用引數預設值,可以指定某一個引數不得省略,如果省略就報錯,如下
function throwIfMissing() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
}
foo()
// Error: Missing parameter
foo(2) // 2
複製程式碼
2. rest 引數
ES6 中 增加了 rest 引數(...變數名),用於獲取函式多餘的引數,rest引數搭配的變數是一個陣列,該變數將多餘的引數放入陣列中。
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
// 注意:rest 引數之後不能再有其他引數,另外rest引數也不計算在
函式的length屬性中。
複製程式碼
3. 嚴格模式
ES6 中,如果函式引數使用了預設值,解構賦值,或者擴充套件運算子,那麼函式內部將不能顯式設定為嚴格模式,否則會報錯。因為函式執行的時候 先執行函式引數,在執行函式體,但是因為只有在函式體中才能知道引數是否以嚴格模式執行,但是引數卻應該先於函式執行。有兩種方法可以規避:一、 設定全域性嚴格模式,二、把函式包在一個無引數的立即執行函式裡面。
4. name屬性
返回函式的函式名,如下:
function foo(){}
foo.name // foo
var f = function(){}
// ES5
f.name // ''
// ES6
f.name // f
var f = function c(){}
f.name // c
複製程式碼
5. 箭頭函式
ES6 允許使用 “箭頭” (=>)定義函式
var f = v => v;
// 等同於
var f = function (v) {
return v;
};
var f = () => 5;
// 等同於
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同於
var sum = function(num1, num2) {
return num1 + num2;
};
// 如果箭頭函式後面的語句較多就要用大括號包裹起來 並return返回
var sum = (num1, num2) => { return num1 + num2;
//rest 引數與箭頭函式結合的例子。
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]]
複製程式碼
注意點
1. 函式體內的this物件,就是在定義時所在的物件,而不是使用時所在的物件。
2. 不可以當作建構函式,也就是說,不可以使用new命令,否則會丟擲一個錯誤。
3. 不可以使用arguments物件,該物件在函式體內不存在。如果要用,可以用 rest 引數代替。
4. 不可以使用yield命令,因此箭頭函式不能用作 Generator 函式。
5. 由於箭頭函式沒有自己的this,所以當然也就不能用call()、apply()、bind()這些方法去改變this的指向。
複製程式碼
不適用場景
1. 定義物件的方法,且該方法內部包括this
2. 動態定義this 的場合,如點選事件中this 的指向
複製程式碼
巢狀的箭頭函式
箭頭函式內部可以在巢狀使用箭頭函式。
6. 尾呼叫優化
什麼是尾呼叫
函數語言程式設計的一個重要概念,指某個函式的最後一步是呼叫另一個函式
function f(x){
return g(x);
}
// 一下都不屬於
// 情況一
function f(x){
let y = g(x);
return y;
}
// 情況二
function f(x){
return g(x) + 1;
}
// 情況三
function f(x){
g(x);
}
複製程式碼
尾呼叫優化
只保留內層函式的呼叫幀。如果所有函式都是尾呼叫,那麼完全可以做到每次執行時,呼叫幀只有一項,這將大大節省記憶體。這就是“尾呼叫優化”的意義。
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();
// 等同於
function f() {
return g(3);
}
f();
// 等同於
g(3);
複製程式碼
注意,只有不再用到外層函式的內部變數,內層函式的呼叫幀才會取代外層函式的呼叫幀,否則就無法進行“尾呼叫優化”。
尾遞迴
函式呼叫自身,稱為遞迴。如果尾呼叫自身,就稱為尾遞迴。
ES6 的尾呼叫優化只在嚴格模式下開啟,正常模式是無效的。
尾遞迴優化的實現
在正常模式下,可以使用減少呼叫棧,採用迴圈換掉遞迴的方法
歡迎關注 公眾號【小夭同學】
ES6入門系列
Git教程