hello大家好,又見面了。
假期轉(zhuǎn)瞬即逝,年后開工的第一天,早上是真的不想起床吖,為了不遲到閉著眼睛就穿衣服。
好啦好啦,步入正題啦,打起精神哦!
前言
函數(shù)是所有編程語言中重要的組成部分,在Es6出現(xiàn)之前 JavaScript的函數(shù)語法一直沒有太大的變化,從而遺留了很多問題和隱晦的做法,導(dǎo)致實(shí)現(xiàn)一些功能需要編寫很多代碼。
函數(shù)形參默認(rèn)值
JavaScript函數(shù)有一個(gè)特別的地方,就是無論在函數(shù)形參里定義了多少參數(shù),都可以傳入任意數(shù)量的參數(shù),但是有的情況下,我們的參數(shù)只是可填,這樣的話我們還在函數(shù)體呢寫一堆邏輯從而導(dǎo)致代碼冗余,還好Es6版本出現(xiàn)了函數(shù)默認(rèn)值。
我們用Es5和Es6代碼來比對(duì)一下
Es5處理默認(rèn)參數(shù)
function person(name, age) {
name = typeof(name) != "undefined" ? name : `蛙人${+ new Date()}`
age = typeof(age) != "undefined" ? age : 24
}
person()
上面example中是Es5這樣處理默認(rèn)參數(shù)值的,假如我們參數(shù)多的話,這么寫代碼的話會(huì)造成非常冗余的,于是Es6就出現(xiàn)函數(shù)參數(shù)默認(rèn)值。
Es6處理默認(rèn)參數(shù)
function person(name = "蛙人", age = 24) {
console.log(name, age)
}
person() // 蛙人 24
person("張三", 30) // 張三 30
person(null, null) // null null
上面example是Es6中處理的默認(rèn)參數(shù),可以看到代碼非常簡(jiǎn)化,上面代碼可以看到參數(shù)傳入了null,對(duì)于默認(rèn)參數(shù)null也是一個(gè)合法值,這種情況下只有函數(shù)參數(shù)為undefined時(shí)才會(huì)使用默認(rèn)值。
函數(shù)參數(shù)表達(dá)式
關(guān)于默認(rèn)參數(shù)值,最有趣的特性可能就是非原始值傳參了,也可以把默認(rèn)參數(shù)定義為函數(shù)or 變量。
function defaultName() {
return "蛙人"
}
function person(name = defaultName()) {
console.log(name)
}
person("張三") // 張三
person() // 蛙人
需要注意的是,默認(rèn)參數(shù)的表達(dá)式不是一創(chuàng)建函數(shù)就立刻執(zhí)行的,而是當(dāng)該函數(shù)
person被調(diào)用的時(shí)候并且沒有傳入?yún)?shù)才會(huì)執(zhí)行。
上面example中,如果不傳參才會(huì)調(diào)用默認(rèn)值的defaultName函數(shù)。
下面來看一下默認(rèn)參數(shù)傳入變量。
let defaultName = "蛙人"
function person(name = defaultName) {
console.log(name)
}
person("張三") // 張三
person() // 蛙人
function person(name, nickName = name) {
console.log(name, nickName)
}
person("張三") // 張三 張三
person("蛙人", "掘金蛙人") // 蛙人 掘金蛙人
上面example中,第一個(gè)代碼塊的里面我們都能看的懂,只不過把之前的函數(shù)換成了變量??吹诙€(gè)代碼塊里的代碼,我們把nickName參數(shù)默認(rèn)值設(shè)置成了第一個(gè)參數(shù)name參數(shù),這是在引用參數(shù)默認(rèn)值的時(shí)候,只允許引用前面參數(shù)的值,相當(dāng)于函數(shù)參數(shù)就是定義的變量,我們后面的變量可以訪問前面變量的,但是只限制在于當(dāng)前作用域中,這個(gè)函數(shù)形參里就是當(dāng)前作用域。我們?cè)倏匆粋€(gè)例子。
function person(name = nickName, nickName) {
console.log(name, nickName)
}
person("張三") // 張三 張三
上面example中,第一個(gè)參數(shù)默認(rèn)值是第二個(gè)參數(shù),這時(shí)運(yùn)行會(huì)拋出一個(gè)錯(cuò)誤,因?yàn)檫@時(shí)在定義第二個(gè)變量前去訪問,會(huì)造成暫時(shí)死區(qū),如果不明白暫時(shí)死區(qū)的可以去看我的上一篇文章。《一看就懂的var、let、const三者區(qū)別》
函數(shù)參數(shù)默認(rèn)值對(duì)arguments的影響
當(dāng)使用函數(shù)默認(rèn)參數(shù)時(shí),arguments對(duì)象的行為會(huì)與以往不同
Es5非嚴(yán)格模式下使用arguments
Es5非嚴(yán)格模式下,函數(shù)命名參數(shù)的變化會(huì)體現(xiàn)在arguments對(duì)象上,arguments獲取的是當(dāng)前函數(shù)的實(shí)參,arguments在非嚴(yán)格模式下它跟形參是映射關(guān)系,就是形參有變化arguments跟著變。
function test(a, b) {
console.log(a == arguments[0]) // true
console.log(b == arguments[1]) // true
a = "a"
b = "b"
console.log(arguments) // ["a", "b"]
}
test(1, 2)
上面example中,在非嚴(yán)格模式下,命名參數(shù)的變化會(huì)同步更新到arguments對(duì)象中。當(dāng)a參數(shù)的變化,會(huì)映射到arguments[0]對(duì)象上。
Es5嚴(yán)格模式下使用arguments
下面我們?cè)賮砜匆幌聡?yán)格模式下的arguments
function test(a, b) {
'use strict';
console.log(arguments) // [1, 2]
b = 10
console.log(arguments) // [1, 2]
}
test(1, 2)
上面example是嚴(yán)格模式下的,可以看出當(dāng)我們改變參數(shù)b時(shí),再次打印arguments對(duì)象,它還是初始化值。在嚴(yán)格模式下JavaScript中取消了arguments對(duì)象這個(gè)令人困惑的行為,無論參數(shù)如何變化,arguments對(duì)象不再隨之改變。
Es6中使用默認(rèn)參數(shù)值對(duì)arguments的影響
在Es6中,如果一個(gè)函數(shù)使用了默認(rèn)參數(shù)值,那么arguments對(duì)象的行為都將與JavaScript中的嚴(yán)格模式下保持一致。
function test(a, b = 2) {
a = 12
b = 10
console.log(arguments) // [1]
}
test(1)
上面example中,arguments對(duì)象打印出[1]是因?yàn)?code>arguments對(duì)象獲取的是實(shí)參,我們可以看到實(shí)參參數(shù)就傳了一個(gè)值,所以arguments對(duì)象就只有一個(gè)值。再看第二點(diǎn),a和b的參數(shù)都改變了值,但是arguments對(duì)象還是沒有改變,這就是上面說的,如果一個(gè)函數(shù)使用了默認(rèn)參數(shù)值,那么arguments對(duì)象的行為都將與JavaScript中的嚴(yán)格模式下保持一致。
處理無命名參數(shù)
在js中函數(shù)參數(shù)數(shù)量是任意的,當(dāng)傳入更少的數(shù)量,默認(rèn)參數(shù)的特性可以有效的簡(jiǎn)化函數(shù)聲明的代碼。當(dāng)傳入更多的數(shù)量,Es6也同樣提供了更好的方案。
Es5中獲取無命名參數(shù)
function test(a, b, c) {
console.log(arguments) // [1, 2, 3]
}
test(1, 2, 3)
上面example中,arguments對(duì)象雖然也可以實(shí)現(xiàn)獲取所有的參數(shù),但是呢如果我們想獲取第二個(gè)參數(shù)之后的所有參數(shù),那么還得循環(huán)去排除。
Es6中獲取無命名參數(shù)
function test(...parmas) {
console.log(params) // [1, 2, 3, 4]
}
test(1, 2, 3, 4)
function test(a, b, ...params) {
console.log(params)
}
test(1, 2, 3, 4)
上面example中,第一個(gè)代碼塊里實(shí)現(xiàn)了在Es6中獲取全部的參數(shù),可是還不滿足我們的需求。那么看第二個(gè)代碼塊里的代碼就實(shí)現(xiàn)了,我們獲取第二個(gè)參數(shù)后面所有的參數(shù)。
Es6獲取無命名參數(shù)弊端
首先,每一個(gè)函數(shù)只能聲明一個(gè)獲取不定參數(shù),而且只能放在函數(shù)的末尾,否則會(huì)報(bào)錯(cuò)。
function test(...params, a, b) {
}
test()
上面example中,會(huì)拋出錯(cuò)誤,聲明了不定參數(shù)數(shù)之后,就不能繼續(xù)在后面聲明參數(shù)。
還有一點(diǎn),不定參數(shù)不能定義在對(duì)象字面量的setter中,因?yàn)?code>setter函數(shù)只接收一個(gè)函數(shù),寫成不定參數(shù)之后就會(huì)是一個(gè)數(shù)組,這樣就會(huì)導(dǎo)致程序異常。
let obj = {
set name(...params) {
}
}
函數(shù)name屬性
在JavaScript中所有的函數(shù)都有一個(gè)name屬性,該屬性保存的是該函數(shù)名稱的字符串。沒有名稱的函數(shù)也仍然有name屬性,該name屬性值為空字符串。
function person() {}
let test = function() {}
console.log(person.name) // person
console.log(test.name) // test
上面example中,person函數(shù)name屬性值為"person",對(duì)應(yīng)著聲明時(shí)的函數(shù)名稱。匿名函數(shù)表達(dá)式test函數(shù)的name名稱,對(duì)應(yīng)著被賦值為匿名函數(shù)的變量。
name屬性的特殊情況
我原來以為每個(gè)函數(shù)的name名稱都是對(duì)應(yīng)著當(dāng)前的函數(shù)名,后來發(fā)現(xiàn)并不是這么回事。下面來看一下函數(shù)的特殊情況
var person = {
get getName() {
return "蛙人"
}
}
console.log(Object.getOwnPropertyDescriptor(person, 'getName').get.name) // get getName
function test() {}
console.log(test.bind().name) // bound test
上面example中,person.getName是一個(gè)取值函數(shù)getter,所以它的函數(shù)名稱get getName,如果是setter函數(shù)的話那么名稱會(huì)有帶有前綴set。通過bind創(chuàng)建的函數(shù),它的名稱帶有"bound"前綴。
箭頭函數(shù)
Es6中箭頭函數(shù)是其中最有趣的特性,箭頭函數(shù)是一種使用箭頭=>定義函數(shù)的新語法,但是它與傳統(tǒng)的JavaScript函數(shù)有些不同,具體看下面幾點(diǎn)。
- 沒有
this、super、arguments - 不能通過
new關(guān)鍵字調(diào)用 - 沒有原型
prototype - 不可以改變
this指向 - 不支持重復(fù)的命名參數(shù)
箭頭函數(shù)和傳統(tǒng)函數(shù)一樣都有一個(gè)name屬性,這一點(diǎn)是不變的。
箭頭函數(shù)語法
let person = () => "蛙人"
// 相當(dāng)于下代碼
function person() {
return "蛙人"
}
上面example中,當(dāng)箭頭函數(shù)右側(cè)的表達(dá)式求值后會(huì)立即返回。
箭頭函數(shù)參數(shù)
let getName = val => val
// 相當(dāng)于下代碼
function getName(val) {
return val
}
當(dāng)箭頭函數(shù)只有一個(gè)參數(shù)時(shí),就可以省略括號(hào),直接寫參數(shù)名。如果要傳入兩個(gè)或多個(gè)參數(shù),則就需要帶上括號(hào)??聪旅胬?/p>
let sum = (a, b) => a + b
// 相當(dāng)于下代碼
function sun(a, b) {
return a + b
}
如果你想返回一個(gè)對(duì)象字面量,可以這樣寫
let getObj = () => ({name: "蛙人", age: 24}) // {name: "蛙人", age: 24}
// 相當(dāng)于下代碼
function getObj() {
return {
name: "蛙人",
age: 24
}
}
箭頭函數(shù)沒有this
箭頭函數(shù)的this值,取決于函數(shù)外部非箭頭函數(shù)的this值,如果上一層還是箭頭函數(shù),那就繼續(xù)往上找,如果找不到那么this就是window對(duì)象
let person = {
test: () => {
console.log(this)
},
fn() {
return () => {
console.log(this)
}
}
}
person.test() // window
person.fn()() // person對(duì)象
上面example中,可以清楚的看到箭頭沒有this,那么它的this只會(huì)去找外層的非箭頭函數(shù)的函數(shù)。
箭頭函數(shù)沒有arguments對(duì)象
同樣箭頭函數(shù)也沒有arguments對(duì)象,但是如果它外層還有一層非箭頭函數(shù)的話,就會(huì)去找外層的函數(shù)的arguments對(duì)象, 如下
let test1 = () => console.log(arguments) // 執(zhí)行該函數(shù)會(huì)拋出錯(cuò)誤
function test2(a, b, c) {
return () => {
console.log(arguments) // [1, 2, 3]
}
}
test2(1, 2, 3)()
上面example中,可以清楚的看到當(dāng)前的箭頭函數(shù)沒有arguments對(duì)象,然而就去它的外層去找非箭頭函數(shù)的函數(shù)。注意:箭頭函數(shù)找arguments對(duì)象只會(huì)找外層非箭頭函數(shù)的函數(shù),如果外層是一個(gè)非箭頭函數(shù)的函數(shù)如果它也沒有arguments對(duì)象也會(huì)中斷返回,就不會(huì)在往外層去找了??聪旅胬?/p>
function test(a) {
return function() {
return () => {
console.log(arguments) // []
}
}
}
test(1)()()
上面example中可以看到,里面的箭頭函數(shù)往外層找非箭頭函數(shù)的函數(shù),然后不管外層這個(gè)函數(shù)有沒有arguments對(duì)象都會(huì)返回。只有它是非箭頭函數(shù)就可以,如果外層是箭頭函數(shù)還會(huì)繼續(xù)往外層找。
箭頭函數(shù)不能用new關(guān)鍵字聲明
let test = () => {}
new test() // 拋出錯(cuò)誤,找不到constructor對(duì)象
箭頭函數(shù)沒有原型prototype
切記,箭頭函數(shù)沒有原型,有可能面試官會(huì)問,JavaScript中所有的函數(shù)都有prototype屬性嗎
let test = () => {}
test.prototype // undefined
箭頭函數(shù)不能改變this指向
let person = {}
let test = () => console.log(this)
test.bind(person)()
test.call(person)
test.apply(person)
上面example中,改變this指向的方法都不會(huì)拋出錯(cuò)誤,但是都無效,都不能改變this指向。
箭頭函數(shù)不能重復(fù)命名參數(shù)
let sum = (a, a) => {} // 拋出錯(cuò)誤,參數(shù)不能重復(fù)