ES6里关于函数的拓展(二)

一、构造函数

  Function构造函数是JS语法中很少被用到的一部分,通常我们用它来动态创建新的函数。这种构造函数接受字符串形式的参数,分别为函数参数及函数体

var add = new Function("first", "second", "return first + second");
console.log(add(1, 1)); // 2

  ES6增强了Function构造函数的功能,支持在创建函数时定义默认参数和不定参数。唯一需要做的是在参数名后添加一个等号及一个默认值

var add = new Function("first", "second = first","return first + second");
console.log(add(1, 1)); // 2
console.log(add(1)); // 2
//在这个示例中,调用add(1)时只传入一个参数,参数second被赋值为first的值。这种语法与不使用Function声明函数很像

  定义不定参数,只需在最后一个参数前添加...

var pickFirst = new Function("...args", "return args[0]");
console.log(pickFirst(1, 2)); // 1

  在这段创建函数的代码中,只定义了一个不定参数,函数返回传入的第一个参数。

  对于Function构造函数,新增的默认参数和不定参数这两个特性使其具备了与声明式创建函数相同的能力。

二、参数尾逗号

  ES6允许函数的最后一个参数有尾逗号(trailing comma)。此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。

function clownsEverywhere(
  param1,
  param2
) { /* ... */ }

clownsEverywhere(
  'foo',
  'bar'
);
//上面代码中,如果在param2或bar后面加一个逗号,就会报错。

  如果像上面这样,将参数写成多行(即每个参数占据一行),以后修改代码的时候,想为函数clownsEverywhere添加第三个参数,或者调整参数的次序,就势必要在原来最后一个参数后面添加一个逗号。这对于版本管理系统来说,就会显示添加逗号的那一行也发生了变动。这看上去有点冗余,因此新的语法允许定义和调用时,尾部直接有一个逗号。这样的规定使得函数参数与数组和对象的尾逗号规则保持一致了

三、name属性

  由于在JS中有多种定义函数的方式,因而辨别函数就是一项具有挑战性的任务。此外,匿名函数表达式的广泛使用更是加大了调试的难度,开发者们经常要追踪难以解读的栈记录。为了解决这些问题,ES6为所有函数新增了name属性。

  ES6中所有的函数的name属性都有一个合适的值 。

function doSomething() {
    // ...
}
var doAnotherThing = function() {
    // ...
};
console.log(doSomething.name); // "doSomething"
console.log(doAnotherThing.name); // "doAnotherThing"
//在这段代码中,dosomething()函数的name属性值为"dosomething",对应着声明时的函数名称;匿名函数表达式doAnotherThing()的name属性值为"doAnotherThing",对应着被赋值为该匿名函数的变量的名称

  特殊尽情况:管确定函数声明和函数表达式的名称很容易,ES6还是做了更多的改进来确保所有函数都有合适的名称

var doSomething = function doSomethingElse() {
    // ...
};
var person = {
    get firstName() {
        return "huochai"
    },
    sayName: function() {
        console.log(this.name);
    }
}
console.log(doSomething.name); // "doSomethingElse"
console.log(person.sayName.name); // "sayName"
var descriptor = Object.getOwnPropertyDescriptor(person, "firstName");
console.log(descriptor.get.name); // "get firstName"

  在这个示例中,dosomething.name的值为"dosomethingElse",是由于函数表达式有一个名字,这个名字比函数本身被赋值的变量的权重高

  person.sayName()的name属性的值为"sayName",因为其值取自对象字面量。与之类似,person.firstName实际上是一个getter函数,所以它的名称为"get firstName",setter函数的名称中当然也有前缀"set"

  还有另外两个有关函数名称的特例:

  (1)通过bind()函数创建的函数,其名称将带有"bound"前缀;

  (2)通过Function构造函数创建的函数,其名称将带有前缀"anonymous"

var doSomething = function() {
    // ...
};
console.log(doSomething.bind().name); // "bound doSomething"
console.log((new Function()).name); // "anonymous"

  绑定函数的name属性总是由被绑定函数的name属性及字符串前缀"bound"组成,所以绑定函数dosomething()的name属性值为"bound dosomething"

  注意:函数name属性的值不一定引用同名变量,它只是协助调试用的额外信息,所以不能使用name属性的值来获取对于函数的引用

四、判断调用

  ES5中的函数结合new使用,函数内的this值将指向一个新对象,函数最终会返回这个新对象

function Person(name) {
    this.name = name;
}
var person = new Person("huochai");
var notAPerson = Person("huochai");
console.log(person); // "[Object object]"
console.log(notAPerson); // "undefined"

  给notAperson变量赋值时,没有通过new关键字来调用person(),最终返回undefined(如果在非严格模式下,还会在全局对象中设置一个name属性)。只有通过new关键字调用person()时才能体现其能力,就像常见的JS程序中显示的那样。

  而在ES6中,函数混乱的双重身份终于将有一些改变

  JS函数有两个不同的内部方法:[[Call]]和[[Construct]]

  当通过new关键字调用函数时,执行的是[[construct]]函数,它负责创建一个通常被称作实例的新对象,然后再执行函数体,将this绑定到实例上

  如果不通过new关键字调用函数,则执行[[call]]函数,从而直接执行代码中的函数体

  具有[[construct]]方法的函数被统称为构造函数

  注意:不是所有函数都有[[construct]]方法,因此不是所有函数都可以通过new来调用

1、ES5判断函数被调用

  在ES5中,如果想确定一个函数是否通过new关键字被调用,或者说,判断该函数是否作为构造函数被调用,最常用的方式是使用instanceof操作符

function Person(name) {
    if (this instanceof Person) {
        this.name = name; // 使用 new
    } else {
        throw new Error("You must use new with Person.")
    }
}
var person = new Person("huochai");
var notAPerson = Person("huochai"); // 抛出错误

  在这段代码中,首先检查this的值,看它是否为构造函数的实例,如果是,则继续正常执行。如果不是,则抛出错误。由于[[construct]]方法会创建一个person的新实例,并将this绑定到新实例上,通常来讲这样做是正确的。但这个方法也不完全可靠,因为有一种不依赖new关键字的方法也可以将this绑定到person的实例上。

function Person(name) {
    if (this instanceof Person) {
        this.name = name; // 使用 new
    } else {
        throw new Error("You must use new with Person.")
    }
}
var person = new Person("huochai");
var notAPerson = Person.call(person, "huochai"); // 不报错

  调用person.call()时将变量person传入作为第一个参数,相当于在person函数里将this设为了person实例。对于函数本身,无法区分是通过person.call()(或者是person.apply())还是new关键字调用得到的person的实例

2、元属性new.target

  为了解决判断函数是否通过new关键字调用的问题,ES6引入了new.target这个元属性。元属性是指非对象的属性,其可以提供非对象目标的补充信息(例如new)。当调用函数的[[construct]]方法时,new.target被赋值为new操作符的目标,通常是新创建对象实例,也就是函数体内this的构造函数;如果调用[[call]]方法,则new.target的值为undefined

  有了这个元属性,可以通过检查new.target是否被定义过,检测一个函数是否是通过new关键字调用的

function Person(name) {
    if (typeof new.target !== "undefined") {
        this.name = name; // 使用 new
    } else {
        throw new Error("You must use new with Person.")
    }
}
var person = new Person("huochai");
var notAPerson = Person.call(person, "match"); // 出错!

  也可以检查new.target是否被某个特定构造函数所调用

function Person(name) {
    if (new.target === Person) {
        this.name = name; // 使用 new
    } else {
        throw new Error("You must use new with Person.")
    }
}
function AnotherPerson(name) {
    Person.call(this, name);
}
var person = new Person("huochai");
var anotherPerson = new AnotherPerson("huochai"); // 出错!

  在这段代码中,如果要让程序正确运行,new.target一定是person。当调用 new Anotherperson("huochai") 时, 真正的调用Person. call(this,name)没有使用new关键字,因此new.target的值为undefined会抛出错误

  注意:在函数外使用new.target是一个语法错误。

五、块级函数

  在ES5中,在代码块中声明一个函数(即块级函数)严格来说应当是一个语法错误, 但所有的浏览器都支持该语法。不幸的是,每个浏览器对这个特性的支持都稍有不同,所以最好不要在代码块中声明函数,更好的选择是使用函数表达式。

   为了遏制这种不兼容行为, ES5的严格模式为代码块内部的函数声明引入了一个错误

"use strict";
if (true) {
    // 在 ES5 会抛出语法错误, ES6 则不会
    function doSomething() {
        // ...
    }
}
  //在ES5中,代码会抛出语法错误。而在ES6中,会将dosomething()函数视为一个块级声明,从而可以在定义该函数的代码块内访问和调用它

"use strict";
if (true) {
    console.log(typeof doSomething); // "function"
    function doSomething() {
        // ...
    }
    doSomething();
}
console.log(typeof doSomething); // "undefined"

  在定义函数的代码块内,块级函数会被提升至顶部,所以typeof dosomething的值为"function",这也佐证了,即使在函数定义的位置前调用它,还是能返回正确结果。但是一旦if语句代码块结束执行,dosomething()函数将不再存在

1、使用场景

  块级函数与let函数表达式类似,一旦执行过程流出了代码块,函数定义立即被移除。二者的区别是,在该代码块中,块级函数会被提升至块的顶部,而用let定义的函数表达式不会被提升

"use strict";
if (true) {
    console.log(typeof doSomething); // 抛出错误
    let doSomething = function () {
        // ...
    }
    doSomething();
}
console.log(typeof doSomething);

  在这段代码中,当执行到typeof dosomething时,由于此时尚未执行let声明语句,dosomething()还在当前块作用域的临时死区中,因此程序被迫中断执行

  因此,如果需要函数提升至代码块顶部,则选择块级函数;如果不需要,则选择let表达式。

2、非严格模式

  在ES6中,即使处于非严格模式下,也可以声明块级函数,但其行为与严格模式下稍有不同。这些函数不再提升到代码块的顶部,而是提升到外围函数或全局作用域的顶部

// ES6 behavior
if (true) {
    console.log(typeof doSomething); // "function"
    function doSomething() {
        // ...
    }
    doSomething();
}
console.log(typeof doSomething); // "function"

  在这个示例中,dosomething()函数被提升至全局作用域,所以在if代码块外也可以访问到。

  ES6将这个行为标准化了,移除了之前存在于各浏览器间不兼容的行为,所以所有ES6的运行时环境都将执行这一标准。

原文地址:https://www.cnblogs.com/goloving/p/7887267.html