function log(x, y) { y = y || 'world' console.log(x + ' ' + y); } log('hello') // hello world log('hello','China') // hello China log('hello', '') // hello world /* * 若是 y 沒有賦值,指定默認值爲world // log('hello') // hello world * 若是 y 有賦值,指定賦值 log('hello','China') // hello China * 若是 y 有賦值,可是爲 boolean 值 false,則該賦值就不起做用了 log('hello', '') // hello world * */
console.log((function (a) {}).length) // 1 console.log((function (a = 1) {}).length) // 0 console.log((function (a, b = 1, c) {}).length) //1 console.log((function (a, b , c = 2) {}).length) // 2 // 返回沒有賦值形參的個數 // 若是設置了默認值的參數不是尾參數,那麼length屬性也再也不計入後面的參數
賦值的形參造成單獨的做用域編程
function add(...value) { let sum = 0 for(let val of value) { sum += val } return sum } const NUM = add(2, 5, 8, 90) console.log(NUM)
var f = v => v; // 若是見箭頭函數不須要參數 或者 須要對個參數,就是用圓括號表明參數部分 var sum = (num1, num2) => num1 + num2 // 若是箭頭函數的代碼塊部分多餘一條語句,就要使用大括號將其闊起來, // 並使用return語句返回 // 若是箭頭函數直接返回一個對象,必須在對象外面加上括號 var getTempItem = id => ({ id: id, name: "Temp"}) // 箭頭函數 注意事項 /* * 函數體內的this對象就是定義時所在的對象,而不是使用時所在的隊形 * 不能夠當作構造函數 * 不能夠使用arguments對象 * 不能夠使用yield命令,所以箭頭函數態用做Generator函數 * 箭頭函數可讓this指向固定化,這種形式很是有利於封裝回調函數 * 箭頭函數能夠綁定this,大大減小了顯示綁定this對象的寫法(call,apply,bind)*/
《ES6標準入門》 第三版數組
箭頭函數有幾個使用注意點。
(1)函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。
(2)不能夠看成構造函數,也就是說,不能夠使用new命令,不然會拋出一個錯誤。
(3)不能夠使用arguments對象,該對象在函數體內不存在。若是要用,能夠用 rest 參數代替。
(4)不能夠使用yield命令,所以箭頭函數不能用做 Generator 函數。
上面四點中,第一點尤爲值得注意。this對象的指向是可變的,可是在箭頭函數中,它是固定的。app
箭頭函數可讓setTimeout裏面的this,綁定定義時所在的做用域,而不是指向運行時所在的做用域。函數式編程
箭頭函數可讓this指向固定化,這種特性頗有利於封裝回調函數。函數
箭頭函數能夠綁定this對象,大大減小了顯式綁定this對象的寫法(call、apply、bind)。優化
可是,箭頭函數並不適用於全部場合,因此如今有一個 提案,提出了「函數綁定」(function bind)運算符,用來取代call、apply、bind調用。this
函數綁定運算符是並排的兩個冒號(::),雙冒號左邊是一個對象,右邊是一個函數。該運算符會自動將左邊的對象,做爲上下文環境(即this對 象),綁定到右邊的函數上面。spa
若是雙冒號左邊爲空,右邊是一個對象的方法,則等於將該方法綁定在該對象上面。rest
就是指某個函數的後一步是調用另外一個函數。code
函數調用會在內存造成一個「調用記錄」,又稱「調用幀」(call frame),保存調用位置和內部變量等信息。
若是在函數A的內部調用函數B,那 麼在A的調用幀上方,還會造成一個B的調用幀。
等到B運行結束,將結果返回到A,B的調用幀纔會消失。若是函數B內部還調用函數C,那就還有一 個C的調用幀,
以此類推。全部的調用幀,就造成一個「調用棧」(call stack)。
尾調用因爲是函數的後一步操做,因此不須要保留外層函數的調用幀,由於調用位置、內部變量等信息都不會再用到了,只要直接用內層函數的調用 幀,取代外層函數的調用幀就能夠了。
只保留內層函數的調用幀。若是全部函數都是尾調用,那麼徹底能夠作到每次執行時,調用幀只有 一項,這將大大節省內存。這就是「尾調用優化」的意義。
注意,只有再也不用到外層函數的內部變量,內層函數的調用幀纔會取代外層函數的調用幀,不然就沒法進行「尾調用優化」。
函數調用自身,稱爲遞歸。若是尾調用自身,就稱爲尾遞歸。
遞歸很是耗費內存,由於須要同時保存成千上百個調用幀,很容易發生「棧溢出」錯誤(stack overflow)。但對於尾遞歸來講,因爲只存在一個調用幀, 因此永遠不會發生「棧溢出」錯誤。
/* ----------- 普通遞歸 ------------------ */ function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1); } let num1 = factorial(5) console.time(1); //設置時間起點 console.log(num1); // 120 console.timeEnd(1); // 3.450ms /* 上面代碼是一個階乘函數,計算n的階乘,多須要保存n個調用記錄,複雜度 O(n) 。 */ /* ----------- 尾遞歸 ------------------ */ /* 若是改寫成尾遞歸,只保留一個調用記錄,複雜度 O(1) 。 */ function factorial(n, total) { if (n === 1) return total; return factorial(n - 1, n * total); } let num2 = factorial(5, 1) // 120 console.time(1); //設置時間起點 console.log(num2); // 120 console.timeEnd(1); // 0.275ms
ES6 中只要使用尾遞歸,就不會發生棧溢出,相對節省內存。
/* 非尾遞歸的 費氏數列 Fibonacci */ function Fibonacci(n) { if (n <= 1) { return 1 }; return Fibonacci(n - 1) + Fibonacci(n - 2); } // let num21 = Fibonacci(10) // 573147844013817200000 // console.time(1); //設置時間起點 // console.log(num21); // 89 // console.timeEnd(1); // 4.030ms // let num22 = Fibonacci(100) // 573147844013817200000 // console.time(2); //設置時間起點 // console.log(num22); // 堆棧溢出 // console.timeEnd(2); // 4.堆棧溢出 // let num23 = Fibonacci2(1000) // 7.0330367711422765e+208 // console.time(3); //設置時間起點 // console.log(num23); // 堆棧溢出 // console.timeEnd(3); // 堆棧溢出 /* 尾遞歸優化過的 Fibonacci 數列實現以下 */ function Fibonacci2(n, ac1 = 1, ac2 = 1) { if (n <= 1) { return ac2 }; return Fibonacci2(n - 1, ac2, ac1 + ac2); } // let num21 = Fibonacci2(10) // 573147844013817200000 // console.time(1); //設置時間起點 // console.log(num21); // 89 // console.timeEnd(1); // 3.469ms // let num22 = Fibonacci2(100) // 573147844013817200000 // console.time(2); //設置時間起點 // console.log(num22); // 573147844013817200000 // console.timeEnd(2); // 4.511ms // let num23 = Fibonacci2(1000) // 7.0330367711422765e+208 // console.time(3); //設置時間起點 // console.log(num23); // 7.0330367711422765e+208 // console.timeEnd(3); // 4.053ms /* ES6 是如此,第一次明確規定,全部 ECMAScript 的實 現,都必須部署「尾調用優化」。這就是說,ES6 中只要使用尾遞歸,就不會發生棧溢出,相對節省內存。 */
/* 尾遞歸的實現,每每須要改寫遞歸函數,確保後一步只調用自身。作到這一點的方法,就是把全部用到的內部變量改寫成函數的參數。 */ function tailFactorial(n, total) { if (n === 1) return total; return tailFactorial(n - 1, n * total); } function factorial(n) { return tailFactorial(n, 1); } factorial(5) // 120 // 上面代碼經過一個正常形式的階乘函數factorial,調用尾遞歸函數tailFactorial,看起來就正常多了。 // 函數式編程有一個概念,叫作柯里化(currying),意思是將多參數的函數轉換成單參數的形式。這裏也能夠使用柯里化。 function currying(fn, n) { return function (m) { return fn.call(this, m, n); }; } function tailFactorial(n, total) { if (n === 1) return total; return tailFactorial(n - 1, n * total); } const factorial = currying(tailFactorial, 1); factorial(5) // 120