1.函数与解构赋值的默认值结合使用
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
function m2({x, y} = { x: 0, y: 0 }) { //参数的默认值是一个有具体属性的对象
return [x, y];
}
// x 有值,y 无值的情况
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]
ps:函数的length属性:返回没有指定默认值的参数个数,如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。
2.rest参数(...变量名)别名:剩余运算符
rest参数用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的 变量是一个数组,该变量将多余的参数放入数组中。
function sumRest (...m) {
var total = 0;
for(var i of m){
total += i;
}
return total;
}
console.log(sumRest(1,2,3)); //6 传递给 sumRest 函数的一组参数值,被整合成了数组 m。
使用arguments参数:
function sumArgu () {
var result = 0;
for (var i = 0; i < arguments.length; i++) {
result += arguments[i];
}
return result
}
console.log(sumArgu(1,2,3));//6
注意:rest参数可理解为剩余的参数,所以必须在最后一位定义,后面不能有别的参数,如果定义在中间会报错。
var array = [1,2,3,4,5,6];
var [a,b,...c] = array;
3.严格模式
(只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式)
如何避免:1.设定全局性的严格模式。2.把函数包在一个无参数的立即执行函数里面。
4.箭头函数(重点)
普通函数和箭头函数的区别:
(1)没有this
箭头函数没有this,需要查找作用域链确定this。则如果箭头函数被非箭头函数包含, this 绑定的就是最近一层非箭头函数的 this。
例子:
<button id="button">点击变色</button>
function Button(id) {
this.element = document.querySelector("#" + id);
this.bindEvent();
}
Button.prototype.bindEvent = function() {
this.element.addEventListener("click", this.setBgColor, false); // 注册事件
// this 值是该元素的引用this = Button {element: button#button}
};
Button.prototype.setBgColor = function() {
//this 指向的是button 本身 <button id="button">点击变色</button>
this.element.style.backgroundColor = '#1abc9c' //所以这里会报错
this.style.backgroundColor = '#1abc9c' // 直接修改
};
e6的方法:
Button.prototype.bindEvent = function() { this.element.addEventListener("click", event => this.setBgColor(event), false);
};
var button = new Button("button");
因为箭头函数没有 this,所以也不能用 call()、apply()、bind() 这些方法改变 this 的指向
(2)没有arguments
arguments是传递给函数的参数的类数组对象
例子:
function func1(a, b, c) {
console.log(arguments[0]); // expected output: 1
console.log(arguments[1]); // expected output: 2
console.log(arguments[2]) // expected output: 3
}
func1(1, 2, 3);
但是,箭头函数可以访问外围函数的 arguments 对象
例子:
function constant() {
return () => arguments[0]
}
var result = constant(1);
console.log(result()); // 1
如何访问箭头函数的参数?
通过命名参数或者 rest 参数的形式访问参数。
例子:
let nums = [1,2,3]
let num = (...nums) =>{console.log(nums)} // [1,2,3]
num(1,2,3)
(3)不能通过new关键字调用
var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor
//箭头函数并没有 [[Construct]] 方法,不能被用作构造函数,如果通过 new 的方式调用,会报错
因为JavaScript 函数有两个内部方法:[[Call]] 和 [[Construct]]。
当通过 new 调用函数时,执行 [[Construct]] 方法,创建一个实例对象,然后再执行函 数体,将 this 绑定到实例上。
执行new这个操作后的内部步骤:
1.创建一个空对象,做为将要返回的对象实例。
2.将这个空对象的原型,指向构造函数的 prototype 属性
3.将这个空对象赋值给函数内部的 this 关键字
4.开始执行构造函数内部的代码
如果不用new关键字调用的函数,执行的是[[call]]方法,直接执行代码中的函数体。
(4)没有new.target
(5)没有原型
由于不能使用 new 调用箭头函数,所以也没有构建原型的需求,于是箭头函数也不存在 prototype 这个属性。
(6)没有super
补充:箭头函数不适用的场景:
1.定义对象的方法,且该方法内部包括this
const cat = {
lives: 9,
jumps: () => {
this.lives--;
}
}
//对象不构成单独的作用域,调用cat.jumps()时,this指向全局,如果是普通函数,this指向cat
2.需要动态this
5.尾调优化(只保留内层函数的调用帧)
是指某个函数的最后一步是调用另一个函数。函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();
// 如果g()不是尾部调用,那么f()需要保存m,n变量,g()调用的位置等信息,但是因为此处是尾部调用,
则只保留g(3)的调用帧
注意:只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。
6.尾递归(函数调用自身)
正常递归:(时间复杂度o(n),保存n个调用记录)function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
factorial(5) // 120
尾递归:(时间复杂度o(1),保存最后一个调用记录)function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5, 1) // 120
ES6 中只要使用尾递归,就不会发生栈溢出(或者层层递归造成的超时),相对节省内存。
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。
此处有个函数柯里化,后面遇到,详细看
7.函数参数的尾逗号
允许函数的最后一个参数有尾逗号(在此之前函数的定义和调用,都不允许最后一个参数出现逗号)
8.function.prototype.toString()
要求返回一模一样的原始代码(以前返回的代码会自动省略注释和空格)
9.catch命令参数省略
try {
// ...
} catch { // 原来是catch(err) 现在可以省略这个参数
}