ES5中設置默認值很是不方便, 咱們這樣寫:javascript
function fun(a){ a = a || 2; console.log(a); } fun(); //2 fun(0); //2 fun(1); //1
以上寫法, 若是傳入了參數, 但這個參數對應值的布爾型是 false, 就不起做用了。固然你也能夠判斷 arguments.length
是否爲0來避免這個問題, 但每一個函數這樣寫就太囉嗦了, 尤爲參數比較多的時候。在 ES6 中咱們能夠直接寫在參數表中, 若是實際調用傳遞了參數, 就用這個傳過來的參數, 不然用默認參數。像這樣:java
function fun(a=2){ console.log(a); } fun(); //2 fun(0); //0 fun(1); //1
其實函數默認參數這一點最強大的地方在於能夠和解構賦值結合使用:編程
//參數傳遞 function f([x, y, z=4]){ return [x+1, y+2, z+3]; } var [a, b, c] = f([1, 2]); //a=2, b=4, c=7 [[1, 2], [3, 4]].map(([a, b]) => a + b); //返回 [3, 7]
經過上面這個例子不難發現, 不只能夠用解構的方法設置初始值, 還能夠進行參數傳遞。固然, 這裏也能夠是對象形式的解構賦值。若是傳入的參數沒法解構, 就會報錯:segmentfault
function fun1({a=1, b=5, c='A'}){ console.log(c + (a + b)); } fun1({}); //'A6' fun1(); //TypeError, 由於沒法解構 //但這樣設計函數對使用函數的碼農很不友好 //因此, 技巧: function fun2({a=1, b=5, c='A'}={}){ console.log(c + (a + b)); } fun2(); //'A6'
注意, 其實還有一種方法, 但不如這個好, 咱們比較以下:數組
//fun1 比 fun2 好, 不會產生之外的 undefined function fun1({a=1, b=5, c='A'}={}){ console.log(c + (a + b)); } function fun2({a, b, c}={a: 1, b: 5, c: 'A'}){ console.log(c + (a + b)); } //傳了參數, 但沒傳所有參數就會出問題 fun1({a: 8}); //'A13' fun2({a: 8}); //NaN
不過這裏強烈建議, 將具備默認值的參數排在參數列表的後面。不然調用時依然須要傳參:閉包
function f1(a=1, b){ console.log(a + b); } function f2(a, b=1){ console.log(a + b); } f2(2); //3 f1(, 2); //報錯 f1(undefined, 2); //3, 注意這裏不能用 null 觸發默認值
這裏咱們還須要單獨討論一下默認參數對 arguments 的影響:app
function foo(a = 1){ console.log(a, arguments[0]); } foo(); //1 undefined foo(undefined); //1 undefined foo(2); //2 2 foo(null); //null null
很明顯,默認參數並不能加到 arguments 中。函數式編程
這個屬性ES6 以前就是存在的, 記得length表示預計傳入的形參個數, 也就是沒有默認值的形參個數:函數
(function(a){}).length; //1 (function(a = 5){}).length; //0 (function(a, b, c=5){}).length; //2 (function(...args){}).length; //0, rest參數也不計入 length
rest 參數形式爲 ...變量名
, 它會將對應的所有實際傳遞的變量放入數組中, 能夠用它來替代 arguments:優化
function f(...val){ console.log(val.join()); } f(1, 2); //[1, 2] f(1, 2, 3, 4); //[1, 2, 3, 4] function g(a, ...val){ console.log(val.join()); } g(1, 2); //[2] g(1, 2, 3, 4); //[2, 3, 4]
不然這個函數 g 你的這樣定義函數, 比較麻煩:
function g(a){ console.log([].slice.call(arguments, 1).join()); }
這裏須要注意2點:
建議:
擴展運算符相似 rest運算符的逆運算, 用 ...
表示, 放在一個(類)數組前, 將該數組展開成獨立的元素序列:
console.log(1, ...[2, 3, 4], 5); //輸出1, 2, 3, 4, 5
擴展運算符的用處不少:
[...document.querySelectorAll('li')]; //[<li>, <li>, <li>];
function push(arr, ...val){ return arr.push(...val); //調用函數時, 將數組變爲序列 }
var arr = [1, 2, 3]; var max = Math.max(...arr); //3 var arr2 = [4, 5, 6]; arr.push(...arr2); //[1, 2, 3, 4, 5, 6] new Date(...[2013, 1, 1]); //ri Feb 01 2013 00: 00: 00 GMT+0800 (CST)
var more = [4, 5]; var arr = [1, 2, 3, ...more]; //[1, 2, 3, 4, 5] var a1 = [1, 2]; var a2 = [3, 4]; var a3 = [5, 6]; var a = [...a1, ...a2, ...a3]; //[1, 2, 3, 4, 5, 6]
var a = [1, 2, 3, 4, 5]; var [a1, ...more] = a; //a1 = 1, more = [2, 3, 4, 5] //注意, 擴展運算符必須放在解構賦值的結尾, 不然報錯
var str = "hello"; var alpha = [...str]; //alpha = ['h', 'e', 'l', 'l', 'o'] [...'x\uD83D\uDE80y'].length; //3, 正確處理32位 unicode 字符
建議:使用擴展運算符(...)拷貝數組。
name 屬性返回函數的名字, 對於匿名函數返回空字符串。不過對於表達式法定義的函數, ES5 和 ES6有差異:
var fun = function(){} fun.name; //ES5: "", ES6: "fun" (function(){}).name; //""
對於有2個名字的函數, 返回後者, ES5 和 ES6沒有差異:
var fun = function baz(){} fun.name; //baz
對於 Function 構造函數獲得的函數, 返回 anonymous
:
new Function("fun").name; //"anonymous" new Function().name; //"anonymous" (new Function).name; //"anonymous"
對於 bind 返回的函數, 加上 bound
前綴
function f(){} f.bind({}).name; //"bound f" (function(){}).bind({}).name; //"bound " (new Function).bind({}).name; //"bound anonymous"
箭頭函數的形式以下:
var fun = (參數列表) => {函數體};
若是隻有一個參數(且不指定默認值), 參數列表的圓括號能夠省略; (若是沒有參數, 圓括號不能省略)
若是隻有一個 return 語句, 那麼函數體的花括號也能夠省略, 同時省略 return 關鍵字。
var fun = value => value + 1; //等同於 var fun = function(value){ return value + 1; }
var fun = () => 5; //等同於 var fun = function(){ return 5; }
若是箭頭函數的參數或返回值有對象, 應該用 ()
括起來:
var fun = n => ({name: n}); var fun = ({num1=1, num2=3}={}) => num1 + num2;
看完以前的部分, 箭頭函數應該不陌生了:
var warp = (...val) => val; var arr1 = warp(2, 1, 3); //[2, 1, 3] var arr2 = arr1.map(x => x * x); //[4, 1, 9] arr2.sort((a, b) => a - b); //[1, 4, 9]
使用箭頭函數應注意如下幾點:
舉幾個箭頭函數的實例:
實例1: 實現功能如: insert(2).into([1, 3]).after(1)
或insert(2).into([1, 3]).before(3)
這樣的函數:
var insert = value => ({ into: arr => ({ before: val => { arr.splice(arr.indexOf(val), 0, value); return arr; }, after: val => { arr.splice(arr.indexOf(val) + 1, 0, value); return arr; } }) }); console.log(insert(2).into([1, 3]).after(1)); console.log(insert(2).into([1, 3]).before(3));
實例2: 構建一個管道(前一個函數的輸出是後一個函數的輸入):
var pipe = (...funcs) => (init_val) => funcs.reduce((a, b) => b(a), init_val); //實現 2 的 (3+2) 次方 var plus = a => a + 2; pipe(plus, Math.pow.bind(null, 2))(3); //32
實例3: 實現 λ 演算
//fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v))) var fix = f => (x => f(v => x(x)(v)))(x => f(v => x(x)(v)));
建議:箭頭函數取代 Function.prototype.bind,不該再用 self / _this / that 綁定 this。其次,簡單的、不會複用的函數,建議採用箭頭函數。若是函數體較爲複雜,行數較多,仍是應該採用傳統的函數寫法。
這裏須要強調,如下狀況不能使用箭頭函數:
let calculator = { array: [1, 2, 3], sum: () => { return this.array.reduce((result, item) => result + item); //這裏的 this 成了 window } }; calculator.sum(); //"TypeError: Cannot read property 'reduce' of undefined"
function Cat(name) { this.name = name; } Cat.prototype.sayCatName = () => { return this.name; //和上一個問題同樣:這裏的 this 成了 window }; let cat = new Cat('Mew'); cat.sayCatName(); //undefined
const button = document.getElementById('myButton'); button.addEventListener('click', () => { this.innerHTML = 'Clicked button'; //這裏的 this 本應該是 button, 但不幸的成了 window });
let Message = (text) => { this.text = text; }; let helloMessage = new Message('Hello World!'); //TypeError: Message is not a constructor
let multiply = (a, b) => b === undefined ? b => a * b : a * b; //這個太難讀了,太費時間 let double = multiply(2); double(3); //6 multiply(2, 3); //6
ES7 中提出了函數綁定運算, 免去咱們使用 call, bind, apply 的各類不方便, 形式以下:
objName::funcName
如下幾組語句兩兩等同
var newFunc = obj::func; //至關於 var newFunc = func.bind(obj); var result = obj::func(...arguments); //至關於 var result = func.apply(obj, arguments);
若是 ::
左邊的對象本來就是右邊方法中的 this, 左邊能夠省略
var fun = obj::obj.func; //至關於 var fun = ::obj.func; //至關於 var fun = obj.func.bind(obj);
::
運算返回的仍是對象, 能夠進行鏈式調用:
$('.my-class')::find('p')::text("new text"); //至關於 $('.my-class').find('p').text("new text");
尾調用是函數式編程的概念, 指在函數最後調用另外一個函數。
//是尾調用 function a(){ return g(); } function b(p){ if(p>0){ return m(); } return n(); } function c(){ return c(); } //如下不是尾調用 function d(){ var b1 = g(); return b1; } function e(){ g(); } function f(){ return g() + 1; }
尾調用的一個顯著特色就是, 咱們能夠將函數尾部調用的函數放在該函數外面(後面), 而不改變程序實現結果。這樣能夠減小函數調用棧的開銷。
這樣的優化在 ES6 的嚴格模式中被強制實現了, 咱們須要作的僅僅是在使用時候利用好這個優化特性, 好比下面這個階乘函數:
function factorial(n){ if(n <= 1) return 1; return n * factorial(n - 1); } factorial(5); //120
這個函數計算 n 的階乘, 就要在內存保留 n 個函數調用記錄, 空間複雜度 O(n), 若是 n 很大可能會溢出。因此進行優化以下:
"use strict"; function factorial(n, result = 1){ if(n <= 1) return result; return factorial(n - 1, n * result); } factorial(5); //120
固然也可使用柯里化:
var factorial = (function factor(result, n){ if(n <= 1) return result; return factor(n * result, n - 1); }).bind(null, 1); factorial(5); //120
這個僅僅是一個提案: 爲了更好地進行版本控制, 在函數參數尾部加一個逗號, 表示該函很多天後會被修改, 便於版本控制器跟蹤。目前並未實現。
這裏僅僅討論 ES6 中的變量做用域。除了 let 和 const 定義的的變量具備塊級做用域之外, var
和 function
依舊遵照詞法做用域, 詞法做用域能夠參考博主的另外一篇文章javascript函數、做用域鏈與閉包
首先看一個例子:
var x = 1; function f(x, y=x){ console.log(y); } f(2); //2
這個例子輸出了2, 由於 y 在初始化的時候, 函數內部的 x 已經定義並完成賦值了, 因此, y = x
中的 x
已是函數的局部變量 x 了, 而不是全局的 x。固然, 若是局部 x 變量在 y 聲明以後聲明就沒問題了。
var x = 1; function f(y=x){ let x = 2 console.log(y); } f(); //1
那若是函數的默認參數是函數呢?燒腦的要來了:
var foo = "outer"; function f(x){ return foo; } function fun(foo, func = f){ console.log(func()); } fun("inner"); //"outer"
若是基礎好, 那就根本談不上不燒腦。由於, 函數中的做用域取決於函數定義的地方, 函數中的 this 取決於函數調用的方式。(敲黑板)
但若是這樣寫, 就是 inner 了, 由於func默認函數定義的時候 fun內的 foo 已經存在了。
var foo = "outer"; function fun(foo, func = function(x){ return foo; }){ console.log(func()); } fun("inner"); //"inner"
技巧: 利用默認值保證必需的參數被傳入, 而減小對參數存在性的驗證:
function throwErr(){ throw new Error("Missing Parameter"); } function fun(necessary = throwErr()){ //...若是參數necessary沒有收到就使用參數, 從而執行函數拋出錯誤 } //固然也能夠這樣表示一個參數是可選的 function fun(optional = undefined){ //... }
箭頭函數的做用域和定義時的上下文一致, 但能夠經過調用方式改變:
window && (window.name = "global") || (global.name = "global"); var o = { name: 'obj-o', foo: function (){ setTimeout(() => {console.log(this.name); }, 500); } } var p = { name: 'obj-p', foo: function (){ setTimeout(function(){console.log(this.name); }, 1000); } } o.foo(); //"obj-o" p.foo(); //"global" var temp = { name: 'obj-temp' } o.foo.bind(temp)(); //"obj-temp" o.foo.call(temp); //"obj-temp" o.foo.apply(temp); //"obj-temp" p.foo.bind(temp)(); //"global" p.foo.call(temp); //"global" p.foo.apply(temp); //"global"