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。

相关文章
相关标签/搜索