1 ECMAScript 6 简介

  • ECMAScript 6.0(以下简称 ES6)是 JavaScript 语⾔的下⼀代标准,已经在 2015 年 6 ⽉正式发布了。
  • 其⽬标是使得 JavaScript 语⾔可以用来编写复杂的⼤型应⽤程序,成为企业级开发语⾔。

    ① ECMAScript 和 JavaScript 的关系

  • ECMAScript是JavaScript的规格

  • JavaScript是ECMAScript的⼀种实现

    ② ES6 与 ECMAScript 2015 的关系

  • 2011年,ECMAScript5.1版发布后,就开始制定 6.0 版了。因此,ES6这个词就是指 JavaScript 语⾔的下⼀个版本。

  • 2015年6⽉发布了ES6的第⼀个版本,正式名称就是《ECMAScript 2015 标准》(简称ES2015)。
  • 2016年6⽉⼩幅修订的ES6.1版本发布,简称ES2016标准,同样2017年6⽉发布 ES2017 标准。
  • 因此ES6既是⼀个历史名词,也是⼀个泛指,含义是5.1版以后的JavaScript的下⼀代标准,涵盖了ES2015、ES2016、ES2017等等。

    ③ ECMAScript 的历史

  • 1997年发布了ECMAScript 1.0版本

  • 1998年6⽉发布了ECMAScript 2.0
  • 1999年12⽉发布了ECMAScript 3.0,在业界得到⼴泛⽀持,成为通⾏标准,奠定了JavaScript语⾔的基本语法。
  • 2000年ECMAScript 4.0开始酝酿,其⼤部分内容被ES6继承了。因此,ES6制定的起点其实是2000年。
  • 2007年10⽉,ECMAScript4.0版草案发布。
  • 2008年7⽉由于各⽅分歧太⼤,争论过于激烈,ECMA开会决定,中⽌ECMAScript 4.0的开发,并发布ECMAScript3.1(改名为 ECMAScript 5)。
  • 2009 年 12 ⽉,ECMAScript 5.0 版正式发布。
  • 2011 年 6 ⽉,ECMAScript 5.1 版发布,并且成为 ISO 国际标准(ISO/IEC 16262:2011)
  • 2013 年 3 ⽉,ECMAScript 6 草案冻结,不再添加新功能。新的功能设想将被放到 ECMAScript7。
  • 2013 年 12 ⽉,ECMAScript 6 草案发布。然后是 12 个⽉的讨论期,听取各⽅反馈。
  • 2015 年 6 ⽉,ECMAScript 6 正式通过,成为国际标准。从 2000 年算起,这时已经过去了 15年。
  • 2016年 ES2016(ES7) 新功能主要包括:
    • Array.prototype.includes检查数组中是否存在值;(区别ES6字符串的includes⽅法)
    • Exponentiation Operator 求幂运算 (a ** b等价于Math.pow(a,b))
  • 2017年 ES2017(ES8) 部分功能:
    • Object.values/entries/getOwnPropertyDescriptors
    • String.prototype.padStart/padEnd
    • 函数参数列表和调⽤中的尾逗号(Trailing commas)
    • Async Functions 异步函数(async/await)

推荐查看在线文档

2 let和const命令

  • S6新增了let和const来声明变量,主要是解决var声明变量所造成的困扰和问题:
    • var存在变量提升
    • var可以重复声明变量
    • var不⽀持块级作⽤域
    • var不能⽤于定义常量
  • let命令,⽤来声明变量。它的⽤法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
  • const声明⼀个只读的常量。⼀旦声明,常量的值就不能改变。
  • const声明的变量不得改变值,这意味着,const⼀旦声明变量,就必须⽴即初始化,不能留到以后赋值。

    ① 变量的提升问题

  • 由var声明的变量存在变量提升

  • var声明的变量可以在声明之前使⽤,相当于默认为其声明其值为undefined ```javascript function text1(){ console.log(name); //undefined console.log(age); //undefined var name = “zhangsan”; var age = 20; console.log(name); //zhangsan console.log(age); //20 } text1();

//等价于如下 function text2(){ var name,age; console.log(name); //undefined console.log(age); //undefined name = “zhangsan”; age = 20; console.log(name); //zhangsan console.log(age); //20 } text2(); //注意:在函数内加var为局部变量,不加var则是全局变量(在执⾏当前函数之后)

  1. let声明的变量⼀旦⽤let声明,那么在声明之前,此变量都是不可⽤的,术语称为 “暂时性死区”
  2. ```javascript
  3. console.log(a); //undefined
  4. //console.log(b); //引⽤错误ReferenceError: Cannot access 'b' before
  5. initialization
  6. var a = 10;
  7. let b = 20;
  8. console.log(a); //10
  9. console.log(b); //20

对’暂时性死区’的理解

  • 只要块级作⽤域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
  • ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从⼀开始就形成了封闭作⽤域。凡是在声明之前就使⽤这些变量,就会报错。
  • 总之,在代码块内,使⽤let命令声明变量之前,该变量都是不可⽤的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
var tmp ="aaa";
if(true){
    // TDZ开始
    //tmp = 'bbb'; // ReferenceError
    //console.log(tmp); // ReferenceError

    let tmp; // TDZ结束
    console.log(tmp); // undefined
    tmp = "ccc";
    console.log(tmp); // ccc
}

② 重复声明变量

  • var可以重复声明变量
  • let不允许在相同作⽤域内,重复声明同⼀个变量。
    function demo(c){
      var a = 10;
      var a = 20; //可以使⽤var重复声明var已经声明过的变量a
      //let a = 30; //报错,不可以使⽤let声明已经被var声明过的变量a
      //错误信息:SyntaxError: Identifier 'a' has already been declared
      let b = 30;
      //let b = 40; //报错,不可以使⽤let重复声明变量b
      //var b = 50; //报错,不可以使⽤var声明已经被let声明过的变量
      //SyntaxError: Identifier 'b' has already been declared
      //let c = 70; //报错,不可以使⽤let重复声明已存在的参数c
      //SyntaxError: Identifier 'c' has already been declared
    }
    demo(60);
    

    ③ 块级作用域

    let命令所在的代码块内有效,并且所⽤域也仅限于当前代码有效 ```javascript //案例1:使⽤let声明变量只在代码块中有效 { var a = 10; let b = 20; }

console.log(a); //10 //console.log(b); //报错 ReferenceError: b is not defined //案例2: for循环的计数器,就很合适使⽤let命令。 for(var i=0;i<10;i++){} console.log(i); //10

for(let j=0;j<10;j++){} //console.log(j); //报错:ReferenceError: j is not defined //案例3 var m = []; for (var i = 0; i < 10; i++) { m[i] = function () { console.log(i); }; } m0; // 10 /数组m成员⾥⾯的i,都指向的是同⼀个i,导致运⾏时输出的是最后⼀轮的i的值,也就是 10 代码中存在两个全局变量,数组a与循环变量i,数组中每个元素保存着一个匿名函数,该函数引用了循环变量i。 在调用a0 时,i值是已经循环10次后的变量值10. 如果想要在函数体内保存每次循环时的值,可以用即时调用函数表达式或使用let块级循环作用域. 后者使用es6语法,将 var i 改为 let i 即可,保证10个函数元素,依次打印0,1,2…/

var n = []; for (let i = 0; i < 10; i++) { n[i] = function () { console.log(i); }; } n6; // 6 //for循环变量的这部分是⼀个⽗作⽤域,⽽循环体内部是⼀个单独的⼦作⽤域 //⽽let,声明的变量仅在块级作⽤域内有效,最后输出的是6 / 类似于如下格式 var aa = []; { let i = 1; { let k = i; aa[k] = function(){ console.log(k); } } i++; { let k = i; aa[k] = function(){ console.log(k); } } … } /

<a name="7BJfB"></a>
## ④ 定义常量--const命令

- const声明⼀个只读的常量。⼀旦声明,常量的值就不能改变。类似于java中的final关键字。
- const声明的变量不得改变值,这意味着,const⼀旦声明变量,就必须⽴即初始化,不能留到以后赋值。
- const的作⽤域与let命令相同:只在声明所在的块级作⽤域内有效。
- const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后⾯使⽤。
- const声明的常量,也与let⼀样不可重复声明。
```javascript
//案例1:常量不可修改,重复声明
const PI = 3.1415926;
console.log(PI)
//PI = 3.14 //常量不可再次赋值:
// 原因:TypeError: Assignment to constant variable.
//const PI = 3.14 //不可重复声明
// 原因:SyntaxError: Identifier 'PI' has already been declared

//案例2:常量声明只在块级别所⽤域内有效
{
  const CEO = "⾸席执⾏官"
  console.log(CEO)
}
//console.log(CEO) //报错:ReferenceError: CEO is not defined

//案例3:常量也是不可提升,及必须在声明后使⽤常量
//console.log(CTO) //ReferenceError: Cannot access 'CTO' before initialization
const CTO = "⾸席技术官"
console.log(CTO)

本质

const实际上保证的,并不是变量的值不得改动,⽽是变量指向的那个内存地址所保存的数据不得改动。 对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。 但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是⼀个指向实际数据的指针, const只能保证这个指针是固定的(即总是指向另⼀个固定的地址),⾄于它指向的数据结构是不是可变的,就完全不能控制了。

//定义⼀个⼈这个常量对象
const person = {name:'zhangsan',age:20};

//尝试修改⼈对象,是不可以修改的。
//person = {name:"lisi",age:22}
//TypeError: Assignment to constant variable. 分配给常量变量

//但是修改person对象中的⼀个属性值,可以成功
person.age = 30;
console.log(person); //{name: "zhangsan", age: 30}

//若是连属性都不可以修改的话,可以使⽤ES5中的Object.freeze()
const p = Object.freeze(person);
p.age = 25; //没有报错,但是修改不了属性值
console.log(person); //{name: "zhangsan", age: 30}

3 变量的解构赋值

ES6 允许按照⼀定模式,从数组和对象中提取值,对变量进⾏赋值,这被称为 解构(Destructuring)
1. 数组的解构赋值
2. 对象的解构赋值
3. 字符串的解构赋值
4. 数值和布尔值的解构赋值
5. 函数参数的解构赋值
6. 圆括号问题
7. ⽤途

① 数组的解构赋值

属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

//声明多个变量并赋值
let a = 10;
let b = 20;
let c = 30;

//ES6 允许写成下⾯这样
let [x,y,z]=[10,20,30];
console.log(x); //10
console.log(y); //20
console.log(z); //30

//从数组中提取值,按照对应位置,对变量赋值
let [x1,[[y1],z1]]=[10,[[20],30]]; //嵌套数组
console.log(x1); //10
console.log(y1); //20
console.log(z1); //30

其它数组的解构赋值

//解构赋部分数值
let [m, n] = [10,20,30];
console.log(m); //10
console.log(n); //20

let [i,[j],k] = [10,[20,30],40];
console.log(i); //10
console.log(j); //20
console.log(k); //40

let [x, , y] = [1, 2, 3];
console.log(x); //1
console.log(y); //3

let [ , , z] = ["one", "two", "three"];
console.log(z); //three
//其中...为ES6的扩展运算符,即d加上...可以接收多个值

let [a, ...d] = [1, 2, 3, 4];
console.log(a); // 1
console.log(d); // [2, 3, 4]

let [x1, y1, ...z1] = ['a'];
console.log(x1); // "a"
console.log(y1); // undefined
console.log(z1); // []

//没有接收到值得变量,默认为undefined
let [z2] = []; //z2:undefined
let [x2, y2] = [1]; //x2:1,y2:undefined

如果等号的右边不是数组(或者严格地说,不是可遍历的结构(Iterator)),那么将会报错。

// 以下都会报类型错误
let [foo] = 1; //TypeError: 1 is not iterable 不是可迭代的
let [foo] = false; //TypeError: false is not iterable
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

默认值:

//当⼀个数组成员严格等于undefined,默认值才会⽣效。
let [b = true] = [];
console.log(b); //true

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x1, y1 = 'b'] = ['a', undefined]; // x1='a', y1='b'
console.log(x); //a
console.log(y); //b

let [m = 1] = [undefined];
console.log(m) // 1

let [n = 1] = [null]; //值不是undefined,所以没有使⽤默认值
console.log(n) // null

② 对象的解构赋值

解构不仅可以⽤于数组,还可以⽤于对象。

//对象的解构赋值与数组不同,要求变量必须与属性同名,才能取到正确的值。
//let {name, age} = {name:'张三', age:20};
let {age, name} = {name:'张三', age:20};
let {sex} = {name:'张三', age:20};//解构失败,变量的值等于undefined
console.log(name); //张三
console.log(age); //20
console.log(sex); //undefined

//如果变量名与属性名不⼀致,必须写成下⾯这样
let {email:em, password:ps} = {email:'zs@163.com', password:'123456'};
//如上代码email和password都是匹配的模式,em才是变量。真正被赋值的是变量em,⽽不是模式
email
console.log(em); //zs@163.com
console.log(ps); //123456
console.log(email); //错误 ReferenceError: email is not defined

与数组⼀样,解构也可以⽤于嵌套结构的对象。

//定义⼀个书的信息
var obj = {
  book: [
    'JavaScript权威指南',
    {author:'小淘',price:132}
  ]
};
//let {book:[title, {author, price}]} = obj;
//此时book是模式,不是变量,因此不会被赋值。如果book也要作为变量赋值,可写成如下:
let {book,book:[title, {author, price}]} = obj;
console.log(title); //JavaScript权威指南
console.log(author);//小淘
console.log(price); //132
console.log(book); //['JavaScript权威指南',{author:'⼩淘',price:132}]

对象的解构也可以指定默认值

var {x=3} = {};
console.log(x); // 3

var {x1, y1=5} = {x1:1};
console.log(x1); // 1
console.log(y1); // 5

var {x2: y2=3} = {};
console.log(y2); // 3

var {x3: y3=3} = {x3: 5};
console.log(y3); // 5

③ 字符串的解构赋值(了解)

字符串也可以解构赋值。这是因为此时,字符串被转换成了⼀个类似数组的对象。

const [a, b, c, d, e] = 'hello';
console.log(a); // "h"
console.log(b); // "e"
console.log(c); // "l"
console.log(d); // "l"
console.log(e); // "o"

类似数组的对象都有⼀个length属性,因此还可以对这个属性解构赋值。

let {length : len} = 'hello';
console.log(len); // 5

④ 数值和布尔值的解构赋值(了解)

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

//解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。
let {toString: s1} = 123;
//数值和布尔值的包装对象都有toString属性

console.log(s1 === Number.prototype.toString); // true
let {toString: s2} = true;
console.log(s2 === Boolean.prototype.toString); // true

//由于undefined和null⽆法转为对象,所以对它们进⾏解构赋值,都会报错。
let { prop: x3 } = undefined; // TypeError
let { prop: y3 } = null; // TypeError

⑤ 函数参数的解构赋值

//函数的参数也可以使⽤解构赋值。
function move({x=0, y=0} = {}) {
    return [x, y];
}
console.log(move({x:3, y:8})); // [3, 8]
console.log(move({x:3})); // [3, 0]
console.log(move({})); // [0, 0]
console.log(move()); // [0, 0]

⑥ 圆括号问题(了解)

  • 变量声明语句,模式不能使⽤圆括号
  • 函数参数也属于变量声明,因此不能带有圆括号 ```javascript // 变量声明语句,模式不能使⽤圆括号,以下6⾏全部报错 // let [(a)] = [1]; // let {x: (c)} = {}; // let ({x: c}) = {}; // let {(x: c)} = {}; // let {(x): c} = {}; // let { o: ({ p: p }) } = { o: { p: 2 } };

//函数参数也属于变量声明,因此也不能带有圆括号 //function f([(z)]) { return z; } //function f([z,(x)]) { return x; }

//将整个模式放在圆括号之中,导致报错 //({ p: a }) = { p: 42 }; //([a]) = [5];

//将⼀部分模式放在圆括号之中,导致报错 // let[({ p: a }), { x: c }] = [{}, {}];

//可以使⽤圆括号的情况只有⼀种:赋值语句的⾮模式部分,可以使⽤圆括号。 let b,d; [(b)] = [3]; // 正确 模式是取数组的第⼀个成员跟圆括号⽆关 ({p:(d)} = {p:20}); // 正确 模式是p,⽽不是d [(parseInt.prop)] = [3]; // 正确 与第⼀⾏语句的性质⼀致。 // ⾸先它们都是赋值语句,⽽不是声明语句;其次它们的圆括号都不属于模式的⼀部分

<a name="ofuEw"></a>
## ⑦ 用途
(1)交换变量的值
```javascript
let x = 1;
let y = 2;
[x, y] = [y, x];

(2)从函数返回多个值

// 函数只能返回⼀个值,如果要返回多个值,只能将它们放在数组或对象⾥返回。
// 有了解构赋值,取出这些值就⾮常⽅便。
// 返回⼀个数组
function example1() {
    return [1, 2, 3];
}
let [a, b, c] = example1();

// 返回⼀个对象
function example2() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example2();

(3)函数参数的定义

// 解构赋值可以⽅便地将⼀组参数与变量名对应起来
// 参数是⼀组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 参数是⼀组⽆次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});

(4)提取 JSON 数据

// 解构赋值对提取 JSON 对象中的数据,尤其有⽤。
let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;

console.log(id, status, number);
// 42, "OK", [867, 5309]

4 字符串的扩展

  • 模板字符串
  • 模板字符串实例
  • 标签模板

    ① 模板字符串

  • 模板字符串(template string)是增强版的字符串,⽤反引号(`)标识。

  • 它可以当作普通字符串使⽤,也可以⽤来定义多⾏字符串,或者在字符串中嵌⼊变量
  • 如果在模板字符串中需要使⽤反引号,则前⾯要⽤反斜杠转义。
  • 如果使⽤模板字符串表示多⾏字符串,所有的空格和缩进都会被保留在输出之中。
  • 模板字符串之中可以放⼊js表达式、对象属性、还能调⽤函数。
  • 注意:如果模板字符串中的变量没有声明,将报错。 ```javascript //模板字符串的使⽤ let url = “http://www.baidu.com“; let title = “百度”;

//传统的⼦串拼装 console.log(‘‘+title+’‘); //输出:百度

//使⽤模板⼦串 console.log(<a href="${url}">${title}</a>); //输出:百度

//在模板⼦串中输出反引号,可使⽤反斜线转义 console.log(Hello \ZhangSan`!~`); //输出:

//传统定义多⾏⼦串格式 let tpl1 = “

    “ + “
  • 北京
  • “ + “
  • 上海
  • “ + “
  • ⼴州
  • “ + “
“; console.log(tpl1); //输出:
  • 北京
  • 上海
  • ⼴州

//使⽤模板⼦串定义多⾏⼦串格式,⽽且所有的空格和缩进都会被保留 let tpl2 = <ul> <li>北京</li> <li>上海</li> <li>⼴州</li> </ul>; console.log(tpl2); / 输出:

  • 北京
  • 上海
  • ⼴州
/

//模板⼦串中⽀持使⽤对象属性、表达式运算及函数调⽤等操作 let stu = {name:”张三”,age:18}; console.log(我叫${stu.name},今年${stu.age}岁,就读于清华⼤⼀,等毕业我就${stu.age+4}岁了!); //输出:我叫张三,今年18岁,就读于清华⼤⼀,等毕业我就22岁了!

<a name="Lejj5"></a>
## ② 模板字符串实例:
```javascript
//将下⾯的数据输出到模板ul标签中
let data = [
  {name:"张三",sex:1},
  {name:"李四",sex:0},
  {name:"王五",sex:1},
  {name:"赵六",sex:0},
];

let tpl = `<ul>${data.map(stu => `<li>${stu.name}</li>`).join("")}</ul>`;
console.log(tpl);
//输出结果:<ul><li>张三</li><li>李四</li><li>王五</li><li>赵六</li></ul>

//也可以写出如下格式
let tpl2 = `<ul>
    ${data.map(stu => `
      <li>
        ${stu.name}
        ${stu.sex==1?"男":"⼥"}
      </li>
    `).join("")}
  </ul>`;
document.body.innerHTML = tpl2; //输出⻚⾯body中

③ 模板标签

模板字符串的功能,不仅仅是上⾯这些。
它可以紧跟在⼀个函数名后⾯,该函数将被调⽤来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。

alert("hello1");
//等同于
alert`hello2`;
  • 标签模板其实不是模板,⽽是函数调⽤的⼀种特殊形式。
  • “标签”指的就是函数,紧跟在后⾯的模板字符串就是它的参数。
  • 如果模板字符中有变量,就不是简单的调⽤了,⽽是会将模板字符串先处理成多个参数,再调⽤函数。

    let a = 5;
    let b = 10;
    tag`Hello ${ a + b } world ${ a * b }`;
    // 等同于
    //tag(['Hello ', ' world ', ''], 15, 50);
    //function tag(tpldata, value1, value2){
      // ...
    //}
    // 等同于
    function tag(tpldata, ...values){
    console.log(tpldata);//["Hello ", " world ", ""]
    console.log(values); //[15, 50]
    console.log(arguments);
    //[["Hello ", " world ", ""],15,50]
    }
    

    “标签模板”的⼀个重要应⽤,就是过滤 HTML 字符串,防⽌⽤户输⼊恶意内容。

    //定义⼀个安全处理html标签函数
    function SaferHTML(tpldata, ...values){
    let s = tpldata[0];
    for (let i = 0; i < values.length; i++){
      let arg = String(values[i]);
      //在替换中转义特殊字符
      s += arg.replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;");
      //不要转义模板中的特殊字符。
      s += tpldata[i+1];
    }
    return s;
    }
    //测试
    let content = '<script>alert("Hello")<\/script>'; // 恶意代码
    let message = SaferHTML`<p>${content} 已向您发送消息.</p>`; //使⽤标签模板
    console.log(message);
    //输出:<p>&lt;script&gt;alert("Hello")&lt;/script&gt; 已向您发送消息.</p>
    

    5 字符串的新增方法

  • 实例方法:includes(), startsWith(), endsWith()— ⼦串查找⽅法,返回布尔值。

  • 实例方法:repeat()— ⽅法返回⼀个新字符串,表示将原字符串重复n次。
  • 实例方法:padStart(),padEnd() — 字符串补全⻓度的功能。
  • 实例方法:trimStart(),trimEnd()—消除字符串头部或尾部的空格,返回新串,不修改原字符串。
  • 实例方法:matchAll()— 返回⼀个正则表达式在当前字符串的所有匹配。

    ① 实例方法:includes(), startsWith(), endsWith():

  • JavaScript 只有indexOf⽅法,可以⽤来确定⼀个字符串是否包含在另⼀个字符串中。

  • ES6 ⼜提供了三种新⽅法。
    • includes(): 返回布尔值,表示是否找到了参数字符串。
    • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
    • endsWith(): 返回布尔值,表示参数字符串是否在原字符串的尾部。 ```javascript //定义⼀个url地址 let url = ‘http://www.baidu.com/a/b.html‘;

//判断url字符串是否是以http开头 console.log(url.startsWith(‘http’)); //true

//判断url字符串是否是以html结束 console.log(url.endsWith(‘html’)); //true

//判断url字符串中是否包含baidu⼦串 console.log(url.includes(‘baidu’)); //true

<a name="IWUoR"></a>
## ② 实例方法:repeat()
repeat⽅法返回⼀个新字符串,表示将原字符串重复n次。
```javascript
//将原字符串重复指定次数
console.log('x'.repeat(3)); // 'xxx' 3次重复x
console.log('hello'.repeat(2)); // 'hellohello' 2次重复hello
console.log('na'.repeat(0)); // '' 0次重复,故没有输出内容
console.log('na'.repeat(2.9)); // 'nana' 参数会⾃动舍去取整为2
console.log('na'.repeat(-1)); // 范围错误 RangeError: Invalid count value

③ 实例方法:padStart() / padEnd()

  • ES2017 引⼊了字符串补全⻓度的功能。
  • 如果某个字符串不够指定⻓度,会在头部或尾部补全。
    • padStart()⽤于头部补全
    • padEnd()⽤于尾部补全
  • 这两个⽅法都有两个参数:
    • 第⼀个参数⽤来指定字符串的最⼩⻓度,
    • 第⼆个参数是⽤来补全的字符串,省略默认补空格。 ```javascript console.log(“123”.padStart(5,”0”)); //00123 console.log(“123”.padEnd(5,”0”)); //12300 console.log(“123”.padEnd(2,”0”)); //123

//补⻬等宽编号 console.log(“3”.padStart(8,”20190000”)); //20190003 console.log(“23”.padStart(8,”20190000”)); //20190023 console.log(“123”.padStart(8,”20190000”)); //20190123

//另⼀个⽤途是提示字符串格式(如下提示⽇期格式)。 console.log(‘12’.padStart(10, ‘YYYY-MM-DD’)); // “YYYY-MM-12” console.log(‘09-12’.padStart(10, ‘YYYY-MM-DD’)); // “YYYY-09-12”

<a name="4M1km"></a>
## ④ 实例方法:trimStart(),trimEnd()

- ES2019 对字符串实例新增了trimStart()和trimEnd()这两个⽅法。
- 它们的⾏为与trim()⼀致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。
- 它们返回的都是新字符串,不会修改原始字符串。
```javascript
const s = " abc ";
s.trim() // "abc"
s.trimStart() // "abc "
s.trimEnd() // " abc"
  • 浏览器还部署了额外的两个⽅法,trimLeft()是trimStart()的别名,trimRight()是trimEnd()的别名。

    ⑤ 实例方法:matchAll()

    matchAll()⽅法返回⼀个正则表达式在当前字符串的所有匹配。 ```javascript let info = “
    • 北京
    • 上海
    • ⼴州
    “; //传统匹配⽅式 console.log(info.match(/
  • (.*?)<\/li>/g)); //结果:[“
  • 北京
  • “, “
  • 上海
  • “, “
  • ⼴州
  • “]

//ES2020 增加了String.prototype.matchAll()⽅法, //可以⼀次性取出所有匹配。不过,它返回的是⼀个遍历器(Iterator),⽽不是数组。 console.log([…info.matchAll(/

  • (.*?)<\/li>/g)]); //结果:[Array(2), Array(2), Array(2)] //其中[“
  • 北京
  • “,”北京”,index:4,input:”
    “,groups: undefined]

    <a name="Hjrl1"></a>
    # 6 运算符的扩展
    es6之扩展运算符 三个点(...)
    
    - 对象的扩展运算符
    - 数组的扩展运算符
    - 剩余参数的处理
    <a name="ee4j2"></a>
    ## ① 对象的扩展运算符
    ```javascript
    //声明⼀个对象stu1,内有三个属性
    let stu1 = {name:'李四',age:22,sex:'男'};
    //使⽤三个点将stu1对象中的属性复制⼀份赋给stu2(独⽴的)
    let stu2 = {...stu1};
    stu1.age = 30;
    console.log(stu2); //{name: "李四", age: 22, sex: "男"}
    

    ② 数组的扩展运算符

    //声明⼀个数组,并赋初值,使⽤三个点实现数组的复制
    let a = [10,20,30];
    let b = [...a]; //复制数组a中的所有元素赋给b
    a[2] = 300; //修改数组a中的⼀个元素值
    console.log(b); //数组b没有影响 [10, 20, 30]
    
    //使⽤三个点实现将数组转为参数序列。
    function demo(x,y,z){
        console.log(x,y,z); //10,20,300
    }
    demo(...a); //使⽤三个点将数组转换为参数序列
    

    ③ 使用三个点(…)对剩余参数的处理

    //在以前我们使⽤arguments来收集函数调⽤传递的任意数量参数。
    function sum(){
      let res = 0;
      //遍历所有参数
      for(let v of arguments){
          res += v; //累加
      }
      return res;
    }
    console.log(sum(10,20,30,40)); //100 调⽤上⾯函数可以传递很多参数
    //执⾏效果同时,使⽤三点来收集所有参数
    function sum2(...numbers){
      let res = 0;
      for(let v of numbers){
          res += v;
      }
      return res;
    }
    console.log(sum2(10,20,30,40)); //100
    

    剩余参数的处理实例

    //定义⼀个处理折扣的函数,第⼀个参数为折率,其余参数为价格
    function discount(rate,...prices){
        return prices.map(p => p*rate);
    }
    //计算0.68参数以外的其他价格,打完6.8折后的值。
    console.log(discount(0.68,125,98,246,50));//[85, 66.64, 167.28, 34]
    
    //有⼀条信息,内容为姓名、编号和数据
    const info = ["zhangsan","1002",12,23,45,34];
    //使⽤解构赋值
    const [name,id,...params] = info;
    console.log(name,id,params); //zhangsan 1002 [12, 23, 45, 34]
    

    实例:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>扩展运算符实例</title>
        <style>
            body{background-color: darkorange;}
            #hid{color:white;font-size:80px;text-align:center;}
            #hid span{
                margin:5px;
                cursor:pointer;
                display:inline-block;
                transsition:transform 0.25s;
            }
            /*定义⿏标移⼊的动画效果*/
            #hid span:hover{
                transform:translateY(-20px) rotate(10deg) scale(2);
            }
        </style>
    </head>
    <body>
    <h1 id="hid">Hello ES6!</h1>
    <script>
        //获取⻚⾯中h1元素节点
        const hid = document.querySelector("#hid");
        //获取节点中间的内容,作为参数调⽤下⾯函数,并将返回结果替换原先h1标签之间的内容中
        hid.innerHTML = wrapWithSpan(hid.textContent);
        //⾃定义函数
        function wrapWithSpan(str){
            //将内容使⽤map遍历,并未每个字符外添加span标签后合并返回。
            return [...str].map(v=>`<span>${v}</span>`).join("");
        }
    </script>
    </body>
    </html>
    

    7 数值的扩展

    ① 二进制和八进制表示法:

    ES6 提供了⼆进制和⼋进制数值的新的写法,分别⽤前缀0b(或0B)和0o(或0O)表示。

    0b111110111 === 503 // true
    0o767 === 503 // true
    

    ② Number.isFinite(), Number.isNaN()

    • ES6 在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个⽅法。
    • Number.isFinite()⽤来检查⼀个数值是否为有限的(finite),即不是Infinity。

      //注意,如果参数类型不是数值,Number.isFinite⼀律返回false。
      Number.isFinite(15); // true
      Number.isFinite(0.8); // true
      Number.isFinite(NaN); // false
      Number.isFinite(Infinity); // false
      Number.isFinite(-Infinity); // false
      Number.isFinite('foo'); // false
      Number.isFinite('15'); // false
      Number.isFinite(true); // false
      
    • Number.isNaN()⽤来检查⼀个值是否为NaN。

      //如果参数类型不是NaN,Number.isNaN⼀律返回false。
      Number.isNaN(NaN) // true
      Number.isNaN(15) // false
      Number.isNaN('15') // false
      Number.isNaN(true) // false
      Number.isNaN(9/NaN) // true
      Number.isNaN('true' / 0) // true
      Number.isNaN('true' / 'true') // true
      

      ③ Number.parseInt(), Number.parseFloat()

      ES6 将全局⽅法parseInt()和parseFloat(),移植到Number对象上⾯,⾏为完全保持不变。 ```javascript // ES5的写法 parseInt(‘12.34’) // 12 parseFloat(‘123.45#’) // 123.45

    // ES6的写法 Number.parseInt(‘12.34’) // 12 Number.parseFloat(‘123.45#’) // 123.45

    这样做的⽬的,是逐步减少全局性⽅法,使得语⾔逐步模块化。
    ```javascript
    Number.parseInt === parseInt // true
    Number.parseFloat === parseFloat // true
    

    ④ Number.isInteger()

    • Number.isInteger()⽤来判断⼀个数值是否为整数。

      Number.isInteger(25) // true
      Number.isInteger(25.1) // false
      

      ⑤ Math 对象的扩展:

      ES6 在 Math 对象上新增了 17 个与数学相关的⽅法。所有这些⽅法都是静态⽅法,只能在 Math对象上调⽤。
      具体详⻅⼿册

      8 函数的扩展

    • 函数参数的默认值

    • 箭头函数
    • 箭头函数中this的理解:
    • 箭头函数使⽤注意点

      ① 函数参数的默认值:

    • ES6之前,不能直接为函数的参数指定默认值,只能采⽤变通的⽅法。

    • ES6允许为函数的参数设置默认值,即直接写在参数定义的后⾯。
    • 参数默认值的位置:
      • 通常情况下,定义了默认值的参数,应该是函数的尾参数。
      • 因为这样⽐较容易看出来,到底省略了哪些参数。 ```javascript //ES6之前,不能直接为函数的参数指定默认值,只能采⽤变通的⽅法。 function func(name){ name = name || “World”; return “Hello “+name; } console.log(func()); //Hello World console.log(func(“ZhangSan”)); //Hello ZhangSan

    //ES6允许为函数的参数设置默认值,即直接写在参数定义的后⾯。 function add(x=0,y=0){ return x+y; } console.log(add()); //0 console.log(add(10)); //10 //跳过第⼀个参数给第⼆个参数y传值 console.log(add(undefined,20)); //20 console.log(add(10,20));//30

    <a name="i2HB3"></a>
    ## ② 箭头函数:
    ES6允许使⽤“箭头”(=>)定义函数。
    ```javascript
    var f = v => v;
    // 等同于
    var f = function (v) {
      return v;
    };
    

    如果箭头函数不需要参数或需要多个参数,就使⽤⼀个圆括号代表参数部分。

    //⽆参数
    var f = () => 5;
    // 等同于
    var f = function () { return 5 };
    //多个参数
    var sum = (num1, num2) => num1 + num2;
    // 等同于
    var sum = function(num1, num2) {
      return num1 + num2;
    };
    
    • 如果箭头函数的代码块部分多于⼀条语句,就要使⽤⼤括号将它们括起来,并且使⽤return语句返回。
    • 也就是说默认函数体没有⼤括号是⾃带隐式返回renturn的。

      var sum = (num1, num2) => { return num1 + num2; }
      
    • 由于⼤括号被解释为代码块,所以如果箭头函数直接返回⼀个对象,必须在对象外⾯加上括号,否则会报错。 ```javascript // 报错 let getTempItem = id => { id: id, name: “Temp” };

    // 不报错 let getTempItem = id => ({ id: id, name: “Temp” });

    <a name="71OMf"></a>
    ## ③ 箭头函数中this的理解:
    传统javaScript代码中this的使⽤:
    ```javascript
    //定义⼀个stu学⽣对象,内有:两个属性、⼀个⽅法。
    const stu = {
        name:"张三",
        likes:['吃饭','睡觉','敲代码'],
        printLikes:function(){
            //使⽤map遍历likes属性,并输出信息
            this.likes.map(function(like){
                //此处的this代表的是window对象,⽽⾮stu对象
                console.log(`${this.name} 喜欢 ${like}`);
            });
        }
    };
    stu.printLikes(); //使⽤stu对象调⽤⾃⼰的⽅法
    /* 输出结果:
     喜欢 吃饭
     喜欢 睡觉
     喜欢 敲代码
     */
    

    上⾯的输出this.name没有信息,如下进⾏修改就可以了。

    //定义⼀个stu学⽣对象,内有:两个属性、⼀个⽅法。
    const stu = {
        name:"张三",
        likes:['吃饭','睡觉','敲代码'],
        printLikes:function(){
            let self = this;
    //使⽤map遍历likes属性,并输出信息
            this.likes.map(function(like){
    //此处的this代表的是window对象,⽽⾮stu对象
                console.log(`${self.name} 喜欢 ${like}`);
            });
        }
    };
    stu.printLikes(); //使⽤stu对象调⽤⾃⼰的⽅法
    /* 输出结果:
     张三 喜欢 吃饭
     张三 喜欢 睡觉
     张三 喜欢 敲代码
     */
    

    使⽤箭头函数后的效果

    //定义⼀个stu学⽣对象,内有:两个属性、⼀个⽅法。
    const stu = {
        name:"张三",
        likes:['吃饭','睡觉','敲代码'],
        printLikes:function(){
    //使⽤map遍历likes属性,并输出信息
            this.likes.map(like => {
    //箭头函数中没有⾃⼰的this,故此处this是继承⽗作⽤域的。
    //⽽且是在定义的时候已指定,不会随着调⽤⽽改变。
                console.log(`${this.name} 喜欢 ${like}`);
            });
        }
    };
    stu.printLikes(); //使⽤stu对象调⽤⾃⼰的⽅法
    /* 输出结果:
     张三 喜欢 吃饭
     张三 喜欢 睡觉
     张三 喜欢 敲代码
     */
    

    ④ 箭头函数使用注意点

    箭头函数有⼏个使⽤注意点。
    (1)函数体内的this对象,就是定义时所在的对象,⽽不是使⽤时所在的对象。
    (2)不可以当作构造函数,也就是说,不可以使⽤new命令,否则会抛出⼀个错误。
    (3)当你真的需要this的时候,如为对象添加普通⽅法或事件绑定回调函数使⽤箭头函数,可能获取不到this。
    (4)不可以使⽤arguments对象,该对象在函数体内不存在。
    上⾯四点中,第⼀点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。

    //1.箭头函数不可以作为构造函数使⽤
    const Stu1 = (name,age)=>{
        this.name = name;
        this.age = age;
    }
    //要改成常规函数如下:
    const Stu2 = function(name,age){
        this.name = name;
        this.age = age;
    }
    //实例化
    //s = new Stu1("zhangsan",20);//报错:TypeError: Stu is not a constructor
    s = new Stu2("zhangsan",20); //正常
    console.log(s); //Stu2 {name: "zhangsan", age: 20}
    
    //定义Stu类
    const Stu = function(name,age){
        this.name = name;
        this.age = age;
    }
    s = new Stu("zhangsan",20); //正常实例化
    //为s原型对象添加⼀个getInfo⽅法,使⽤箭头函数,⾥⾯的this是window对象
    Stu.prototype.getInfo = ()=>{
        return `我叫${this.name},今年${this.age}岁`;
    }
    //改成常规函数
    tu.prototype.getInfo2 = function(){
        return `我叫${this.name},今年${this.age}岁`;
    }
    console.log(s.getInfo()); //我叫,今年undefined岁
    console.log(s.getInfo2()); //我叫zhangsan,今年20岁
    

    9 数组的扩展

    • 数组的遍历
    • for…of循环的使⽤实例
    • Array.from()
    • Array.of()
    • 数组实例的 find() 和 findIndex()
    • 数组实例的 some() 和 every()
    • 数组实例的 fill()

      ① 数组的遍历:

      //定义数组
      let a = ["aaa","bbb","ccc"];
      a.name = "zhangsan"; //添加⾮数字属性
      //1.使⽤for循环遍历数组(传统⽅式)
      for(let i=0;i<a.length;i++){
        console.log(a[i]);
      }
      //2.使⽤for...in遍历(特点是会遍历出其他⾮数字属性信息)
      for(let i in a){
        console.log(a[i])
      }
      //3.使⽤forEach遍历数组(不⽀持break和continue)
      a.forEach(function(v){
        console.log(v);
      });
      //使⽤箭头函数
      a.forEach(v =>{
        console.log(v);
      });
      //4.ES6我们提供了for...of循环
      //特点不会遍历⾮数字属性,⽀持break和continue的使⽤
      for(let v of a){
        console.log(v)
      }
      

      ② for…of循环的使用实例

    • for…of 语句创建⼀个循环来迭代可迭代的对象。

    • 在 ES6 中引⼊的 for…of 循环,以替代 for…in 和 forEach(),并⽀持新的迭代协议。
    • for…of 允许你遍历Arrays(数组),Strings(字符串),Maps(映射),Sets(集合)等可迭代的数据结构等。 ```javascript //数组对象entries()⽅法返回⼀个数组的迭代对象,该对象包含数组的键值对(key/value)。 //1.使⽤for…of遍历数组: let a = [‘zhangsan’,’lisi’,’wangwu’]; for(let [k,v] of a.entries()){ console.log(k,v); } //0 “zhangsan” //1 “lisi” //1 “lisi”

    //2.使⽤for…of遍历Maps(映射) const iterable1 = new Map([[‘one’,’zhangsan’],[‘two’,’lisi’], [‘three’,’wangwu’]]); for (const [key, value] of iterable1) { console.log(${key} -> ${value}); } //one -> zhangsan //two -> lisi //three -> wangwu //Set(集合) 对象允许你存储任何类型的唯⼀值,这些值可以是原始值或对象。

    //3.使⽤for…of遍历Set(集合) const iterable2 = new Set([10,30,20,10,30]); for (const value of iterable2) { console.log(value); } //10 //30 //20 //字符串⽤于以⽂本形式存储数据

    //4.使⽤for…of遍历字符串 const iterable3 = ‘Hello’; for (const value of iterable3) { console.log(value); } //H //e //l //l //o

    //5.使⽤for…of遍历arguments Object(参数对象) function demo(){ for(const arg of arguments) { console.log(arg); } } demo(‘a’,’b’,’c’); //a //b //c

    <a name="P080c"></a>
    ## ③ Array.from()
    
    - Array.from()⽅法就是将⼀个类数组对象或者可遍历对象转换成⼀个真正的数组。
    - 格式:Array.from(arrayLike[, mapFn[, thisArg]])
       - arrayLike:想要转换成数组的伪数组对象或可迭代对象
       - mapFn:如果指定了该参数,新数组中的每个元素会执⾏该回调函数;
       - thisArg:可选参数,执⾏回调函数 mapFn 时 this 对象。
    - 返回值:是⼀个新的数组实例(真正的数组)
    ```javascript
    let array = {
        0: 'name',
        1: 'age',
        2: 'sex',
        3: ['user1','user2','user3'],
        'length': 4
    };
    let arr = Array.from(array);
    console.log(arr); // ['name','age','sex',['user1','user2','user3']]
    

    Array.from()的案例

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Array.from()使⽤实例</title>
    </head>
    <body>
    <ul>
        <li>zhangsan</li>
        <li>lisi</li>
        <li>wangwu</li>
    </ul>
    <script type="text/javascript">
        //获取上⾯的li节点对象列表
        let nlists = document.querySelectorAll("li");
        console.log(nlists); //NodeList(3) [li, li, li]
        //将NodeList列表对象转成数组,并解析出每个元素之间的内容。
        const alist = Array.from(nlists,li => li.textContent);
        console.log(alist); // ["zhangsan", "lisi", "wangwu"]
    </script>
    </body>
    </html>
    

    ④ Array.of()

    Array.of()⽅法⽤于将⼀组值转化为数组,即新建数组,⽽不考虑参数的数量或类型。

    //使⽤Array.of()创建数组
    console.log(Array.of()); //[] 创建⼀个空数组
    console.log(Array.of(8)); //[8] 创建只有⼀个元素值为8的数组
    console.log(Array.of(1, 2, 3)); //[1,2,3] 创建⼀个值为1,2,3的数组
    
    //以前直接使⽤Array创建数组
    console.log(Array()); //[] 创建⼀个空数组
    console.log(Array(4)); // [ , , , ] 创建拥有4个元素空值的数组
    console.log(Array(1, 2, 3)); //[1,2,3] 创建⼀个值为1,2,3的数组
    

    ⑤ 数组实例的 find() 和 findIndex()

    • find()⽅法,⽤于找出第⼀个符合条件的数组成员。
      • 参数是⼀个回调函数,所有数组成员依次执⾏该回调函数。
      • 直到找出第⼀个返回值为true的成员,然后返回该成员。
      • 如果没有符合条件的成员,则返回undefined。
    • findIndex()⽅法的⽤法与find⽅法⾮常类似,返回第⼀个符合条件的数组成员的位置。
      • 返回成员位置,如果所有成员都不符合条件,则返回-1。 ```javascript const data =[ {name:’zhangsan’,age:22,sex:’man’}, {name:’lisi’,age:25,sex:’woman’}, {name:’wangwu’,age:23,sex:’man’}, ]; //使⽤find获取name属性值为lisi的信息 let s1 = data.find(function(v){ return v[‘name’]==’lisi’ }); //同上效果使⽤箭头函数 let s2 = data.find(v => v[‘name’]==’lisi’); console.log(s1); //{name: “lisi”, age: 25, sex: “woman”} console.log(s2); //{name: “lisi”, age: 25, sex: “woman”} //使⽤find获取name属性值为lisi的信息 let s3 = data.findIndex(function(v){ return v[‘name’]==’lisi’ });

    //同上效果使⽤箭头函数 let s4 = data.findIndex(v => v[‘name’]==’lisi’); console.log(s3); //1 console.log(s4); //1

    <a name="X3bsO"></a>
    ## ⑥ 数组实例的 some() 和 every()
    
    - every()和 some()⽬的:确定数组的所有成员是否满⾜指定的测试.
       - some()⽅法 只要其中⼀个为true 就会返回true。
       - every()⽅法必须所有都返回true才会返回true,哪怕有⼀个false,就会返回false。
    - 即:every:⼀假即假; some:⼀真即真。
    ```javascript
    const data =[
        {name:'zhangsan',age:22,sex:'man'},
        {name:'lisi',age:25,sex:'woman'},
        {name:'wangwu',age:23,sex:'man'},
    ];
    
    //使⽤some判断data中是否含有⼀条name以"wang"开头的
    let s1 = data.some(v => v['name'].startsWith("wang"));
    console.log(s1); //true
    
    //使⽤every判断data信息中是否都是age⼤于20的信息。
    let s2 = data.every(v => v['age']>20);
    console.log(s2); //true 若有⼀个不符合则返回false
    

    ⑦数组实例的 .fill()

    • fill()函数,使⽤指定的元素替换原数组内容,会改变原来的数组。
    • 语法结构:arr.fill(value[, start[, end]])
      • value:替换值。
      • start:替换起始位置(数组的下标),可以省略。
      • end:替换结束位置(数组的下标),如果省略不写就默认为数组结束。 ```javascript //空数组则没有替换 console.log([].fill(6)); //[]

    //将数组中的值都替换成指定值6 console.log([1,2,3].fill(6));//(3) [6, 6, 6]

    //从数组索引位置2开始替换成指定值6,替换到数组结束位置。 console.log([1,2,3,4,5].fill(6,2)); //(5) [1, 2, 6, 6, 6]

    //从数组索引位置2开始替换到索引位置4前结束。 console.log([1,2,3,4,5].fill(6,2,4)); //(5) [1, 2, 6, 6, 5]

    <a name="0V41Z"></a>
    # 10 Set和Map数据结构
    es6 提供了两种新的数据结构 Set 和 Map
    <a name="pOREp"></a>
    ## Set:
    
    - Set 是⼀个构造函数,⽤来⽣成 Set 数据结构,它类似于数组,但是成员的值都是唯⼀的,没有重复的值.
    - 初始化 Set 可以接受⼀个数组或类数组对象作为参数,也可以创建⼀个空的 Set
    ```javascript
    var s1 = new Set();
    var s2 = new Set([1, 2, 3]);
    console.log(s1);// Set(0) {}
    console.log(s2);// Set(3) {1, 2, 3}
    

    在 Set 中成员的值是唯⼀的,重复的值⾃动被过滤掉

    var s1 = new Set([1, 2, 2, 3, 1, 4]);
    console.log(s1);// Set(4) {1, 2, 3, 4}
    
    • Set 的⼀些属性⽅法:

      • size:返回成员总数
      • add(value):添加某个值,返回Set结构本身
      • delete(value):删除某个值,返回⼀个布尔值,表示删除是否成功
      • has(value):返回⼀个布尔值,表示该值是否为Set的成员
      • clear():清除所有成员,没有返回值
        var set = new Set([1,2]);
        set.add(3);// 添加成员
        console.log(set.size);// 3 成员总数
        console.log(set);// Set(3) {1, 2, 3}
        set.add([4,5]);// 添加成员
        console.log(set.size);// 4 成员总数
        console.log(set.has(2));// true 有该成员
        console.log(set);// Set(4) {1, 2, 3, [4, 5]}
        set.delete(2);// 删除成员
        console.log(set);// Set(3) {1, 3, [4, 5]}
        console.log(set.has(2));// false 没有该成员
        set.clear();// 清除所有成员
        console.log(set);// Set(0) {}
        
        得益于数据结构 Set 查找更快速⾼效,但也因为数据结构的内部数据是⽆序的,⽆法实现按下标改查,排序等操作
        var arr = [1,2,3,'a',4,'b'];
        var set = new Set(arr);
        console.log(set[0]);// undefined
        console.log(set['a']);// undefined
        

        Map:

    • Map 是⼀个构造函数,⽤来⽣成 Map 数据结构,它类似于对象,也是键值对的集合.

    • 但是“键”可以是⾮字符串, 初始化 Map 需要⼀个⼆维数组,或者直接初始化⼀个空的 Map

      var m1 = new Map();
      var m2 = new Map([['a', 123], ['b', 456], [3, 'abc']]);
      console.log(m1);// Map(0) {}
      console.log(m2);// Map(3) {"a" => 123, "b" => 456, 3 => "abc"}
      
    • Map 的⼀些操作⽅法:

      • set(key, value):设置键值对
      • get(key):获取键对应的值
      • has(key):是否存在某个键
      • delete(key):删除某个键值对,返回⼀个布尔值,表示删除是否成功
        var map = new Map([['a', 123], ['b', 456], [3, 'abc']]);
        map.set('c',789);
        console.log(map.get('c')); // 789
        console.log(map.has('b')); // true 此key存在
        map.delete(3); // true 成功删除key
        console.log(map); // Map(3) {"a" => 123, "b" => 456, "c" => 789}
        
    • 传统 Object 只能⽤字符串作键,Object 结构提供了“字符串 — 值”的对应

    • Map 结构提供了“键 — 值”的对应,是⼀种更完善的 Hash 结构实现
    • 如果你需要“键值对”的数据结构,Map ⽐ Object 更快速 更⾼效 更合适
    • 遍历Map和Set对象 ```javascript /遍历Map var map = new Map([[‘a’, 123], [‘b’, 456], [3, ‘abc’],[‘c’, 789]]); map.forEach((val,key,obj)=>{ console.log(‘val: ‘+val); console.log(‘key: ‘+key); console.log(obj); }); // 结果如下图

    //遍历Set var set = new Set([‘a’,’b’,’c’,’d’]); set.forEach((val,key,obj)=>{ console.log(‘val: ‘+val); console.log(‘key: ‘+key); console.log(obj); });// 结果如下图

    当然,还可以使⽤es6中的 for of 遍历
    ```javascript
    var map = new Map([['a', 123], ['b', 456], [3, 'abc'],['c', 789]]);
    for (const [key,val] of map) {
        console.log(key+' : '+val);
    }
    // a : 123
    // b : 456
    // 3 : abc
    // c : 789
    
    var set = new Set(['a','b','c','d']);
    for (const val of set) {
        console.log(val);// a b c d
    }
    

    11 对象的扩展

    属性的简洁表示法

    ES6 允许在⼤括号⾥⾯,直接写⼊变量和函数,作为对象的属性和⽅法。这样的书写更加简洁。

    const foo = 'bar';
    const baz = {foo};
    baz // {foo: "bar"}
    
    // 等同于
    const baz = {foo: foo};
    

    上⾯代码中,变量foo直接写在⼤括号⾥⾯。这时,属性名就是变量名, 属性值就是变量值。下⾯是另⼀个例⼦。

    function f(x, y) {
        return {x, y};
    }
    
    // 等同于
    function f(x, y) {
        return {x: x, y: y};
    }
    f(1, 2) // Object {x: 1, y: 2}
    

    除了属性简写,⽅法也可以简写。

    const o = {
      method() {
        return "Hello!";
      }
    };
    // 等同于
    const o = {
      method: function() {
        return "Hello!";
      }
    };
    

    下⾯是⼀个实际的例⼦。

    let birth = '2000/01/01';
    const Person = {
      name: '张三',
      //等同于birth: birth
      birth,
      // 等同于hello: function ()...
      hello() { console.log('我的名字是', this.name); }
    };
    //这种写法⽤于函数的返回值,将会⾮常⽅便。
    function getPoint() {
      const x = 1;
      const y = 10;
      return {x, y};
    }
    getPoint()
    // {x:1, y:10}
    

    CommonJS模块输出⼀组变量,就⾮常合适使⽤简洁写法。

    let ms = {};
    function getItem (key) {
        return key in ms ? ms[key] : null;
    }
    function setItem (key, value) {
        ms[key] = value;
    }
    function clear () {
        ms = {};
    }
    module.exports = { getItem, setItem, clear };
    // 等同于
    module.exports = {
        getItem: getItem,
        setItem: setItem,
        clear: clear
    };
    

    简洁写法在打印对象时也很有⽤

    let user = {
        name: 'test'
    };
    
    let foo = {
        bar: 'baz'
    };
    
    console.log(user, foo)
    // {name: "test"} {bar: "baz"}
    console.log({user, foo})
    // {user: {name: "test"}, foo: {bar: "baz"}}
    

    上⾯代码中,console.log直接输出user和foo两个对象时,就是两组键值对,可能会混淆。
    把它们放在⼤括号⾥⾯输出,就变成了对象的简洁表示法,每组键值对前⾯会打印对象名,这样就⽐较清晰了。

    12 对象的新增方法

    ① Object.is()

    • ES5 ⽐较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。
    • 它们都有缺点,前者会⾃动转换数据类型,后者的NaN不等于⾃身,以及+0等于-0。
    • JavaScript 缺乏⼀种运算,在所有环境中,只要两个值是⼀样的,它们就应该相等。
    • ES6 提出“Same-value equality”(同值相等)算法,⽤来解决这个问题。
    • Object.is就是部署这个算法的新⽅法。它⽤来⽐较两个值是否严格相等,与严格⽐较运算符(===)的⾏为基本⼀致。 ```javascript Object.is(‘foo’, ‘foo’) // true Object.is({}, {}) //false

    +0 === -0 //true NaN === NaN // false

    Object.is(+0, -0) // false Object.is(NaN, NaN) // true

    <a name="nC0Tq"></a>
    ## ② Object.assign()
    Object.assign⽅法⽤于对象的合并,将源对象(source)的所有可枚举属性,复制到⽬标对象(target)。
    ```javascript
    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.assign⽅法的第⼀个参数是⽬标对象,后⾯的参数都是源对象。
    • 注意,如果⽬标对象与源对象有同名属性,或多个源对象有同名属性,则后⾯的属性会覆盖前⾯的属性。 ```javascript const target = { a: 1, b: 1 };

    const source1 = { b: 2, c: 2 }; const source2 = { c: 3 };

    Object.assign(target, source1, source2); target // {a:1, b:2, c:3}

    <a name="Jnwx3"></a>
    ## ③ Object.getOwnPropertyDescriptors()
    
    - ES5 的Object.getOwnPropertyDescriptor()⽅法会返回某个对象属性的描述对象(descriptor)。
    - ES2017 引⼊了Object.getOwnPropertyDescriptors()⽅法,返回指定对象所有⾃身属性(⾮继承属性)的描述对象。
    ```javascript
    const obj = {
    foo: 123,
        get bar() { return 'abc' }
    };
    Object.getOwnPropertyDescriptors(obj)
    // { foo:
    // { value: 123,
    // writable: true,
    // enumerable: true,
    // configurable: true },
    // bar:
    // { get: [Function: get bar],
    // set: undefined,
    // enumerable: true,
    // configurable: true } }
    

    上⾯代码中,Object.getOwnPropertyDescriptors()⽅法返回⼀个对象,所有原对象的属性名都是该对象的属性名, 对应的属性值就是该属性的描述对象。

    proto属性,Object.setPrototypeOf(),Object.getPrototypeOf()

    JavaScript 语⾔的对象继承是通过原型链实现的。ES6 提供了更多原型对象的操作⽅法。
    具体详⻅⽂档

    ⑤ Object.keys(),Object.values(),Object.entries()

    ES5 引⼊了Object.keys⽅法,返回⼀个数组,
    成员是参数对象⾃身的(不含继承的)所有可遍历(enumerable)属性的键名。

    var obj = { foo: 'bar', baz: 42 };
    Object.keys(obj)
    // ["foo", "baz"]
    

    ES2017引⼊了跟Object.keys配套的Object.values和Object.entries,
    作为遍历⼀个对象的补充⼿段,供for…of循环使⽤。

    let {keys, values, entries} = Object;
    let obj = { a: 1, b: 2, c: 3 };
    
    for (let key of keys(obj)) {
        console.log(key); // 'a', 'b', 'c'
    }
    for (let value of values(obj)) {
        console.log(value); // 1, 2, 3
    }
    for (let [key, value] of entries(obj)) {
        console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
    }
    

    13 Class 的基本语法

    • ES6 提供了更接近传统语⾔的写法,引⼊了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
    • 基本上,ES6 的class可以看作只是⼀个语法糖,它的绝⼤部分功能,ES5 都可以做到,
    • 新的class写法只是让对象原型的写法更加清晰、更像⾯向对象编程的语法⽽已。

      class Point {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
        toString() {
            return '(' + this.x + ', ' + this.y + ')';
        }
      }
      

      constructor⽅法是类的默认⽅法,通过new命令⽣成对象实例时,⾃动调⽤该⽅法。

      //定义类
      class Point {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
        toString() {
            return '(' + this.x + ', ' + this.y + ')';
        }
      }
      var point = new Point(2, 3);
      point.toString() // (2, 3)
      

      Class 表达式
      与函数⼀样,类也可以使⽤表达式的形式定义。

      const MyClass = class Me {
      getClassName() {
          return Me.name;
      }
      };
      

      上⾯代码使用表达式定义了⼀个类。需要注意的是,这个类的名字是Me,
      但是Me只在 Class 的内部可用,指代当前类。在 Class 外部,这个类只能⽤MyClass引用。

      let inst = new MyClass();
      inst.getClassName() // Me
      Me.name // ReferenceError: Me is not defined
      
    • 静态⽅法

    • 类相当于实例的原型,所有在类中定义的⽅法,都会被实例继承。
    • 如果在⼀个⽅法前,加上static关键字,就表示该⽅法不会被实例继承,⽽是直接通过类来调⽤,这就称为“静态⽅法”。 ```javascript class Foo { static classMethod() {
      return 'hello';
      
      } }

    Foo.classMethod() // ‘hello’

    var foo = new Foo(); foo.classMethod() // TypeError: foo.classMethod is not a function

    <a name="LHMbO"></a>
    # 14 Class 的继承
    Class 可以通过extends关键字实现继承,这⽐ ES5 的通过修改原型链实现继承,要清晰和⽅便很多。
    ```javascript
    class Point {
    }
    
    class ColorPoint extends Point {
    }
    
    • 上⾯代码定义了⼀个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和⽅法。
    • 但是由于没有部署任何代码,所以这两个类完全⼀样,等于复制了⼀个Point类。
    • 下⾯,我们在ColorPoint内部加上代码。

      class ColorPoint extends Point {
        constructor(x, y, color) {
            super(x, y); // 调⽤⽗类的constructor(x, y)
            this.color = color;
        }
        toString() {
            return this.color + ' ' + super.toString(); // 调⽤⽗类的toString()
        }
      }
      
    • 上⾯代码中,constructor⽅法和toString⽅法之中,都出现了super关键字, 它在这⾥表示⽗类的构造函数,⽤来新建⽗类的this对象。

    • super 关键字
    • super这个关键字,既可以当作函数使⽤,也可以当作对象使⽤。在这两种情况下,它的⽤法完全不同。
    • 第⼀种情况,super作为函数调⽤时,代表⽗类的构造函数。
    • ES6 要求,⼦类的构造函数必须执⾏⼀次super函数。
      class A {}
      class B extends A {
      constructor() {
          super();
      }
      }