es5中,傳入函數的參數若是爲'',那麼在進行判斷時會轉化爲false,對結果形成影響es6
function log(x, y) { y = y || 'World'; console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello World //這裏!
es6容許爲函數的參數設置默認值,直接寫在參數定義的後面,只有在參數是undefined時才採用默認值編程
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}
參數的變量是默認聲明的,不能再次聲明segmentfault
function foo(x=5){ let x =1 // error const x =2 // error }
使用參數默認值時,函數不能有同名函數數組
function foo(x,x,y=1){...} //報錯
function foo({x,y = 5}){ console.log(x,y) } foo({}) //undefined,5 foo({x:1}) //1,5 foo({x:1,y:2}) //1,2 foo() //報錯: TypeError: Cannot read property 'x' of undefined 解釋: 上面代碼只使用了對象的解構賦值默認值,沒有使用函數參數的默認值 {x,y =5}是一個參數,這個參數裏面的內容,賦了默認值,可是這個參數自己,並無默認值,因此找不到x
能夠寫成:雙重默認值:若是沒提供參數,foo的參數默認一個空對象瀏覽器
function foo({x,y=5}={}){ console.log(x,y) }
下面代碼中,函數fetch沒有第二個參數時,函數參數的默認值就會生效,
而後纔是解構賦值的默認值生效,變量method纔會取到默認值GETapp
function fetch(url, { body = '', method = 'GET', headers = {} } = {}) { console.log(method); } fetch('http://example.com') // "GET"
有差異的寫法:編程語言
寫法一: function m1({x=0,y=0} = {}){ return [x,y] } 寫法二: function m2({x,y} ={x:0,y:0}){ return [x,y] }
寫法一:對函數參數進行了解構賦值,爲空對象,對函數參數裏面的內容進行了解構賦值
寫法二:對函數參數進行了解構賦值,但沒進行參數裏面內容的解構賦值,因此只有在沒有傳入參數的時候,默認值才生效,傳入參數,默認值久被覆蓋,返回undefined函數式編程
m1() //[0,0] m2() //[0,0] m1({x:3,y:8}) //[3,8] m2({x:3,y:8}) //[3,8] m1({x:3}) //[3,0] m2({x:3}) //[3,undefined] m1({}) //[0,0] m2({}) //[undefined,undefined] m1({z:3}) //[0,0] m2({z:3}) //[undefined,undefined]
當定義參數默認值的時候,應該是函數的尾參數函數
function f(x=1,y){ return [x,y] } f() // [1, undefined] f(2) // [2, undefined] f(, 1) // 報錯 f(undefined, 1) // [1, 1] function f(x, y = 5, z) { return [x, y, z]; } f() // [undefined, 5, undefined] f(1) // [1, 5, undefined] f(1, ,2) // 報錯 f(1, undefined, 2) // [1, 5, 2]
length屬性的含義:該函數預期傳入的參數個數fetch
(function (a){...}).length //1
當參數指定了默認值以後,length將返回沒有指定默認值的參數個數
(function (a=5){}).length // 0 (function (a, b, c = 5) {}).length // 2
當指定了默認值的參數不是尾參數,length將再也不計入後面的參數
(function (a = 0, b, c) {}).length // 0 (function (a, b = 1, c) {}).length // 1
當給參數設置了默認值,當函數初始化時,參數會造成一個單獨的做用域,初始化結束,做用域消失
var x = 1 function f(x,y=x){ console.log(y) } f(2) //2
上面代碼中,參數y的默認值等於變量x。調用函數f時,參數造成一個單獨的做用域。
在這個做用域裏面,默認值變量x指向第一個參數x,而不是全局變量x,因此輸出是2。
let x = 1 function f(y = x){ let x = 2 console.log(y) } f() //1
上面代碼中,函數f調用時,參數y = x造成一個單獨的做用域。
這個做用域裏面,變量x自己沒有定義,因此指向外層的全局變量x。
函數調用時,函數體內部的局部變量x影響不到默認值變量x。
若是此時,全局變量x不存在,就會報錯。
function f(y = x) { let x = 2; console.log(y); } f() // ReferenceError: x is not defined
下面代碼中,參數x = x造成一個單獨做用域。
實際執行的是let x = x,因爲暫時性死區的緣由,這行代碼會報錯」x 未定義「。
var x = 1; function foo(x = x) {...} foo() // ReferenceError: x is not defined
若是參數的默認值是一個函數,該函數的做用域也遵照這個規則
let foo = 'outer' function bar(func = () =>foo){ let foo = 'inner' console.log(func()) } bar() //outer
若是寫成下面這樣,就會報錯。
function bar(func = () => foo) { let foo = 'inner'; console.log(func()); } bar() // ReferenceError: foo is not defined
var x=1; function foo(x,y=function(){x=2}){ var x = 3 y() console.log(x) } foo() //3 console.log(x) //1
解釋:
var x1 = 1; function foo (x2, y = function () { x2 = 2; console.log(x2); }) { var x3 = 3; y(); console.log(x3); } foo(); console.log(x1);
首先全局做用域中有一個x(x1),函數foo的參數(中間做用域)中有一個x(x2),函數foo內部有一個x(x3)
在foo參數造成的做用域裏聲明瞭變量x,y.
y的默認值是一個函數,這個函數內部的x,指向同一個做用域的x.
在foo內部造成的做用域裏聲明瞭變量x,由於和x1不在同一個做用域,因此不一樣於x1,是x3
因此執行y後,內部變量x和外部全局變量x的值都沒變
中間做用域
若是將var x = 3的var刪掉,函數內部的x指向外部的x
參數的默認值不是在定義時執行,而是在運行時執行。若是參數已經賦值,默認值中的函數就不會運行。
(...變量名),用於獲取多餘函數,rest參數搭配的變量是一個數組,該變量將多餘的參數放入數組中
function add(...values){ let sum = 0 for(var val og values){ sum += val } return sum } add(2,5,3) //10
下面是一個 rest 參數代替arguments變量的例子。
// arguments變量的寫法 function sortNumbers() { return Array.prototype.slice.call(arguments).sort(); } // rest參數的寫法 const sortNumbers = (...numbers) => numbers.sort();
/*arguments對象不是數組,而是一個相似數組的對象。
因此爲了使用數組的方法,必須使用Array.prototype.slice.call先將其轉爲數組。*/
下面是一個利用 rest 參數改寫數組push方法的例子
function push(array,...items){ items.forEach(function(item){ array.push(item) }) } var a = []; push(a, 1, 2, 3)
注意:rest 參數以後不能再有其餘參數(即只能是最後一個參數),不然會報錯。
函數的length屬性,不包括 rest 參數
(function(a) {}).length // 1 (function(...a) {}).length // 0 (function(a, ...b) {}).length // 1
函數參數使用了默認值、解構賦值、或者擴展運算符,
那麼函數內部就不能顯式設定爲嚴格模式,不然會報錯
兩種方法能夠規避這種限制。第一種是設定全局性的嚴格模式,這是合法的。
'use strict'; function doSomething(a, b = a) { // code }
第二種是把函數包在一個無參數的當即執行函數裏面。
const doSomething = (function () { 'use strict'; return function(value = 42) { return value; }; }());
返回該函數的函數名
var f = function () {}; // ES5 f.name // "" // ES6 f.name // "f" Function構造函數返回的函數實例,name屬性的值爲anonymous。 (new Function).name // "anonymous" bind返回的函數,name屬性值會加上bound前綴。 function foo() {}; foo.bind({}).name // "bound foo" (function(){}).bind({}).name // "bound "
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; };
var sum = (num1, num2) => { return num1 + num2; }
// 報錯 let getTempItem = id => { id: id, name: "Temp" }; // 不報錯 let getTempItem = id => ({ id: id, name: "Temp" });
let fn = ()=>void doesNotReturn()
const full = ({ first, last }) => first + ' ' + last; // 等同於 function full(person) { return person.first + ' ' + person.last; }
[1,2,3].map(x=>x*x) [1,2,3].map(function (x) { return x * x; }); var result = values.sort((a,b)=>{a*b}) var result = values.sort(function(a,b){ return a*b })
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]
function foo(){ setTimeout(()=>{ console.log('id':this.id) },100) } var id = 21 foo.call({id:42}) //id:42
解釋:
上面代碼中,setTimeout()的參數是一個箭頭函數, 這個箭頭函數的定義生效是在foo函數生成時,而它的真正執行要等到 100 毫秒後。 若是是普通函數,執行時this應該指向全局對象window,這時應該輸出21。 可是,箭頭函數致使this老是指向函數定義生效時所在的對象(本例是{id: 42}), 因此打印出來的是42
function Timer(){ this.s1 = 0; this.s2 = 0; setInterval(()=>this.s1++,1000) setInterval(function(){ this.s2++ },1000) } var timer = new Timer() setTimeout(() => console.log('s1: ', timer.s1), 3100); setTimeout(() => console.log('s2: ', timer.s2), 3100); // s1: 3 // s2: 0
解釋:
Timer函數內部設置了2個定時器: 箭頭函數的this指向了定義時所在的做用域,因此是Timer函數 普通函數的this指向了運行時所在的做用域,因此是window(全局對象) 因此3100ms後,timer.s1被更新了3次,timer.s2一次都沒更新
var handler = { id:'123456', init:function(){ document.addEventListener('click', event=>this.doSomething(event.type),false) }, doSomething:function(type){ console.log('Handling ' + type + ' for ' + this.id); } }
解釋:
init方法中箭頭函數的this指向handler對象.
this指向固定化,並非由於箭頭函數內部綁定this,而是箭頭函數沒有本身的this,
致使內部的this就是外層代碼塊的this,也是由於沒有this,就不能用做構造函數.
es6: function foo(){ setTimeout(()=>{ console.log('id':this.id) },100) } es5: function foo(){ var _this = this setTimeout(funciton(){ console.log('id',_this.id) },100) }
function foo() { return () => { return () => { return () => { console.log('id:', this.id); }; }; }; } var f = foo.call({id: 1}); var t1 = f.call({id: 2})()(); // id: 1 var t2 = f().call({id: 3})(); // id: 1 var t3 = f()().call({id: 4}); // id: 1
下面代碼中,箭頭函數內部的變量arguments,實際上是函數foo的arguments變量
function foo(){ setTimeout(()=>{ console.log('args:',arguments) },100) } foo(2,4,6,8) //args:[2,4,6,8]
由於箭頭函數沒有本身的this,因此bind(),apply(),call()都不能改變this指向
(function (){ return[(()=>this.x).bind({x:'inner'})()] }).call({x:'outer'}) //['outer']
const cat = { lives:9, jump:()=>{ this.lives-- } }
由於對象不構成單獨的做用域,因此此時的箭頭函數的this指向全局對象
若是是普通函數的this指向cat
var button = document.getElementById('press') button.addEventListener('click',()=>{ this.classList.toggle('on') })
上面代碼運行時,點擊按鈕會報錯,
由於button的監聽函數是一個箭頭函數,致使裏面的this就是全局對象。
若是改爲普通函數,this就會動態指向被點擊的按鈕對象。
* es5: function insert(value) { return {into: function (array) { return {after: function (afterValue) { array.splice(array.indexOf(afterValue) + 1, 0, value); return array; }}; }}; } insert(2).into([1, 3]).after(1); * es6: let insert = (value)=>{ ({into:(array)=>({after:(afterValue)=>{ array.splice(array.indexOf(afterValue) + 1, 0, value) return array; }})}) } insert(2).into([1, 3]).after(1); //[1, 2, 3]
indexOf,存在返回對應位置,不存在返回-1
splice(新增/刪除位置,刪除的數量(若是爲0就不刪除),內容)
一個部署管道機制(pipeline)的例子,即前一個函數的輸出是後一個函數的輸入
const pipeline = (...funcs)=>{ val=>funcs.reduce((a,b)=>b(a),val) } const plus1 = a=>a+1 const mult2 = a=>a*2 const addThenMult = pipeline(plus1,mult2) addThenMult(5) //12 即: const plus = a=>a+1 const mult2 = a=>a*2 mult2(plus1(5)) // 12
reduce():
arr.reduce(function(prev,cur,index,arr){ ... },init) arr:原數組 prev:上一次調用回調時的返回值.或者初始值init cur:當前正在處理的元素 index:表示當前正在處理的數組元素的索引,若提供 init 值,則索引爲0,不然索引爲1 init:初始值
尾調用:函數式編程的一個概念,指某個函數的最後一步時調用另外一個函數
function f(x){ return g(x) }
上面代碼中,函數f的最後一步是調用函數g,這就叫尾調用。
如下三種狀況,都不屬於尾調用。
// 狀況一:調用g以後還有賦值操做 function f(x){ let y = g(x); return y; } // 狀況二:調用g以後還有別的操做 function f(x){ return g(x) + 1; } // 狀況三:return的時undefined function f(x){ g(x); }
尾調用不必定出如今函數尾部,只要是最後一步操做便可
function f(x){ if(x>0){ return m(x) } return n(x) }
m和n都屬於尾調用
尾調用優化:只保留內層函數的調用幀
function f() { let m = 1; let n = 2; return g(m + n); } f(); // 等同於 function f() { return g(3); } f(); // 等同於 g(3);
若是全部函數都是尾調用,那麼徹底能夠作到每次執行時,調用幀只有一項,
這將大大節省內存。這就是「尾調用優化」的意義。
function addOne(a){ var one = 1 function inner(b){ retrun b+one } return inner(a) }
注意:目前只有 Safari 瀏覽器支持尾調用優化,Chrome 和 Firefox 都不支持
遞歸:函數調用自身
尾遞歸:尾調用自身
下面代碼是一個階乘函數,計算n的階乘,最多須要保存n個調用記錄,複雜度 O(n) 。
function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1); } factorial(5) // 120
改寫成尾遞歸,只保留一個調用記錄,複雜度 O(1)
function factorial(n, total) { if (n === 1) return total; return factorial(n - 1, n * total); } factorial(5, 1) // 120
非尾遞歸的 Fibonacci 數列實現以下
function Fibonacci (n){ if(n<=1){return 1} return Fibonacci(n-1)+Fibonacci(n-2) } Fibonacci(10) // 89 Fibonacci(100) // 超時 Fibonacci(500) // 超時
function Fibonacci(n,ac1=1,ac2=1){ if(n<=1){return ac2} return Fibonacci(n-1,ac2,ac1+ac2) } Fibonacci2(100) // 573147844013817200000 Fibonacci2(1000) // 7.0330367711422765e+208 Fibonacci2(10000) // Infinity
若是想採用尾遞歸的實現,須要改寫遞歸函數,確保最後一步只調用自身
作到這一點的方法,就是把全部用到的內部變量改寫成函數的參數
計算5的階乘,怎麼作才能夠只傳入一個參數:
方法一:是在尾遞歸函數以外,再提供一個正常形式的函數
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
柯里化:將多參數的函數轉換成單參數的形式
方法二:es6的函數默認值
function factorical(n, total = 1){ if (n === 1) return total; return factorial(n - 1, n * total); } factorial(5) // 120
遞歸的本質是循環操做,
純粹的函數式編程語言沒有循環操做命令,全部的循環都用遞歸實現,
這就是爲何尾遞歸對這些語言極其重要。
ES6的尾調用優化直再嚴格模式下開啓,正常模式無效
這是由於在正常模式下,函數內部有兩個變量,能夠跟蹤函數的調用棧。
func.arguments:返回調用時函數的參數。 func.caller:返回調用當前函數的那個函數。
尾調用優化發生時,函數的調用棧會改寫,所以上面兩個變量就會失真。
嚴格模式禁用這兩個變量,因此尾調用模式僅在嚴格模式下生效。
function restricted() { 'use strict'; restricted.caller; // 報錯 restricted.arguments; // 報錯 } restricted();
(https://es6.ruanyifeng.com/?s...[沒看!]
(https://segmentfault.com/a/11...
ES2017 容許函數的最後一個參數有尾逗號(trailing comma)。
此前,函數定義和調用時,都不容許最後一個參數後面出現逗號。
function clownsEverywhere( param1, param2 ) { /* ... */ } clownsEverywhere( 'foo', 'bar' );
上面代碼中,若是在param2或bar後面加一個逗號,就會報錯。
若是像上面這樣,將參數寫成多行(即每一個參數佔據一行),之後修改代碼的時候,想爲函數clownsEverywhere添加第三個參數,或者調整參數的次序,就勢必要在原來最後一個參數後面添加一個逗號。這對於版本管理系統來講,就會顯示添加逗號的那一行也發生了變更。這看上去有點冗餘,所以新的語法容許定義和調用時,尾部直接有一個逗號。
function clownsEverywhere( param1, param2, ) { /* ... */ } clownsEverywhere( 'foo', 'bar', );
這樣的規定也使得,函數參數與數組和對象的尾逗號規則,保持一致了。
修改後的toString()方法,明確要求返回如出一轍的原始代碼。
function /* foo comment */ foo () {} 原來: foo.toString() // function foo() {} es6: foo.toString() // "function /* foo comment */ foo () {}"
之前明確要求catch命令後面必須跟參數 try { // ... } catch (err) { // 處理錯誤 } ES2019 作出了改變,容許catch語句省略參數。 try { // ... } catch { // ... }