ES6躬行記(14)——函數

  在前面的章節中,已陸陸續續介紹了ES6爲改良函數而引入的幾個新特性,本章將會繼續講解ES6對函數的其他改進,包括默認參數、元屬性、塊級函數和箭頭函數等。html

1、默認參數

  在ES5時代,只能在函數體中定義參數的默認值,而自從ES6引入了默認參數(Default Parameter)後,就能讓參數在聲明時帶上它的默認值,以下代碼所示,func2()函數中的參數默認值在可讀性和簡潔性方面更爲優秀。express

function func1(name) {
  name = name || "strick";            //ES5的參數默認值
}
function func2(name = "strick") {     //ES6的參數默認值
}

1)undefineddom

  只有當不給參數傳值或傳入undefined時,纔會使用它的默認值。即便傳入和undefined同樣的假值(例如false、null等),也得不到它的默認值,以下所示。函數

function func(name = "strick") {
  return name;
}
func(undefined);        //"strick"
func(false);            //false
func(null);             //null

2)位置this

  默認參數既能夠位於普通參數以前,也能夠位於其以後。例以下面的兩個函數,都包含兩個參數,其中一個帶有默認值,依次執行,都能獲得預期的結果。spa

function func1(name = "strick", age) {
  return name;
}
function func2(name, age = 28) {
  return age;
}
func1(undefined);       //"strick"
func2("strick");        //28

3)默認值rest

  參數的默認值既能夠是簡單的字面量,也能夠是複雜的表達式。在每次調用函數時,不只參數會被從新初始化,默認值若是是表達式的話,還會將其從新計算一次。code

function expression1(name, full = "pw" + name) {
  return full;
}
expression1("strick");         //"pwstrick"
expression1("freedom");        //"pwfreedom"

  在上面的代碼中,調用了兩次expression1()函數,返回的結果互不影響。而且full參數的默認值引用了前面的name參數,這是一種有效的語法,但反之就會報錯,以下所示。htm

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;

  在第一篇中曾提到用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正處在臨時死區中,所以在訪問它時會報錯。

  除了以上所列的特性以外,在以前的第三篇的參數解構中,還介紹瞭解構默認值和參數默認值結合使用時的注意點。

2、函數屬性

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屬性可返回形參個數(即聲明時的參數),但它的值會受剩餘參數(已在第二篇中作過介紹)和默認參數的影響,以下代碼所示。

(function rest(name, ...args){ }).length;             //1
(function rest(name, age = 28){ }).length;            //1
(function rest(name, age = 28, school){ }).length;    //1

  根據上面的代碼可知,形參個數的統計會忽略剩餘參數,而且止於默認參數。

3、塊級函數

  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()函數所處的代碼塊還未被執行(即還未聲明),所以會拋出未定義的引用錯誤。

4、元屬性

  元屬性(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。

相關文章
相關標籤/搜索