深刻理解ES6之《函數》

默認參數

ES5中要爲參數指定默認值,只能以下所示:數組

function makeRequst(url, timeout, callback) {
  timeout = timeout || 2000;
  callback = callback || function () { }
}

可是這樣有一個問題若是timeout傳進來的值爲0,則也會賦值變成2000,
因此更加徹底的作法是檢測參數類型安全

function makeRequst(url, timeout, callback) {
  timeout = (typeof timeout !== 'undefined') ? timeout : 2000;
  callback = (typeof callback !== 'undefined') ? callback : function () { }
}

ES6中直接閉包

function makeRequst(url, timeout = 2000, callback = function () { }) {

}

能夠爲任意參數指定默認值,在已指定默認值的參數後能夠繼續聲明無默認值參數app

function makeRequst(url, timeout = 2000, callback) {

}

是否使用函數默認值主要依賴於調用函數是實參是否全等於undefined函數

ES5非嚴格模式下,函數命名參數的變化會體如今arguments對象中優化

function mixArgs(first, second) {
  console.log(first === arguments[0])//true
  console.log(second === arguments[1])//true
  first = 'c'
  second= 'd'
  console.log(first === arguments[0])//true
  console.log(second === arguments[1])//true
}
mixArgs('a', 'b')

而在嚴格模式下,不管參數如何變化,arguments對象再也不隨之改變this

function mixArgs(first, second) {
  'use strict'
  console.log(first === arguments[0])//true
  console.log(second === arguments[1])//true
  first = 'c'
  second = 'd'
  console.log(first === arguments[0])//false
  console.log(second === arguments[1])//false
}
mixArgs('a', 'b')

若是使用了函數默認值,則arguments對象的行爲將與ES5在嚴格模式下保持一致:arguments對象保持與命名參數分離(備註:其實這種分離的特性能夠將參數恢復爲初始值)url

function mixArgs(first, second='b') {
  console.log(first === arguments[0])//true
  console.log(second === arguments[1])//false
  first = 'c'
  second = 'd'
  console.log(first === arguments[0])//false
  console.log(second === arguments[1])//false
}
mixArgs('a')

函數默認值除了能夠給原始值,還能夠指定函數,只不過只有未傳入參數須要默認值時才能去調用些此函數,也就是說默認參數是在函數調用時才求值
指定默認值爲函數不要忘記小括號,若是忘記小括號則傳入的是對函數的引用而不是函數調用的結果spa

function getValue(value) {
  return value + 5
}
function add(first, second = getValue(first)) {
  return first + second
}
console.log(add(1))//7

函數參數有本身的做用域和臨時死區,其與函數體的做用域是各自獨立的,也就是說參數的默認值是不可訪問函數體內聲明的變量prototype

處理無命名參數

在函數命名參數前添加...三個點代表這是一個不定參數

function pick(obj, ...keys) {
  let result = Object.create(null)
  for (let i = 0, len = keys.length; i < len; i++) {
    result[keys[i]] = object[keys[i]]
  }
  return result
}

函數的length屬性統計的是函數命名參數的數量,不定參數的加入不會影響length屬性的值
每一個函數最多隻能聲明一個不定參數,並且必定要放在全部參數的末尾
不定參數不能用於對象字面量setter之中,由於對象字面量setter的參數有且只能有一個

let obj = {
  //Uncaught SyntaxError: Setter function argument must not be a rest parameter
  set name(...value) {
    console.log(value)
  }
}

看一個有趣的例子:不管是否使用不定參數,arguments對象老是包含全部傳入函數的參數
圖片描述
默認參數和不定參數的特性一樣適用於Function構造函數

var add = new Function("first", "second=first", "return first+second")
console.log(add(1))//2

var pickFirst=new Function("...args","return args[0]")
console.log(pickFirst(1,2))//1

展開運算符

舉例來講,Math.max可接受任意數量的參數並返回最大的那個,但若是傳入的是一個數組,則只能使用apply

console.log(Math.max(11, 2, 3, 12, 43, 904, 3543, 43))
let values = [11, 2, 3, 12, 43, 904, 3543, 43]
console.log(Math.max.apply(Math, values))

使用展開運算符就變得特別簡單了

let values = [11, 2, 3, 12, 43, 904, 3543, 43]
console.log(Math.max(...values))

若是你想限定Math.max返回的最小值爲0,還能夠以下使用

let values = [-11, -2, -3, -12]
console.log(Math.max(...values, 0))

函數的name屬性

函數的name屬性的值不必定引用同名變量,它只是協助調用用的額外信息,因此不能使用name屬性的值來獲取函數的引用

function doSomething() { }
console.log(doSomething.name)//doSomething

var doSome = function doSomeElse() { }
var person = {
  get firstName() { return 'angela' },
  sayName: function () { }
}
console.log(doSome.name)//doSomeElse
console.log(person.sayName.name)//sayName
console.log(person.firstName.name)//undefined

var doThing = function () { }
console.log(doThing.bind().name)//bound doThing
console.log((new Function()).name)//anonymous

函數的多重用途

圖片描述

JS函數中有兩個不一樣的內部方法:[[Call]]和[[Construct]]
當經過new關鍵字調用函數時,執行的是[[Construct]]函數,它負責建立一個一般被稱做實例的新對象,而後再執行函數體,將this綁定到實例上
若是不是經過new關鍵字調用函數,則執行[[Call]]函數,從而直接執行代碼中的函數體
不是全部函數都有[[Construct]]方法,所以不是全部方法均可以經過new來調用,具備[[Construct]]方法的函數被統稱爲構造函數

ES5中想肯定一個函數是否經過new關鍵字被調用

function Person(name) {
  if (this instanceof Person) {
    this.name = name
  } else {
    throw new Error('必須經過new關鍵字來調用')
  }
}

可是上述方法不是絕對徹底可靠的,比方說以下調用就失效了

var person = new Person('angela')
var notAPerson = Person.call(person, 'Shing')//這樣對於函數自己是沒法區分是經過Person.Call、Apply仍是new調用獲得的實例

ES6中能夠以下絕對安全的斷定

function Person(name) {
  //或者typeof new.target===Person
  if (typeof new.target !== 'undefined') {
    this.name = name
  } else {
    throw new Error('必須經過new關鍵字來調用')
  }
}
var person = new Person('angela')
var notAPerson = Person.call(person, 'Shing')//拋出錯誤

當調用函數的[[Construct]]方法時,new.target被賦值爲new操做符的目標,若是調用的是[[Call]]方法,則new.target的值爲undefined
在函數外使用new.target是一個語法錯誤

塊級函數

ES5嚴格模式下,在代碼塊內部聲明函數程序會報錯
在ES6嚴格模式下,能夠在代碼塊中聲明函數,塊級函數聲明會被提高至此代碼塊頂部,超出此塊級做用域,則函數將再也不存在

'use strict'
if (true) {
  console.log(typeof doSomeThing)  //function
  doSomeThing()//----------
  function doSomeThing() {
    console.log('----------')
  }
}
console.log(typeof doSomeThing) //undefined

在ES6非嚴格模式下,這些函數再也不提高至代碼塊的頂部而是提高至外圍函數或全局做用域的頂部

if (true) {
  console.log(typeof doSomeThing)  //function
  doSomeThing()//----------
  function doSomeThing() {
    console.log('----------')
  }
}
console.log(typeof doSomeThing) //function

箭頭函數

箭頭函數與傳統函數有以下幾點不一樣

  1. 沒有this、super、arguments和new.target綁定--也就是說箭頭函數中的this、super、arguments和new.target這些值由外圍最近一層非箭頭函數決定
  2. 不能經過new關鍵字調用--由於箭頭函數沒有[[Construct]]
  3. 沒有原型--箭頭函數不存在prototype屬性
  4. 不能夠改變this的綁定--函數內的this值不可被改變
  5. 不支持arguments對象--只能經過命名參數和不定參數來訪問函數中的參數
  6. 不支持重複的命名參數

當箭頭函數只有一個參數時,能夠直接寫參數名,箭頭緊隨其後,箭頭右側的表達式被求值後便當即返回,即便沒有顯式的返回語句
若是要傳入兩個或兩個以上參數則須要在參數兩側添加一對小括號
若是函數沒有參數,也要在聲明時寫一組沒有內容的小括號

let sum = (num1, num2) => num1 + num2
//至關於
let sum = function (num1, num2) {
  return num1 + num2
}

尾調用優化

尾調用指的是函數做爲另外一個函數的最後一條語句被調用,其實也就是知足如下三個條件

  1. 尾調用不訪問當前棧幀的變量--函數不是一個閉包
  2. 在函數內部,尾調用是最後一條語句
  3. 尾調用的結果做爲函數值返回

ES6嚴格模式下,JS引擎纔會進行尾調用自動優化

function f(x){
  return g(x);
}
function factorial(n) {
  if (n <= 1) {
    return 1
  } else {
    //沒法優化,必須在返回後執行乘法操做
    return n * factorial(n - 1)
  }
}

function factorial(n, p = 1) {
  if (n <= 1)
    return 1 * p
  else {
    let result = n * p;
    //優化後
    return factorial(n - 1, result)
  }
}
相關文章
相關標籤/搜索