第10章 函数【ES6部分】

1. ES6 箭头函数:一对圆括号包含命名参数,后跟一个胖箭头(=>),加函数体。

可以实现和普通函数一样的功能,不同之处在于:

(1)无法使用 arguments、super 、new.target 等,同时不能用于构造函数,箭头函数没有 prototype 属性.

在箭头函数中使用 arguments 时,会报错 ReferenceError:arguments is not defined.

(2)当命名参数只有一个时,可以省略圆括号;没有命名参数,或者命名参数多于一个,不可省略

(3)当函数体只有一行时,可以省略大括号,省略大括号时隐式返回代码值,意味着不需要写 return;

2. ES6 可以显式定义默认参数,不需要手动添加 if 条件判断命名参数是否为 undefined;直接在定义命名参数时用等号赋值即可。

几个易混淆的例子:

// 当命名参数有 ES6 显示设置默认值时,arguments的值只和传入的参数值有关,和默认值无关,且改变命名参数值,不会影响arguments的值
function add(num1 = 20, num2) {
  num1 = 15;
  console.log(arguments[0]);
}
add(1); // 1,说明 num1 = 15; 改变命名参数的值,没有影响到 arguments[0] 的值

function add1(num1 = 20, num2) { num1 = 15; console.log(arguments[0]); } add1(); // undefined,说明 arguments[0] 的值只和传入的参数值有关,和默认值无关

function add2(num1, num2) { num1 = 15; console.log(arguments[0]); } add2(1); // 15,说明不使用默认值时且传入值时,改变命名参数的值会影响到 arguments[0] 的值

function add3(num1, num2) { num1 = 15; console.log(arguments[0]); } add3(); // undefined,当不传入值时,改变命名参数的值不会影响到 arguments[0] 的值

3. ES6 模板字符串...

4. ES6 扩展操作符:也就是常说的 "三个点",【1】它可以将可迭代对象拆分,获取可迭代对象中的每个值。【2】同时它也可以收集参数,类似于 arguments 的作用,将传入的多个单独命名参数值组合为一个数组,不过区别是 arguments 是类数组,而收集参数时是 Array 实例。

注意:由于收集参数时,扩展操作符收集参数长度可变,因此只能作为最后一个函数参数,若放在其他参数前,会报错:No parameter is allowed after rest parameter

function getSum(...values) {
  // 顺序累加 values 中的所有值
  // 初始值的总和为 0 
  return values.reduce((x, y) => x + y, 0);
}
console.log(getSum(1,2,3)); // 6 【收集参数用法】
let values = [1, 2, 3, 4];
function getSum() {
  let sum = 0;
  for (let i = 0; i < arguments.length; ++i) {
    sum += arguments[i];
  }
  return sum;
}】
console.log(getSum(...values)); // 10 【拆分可迭代对象用法】

 5. 注意函数声明和函数表达式两种方式定义函数的重要区别是:函数声明具有函数声明提升功能,而函数表达式不具备。红宝书 P305 有具体例子。

console.log(sum(10, 10)); // TypeError: sum is not a function
var sum = function(num1, num2) {
  return num1 + num2;
};

6. arguments.callee:主要作用就是在函数递归时,进行解耦合,arguments.callee 属性是指向 arguments 当前所在函数的指针。见 红宝书 P299,以及 P306

7. this 指向:在标准函数中,this 指向的是将所在函数当成方法调用的上下文对象,而在箭头函数中,this 指向的是定义箭头函数时的上下文。

8. 函数对象的 caller 属性,它指向调用当前函数的函数,如果在全局作用域调用函数,则 caller 值为 null.

9. ES6 新增 new.target 属性,检测函数是否是使用 new 关键字调用的。直接看实例即可:

function King() {
  if (!new.target) {
    throw 'King must be instantiated using "new"'
  }
  console.log('King instantiated using "new"');
}
new King(); // King instantiated using "new" 
King(); // Error: King must be instantiated using "new"

10. ES6 尾调用优化:当外部函数的返回值是内部函数的返回值时,P307 - P310

function outerFunction() {
  return innerFunction(); // 尾调用
}
ES6 优化之前,执行这个例子会在内存中发生如下操作。
(1) 执行到 outerFunction 函数体,第一个栈帧被推到栈上。
(2) 执行 outerFunction 函数体,到 return 语句。计算返回值必须先计算 innerFunction。
(3) 执行到 innerFunction 函数体,第二个栈帧被推到栈上。
(4) 执行 innerFunction 函数体,计算其返回值。
(5) 将返回值传回 outerFunction,然后 outerFunction 再返回值。
(6) 将栈帧弹出栈外。
在 ES6 优化之后,执行这个例子会在内存中发生如下操作。
(1) 执行到 outerFunction 函数体,第一个栈帧被推到栈上。
(2) 执行 outerFunction 函数体,到达 return 语句。为求值返回语句,必须先求值 innerFunction。
(3) 引擎发现把第一个栈帧弹出栈外也没问题,因为 innerFunction 的返回值也是 outerFunction
的返回值。
(4) 弹出 outerFunction 的栈帧。
(5) 执行到 innerFunction 函数体,栈帧被推到栈上。
(6) 执行 innerFunction 函数体,计算其返回值。
(7) 将 innerFunction 的栈帧弹出栈外。
我们可以看到,优化前,每次嵌套都会多占用一个栈帧,而优化后,永远只占用一个栈帧。
尾调用优化的条件:
【1】代码在严格模式下执行;防止尾调用函数引用外部函数的 arguments 等属性。
【2】外部函数的返回值是对尾调用函数的调用;
【3】尾调用函数返回后不需要执行额外的逻辑;
【4】尾调用函数不是引用外部函数作用域中自由变量的闭包。

11. 闭包 P310 会开单章随笔说明......

posted @ 2021-10-14 14:41  TwinkleG  Views(131)  Comments(0)    收藏  举报