ES6

  1. ECMAScript 6(简称ES6)是于2015年6月正式发布的JavaScript语言的标准
  2. 目标是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言
  3. ES6 主要是为了解决 ES5 的先天不足,增加部分特性和功能

let与const

  1. 函数作用域 VS 块作用域

    ES5只有全局作用域和函数作用域,没有块级作用域,常用的做法是使用闭包。

    let和const是块级作用域,变量只能在let和const命令所在的代码块有效。

    1
    2
    for( var i = 0 ; i < 3 ; i++ ){ console.log(i) }; console.log(i); // 0 1 2 3
    for( let i = 0 ; i < 3 ; i++ ){ console.log(i) }; console.log(i); // 0 1 2 ReferenceError
  2. 变量提升 VS 暂时性死区

    var存在变量提升,let和const存在暂时性死区。

    1
    2
    console.log(b); var b = "banana"; // undefined
    console.log(b); let b = "banana"; // Uncaught ReferenceError: b is not defined
  3. 不允许重复声明

    var变量可以多次声明,而let和const不允许在相同作用域内,重复声明同一个变量。

    1
    2
    var d = 1; var d = 2; console.log(d); // 2
    let b = 3; let b = 4; // Identifier 'b' has already been declared
  4. 全局变量 VS 全局对象的属性

    ES5中声明的全局变量属于全局对象( window/global )的属性。

    ES6 中使用 var 和 function 声明的全局变量依旧作为全局对象的属性,使用 let**, const** 命令声明的全局变量不属于全局对象的属性。

    1
    2
    var b = 3; console.log(window.b); // 3
    let d = 5; console.log(window.d); // undefined
  5. const注意事项

    const声明一个只读的常量,一旦声明,常量的值就不能改变。const 其实保证的不是变量的值不变,而是保证变量指向的内存地址所保存的数据不允许改动( 简单数据类型/复杂数据类型 )。

    1
    2
    const names = []; names[0] = 1; console.log(names); // [1]
    const names = []; names = [1]; console.log(names); // Assignment to constant variable.

解构赋值

  • 解构模型

    • 右边:解构的源

    • 左边:解构的目标

  1. 解构(Array)

    • 基本

      1
      2
      3
      4
      let [a, b, c] = [1, 2, 3];
      // a = 1
      // b = 2
      // c = 3
    • 可嵌套

      1
      2
      3
      4
      let [a, [[b], c]] = [1, [[2], 3]];
      // a = 1
      // b = 2
      // c = 3
    • 可忽略

      1
      2
      3
      let [a, , b] = [1, 2, 3];
      // a = 1
      // b = 3
    • 不完全解构

      1
      2
      3
      let [a = 1, b] = []; 
      // a = 1,
      // b = undefined
    • 剩余运算符

      1
      2
      3
      let [a, ...b] = [1, 2, 3];
      //a = 1
      //b = [2, 3]
  2. 字符串解构(String)

    • 字符串解构
      1
      2
      3
      4
      5
      6
      let [a, b, c, d, e] = 'hello';
      // a = 'h'
      // b = 'e'
      // c = 'l'
      // d = 'l'
      // e = 'o'
  3. 对象解构(Object)

    • 基本

      1
      2
      3
      4
      5
      6
      let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
      // foo = 'aaa'
      // bar = 'bbb'

      let { baz : foo } = { baz : 'ddd' };
      // foo = 'ddd'
    • 可嵌套

      1
      2
      3
      4
      let obj = {p: ['hello', {y: 'world'}] };
      let {p: [x, { y }] } = obj;
      // x = 'hello'
      // y = 'world'
    • 可忽略

      1
      2
      3
      let obj = {p: ['hello', {y: 'world'}] };
      let {p: [x, { }] } = obj;
      // x = 'hello'
    • 不完全解构

      1
      2
      3
      4
      let obj = {p: [{y: 'world'}] };
      let {p: [{ y }, x ] } = obj;
      // x = undefined
      // y = 'world'
    • 剩余运算符

      1
      2
      3
      4
      let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40};
      // a = 10
      // b = 20
      // rest = {c: 30, d: 40}

Symbol

ES6引入新的数据类型,表示独一无二的值,常用来定义对象的唯一属性名

  • 基本用法

    1
    2
    3
    4
    5
    6
    7
    let sy = Symbol("KK");
    console.log(sy); // Symbol(KK)
    typeof(sy); // "symbol"

    // 相同参数 Symbol() 返回的值不相等
    let sy1 = Symbol("kk");
    sy === sy1; // false
  • 属性用法(公有属性)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var mySymbol = Symbol();

    //第一种写法
    var a = {};
    a[mySymbol] = "Hello!";

    //第二种写法
    var a = {
    [mySymbol]: "Hellow!"
    }

    //第三种写法
    var a = {};
    Object.defineProperty(a, mySymbol, { value: "Hellow!" });

    //以上写法的结果都相同

    a[mySymbol] // "Hellow!"
  • 常量用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const COLOR_RED = Symbol();
    const COLOR_GREEN = Symbol();

    function getComponent(color) {
    switch(color) {
    case: COLOR_RED:
    return "red";
    case: COLOR_GREEN:
    return "green";
    default:
    throw new Error("Undefind color")
    }
    }
  • Symbol.for()

全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索

1
2
3
4
5
6
let yellow = Symbol("Yellow");
let yellow1 = Symbol.for("Yellow");
yellow === yellow1; // false

let yellow2 = Symbol.for("Yellow");
yellow1 === yellow2; // true
  • Symbol.keyFor()

返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记

1
2
let yellow1 = Symbol.for("Yellow");
Symbol.keyFor(yellow1); // "Yellow"

Map和Set

Map

  • Map中的key

    • key 是字符串
    1
    2
    3
    4
    5
    var myMap = new Map();
    var keyString = "a string";
    myMap.set(keyString, "和键'a string'关联的值"); // "和键'a string'关联的值"
    myMap.get("a string"); // "和键'a string'关联的值"
    myMap.get(keyString); // 因为 keyString === 'a string'
    • key 是对象
    1
    2
    3
    4
    5
    var myMap = new Map();
    var keyObj = {};
    myMap.set(keyObj, "和键 keyObj 关联的值");
    myMap.get({}); // "和键 keyObj 关联的值"
    myMap.get(keyObj); // undefined, 因为 keyObj !== {}
    • key 是函数
    1
    2
    3
    4
    5
    var myMap = new Map();
    var keyFunc = function () {}; // 函数
    myMap.set(keyFunc, "和键 keyFunc 关联的值");
    myMap.get(keyFunc); // "和键 keyFunc 关联的值"
    myMap.get(function() {}) // undefined, 因为 keyFunc !== function () {}
    • key 是 NaN
    1
    2
    3
    4
    5
    var myMap = new Map();
    myMap.set(NaN, "not a number");
    myMap.get(NaN); // "not a number"
    var otherNaN = Number("foo");
    myMap.get(otherNaN); // "not a number"
  • Map的迭代

    • for…of

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      var myMap = new Map();
      myMap.set(0, "zero");
      myMap.set(1, "one");

      // 将会显示两个 log。 一个是 "0 = zero" 另一个是 "1 = one"
      for (var [key, value] of myMap) {
      console.log(key + " = " + value);
      }
      for (var [key, value] of myMap.entries()) {
      console.log(key + " = " + value);
      }
      /* 这个 entries 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组。 */

      // 将会显示两个log。 一个是 "0" 另一个是 "1"
      for (var key of myMap.keys()) {
      console.log(key);
      }
      /* 这个 keys 方法返回一个新的 Iterator 对象, 它按插入顺序包含了 Map 对象中每个元素的键。 */

      // 将会显示两个log。 一个是 "zero" 另一个是 "one"
      for (var value of myMap.values()) {
      console.log(value);
      }
      /* 这个 values 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的值。 */
    • forEach()

      1
      2
      3
      4
      5
      6
      7
      8
      var myMap = new Map();
      myMap.set(0, "zero");
      myMap.set(1, "one");

      // 将会显示两个 logs。 一个是 "0 = zero" 另一个是 "1 = one"
      myMap.forEach(function(value, key) {
      console.log(key + " = " + value);
      }, myMap)
  • Map对象操作

    • Map 与 Array的转换

      1
      2
      3
      4
      5
      6
      7
      var kvArray = [["key1", "value1"], ["key2", "value2"]];

      // Map 构造函数可以将一个 二维 键值对数组转换成一个 Map 对象
      var myMap = new Map(kvArray);

      // 使用 Array.from 函数可以将一个 Map 对象转换成一个二维键值对数组
      var outArray = Array.from(myMap);
    • Map 的克隆

      1
      2
      3
      4
      5
      var myMap1 = new Map([["key1", "value1"], ["key2", "value2"]]);
      var myMap2 = new Map(myMap1);

      console.log(original === clone);
      // 打印 false。 Map 对象构造函数生成实例,迭代出新的对象。
    • Map 的合并

      1
      2
      3
      4
      5
      var first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]);
      var second = new Map([[1, 'uno'], [2, 'dos']]);

      // 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的,对应值即 uno,dos, three
      var merged = new Map([...first, ...second]);

Set

  1. Set 对象存储的值总是唯一的

  2. +0 与 -0 在存储判断唯一性的时候是恒等的,所以不能重复;

  3. undefined 与 undefined 是恒等的,所以不能重复;

  4. NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不能重复。

  • 类型转换

    • Array转Set

      1
      2
      3
      var mySet = new Set(["value1", "value2", "value3"]);
      // 用...操作符,将 Set 转 Array
      var myArray = [...mySet];
    • String转Set

      1
      2
      var mySet = new Set('hello');  // Set(4) {"h", "e", "l", "o"}
      // 注:Set 中 toString 方法是不能将 Set 转换成 String
  • Set对象作用

    • 数组去重
      1
      2
      var mySet = new Set([1, 2, 3, 4, 4]);
      [...mySet]; // [1, 2, 3, 4]
    • 并集
      1
      2
      3
      var a = new Set([1, 2, 3]);
      var b = new Set([4, 3, 2]);
      var union = new Set([...a, ...b]); // {1, 2, 3, 4}
    • 交集
      1
      2
      3
      4
      5
      var a = new Set([1, 2, 3]);
      var b = new Set([4, 3, 2]);
      var intersect = new Set(
      [...a].filter(x => b.has(x))
      ); // {2, 3}
    • 差集
      1
      2
      3
      4
      5
      var a = new Set([1, 2, 3]);
      var b = new Set([4, 3, 2]);
      var difference = new Set(
      [...a].filter(x => !b.has(x)
      )

Reflect与Proxy

  • Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。

  • Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。

  • Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。

字符串

  1. 子串的识别

    1
    2
    3
    4
    5
    let string = "apple,banana,orange";  //均可接受两个参数,(需要搜索的字符串,可选的搜索起始位置索引)
    string.includes("banana"); // true //返回布尔值,判断是否找到参数字符串
    string.startsWith("apple"); // true //返回布尔值,判断参数字符串是否在原字符串的头部
    string.endsWith("apple"); // false //返回布尔值,判断参数字符串是否在原字符串的尾部
    string.startsWith("banana",6) // true //返回子串的位置使用indexOf、lastIndexOf
  2. 字符串重复

    repeat():返回新的字符串,表示将字符串重复指定次数返回。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //一般使用
    console.log("Hello,".repeat(2)); // "Hello,Hello,"
    //如果参数是小数,向下取整
    console.log("Hello,".repeat(3.2)); // "Hello,Hello,Hello,"
    //如果参数是 0 至 -1 之间的小数,会进行取整运算,0 至 -1 之间的小数取整得到 -0 ,等同于 repeat 零次
    console.log("Hello,".repeat(-0.5)); // ""
    //如果参数是 NaN,等同于 repeat 零次
    console.log("Hello,".repeat(NaN)); // ""
    //如果参数是负数或者 Infinity ,会报错:
    console.log("Hello,".repeat(-1)); // RangeError: Invalid count value
    console.log("Hello,".repeat(Infinity)); // RangeError: Invalid count value
    //如果传入的参数是字符串,则会先将字符串转化为数字
    console.log("Hello,".repeat("hh")); // ""
    console.log("Hello,".repeat("2")); // "Hello,Hello,"
  3. 字符串补全

    padStart:返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串。

    padEnd:返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //一般使用
    console.log("h".padStart(5,"o")); // "ooooh"
    console.log("h".padEnd(5,"o")); // "hoooo"
    console.log("h".padStart(5)); // " h"
    //如果指定的长度小于或者等于原字符串的长度,则返回原字符串:
    console.log("hello".padStart(5,"A")); // "hello"
    //如果原字符串加上补全字符串长度大于指定长度,则截去超出位数的补全字符串:
    console.log("hello".padEnd(10,",world!")); // "hello,worl"
    //常用于补全位数:
    console.log("123".padStart(10,"0")); // "0000000123"
  4. 模板字符串

    模板字符串相当于加强版的字符串,用反引号 `

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    //普通字符串
    let string = `Hello'\n'world`;
    console.log(string);
    // "Hello'
    // 'world"

    //多行字符串:
    let string1 = `Hey,
    can you stop angry now?`;
    console.log(string1);
    // Hey,
    // can you stop angry now?

    //字符串插入变量和表达式,变量名和表达式写在 ${} 中
    let name = "Mike";
    let age = 27;
    let info = `My Name is ${name},I am ${age+1} years old next year.`
    console.log(info);
    // My Name is Mike,I am 28 years old next year.

    //字符串中调用函数:
    function f(){
    return "have fun!";
    }
    let string2= `Game start,${f()}`;
    console.log(string2); // Game start,have fun!
  5. 标签模板

    标签模板,是一个函数的调用,其中调用的参数是模板字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    alert`Hello world!`;
    // 等价于
    alert('Hello world!');

    function f(stringArr,...values){
    let result = "";
    for(let i=0;i<stringArr.length;i++){
    result += stringArr[i];
    if(values[i]){
    result += String(values[i]).replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;");
    }
    }
    return result;
    }
    name = '<Amy&MIke>';
    f`<p>Hi, ${name}.I would like send you some message.</p>`;
    // <p>Hi, &lt;Amy&amp;MIke&gt;.I would like send you some message.</p>
  6. 国际化处理

    1
    2
    i18n`Hello ${name}, you are visitor number ${visitorNumber}.`;  
    // 你好**,你是第**位访问者

数值

  • 数值的表示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //二进制表示法新写法: 前缀 0b 或 0B 
    console.log(0b11 === 3); // true
    console.log(0B11 === 3); // true

    //八进制表示法新写法: 前缀 0o 或 0O
    console.log(0o11 === 9); // true
    console.log(0O11 === 9); // true

    //Number.EPSILON 属性表示 1 与大于 1 的最小浮点数之间的差(2 -52)
    Number.EPSILON
  • Math的扩展

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    Math.cbrt	//用于计算一个数的立方根。
    Math.imul //两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
    Math.hypot //用于计算所有参数的平方和的平方根。
    Math.clz32 //用于返回数字的32 位无符号整数形式的前导0的个数
    Math.trunc //用于返回数字的整数部分。
    Math.fround //用于获取数字的32位单精度浮点数形式。
    Math.sign //判断数字的符号(正、负、0)
    Math.expm1()//用于计算 e 的 x 次方减 1 的结果,即 Math.exp(x) - 1 。
    Math.log1p(x)//用于计算1 + x 的自然对数,即 Math.log(1 + x) 。
    Math.log10(x)//用于计算以 10 为底的 x 的对数。
    Math.log2() //用于计算 2 为底的 x 的对数。
    //双曲函数方法
    Math.sinh(x): //用于计算双曲正弦。
    Math.cosh(x): //用于计算双曲余弦。
    Math.tanh(x): //用于计算双曲正切。
    Math.asinh(x): //用于计算反双曲正弦。
    Math.acosh(x): //用于计算反双曲余弦。
    Math.atanh(x): //用于计算反双曲正切。
    //指数运算符
    1 ** 2; // 1
    // 右结合,从右至左计算
    2 ** 2 ** 3; // 256
    // **=
    let exam = 2;
    exam ** = 2; // 4

对象

  • 对象字面量

    • 属性的简洁表示

      1
      2
      3
      4
      5
      6
      const age = 12;
      const name = "Amy";
      const person = {age, name};
      person //{age: 12, name: "Amy"}
      //等同于
      const person = {age: age, name: name}
    • 方法名的简写

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      const person = {
      sayHi(){
      console.log("Hi");
      }
      }
      person.sayHi(); //"Hi"
      //等同于
      const person = {
      sayHi:function(){
      console.log("Hi");
      }
      }
      person.sayHi();//"Hi"
    • 属性名表达式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      //ES6允许用表达式作为属性名,但是一定要将表达式放在方括号内。
      const obj = {
      ["he"+"llo"](){
      return "Hi";
      }
      }
      obj.hello(); //"Hi"

      //属性的简洁表示法和属性名表达式不能同时使用,否则会报错
      const hello = "Hello";
      const obj = {
      [hello]
      };
      obj //SyntaxError: Unexpected token }

      const hello = "Hello";
      const obj = {[hello+"2"]:"world"};
      obj //{Hello2: "world"}
  • 对象扩展运算符

    • 基本用法

      1
      2
      3
      let person = {name: "Amy", age: 15};
      let someone = { ...person };
      someone; //{name: "Amy", age: 15}
    • 合并对象

      1
      2
      3
      4
      let age = {age: 15};
      let name = {name: "Amy"};
      let person = {...age, ...name};
      person; //{age: 15, name: "Amy"}
    • 注意点

      1
      2
      3
      4
      //自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖掉。
      let person = {name: "Amy", age: 15};
      let someone = { ...person, name: "Mike", age: 17};
      someone; //{name: "Mike", age: 17}
  • 对象的新方法

    • Object.assign(target, source_1, ···)

      用于将源对象的所有可枚举属性复制到目标对象中。

      1
      2
      3
      4
      5
      6
      let target = {a: 1};
      let object2 = {b: 2};
      let object3 = {c: 3};
      Object.assign(target,object2,object3);
      // 第一个参数是目标对象,后面的参数是源对象
      target; // {a: 1, b: 2, c: 3

      如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回,因为 null 和 undefined 不能转化为对象,所以会报错。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      //assign 的属性拷贝是浅拷贝:
      let sourceObj = { a: { b: 1}};
      let targetObj = {c: 3};
      Object.assign(targetObj, sourceObj);
      targetObj.a.b = 2;
      sourceObj.a.b; // 2

      //同名属性替换
      targetObj = { a: { b: 1, c:2}};
      sourceObj = { a: { b: "hh"}};
      Object.assign(targetObj, sourceObj);
      targetObj; // {a: {b: "hh"}}

      //数组的处理会将数组处理成对象,所以先将 [2,3] 转为 {0:2,1:3} ,然后再进行属性复制,所以源对象的 0 号属性覆盖了目标对象的 0。
      Object.assign([2,3], [5]); // [5,3]
    • Object.is(value1, value2)

      用来比较两个值是否严格相等,与(===)基本类似。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      //基本用法
      Object.is("q","q"); // true
      Object.is(1,1); // true
      Object.is([1],[1]); // false
      Object.is({q:1},{q:1}); // false

      //与(===)的区别
      //一是+0不等于-0
      Object.is(+0,-0); //false
      +0 === -0 //true
      //二是NaN等于本身
      Object.is(NaN,NaN); //true
      NaN === NaN //false

数组

  • Array.of()

    1
    2
    3
    4
    5
    6
    7
    8
    //将参数中所有值作为元素形成数组。
    console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]

    // 参数值可为不同类型
    console.log(Array.of(1, '2', true)); // [1, '2', true]

    // 参数为空时返回空数组
    console.log(Array.of()); // []
  • Array.from()

    将类数组对象或可迭代对象转化为数组。

    1
    2
    3
    console.log(Array.from([1, 2])); // [1, 2]
    // 参数含空位
    console.log(Array.from([1, , 3])); // [1, undefined, 3]

    参数 Array.from(arrayLike[, mapFn[, thisArg]])

    • arrayLike
      想要转换的类数组对象或可迭代对象。

      console.log(Array.from([1, 2, 3])); // [1, 2, 3]

    • mapFn

      可选,map 函数,用于对每个元素进行处理,放入数组的是处理后的元素。

      console.log(Array.from([1, 2, 3], (n) => n * 2)); // [2, 4, 6]

    • thisArg

      可选,用于指定 map 函数执行时的 this 对象。

      1
      2
      3
      4
      5
      6
      7
      8
      let map = {    
      do: function(n) {
      return n * 2;
      }}
      let arrayLike = [1, 2, 3];
      console.log(
      Array.from(arrayLike, function (n){ return this.do(n);}, map)
      ); // [2, 4, 6]
  • 扩展方法

    1. 查找

      • find()

        查找数组中符合条件的元素,若有多个符合条件的元素,则返回第一个元素

      • findIndex()

        查找数组中符合条件的元素索引,若有多个符合条件的元素,则返回第一个元素索引。

    2. 填充

      • fill()

        将一定范围索引的数组元素内容填充为单个指定的值。

      • copyWithin()

        将一定范围索引的数组元素修改为此数组另一指定范围索引的元素。

    3. 遍历

      • entries()

        遍历键值对。

      • keys()

        遍历键名。

      • values()

        遍历键值。

    4. 包含

      • includes()

        数组是否包含指定值。

    5. 嵌套数组转一维数组

      • flat()

        1
        2
        3
        4
        5
        6
        7
          console.log([1 ,[2, 3]].flat()); // [1, 2, 3]
        // 指定转换的嵌套层数
        console.log([1, [2, [3, [4, 5]]]].flat(2)); // [1, 2, 3, [4, 5]]
        // 不管嵌套多少层
        console.log([1, [2, [3, [4, 5]]]].flat(Infinity)); // [1, 2, 3, 4, 5]
        // 自动跳过空位
        console.log([1, [2, , 3]].flat());<p> // [1, 2, 3]
        1
        2
        3
        4
        5
        6
        7

        - flatMap()

        ```js
        // 参数1:遍历函数,该遍历函数可接受3个参数:当前元素、当前元素索引、原数组
        // 参数2:指定遍历函数中 this 的指向
        console.log([1, 2, 3].flatMap(n => [n * 2])); // [2, 4, 6]
  • 数组缓冲区

    数组缓冲区是内存中的一段地址,是定型数组的基础,实际字节数在创建时确定,之后只可修改其中的数据,不可修改大小。

    • 创建数组缓冲区

      1
      2
      3
      4
      5
      6
      let buffer = new ArrayBuffer(10);
      console.log(buffer.byteLength); // 10
      分割已有数组缓冲区
      let buffer = new ArrayBuffer(10);
      let buffer1 = buffer.slice(1, 3);
      console.log(buffer1.byteLength); // 2
    • 视图

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // 默认 DataView 可操作数组缓冲区全部内容
      let buffer = new ArrayBuffer(10);
      dataView = new DataView(buffer);
      dataView.setInt8(0,1);
      console.log(dataView.getInt8(0)); // 1

      // 通过设定偏移量(参数2)与长度(参数3)指定 DataView 可操作的字节范围
      let buffer1 = new ArrayBuffer(10);
      dataView1 = new DataView(buffer1, 0, 3);
      dataView1.setInt8(5,1); // RangeError
  • 定型数组

    • 数组缓冲区的特定类型的视图。

      可以强制使用特定的数据类型,而不是使用通用的 DataView 对象来操作数组缓冲区。

    • 创建

      通过数组缓冲区生成

      1
      2
      3
      let buffer = new ArrayBuffer(10),    
      view = new Int8Array(buffer);
      console.log(view.byteLength); // 10
  • 扩展运算符

    • 复制数组

      1
      2
      3
      4
      5
      6
      7
      8
      let arr = [1, 2],
      arr1 = [...arr];
      console.log(arr1); // [1, 2]

      // 数组含空位
      let arr2 = [1, , 3],
      arr3 = [...arr2];
      console.log(arr3); [1, undefined, 3]
    • 合并数组

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

函数

  • 函数参数的扩展

    • 默认参数

      1
      2
      3
      4
      5
      6
      function fn(name,age=17){
      console.log(name+","+age);
      }
      fn("Amy",18); // Amy,18
      fn("Amy",""); // Amy,
      fn("Amy"); // Amy,17
    • 不定参数

      不定参数用来表示不确定参数个数,形如,…变量名,具名参数只能放在参数组的最后,并且有且只有一个不定参数。

      1
      2
      3
      4
      5
      function f(...values){
      console.log(values.length);
      }
      f(1,2); //2
      f(1,2,3,4); //4
  • 箭头函数

    • 参数 => 函数体
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var f = (a,b) => {
    let result = a+b;
    return result;
    }
    f(6,2); // 8

    //当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来
    // 报错
    var f = (id,name) => {id: id, name: name};
    f(6,2); // SyntaxError: Unexpected token :

    // 不报错
    var f = (id,name) => ({id: id, name: name});
    f(6,2); // {id: 6, name: 2}
    • 箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象。

迭代器

  • terator 是 ES6 引入的一种新的遍历机制,迭代器有两个核心概念:

    • 迭代器是一个统一的接口,它的作用是使各种数据结构可被便捷的访问,它是通过一个键为Symbol.iterator 的方法来实现。
    • 迭代器是用于遍历数据结构元素的指针(如数据库中的游标)
  • for…of循环遍历,可遍历Array、String、Map、Set、arguments

  • 普通对象不可以迭代,数据结构类似于数组的形式,则可以借助 Array.from() 方法进行转换迭代。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const arrayLink = {length: 2, 0: "zero", 1: "one"}
    // 报 TypeError 异常
    for (let item of arrayLink) {
    console.log(item);
    }

    // 正常运行
    // output:
    // zero
    // one
    for (let item of Array.from(arrayLink)) {
    console.log(item);
    }

class类

在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类,class 的本质是 function。

  1. 基本用法

    • 类定义

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // 匿名类
      let Example = class {
      constructor(a) {
      this.a = a;
      }
      }
      // 命名类
      let Example = class Example {
      constructor(a) {
      this.a = a;
      }
      }
    • 类声明

      1
      2
      3
      4
      5
      class Example {
      constructor(a) {
      this.a = a;
      }
      }
    • 注意要点

      1
      2
      3
      类定义不会被提升,必须在访问前对类进行定义,类中方法不需要 function 关键字,方法间不能加分号。
      new Example();
      class Example {}
    • 类的实例化

      1
      2
      3
      4
      //class 的实例化必须通过 new 关键字。
      class Example {}
      let exam1 = Example();
      // Class constructor Example cannot be invoked without 'new'
  2. decorator

    • 类修饰

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      //第一个参数 target,指向类本身。
      function testable(target) {
      target.isTestable = true;
      }
      @testable
      class Example {}
      Example.isTestable; // true

      //多个参数——嵌套实现
      function testable(isTestable) {
      return function(target) {
      target.isTestable=isTestable;
      }
      }
      @testable(true)
      class Example {}
      Example.isTestable; // true
    • 方法修饰

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      //3个参数:target(类的原型对象)、name(修饰的属性名)、descriptor(该属性的描述对象)。
      class Example {
      @writable
      sum(a, b) {
      return a + b;
      }
      }
      function writable(target, name, descriptor) {
      descriptor.writable = false;
      return descriptor; // 必须返回
      }

      //修饰器执行顺序,由外向内进入,由内向外执行。
      class Example {
      @logMethod(1)
      @logMthod(2)
      sum(a, b){
      return a + b;
      }
      }
      function logMethod(id) {
      console.log('evaluated logMethod'+id);
      return (target, name, desctiptor) => console.log('excuted logMethod '+id);
      }
      // evaluated logMethod 1
      // evaluated logMethod 2
      // excuted logMethod 2
      // excuted logMethod 1
  3. 封装与继承

    • getter/setter

    • extends

      class Child extends Father {...}

    • super

    • 注意要点

模块

ES6 的模块化分为导出(export) @与导入(import)两个模块。

  • 以对象属性的形式export和import

    • 一般形式

      1
      2
      3
      4
      5
      6
      //export.js
      export let x=1;
      export let y=2;

      import{x,y} from "./export.js"
      console.log(x,y)//输出x=1,y=2
    • 函数名的形式

      1
      2
      3
      4
      5
      6
      //export.js
      export function x(){
      }

      import {x} from "./export.js";
      console.log(x)//输出的为x函数
    • import as

      1
      2
      3
      4
      5
      6
      7
      8
      /export.js
      export let x=1;
      export let y=2;

      //import.js
      import * as myVar from "./export.js"
      console.log(myVar.x)//输出为1
      console.log(myVar.y)//输出为2
  • 以模板的形式export和import

    • export
      1
      2
      //export.js
      export default let x=1;
    • import
      1
      2
      import x from  "./export.js";
      console.log(x) //输出的是x

Promise

异步编程的一种解决方案,从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

  • Promise 异步操作有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const p1 = new Promise(function(resolve,reject){
    resolve('success1');
    });
    const p2 = new Promise(function(resolve,reject){
    resolve('success2');
    reject('reject');
    });
    p1.then(function(value){
    console.log(value); // success1
    });
    p2.then(function(value){
    console.log(value); // success2
    });
  • then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    function getNumber(){
    var p = new Promise(function(resolve, reject){
    //做一些异步操作
    setTimeout(function(){
    var num = Math.ceil(Math.random()*10); //生成1-10的随机数
    if(num<=5){
    resolve(num);
    }
    else{
    reject('数字太大了');
    }
    }, 2000);
    });
    return p;
    }

    getNumber()
    .then(
    function(data){
    console.log('resolved');
    console.log(data);
    },
    function(data){
    console.log('rejected');
    console.log(data);
    }
    );
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
      
    - **catch类似于then的第二参数,并有try/catch语句的功能,注意总是返回或终止promise**

    ```js
    getNumber()
    .then(function(data){
    console.log('resolved');
    console.log(data);
    console.log(somedata); //此处的somedata未定义
    })
    .catch(function(reason){
    console.log('rejected');
    console.log(reason);
    });
  • all的用法

    Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调,all会把所有异步操作的结果放进一个数组中传给then。

    1
    2
    3
    4
    5
    Promise
    .all([runAsync1(), runAsync2(), runAsync3()])
    .then(function(results){
    console.log(results);
    });
  • race方法

    all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就是赛跑的意思。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    //请求某个图片资源
    function requestImg(){
    var p = new Promise(function(resolve, reject){
    var img = new Image();
    img.onload = function(){
    resolve(img);
    }
    img.src = 'xxxxxx';
    });
    return p;
    }

    //延时函数,用于给请求计时
    function timeout(){
    var p = new Promise(function(resolve, reject){
    setTimeout(function(){
    reject('图片请求超时');
    }, 5000);
    });
    return p;
    }

    Promise
    .race([requestImg(), timeout()])
    .then(function(results){
    console.log(results);
    })
    .catch(function(reason){
    console.log(reason);
    });

Generator

ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。

  • Generator 有两个区分于普通函数的部分(* 用来表示函数为 Generator 函数,yield 用来定义函数内部的状态)
    • 一是在 function 后面,函数名之前有个 * ;
    • 函数内部有 yield 表达式。
      1
      2
      3
      4
      5
      6
      7
      8
      function* func(){
      console.log("one");
      yield '1';
      console.log("two");
      yield '2';
      console.log("three");
      return '3';
      }

async

  • async 是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联的。

    1
    2
    3
    4
    //name: 函数名称。
    //param: 要传递给函数的参数的名称。
    //statements: 函数体语句。
    async function name([param[, param[, ... param]]]) { statements }
  • 返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    async function helloAsync(){
    return "helloAsync";
    }

    console.log(helloAsync()) // Promise {<resolved>: "helloAsync"}

    helloAsync().then(v=>{
    console.log(v); // helloAsync
    })
  • awync表达式

    async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function testAwait(){
    return new Promise((resolve) => {
    setTimeout(function(){
    console.log("testAwait");
    resolve();
    }, 1000);
    });
    }

    async function helloAsync(){
    await testAwait();
    console.log("helloAsync");
    }
    helloAsync();
    // testAwait
    // helloAsync
  • await针对所跟不同表达式的处理方式:

    • Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。

    • 非 Promise 对象:直接返回对应的值。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      function testAwait(){
      console.log("testAwait");
      }
      async function helloAsync(){
      await testAwait();
      console.log("helloAsync");
      }
      helloAsync();
      // testAwait
      // helloAsync

补充

补充:闭包

  1. 闭包

    闭包是指有权访问另一个函数作用域中的变量的函数

    特点:

    1. 函数嵌套函数
    2. 函数内部可以引用函数外部的参数和变量
    3. 参数和变量不会被垃圾回收机制回收
  2. 闭包的作用

    • 可以在函数的外部访问到函数内部的局部变量
    • 让这些变量始终保存在内存中,直到访问变量的那个函数被销毁。
  3. 作用域链

    JavaScript高级程序设计(第3版)

    当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。

    实现原理

    函数A内部所在的其它函数B可以访问到当前函数A的内部变量,那么如果我们将其内部所在的其它函数B作为返回值将其返回,并在函数A的外部用一个变量C来接收到这个返回值。那么这样,在函数A外部操作这个变量C时,实际上就是在操作函数A的返回值,也就是函数A所生成的包含环境中所在的内部函数B。而这个内部函数B是有权访问到函数A的内部变量的,所以在函数A外部的变量也就可以访问到函数A内部的变量。

  4. 闭包的主要形式

    1. 函数作为返回值

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      function a(){
      var name = 'dov';
      return function(){
      return name;
      }
      }
      var b = a();
      console.log(b()) //dov

      function fn(){
      var num = 3;
      return function(){
      var n = 0;
      console.log(++n);
      console.log(++num);
      }
      }
      var fn1 = fn()
      fn1() //1 4
      fn1() //1 5

      for(var i = 0; i < 5; ++i){
      setTimeout(function(){
      console.log(i+' ');
      },100);
      }
      //5 5 5 5 5
      for(var i = 0; i < 5; ++i){
      (function(i){
      setTimeout(function(){
      console.log(i+' ');
      },3000);
      }(i));
      }
      //0 1 2 3 4
      for(var i = 0; i < 5; ++i){
      (function(i){
      setTimeout(function(){
      console.log(i+' ');
      },i*1000);
      }(i));
      }
      //0 1 2 3 4
    2. 闭包作为参数传递

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      var num = 15;
      var fn1 = function(x){
      if(x>num){
      console.log(x);
      }
      }
      void function(fn2){
      var num = 100
      fn2(30)
      }(fn1)
      //30
  5. 闭包的优劣

    好处

    1. 保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突

    2. 在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)

    3. 匿名自执行函数可以减少内存消耗

    坏处

    1. 其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;

    2. 其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响

补充:堆与栈

  1. 堆与栈

    栈(stack):栈会自动分配内存空间,会自动释放,存放基本类型,简单的数据段,占据固定大小的空间。(基本类型:String,Number,Boolean,Null,Undefined)
    堆(heap):动态分配的内存,大小不定也不会自动释放,存放引用类型,指那些可能由多个值构成的对象,保存在堆内存中,包含引用类型的变量,实际上保存的不是变量本身,而是指向该对象的指针。(引用类型:Function,Array,Object)

  2. 区分

    栈:所有在方法中定义的变量都是放在栈内存中,随着方法的执行结束,这个方法的内存栈也自然销毁。
    优点:存取速度比堆快,仅次于直接位于CPU中的寄存器,数据可以共享;
    缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
    堆:堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(参数传递)。创建对象是为了反复利用,这个对象将被保存到运行时数据区。

  3. 堆与栈的溢出

    栈:可以递归调用方法,这样随着栈深度的增加,JVM维持着一条长长的方法调用轨迹,知道内存不够分配,产生栈溢出。
    堆:循环创建对象,通俗点就是不断的new 一个对象。

  4. 传值与传参

    栈和堆的区别:即基本类型和引用类型的区别

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var a = [1,0,9,8,7];
    var b = a;
    b[0] = 3;
    console.log(b); // [3, 0, 9, 8, 7]
    console.log(a); // [3, 0, 9, 8, 7]

    var c = 5;
    var d = c;
    d = 9;
    console.log(d); // 9
    console.log(c); // 5
  5. 深浅拷贝

    工作中会经常遇到操作数组、对象的情况,需要你将原数组、对象进行镜像(备份)。

    基本数据类型

    string、number、null、undefined、boolean、symbol(ES6新增) 变量值存放在栈内存中,可直接访问和修改变量的值
    基本数据类型不存在拷贝

    引用类型

    Object Function RegExp Math Date 值为对象,存放在堆内存中,在栈内存中变量保存的是一个指针,指向对应在堆内存中的地址。当访问引用类型的时候,要先从栈中取出该对象的地址指针,然后再从堆内存中取得所需的数据。

    浅拷贝

    • 简单的引用拷贝

      1
      2
      3
      4
      5
      var a = [1,2,3,4];
      var b = a;
      b[0] = 0;
      console.log(a); // [0, 2, 3, 4]
      console.log(a); // [0, 2, 3, 4]
    • 数组的concat、slice拷贝

      对于成员为基本类型的可以当成深拷贝

      1
      2
      3
      4
      5
      6
      var a = [1,2,3,4];
      var b = a.concat();
      // var b = a.slice(0);
      b[0] = 0;
      console.log(a); // [1, 2, 3, 4]
      console.log(b); // [0, 2, 3, 4]

      对于成员为引用类型的依旧是浅拷贝

      1
      2
      3
      4
      5
      6
      var a = [1,2,[3,4],{name:'ccy'}];
      var b = a.concat();
      // var b = a.slice(0);
      b[3].name = 'hs';
      console.log(a[3]); // {name:'hs'}
      console.log(b[3]); // {name:'hs'}
    • 对象的assign拷贝

      对于对象的属性为基本类型的可以当成深拷贝。

      1
      2
      3
      4
      5
      var a = {age:18,name:'cc',info:{address:'wuhan',interest:'playCards'}};
      var b = Object.assign({},a);
      b.name = 'dd';
      console.log(a.name); // cc
      console.log(b.name); // dd

      对于对象的属性为引用类型的依旧是浅拷贝。

      1
      2
      3
      4
      5
      var a = {age:18,name:'ccy',info:{address:'wuhan',interest:'playCards'}};
      var b = Object.assign({},a);
      b.info.address = 'shenzhen';
      console.log(a.info); // {address:'shenzhen',interest:'playCards'}
      console.log(b.info); // {address:'shenzhen',interest:'playCards'}

    深拷贝

    • 自定义深度拷贝函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      function deepClone(o) {
      // 判断如果不是引用类型,直接返回数据即可
      if (typeof o === 'string' || typeof o === 'number' || typeof o === 'boolean' || typeof o === 'undefined') {
      return o
      } else if (Array.isArray(o)) { // 如果是数组,则定义一个新数组,完成复制后返回
      // 注意,这里判断数组不能用typeof,因为typeof Array 返回的是object
      console.log(typeof []) // --> object
      var _arr = []
      o.forEach(item => { _arr.push(item) })
      return _arr
      } else if (typeof o === 'object') {
      var _o = {}
      for (let key in o) {
      _o[key] = deepClone(o[key])
      }
      return _o
      }
      }
      var arr = [1, 2, 3, 5]
      var cloneArr = deepClone(arr)
      console.log(cloneArr) // --> [ 1, 2, 3, 5 ]
      console.log(arr === cloneArr) // --> false
      var obj = { name: 'ccc', age: 18 }
      var cloneObj = deepClone(obj)
      console.log(cloneObj) // --> { name: 'ccc', age: 18 }
      console.log(obj === cloneObj) // false
      obj.name = 'www'
      console.log(obj) // --> { name: 'www', age: 18 }
      console.log(cloneObj) // --> { name: 'ccc', age: 18 }
    • 函数库 lodash 的 _.cloneDeep、 JQuery 的 $.extend都实现了深拷贝