一、函數參數的默認值
ES6 允許為函數的參數設置默認值,即直接寫在參數定義的后面。
1、基本用法
默認值的生效條件
不傳參數,或者明確的傳遞 undefined 作為參數,只有這兩種情況下,默認值才會生效。
注意:null 就是 null,不會使用默認值。
// ES6 之前的默認值實現方式
const log = (x, y) => {// typeof類型檢測:返回表示當前數據類型的字符串if (typeof y === 'undefined') {y = 'world'}console.log(x, y)
}log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
// ES6 默認值實現方式
function log(x, y = 'World') {console.log(x, y)
}log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
默認值的一些規則
參數變量是默認聲明的,所以不能用let或const再次聲明。
function foo(x = 5) {let x = 1 // errorconst x = 2 // error
}
使用參數默認值時,函數不能有同名參數。
// 不報錯
function foo(x, x, y) {// ...
}// 報錯 SyntaxError: Duplicate parameter name not allowed in this context
function foo(x, x, y = 1) {// ...
}
參數默認值是惰性求值的。參數默認值不是傳值的,而是每次都重新計算默認值表達式的值。
let x = 99
function foo(p = x + 1) {console.log(p)
}foo() // 100x = 100
foo() // 101
2、與解構賦值默認值結合使用
函數參數默認值 可以與 解構賦值的默認值,結合起來使用。通過給函數參數設置默認值,可以避免在沒有提供參數時出現錯誤。
// 只使用對象的解構賦值默認值
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()調用時沒提供參數,變量 x 和 y 就不會生成,從而報錯
foo() // TypeError: Cannot read property 'x' of undefined// -------------------------------------------// 使用對象的解構賦值默認值 + 函數參數的默認值
function foo({ x, y = 5 } = {}) {console.log(x, y)
}foo() // undefined 5
// 只使用對象的解構賦值默認值
function fetch(url, { body = '', method = 'GET', headers = {} }) {console.log(method)
}fetch('http://example.com', {}) // 'GET'
fetch('http://example.com') // 報錯// --------------------------------------------------// 使用對象的解構賦值默認值 + 函數參數的默認值
function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {console.log(method)
}fetch('http://example.com') // 'GET'
注意,函數參數的默認值生效以后,參數解構賦值依然會進行。
// 參數默認值 { a: 'hello' } 生效;進行解構賦值,從而觸發參數變量 b 的默認值生效。
function f({ a, b = 'world' } = { a: 'hello' }) {console.log(b)
}f() // world// 解構賦值的默認值只在屬性值為 undefined 時才會生效
function f({ a, b = 'world' } = { b: 'hello' }) {console.log(b)
}f() // hello
3、參數默認值的位置
通常情況下,定義了默認值的參數,應該是函數的尾參數。因為這樣比較容易看出來,到底省略了哪些參數。如果非尾部的參數設置默認值,實際上這個參數是沒法省略的。
// 例一
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]
上面代碼中,有默認值的參數都不是尾參數。這時,無法只省略該參數,而不省略它后面的參數,除非顯式輸入 undefined。
如果傳入 undefined,將觸發該參數等于默認值,null 則沒有這個效果。
function foo(x = 5, y = 6) {console.log(x, y)
}foo(undefined, null) // 5 null
4、函數的 length 屬性
函數的 length 屬性,等于該函數預期傳入的參數個數。
當函數指定默認值后,length 屬性將失真。將返回沒有指定默認值的參數個數,如果設置了默認值的參數不是尾參數,那么 length 屬性也不再計入后面的參數了。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2(function(...args) {}).length // 0(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
5、應用
利用參數默認值,可以指定某一個參數不得省略,如果省略就拋出一個錯誤。
function throwIfMissing() {throw new Error('Missing parameter')
}function foo(mustBeProvided = throwIfMissing()) {return mustBeProvided
}foo() // Error: Missing parameter
上面代碼的 foo 函數,如果調用的時候沒有參數,就會調用默認值 throwIfMissing 函數,從而拋出一個錯誤。
從上面代碼還可以看到,參數 mustBeProvided 的默認值等于 throwIfMissing 函數的運行結果(注意函數名 throwIfMissing 之后有一對圓括號),這表明參數的默認值不是在定義時執行,而是在運行時執行。如果參數已經賦值,默認值中的函數就不會運行。
另外,可以將參數默認值設為 undefined,表明這個參數是可以省略的。
function foo(optional = undefined) { ··· }
二、reset 參數(…不定參數)
ES6 引入 rest 參數(形式為…變量名),用于獲取函數的多余參數,這樣就不需要使用 arguments 對象了。rest 參數搭配的變量是一個數組,該變量將多余的參數放入數組中。
function add(...values) {let sum = 0for (var val of values) {sum += val}return sum
}add(2, 5, 3) // 10
// arguments 變量的寫法
function sortNumbers() {// arguments 為類數組對象,需先使用 Array.from 轉換為數組return Array.from(arguments).sort()
}// rest 參數的寫法 (reset 參數為真正的數組)
const sortNumbers = (...numbers) => numbers.sort()
注意,rest 參數之后不能再有其他參數(即只能是最后一個參數),否則會報錯。
// 報錯
function f(a, ...b, c) {// ...
}
函數的 length 屬性,不包括 rest 參數。
(function(a) {}).length // 1
(function(...a) {}).length // 0
(function(a, ...b) {}).length // 1
三、箭頭函數
ES6 規定了可以使用 “箭頭” => 來定義一個函數,語法更加簡潔。它沒有自己的 this、arguments、super 或 new.target,箭頭函數表達式更適用于那些本來需要匿名函數的地方,但它不能用作構造函數。
1、基本使用
普通函數
function 函數名() {}const 變量名 = function () {}
箭頭函數
(參數) => {函數體}const 變量名 = () => {}
// 基本語法
const add = (x, y) => {return x + y
}// 有且只有一個參數,()可以省略
const add = x => {return x + 1
}// 有且只有一條語句,且為 returen 語句,{} 和 return 可以省略
const add = (x, y) => x + y// return 為對象時,對象外需要加 ()
const add = (x, y) => {return {value: x + y}
}
const add = (x, y) => ({ value: x + y })
2、函數 this 指向
es5 中的 this 指向
函數 this 的取值,是在函數執行的過程中確定的,不是在函數定義時確定的。
(1) 作為普通函數被調用,this 指向 window
(2) 作為對象方法被調用時,this 指向當前對象
(3) 在構造函數中(es5, es6的class方法),this 指向通過構造函數創建的實例對象
(5) 定時器中函數的 this 指向 window,定時器中函數相當于普通函數被調用(setTimeout | setInterval)
es6 中箭頭函數的 this 指向
箭頭函數中的 this 是在定義的時候綁定的,this 取上級作用域的 this,箭頭函數本身不會決定 this 的值。
call、 apply 、 bind
fn.call(this, …params) 和 fn.apply(this, [params]) 都是用來改變函數的this指向
區別是傳參不同,call()接受的是列表,apply()接受的是數組
fn.bind(this, params) 方法也是用來改變函數this指向,但是不會立即執行,而是返回一個新函數
3、不適用箭頭函數的場景
作為構造函數
因為箭頭函數沒有 this,而構造函數的核心就是 this。
需要 this 指向調用對象的時候
因為箭頭函數沒有 this,所以如果箭頭函數中出現了 this,那么這個 this 就是外層的!
給事件綁定方法時,比如說通過 addEventListener 給某個事件綁定方法,如果使用箭頭函數,此時 this,會指向父級的this window
在定義某個對象的方法時,不可以使用箭頭函數
在 vue 的 methods 中的方法,也不可以使用箭頭函數,會使 this 指向的不是當前的vm實例,發生錯誤
需要使用 arguments 的時候
箭頭函數沒有 arguments。(這個問題有替代解決方案:不定參數)
沒有原型
由于箭頭函數不能用作構造函數,它們也沒有自己的原型。因此,不能使用 prototype 屬性來添加新方法。
不可以使用 yield 命令,因此箭頭函數不能用作 Generator 函數
四、函數參數的尾逗號
ES2017 允許函數的最后一個參數有尾逗號(trailing comma)。
此前,函數定義和調用時,都不允許最后一個參數后面出現逗號。
function clownsEverywhere(param1,param2
) { /* ... */ }clownsEverywhere('foo','bar'
)
上面代碼中,如果在 param2 或 bar 后面加一個逗號,就會報錯。
如果像上面這樣,將參數寫成多行(即每個參數占據一行),以后修改代碼的時候,想為函數 clownsEverywhere 添加第三個參數,或者調整參數的次序,就勢必要在原來最后一個參數后面添加一個逗號。這對于版本管理系統來說,就會顯示添加逗號的那一行也發生了變動。這看上去有點冗余,因此新的語法允許定義和調用時,尾部直接有一個逗號。
function clownsEverywhere(param1,param2,
) { /* ... */ }clownsEverywhere('foo','bar',
)
這樣的規定也使得,函數參數與數組和對象的尾逗號規則,保持一致了。
五、catch 命令的參數省略
JavaScript 語言的 try…catch 結構,以前明確要求 catch 命令后面必須跟參數,接受 try 代碼塊拋出的錯誤對象。
try {// ...
} catch (err) {// 處理錯誤
}
上面代碼中,catch命令后面帶有參數err。
很多時候,catch代碼塊可能用不到這個參數。但是,為了保證語法正確,還是必須寫。ES2019 做出了改變,允許catch語句省略參數。
try {// ...
} catch {// ...
}