面试官:来说一下ES6新特性? 15383字,最全ES6-ES12总结

lxf2023-05-23 01:13:02

先来张Vue的风暴图,感受一下抽象的席卷~

面试官:来说一下ES6新特性? 15383字,最全ES6-ES12总结

ES(6-12)全版本语法

ES6

es6是js的规格,js是es6的实现

1. 新的声明方式:let

变量

  • 不属于顶层对象window
  • 不允许重复声明
  • 不存在变量提升
  • 暂时性死区
  • 块级作用域

1. 不属于顶层对象window

//声明的是变量,具有作用域
var a = 5
console.log(windeow.a) //可以输出
//没有var,是一个对象
b = 6
console.log(windeow.b) //可以输出

let的出现是为了弥补var将变量挂在window上的缺陷

static文件夹下的文件是原封不动地上传到浏览器

而src文件夹下的文件会经过webpack打包,会规避一些问题

2. 不允许重复声明

var可以多次重复声明(最后一次声明会覆盖前面的声明),而let不能(会报错

可以避免重复命名

3. 不存在变量提升

console.log(a)
var a = 5

//相当于
var a
console.log(a)
a = 5

而let不存在变量提升

4. 暂时性死区

var a = 5
if(true){
    a = 6
    let a
}
//会报错,a没有进行声明,在if{}里是一个暂时性死区

5. 块级作用域

for(var i=0;i<3;i++){
    console.log(i)  //输出0 1 2
}
console.log(i)  //只会输出3,因为var不存在块级作用域,i=3时不满足条件则结束循环,跳出循环之后被外部的console.log输出
//将var改成let,则外部的console报错

块级作用域使得代码更加安全

  • 允许在块级作用域内声明函数
  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部
  • 同时,函数声明还会提升到所在的块级作用域的头部

参考:zhuanlan.zhihu.com/p/100856823

2. 新的声明方式:const

常量,不能被重新赋值

const a
a = 5 //报错,应const a = 5
面试官:来说一下ES6新特性? 15383字,最全ES6-ES12总结

对于引用类型,const不能改变其引用地址,但是可以改变堆内存中的值

const obj = {
    name:'yl',
    age:11
}
//这里添加一行 Object.freeze(obj),后面的就无法改变(但只能冻结第一层,如果多层嵌套需要obj.name)
obj.sex = 'G'  //obj中会添加这一值(堆内存可以改变,栈不能改变)
  • 不属于顶层对象window
  • 不允许重复声明
  • 不存在变量提升
  • 暂时性死区
  • 块级作用域

区别:

varletconst
函数级作用域块级作用域块级作用域
变量提升不存在变量提升不存在变量提升
值可更改值可更改值不可更改

let VS const

默认情况下优先使用const,如果需要被改变再考虑let

let 变量 const 常量

3. ==解构赋值(常用)==

  • 按照一定模式,从数组和对象中提取值,对变量进行赋值
  • 数组解构
  • 对象解构
  • 字符串解构
  • 应用

默认参数的使用(当没有传这个值的时候,默认赋该值)

等号左右两边的结构一样即可

const [a,b,c,d = 5] = [1,2,[3,4]] //输出[1,2,[3,4],5]
const [a,b,c,d = 5] = [1,2,[3,4],6] //输出[1,2,[3,4],6]
//即如果右边有值则为右边的值,否则输出左边赋的默认值;如果右边没有值,左边也没有默认值,则underfined
  • 数组通过索引进行配对(按顺序解构)

  • 对象通过键名进行配对(变量必须和属性同名)

  • 解构也适用于嵌套结构的对象(要使用一样的结构)

let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined //不同名,取不到

嵌套赋值:

let obj = {};
let arr = [];

({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });

obj // {prop:123}
arr // [true]
  • 字符串的解构和数组相似
function foo([a,b,c]) {
    console.log(a,b,c)
}
let arr = [1,2,3]
foo(arr)

对于json

let json = '{"a":"hello","b":"world"}'
let {a,b} = JSON.parse(json)  //将json格式输出成对象,再进行解构赋值

使用了别名之后,真正被赋值的是后者

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined

注意:

// 错误的写法
let x;
{x} = {x: 1};
// 正确的写法
let x;
({x} = {x: 1});  //将解构赋值语句放在一个原括号里
  • 数值和布尔值的解构赋值:

    会先转换为对象

    解构赋值的规则:

    只要等号右边的值不是对象或数组,就先转换为对象

    undefinednull 无法转为对象,故无法进行解构赋值

  • 函数的参数也可以使用解构赋值

用途:

  1. 交换变量的值

    let x = 1;
    let y = 2;
    
    [x, y] = [y, x];
    
  2. 从函数返回多个值

    // 返回一个数组
    function example() {
      return [1, 2, 3];
    }
    let [a, b, c] = example();
    
    // 返回一个对象
    function example() {
      return {
        foo: 1,
        bar: 2
      };
    }
    let { foo, bar } = example();
    
  3. 函数参数的定义

    // 参数是一组有次序的值
    function f([x, y, z]) { ... }
    f([1, 2, 3]);
    
    // 参数是一组无次序的值
    function f({x, y, z}) { ... }
    f({z: 3, y: 2, x: 1});
    
  4. 提取JSON数据

  5. 函数参数的默认值

  6. 遍历Map结构

  7. 输入模块的指定方法

    const { SourceMapConsumer, SourceNode } = require("source-map");
    

4. 数组的各种遍历方式

ES5中的数组遍历方式

  • for循环
  • forEach():没有返回值,只是针对每个元素调用func
  • map():返回新的Array,每个元素为调用func的结果
  • filter():返回符合func条件的元素数组
  • some():返回布尔,判断是否有元素符合func条件
  • every():返回布尔,判断每个元素是否符合func条件
  • reduce():接收一个函数作为累加器
  • for in ???
arr = [1,2,3]

//for 
for(let i = 0; i<arr.length;i++) {
    //....
}

//forEach(不支持break continue)
arr.forEach(function(elem,index,array){
    //.....
})

//map
let result = arr.map(function(value){
    value += 1
    return value
})
console.log(arr,result)  //map循环之后会生成新的数组,不会去更改之前的arr

//filter(过滤)
let result = arr.filter(function(value){
    return value == 2
})
console.log(arr,result)  //会生成一个新的数组,这个新的数组只会保存满足条件的值

//some
let result = arr.some(function(value){
    return value == 4
})
console.log(arr,result)  //返回的是一个布尔值,因为arr中没有4,所以返回false(只要找到一个满足条件的值就会返回true)

//every
let result = arr.every(function(value){
    return value == 2
})
console.log(arr,result)  //所有元素都满足条件时才会返回true

//reduce
//0初始值 prev上一个处理的元素 cur当前处理的元素 index当前处理元素的索引 array原数组
let sum = arr.reduce(function(prev,cur,index,array){
    return prev + cur
},0) //得到的就是求和的结果
//reduce可以实现求max min 去重等
//去重
let res = arr.reduce(function(prev,cur){
    prev.indexOf(cur) == -1 && prev.push(cur)
    return prev
},[])

//for in xx
//这种方法遍历数组会将arr上的所有东西遍历出来(包括原型上的方法)
for(let index in arr){
    //....
}

ES6中数组遍历方法

  • find():返回第一个通过测试的元素
  • findIndex():返回的值为该通过第一个元素的索引
  • for of
  • values()
  • keys()
  • entries()
arr = [1,2,3,2,4]

//find
let res = arr.find(function(value){
    return value == 2
})
console.log(arr,res) //res返回的2为arr的第一个2

//findIndex
let res = arr.findIndex(function(value){
    return value == 2
})
console.log(arr,res) //res返回的是为arr的第一个2的索引

//for of
for(let item of arr){
    console.log(item)
}
//for(let item of arr.values()){} 和上面的效果一样
//arr.values() 为内容
//arr.keys() 为索引
//arr.entries() 为两者都输出
for(let [index,item] of arr.entries()){
    console.log(index,item)
}

5. 数组的扩展

  • 类数组/伪数组

    有长度,但不能使用数组的方法

  • Array.from()

  • Array.of()

  • copyWithin()

  • fill()

  • includes()

es5中,可以通过slice方法将伪数组转换成数组

let arr = Array.prototype.slice.call(divs3)
arr.push(123) //此时已经转换成了真正的数组,使用数组方法不会报错

es6中:

//Array.from() 将其转换为数组
Array.from(arrayLike)

//Array.of()
let arr = Array.of(1,2)
let arr = Array.of(3)
//let arr = new Array(3) 这个返回的是3个空白,并不是数组[3]。这种方法会随着传入的参数个数不同而得到不同的数组

//copyWithin()替换元素
let arr = [1,2,3,4,5]
console.log(arr.copyWithin(1,3))  //从第一个位置开始读取,再读取下标为3的数组,(因为没有第三个参数,所有默认到结尾),于是就用4,5来替换2,3

//fill()填充
//1.
let arr = new Array(3).fill(7) //数组长度为3,用7进行填充,于是得到[7,7,7]
//2.
let arr = [1,2,3,4,5]
arr.fill('yl',1,3) //从下标为1开始替换,直到下标为3(不包括) 得到[1,'yl','yl',4,5]
arr.fill(0)  //全部被替换成0

//includes()是否包含

NAN == NAN 不相等

6. 函数的参数

  • 参数的默认值
  • 与解构赋值结合
  • length属性
  • 作用域
  • 函数的name属性

1. 参数的默认值

//es5
function foo(x,y){
    y = y || 'world'  //判断参数是否存在,但存在问题
    console.log(x,y)
}
foo('hello',0) //如果不传y值,则打印'world';而0由于是false,所以打印出来的是world

//es6
function foo(x, y = 'world'){
    console.log(x,y)
}
foo('hello',0)  //此时打印出来的是hello,0

//函数内部的参数已经默认声明过了,使用const或let再次声明会报错
//函数内部的参数不能重名 eg.foo(x,x,y)报错
function foo(x = 5){
    //这里不能再声明x
}
foo()

//参数的默认值放最后面
function foo(x,z,y=5){}

2. 与解构赋值结合

function foo({x,y = 5}){
    console.log(x,y)
}
foo({})  //打印出 underfined 5(x没有赋值),这里符合解构赋值
//foo() 报错,结构要一样才可以

与默认值一同使用

function ajax(url,{
    body = '',
    method = 'GET',
    headers = {}
} = {}){  //如果不传入第二个参数,则默认值为空值
    console.log(method)
}
ajax('http://ww.imooc.com',{
    method: 'POST'
})  //POST

3. length属性

返回没有指定默认值的个数

4. 作用域

let x = 1
function foo(x,y=x){   //()中形成了一个作用域,故y取到的值为这个作用域里面的x值
    console.log(y)  //2
}
foo(2)   
let x = 1
function foo(y=x){ 
    let x = 2
    console.log(y)  //1
}
foo()  
//没有传入参数,此时y会沿着作用域链**往外**找,找到全局变量中有一个x的值,然后赋值得到
//如果没有声明全局变量,则返回的是underfined

5. 函数的name属性

(new Function).name //输出anonymous

7. 拓展运算符 与 rest参数

  • ...

  • 扩展运算符:把数组或者类数组展开成用逗号隔开的值

  • rest参数:把逗号隔开的值组合成一个数组

    互逆操作

    如果...放在等号左边或是形参上,则rest参数

    如果...放在等号右边或是实参上,则扩展运算符

function foo(a,b,c){
    console.log(a,b,c)
}
let arr = [1,2,3]
foo(..arr)
//如果使用foo(arr)需要使用解构赋值,而使用拓展运算符则会将arr变成1,2,3
//合并数组
let arr1 = [1,2,3]
let arr2 = [4,5,6]

//es5
Array.prototype.push.apply(arr2,arr2)  //在原型上进行push apply

//es6
arr1.push(...arr2)  //...可以打散arr2,再通过push加上去
//打散字符串
let str = 'hello'
var arr = [...str]  //得到["h","e","l","l","o"]

作用域

//es5
function foo(x,y,z) {
    let sum = 0
    Array.prototype.forEach.call(arguments,function(item){  //arguments返回的是伪数组
        sum += item
    })
    return sum
}
console.log(foo(1,2))  //3
console.log(foo(1,2,3))  //6

//使用es6中Array.from转换数组
//Array.from(arguments).forEach(function(item){})

//使用reset参数(对于不确定参数) 参数要放在最后
function foo(...args) {
    console.log(args)
    let sum = 0
    args.forEach(function(item){
        sum += item
    })
    return sum
}

//reset提取剩余的参数
function foo(x,...args) {
    console.log(x)  //1
    console.log(args)  //[2,3,4,5]
}
foo(1,2,3,4,5)
//同样适用于解构赋值中
let [x,...y] = [1,2,3]
console.log(x)  //1
console.log(y)  //[2,3]

8. 箭头函数

  • this指向定义时所在的对象,而不是调用时所在的对象

    箭头函数里没有this,会往外一层去找this

  • 不可以当作构造函数

  • 不可以使用arguments对象

箭头函数的写法:箭头左边是参数,右边是方法体

let sum = (x,y) => {

​ return x + y

}

//可以简写成 let sum = (x,y) => x + y (方法体只有一行代码)

//es5中构造函数
function People(name,age){
    console.log(this)
    this.name = name
    this.age = age
}
let p1 = new People('yl',11)
let foo = (..args) => {
    //console.log(arguments) 浏览器会报错
    //可以使用reset参数进行输出
    console.log(args)
}
foo(1,2,3)

9. 对象的扩展

  • 属性简洁表示法

  • 属性名表达式

  • Object.is() 即===

    面试官:来说一下ES6新特性? 15383字,最全ES6-ES12总结

  • 拓展运算符 与 Object.assign()

  • in

  • 对象的遍历方式

1. 属性简洁表示法 属性名表达式

let name = 'yl'
let age = 11
let s = 'school'
let obj = {
    name,
    age,
    [s]:'gdut'  //如果想要使用变量,则加上[]
    study(){  //es6为对象提供了一种简写的方式,如果使用箭头函数会报错,this指代的是window
        console.log(this.name + 'studying')
    }
}

2. Object.is()

obj1 == obj2 //false

obj存储的是一个引用地址,每一个obj都会进行一次new Object(),在堆内存中进行存储,所以哪怕两个对象内容一模一样,在堆内存中的位置也是不一样的,故返回false

同样 Object.is(obj1 == obj2) //false

let obj1 = obj2

Object.is(obj1 == obj2) //true

3. 拓展运算符 与 Object.assign()

let x = {
    a: 3 
    b: 4
}
let y = {..x}
console.log(y)  //{a:3,b:4}

//Object.assign()
let x = {
    a: 3,  //后面的值会覆盖前面的,所以a:3
    b: 4
}
let y = {
    c:5,
    a:6 
}
Object.assign(y,x)
console.log(y)  //{a:6,b:4,c:5}

4. in

判断对象中是否存在

如果是数组:

console.log(3 in arr) //下标为3是否存在

5. 对象的遍历方式

//1
for (let key in obj){
    console.log(key,obj[key])
}

//2
Object.keys(obj).forEach(key => {
    console.log(key,obj[key])
})

//3
Object.getOwnPropertyNames(obj).forEach(key =>{
    console.log(key,obj[key])
})

//4
Reflect.ownKeys(obj).forEach(key => {
    console.log(key,obj[key])
})

10. 深拷贝与浅拷贝

一篇博客

1. 浅拷贝

let Foo = {
    a: 3,
    b: 4
}
let newFoo = Foo
newFoo.a = 5
//使用object.assign()
let Foo = {
    a: 3,
    b: 4
}
// let newFoo = Foo
Object.assign(newFoo, Foo)
newFoo.a = 5

改变内容,都会改变(因为改变的是引用地址)

2. 深拷贝

  • JSON方式

    JSON.parse() 将JSON字符串转换成JavaScript对象

    JSON.stringify() 将JavaScript对象转换成JSON字符串

    let Foo = {
        a: {
          c:1
        },
        b: 4
    }
    let str = JSON.stringify(Foo)
    let newFoo = JSON.parse(str)
    newFoo.a.c = 5
    
  • 递归

    let checkType = data => {
        return Object.prototype.toString.call(data).slice(8, -1)
    }
    
    let deepClone = target => {
        let targetType = checkType(target)
        let result
        // 初始化操作
        if (targetType === 'Object') {
            result = {}
        } else if (targetType === 'Array') {
            result = []
        } else {
            // 都不是的话证明是基本数据类型,基本数据
            // 类型只会有一个值,所以直接返回这个值就可以了
            return target
        }
        // target不是基本类型,进入遍历
        for (let i in target) {
            let value = target[i]
            let valueType = checkType(value)
            if (valueType === 'Object' || valueType === 'Array') {
                result[i] = deepClone(value) // 递归
            } else {
                // 是基本类型直接赋值
                result[i] = value
            }
        }
        return result
    }
    

不过深拷贝还是建议看Condrail的,好像是叫这个,编程搜深拷贝就行

11. 面向过程与面向对象

面向过程:强调实现需求的步骤

面向对象:对象的属性、方法

面试官:来说一下ES6新特性? 15383字,最全ES6-ES12总结

JavaScript是一种基于对象的语言

类是对象的模板,定义了同一组对象共有的属性和方法

12. ES5中的类与继承

组合式继承

function Animal(name) {
    this.name = name;
}
Animal.prototype.showName = function () {
    console.log('名字为' + this.name);
}

//子类
function Dog(name,color) {
    Animal.call(this,name);  //继承父类的属性,**但不继承父类的方法**
    this.color = color;
}
Dog.prototype = new Animal();  //组合继承,既能继承属性又能继承方法
Dog.prototype.constuctor = Dog;
lett d = new Dog('wangcai','white');
console.log(d1);

继承的其他东西可以看我另一个文章介绍了6种继承的方法

13. ES6中的类与继承

1. class是语法糖

class People {
    constructor(name,age) {
        this.name = name;
        this.age = age;
    }
    showName(){
        console.log(this.name);
    }
}
let p1 = new People('yl',11);
console.log(p1);

2. 继承 extends

class Coder extends People {
    constructor(name,age,company){
        super(name,age);
        this.company = company;
    }
    showCompany(){
        console.log(this.company);
    }
}

3. Setters&Getters

class Animal {
    constructor(type, age) {
        this.type = type;
        this._age = age;
    }
    get age() {  //只读
        return this._age;
    }
    set age(val) {  //可写
        this._age = val;
    }
}

使用这种方式可以在里面写语句

eg.

set age(val) { if (val > 0 && val < 10) { #age = val } }

4. 静态方法

使用static来标记

class Animal {
    constructor(type) {
        this.type = type
    }
    walk() {
        console.log( `I am walking` )
    }
    static eat() {
        console.log( `I am eating` )
    }
}
  1. 类中的构造器不是必须写的,要写实例进行一些初始化的操作,如添加指定属性时才写
  2. 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的
  3. 类中所定义的方法,都是放在了类的原型对象上,供实例去使用
//传统方法
function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);

//class方法
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  toString() {    //方法必须使用该语法,方法名(){}
    return '(' + this.x + ', ' + this.y + ')';
  }
}
  1. Object.assign()一次向类添加多个方法
class Point {
  constructor(){
    // ...
  }
}

Object.assign(Point.prototype, {
  toString(){},
  toValue(){}
});
  1. 类必须使用new调用

es5里,实例的属性是函数原型的属性

在es6中,static声明静态属性,属性属于类不属于实例

function Phone(){
}
Phone.name = '手机';   //name属性属于函数对象的,不属于实例对象,称为静态属性
Phone.prototype.size = '5.5inch';  //原型
let nokia = new Phone();   //实例化
console.log(nokia.name);   //报错
console.log(nokia.size);   //输出 5.5inch
//构造方法
constructor(brand, price){
    this.brand = brand;
    this.price = price;
}
//父类的成员属性
call(){
    console.log("我可以打电话!!");
}
}
class SmartPhone extends Phone {   //用extends来继承
//构造方法
constructor(brand, price, color, size){
    super(brand, price);// Phone.call(this, brand, price)  关键字super
    this.color = color;
    this.size = size;
}
photo(){
    console.log("拍照");
}
playGame(){
    console.log("玩游戏");
}
//call(){
//    console.log('我可以进行视频通话');   //子类对父类方法的重写
}
}
const xiaomi = new SmartPhone('小米',799,'黑色','4.7inch');
  1. 取值get 存值set

14. 新的原始数据类型Symbol

let s = new Symbol() 错误,不能使用new

Symbol不是对象,不能添加属性(是一种类似于字符串的数据类型)

1. 独一无二

这个可以保证相同key值的也保存下来(比如重名学生)

let s1 = Symbol();
console.log(s1);  //Symbol()
let s2 = Symbol();
console.log(s1 === s2);  //false

2. 自动调用toString()函数

const obj = {
    name: 'yl',
    toString(){
        return this.name
    }
}
let s = Symbol(obj);
console.log(s);  //Symbol(yl)

3. Symbol.for()

在全局中注册的

不会每次调用都返回一个新的 Symbol 类型的值,而是先检查给定的key是否已经存在,不存在才新建

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
console.log(s1 === s2);//true

4. Symbol.keyFor()

返回一个已经登记的Symbol类型值的key

const s1 = Symbol('foo')
console.log(Symbol.keyFor(s1)) // undefined

const s2 = Symbol.for('foo')
console.log(Symbol.keyFor(s2)) // foo

5. 属性遍历

const sym = Symbol('imooc')
class User {
    constructor(name) {
        this.name = name
        this[sym] = 'imooc.com'
    }
    getName() {
        return this.name + this[sym]
    }
}
const user = new User('xiecheng')
console.log(user.getName())

for (let key in user) {   //不能遍历symbol类型的值
    console.log(key)
}

for (let key of Object.keys(user)) {  //不能遍历symbol类型的值
    console.log(key)
}

for (let key of Object.getOwnPropertySymbols(user)) {  //只能遍历symbol类型的值
    console.log(key)
}

for (let key of Reflect.ownKeys(user)) {  //全都能遍历
    console.log(key)
}

可以很好地保护symbol值

6. 消除魔术字符串

function getArea(shape) {
    let area = 0
    switch (shape) {
        case 'Triangle'://魔术字符串
            area = 1
            break
        case 'Circle':
            area = 2
            break
    }
    return area
}
console.log(getArea('Triangle'))
const shapeType = {
    triangle: Symbol(),//使用symbol赋一个独一无二的值
    circle: Symbol()
}

function getArea(shape) {
    let area = 0
    switch (shape) {
        case shapeType.triangle:
            area = 1
            break
        case shapeType.circle:
            area = 2
            break
    }
    return area
}
console.log(getArea(shapeType.triangle))

15. 新的数据结构Set

数据结构 Se类似于数组,但是成员的值都是唯一的,没有重复的值

1. 基本语法

生成 Set 实例

  let s = new Set()
  let s = new Set([1, 2, 3, 4])

添加数据

  s.add('hello')
  s.add('goodbye')
  s.add('hello').add('goodbye')  //写在一起

添加重复的数据是无效的

删除数据

s.delete('hello')  // 删除指定数据
s.clear()  // 删除全部数据

统计数据

  // 判断是否包含数据项,返回 true 或 false
  s.has('hello') // true
  // 计算数据项总数
  s.size // 2

数组去重

let arr = [1, 2, 3, 4, 2, 3]
let s = new Set(arr)

合并去重

let arr1 = [1, 2, 3, 4]
let arr2 = [2, 3, 4, 5, 6]
let s = new Set([...arr1, ...arr2])
console.log(s)
console.log([...s])
console.log(Array.from(s))

交集

let s1 = new Set(arr1)
let s2 = new Set(arr2)
let result = new Set(arr1.filter(item => s2.has(item)))
console.log(Array.from(result))

差集

let arr3 = new Set(arr1.filter(item => !s2.has(item)))
let arr4 = new Set(arr2.filter(item => !s1.has(item)))
console.log(arr3)
console.log(arr4)
console.log([...arr3, ...arr4])

2. 遍历方式

  console.log(s.keys()) // SetIterator {"hello", "goodbye"}
  console.log(s.values()) // SetIterator {"hello", "goodbye"}
  console.log(s.entries()) // SetIterator {"hello" => "hello", "goodbye" => "goodbye"}
  s.forEach(item => {
      console.log(item) // hello // goodbye
  })

  for (let item of s) {
      console.log(item)
  }

  for (let item of s.keys()) {
      console.log(item)
  }

  for (let item of s.values()) {
      console.log(item)
  }

  for (let item of s.entries()) {
      console.log(item[0], item[1])  //key值和value值都是一样的
  }

3. WeakSet

区别:

成员只能是对象,而不能是其他类型的值

没有size属性,不能遍历

弱引用

所谓垃圾回收机制:

如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在

const ws = new WeakSet()
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set
let ws = new WeakSet()
const obj1 = {
    name: 'imooc'
}
const obj2 = {
    age: 5
}
console.log(ws)
console.log(ws.has(obj2))

16. 新的数据类型Map

类似于对象,键值对的集合

“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键

也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应

是一种更完善的 Hash 结构实现

如果你需要“键值对”的数据结构,Map 比 Object 更合适。

1. 基本语法

实例化

  let map = new Map();

  let map = new Map([
      ['name','yl'],
      ['age',5]
  ])
  console.log(map);  //Map(2) {"name" => 'yl',"age" => 5}

添加数据

let obj = {
    name: 'yl'
}
map.set(obj,'66');

删除数据

map.delete(keyObj);    // 删除指定的数据
map.clear();           // 删除所有数据

统计数据

  console.log(map.size) //2
  console.log(map.has(keyObj)) //判断是否有 key-value

查询数据

  console.log(map.get(keyObj)) // 和键'keyObj'关联的值

2. 遍历方式

   map.forEach((value, key) => console.log(value, key))  //value, key

   for (let [key, value] of map) {  //key, value
       console.log(key, value)
   }

   for (let key of map.keys()) {
       console.log(key)
   }

   for (let value of map.values()) {
       console.log(value)
   }

   for (let [key, value] of map.entries()) {
       console.log(key, value)
   }

Map VS Object:

  • 键的类型

    Object的键: 字符串或者 Symbols

    Map 的键: 任意值

  • 键的顺序

    Object的键:无序

    Map的键值:有序

    进行遍历时,Map 对象是按插入的顺序返回键值。

  • 键值对的统计

    Object的个数:只能手算

    Map的个数:用size

  • 键值对的遍历

    Object:先获取键数组,再进行迭代

    Map:可直接进行迭代

  • 性能

    在涉及频繁增删键值对的场景下,Map 会有些性能优势

3. WeekMap

// WeakMap 可以使用 set 方法添加成员
const wm1 = new WeakMap()
const key = {
    foo: 1
}
wm1.set(key, 2)
wm1.get(key) // 2

// WeakMap 也可以接受一个数组
// 作为构造函数的参数
const k1 = [1, 2, 3]
const k2 = [4, 5, 6]
const wm2 = new WeakMap([
    [k1, 'foo'],
    [k2, 'bar']
])
wm2.get(k2) // "bar"

区别:

  • WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名

  • 不计入垃圾回收机制

17. 字符串的扩展

1. Unicode表示法(少用)

Unicode有啥用:

保证简便高效和保持与已有编码标准兼容之间的平衡

在内部使用Unicode的应用程序,能够同时存储和处理世界上所有的字符,这消除了传统的国际化方法所面临的一些困难

  1. es5
"\u0061"    // "a"

只限于码点在\u0000~\uFFFF之间的字符

超出须用两个双字节的形式表示

"\uD842\uDFB7"   // "