面试题 - ECMAScript 6

95 阅读15分钟

最近找工作整理了一些面试题,分享给大家一起来学习。如有问题,欢迎指正。

前端面试题系列文章:

ECMAScript 和 JavaScript 的关系

ECMAScript是JavaScript的规格,JavaScript是ECMAScript的一种实现。 通常看做JavaScript的标准化规范。

let、const、var的区别

  1. 作用域:let,const块作用域由 { }包括,var全局作用域(window属性)和函数作用域。
  2. 变量提升:var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。
  3. 暂时性死区:在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。
  4. 重复声明:var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的变量。const和let不允许重复声明变量。
  5. 初始值设置:var 和 let 声明变量可以不用设置初始值。而const声明变量必须设置初始值。
  6. 修改变量值:let声明的变量是可以重新赋值。const声明的常量的值就不能改变(指向的内存地址不变)。
区别varletconst
是否有块级作用域×✔️✔️
是否存在变量提升✔️××
是否添加全局属性✔️××
能否重复声明变量✔️××
是否存在暂时性死区×✔️✔️
是否必须设置初始值××✔️
能否改变指针指向✔️✔️×

扩展运算符

  1. 数组
console.log(...[1, 2, 3]);
console.log(1, ...[2, 3, 4], 5); // 1 2 3 4 5

// ES5 的写法
Math.max.apply(null, [14, 3, 77])
// ES6 的写法
Math.max(...[14, 3, 77])
// 等同于
Math.max(14, 3, 77);
  1. 对象
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }

解构赋值

  • 默认值结构赋值
    let { first: f, last: l } = { first: 'hello', last: 'world' };  // f='hello'; l='world'
    
    let {x = 3} = {};  // x=3
    let {x, y = 5} = {x: 1};  // x=1; y=5
    
    let {x = 3} = {x: undefined};  // x=undefined
    let {x = 3} = {x: null};  // x=3
    
    let [x = 1, y = x] = [];     // x=1; y=1
    let [x = 1, y = x] = [2];    // x=2; y=2
    let [x = 1, y = x] = [1, 2]; // x=1; y=2
    let [x = y, y = 1] = [];     // ReferenceError: y is not defined
    
  • 字符串的解构赋值
    const [a, b, c, d, e] = 'hello';
    a // "h"
    b // "e"
    c // "l"
    d // "l"
    e // "o"
    
    let {length : len} = 'hello';
    len // 5
    
  • 扩展运算符可以与解构赋值结合起来
    const [first, ...rest] = [1, 2, 3, 4, 5];
    first // 1
    rest  // [2, 3, 4, 5]
    
    const [first, ...rest] = [];
    first // undefined
    rest  // []
    
    const [first, ...rest] = ["foo"];
    first  // "foo"
    rest   // []
    

字符串扩展

  1. 字符串添加了遍历器接口,使得字符串可以被for...of循环遍历
    for (let codePoint of 'foo') {
        console.log(codePoint)
    }
    // "f"
    // "o"
    // "o"
    
  2. 模板字符串,用反引号(`)标识
  3. 新增字符串实例方法
    • includes:判断是否包含子字符串

      const son = 'haha' 
      const father = 'xixi haha hehe'
      father.includes(son) // true
      
    • startsWith:判断是否以某个字符串开头

      const father = 'xixi haha hehe'
      father.startsWith('haha') // false
      father.startsWith('xixi') // true
      
    • endsWidth:判断是否以某个字符串结尾

      const father = 'xixi haha hehe'
      father.endsWith('hehe') // true
      
    • repeat:重复多次某个字符串

      const sourceCode = 'repeat for 3 times;'
      const repeated = sourceCode.repeat(3) 
      console.log(repeated) // repeat for 3 times;repeat for 3 times;repeat for 3 times;
      
    • trimStart(),trimEnd(): 去除字符串头部|尾部空格

      const s = ' abc ';
      s.trim() // "abc"
      s.trimStart() // "abc "
      s.trimEnd() // " abc"
      

数值扩展

  1. BigInt数据类型: 用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。
    typeof 1234; // number
    typeof 1234n; // bigint
    typeof BigInt(123); // bigint
    
    1n + 2n; // 3n
    
  2. 指数运算符(**)
    2**2 // 4
    2**3 // 8
    
  3. Number对象上新增方法
    • Number.isFinite():用来检查一个数值是否为有限的(finite),即不是Infinity
    • Number.isNaN():用来检查一个值是否为NaN
    • Number.parseInt():把值转化为整数
    • Number.parseFloat():把值转化为浮点数值
    • Number.isInteger():用来判断一个数值是否为整数
  4. Math对象上新增方法
    • Math.trunc():用于去除一个数的小数部分,返回整数部分
    • Math.sign():用来判断一个数到底是正数、负数、还是零、其他值
    • Math.cbrt():用于计算一个数的立方根

数组扩展

  1. Array对象新增方法
    • Array.from():将类似数组的对象和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map),转为数组
    // 对象
    let arrayLike = {
      0: "a",
      1: "b",
      2: "c",
      length: 3,
    };
    let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
    
    Array.from({ length: 3 }); // [ undefined, undefined, undefined ]
    
    // arguments对象
    function foo() {
      var args = Array.from(arguments);
      // ...
    }
    
    // 字符串
    Array.from("hello"); // ['h', 'e', 'l', 'l', 'o']
    
    // Set
    Array.from(new Set([1, 2, 3, 4, 4])); // [1, 2, 3, 4]
    
    // Map
    const m = new Map();
    const o = { a: "a" };
    m.set(o, "2");
    m.set(1, o);
    Array.from(m); // [[{ a: "a" }, "2"], [1, { a: "a" }]]
    
    
    • Array.of():方法用于将一组值,转换为数组
    Array.of(3, 11, 8) // [3,11,8]
    Array.of(3) // [3]
    Array.of(3).length // 1
    
  2. 新增数组实例方法
    • find() 和 findIndex()
    • entries(),keys() 和 values()
    • includes()

对象的扩展

  1. 函数的name属性,返回函数名
  2. 属性的遍历
    • for…in:只遍历对象自身的和继承的可枚举的属性(不含 Symbol 属性)。一般搭配obj.hasOwnProperty(key)方法判断是否对象自身属性。
    • Object.keys(obj):返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
  3. Object对象新增方法
    • Object.is():比较两个值是否相等,与===类似,区别如下:
    +0 === -0 //true
    NaN === NaN // false
    Object.is(+0, -0) // false
    Object.is(NaN, NaN) // true
    
    • Object.assign():用于对象的合并
    const target = { a: 1 };
    const source1 = { b: 2 };
    const source2 = { c: 3 };
    Object.assign(target, source1, source2);
    target // {a:1, b:2, c:3}
    
    • Object.keys(),Object.values(),Object.entries()
    const obj = { foo: '41', baz: 42 };
    Object.keys(obj)  //  ["foo", "baz"]
    Object.values(obj)  //  ["41", 42]
    Object.entries(obj)  // [ ["foo", "41"], ["baz", 42] ]
    

函数的扩展

  1. 参数默认值
  2. rest 参数(形式为...变量名)
  3. 箭头函数

Set 和 Map 数据结构

  • Set: 本身是一个构造函数,用来生成 Set 数据结构。类似于数组,但是成员的值都是唯一的,没有重复的值。

    const set = new Set([1, 2, 3, 4, 4]);
    [...set] // [1, 2, 3, 4]
    set.size // 4
    set.add(5) 
    set.has(2) // true
    set.delete(2) 
    set.has(2) // false
    

    使用Set去重

    // 去除数组的重复成员
    [...new Set(array)]
    
    // 字符串去重
    [...new Set('ababbc')].join('')
    // "abc"
    

    Set 结构转为数组

    const items = new Set([1, 2, 3, 4, 5]);
    const array = Array.from(items);
    const array1 = [...items];
    

    遍历方法 keys(),values(),entries(),for...of,forEach()

    let set = new Set(["red", "green", "blue"]);
    for (let item of set.keys()) {
      console.log(item);
    }
    // red
    // green
    // blue
    
    for (let item of set.values()) {
      console.log(item);
    }
    // red
    // green
    // blue
    
    for (let item of set.entries()) {
      console.log(item);
    }
    // ["red", "red"]
    // ["green", "green"]
    // ["blue", "blue"]
    
    for (let x of set) {
      console.log(x);
    }
    // red
    // green
    // blue
    
    set.forEach((value, key) => console.log(key + " : " + value));
    // red : red
    // green : green
    // blue : blue
    
    
  • Map 本身是一个构造函数, 用来生成Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键

    const m = new Map();
    const o = { a: "a" };
    m.set(o, "2");
    m.set(1, o);
    
    m.has(1);  // true
    m.has("2");  // false
    m.has(o);  // true
    
    m.get(o);  // "2"
    m.get(1);  // {a: 'a'}
    
    m.delete(1)
    m.has(1);  // false
    
    m.size; // 1
    

    遍历方法 keys(),values(),entries(),for...of,forEach()

    const map = new Map([
      ["F", "no"],
      ["T", "yes"],
    ]);
    
    for (let key of map.keys()) {
      console.log(key);
    }
    // "F"
    // "T"
    
    for (let value of map.values()) {
      console.log(value);
    }
    // "no"
    // "yes"
    
    for (let item of map.entries()) {
      console.log(item[0], item[1]);
    }
    // "F" "no"
    // "T" "yes"
    
    for (let [key, value] of map.entries()) {
      console.log(key, value);
    }
    // "F" "no"
    // "T" "yes"
    
    // 等同于使用map.entries()
    for (let [key, value] of map) {
      console.log(key, value);
    }
    // "F" "no"
    // "T" "yes"
    

Symbo的理解和使用

一种新的原始数据类型Symbol,表示独一无二的值

let s1 = Symbol()
let s2 = Symbol()
let s3 = Symbol('another symbol')

s1 === s2 // false
typeof s1  // 'symbol'

应用场景:

  1. 使用Symbol来作为对象属性名(key)

    Symbol类型的key不能通过Object.keys()或者for...in来枚举,且使用JSON.stringify()转换会丢失。所以,利用该特性,我们可以把一些不需要对外操作和访问的属性使用Symbol来定义。

    const name = Symbol('name')
    let obj = {
        [name]: 'Lily',
        age: 18,
        title: 'Engineer'
     }
    
     Object.keys(obj)   // ['age', 'title']
     for (let p in obj) {
        console.log(p)   // 分别会输出:'age' 和 'title'
     }
     JSON.stringify(obj) // {"age":18,"title":"Engineer"}
     
    Object.getOwnPropertySymbols(obj) // [Symbol(name)]
    
  2. 使用Symbol来替代常量

    当需要多个常量来进行业务逻辑判断处理时,使用Symbol可保证唯一性

    const TYPE_AUDIO = Symbol()
    const TYPE_VIDEO = Symbol()
    const TYPE_IMAGE = Symbol()
    
  3. 使用Symbol定义类的私有属性/方法

    const PASSWORD = Symbol()
    class Login {
      constructor(username, password) {
        this.username = username
        this[PASSWORD] = password
      }
    
      checkPassword(pwd) {
          return this[PASSWORD] === pwd
      }
    }
    
    
    const login = new Login('admin', '123456')
    login.checkPassword('123456')  // true
    

Proxy 的理解和使用

用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)

let proxy = new Proxy(target, handler);

target表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理))

handler参数也是一个对象,用来定制拦截行为。

  • 用法

    let obj = new Proxy({}, {
        get: function (target, propKey, receiver) {
            console.log(`获取对象属性时执行`);
            return Reflect.get(target, propKey, receiver);
        },
        set: function (target, propKey, value, receiver) {
          console.log(`设置对象属性时执行`);
          return Reflect.set(target, propKey, value, receiver);
        },
      });
    

    Reflect方法,保证原生行为能够正常执行

  • 使用场景

    • 拦截和监视外部对对象的访问
    • 降低函数或类的复杂度
    • 在复杂操作前对操作进行校验或对所需资源进行管理

Promise 的理解和使用

Promise 是异步编程的一种解决方案。解决了地狱回调的问题。

  • 状态

    • pending(进行中)
    • fulfilled(已成功)
    • rejected(已失败)
  • 特点

    • 对象的状态不受外界影响
    • 一旦状态改变(从pending变为fulfilled和从pending变为rejected),就不会再变,任何时候都可以得到这个结果
  • 用法

    const promise = new Promise(function (resolve, reject) {
      setTimeout(() => {
        if ("判断执行成功") {
          resolve("success");
        } else {
          reject("error");
        }
      }, 50);
    });
    
    promise
      .then(
        (result) => {
          console.log("resolved状态的回调执行");
        },
        (error) => {
          console.log("rejected状态的回调执行");
        }
      )
      .catch((error) => {
        console.log("rejected状态的回调执行 或 捕获then中抛出错误");
      })
      .finally(() => {
        console.log("没有入参,任何状态都执行");
      });
    
    
  • 常用API

    • Promise.all() 返回新的Promise对象。传入多个Promise,所有状态都为fulfilled时,当前Promise对象状态为fulfilled。或者只要有一个状态为rejected,当前Promise对象状态为rejected。
    • Promise.allSettled() 返回新的Promise对象,传入多个Promise,所有状态都发生改变(fulfilled|rejected),当前Promise对象状态为fulfilled。
    • Promise.race() 返回新的Promise对象,传入多个Promise,只要有一个状态改变时(fulfilled|rejected),当前Promise对象状态就改变(fulfilled|rejected)
    • Promise.any() 返回新的Promise对象,传入多个Promise,只要有一个状态为fulfilled时,当前Promise对象状态为fulfilled
  • 使用场景

    • 链式操作
    new Promise((resolve, reject) => {
      resolve(1);
    })
      .then((res) => {
        return res + 1;
      })
      .then((res) => {
        return res + 1;
      })
      .then((res) => {
        console.log(res + 1);  // 4
      });
    
    • Promise.all()实现发送请求获取多个数据后执行某个操作。
    const p1 = new Promise((resolve, reject) => {
      resolve(1);
    });
    const p2 = new Promise((resolve, reject) => {
      resolve(2);
    });
    Promise.all([p1, p2]).then((res) => {
      console.log(res);  // [1, 2]
    });
    
  • 简易实现Promise

    function MyPromise(fn) {
      let status = "pending";
      let fulfilledsCallbackArr = [];
      let rejectedCallbackArr = [];
    
      let resolve = (res) => {
        if (status === "pending") {
          status = "fulfilled";
          fulfilledsCallbackArr.forEach((f) => f(res));
        }
      };
      let reject = (res) => {
        if (status === "pending") {
          status = "rejected";
          rejectedCallbackArr.forEach((f) => f(res));
        }
      };
    
      this.then = function (f1, f2) {
        if (status === "pending") {
          return new MyPromise((rel, rej) => {
            fulfilledsCallbackArr.push((res) => {
              rel(f1(res));
            });
            rejectedCallbackArr.push((res) => {
              rej(f2(res));
            });
          });
        } else {
          return new MyPromise((rel, rej) => {
            if (status === "fulfilled") {
              rel(f1(res));
            }
    
            if (status === "fulfilled") {
              rej(f2(res));
            }
          });
        }
      };
      this.catch = function (f) {
        rejectedCallbackArr.push(f);
      };
    
      fn(resolve, reject);
    }
    

Generator 的理解和使用

Generator:(生成器)是ES6标准引入的新的数据类型。generator由function*定义,除了return语句,还可以用yield返回多次

function* generator() {
    yield 1;
    yield 2;
}
const gen = generator(); 
console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: undefined, done: false}

function* foo(x) {
    console.log("hi" + (yield x + 1))
    yield x + 2
    return x + 3;
}
const f = foo(1);
console.log(f.next()); // { value: 2, done: false }
console.log(f.next()); // hiundefined // { value: 3, done: false }
console.log(f.next()); // { value: 4, done: true }
console.log(f.next()); // {value: undefined, done: true}

next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。返回的value就是yield的返回值,done表示这个generator是否已经执行结束了。

yield表达式可以暂停函数执行,next方法用于恢复函数执行,这使得Generator函数非常适合将异步任务同步化

const request = (id) => {
    setTimeout(() => {
        const obj = {
            id:id
        }
        it.next(obj)
    }, 1000)
}
function* gen(mydata) {
    const data = yield request(mydata);
    const data1 = yield request(data.id);
    const data2 = yield request(data1.id);
    console.log('Generator:' + data3.id);
}

const it = gen("123")
it.next()

js模块化加载规范

  • 什么是模块化

    将一个复杂的程序依照一定的规则封装成几个文件, 并进行组合在一起。每个文件模块的内部数据是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信。

  • 为什么需要模块化(模块化优点)?

    • 避免全局作用域污染
    • 提高代码复用
    • 提高代码可维护性
    • 更好的分离,实现按需加载
  • 模块化规范

    • AMD:浏览器端的模块加载规范。代表库有RequireJS
    • CMD浏览器端的模块加载规范。代表库有SeaJS
    • CommonJS服务器端的模块加载规范。代表库有Node.js
    • ES6 Module浏览器和服务器通用的模块加载规范
    /** AMD写法 **/
    define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { 
         // 等于在最前面声明并初始化了要用到的所有模块
        a.doSomething();
        if (false) {
            // 即便没用到某个模块 b,但 b 还是提前执行了
            b.doSomething()
        } 
    });
    
    /** CMD写法 **/
    define(function(require, exports, module) {
        var a = require('./a'); //在需要时申明
        a.doSomething();
        if (false) {
            var b = require('./b');
            b.doSomething();
        }
    });
    
    /** CommonJs **/
    module.exports = { 
        doSomething: () => {
            var a = require('./a');
            a.doSomething();
            if (false) {
                var b = require('./b');
                b.doSomething();
            }
        }
    }
    
    /** ES6 Module **/
    import { doSomething as aDoSomething } from './a';
    import { doSomething as bDoSomething} from './b';
    export { 
       doSomething: () => {
            aDoSomething();
            if (false) {
                bDoSomething();
            }
        } 
    };
    

CommonJS 与 ES6 moudle

  • CommonJS

    commonjs 是 Node 中的模块规范,通过 require 及 exports 进行导入导出 (进一步延伸的话,module.exports 属于 commonjs2)

    commonjs 模块可以运行在 node 环境及 webpack 环境下的,但不能在浏览器中直接使用。(前端使用时,会编译成浏览器可识别的语法)

    特点:

    • 所有代码都运行在模块作用域,不会污染全局作用域。
    • 模块加载是一项阻塞操作,也就是同步加载。即执行到require时才会加载模块,等待加载完成之后才会执行后面的代码。
    • 同一模块多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
    • CommonJS模块的加载机制是,输入的是被输出的值的浅拷贝。即修改复杂类型数据对象时,会影响到另一个模块的值。
    // lib.js
    var counter = 3;
    var obj = {
        counter: 3
    };
    function incCounter() {
      counter++;
      obj.counter++;
    }
    module.exports = {
      counter: counter,
      obj: obj,
      incCounter: incCounter,
    };
    
    // main.js
    var counter = require('./lib').counter;
    var obj = require('./lib').obj;
    var incCounter = require('./lib').incCounter;
    
    console.log(counter);  // 3
    console.log(obj.counter);  // 3
    incCounter();
    console.log(counter); // 3
    console.log(obj.counter);  // 4
    
  • ES6 Module

    ES6 Module成为浏览器和服务器通用的模块解决方案

    特点:

    • import和export命令只能在模块的顶层,不能在代码块之中。
    • ES6 可以在编译时就完成模块加载。ES6 的Module语法有些浏览器是不支持的,因此需要Babel先进性转码,将import和export命令转成ES5语法才能被浏览器解析。
  • CommonJS 与 ES6 moudle区别

    1. CommonJS模块是运行时加载(不需要编译),ES6 Modules是编译时输出接口
    2. CommonJS输出的是值的浅拷贝,基本数据类型的值输出不受被模块内部改变影响;ES6 Modules输出的是值的引用,模块的内部的改变会影响引用的改变
    3. CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
    4. CommonJS this指向当前模块module.exports,ES6 Modules this指向undefined

import() 与 require()区别

import()require() 是两种 JavaScript 模块加载的方式

  1. 模块规范

    • require() 是 Node.js 中的CommonJS模块加载方法,用于在服务器端环境中加载模块。它是同步加载的,通常用于静态模块的加载。
    • import() 是 ECMAScript 模块规范(ES6/ES2015)中引入的动态导入语法,用于在浏览器和现代 JavaScript 环境中加载模块。它是异步加载的,通常用于动态模块的加载。
  2. 返回值

    • require() 返回模块的导出内容(exports),可以是对象、函数等。
    • import() 返回一个 Promise,它在模块加载成功后会解析为导出的模块内容。
  3. 用途:

    • require() 通常用于 Node.js 服务器端开发,或者一些前端构建工具(如Webpack)的配置中。
    • import() 用于在现代浏览器环境中动态加载模块,特别是在使用模块分割(code splitting)和懒加载(lazy loading)时非常有用。
const module = require('module-name');

import('module-name').then(module => {
  // 模块加载成功后的操作
}).catch(error => {
  // 模块加载失败后的操作
});

箭头函数与普通函数的区别

  1. 写法简洁
  2. 箭头函数内this指向外层作用域的this,所以在它在定义时已经确定了,之后不会改变
    const func = () => {
       console.log(this)   // window
    }
    func() // window
    
  3. call()、apply()、bind()等方法不能改变箭头函数中this的指向
    var id = 1;
    const obj = {id:2}
    
    function func1(a,b) { 
     console.log(this.id);
    }
    func1.call(obj,1,2); // 2
    func1.apply(obj,[1,2]); // 2
    func1.bind(obj,1,2)(); // 2
    
    const func2 = (a,b) => { 
     console.log(this.id);
    }
    func2.call(obj,1,2); // 1
    func2.apply(obj,[1,2]); // 1
    func2.bind(obj,1,2)(); // 1
    
  4. 箭头函数不能作为构造函数使用,所以没有prototype
    const func = () => { }
    console.log(func.prototype)   // undefined
    
  5. 箭头函数没有自己的arguments
    function func1(a, b) {
      console.log(arguments[0]);  // 1
      console.log(arguments[1]);  // 2
    }
    func1(1, 2);
    
    const func2 = (a, b) => {
      console.log(arguments[0]);  // error: arguments is not defined
      console.log(arguments[1]);  // error: arguments is not defined
    }
    func2(1, 2);
    
  6. 箭头函数不能用作Generator函数,不能使用yeild关键字

对函数参数arguments与rest参数的理解

arguments与rest区别:

  1. rest参数只包含那些没有对应形参的实参,而arguments对象包含了传给函数的所有实参

  2. arguments对象不是一个真正的数组,而rest是真正的Array实例(真数组

  3. arguments对象还有一些附加属性(如callee属性)

    // arguments变量的写法
    function sortNumbers() {
      return Array.prototype.slice.call(arguments).sort();
    }
    // rest参数的写法
    const sortNumbers = (...numbers) => numbers.sort();
    

arguments对象:arguments对象是function(非箭头函数)中一个特殊的局部变量

  • arguments原型是Object,因此不能调用Array原型上的各种操作方法

  • arguments有内置的Symbol(Symbol.iterator)属性,可以被for..of遍历的

    // arguments转为Array
    [...arguments]
    // 或
    Array.from(arguments)
    // 或
    Array.prototype.slice.call(arguments);
    // 或
    [].slice.call(arguments)
    

reset:ES6 引入 rest 参数(形式为...变量名),这样就不需要使用arguments对象了

  • rest参数之后不能再有其他参数
  • 函数的length属性,不包括 rest 参数。
(function(a) {}).length  // 1
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1