# JavaScript進階系列之function篇

JavaScript進階系列之function篇

天天都在codeing,可是若是沒有總結的話,根本記不住。之後按期寫文章,無論有沒有人看都會有必定的收穫。git

目錄:

個人GitHub,歡迎stargithub

函數的參數

默認參數

使用了默認參數的函數,會自動啓用ES6面試

function fn(a, b = 1) {

}
fn(1)

不傳或者手動傳遞undefined都會使用默認的參數。閉包

除此以外,和正常的ES5還有一些區別:app

  • 形參列表裏的參數,都至關於使用let聲明的同樣(意味着存在TDZ)
  • 默認參數能夠是形參列表裏的變量,但不能夠是函數體內的變量哦。
  • 使用了默認參數,說明當前使用的ES6,因此當前scope都是處於strict模式下的。
形參列表裏的參數的 scope和函數體內的 scope是兩個 scope(書上是這麼說的,可是他媽的若是是兩個 scope,那我從新用 let聲明爲何還報錯?幹!因此我以爲應該只是說形參列表的默認參數不能使用函數做用域內部的變量,但仍是屬於 同一個scope,由於相似的 for循環,括號裏和花括號裏是兩個 scope我就能用 let重複聲明)

默認參數對arguments的影響

記住一點,嚴格模式下arguments只和傳入的實參同樣,並且不保證同步。函數

因此一旦使用了默認參數,就說明要麼是沒傳,要麼是傳了undefined。那arguments裏就確定沒有默認參數了。優化

function fn(a, b = 1) {
  console.log(arguments[0] === a) // true
  console.log(arguments[1] === b) // false
}
fn(1)

無名參數

function fn(a, ...args) {

}

使用限制:ui

  • 不定參數只能在參數列表的最後
  • 不定參數不能用在setter中,由於setter的參數只能有一個value,在不定參數的定義中是能夠有無限多。這二者在當前上下文中不容許

一些注意的點:this

  • arguments中只存儲傳入的參數
  • fn.length則是命名參數的個數,也就是說fnlength屬性是不包括args中的東東的
其實在 ES4的草案中, arguments對象是會被不定參數給幹掉的,不過 ES4擱置之後,等到 ES6出來,它很 ES4的區別是保留了 arguments對象

arguments

在非嚴格模式下,arguments對象和實參保持同步:prototype

function fn(a, b) {
  console.log(a === arguments[0])
  a = 'hehe'
  console.log(a === arguments[1])
}
fn(1, 2)
結果都是true

之因此給實參加粗,是由於即便保持同步,也只是和傳入的參數保持一致,好比我若是沒有傳入b,而後我修改了b,這個時候arguments[0]b是不一致的。

可是在嚴格模式下,arguments和參數則不會保持同步。

箭頭函數

與普通函數的區別:

  • 沒有new.target、this、arguments、super,這些東西都是最近一層非箭頭函數的東西.(因此,一旦不存在這樣的函數,可是在箭頭函數中訪問了這些keyword就會拋出錯誤)
  • 不能被new調用,沒有[[construct]]內部方法
  • 沒有prototype屬性
  • this遵循詞法做用域,運行過程當中不會改變
  • 不管是否爲嚴格模式,都不能有同名的參數
  • 由於沒有arguments,因此參數只能經過命名參數和不定參數來訪問
  • 不能使用yield關鍵字,因此也就不能當作generator函數咯
注意,可否被用做 constructor和其有無 prototype屬性無關

就算用call、apply、bind這樣的方法,也無法改變箭頭函數的this。不過經過bind能夠傳遞參數卻是真的

函數的name屬性

name屬性是爲了更好地辨別函數:

function fn() {}  // fn
const a = function() {} // a
const b = fn // fn
const c = a // a
const d = function hehe() {} // hehe

註釋就是對應函數的name。仔細觀察很容易發現,若是函數是使用函數聲明建立的,那name就是function關鍵字後的string。若是是使用賦值語句建立的,那name就是對應的變量名。並且一旦function.name肯定下來,後續賦值給其餘變量也不會改變。其中function聲明比賦值語句的優先級高。

特殊狀況:

const obj = {
  get name() {

  },
  hehe() {

  }
}
console.log(obj.name) // 書上說是 get name,可是我親測是undefined啊
console.log(obj.hehe) // hehe

另外bind出來的函數,name帶有bound前綴;經過Function建立的函數帶有anonymous

函數的 name屬性不必定同步於引用變量,只是一個協助調試用的額外信息而已,因此不要使用 name屬性來獲取函數的引用

函數節流、函數防抖

節流就是等到你不觸發了我在執行:

function debounce(fn, time, immediate = false) {
  let clear
  return function(...args) {
    if (immediate) {
      immediate = false
      fn(...args)
      return
    }
    if (clear) {
      clearTimeout(clear)
    }
    clear = setTimeout(() => {
      fn(...args)
      clear = 0
    }, time)
  }
}

防抖就是不管你觸發多少次,我只在規定的時間裏觸發一次

function throttle(fn, time, immediate = false) {
  let clear
  let prev = 0
  return function(...args) {
    if (immediate) {
      immediate = false
      fn(...args)
      return
    }
    if (!clear && Date.now() - prev >= time) {
      prev = Date.now()
      clear = setTimeout(() => {
        fn(...args)
        clear = 0
        prev = 0
      }, time)
    }
  }
}

尾遞歸

尾調用就是函數做爲另外一個函數的最後一條語句被調用。

ES5中,尾調用的實現和普通的函數調用同樣,都是建立一個新的stack frame,將其push到調用棧,來表示函數調用,若是在循環調用中,調用棧的大小過大就會爆棧。

而尾遞歸優化呢,指的就是不在建立新的stack frame,而是清除掉當前的stack frame,而後重用便可。這樣,尾遞歸的時候,整個調用棧的大小就不會變了,達到了優化的效果。

如下狀況會優化:

  • 尾調用不訪問當前stack frame的變量。也就是說函數不能是一個閉包
  • 在函數內部,必須是最後一條語句
  • 尾調用的結果做爲返回值返回
function fn1() {
  // 其餘語句
  return fn2()
}

深刻點不知道的

JavaScript函數中有兩個內部方法:[[call]][[construct]]。經過new來調用函數的時候執行的是construct內部方法,而正常調用則執行call內部方法。

  • 前者負責建立一個實例instance,而後執行函數體。當使用new調用函數的時候,new.target被賦值爲new操做符的目標,一般就是被new調用的構造函數。因此若是須要判斷函數是否被new調用,則只須要查看new.target是否爲undefined便可
  • 後者直接執行代碼中的函數體
具備 [[construct]]內部方法的函數被統稱爲構造函數。不是全部的函數都是構造函數,因此不是全部的函數都可以被 new調用(好比箭頭函數)。這個具體細節看下文。

JS中的三種Function

JS目前具備三種類型的function object

  • ECMAScript Function Object:全部經過JS語言生成的function object都是ECMAScript Function Object
  • Built-in Function:引擎內置的全部function object若是沒有實現爲ECMAScript Function Object,必須實現爲此處的Built-in Function Object
  • Bound FunctionFunction.prototype.bind生成的function object爲<u>Bound Function Object</u>,調用<u>Bound Function Object</u>會致使調用綁定的<u>bound target function</u>;

ES6標準指出,函數內部都有兩個方法:[[call]] [[construct]] 。前者是普通調用,後者是new調用。

而即使都是new調用,built in 和 普通的 function object仍是有所差異:

  • new operator做用於ECMAScript Function Object會根據當前function objectprototype屬性生成一個新的對象,並將其做爲this傳入function object進行調用;
  • new operator做用於Built-in Function Object的時候不會生成一個新的對象做爲this傳入當前的function object,而是由當前的function objectfunction call的時候本身生成一個新的對象。
常常看到面試題問 new operator執行了哪些操做,而後就開始巴拉巴拉:根據原型生成一個新的對象,而後將新的對象做爲this調用函數,最後根據函數的返回值是否爲對象來判斷應該返回什麼。。。(心中千萬只草泥馬飄過);固然,若是要用 JS來模擬 new operator那隻能按照這個流程搞,頂多再用上 new.target

js中的函數都有prototype?

之前一直覺得全部js函數都有prototype,直到最近才發現不是。

除非在特定函數的描述中另有指定,不然不是構造函數的內置函數不具備原型屬性。

也就是說,js的一些內置函數原本就沒打算用做constructor,也就沒有添加[[construct]] internal-method。可是反過來不必定成立,由於有的構造函數沒有prototype,但它仍然是一個構造函數,好比:

console.log(Proxy.prototype); // undefined
// 可是能夠經過new Proxy(args)來建立對象

按照規範,若是一個function-object 既具備prototype屬性,又具備[[construct]] internal-method,那麼它就是一個constructor,此時該function-object承擔着creates and initializes objects的責任;

Proxy constructor爲何沒有prototype屬性呢?雖然constructor用於 creates and initializes objects,但若是生成的對象的[[prototype]]屬性不須要constructorprototype屬性初始化,那麼constructorprototype就沒有存在的必要。

也就是說,大部分狀況下只要某個 functionprototype屬性,同時又具備 [[constructor]],那這個 function就是一個 constructor

可是某些特殊狀況下也會有例外,即:它不承擔建立對象而且初始化。可是因爲某些緣由它又同時具有了上述條件。

這是規範中指出的,目前尚未在 built-in function中發現過這種特例。不過在 function object中有兩個特例。

generator function

generator 不是 constructor ,可是同時具有 prototype

Function.prototype.bind生成的Bound function object

經過 bind 生成的bound function 是沒有 prototype 屬性,不過它仍然能夠看成一個 constructor

總結

綜上所述,明確瞭如下幾點:

  • 不是全部函數都是構造函數,必須有內部方法[[construct]]
  • 不是全部函數都有prototype屬性
  • 有無prototype屬性和函數是否爲構造函數無關,只要有[[construct]]屬性就是構造函數
  • 不是有所的構造函數都能被new調用,好比Symbol

延伸

一個function object能夠用new調用的條件是什麼?

也就是說,是否能夠用new方式調用,和函數是否是構造函數沒有關係,有沒有prototype也不要緊,只要函數對象上具備內部的[[construct]],而且函數自己是容許new調用的,就能夠經過new來調用該function

一些值得注意的點

  • 嚴格模式下,function聲明是存在塊級做用域的。不過在當前的scope中不存在TDZ。非嚴格模式下則不存在塊級做用域的特性,會直接提高至頂層做用域
if (true) {
  function a() {}
}
console.log(a) // undefined
  • 方法和函數:在ES6以前js中的方法就是某個函數做爲某個對象的非數據屬性,除此以外和函數沒有任何區別。可是在ES6以後,全部的方法內部都有一個[[HomeObject]]屬性,對象的方法有,可是普通的函數則沒有。通常狀況下都不會有什麼區別,可是在使用super的時候會有區別
const proto = {
  method() {
    return 'this is a method on proto'
  }
}

const obj = Object.setPrototypeOf({
  test() {
    console.log(super.method())
  }
}, proto)
obj.test() // this is a method on proto

const obj = Object.setPrototypeOf({}, proto)
obj.test = function() {
  super.method() // 語法錯誤
}
obj.test()

最後

個人GitHub,歡迎star.

發現錯誤,歡迎在評論裏指出😆。

相關文章
相關標籤/搜索