在前面的章節中,已陸陸續續介紹了ES6爲改良函數而引入的幾個新特性,本章將會繼續講解ES6對函數的其他改進,包括默認參數、元屬性、塊級函數和箭頭函數等。html
在ES5時代,只能在函數體中定義參數的默認值,而自從ES6引入了默認參數(Default Parameter)後,就能讓參數在聲明時帶上它的默認值,以下代碼所示,func2()函數中的參數默認值在可讀性和簡潔性方面更爲優秀。express
function func1(name) { name = name || "strick"; //ES5的參數默認值 } function func2(name = "strick") { //ES6的參數默認值 }
1)undefineddom
只有當不給參數傳值或傳入undefined時,纔會使用它的默認值。即便傳入和undefined同樣的假值(例如false、null等),也得不到它的默認值,以下所示。函數
function func(name = "strick") { return name; } func(undefined); //"strick" func(false); //false func(null); //null
2)位置this
默認參數既能夠位於普通參數以前,也能夠位於其以後。例以下面的兩個函數,都包含兩個參數,其中一個帶有默認值,依次執行,都能獲得預期的結果。spa
function func1(name = "strick", age) { return name; } function func2(name, age = 28) { return age; } func1(undefined); //"strick" func2("strick"); //28
3)默認值rest
參數的默認值既能夠是簡單的字面量,也能夠是複雜的表達式。在每次調用函數時,不只參數會被從新初始化,默認值若是是表達式的話,還會將其從新計算一次。code
function expression1(name, full = "pw" + name) { return full; } expression1("strick"); //"pwstrick" expression1("freedom"); //"pwfreedom"
在上面的代碼中,調用了兩次expression1()函數,返回的結果互不影響。而且full參數的默認值引用了前面的name參數,這是一種有效的語法,但反之就會報錯,以下所示。htm
function expression2(name = full, full) { return name; } expression2(undefined, "strick"); //拋出未定義的引用錯誤
4)限制對象
第一條限制是在包含默認值的參數序列中,不容許出現同名參數。不管同名的是有默認值,亦或是無默認值,都是不容許的,以下所示。
function restrict1(name = "strick", name) { } function restrict1(name = "strick", age, age) { }
第二條限制是不能在函數體中爲默認參數用let或const從新聲明,以下代碼所示,會拋出重複聲明的語法錯誤。
function restrict2(name = "strick") { let name = "freedom"; }
由於默認參數至關因而用let聲明的變量,因此是不容許重複聲明的。上面代碼中的restrict2()函數,它的name參數的初始化相似於下面這樣。
let name = "strick";
參數序列中只要包含了默認參數,那麼其它普通參數也會用let聲明。知道這一點後,就能很容易的解釋上一節第二個示例,在調用expression2()函數時會拋出未定義的錯誤緣由。函數中的兩個參數的初始化至關於下面這樣。
let name = full,
full;
在第一篇中曾提到用let聲明的變量,在聲明以前都會被放到臨時死區中,而在此時訪問這些變量就會觸發運行時錯誤。
5)三個做用域
根據ES6規範的9.1.2小節可知,當參數序列中包含默認參數時,將會出現三個做用域:參數做用域、函數外層做用域和函數體內做用域。關於這三個做用域須要注意兩點:
(1)函數體內能夠修改參數的值,但不能爲其從新聲明。
(2)參數做用域能夠訪問外層做用域中的變量,但不能訪問函數體內的變量。
第一點很好理解,已在上文中作過解釋。關於第二點,可先查看下面的兩個函數。
let full = "freedom"; function scope1(name = full) { return name; } scope1(); function scope2(name = en) { let en = "justify"; return name; } scope2();
調用scope1()函數獲得的返回值是「freedom」,而調用scope2()函數非但得不到結果,還會拋出en未定義的錯誤。接下來改造scope1()函數,把full變量改爲name變量,以下所示。
let name = "freedom"; function scope1(name = name) { return name; }
此時再次調用scope1()函數,獲得的倒是name未定義的錯誤。雖然在外層做用域中包含名爲name的變量,可是參數擁有本身的做用域,會先從當前做用域中查找變量,此時的name正處在臨時死區中,所以在訪問它時會報錯。
除了以上所列的特性以外,在以前的第三篇的參數解構中,還介紹瞭解構默認值和參數默認值結合使用時的注意點。
1)name
經過函數的name屬性可獲得它聲明時所用的名稱。ES6規定此屬性既不可寫,也不可枚舉,只容許配置。在不一樣場景中,它的返回值會不一樣,具體以下所列,每一條規則後面都給出了相應的示例。
(1)利用Function構造器建立的函數,它的名稱是「anonymous」。
var func = new Function("a", "b", "return a+b;"); func.name; //"anonymous"
(2)若是是用匿名函數表達式建立的函數,那麼它的名稱就是變量名;若是改用命名函數表達式建立,那麼它的名稱就是等號右側的函數名稱。
var expression1 = function() { }; expression1.name; //"expression1" var expression2 = function named() { }; expression2.name; //"named"
(3)當用bind()方法綁定一個函數時,它的名稱就會加「bound」前綴。
function age() { } age.bind(this).name; //"bound age"
(4)訪問器屬性包含寫入方法和讀取方法,它們的名稱會分別加「set」和「get」前綴。注意,須要調用Object.getOwnPropertyDescriptor()才能引用這兩個方法。
var obj = { get age() { }, set age(value) { } }; var descriptor = Object.getOwnPropertyDescriptor(obj, "age"); descriptor.get.name; //"get age" descriptor.set.name; //"set age"
(5)若是對象的方法是用Symbol命名的,那麼這個Symbol的描述就是它的名稱。
var sym = Symbol("age"), obj = { [sym]: function() {} }; obj[sym].name; //"[age]"
2)length
函數的length屬性可返回形參個數(即聲明時的參數),但它的值會受剩餘參數(已在第二篇中作過介紹)和默認參數的影響,以下代碼所示。
(function rest(name, ...args){ }).length; //1 (function rest(name, age = 28){ }).length; //1 (function rest(name, age = 28, school){ }).length; //1
根據上面的代碼可知,形參個數的統計會忽略剩餘參數,而且止於默認參數。
ES6容許塊級函數(Block-Level Function)的聲明,即在塊級做用域中聲明函數,而在ES5中如此操做的話,將會拋出語法錯誤的異常。
1)嚴格模式
在嚴格模式中,塊級函數的聲明可提高至當前代碼塊的頂部,在代碼塊以外是不可見的,以下代碼所示。
"use strict"; (function() { func("strick"); //拋出未定義的引用錯誤 if(true) { func("freedom"); //"freedom" function func(name) { return name; } { func("jane"); //"jane" } } func("justify"); //拋出未定義的引用錯誤 })();
只有在func()函數所處的代碼塊或與之相鄰的代碼塊中,才能被正確調用。
2)普通模式
在普通模式(即非嚴格模式)中,只有當塊級函數所在的代碼塊被成功執行後,它的聲明才能被提高至當前腳本文件或函數體的頂部,以下代碼所示。
(function() { func("strick"); //拋出未定義的引用錯誤 if(true) { func("freedom"); //"freedom" function func(name) { return name; } { func("jane"); //"jane" } } func("justify"); //"justify" })();
在代碼塊以外調用了兩次func()函數,因爲第一次調用時,func()函數所處的代碼塊還未被執行(即還未聲明),所以會拋出未定義的引用錯誤。
元屬性(Meta Property)就是非對象的屬性,可以以屬性訪問的形式讀取特殊的元信息。new.target是由ES6引入的一個元屬性,可檢測一個函數是否與new運算符組合使用,而且只能存在於函數體內。
在JavaScript中,new是一個關鍵字,而不是一個對象。但當函數做爲構造函數被調用時,new.target可以指向新建立的目標對象;而當函數做爲普通函數被調用時,new.target的值爲undefined,以下所示。
function func1() { typeof new.target; //"function" } new func1(); function func2() { new.target === undefined; //true } func2();
把func1()做爲構造函數使用,在其函數體中利用typeof運算符檢測出new.target是一個函數對象;而在func2()函數中,讓new.target和undefined進行了全等比較,獲得的結果爲true。