ES6
在函數方面的擴展比較豐富也很實用,本篇歸納了這中的精華知識。segmentfault
箭頭函數是ES6
中定義函數的新形式。
新形式不只簡化了定義方式,更爲函數自己減重(其this
, argumnets
等與以前不一樣)。數組
let fn = () => { console.log('fn'); }; fn(); // 'fn' 若是隻有一個參數,能夠省略括號。 (n => { console.log(n); })(1); // 1 若是不帶 {} ,意味着直接返回 => 指向的目標。 console.log( (n => 2)(1) ); // 2 注意,指向的目標只能是單體,若是爲表達式須要用 () 包裹造成單體。 console.log( (n => (n + 1))(1) ); // 2
箭頭函數沒有本身的this
,其使用的this
是引用外層的(相似閉包)。
所以其裏面的this
是固定的,在定義的那一刻起就已經肯定,不會再變。閉包
非嚴格模式下。 --- 以前的函數是執行時肯定 this 。 window.id = 0; let obj = { id: 1 }; let fn = function () { console.log(this.id); }; fn(); // 0,引用的是 window 。 obj.fn = fn; obj.fn(); // 1,引用的是 obj 。 --- 箭頭函數是定義時肯定 this 。 window.id = 0; let obj = { id: 1 }; let fn = () => { console.log(this.id); }; fn(); // 0,引用的是 window 。 obj.fn = fn; obj.fn(); // 0,引用的是 window 。
再利用多層箭頭函數來講明。
多層箭頭函數,this
的尋找途徑是一層層向上查找的,相似做用域鏈查找。
因此多層箭頭函數在初次獲取到this
時,所有函數的this
便都肯定了。app
foo.call({id: 1})()(); // id: 1 function foo() { return () => { return () => { console.log('id:', this.id); }; }; } --- 等價於 function foo() { let _this = this; return function() { return function() { console.log('id:', _this.id); } } }
由於沒有自身的this
。
因此沒法成爲構造函數,不能使用new
操做符。
因此不能用call
, apply
和bind
這些方法改變this
的指向。async
let Person = () => {}; let p = new Person(); // 報錯,Person is not a constructor。 window.id = '000'; let fn = () => { console.log(this.id) }; let fn1 = fn.bind({ id: '111' }); fn1(); // '000'
函數體內沒有arguments
對象,可使用rest
參數代替。
不能使用yield
命令,所以箭頭函數不能用做Generator
函數(可使用async
函數)。函數
let fn = (...args) => { console.log(args); // [1, 2] console.log(arguments); // 報錯,arguments is not defined。 }; fn(1, 2);
能夠爲參數設定默認值。
當沒有傳遞該參數或值爲undefined
時,默認值將被使用。
借用此方式,能夠簡化函數體,並使參數的性質、類型等更加清晰。ui
--- ES6 以前 function fn(id, conf) { id || requiredParam(); conf = conf || {}; conf.name = conf.name || ''; conf.ago = conf.ago || 0; console.log(id, conf); } --- ES6 以後 function fn( id = requiredParam(), conf = { name: '', ago: 0 } ) { console.log(id, conf); } function requiredParam() { throw Error('Missing parameter.'); }
結合解構賦值,默認值設定的功能會更爲強大。
關於解構賦值,可參考此連接。
爲了直觀的顯示它的優點,咱們將最終的結果分紅三步。this
1.使用解構賦值,快速聲明變量,並賦予相應的屬性值。 fn({ id: '0003' }); // 二者都打印出:'0003' undefined --- ES6 以前 function fn(conf) { let id = conf.id; let name = conf.name; console.log(id, name); } --- ES6 以後 function fn({id, name}) { console.log(id, name); } 2.緊接着,爲解構中的變量設定默認值。 fn({ id: '0003' }); // 二者都打印出:'0003' 'Unnamed' --- ES6 以前 function fn(conf) { let id = conf.id || '0000'; let name = conf.name || 'Unnamed'; console.log(id, name); } --- ES6 以後 function fn({ id = '0000', name = 'Unnamed' }) { console.log(id, name); } 3.最後,再爲此參數設定默認值。 fn(); // 二者都打印出:'0000' 'Unnamed' --- ES6 以前 function fn(conf) { conf = conf || { id: '0000', name: 'Unnamed' }; let id = conf.id; let name = conf.name; console.log(id, name); } --- ES6 以後 function fn({ id = '0000', name = 'Unnamed' } = {}) { console.log(id, name); }
再思考一個問題:是在解構中仍是在參數默認值中設定屬性的值?設計
function fn1(x = {}, {a = 1, b = 2} = x) { console.log(a, b, x) } function fn2(x = {a: 1, b: 2}, {a, b} = x) { console.log(a, b, x) } 這兩方法的區別在於:變量a, b的默認值的設置地點。 若是要優先確保解析後的變量有默認值,第一種方式更爲有效。 fn1(); // 1 2 {} fn2(); // 1 2 {a:1, b:2} fn1({}); // 1 2 {} fn2({}); // undefined undefined {} fn1({ a: 0 }); // 0 2 {a:0} fn2({ a: 0 }); // 0 undefined {a:0}
將擴展運算符做用於參數,即爲rest
參數。
它會將全部相應的傳入參數合併成一個數組,賦值給rest
參數。 rest
參數只能是最後一個參數,沒有正則中所謂的貪婪性,不然會報錯。rest
打印出:'0001' ['m1','m2']。 fn('0001', 'm1', 'm2'); function fn(groupId, ...members) { console.log(groupId, members); }
若是函數參數使用了默認值、解構賦值或擴展運算符,就產生了參數做用域。
執行函數體時,會先默認聲明參數變量。
若是存在參數做用域,會先執行它,再到函數體做用域中。
初始化結束後,參數做用域消失,以後函數體會默認聲明同名變量指向相應的參數變量。
由於做用域的存在,參數是惰性(調用時)求值的。
let n = 0; fn(); // 1 n = 1; fn(); // 2 function fn(num = (n + 1)) { console.log(num); }
由於默認聲明原則,在函數體中聲明同名參數至關二次聲明。
使用let
, const
至關重複聲明,會報錯。
使用var
會解綁函數體與參數做用域的關聯,變量便成了純粹的函數體變量。
--- 普通 let x = 0; fn(1); // 2 function fn(x, y = () => { console.log(x) }) { x = 2; y(); } --- 解綁 let x = 0; fn(1); // 1 function fn(x, y = () => { console.log(x) }) { var x = 2; y(); }
若是存在參數做用域,就不能在函數體中顯式的設定嚴格模式,不然報錯。
由於函數內部的嚴格模式,應該同時做用於函數體和參數做用域。
可是隻有進入函數體,才能知道是否有顯式地聲明,而參數體卻先於函數體執行。
不過能夠變通的,將此函數置於一個處在嚴格模式的環境中。
報錯:Illegal 'use strict' directive ... function fn(n = 0) { "use strict"; }
不一樣形式的函數,其name
屬性值構建的方式也不相同,下面是我的總結的八種方式。
1.聲明式,直接爲函數名。 console.log(fn.name); // 'fn' function fn() {} 2.命名函數表達式,直接爲函數名。 let fn1 = function fn() {}; console.log(fn1.name); // 'fn' 3.表達式,爲第一次賦值的變量/屬性。 let fn = function() {}; console.log(fn.name); // 'fn' let fn1 = fn(); console.log(fn.name); // 'fn' let obj = { fn: function() {} }; console.log(fn.name); // 'fn' 4.沒有賦值的匿名錶達式,爲空。 console.log( (function() {}).name ); // '' 5.經過構造函數生成的,爲 anonymous 。 console.log( (new Function()).name ); // 'anonymous' 6.經過 bind() 生成的,name 屬性值會加上 bound 前綴。 console.log( (function() {}).bind({}).name ); // 'bound ' console.log( (function fn() {}).bind({}).name ); // 'bound fn' 7.若是對象的方法名爲 Symbol 值,name 屬性返回的是此 Symbol 的描述。 let s1 = Symbol(); let s2 = Symbol('s2'); console.log( ({ [s1]() {} })[s1].name ); // '' console.log( ({ [s2]() {} })[s2].name ); // [s2] 8.getter/setter 函數的 name 屬性,在其描述對象的 get/set 屬性上,爲 get/set 函數名。 let obj = { get name() {} }; Object.getOwnPropertyDescriptor(obj, 'name').get.name; // 'get name'
其本質含義是該函數預期傳入的參數個數。
若是參數有默認值或爲rest
參數,則它以及它以後的參數都不會被計算在內。
基於這點,在參數設計上,通常把能夠省略或有默認值的參數設置爲尾參數。
console.log( (function(...args) {}).length ); // 0 console.log( (function(a, {b}, c = 5, d) {}).length ); // 2
類數組對象arguments
保存的僅僅存儲調用方法時傳進來的參數。
這意味着,使用默認值的參數、解構參數或rest
參數等都不在其中。
(function (name = 'Wmaker') { console.log(name, arguments.length); })(); // 'Wmaker' 0 (function ({a, b}) { console.log(a, b, arguments.length); })({ a: 1, b: 2 }); // 1 2 1 (function (...arr) { console.log(arr, arguments.length); })(1, 2, 3); // [1, 2, 3] 3