ES6函數的擴展
1.函數默認值
定義:ES6允許為函數設定默認值,即直接寫在參數定義的后面
示例
function print(x,y='world'){
console.log(x,y);
}
print('hello'); //hello world
在ES5中則需要判斷傳參是否存在,再進行默認值賦值
參數變量是默認聲明的,使用let和const再次進行聲明會報錯,var聲明則不會,因為let定義不允許在相同作用域內重復聲明一個變量。
2.函數的length屬性
定義:函數的length屬性將返回沒有指定默認值的參數個數
示例
//在ES5中獲取函數的參數個數需要函數return
function count(x,y){
return arguments.length;
}
//ES6中新增函數length屬性
(function (x,y){}).length; //2
function count(x,y = "world"){
}
console.log(count.length); //1,指定默認值不算進去
3.函數的name屬性
定義:獲取函數的名稱
var hello = function(){};
hello.name; //hello
//Function構造函數返回的示例name為anonymous
(new Function()).name; //anonymous
4.rest參數
定義:rest參數的寫法為(...變量名),用于獲取函數多余的變量,可替代arguments,rest參數中的變量是一個數組,arguments是類數組對象需要使用Array.from()方法轉換成真正數組。
示例
function add(...numbers){
let count = 0;
for (let i of numbers){
count += i;
}
return count;
}
//或者使用numbers.forEach(x => count += x);
rest參數變量是一個數組,數組特有的方法都可以使用。
rest參數后不能再有其他的參數,rest參數必須為最后一個參數,使用函數屬性length獲取參數個數,rest參數不計入。
示例
function add(a,...numbers,c){
} //error
(function(a,b,...numbers){}).length; //2,rest參數不算
5.擴展運算符...
定義: 擴展運算符的寫法為(...),將一個數組轉為用逗號分隔的參數序列
示例
console.log(1,...[2,3,4,5],6); //1,2,3,4,5,6
使用擴展運算符替代apply()方法
//ES5
Math.max.apply(null,[4,6,0]); //Math.max不能接受數組為參數
Math.max(4,6,0);
//ES6
Math.max(...[4,6,0]);
使用擴展運算符合并數組
//ES5
var arr1 = [1,2,3];
var arr2 = [4,5,6,];
var arr = arr1.concat(arr2);
//ES6
var array1 = Array.of(1,2,3);
var array2 = Array.of(4,5,6);
var array = [...array1,...array2];
使用擴展運算符可將具有Iterator接口的數據結構轉換成真正的數組(ES6數組擴展篇章也有提及)
[...'hello']; //將字符串轉化成數組
var NodeList = [...document.querySelectorAll('p')];//類似數組對象
var someSet = new Set([4,5,6]);
var array = [...someSet.keys()]; //Set數據結構
var someMap = new Map([['name','lijaha'],['age',20]]);
var array = [...someMap.keys()]; //Map數據結構
6.箭頭函數
定義: ES6允許使用箭頭( => )定義函數
示例
var hello = x => x;
//如果箭頭函數不需要參數或者需要多個參數就用()圓括號代表參數部分
var hello = () => 1; //return 1
var hello = (x,y) => x+y;
//如果箭頭函數的代碼塊語句多于一句,需要使用大括號{}括起來
var hello = (x,y) => {return x+y};
//如果箭頭函數返回的是對象,則需要在大括號外加上小括號({})
var hello = id => ({id: id,name: 'world'});
箭頭函數體內的this對象是定義時所在的對象,而不是使用時所在的對象,稱為箭頭函數的this指向固定化。因為箭頭函數內部是沒有自己的this(不能作為構造函數,也不能使用apply、call、bind等方法改變this指向),所以它的this是外層代碼塊的this。
箭頭函數內部沒有arguments對象,需使用rest參數代替
var hello = (...numbers) => {console.log(...numbers)};
尾調用優化
定義:指某個函數在最后一步調用另一個函數
function hello(){
return hi();
} //尾調用的結尾一定是return xx();
函數調用會在內存中形成一個“調用幀”,保存調用位置和內部變量等信息。如果函數A中調用用了函數B,則會在函數A調用幀的上方形成一個B的調用幀,函數B執行完畢后,函數A中的調用幀B才會結束。如果函數B內調用了函數C,就會形成一個C地調用幀,所有的調用幀就會形成一個‘調用棧’。
//尾調用是函數的最后一步操作,所以不需要保留外層函數的調用幀,外層函數的調用位置和內部變量都不會用到。
function hello(){
let m = 1;
let n = 2;
return g(m+n);
}
//等同于
function hello(){
return g(3);
}
//等同于
g(3);
//上面代碼中,如果函數g不是尾調用則函數hello需要保留內部變量m、n以及調用位置等信息,由于函數hello調用函數g后,hello函數就結束了,則可以刪除hello()的調用幀,只保留g()的調用幀。
這就叫做‘尾調用優化’,即只保留內層函數的調用幀,如果所有的函數都是尾調用,則可以做到每次執行時調用幀只有一項。
//只有不再用到外層函數的內部變量,內層函數的調用幀才能取代外層函數的調用幀,實現尾調用優化。
function hello(a){
let b = 1;
function hi(a){
return a + b; //使用了hello函數的內部變量b
}
return hi(a);
}
尾遞歸
函數自己調用自己則叫遞歸,函數在尾部調用自己則稱為尾遞歸
//使用遞歸實現階乘,遞歸過程會比較耗費內存,因為保存大量的調用幀,容易發生棧溢出。
function factorial(n){
if(n == 1) return 1;
return nfactorial(n-1);
}
//使用尾遞歸
function factorial(n,total=1){ //使用默認值
if(n == 1) return total;
return factorial(n-1,ntotal);
} //每次只保存一個調用記錄
一般的遞歸的復雜度為O(n),使用尾遞歸的復雜度為O(1)
需要開啟嚴格模式,尾調用優化才會生效,一旦使用尾調用優化,函數的內部對象arguments和caller都會失效,因為整個外層函數調用幀會被刪除掉,這兩個對象也就不存在了。嚴格模式下,這兩個對象也是不可用的。
function hello(){
"use strict";
hello.arguments; //error
hello.caller; //error
}
本文章參考阮一峰《ES6 標準入門》