主要知識點有:函數參數默認值、剩餘參數、擴展運算符、new.target屬性、塊級函數、箭頭函數以及尾調用優化
在ES5中,咱們給函數傳參數,而後在函數體內設置默認值,以下面這種方式。segmentfault
function a(num, callback) { num = num || 6 callback = callback || function (data) {console.log('ES5: ', data)} callback(num * num) } a() //ES5: 36,不傳參輸出默認值 //你還能夠這樣使用callback a(10, function(data) { console.log(data * 10) // 1000, 傳參輸出新數值 })
弊端:此處的 num 的有效值實際上有多是 0 ,但由於 0 是假值,就會致使 num 的值在這種狀況下會被替換爲 6;
能夠用 typeof 來檢測參數的類型:數組
function a(num, callback) { num = (typeof num!== "undefined") ? num: 6; callback = (typeof callback !== "undefined") ? callback : function (data) {console.log('ES5: ', data)}; callback(num * num) }
雖然這種方法更安全,但依然爲實現一個基本需求而書寫了過多的代碼。它表明了一種公共
模式,而流行的 JS 庫中都充斥着相似的模式。安全
function a(num = 6, callback = function (data) {console.log('ES6: ', data)}) { callback(num * num) } a() //ES6: 36, 不傳參輸出默認值 a(10, function(data) { console.log(data * 10) // 1000,傳參輸出新數值 })
使用ES6的默認值寫法可讓函數體內部的代碼更加簡潔優雅閉包
function mixArgs(first, second) { console.log(first === arguments[0]); console.log(second === arguments[1]); first = "c"; second = "d"; console.log(first === arguments[0]); console.log(second === arguments[1]); } mixArgs("a", "b"); //輸出 true true true true
function mixArgs(first, second) { "use strict"; console.log(first === arguments[0]); console.log(second === arguments[1]); first = "c"; second = "d" console.log(first === arguments[0]); console.log(second === arguments[1]); } mixArgs("a", "b"); //輸出 true true false false
arguments 對象的表現老是會與 ES5 的嚴格模式一致,不管此時函數是否明確運行在嚴格模式下。app
// 非嚴格模式 function mixArgs(first, second = "b") { console.log(arguments.length); console.log(first === arguments[0]); console.log(second === arguments[1]); first = "c"; second = "d" console.log(first === arguments[0]); console.log(second === arguments[1]); } mixArgs("a"); //輸出 1 true false false false
此時arguments.length =1 ,由於只給 mixArgs() 傳遞了一個參數。這也意味着arguments[1] 的值是 undefined ,符合將單個參數傳遞給函數時的預期;這同時意味着first 與 arguments[0] 是相等的。改變 first 和 second 的值不會對 arguments 對象形成影響,不管是否在嚴格模式下,因此你能夠始終依據 arguments 對象來反映初始調用狀態。函數
參數不只能夠設置默認值爲字符串,數字,數組或者對象,還能夠是一個函數。性能
function add() { return 10 } function a(num = add()){ console.log(num) } a() // 10
第一章咱們提到了let和const什麼變量的臨時死區(TDZ),默認參數既然是參數,那麼也一樣有臨時死區,函數的做用域是獨立的,a函數不能共享b函數的做用域參數。優化
//這是個默認參數臨時死區的例子,當初始化a時,b尚未聲明,因此第一個參數對b來講就是臨時死區。 function add(a = b, b){ console.log(a + b) } add(undefined, 2) // b is not define
上面說的參數都是命名參數,而無命名參數也是函數傳參時常常用到的。當傳入的參數是一個對象,不是一個具體的參數名,則是無命名參數。this
function add(object){ console.log(object.a + object.b) } let obj = { a: 1, b: 2 } add(obj) // 3
使用...(展開運算符)的參數就是不定參數,它表示一個數組。
function add(...arr){ console.log(a + b) } let a = 1,b = 2 add(a, b) // 3
不定參數的使用限制:
//錯誤的寫法1 function add(...arr, c){ console.log(a + b) } let a = 1,b = 2,c = 3 add(a, b, c)
//錯誤的寫法2 let obj = { set add(...arr) { } }
剩餘參數如何影響 arguments 對象
arguments 對象在函數被調用時反映了傳入的參數,與剩餘參數能協同工做,就像以下程序所演示的:
function checkArgs(...args) { console.log(args.length); console.log(arguments.length); console.log(args[0], arguments[0]); console.log(args[1], arguments[1]); } checkArgs("a", "b"); //輸出 2 2 a a b b
arguments 對象總能正確反映被傳入函數的參數,而無視剩餘參數的使用。
ES6中的構造函數Function新增了支持默認參數和不定參數。
考慮一下Math.max()方法,它接受任意數量的參數,並會返回其中的最大值。
//兩個值進行比較 let value1 = 25, value2 = 50; console.log(Math.max(value1, value2)); // 50 //一個數組中找到最大值(es5) let values = [25, 50, 75, 100] console.log(Math.max.apply(Math, values)); //es6 let values = [25, 50, 75, 100] // 等價於 console.log(Math.max(25, 50, 75, 100)); console.log(Math.max(...values)); // 100
擴展運算符傳遞參數
//假設你想讓 Math.max() 返回的最小值爲 0 (以防數組中混入了負值),你能夠將參數 0 單獨傳入,並繼續爲其餘參數使用擴展運算符 let values = [-25, -50, -75, -100] console.log(Math.max(...values, 0)); // 0
ES6 給全部函數添加了 name 屬性。
//函數聲明 function doSomething() { // ... } //匿名函數表達式 var doAnotherThing = function() { // ... }; console.log(doSomething.name); // "doSomething" console.log(doAnotherThing.name); // "doAnotherThing"
//doSomethingElse的優先級高於doSomething var doSomething = function doSomethingElse() { // ... }; //person.firstName 實際是個 getter 函數,所以它的名稱是 "get firstName" var person = { get firstName() { return "Nicholas" }, sayName: function() { console.log(this.name); } } console.log(doSomething.name); // "doSomethingElse" console.log(person.sayName.name); // "sayName" var descriptor = Object.getOwnPropertyDescriptor(person, "firstName"); console.log(descriptor.get.name); // "get firstName"
var doSomething = function() { // ... }; console.log(doSomething.bind().name); // "bound doSomething" console.log((new Function()).name); // "anonymous"
JS 爲函數提供了兩個不一樣的內部方法: [[Call]] 與 [[Construct]] 。當函數未使用 new進行調用時, [[call]] 方法會被執行,運行的是代碼中顯示的函數體。而當函數使用 new進行調用時, [[Construct]] 方法則會被執行,負責建立一個被稱爲新目標的新的對象,並
且使用該新目標做爲 this 去執行函數體。擁有 [[Construct]] 方法的函數被稱爲構造器。
使用instanceof
function Person(name) { if (this instanceof Person) { this.name = name; // 使用 new } else { throw new Error("You must use new with Person.") } } var person = new Person("Nicholas"); var notAPerson = Person("Nicholas"); // 拋出錯誤
但這種狀況下並不可靠:
function Person(name) { if (this instanceof Person) { this.name = name; // 使用 new } else { throw new Error("You must use new with Person.") } } var person = new Person("Nicholas"); var notAPerson = Person.call(person, "Michael"); // 奏效了!
經過檢查 new.target 是否被定義,這個新的元屬性就讓你能安全地判斷函數是否被使用new進行了調用。
function Person(name) { if (typeof new.target !== "undefined") { this.name = name; // 使用 new } else { throw new Error("You must use new with Person.") } } var person = new Person("Nicholas"); var notAPerson = Person.call(person, "Michael"); // 出錯!
也能夠檢查 new.target 是否被使用特定構造器進行了調用,例如如下代碼:
function Person(name) { if (new.target === Person) { this.name = name; // 使用 new } else { throw new Error("You must use new with Person.") } } function AnotherPerson(name) { Person.call(this, name); } var person = new Person("Nicholas"); var anotherPerson = new AnotherPerson("Nicholas"); // 出錯!
警告:在函數以外使用 new.target 會有語法錯誤。
"use strict"; if (true) { // 在 ES5 會拋出語法錯誤, ES6 則不會 function doSomething() { // ... } }
塊級函數會被提高到定義所在的代碼塊的頂部:
"use strict"; if (true) { console.log(typeof doSomething); // "function" function doSomething() { // ... } doSomething(); } console.log(typeof doSomething); // "undefined"
let 函數表達式:
"use strict"; if (true) { console.log(typeof doSomething); // 拋出錯誤 let doSomething = function () { // ... } doSomething(); } console.log(typeof doSomething);
ES6 在非嚴格模式下一樣容許使用塊級函數,但行爲有細微不一樣。塊級函數的做用域會被提高到所在函數或全局環境的頂部,而不是代碼塊的頂部。
// ES6 behavior if (true) { console.log(typeof doSomething); // "function" function doSomething() { // ... } doSomething(); } console.log(typeof doSomething); // "function"
箭頭函數與傳統的 JS 函數區別:
var getName = () => "Nicholas"; // 有效等價於: var getName = function() { return "Nicholas"; };
var reflect = value => value; // 有效等價於: var reflect = function(value) { return value; };
var sum = (num1, num2) => num1 + num2; // 有效等價於: var sum = function(num1, num2) { return num1 + num2; };
var sum = (num1, num2) => { return num1 + num2; }; // 有效等價於: var sum = function(num1, num2) { return num1 + num2; }; //將對象字面量包裹在括號內,標示了括號內是一個字面量而不是函數體。 var getTempItem = id => ({ id: id, name: "Temp" }); // 有效等價於: var getTempItem = function(id) { return { id: id, name: "Temp" }; };
let person = function(name) { return { getName: function() { return name; } }; }("Nicholas"); console.log(person.getName()); // "Nicholas"
let person = ((name) => { return { getName: function() { return name; } }; })("Nicholas"); console.log(person.getName()); // "Nicholas"
譯註:使用傳統函數時, (function(){/ 函數體/})(); 與 (function(){/ 函數體/}());
這兩種方式都是可行的。
但若使用箭頭函數,則只有下面的寫法是有效的: (() => {/ 函數體/})();
尾調用是指在函數return的時候調用一個新的函數,因爲尾調用的實現須要存儲到內存中,在一個循環體中,若是存在函數的尾調用,你的內存可能爆滿或溢出。
ES6中,引擎會幫你作好尾調用的優化工做,你不須要本身優化,但須要知足下面3個要求:
一、函數不是閉包
二、尾調用是函數最後一條語句
三、尾調用結果做爲函數返回
一個知足以上要求的函數以下所示:
"use strict"; function a() { return b(); }
下面的都是不知足的寫法:
//沒有return不優化 "use strict"; function a() { b(); } //不是直接返回函數不優化 "use strict"; function a() { return 1 + b(); } //尾調用是函數不是最後一條語句不優化 "use strict"; function a() { const s = b(); return s } //閉包不優化 "use strict"; function a() { const num = 1 function b() { return num } return b }
尾調用實際用途——遞歸函數優化
在ES5時代,咱們不推薦使用遞歸,由於遞歸會影響性能。
可是有了尾調用優化以後,遞歸函數的性能有了提高。
//新型尾優化寫法 "use strict"; function a(n, p = 1) { if(n <= 1) { return 1 * p } let s = n * p return a(n - 1, s) } //求 1 x 2 x 3的階乘 let sum = a(3) console.log(sum) // 6