首頁>技術>

前言

 函式是所有程式語言中重要的組成部分,在Es6出現之前 JavaScript的函式語法一直沒有太大的變化,從而遺留了很多問題和隱晦的做法,導致實現一些功能需要編寫很多程式碼。

函式形參預設值

 JavaScript函式有一個特別的地方,就是無論在函式形參裡定義了多少引數,都可以傳入任意數量的引數,但是有的情況下,我們的引數只是可填,這樣的話我們還在函式體呢寫一堆邏輯從而導致程式碼冗餘,還好Es6版本出現了函式預設值。

我們用Es5和Es6程式碼來比對一下

Es5處理預設引數
function person(name, age) {    name = typeof(name) != "undefined" ? name : `蛙人${+ new Date()}`    age = typeof(age) != "undefined" ? age : 24    }person()

上面example中是Es5這樣處理預設引數值的,假如我們引數多的話,這麼寫程式碼的話會造成非常冗餘的,於是Es6就出現函式引數預設值。<br>

Es6處理預設引數
function person(name = "王二", age = 24) {    console.log(name, age)}person()  // 王二 24person("張三", 30) // 張三 30person(null, null) // null null

上面example是Es6中處理的預設引數,可以看到程式碼非常簡化,上面程式碼可以看到引數傳入了null,對於預設引數null也是一個合法值,這種情況下只有函式引數為undefined時才會使用預設值。

函式引數表示式

 關於預設引數值,最有趣的特性可能就是非原始值傳參了,也可以把預設引數定義為函式or 變數。

function defaultName() {    return "王二"}function person(name = defaultName()) {    console.log(name)}person("張三") // 張三person() // 王二

需要注意的是,預設引數的表示式不是一建立函式就立刻執行的,而是當該函式person被呼叫的時候並且沒有傳入引數才會執行。

上面example中,如果不傳參才會呼叫預設值的defaultName函式。

下面來看一下預設引數傳入變數。

let defaultName = "王二"function person(name = defaultName) {     console.log(name)}person("張三") // 張三person() // 王二function person(name, nickName = name) {     console.log(name, nickName)}person("張三") // 張三 張三person("王二", "李四") // 王二 李四

上面example中,第一個程式碼塊的裡面我們都能看的懂,只不過把之前的函式換成了變數。看第二個程式碼塊裡的程式碼,我們把nickName引數預設值設定成了第一個引數name引數,這是在引用引數預設值的時候,只允許引用前面引數的值,相當於函式引數就是定義的變數,我們後面的變數可以訪問前面變數的,但是隻限制在於當前作用域中,這個函式形參裡就是當前作用域。我們再看一個例子。

function person(name = nickName, nickName) {     console.log(name, nickName)}person("張三") // 張三 張三

上面example中,第一個引數預設值是第二個引數,這時執行會丟擲一個錯誤,因為這時在定義第二個變數前去訪問,會造成暫時死區,如果不明白暫時死區的可以去看我的上一篇文章。《一看就懂的var、let、const三者區別》

函式引數預設值對arguments的影響

當使用函式預設引數時,arguments物件的行為會與以往不同

Es5非嚴格模式下使用arguments

 Es5非嚴格模式下,函式命名引數的變化會體現在arguments物件上,arguments獲取的是當前函式的實參,arguments在非嚴格模式下它跟形參是對映關係,就是形參有變化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中,在非嚴格模式下,命名引數的變化會同步更新到arguments物件中。當a引數的變化,會對映到arguments[0]物件上。

Es5嚴格模式下使用arguments

下面我們再來看一下嚴格模式下的arguments

function test(a, b) {    'use strict';    console.log(arguments)  // [1, 2]    b = 10    console.log(arguments) // [1, 2]}test(1, 2)

上面example是嚴格模式下的,可以看出當我們改變引數b時,再次列印arguments物件,它還是初始化值。在嚴格模式下JavaScript中取消了arguments物件這個令人困惑的行為,無論引數如何變化,arguments物件不再隨之改變。

Es6中使用預設引數值對arguments的影響

在Es6中,如果一個函式使用了預設引數值,那麼arguments物件的行為都將與JavaScript中的嚴格模式下保持一致。

function test(a, b = 2) {    a = 12    b = 10    console.log(arguments) // [1]}test(1)

上面example中,arguments物件打印出[1]是因為arguments物件獲取的是實參,我們可以看到實參引數就傳了一個值,所以arguments物件就只有一個值。再看第二點,a和b的引數都改變了值,但是arguments物件還是沒有改變,這就是上面說的,如果一個函式使用了預設引數值,那麼arguments物件的行為都將與JavaScript中的嚴格模式下保持一致。

處理無命名引數

 在js中函式引數數量是任意的,當傳入更少的數量,預設引數的特性可以有效的簡化函式宣告的程式碼。當傳入更多的數量,Es6也同樣提供了更好的方案。

Es5中獲取無命名引數
function test(a, b, c) {    console.log(arguments) // [1, 2, 3]}test(1, 2, 3)

上面example中,arguments物件雖然也可以實現獲取所有的引數,但是呢如果我們想獲取第二個引數之後的所有引數,那麼還得迴圈去排除。

Es6中獲取無命名引數
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中,第一個程式碼塊裡實現了在Es6中獲取全部的引數,可是還不滿足我們的需求。那麼看第二個程式碼塊裡的程式碼就實現了,我們獲取第二個引數後面所有的引數。

Es6獲取無命名引數弊端

  首先,每一個函式只能宣告一個獲取不定引數,而且只能放在函式的末尾,否則會報錯。

function test(...params, a, b) {	}test()

上面example中,會丟擲錯誤,聲明瞭不定引數數之後,就不能繼續在後面宣告引數。<br> 還有一點,不定引數不能定義在物件字面量的setter中,因為setter函式只接收一個函式,寫成不定引數之後就會是一個數組,這樣就會導致程式異常。

let obj = {  set name(...params) {  }}
函式name屬性

在JavaScript中所有的函式都有一個name屬性,該屬性儲存的是該函式名稱的字串。沒有名稱的函式也仍然有name屬性,該name屬性值為空字串。

function person() {}let test = function() {}console.log(person.name) // personconsole.log(test.name) // test

上面example中,person函式name屬性值為"person",對應著宣告時的函式名稱。匿名函式表示式test函式的name名稱,對應著被賦值為匿名函式的變數。

name屬性的特殊情況

 我原來以為每個函式的name名稱都是對應著當前的函式名,後來發現並不是這麼回事。下面來看一下函式的特殊情況

var person = {    get getName() {        return "蛙人"    }}console.log(Object.getOwnPropertyDescriptor(person, 'getName').get.name)  // get getNamefunction test() {}console.log(test.bind().name) // bound test

上面example中,person.getName是一個取值函式getter,所以它的函式名稱get getName,如果是setter函式的話那麼名稱會有帶有字首set。透過bind建立的函式,它的名稱帶有"bound"字首。

箭頭函式

Es6中箭頭函式是其中最有趣的特性,箭頭函式是一種使用箭頭=>定義函式的新語法,但是它與傳統的JavaScript函式有些不同,具體看下面幾點。

沒有this、super、arguments不能透過new關鍵字呼叫沒有原型prototype不可以改變this指向不支援重複的命名引數

箭頭函式和傳統函式一樣都有一個name屬性,這一點是不變的。

箭頭函式語法
let person = () => "王二"// 相當於下程式碼function person() {	return "王二"}

上面example中,當箭頭函式右側的表示式求值後會立即返回。

箭頭函式引數
let getName = val => val// 相當於下程式碼function getName(val) {    return val}

當箭頭函式只有一個引數時,就可以省略括號,直接寫引數名。如果要傳入兩個或多個引數,則就需要帶上括號。看下面例子

let sum = (a, b) => a + b// 相當於下程式碼function sun(a, b) {    return a + b}

如果你想返回一個物件字面量,可以這樣寫

let getObj = () => ({name: "王二", age: 24}) // {name: "王二", age: 24}// 相當於下程式碼function getObj() {    return {    	name: "王二",        age: 24    }}
箭頭函式沒有this

箭頭函式的this值,取決於函式外部非箭頭函式的this值,如果上一層還是箭頭函式,那就繼續往上找,如果找不到那麼this就是window物件

let person = {    test: () => {        console.log(this)    },    fn() {        return () => {            console.log(this)        }    }}person.test()  // windowperson.fn()()  // person物件

上面example中,可以清楚的看到箭頭沒有this,那麼它的this只會去找外層的非箭頭函式的函式。

箭頭函式沒有arguments物件

同樣箭頭函式也沒有arguments物件,但是如果它外層還有一層非箭頭函式的話,就會去找外層的函式的arguments物件, 如下

let test1 = () => console.log(arguments)  // 執行該函式會丟擲錯誤function test2(a, b, c) {    return () => {        console.log(arguments) // [1, 2, 3]    }}test2(1, 2, 3)()

上面example中,可以清楚的看到當前的箭頭函式沒有arguments物件,然而就去它的外層去找非箭頭函式的函式。注意:箭頭函式找arguments物件只會找外層非箭頭函式的函式,如果外層是一個非箭頭函式的函式如果它也沒有arguments物件也會中斷返回,就不會在往外層去找了。看下面例子

function test(a) {    return function() {        return () => {            console.log(arguments) // []        }    }}test(1)()()

上面example中可以看到,裡面的箭頭函式往外層找非箭頭函式的函式,然後不管外層這個函式有沒有arguments物件都會返回。只有它是非箭頭函式就可以,如果外層是箭頭函式還會繼續往外層找。

箭頭函式不能用new關鍵字宣告
let test = () => {}new test() // 丟擲錯誤,找不到constructor物件
箭頭函式沒有原型prototype

切記,箭頭函式沒有原型,有可能面試官會問,JavaScript中所有的函式都有prototype屬性嗎

let test = () => {}test.prototype // undefined
箭頭函式不能改變this指向
let person = {}let test = () => console.log(this)test.bind(person)()test.call(person)test.apply(person)

上面example中,改變this指向的方法都不會丟擲錯誤,但是都無效,都不能改變this指向。

18
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Prometheus指標收集方式