《深刻理解ES6》筆記——函數(3)

做爲前端切圖仔,愈加以爲本身離不開函數了。前端

說到JavaScript函數,腦子裏都是匿名函數、普通函數、閉包函數、構造函數......而後還能說出一大堆函數的概念。若是你達到這個水平,那麼函數對你來講沒有難度,是一個前端老鳥了。react

當我閉上眼睛,不看鍵盤,手指在鍵盤上敲擊出一個排序函數的時候,我在想,仍是回顧一下函數的基本知識才有意思。segmentfault

函數的默認參數

在ES5中,咱們給函數傳參數,而後在函數體內設置默認值,以下面這種方式。數組

function a(num, callback) {
  num = num || 6
  callback = callback || function (data) {console.log('ES5: ', data)}
  callback(num * num)
}
a() //ES5: 36,不傳參輸出默認值

//你還能夠這樣使用callback
a(10, function(data) {
  console.log(data * 10) // 1000, 傳參輸出新數值
})

而在ES6中,咱們使用新的默認值寫法。閉包

function a(num = 6, callback = function (data) {console.log('ES6: ', data)}) {
  callback(num * num)
}

a() //ES6: 36, 不傳參輸出默認值

a(10, function(data) {
  console.log(data * 10) // 1000,傳參輸出新數值
})

使用ES6的默認值寫法可讓函數體內部的代碼更加簡潔優雅app

默認值對arguments對象的影響函數

咱們先要了解arguments對象是什麼?準確一點來講它是一個類數組對象,它存在函數內部,它將當前函數的全部參數組成了一個類數組對象。性能

function a(num, b){
  console.log(arguments) // {"0": 6, "1": 10}
  console.log(arguments.length) // 2
}

a(6, 10)

上面的輸出結果看起來很正常,那麼,若是咱們加上參數默認值會怎樣呢?學習

function a(num = 1, b = 1){
console.log(arguments)
}
a() // {} 默認值不能被arguments識別。
a(6, 10) // {"0":6,"1":10}優化

下面咱們看一下修改參數默認值對arguments的影響。

一、在ES5的非嚴格模式下,一開始輸入的參數是1,那麼能夠獲取到arguments[0](表示第一個參數)全等於num,修改num = 2以後,arguments[0]也能更新到2。

function a(num){
  console.log(num === arguments[0]) //true
  num = 2 //修改參數默認值
  console.log(num === arguments[0]) //true
}
a(1)

二、在ES5的非嚴格模式下,arguments就不能在函數內修改默認值後跟隨着跟新了。

"use strict"; //嚴格模式   
function a(num) {
  console.log(num === arguments[0]); // true
  num = 2;
  console.log(num === arguments[0]); // false
}
a(1);

在ES6環境下,默認值對arguments的影響和ES5嚴格模式是一樣的標準。

默認參數表達式

參數不只能夠設置默認值爲字符串,數字,數組或者對象,還能夠是一個函數。

function add() {
  return 10
}
function a(num = add()){
  console.log(num)
}
a() // 10

默認參數的臨時死區

第一章咱們提到了let和const什麼變量的臨時死區(TDZ),默認參數既然是參數,那麼也一樣有臨時死區,函數的做用域是獨立的,a函數不能共享b函數的做用域參數。

//這是個默認參數臨時死區的例子,當初始化a時,b尚未聲明,因此第一個參數對b來講就是臨時死區。
function add(a = b, b){
  console.log(a + b)
}
add(undefined, 2) // b is not define

無命名參數

上面說的參數都是命名參數,而無命名參數也是函數傳參時常常用到的。當傳入的參數是一個對象,不是一個具體的參數名,則是無命名參數。

function add(object){
  console.log(object.a + object.b)
}
let obj = {
  a: 1,
  b: 2
}
add(obj) // 3

不定參數的使用:使用...(展開運算符)的參數就是不定參數,它表示一個數組。

function add(...arr){
  console.log(a + b)
}
let a = 1,b = 2
add(a, b) // 3

不定參數的使用限制:必須放在全部參數的末尾,不能用於對象字面量setter中。

//錯誤的寫法1
function add(...arr, c){
  console.log(a + b)
}
let a = 1,b = 2,c = 3
add(a, b, c)

//錯誤的寫法2
let obj = {
  set add(...arr) {
  
  }
}

ES6中的構造函數Function新增了支持默認參數和不定參數。

展開運算符(...)

展開運算符的做用是解構數組,而後將每一個數組元素做爲函數參數。

有了展開運算符,咱們操做數組的時候,就能夠再也不使用apply來指定上下文環境了。

//ES5的寫法
let arr = [10, 20, 50, 40, 30]
let a = Math.max.apply(null, arr)
console.log(a) // 50

//ES6的寫法
let arr = [10, 20, 50, 40, 30]
let a = Math.max(...arr)
console.log(a) // 50

塊級函數

嚴格模式下:在ES6中,你能夠在塊級做用域內聲明函數,該函數的做用域只限於當前塊,不能在塊的外部訪問。

"use strict";
if(true) {
  const a = function(){
  
  }
}

非嚴格模式:即便在ES6中,非嚴格模式下的塊級函數,他的做用域也會被提高到父級函數的頂部。因此你們寫代碼儘可能使用嚴格模式,避免這些奇葩狀況。

箭頭函數(=>)

若是看到你這裏,你發現你尚未在項目中使用過箭頭函數,不要緊,你並不low,而是學習不夠努力。

const arr = [5, 10]
const s = arr.reduce((sum, item) => sum + item)
console.log(s) // 15

箭頭函數和普通函數的區別是:

一、箭頭函數沒有this,函數內部的this來自於父級最近的非箭頭函數,而且不能改變this的指向。

二、箭頭函數沒有super

三、箭頭函數沒有arguments

四、箭頭函數沒有new.target綁定。

五、不能使用new

六、沒有原型

七、不支持重複的命名參數。

箭頭函數的簡單理解

一、箭頭函數的左邊表示輸入的參數,右邊表示輸出的結果。

const s = a => a
console.log(s(2)) // 2

二、箭頭函數中,最重要的this報錯將再也不成爲你天天都擔憂的bug。

三、箭頭函數還能夠輸出對象,在react的action中就推薦這種寫法。

const action = (type, a) => ({
  type: "TYPE",
  a
})

四、支持當即執行函數表達式寫法

const test = ((id) => {
  return {
    getId() {
      console.log(id)
    }
  }
})(18)
test.getId() // 18

五、箭頭函數給數組排序

const arr = [10, 50, 30, 40, 20]
const s = arr.sort((a, b) => a - b)
console.log(s) // [10,20,30,40,50]

尾調用優化

尾調用是什麼鬼?

尾調用是指在函數return的時候調用一個新的函數,因爲尾調用的實現須要存儲到內存中,在一個循環體中,若是存在函數的尾調用,你的內存可能爆滿或溢出。

ES6中,引擎會幫你作好尾調用的優化工做,你不須要本身優化,但須要知足下面3個要求:

一、函數不是閉包

二、尾調用是函數最後一條語句

三、尾調用結果做爲函數返回

一個知足以上要求的函數以下所示:

"use strict";   
function a() {
  return b();
}

下面的都是不知足的寫法:

//沒有return不優化
"use strict";
function a() {
  b();
}

//不是直接返回函數不優化
"use strict";
function a() {
  return 1 + b();
}

//尾調用是函數不是最後一條語句不優化
"use strict";
function a() {
  const s = b();
  return s
}

//閉包不優化
"use strict";
function a() {
  const num = 1
  function b() {
    return num
  }
  return b
}

尾調用實際用途——遞歸函數優化

在ES5時代,咱們不推薦使用遞歸,由於遞歸會影響性能。

可是有了尾調用優化以後,遞歸函數的性能有了提高。

//新型尾優化寫法
"use strict";  
function a(n, p = 1) {
  if(n <= 1) {
    return 1 * p
  }
  let s = n * p
  return a(n - 1, s)
}
//求 1 x 2 x 3的階乘
let sum = a(3)
console.log(sum) // 6

總結

函數這一章涉及到的知識點比較多,默認參數,命名參數,不定參數,展開運算符,箭頭函數,尾調用優化。

第一次學習這些知識的人能夠關注箭頭函數和展開運算符的使用,這是最重要也最經常使用的知識,若是你已經在項目中使用過這些知識,那麼做爲鞏固也是有幫助的,俗話說溫故知新。

=> 返回文章目錄

相關文章
相關標籤/搜索