一、构造函数

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

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

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

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

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

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

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

  对于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的运行时环境都将执行这一标准。

ES6里关于函数的拓展(二)的更多相关文章

  1. ES6里关于函数的拓展(三)

    一.箭头函数 在ES6中,箭头函数是其中最有趣的新增特性.顾名思义,箭头函数是一种使用箭头(=>)定义函数的新语法,但是它与传统的JS函数有些许不同,主要集中在以下方面: 1.没有this.su ...

  2. ES6里关于函数的拓展(一)

    一.形参默认值 Javascript函数有一个特别的地方,无论在函数定义中声明了多少形参,都可以传入任意数量的参数,也可以在定义函数时添加针对参数数量的处理逻辑,当已定义的形参无对应的传入参数时为其指 ...

  3. 进阶路上有你我-相互相持篇之ES6里箭头函数里的this指向问题

    首先复习下普通函数里的this指向: function test(){ console.log(this) } test() 你会秒杀的毫无疑问的回答:window,针对普通函数:谁调用了函数  函数 ...

  4. ES6里关于类的拓展(二):继承与派生类

    继承与派生类 在ES6之前,实现继承与自定义类型是一个不小的工作.严格意义上的继承需要多个步骤实现 function Rectangle(length, width) { this.length = ...

  5. ES6里关于类的拓展(一)

    大多数面向对象的编程语言都支持类和类继承的特性,而JS却不支持这些特性,只能通过其他方法定义并关联多个相似的对象,这种状态一直延续到了ES5.由于类似的库层出不穷,最终还是在ECMAScript 6中 ...

  6. ES6里关于作用域的拓展:块级作用域

    过去,javascript缺乏块级作用域,var声明时的声明提升.属性变量等行为让人困惑.ES6的新语法可以帮助我们更好地控制作用域. 一.var声明 1.变量提升:var声明会发生“变量提升”现象, ...

  7. ES6里关于数字的拓展

    一.指数运算符 ES6引入的唯一一个JS语法变化是求幂运算符,它是一种将指数应用于基数的数学运算.JS已有的Math.pow()方法可以执行求幂运算,但它也是为数不多的需要通过方法而不是正式的运算符来 ...

  8. ES6里关于字符串的拓展

    一.子串识别 自从 JS 引入了 indexOf() 方法,开发者们就使用它来识别字符串是否存在于其它字符串中.ES6 包含了以下三个方法来满足这类需求: 1.includes():该方法在给定文本存 ...

  9. ES6里箭头函数的陷阱

    ECMAScript 6新增了箭头函数 原来的匿名函数 function(){},现在可以简化成()=>{} 看起来高大上,像C#什么的语法. 但是箭头函数的this对象,不能更改,总是指向函数 ...

随机推荐

  1. hdu 1811 Rank of Tetris (拓扑 & 并查集)

    Rank of Tetris Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)To ...

  2. flutter channel master

    flutter可能是未来跨平台开发的又一技术框架,那么对于一个app,我们不可能完全用flutter来开发,那么就意味着我们需要在已有的Android和iOS代码中去集成flutter.目前这一技术还 ...

  3. [poj] 1269 [zoj] 1280 Interesting Lines || 求两直线交点

    POJ原题 ZOJ原题 多组数据.每次给出四个点,前两个点确定一条直线,后两个点确定一条直线,若平行则输出"NONE",重合输出"LINE",相交输出" ...

  4. spring in action学习笔记十五:配置DispatcherServlet和ContextLoaderListener的几种方式。

    在spring in action中论述了:DispatcherServlet和ContextLoaderListener的关系,简言之就是DispatcherServlet是用于加载web层的组件的 ...

  5. Linux 安装配置JDK

    一.下载jdk 参考:http://www.codingyun.com/article/40.html 可以先下载到本地,然后ftp到服务器 也可以直接在服务器下载(windows版本的区分32位与6 ...

  6. 【ZOJ4061】Magic Multiplication(构造)

    题意:定义一个新运算为两个数A,B上每一位相乘,然后顺次接在一起,现在给定结果C和原来两个数字的长度,要求恢复成原来的数字A,B 若有多解输出A字典序最小的,A相同输出B字典序最小的,无解输出Impo ...

  7. Java I/O 笔记

    1. Java常用I/O类概述 2. 文件I/O 你可以根据该文件是二进制文件还是文本文件来选择使用FileInputStream(FileOutputStream)或者FileReader(File ...

  8. [ CodeVS冲杯之路 ] P1166

    不充钱,你怎么AC? 题目:http://codevs.cn/problem/1166/ 有许久没有刷题了,忙着过中秋去了嘿嘿 首先它的每一行是独立的,我们可以直接把它拆分成 n 互不相关的子问题做 ...

  9. 杭电oj2064、2067、2068、2073、2076-2078、2080、2083-2085

    2064  汉诺塔III #include<stdio.h> int main(){ int n,i; _int64 s[]; while(~scanf("%d",&a ...

  10. centos 下文件夹共享

    [root@localhost share]# yum install samba -y[root@localhost share]# cp /etc/samba/smb.conf /etc/samb ...