ES6新技术之函数技术

Understanding ECMAScript 6 积累了相当的ES5函数技术的使用经验,ES6也对函数对象技术作了多处的使用上的改进,让其更加的灵活方便。

本文部分内容择译自《Understanding ECMAScript 6 》。

参数

第一大改进是函数的参数的使用,包括:参数的默认值,余参和实参的展开操作。

形式参数的默认值

第一,支持定义形式参数的默认值,这样ES6函数在「参数值」上提供了灵活性,节省函数定义时的手动(extra code)检查参数值的存在与否;

function makeRequest(url, timeout = 2000, callback = function() {}) {
    // the rest of the function
}

// uses default timeout and callback
makeRequest("/foo");

// uses default callback
makeRequest("/foo", 500);

// doesn't use defaults
makeRequest("/foo", 500, function(body) {
    doSomething(body);
});

余参

第二,提供的余参(Rest parameters )新语法,这样新函数在「参数个数」(number of parameters)上提供了灵活性,一种比ES5 arguments更合理更灵活的方式。余参语法(...restPara)自动将调用函数时提供无名(无形式定义)的参数「收集」到一起,组成一个参数数组。

function pick(object, ...keys) {
  let result = Object.create(null);

// 循环不用担心边界,不像arguments包含了所有参数,你循环不希望有的参数
  for (let i = 0, len = keys.length; i < len; i++) {
    result[keys[i]] = object[keys[i]];
  }
  return result;
}

ES6余参相对ES5 arguments对象,ES5 arguments 对形式参数是盲的,不知道函数参数具体定义;而ES6余参明确是“余下的参数”,有一部分参数(余下之外的参数)是明确的。

余参有两项限制:第一,每个函数只有一个余参,且必位于尾部;第二,不用于 对象的setter ,因为setter 只能有一个参数。

参数的默认值(默认参数)让函数可以用「比定义更少的参数」使用函数,而余参则相反,让函数可用「比定义更多的参数」使用函数。

实际参数的展开操作符

函数参数的第三项改进,是在调用(处理数组元素的)函数时,可使用展开操作符(spread operator ),展开「数组型实参」为多个形式参数,简化函数的编程任务;

如果你有一个「处理某个数组的函数方法」——形式参数是该数组的数组项(例如Math.max(25, 50, 75, 100))——时,现在你不必预先手动拆开数组一个一个的传(或改用数组形参,在函数内重新处理),或者用fn.apply()技巧,只需要在调用的时候使用实参操作展开操作符,系统为自动为你拆开数组,再调用。

 let values = [25, 50, 75, 100]

// equivalent to
// console.log(Math.max(25, 50, 75, 100));
console.log(Math.max(...values)); // 100

这里展开操作符有一些使用上假定,假定第一,一个有多个数组元素形式参数的函数方法;第二,展开操作只用于实参展开,这个与形式余参容易混淆,因为语法上都是三点(...);

展开操作符除了用于函数实参的展开调用,还可用以数组创建(数组字面量上子数组的展开),和对象创建(子属性展开)。

const x = ['a', 'b'];
const y = ['c'];
const z = ['d', 'e'];

const arr = [...x, ...y, ...z]; //['a', 'b', 'c', 'd', 'e']

新形式

第二大改进是函数形式的本身,主要是箭头函数,还有块级函数等。

箭头函数

ECMAScript 6 中函数的最大变化是增加了箭头函数,一种新函数形式/形态。箭头函数旨在代替匿名函数表达式。箭头函数具有更简洁的语法、自动的词法"this"绑定,并且没有"arguments"对象。此外,箭头函数不能更改其"this"绑定,因此不能用作构造函数。

块内函数

ECMAScript 6 支持块级(局部分支)的函数定义,这个行为以ES5以前是非法的(严格模式下会报语法错误),因为看似不合理。然而如果某个流程分支如果足够复杂,能抽象出通用代码作函数,也是行得通的,合理的。

"use strict";

if (true) {
  console.log(typeof doSomething);   // throws an error

  let doSomething = function () {
    // empty
  }

  doSomething();
}

console.log(typeof doSomething);

元属性

fn.name属性用于调试追踪

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"

new.target

JS函数有双重角色——普通函数和对象构造函数。ES5之前,没有严格限定开发者将一个普通将一个普通错当构造函数的行为。只能通过instanceof 勉强规避这种行为。现在ES6 提供了函数内部属性 切底规避这种行为。

function Person(name) {

  if (typeof new.target !== "undefined") {
    this.name = name;
  } else {
    throw new Error("You must use new with Person.")
  }
}

var person = new Person("Nicholas");

var notAPerson = Person.call(person, "Michael"); // error!

使用new 操作符时函数的执行情况

JavaScript 对函数内部有两种不同的内部方法:[[Call]] 和[[Construct]]。当调用没有 new 的函数时,将执行[[Call]] 方法,该方法直接执行函数体代码。

而当使用 new 调用函数时,则调用[[Construct]]方法。[[Construct]]方法先创建一个名为实例的新对象,然后绑定函数体"this",再执行函数体。

具有 [[Construct]]方法的函数称为构造函数。箭头函数只有[[Call]] ,不能用作对象构造函数。

尾调优化

Tail call optimization allows some function calls to be optimized to maintain a smaller call stack, use less memory, and prevent stack overflow errors.

This optimization is applied by the engine automatically when it is safe to do so;

however, you might decide to rewrite recursive functions to take advantage of this optimization.

尾调用优化允许优化某些函数调用,以维护较小的调用堆栈,使用更少的内存,并防止堆栈溢出错误。

当引擎安全时,自动应用此优化;

但是,您可能决定重写递归函数以利用此优化。

ES6

参考

http://blog.poetries.top/js-knowledge-note/#/note/basis/func?id=%E5%B0%BE%E9%80%92%E5%BD%92

裸男
Nakeman.cn 2023 Build by Gatsby and Tailwind, Deploy on Netlify.