摘要:否則調用時依然需要傳參報錯注意這里不能用觸發默認值這里我們還需要多帶帶討論一下默認參數對的影響很明顯,默認參數并不能加到中。關于作用域集中在函數擴展的最后討論。那如果函數的默認參數是函數呢燒腦的要來了如果基礎好那就根本談不上不燒腦。
參數默認值
ES5中設置默認值非常不方便, 我們這樣寫:
function fun(a){ a = a || 2; console.log(a); } fun(); //2 fun(0); //2 fun(1); //1
以上寫法, 如果傳入了參數, 但這個參數對應值的布爾型是 false, 就不起作用了。當然你也可以判斷 arguments.length 是否為0來避免這個問題, 但每個函數這樣寫就太啰嗦了, 尤其參數比較多的時候。在 ES6 中我們可以直接寫在參數表中, 如果實際調用傳遞了參數, 就用這個傳過來的參數, 否則用默認參數。像這樣:
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]
通過上面這個例子不難發現, 不僅可以用解構的方法設置初始值, 還可以進行參數傳遞。當然, 這里也可以是對象形式的解構賦值。如果傳入的參數無法解構, 就會報錯:
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 的影響:
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 中。
函數的 length 屬性
這個屬性ES6 之前就是存在的, 記得length表示預計傳入的形參個數, 也就是沒有默認值的形參個數:
(function(a){}).length; //1 (function(a = 5){}).length; //0 (function(a, b, c=5){}).length; //2 (function(...args){}).length; //0, rest參數也不計入 lengthrest 參數
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參數必須是函數的最后一個參數, 它的后面不能再定義參數, 否則會報錯。
rest參數不計入函數的 length 屬性中
建議:
所有配置項都應該集中在一個對象,放在最后一個參數,布爾值不可以直接作為參數。這樣方便調用者以任何順序傳遞參數。
不要在函數體內使用arguments變量,使用rest運算符(...)代替。因為rest運算符顯式表明你想要獲取參數,而且arguments是一個類似數組的對象,而rest運算符可以提供一個真正的數組。
使用默認值語法設置函數參數的默認值。
擴展運算符擴展運算符類似 rest運算符的逆運算, 用 ... 表示, 放在一個(類)數組前, 將該數組展開成獨立的元素序列:
console.log(1, ...[2, 3, 4], 5); //輸出1, 2, 3, 4, 5
擴展運算符的用處很多:
可以用于快速改變類數組對象為數組對象, 也是用于其他可遍歷對象:
[...document.querySelectorAll("li")]; //[
結合 rest 參數使函數事半功倍:
function push(arr, ...val){ return arr.push(...val); //調用函數時, 將數組變為序列 }
替代 apply 寫法
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"] [..."xuD83DuDE80y"].length; //3, 正確處理32位 unicode 字符
建議:使用擴展運算符(...)拷貝數組。
name 屬性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]
使用箭頭函數應注意以下幾點:
不可以將函數當做構造函數調用, 即不能使用 new 命令;
不可以在箭頭函數中使用 yield 返回值, 所以不能用過 Generator 函數;
函數體內不存在 arguments 參數;
函數體內部不構成獨立的作用域, 內部的 this 和定義時候的上下文一致; 但可以通過 call, apply, bind 改變函數中的 this。關于作用域, 集中在ES6函數擴展的最后討論。
舉幾個箭頭函數的實例:
實例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"
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97422.html
摘要:數組的擴展將類數組對象和可遍歷對象轉化為真正的數組。這兩個函數的參數都是回調函數。遍歷數組找到符合條件回調函數返回為的第一個值返回其值返回其下標。這三個方法用來遍歷數組返回一個遍歷器供使用其中是對鍵的遍歷是對值的遍歷是對鍵值對的遍歷。 數組的擴展 Array, from() 將類數組對象和可遍歷對象轉化為真正的數組。 var arrayLike = { 0 : a, 1 : b...
摘要:原來的也被修改了數組實例的喝方法,用于找出第一個符合條件的數組成員。它的參數是一個回調函數,所有數組成員依次執行該回調函數,直到找出第一個返回值為的成員,然后返回該成員。數組實例的方法使用給定值,填充一個數組。 1 Array.from() Array.from方法用于將兩類對象轉為真正的數組:類似數組的對象(array-like object)和可遍歷(iterable)的對象(包括...
摘要:返回空字符串,返回將一個具名函數賦值給一個變量,則和的屬性都返回這個具名函數原本的名字。不可以使用對象,該對象在函數體內不存在。等到運行結束,將結果返回到,的調用幀才會消失。 1 函數參數的默認值 ES6允許為函數的參數設置默認值,即直接寫在參數定義的后面: function log(x = message.,y = duration infomation.) { consol...
摘要:第二個參數指定修飾符,如果存在則使用指定的修飾符。屬性表示是否設置了修飾符屬性的屬性返回正則表達式的正文的屬性返回正則表達式的修飾符字符串必須轉義,才能作為正則模式。 1 RegExp構造函數 ES6 允許RegExp構造函數接受正則表達式作為參數。第二個參數指定修飾符,如果存在則使用指定的修飾符。 var regexp = new RegExp(/xyz/i, ig); consol...
摘要:屬性的簡潔表示法允許直接寫入變量和函數作為對象的屬性和方法。,中有返回一個數組,成員是參數對象自身的不含繼承的所有可遍歷屬性的鍵名。對象的擴展運算符目前,有一個提案,將解構賦值擴展運算符引入對象。 1 屬性的簡潔表示法 ES6允許直接寫入變量和函數作為對象的屬性和方法。 寫入屬性 var name = value; var obj = { name }; console.log...
摘要:正則表達式擴展構造函數支持傳入正則得到拷貝,同時可以用第二參修改修飾符引入新的修飾符中的修飾符有個加上的修飾符,一共個修飾符描述描述多行模式忽略大小寫模式全局匹配模式模式粘連模式模式為了兼容自己我們需要在一下情況使用該模式情況很明顯這個是不 正則表達式擴展 構造函數支持傳入正則得到拷貝,同時可以用第二參修改修飾符 var reg = /^abc/ig; var newReg_ig = ...
閱讀 2835·2023-04-25 17:59
閱讀 676·2023-04-25 15:05
閱讀 669·2021-11-25 09:43
閱讀 3026·2021-10-12 10:13
閱讀 3532·2021-09-27 13:59
閱讀 3577·2021-09-23 11:21
閱讀 3872·2021-09-08 09:35
閱讀 561·2019-08-29 17:12