深入理解ES6 003【學習筆記】

函數

參數默認值,以及一些關於arguments對象,如何使用表達式作為參數、參數的臨時死區。

以前設置默認值總是利用在含有邏輯或操作符的表達式中,前一個值是false時,總是返回後面一個的值,但如果我們給參數傳0時,就有些麻煩。需要去驗證一下類型

function makeRequest(url,timeout,callback){
  timeout = timeout || 2000;
  callback = callback || function(){};
  // 邏輯
}
// 解決傳0問題
function makeRequest(url,timeout,callback){
  timeout = typeof timeout !=='undefined'?timeout: 2000;
  callback = typeof callback !=='undefined'?callback : function(){};
  // 邏輯
}

默認參數

不傳或傳undefined。如果傳null不會用默認,會認為是一個合法的值。默認參數對arguments對象的影響。之前的參數傳遞會體現在arguments對象上。 在es5非嚴格模式下,命名參數的變化會同步更新到arguments對象上,但在es5的嚴格模式下,取消了這個行為,無論參數如何變化,arguments對象不再隨之改變。 如下

function mixArgs(first,second){
  "use strict"

  console.log(first === arguments[0]);
  console.log(second === arguments[1]);

  first = 'c';
  second = 'd'

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

es6中,如果一個函數使用了默認參數值,則無論是否顯式定義了嚴格模式,arguments對象的行為都將與es5 嚴格模式下保持一致。

默認參數表達式

初次解析函數聲明時不會調用getValue()方法,只有當調用add()函數且不傳入第二個參數時才會調用。

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

先定義參數可以作為後定義參數的默認值,反過來不成立(first=second,second)因為second比first晚定義,因此其不能作為first的默認值。為了幫助你理解背後的原理,我們重溫一下臨時死區TDZ的概念。與let聲明類似,定義參數時會為每個參數創建一個新的標識符綁定,該綁定在初始化之前不可被引用,如果試圖訪問會導致程序拋出錯誤。
函數參數有自己的作用域和臨時死區,其與函數體的作用域是各自獨立的,也就是說參數的默認值不可訪問函數體內的變量
無論是是否使用不定參數,arguments對象總是包含所有傳入函數的參數

處理無命名參數--不定參數

// 函數的length屬性統計的是函數命名參數的數量,不定參數的加入不會影響length屬性的值。
function pick(object, ...keys){
  let result = Object.create(null);
  for(let i=0,len=keys.length;i<len;i++){
    result[keys[i]] = object[keys[i]]
  }
  return result;
}
  • 不定參數(當然在函數參數中使用)
  • 每個函數最多只能聲明一個不定參數
  • 而且一定要放在所有參數的未尾
  • 不許在setter中使用不定參數,是因為對象字面量setter的參數有且只能有一個(在不定參數的定義中,參數的數量可以無限多,所以在當前上下文中允許使用不定參數。
let object = {
  // 語法錯誤 不可以在Setter中使用不定參數
  set name(...value){
    // ...
  }
}

無論是否使用不定參數,arguments對象總是包含所有傳入函數的參數

增強的Function構造函數

可以在動態創建函數時使用默認值及不定參數,唯一需要做的是在參數名後添加一個等號和一個默認值,定義不定參數,只需在最後一個參數前添加...。如下

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

展開運算符

展開運算符可以讓你指定一個數組,將它們打散後作為各自獨立的參數傳入函數。JavaScript內建的Math.max()方法可以接受任意數量的參數並返回值最大的那個,這是一個簡單的用例

自己理解,注意,前面那個是定義時用的,這個是調用時使用(參數是分開的,而你有一個數組)

如下,之前數組求最大值的做法

let values = [25,50,75,100]
console.log(Math.max.apply(Math,values)) // 100;
// max方法不允許傳入一個數組
console.log(Math.max(...values))
// 展開運算符後面可以繼續添加參數
console.log(Math.max(...values,110)) //110

函數的name屬性

聲明函數.name屬性是它聲明函數名,函數表達式如果函數是匿名的則就是其變量的名稱,如果不是則是其聲明的名字,

它只是協助調試用的額外信息,所以不能使用name屬性的值來獲取對於函數的引用。

function somethingDoing(){
  // 空函數
}
var doSomeThing = function doSomethingElse() {
  // 空函數
}
var person = {
  get firstName(){
    return 'Nicholas';
  },
  sayName:function(){
    console.log(this.name)
  }
}
console.log(somethingDoing.name) // somethingDoing
console.log(doSomeThing.name) // doSomethingElse 函數表達式有個名字這個名字比要賦值的變量權重高
console.log(person.sayName.name) // sayName
console.log(person.firstName.name) // get firstName
// 兩個特例
// 通過bind()創建的函數 會帶前綴bind
var doSomthing = function(){}
console.log(doSomthing.bind().name) // "bound doSomething"
console.log(new Function().name) // anonymous

明確函數的多重用途

es5 當new 時 函數內的this值指向一個新對象,函數最終返回一個新對象(沒有明確返回一個對象時)

function Person(name){
  this.name = name;
}
var person = new Person('Nicholas');
var notPerson = Person('Nicholas'); 
console.log(person) // '[Object object]'
console.log(notPerson) // undefined

上例中,在es6中函數有兩個不同的內部訪問[[Call]][[Construct]]。當通過new關鍵字調用函數時,執行的是[[Construct]]函數,它負責創建一個通常被稱作實例的新對象,然後執行函數體,將this綁到實例上。而不使用new關鍵字時,則調用[[Call]]執行函數體,具有[[Construct]]方法的函數統稱為構造函數。切記不是所有的函數都有[[Construct]]方法 如:箭頭函數

es5判斷函數是否被new調用

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

上面的方式在一定程度上可以避免,但不要忘了還有一個call和apply呢……

var person = new Person('Nicholas');
var person =  Person('Nicholas'); // 報錯
var person =  Person.call(person,'Michael'); // 通過

無屬性new.target

為瞭解決判斷函數是否通過new關鍵字調用的問題,es6引入了new.target這個元屬性,元屬性是指非對象的屬性,其可以提供非對象目標的補充信息。當調用函數[[Construct]]方法時,new.target上被賦為new的操作目標,通常是新創建對象實例,也就是函數體內this的構造函數;如果調用[[Call]]方法,則new.target值為undefined,有了這個元屬性,可以通過檢查new.target是否被定義過來安全地檢測是否通過new關鍵字調用的。

function Person(name){
  if(typeof new.target!=='undefined'){
    this.name = name;
  }else {
    throw new Error('必須通過new關鍵字來調用Person')
  }
}
var person = new Person('Nicholas'); // 通過
var person =  Person('Nicholas'); // 報錯
var person =  Person.call(person,'Michael'); // 報錯

也可能通過new.target是否被某個特定的構造函數所調用。如下:

function Person(name){
  // if(typeof new.target!=='undefined'){
    if(new.target===Person)
    this.name = name;
  }else {
    throw new Error('必須通過new關鍵字來調用Person')
  }
}

function AnotherPerson(name){
  Person.call(this,name)
}
var person = new Person('Nicholas');
var otherPerson = new AnotherPerson('Michael') // 拋出錯誤

塊級函數

es5的嚴格格式下,我們在代碼塊中聲明函數是不被允許的,而在es6中將doSomething()函數視作一個塊級聲明,從而可以在定義該函數的代碼內訪問和調用它:

"use strict"

if(true){
  console.log(typeof doSomething) // "function"
  function doSomething(){
    // 空函數
  }
  doSomething() //
}
typeof doSomething // undefined

和let的使用差不多,唯一區別的是塊級是函數,會將聲明提到代碼塊的頂部。注意函數表達式聲明的區別。在es6中,即使處於非嚴格模式下,也可以聲明塊級函數,但其行為與嚴格模式下稍有不同。這些函數不再提升至代碼塊的頂部,而是提升至外圍函數或全局作用域的頂部,如下:

// ECMAScript 6中的行為

if(true){
  console.log(typeof doSomething) // "function"
  function doSomething(){
    // 空函數
  }
  doSomething() //
}
typeof doSomething // function

箭頭函數

let PageHandler = {
  id:"123456",
  init: function(){
    document.addEventListener("click",function(event){
      this.doSomething(event.type); // 拋出錯誤
    },false)
  },
  doSomething: function(type){
    console.log("Handling "+ type + " for "+this.id)
  }
}

上面的代碼並沒有如預期的正常運行。因為this的綁定是事件目標對象的引用(這裡是document),而沒有綁定在PageHandler,且由於this.doSomething()在目標document中不存在,所以無法正常執行,之前的做法是通過bind()方法顯示地將this綁定到PageHandler函數上來修正這個問題。如下

let PageHandler = {
  id:"123456",
  init: function(){
    document.addEventListener("click",(function(event){
      this.doSomething(event.type); 
    }).bind(this),false)
  },
  doSomething: function(type){
    console.log("Handling "+ type + " for "+this.id)
  }
}

調用bind(this)後事實上創建了一個新函數,它的this被綁定到當前的this即PageHandler。為了避免創建一個額外的函數,我們可以通過一個更好的方式來修正這段代碼,使用箭頭函數。箭頭函數中沒有this綁定,必須通過查找作用域鏈來決定其值。

let PageHandler = {
  id:"123456",
  init: function(){
    document.addEventListener("click",(event)=>{
      this.doSomething(event.type); 
    },false)
  },
  doSomething: function(type){
    console.log("Handling "+ type + " for "+this.id)
  }
}
  • 沒有this、super、arguments和new.target綁定
  • 不能通過new關鍵字調用 箭頭函數沒有[[Construct]]方法,所以不能被用作構造函數
  • 沒有原型
  • 不可以改變this的綁定
  • 不支持arguments對象
  • 不支持重復的命名參數

箭頭函數也同樣有一個name屬性,這與其它函數的規則相同

this綁定

如果箭頭函數被非箭頭函數包含,則this綁定的是最近一層非箭頭函數的this;否則,this的值會被設置為undefined.

箭頭函數和數組

箭頭函數的語法簡潔,非常適用於數組處理。舉例來說,比如給數組排序,通過是

var result = values.sort(function(a,b){
  return a - b;
})

// 我們可以將其簡寫至如下
var result = values.sort((a,b)=>a-b)

諸如sort()map()reduce()這些可以接受回調函數的數組方法,

尾調用優化

函數做為另一個函數的最後一條語句被調用。

function doSomething(){
  return doSomethingElse() // 尾調用;
}

在es5引擎中,尾調用的實現與其他函數調用的實現類似:創建一個新的棧幀(stack frame),將其推入調用棧來表示函數調用。也就是說,在循環調用中,每一個未用完的棧楨都會被保存在內存中,當調用棧變得過大時會造成程序問題。

為了縮減嚴格模式下尾調用棧的大小(非嚴格格式不受影響),如果滿足以下條件,尾調用不再創建新的棧楨,而是清除非重用當前棧楨

  • 尾調用不訪問當前棧楨的變量(也就是說函數不是一個閉包)
  • 在函數內部,尾調用是最後一條語句
  • 尾調用的結果作為函數的返回值
"use strict"

function doSomething(){
  return doSomethingElse() // 尾調用優化;
}

// 注意以上3個條件

如何利用尾調用優化


function factorial(n){
  if(n<=1){
    return 1
  } else {
    // 無法優化
    return n * factorial(n-1)
  }
}

// 可以利用傳遞第二個參數來保存每次階乘的結果(並且默認結果是1)
function factorial(n,p=1){
  if(n<=1){
    return 1*p
  }else {
    let result = n*p;
    //優化後
    return factorial(n-1,result)
  }
}

主題測試文章,只做測試使用。發佈者:Walker,轉轉請注明出處:https://walker-learn.xyz/archives/4310

(0)
Walker的頭像Walker
上一篇 2025年3月8日 12:39
下一篇 2025年3月8日 10:59

相關推薦

  • Go工程師體系課 protobuf_guide【學習筆記】

    Protocol Buffers 入門指南 1. 簡介 Protocol Buffers(簡稱 protobuf)是 Google 開發的一種語言無關、平台無關、可擴展的結構化數據序列化機制。與 JSON、XML 等序列化方式相比,protobuf 更小、更快、更簡單。 項目主頁:https://github.com/protocolbuffers/prot…

    個人 2025年11月25日
    1.2K00
  • 深入理解ES6 006【學習筆記】

    Symbol和Symbol屬性 第6種原始數據類型:Symbol。私有名稱原本是為了讓開發者們創建非字符串屬性名稱而設計的,但是一般的技術無法檢測這些屬性的私有名稱 創建Symbol let firstName = Symbol(); let person = {} person[firstName] = "Nicholas"; cons…

    個人 2025年3月8日
    1.2K00
  • TS珠峰 002【學習筆記】

    泛型 /* * @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git &a…

    個人 2025年3月27日
    1.5K00
  • Go工程師體系課 018【學習筆記】

    API 網關與持續部署入門(Kong & Jenkins) 對應資料目錄《第 2 章 Jenkins 入門》《第 3 章 通過 Jenkins 部署服務》,整理 Kong 與 Jenkins 在企業級持續交付中的實戰路徑。即便零基礎,也能順著步驟搭建出自己的網關 + 持續部署流水線。 課前導覽:甚麼是 API 網關 API 網關位於客戶端與後端微服務…

    個人 2025年11月25日
    18700
  • 深入理解ES6 008【學習筆記】

    迭代器(Iterator)和生成器(Generator) 這個新特性對於高效的數據處理而言是不可或缺的,你也會發現在語言的其他特性中也都有迭代器的身影:新的for-of循環、展開運算符(...)、甚至連異步編程都可以使用迭代器。 迭代器是一種特殊的對象,它具有一些專門為迭代過程設計的專有接口,所有的迭代器對象都有一個next()方法,每次調用都返回一個結果對…

    個人 2025年3月8日
    1.1K00
簡體中文 繁體中文 English
歡迎🌹 Coding never stops, keep learning! 💡💻 光臨🌹