在前面的章節中,已陸陸續續介紹了ES6為改良函數而引入的幾個新特性,本章將會繼續講解ES6對函數的其余改進,包括默認參數、元屬性、塊級函數和箭頭函數等。
## 一、默認參數
  在ES5時代,只能在函數體中定義參數的默認值,而自從ES6引入了默認參數(Default Parameter)后,就能讓參數在聲明時帶上它的默認值,如下代碼所示,func2()函數中的參數默認值在可讀性和簡潔性方面更為優秀。
~~~
function func1(name) {
name = name || "strick"; //ES5的參數默認值
}
function func2(name = "strick") { //ES6的參數默認值
}
~~~
**1)undefined**
  只有當不給參數傳值或傳入undefined時,才會使用它的默認值。即使傳入和undefined一樣的假值(例如false、null等),也得不到它的默認值,如下所示。
~~~
function func(name = "strick") {
return name;
}
func(undefined); //"strick"
func(false); //false
func(null); //null
~~~
**2)位置**
  默認參數既可以位于普通參數之前,也可以位于其之后。例如下面的兩個函數,都包含兩個參數,其中一個帶有默認值,依次執行,都能得到預期的結果。
~~~
function func1(name = "strick", age) {
return name;
}
function func2(name, age = 28) {
return age;
}
func1(undefined); //"strick"
func2("strick"); //28
~~~
**3)默認值**
  參數的默認值既可以是簡單的字面量,也可以是復雜的表達式。在每次調用函數時,不僅參數會被重新初始化,默認值如果是表達式的話,還會將其重新計算一次。
~~~
function expression1(name, full = "pw" + name) {
return full;
}
expression1("strick"); //"pwstrick"
expression1("freedom"); //"pwfreedom"
~~~
  在上面的代碼中,調用了兩次expression1()函數,返回的結果互不影響。并且full參數的默認值引用了前面的name參數,這是一種有效的語法,但反之就會報錯,如下所示。
~~~
function expression2(name = full, full) {
return name;
}
expression2(undefined, "strick"); //拋出未定義的引用錯誤
~~~
**4)限制**
  第一條限制是在包含默認值的參數序列中,不允許出現同名參數。無論同名的是有默認值,亦或是無默認值,都是不允許的,如下所示。
~~~
function restrict1(name = "strick", name) { }
function restrict1(name = "strick", age, age) { }
~~~
  第二條限制是不能在函數體中為默認參數用let或const重新聲明,如下代碼所示,會拋出重復聲明的語法錯誤。
~~~
function restrict2(name = "strick") {
let name = "freedom";
}
~~~
  因為默認參數相當于是用let聲明的變量,所以是不允許重復聲明的。上面代碼中的restrict2()函數,它的name參數的初始化類似于下面這樣。
~~~
let name = "strick";
~~~
  參數序列中只要包含了默認參數,那么其它普通參數也會用let聲明。知道這一點后,就能很容易的解釋上一節第二個示例,在調用expression2()函數時會拋出未定義的錯誤原因。函數中的兩個參數的初始化相當于下面這樣。
~~~
let name = full,
full;
~~~
  在[第一篇](https://www.cnblogs.com/strick/p/10161595.html)中曾提到用let聲明的變量,在聲明之前都會被放到臨時死區中,而在此時訪問這些變量就會觸發運行時錯誤。
**5)三個作用域**
  根據ES6規范的9.1.2小節可知,當參數序列中包含默認參數時,將會出現三個作用域:參數作用域、函數外層作用域和函數體內作用域。關于這三個作用域需要注意兩點:
  (1)函數體內可以修改參數的值,但不能為其重新聲明。
  (2)參數作用域可以訪問外層作用域中的變量,但不能訪問函數體內的變量。
  第一點很好理解,已在上文中做過解釋。關于第二點,可先查看下面的兩個函數。
~~~
let full = "freedom";
function scope1(name = full) {
return name;
}
scope1();
function scope2(name = en) {
let en = "justify";
return name;
}
scope2();
~~~
  調用scope1()函數得到的返回值是“freedom”,而調用scope2()函數非但得不到結果,還會拋出en未定義的錯誤。接下來改造scope1()函數,把full變量改成name變量,如下所示。
~~~
let name = "freedom";
function scope1(name = name) {
return name;
}
~~~
  此時再次調用scope1()函數,得到的卻是name未定義的錯誤。雖然在外層作用域中包含名為name的變量,但是參數擁有自己的作用域,會先從當前作用域中查找變量,此時的name正處在臨時死區中,因此在訪問它時會報錯。
  除了以上所列的特性之外,在之前的[第三篇](https://www.cnblogs.com/strick/p/10172871.html)的參數解構中,還介紹了解構默認值和參數默認值結合使用時的注意點。
## 二、函數屬性
**1)name**
  通過函數的name屬性可得到它聲明時所用的名稱。ES6規定此屬性既不可寫,也不可枚舉,只允許配置。在不同場景中,它的返回值會不同,具體如下所列,每一條規則后面都給出了相應的示例。
  (1)利用Function構造器創建的函數,它的名稱是“anonymous”。
~~~
var func = new Function("a", "b", "return a+b;");
func.name; //"anonymous"
~~~
  (2)如果是用匿名函數表達式創建的函數,那么它的名稱就是變量名;如果改用命名函數表達式創建,那么它的名稱就是等號右側的函數名稱。
~~~
var expression1 = function() { };
expression1.name; //"expression1"
var expression2 = function named() { };
expression2.name; //"named"
~~~
  (3)當用bind()方法綁定一個函數時,它的名稱就會加“bound”前綴。
~~~
function age() { }
age.bind(this).name; //"bound age"
~~~
  (4)訪問器屬性包含寫入方法和讀取方法,它們的名稱會分別加“set”和“get”前綴。注意,需要調用Object.getOwnPropertyDescriptor()才能引用這兩個方法。
~~~
var obj = {
get age() { },
set age(value) { }
};
var descriptor = Object.getOwnPropertyDescriptor(obj, "age");
descriptor.get.name; //"get age"
descriptor.set.name; //"set age"
~~~
  (5)如果對象的方法是用Symbol命名的,那么這個Symbol的描述就是它的名稱。
~~~
var sym = Symbol("age"),
obj = {
[sym]: function() {}
};
obj[sym].name; //"[age]"
~~~
**2)length**
  函數的length屬性可返回形參個數(即聲明時的參數),但它的值會受剩余參數(已在[第二篇](https://www.cnblogs.com/strick/p/10172721.html)中做過介紹)和默認參數的影響,如下代碼所示。
~~~
(function rest(name, ...args){ }).length; //1
(function rest(name, age = 28){ }).length; //1
(function rest(name, age = 28, school){ }).length; //1
~~~
  根據上面的代碼可知,形參個數的統計會忽略剩余參數,并且止于默認參數。
## 三、塊級函數
  ES6允許塊級函數(Block-Level Function)的聲明,即在塊級作用域中聲明函數,而在ES5中如此操作的話,將會拋出語法錯誤的異常。
**1)嚴格模式**
  在嚴格模式中,塊級函數的聲明可提升至當前代碼塊的頂部,在代碼塊之外是不可見的,如下代碼所示。
~~~
"use strict";
(function() {
func("strick"); //拋出未定義的引用錯誤
if(true) {
func("freedom"); //"freedom"
function func(name) {
return name;
}
{
func("jane"); //"jane"
}
}
func("justify"); //拋出未定義的引用錯誤
})();
~~~
  只有在func()函數所處的代碼塊或與之相鄰的代碼塊中,才能被正確調用。
**2)普通模式**
  在普通模式(即非嚴格模式)中,只有當塊級函數所在的代碼塊被成功執行后,它的聲明才能被提升至當前腳本文件或函數體的頂部,如下代碼所示。
~~~
(function() {
func("strick"); //拋出未定義的引用錯誤
if(true) {
func("freedom"); //"freedom"
function func(name) {
return name;
}
{
func("jane"); //"jane"
}
}
func("justify"); //"justify"
})();
~~~
  在代碼塊之外調用了兩次func()函數,由于第一次調用時,func()函數所處的代碼塊還未被執行(即還未聲明),因此會拋出未定義的引用錯誤。
## 四、元屬性
  元屬性(Meta Property)就是非對象的屬性,能夠以屬性訪問的形式讀取特殊的元信息。new.target是由ES6引入的一個元屬性,可檢測一個函數是否與new運算符組合使用,并且只能存在于函數體內。
  在JavaScript中,new是一個關鍵字,而不是一個對象。但當函數作為構造函數被調用時,new.target能夠指向新創建的目標對象;而當函數作為普通函數被調用時,new.target的值為undefined,如下所示。
~~~
function func1() {
typeof new.target; //"function"
}
new func1();
function func2() {
new.target === undefined; //true
}
func2();
~~~
  把func1()作為構造函數使用,在其函數體中利用typeof運算符檢測出new.target是一個函數對象;而在func2()函數中,讓new.target和undefined進行了全等比較,得到的結果為true。
*****
> 原文出處:
[博客園-ES6躬行記](https://www.cnblogs.com/strick/category/1372951.html)
[知乎專欄-ES6躬行記](https://zhuanlan.zhihu.com/pwes6)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020