在前面的章节中,已陆陆续续介绍了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"); //

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;

  在第一篇中曾提到用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正处在临时死区中,因此在访问它时会报错。

  除了以上所列的特性之外,在之前的第三篇的参数解构中,还介绍了解构默认值和参数默认值结合使用时的注意点。

二、函数属性

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;             //
(function rest(name, age = 28){ }).length; //
(function rest(name, age = 28, school){ }).length; //

  根据上面的代码可知,形参个数的统计会忽略剩余参数,并且止于默认参数。

三、块级函数

  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躬行记(14)——函数的更多相关文章

  1. ES6躬行记(1)——let和const

    古语云:“纸上得来终觉浅,绝知此事要躬行”.的确,不管看了多少本书,如果自己不实践,那么就很难领会其中的精髓.自己研读过许多ES6相关的书籍和资料,平时工作中也会用到,但在用到时经常需要上搜索引擎中查 ...

  2. ES6躬行记 笔记

    ES6躬行记(18)--迭代器 要实现以下接口## next() ,return,throw 可以用for-of保证迭代对象的正确性 例如 var str = "向

  3. ES6躬行记(15)——箭头函数和尾调用优化

    一.箭头函数 箭头函数(Arrow Function)是ES6提供的一个很实用的新功能,与普通函数相比,不但在语法上更为简洁,而且在使用时也有更多注意点,下面列出了其中的三点: (1)由于不能作为构造 ...

  4. ES6躬行记(21)——类的继承

    ES6的继承依然是基于原型的继承,但语法更为简洁清晰.通过一个extends关键字,就能描述两个类之间的继承关系(如下代码所示),在此关键字之前的Man是子类(即派生类),而在其之后的People是父 ...

  5. ES6躬行记(3)——解构

    解构(destructuring)是一种赋值语法,可从数组中提取元素或从对象中提取属性,将其值赋给对应的变量或另一个对象的属性.解构地目的是简化提取数据的过程,增强代码的可读性.有两种解构语法,分别是 ...

  6. ES6躬行记(7)——代码模块化

    在ES6之前,由于ECMAScript不具备模块化管理的能力,因此往往需要借助第三方类库(例如遵守AMD规范的RequireJS或遵循CMD规范的SeaJS等)才能实现模块加载.而自从ES6引入了模块 ...

  7. ES6躬行记(4)——模板字面量

    模板字面量(Template Literal)是一种能够嵌入表达式的格式化字符串,有别于普通字符串,它使用反引号(`)包裹字符序列,而不是双引号或单引号.模板字面量包含特定形式的占位符(${expre ...

  8. ES6躬行记(20)——类

    ES6正式将类(Class)的概念在语法层面标准化,今后不必再用构造函数模拟类的行为.而ES6引入的类本质上只是个语法糖(即代码更为简洁.语义更为清晰),其大部分功能(例如继承.封装和复用等)均可在E ...

  9. ES6躬行记(19)——生成器

    根据ES6制订的标准自定义迭代器实现起来比较复杂,因此ES6又引入了生成器的概念,生成器(Generator)是一个能直接创建并返回迭代器的特殊函数,可将其赋给可迭代对象的Symbol.iterato ...

随机推荐

  1. C++第三课:类的使用(一)[个人见解]

    说到C++语言的类,也称对象.在C++中首先得了解的三大特性:继承.封装.多态. 使用C++类,间接的反映出你所学习C++的深度,这章很重要,但小编未必能全部讲到,还望谅解. 类是C++语言中新添加的 ...

  2. Django model对象接口

    Django model查询 # 直接获取表对应字段的值,列表嵌元组形式返回 Entry.objects.values_list('id', 'headline') #<QuerySet [(1 ...

  3. MORE XOR

    MORE XOR #include<bits/stdc++.h> using namespace std; ; int a[maxn]; ][maxn]; int main() { ios ...

  4. Katalon Studio之请求响应中文乱码解决方法

    最近在用Katalon做接口测试过程中发现请求响应消息中返回的中文均为乱码,这是因为我们使用的系统环境在初始安装时选择的中文简体,导致windows系统默认编码格式为GBK,但是KS的编码格式是UTF ...

  5. java实现多线程使用多个代理ip的方式爬取网页页面内容

    项目的目录结构 核心源码: package cn.edu.zyt.spider; import java.io.BufferedInputStream; import java.io.FileInpu ...

  6. CSS面试细节整理(二)

    5.css盒模型: CSS 框模型 (Box Model) 规定了元素框处理元素内容.内边距.边框 和 外边距 的方式

  7. 基于js的数据结构与算法-数组

    Given an array of integers, return indices of the two numbers such that they add up to a specific ta ...

  8. 【开源项目】电视盒子好用又强大的APP: TVRemoteIME

    TVRemoteIME 电视盒子的远程输入法应用,可跨屏远程输入.跨屏远程控制盒子.远程文件管理.HTTP/RTMP/MMS网络视频直播.ED2K/种子文件的视频文件边下边播 应用的诞生 自从家里有电 ...

  9. 【安富莱专题教程第5期】工程调试利器RTT实时数据传输组件,替代串口调试,速度飞快,可以在中断和多任务中随意调用

    说明:1.串口作为经典的调试方式已经存在好多年了,缺点是需要一个专门的硬件接口.现在有了SEGGER的RTT(已经发布有几年了),无需占用系统额外的硬件资源,而且速度超快,是替代串口调试的绝佳方式.2 ...

  10. 分布式数据中间件TDDL、Amoeba、Cobar、MyCAT架构比较

    框架比较 TDDL Amoeba Cobar MyCat 点评 TDDL不同于其它几款产品,并非独立的中间件,只能算作中间层,是以Jar包方式提供给应用调用.属于JDBC Shard的思想,网上也有很 ...