前言

本章介紹函數的擴展。有些不常用的知識了解即可。
本章原文鏈接:函數的擴展

函數參數的默認值

ES6 允許為函數的參數設置默認值,即直接寫在參數定義的后面。
當函數形參沒有被賦值時,才會將默認值賦值給函數參數。

// 默認值直接寫在行參后面function sampleFn(sample = 0, sample1 = 0) {  return sample + sample1;}

注意:

  • 參數變量是默認聲明的,所以不能用letconst再次聲明。
  • 使用參數默認值時,函數不能有同名參數。
  • 參數默認值是惰性求值的。
  • 函數的默認值指定后,函數length屬性返回的是沒有指定默認值的參數的個數。
  • 參數的默認值一旦設定,函數進行聲明初始化時,參數會形成一個多帶帶的作用域(context)。
// 默認值直接寫在行參后面function sampleFn(sample = 0, sample1 = 0,sample = 1) { // 不能有同名參數  let sample = 1; // 不能再次聲明  return sample + sample1;}

注意:通常情況下,定義了默認值的參數,應該是函數的尾參數。也就是放在最后面。

解構賦值默認值

// 函數的默認值與結構賦值的默認值可以結合使用function sampleFn({ sample = 0, sample1 = 0 } = {}) { // 函數參數默認值  return sample + sample1;}console.log(sampleFn({ sample: 23, sample1: 33 })); // 56 參數需對應解構賦值的類型

作用域

當函數參數設置了默認值,函數進行聲明初始化時,函數參數會生成一個多帶帶的作用域,等到初始化結束,該作用域就會消失。而且該行為只在函數參數指定了默認值才會出現。

let sample = 1;/*     在聲明的時候出現多帶帶作用域  在這個作用域中,變量沒有定義,于是指向外層變量    函數調用時,函數內部變量影響不到默認值變量*/function sampleFn(sample1 = sample) {   let sample = 2;  console.log(sample1);  return sample1;}sampleFn() // 1

rest 參數

ES6 引入 rest 參數 ,用于獲取函數的多余參數。
arguments 對象是類數組,rest 參數是真正的數組。
?
形式為:...變量名,函數的最后一個命名參數以...為前綴。

// 下面例子中 ...values 為 rest參數 ,用于獲取多余參數const sample = function (title, ...values) {  let sample = values.filter(    (item) => {      return item % 2 === 0;    }  )  return (title + sample);}console.log(sample("求偶數", 1, 2, 6, 2, 1));  // 求偶數 2, 6, 2

注意:rest參數 只能是函數的最后一個參數,函數的length不包括rest參數

嚴格模式

在JavaScript中,只要在函數中的嚴格模式,會作用于函數參數和函數體。
ES2016 規定只要函數參數使用了默認值、解構賦值、或者擴展運算符,那么函數內部就不能顯式設定為嚴格模式,否則會報錯。

function sample() { // 參數使用默認值、解構賦值、擴展運算符   use strict; // 開啟嚴格模式}

name 屬性

函數的name屬性,返回該函數的函數名。

function sample() { };let sample1 = function () { };function sample2() {};console.log(sample.name); // sampleconsole.log(sample1.name); //  sample1 // bound sample2  使用了 bind 方法,輸出會有bound前綴console.log(sample2.bind({}).name); console.log((new Function).name); // anonymous  構造函數的name值為 anonymous

箭頭函數

簡單介紹

ES 6 新增一種函數定義方法,使用箭頭連接參數列與函數題。
箭頭函數相當于匿名函數,并且簡化了函數定義,箭頭函數沒有prototype

// 普通函數let sample = function (item) {  return item;};// 上面函數等同于下面函數// 使用箭頭函數let sample = (item) => { return item}; // 箭頭函數

箭頭函數簡寫

沒錯,箭頭函數還可以簡寫

  1. 當參數只有一個時,可以省略箭頭左邊的括號,但沒有參數時,括號不可以省略。
  2. 當函數體只有一個表達式時,可省略箭頭右邊的大括號,但同時必須省略return語句 并寫在一行。
  3. 當函數體分多于一條語句,就要使用大括號將它們括起來,并且使用return語句返回。
// 下面幾種函數寫法都相同let sample = function (item) {  return item;};let sample = (item) => { return item}; // 箭頭函數 不省略let sample = item => { return item}; // 省略左邊圓括號let sample = (item) => item; // 省略右邊大括號和 returnlet sample = item => item; // ?省略左邊圓括號和右邊花括號和return// 如果不需要返回值的特殊情況let sample = item => void item;console.log(sample()); // undefined

注意點

  • 箭頭函數 的 This 默認指向定義它的作用域的 This
  • 箭頭函數 不能用作構造函數。
  • 箭頭函數 不可以使用 arguments 對象,該對象在函數體內不存在。
  • 箭頭函數 不可以使用yield命令,也就不能作為 Generator 函數。

箭頭函數的this

箭頭函數 會繼承自己定義時所處的作用域鏈上一層的this
箭頭函數 this在定義的時候已經確定了,所以箭頭函數this不會改變。
使用 call()apply() 方法時,也不能重新給箭頭函數綁定thisbing()方法無效。

window.sample = "window 內 ";function sampleFn() {  let thiz = this;  let sample = "sampleFn 內 ";  let sampleObj = {    sample: "sampleObj 內 ",    // 普通函數    sampleFn1: function () {      console.log(thiz === this);      console.log(this.sample);    },    // 箭頭函數    sampleFn2: () => {      // 箭頭函數的作用域為 sampleObj 上一層為 sampleFn      console.log(thiz === this); //箭頭函數的 this        console.log(this.sample);    }  }  sampleObj.sampleFn1();  // false, sampleObj 內   sampleObj.sampleFn2();  // true, window 內}sampleFn();

尾調用優化

有兩個概念

  1. 尾調用
    尾調用(Tail Call)是函數式編程的一個重要概念,就是指某個函數的最后一步是調用另一個函數。

  2. 尾遞歸
    函數調用自身,稱為遞歸。如果尾調用自身,就稱為尾遞歸。

ES6 明確規定,所有 ECMAScript 的實現,都必須部署“尾調用優化”。
這就是說,ES6 中只要使用尾遞歸,就不會發生棧溢出(或者層層遞歸造成的超時),相對節省內存。

這是什么意思呢?

尾調用的作用,在原文中是這樣寫的:

我們知道,函數調用會在內存形成一個“調用記錄”,又稱“調用幀”(call frame),保存調用位置和內部變量等信息。如果在函數A的內部調用函數B,那么在A的調用幀上方,還會形成一個B的調用幀。等到B運行結束,將結果返回到A,B的調用幀才會消失。如果函數B內部還調用函數C,那就還有一個C的調用幀,以此類推。所有的調用幀,就形成一個“調用棧”(call stack)。
尾調用由于是函數的最后一步操作,所以不需要保留外層函數的調用幀,因為調用位置、內部變量等信息都不會再用到了,只要直接用內層函數的調用幀,取代外層函數的調用幀就可以了。

換種方式解釋吧

函數被調用的時候會有函數執行上下文被壓入執行棧中,直到函數執行結束,對應的執行上下文才會出棧。
在函數A的內部調用函數B,如果函數B中有對函數A中變量的引用,那么函數A即使執行結束對應的執行上下文也無法出棧,如果函數B內部還有調用函數C那么要等函數C執行完,函數A、B對應的執行上下文才能出棧,以此類推,執行棧中要上一個函數(內層函數)的執行上下文,這就是尾調用優化。

// 尾遞歸function sampleFn(sample) {  if (sample <= 1) return 1;  return sampleFn(sample - 1) + sample;}sampleFn(2);

注意 :

  • 當內層函數沒有用到外層函數的內部變量的時候才可以進行尾調用優化。
  • 目前只有 Safari 瀏覽器支持尾調用優化,Chrome 和 Firefox 都不支持。

ES 6 的小修改

函數參數尾逗號

ES2017 允許函數的最后一個參數有尾逗號(trailing comma)。
這樣的規定也使得,函數參數與數組和對象的尾逗號規則,保持一致了。

function sampleFn(  sample1,  sample2,  sample3, // 可以在最后一個參數后面加 逗號 ,) {}

toString()修改

Function.prototype.toString()
ES2019 對函數實例的toString()方法做出了修改。明確要求返回一模一樣的原始代碼。
toString()方法返回函數代碼本身,ES6前會省略注釋和空格。

function sampleFn() {  // 注釋}let sample = sampleFn.toString();console.log(sample);//輸出 完全一樣的原始代碼,包括空格與注釋/*function sampleFn() {  // 注釋}*/

catch 修改

ES2019 改變了catch語句后面必須攜帶參數的要求。允許catch語句省略參數。

try {  // ...} catch { // 不帶參數  // ...}