@Dale-Lin
2018-06-17T21:22:39.000000Z
字数 6021
阅读 795
深入理解ES6
ES6中简化了为形参提供默认值得过程:
function makeRequest(url, timeout = 2000, callback = function() {}){
//some code
}
这个函数中,只有第一个参数是被认为总是要为其传入值的,其他两个参数都有默认值。
在已指定默认值的参数后可以继续声明无默认值参数。
切记,当使用默认参数值时,arguments 对象的行为与以往不同。
ES5中:
function mixArgs(first, second){
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = "c";
second = "d";
console.log(first === arguments[0];
console.log(second === arguments[1];
}
mixArgs("a", "b"); //true, true, true, true
在非严格模式下,命名参数的变化会同步更新到 arguments 对象中,所以当 first 和 second 被赋予新值时,arguments[0] 和 arguments[1] 相应地也就更新了,最终所有===全等比较的结果为 true。
如果在严格模式下,无论参数如何变化,arguments 对象不再随之改变:
function mixArgs(first, second){
"use strict";
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = "c";
second = "d";
console.log(first === arguments[0];
console.log(second === arguments[1];
}
mixArgs("a", "b"); //true, true, false, false
在ES6中,如果一个函数使用了默认参数值,无论是否显示定义了严格模式,arguments 对象的行为都将与ES5严格模式下的保持一致:
function mixArgs(first, second = "b") {
console.log(arguments.length);
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = "c";
second = "d";
console.log(first === arguments[0]);
console.log(second === arguments[1]);
}
mixArgs("a"); //1, true, false, false, false, false
first、second 并不会影响 arguments 对象。
可以通过函数执行来设置默认参数值:
function getValue(){
return 5;
}
function add(first, second = getValue()){
return first + second;
}
console.log(add(1, 1)); //2
console.log(add(1)); //6
函数声明的时候并没有调用默认复制函数,当不使用默认赋值(传入参数值)的时候,也不调用。只有在调用 add() 函数,且不传入第二个参数的时候才会调用。
稍微改动一下,让它每次返回不同的值:
let value = 5;
function getValue() {
return value++;
}
function add(first, second = getValue()) {
return first + second;
}
console.log(add(1, 1)); //2
console.log(add(1)); //6
console.log(add(1)); //7
因为默认函数是在函数调用时求值,所以可以使用先定义的参数作为后定义参数的默认值:
function add(first, second = first){
return first + second;
}
也可以对先定义的参数进行加工再作为后定义参数的默认值。
注意,如果操作相反会因为参数的临时死区问题造成错误。
在函数的命名参数前添加三个点(...)就表明这是一个不定参数,该参数为一个数组,包含着它之后传入的所有参数。
对象字面量 setter 的参数有且只能有一个。
如果声明函数时定义了不定参数,则在函数被调用时,arguments 对象包含了所有传入函数的参数:
function checkArgs(...args){
console.log(args.length);
console.log(arguments.length);
console.log(args[0], arguments[0]);
console.log(args[1],argumets[1]);
}
checkArgs('a', 'b');
//2
//2
//a a
//b b
无论是否使用不定参数 arguments 对象总是包含所有传入函数的参数。
不说了。
为了更好地调试广泛使用的匿名函数,ES6 为所有函数新增了 name 属性
ES6 中所有的函数的 name 属性都有一个合适的值:
function doSomething(){
//empty
}
var doAnotherThing = function (){
//empty
};
console.log(doSomething.name); //"doSomething"
console.log(doAnotherThing.name); //"doAnotherThing"
ES6做了很多改进来确保所有函数都有合适的名称:
var doSomething = function doSomethingElse(){
//empty
};
var person = {
get firstName(){
return "Nicholas";
},
sayName: function(){
console.log(this.name);
}
};
console.log(doSomething.name); //"doSomethingElse"
console.log(person.sayName.name); //"sayName"
console.log(person.firstName.name); //"get firstName"
在确定函数的 name 属性时:
1. 函数表达式名字 > 函数赋值变量名字;
2. 对象属性值取自对象字面量;
3. getter 函数的名称中有前缀 "getter";
4. setter 函数的名称中有前缀 "setter";
5. 通过 bind() 函数创建的函数,有前缀 "bound";
6. 通过 Function 构造函数创建的函数,有前缀 "anonymous"。
切记,函数 name 属性只是协助调试用的额外信息。
为了判断函数是否通过 new 关键字调用,ES6 引入了 new.target 这个元属性。
元属性是非对象的属性,可以提供非对象目标的补充信息。
当调用函数的构造方法时,new.target 被赋值为 new 操作符的目标,通常是新创建的对象示例,就是函数体内 this 的构造函数;如果调用 call 方法,则为 undefined。
可以通过该元属性来判断某个函数是否是通过 new 关键字调用的:
function Person(name){
if (typeof new.target !== "undefined") {
this.name = name;
} else {
throw new Error("必须通过 new 关键字来调用 Person。");
}
}
var person = new Person("Nicholas");
var notAPerson = Person.call(person, "Michael"); //throw an error!
ES5以前,严格模式不允许在代码块中声明块级函数,会抛出错误。
ES6中,会将代码块中的块级函数正确地视作一个块级声明,从而可以在定义该函数的代码块内访问和调用它:
"use strict"
if (true) {
console.log(typeof doSomething); //"function"
function doSomething(){
//empty
}
doSomething();
}
console.log(typeof doSomething); //"undefined"
由于在 if 语句块中,块级函数声明会被提升至顶部,所以 typeof 的值为 "function",一旦离开 if 语句块,函数将不再存在。
块级函数与 let 函数表达式类似,一旦执行过程流出了代码块,函数定义立即被移除。但是 let 定义的函数表达式不会被提升。
如果需要函数提升至代码块顶部,则选择块级函数;否则选择 let 表达式。
ES6 中,若在非严格模式下声明块级函数,函数会提升至外围函数或全局作用域顶部:
if (true) {
console.log(typeof doSomething); //"function"
function doSomething(){
//empty
}
doSomething();
}
console.log(typeof doSomething); //"function"
ES6 箭头函数是一种使用箭头(=>)定义函数的新语法:
当箭头函数只有一个参数时,可以直接写参数名,箭头紧随其后,箭头右侧的表达式被求值后便立即返回,即使没有显式的返回语句,也会返回传入的第一个参数,不需要更多的语法铺垫:
let reflect = value => value;
实际上相当于:
let reflect = function(value) {
return value;
};
若传入两个或以上的参数、或没有参数,则加一对小括号:
let sum = (num1, num2) => num1 + num2;
let getName = () => "Nicholas";
如果要编写多个表达式的传统函数体,需要用花括号包裹,并显式定义返回值:
let sum = (num1, num2) => {
return num1 + num2;
}
如果要返回一个对象字面量,要用括号包裹:
let getTempItem = id => ({id: id, name: "Temp"});
立即执行函数表达式能够在不保存对函数的引用的情况下调用一个函数。当想创建一个与其他程序隔离的作用域时,这种模式很方便。
只要将箭头函数包括在小括号里,就可以实现该功能:
let person = ((name) => {
return {
getName: function() {
return name;
}
};
})("Nicholas");
console.log(person.getName()); //"Nicholas"
参数用另一个括号包裹。
箭头函数中出现的 this 不会绑定在函数执行的时候,而是通过查找作用域来决定:
let PageHandler = {
id: "112345",
init: function() {
document.addEventListener("click",
event => this.doSomething(event.type), false);
},
doSomething: function(type) {
console.log("Handling " + type + " for " + this.id);
}
};
如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this;否则,this 的值会被设置为 undefined。
上例若不用箭头函数,this 会绑定在事件处理函数的触发元素上(这里是 document 对象,除非传统函数使用 bind 方法修改)。
箭头函数中的 this 取决于函数外部非箭头函数的 this 值,且不能通过 call()、apply()、bind()修改箭头函数 this 值(虽然可以使用)。
箭头函数缺少正常函数所拥有的 prototype 属性,他的设计初衷是“即用即弃”,所以不能用来定义新的类型。
箭头函数的语法简介,非常适用于数组处理。
诸如 sort()、map() 及 reduce() 这些可以接受回调函数的数组方法,都可以通过箭头函数语法简化编码过程并减少代码量:
var result = values.sort((a, b) => a - b);
箭头函数没有自己的 arguments 对象:
function a() {
return () => arguments[0];
}
var arrowFunction = a(5);
console.log(arrowFunction); //5
但是箭头函数始终可以访问外围函数的 arguments。
尾调用指的是函数作为另一个函数的最后一条语句被调用:
function doSomething() {
return doSomethingElse(); //尾调用
}
ES6 缩减了严格模式下尾调用栈的大小,如果满足以下条件,尾调用不再创建新的栈帧,而是清楚并重用当前栈帧:
递归函数是最主要的应用场景,例如该阶乘函数:
function factorial(n) {
if (n <= 1) {
return 1;
} else {
//无法优化,必须在返回后执行乘法操作
return n * factorial(n - 1);
}
}
由于在递归调用前执行了乘法操作,因而当前版本的阶乘函数无法被引擎优化。
优化这个函数,首先要确保乘法不会在函数调用后运行,可以通过默认参数来将乘法操作移出 return 语句,函数可以携带着临时结果进入到下一个迭代中:
function factorial(n, p = 1) {
if (n <= 1) {
return 1 * p;
} else {
let result = n * p
//优化后
return factorial(n - 1, result);
}
}