ES的新特性

2020-08-06 23:52:06 浏览数 (1)

❝掌握ES的新特性 ❞

let与块作用域

在ES中作用域一般分为三种:

  • 全局作用域
  • 函数作用域
  • 块级作用域

首先来看一下ES6新增加的特性块级作用域,至于作用域的深入讲解后续会专门出一篇文章进行讲解

在ES6之前的版本没有块级作用域,如下代码:在if块内部定义var 变量 fooif块外都可以访问到,因为var定义的变量是全局变量

代码语言:javascript复制
if (true) {
    var foo = "zce";
}
console.log(foo);

let定义变量,在if块的外部是无法访问的

代码语言:javascript复制
if (true) {
    let foo = "zce";
}
console.log(foo);//foo is not defined

除了在if块,还有for循环块的作用域,如下代码:使用var定义的为全局变量在for嵌套for语句var i = 3 所以外层循环在i=3的时候停止了循环

代码语言:javascript复制
for(var i =0;i<3;i  ){
    for(var i =0;i<3;i  ){
        console.log(i);//当内层循环完毕 外层i也=3 所以循环只执行了一次
    }
}

var改成let,就可以使循环正确完成即:外层循环3次,外层每循环一次内层循环三次。因为let所在的作用域属于for,而每个for循环中定义的let变量都是在不同的作用域中的.所以for嵌套循环即使循环变量名字相同也不会有影响。「一般推荐不要使用同名的计数器」

代码语言:javascript复制
for(let i =0;i<3;i  ){
    for(let i =0;i<3;i  ){
        console.log(i);//当内层循环完毕 不会影响外层循环
    }
}

var 声明的变量还会存在事件绑定的问题,如下代码:

不管调用eles[0]还是eles[1]还是eles[2]打印的结果都是3,这是因为var i 是全局作用域中,i 它并没有保存在事件函数中。

代码语言:javascript复制
var eles = [{},{},{}];
for(var i = 0;i<eles.length;i  ){
    eles[i].onclick=function(){
        console.log(i);//打印的是全局作用域的i
    }
}
eles[2].onclick();//3

看如下的调试信息i存在Global作用域也就是全局作用域中

在调用onclick时候,可以看到[[Scopes]]就是作用域,而里面只有Global的作用域

然后我们看Global里面可以找到 i = 3

一般这种问题,可以通过闭包来解决,是i保存在闭包的函数中,这样打印结果是正常的。

代码语言:javascript复制
var eles = [{},{},{}];
for(var i = 0;i<eles.length;i  ){
    eles[i].onclick=(function(i){
   return function(){
     console.log(i);
   }
  })(i)
}
eles[1].onclick();//1

下面我们再来看一下闭包是如何获取的,通过调试来查看内部的信息,闭包的情况下i的值存在Closure 作用域中

其实上述的问题就是块级作用域的问题,完全可以使用ES6的新特性let声明的块级作用域解决

代码语言:javascript复制
for(let i = 0;i<eles.length;i  ){
    eles[i].onclick=function(){
        console.log(i);
    }
}
eles[2].onclick();//2

如下可以看到在Block作用域中找到i=2

使用let的好处,如下代码两个i互不影响

代码语言:javascript复制
for(let i =0;i<3;i  ){
    let i = 'foo';
    console.log(i);//foo
}

上述代码为什么两个i互不影响呢?其实我们可以将循环进行拆解,如下的伪代码,两个i在不同的作用域中是互不影响的

代码语言:javascript复制
let i = 0;
if(i<3){
    let i = 'foo';
    console.log(i);
}
i  ;

if(i<3){
    let i = 'foo';
    console.log(i);
}
i  ;

if(i<3){
    let i = 'foo';
    console.log(i);
}
i  ;

let 声明的变量不会变量提升,而var会对变量进行提升

代码语言:javascript复制
//let 的声明不会变量提升 必须先声明变量在使用变量
console.log(foo);//undefined
var foo = 'zce';

console.log(foo);//Cannot access 'foo' before initialization
let foo = 'zce';
  • 总结let与var区别
    • let不存在变量提升,var存在变量提升
    • 暂时性死区:块级作用域只要let 声明变量,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
    • let不允许重复声明,var可以重复声明

const 常量

const 声明一个只读的常量,声明过后不允许修改

  • const 一旦声明赋值后就不允许修改
代码语言:javascript复制
const name ='jake';
//const 一旦声明赋值后就不允许修改
name = '1223';
  • const 声明的变量必须有初始值否则会报错
代码语言:javascript复制
//const 声明变量必须有初始值否则报错
const name;
name = 'zce';
  • const 只是不允许修改内存地址 如对象的属性是可以修改的
代码语言:javascript复制
//const 只是不允许修改内存地址 如下对象的属性修改是可以的
const obj = {};
obj.name = 'zce';

//如下就会报错 因为内存地址改变了
obj = {}

不用var,主用const配合let,const 不允许修改内存地址

数组的解构

代码语言:javascript复制
const arr = [100,200,300];

//解构
const [foo,bar,baz] = arr;
console.log(foo,bar,baz);//100 200 300
  • 提取指定成员的位置
代码语言:javascript复制
//提取指定位置的成员
const [,,baz] = arr;
console.log(baz);//300
  • 提取指定位置之后的所有成员 是一个数组
代码语言:javascript复制
const [foo,...rest] = arr;
console.log(rest);//[200,300]

const [...rest] = arr;
console.log(rest);//[100,200,300]
  • 解构成员的个数小于被解构的数组长度 就会从前到后的提取
代码语言:javascript复制
//解构成员的个数小于被解构的数组长度 就会从前到后的提取
const [foo] = arr;
console.log(foo);//100
  • 如果解构成员的个数大于数组的长度,多出来的成员是undefined
代码语言:javascript复制
//如果解构成员的个数大于数组的长度,多出来的成员是undefined
const [foo,bar,ace,more] = arr;
console.log(more);//undefined
  • 给成员设置默认值
代码语言:javascript复制
const [foo,bar,ace=123,more='default value'] = arr;
console.log(more);//default value
  • 通过数组解构的方式可以大大的简化代码
代码语言:javascript复制
const path = '/foo/bar/baz';
//常用写法
const temp = path.split('/');
const rootdir = temp[1];
console.log(rootdir);//foo

//通过数组解构的方式可以大大的简化代码
const [,rootdir] = path.split('/');
console.log(rootdir);

对象的解构

对象的解构比较简单,如下代码还可以通过解构给成员属性重新命名

代码语言:javascript复制
//对象解构
const obj = {name:'zce',age:18}

//注意变量冲突
const name ='tom';
//通过:起一个别名解决冲突
const {name:objName = 'jack',age}=obj;
console.log(name,age,objName);

const {log} = console;
log('foo');

console.log();

模板字符串

  • 支持换行
代码语言:javascript复制
//支持换行
const str = `hello es2015,

            this is a string`;
  • 支持嵌入变量以及表达式
代码语言:javascript复制
const name= 'tom';
const msg = `hey,${name} --- ${1   2 }===${Math.random()}`;
console.log(msg);
  • 支持代表标签的模板字符串
代码语言:javascript复制
const str1 = console.log`hello world`;

const name = 'tom';
const gender = true;

//可以接收到name gender的返回值
function myTagFunc(strings,name,gender){
    console.log(strings,name,gender);//[ 'hey,', ' is a ', '' ] tom true
    const sex = gender ? '男' : '女';
    return strings[0] name strings[1] sex strings[2];
}

const result = myTagFunc`hey,${name} is a ${gender}`;

console.log(result);//hey,tom is a 男

字符串的扩展

代码语言:javascript复制
//es6 字符串的扩展 判断字符串当中是否包含指定的内容

const message = "Error: foo is not defined.";
console.log(message.startsWith('Error'));//字符串头部匹配
console.log(message.endsWith('.'));//字符串尾部匹配
console.log(message.includes('foo'));//字符串中是否包含某个 字符串参数

默认参数值及剩余参数

一般的参数写法如下

代码语言:javascript复制
//一般的写法
function foo(enable){
    //需要判断函数是否传递了
//不能这样写:enable = enable || true; 这样如果传入false还会使用的默认值true
    enable = enable === undefined ? true : enable;
    console.log('foo invoked - enable:');
    console.log(enable);
}
foo(false);

ES6的新特性增加了函数参数的默认值 这样就不用判断参数是否为空了

代码语言:javascript复制
//es6 新的写法 注意如果有多个参数 默认参数一定要放在参数列表最后面 可以定义多个默认参数
function foo(bar,enable = true,tag = "TAG"){
    console.log(bar,enable,tag);
}
foo('hello');

函数接受剩余的参数,接受任意个数的参数 一般通过arguments接受

ES6 引入了... 的形式 已数组的形式去接收当前位置开始所有的形参 同样只能出现在形参列表的最后一位,而且只能出现一次

代码语言:javascript复制
function foo(first,...args){
    console.log(first,args);
}
foo(1,2,3,4);//[ 1, 2, 3, 4 ]

展开数组

代码语言:javascript复制
// ... 操作符展开数组 而不用在根据下标进行展开数组
const arr= ['foo','bar','baz'];

console.log(arr[0],arr[1],arr[2]);

//以前的写法 打印数组
console.log.apply(console,arr);

//新特性直接通过...操作符可输出数组的值 大大减少了操作
console.log(...arr);

箭头函数

传统定义的函数

代码语言:javascript复制
//传统定义函数
function inc(number){
    return number   1;
}
  • 箭头函数 没有括号 则作为结果返回
代码语言:javascript复制
const inc = n => n   1;
console.log(inc(100));
  • 多行代码 需要使用括号 并且需要些return进行返回
代码语言:javascript复制
//多行代码 需要使用括号 并且需要些return进行返回
const inc = (n, m) => {
    console.log('inc invoked');
    return n   m;
}

const arr = [1, 2, 3, 4, 5, 6];
//过滤偶数 箭头函数使代码更加剪短易读
console.log(arr.filter(i => i % 2));
  • 箭头函数与this 箭头函数不会改变this的指向

如果使用箭头函数那么箭头函数的this是什么,那么箭头函数的里面的this就是什么 不会发生改变

代码语言:javascript复制
const person = {
    name: "tom",
    // syaHi: function () {
    //     console.log(`he,my name is ${this.name}`);//tom
    // }
    syaHi: ()=> {
        // 箭头函数与普通函数的区别 不会改变this的指向 也就是说在箭头函数外面的this是什么 函数里面this就是什么
        console.log(`he,my name is ${this.name}`);//undefined 
    },
    sayHiAsync:function(){
        // const _this = this;//借助闭包的机制 保存当前作用域的this
        // setTimeout(function() {
        //     最终会放在全局对象上调用 无法拿到当前作用域的this对象 拿到的是全局对象
        //     console.log(_this.name);//undefined
        // }, 1000);

        //还可以使用箭头函数来解决 因为箭头函数的作用域就是当前作用域的对象 不会改变this
        setTimeout(()=> {
            //最终会放在全局对象上调用 无法拿到当前作用域的对象
            console.log(this.name);//tom
        }, 1000);
        //一般需要用到闭包解决的this都可以使用箭头函数解决
    }
}

person.sayHiAsync();

「一般需要用到闭包解决的this都可以使用箭头函数解决」

对象字面量新特性

代码语言:javascript复制
/* 对象字面量新特性 */

const bar = '345';

const obj = {
    foo:123,
    bar,//变量名与添加的属性名一致
    method(){//添加方法可以省略掉:和function
        console.log(this);//内部的this指向当前的对象
    }
}
console.log(obj);//{ foo: 123, bar: '345', method: [Function: method] }
obj.method();

//通过方括号动态使用属性名 可以使用任意的表达式 表达式的结果将会作为对象属性的属性名
obj[Math.random()] = 123;//计算属性名

对象扩展方法

  • Object.assign 将多个源对象中的属性复制到目标对象中
代码语言:javascript复制
const source1 = {
    a: 123,
    b: 123
}

const target = {
    a: 456,
    c: 456
}

//第一个参数是目标对象 也就是所有源对象的属性都会复制到目标对象中
const result = Object.assign(target, source1);
console.log(result);//{ a: 123, c: 456, b: 123 } 后面对象的属性覆盖第一个对象的属性 可以看到a属性被源对象覆盖了
console.log(target);//{ a: 123, c: 456, b: 123 }
console.log(result === target);//返回的对象和目标对象完全相等的

//复制一个对象应用
//比如下面的例子 向函数传递一个对象 如果函数改变了对象的某个属性的属性值 那么外部的对象也会发生改变
function func(obj) {
    obj.name = 'func obj';
    console.log(obj);
}
const obj = {name:'global obj'};

func(obj);//{ name: 'func obj' }

console.log(obj);//{ name: 'func obj' }

//可以使用object.assign 复制一个对象就不会改变外部对象
function copyFunc(obj){
    const o = Object.assign({},obj);
    o.name = 'func obj';
    console.log(o);//{ name: 'func obj' }
}
const obj2 = {name:'global obj'};
copyFunc(obj2);
console.log(obj2);//{ name: 'global obj' }
  • Object.is 扩展方法 判断两个值是否相等
代码语言:javascript复制
//== 会在比较之前自动转换类型
//=== 严格判断类型
console.log(0==false,0===false);//true false
console.log( 0===-0);//true 三等运算符是无法比较 0 和 -0
console.log(NaN===NaN);//false 两个NaN不相等的
//Object.is() 新的一种全等的方法 0和-0可以区分开 两个NaN是相等的 一般不推荐这种方法还是使用===运算法进行比较
console.log(Object.is( 0,-0));//false
console.log(Object.is(NaN,NaN));//true

Proxy

监视某个对象属性的读写,以前ES5使用Object.defineProperty 在vue3.0以前的版本使用这样的方法进行属性响应从而实现了数据绑定Object.defineProperty 需要针对每一个属性进行设置,ES6 提供Proxy监视对象的读写过程 Proxy可以监视对象的所有属性 要强大很多,Proxy 能够监视到更多对象操作,Proxy 更好的支持数组对象的监视;Proxy 是以非侵入的方式监管了对象的读写.

代码语言:javascript复制
//代理对象 监视对象读和写
const person = {
    name:'zce',
    age:20
}
//返回一个代理对象 第一个参数就是需要代理的目标对象 第二个参数是代理的处理对象
const personProxy = new Proxy(person,{
    //监视属性的访问
    get(target,property){
        //target 目标对象 property属性名
        // console.log(target,property);//{ name: 'zce', age: 20 } name
        //返回值是外部访问属性的结果 先判断目标对象是否存在属性
        return property in target ? target[property] : 'default';
    },
    //监视属性的写入
    set(target,property,value){
        //target 目标对象 property属性名 value属性值
        console.log(target,property,value);//{ name: 'zce', age: 20 } gender true
        //可以做一些数据校验
        if(property === 'age'){
            //判断属性值是否为数字
            if (!Number.isInteger(value)) {
                throw new TypeError(`${value} is not an int`);
            }
        }
        target[property] = value;
    }
});
console.log(personProxy.name);//zce
console.log(personProxy.xxx);//default

personProxy.gender= true;
personProxy.age = 123;

Proxy 对比 VS Object.defineProperty

更多的方法可以查询MDN-Proxy

  • Proxy 能够监视到更多对象操作 , Object.defineProperty 只能监视对象属性的读写
代码语言:javascript复制
const proxy  = new Proxy(person,{
    deleteProperty(target,property){
        console.log(`delete ${property}`);
        //监听属性的删除
        delete target[property];
    }
});

delete proxy.age;
console.log(person);//{ name: 'zce', gender: true }
  • Proxy 更好的支持数组对象的监视
代码语言:javascript复制
const list = [];

const listProxy = new Proxy(list,{
    set(target,property,value){
        console.log(target,property,value);//[] 0(数组的下标) 100(值)
        target[property] = value;
        return true;//写入成功
    }
});
listProxy.push(100);
  • Proxy 是以非侵入的方式监管了对象的读写

Object.defineProperty 会侵入对象,如下代码defineProperty会在对象p中新增_name_age属性,其实就是对p对象侵入了。

代码语言:javascript复制
const p = {};
Object.defineProperty(p,'name',{
    get(){
        console.log("name 被访问");
        return p._name;
    },
    set(value){
        console.log("name 被设置");
        p._name = value;
    }
});
Object.defineProperty(p,'age',{
    get(){
        console.log("age 被访问");
        return p._age;
    },
    set(value){
        console.log("age 被设置");
        p._age = value;
    }
});
p.name = 'jake';
console.log(p.name);//jake
  • Proxy无操作转发代理
代码语言:javascript复制
let target = {};
let p = new Proxy(target, {});

p.a = 37;   // 操作转发到目标

console.log(target.a);    // 37. 操作已经被正确地转发
  • 操作 DOM 节点
代码语言:javascript复制
let view = new Proxy({
  selected: null
}, {
  set: function(obj, prop, newval) {
    let oldval = obj[prop];

    if (prop === 'selected') {
      if (oldval) {
        oldval.setAttribute('aria-selected', 'false');
      }
      if (newval) {
        newval.setAttribute('aria-selected', 'true');
      }
    }

    // 默认行为是存储被传入 setter 函数的属性值
    obj[prop] = newval;

    // 表示操作成功
    return true;
  }
});

let i1 = view.selected = document.getElementById('item-1');
console.log(i1.getAttribute('aria-selected')); // 'true'

let i2 = view.selected = document.getElementById('item-2');
console.log(i1.getAttribute('aria-selected')); // 'false'
console.log(i2.getAttribute('aria-selected')); // 'true'

Reflect

Reflect 属于一个静态类,Reflect 内部封装了一系列对对象的底层操作 14个静态方法 其中废弃掉了一个,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同,Reflect 成员方法就是Proxy处理对象的默认实现

代码语言:javascript复制
const obj = {
    foo:'123',
    bar:'456'
}

const proxy = new Proxy(obj,{
    get(target,property){
        console.log('watch logic~');
        return Reflect.get(target,property);
    }
});

console.log(proxy.foo);

「Reflect 的意义:提供统一一套用于操作对象的API」

代码语言:javascript复制
const obj = {
    name:'zce',
    age:18
}
//传统的写法
// console.log('name' in obj);
// console.log(delete obj['age']);
// console.log(Object.keys(obj));

//统一的方法 推荐使用Reflect来操作对象
console.log(Reflect.has(obj,'name'));//是否存在属性
console.log(Reflect.deleteProperty(obj,'age'));
console.log(Reflect.ownKeys(obj));

Class

一般定义一个类型是通过函数 原型的方式去创建一个类

代码语言:javascript复制
function Person(name){
    this.name = name;
}
Person.prototype.say = function(){
    console.log(`hi my name is ${this.name}`);
}
Person.t = "123";

Class写法

代码语言:javascript复制
class Person{
    constructor(name){
        this.name = name;
    }
    //实例方法
    say(){
        console.log(`hi my name is ${this.name}`);
    }
    //静态方法
    static create(name){
        //注意静态方法中的this是指向调用的作用域 而不是实例的作用域
        return new Person(name);
    }
}
// const p = new Person('tom');
const p = Person.create('tom')
p.say();

//继承
class Student extends Person{
    constructor(name,number) {
        super(name);//super始终指向父类 可以调用父类的方法super.say()
        this.number = number;
    }
    hello(){
        super.say();//调用父类的方法
        console.log(`my school number is ${this.number}`);
    }
}

const s = new Student('jake',123);
s.hello();

Set

Set 数据结构 集合 Set内部成员不允许重复

代码语言:javascript复制
const s = new Set();
s.add(1).add(2).add(3).add(4).add(2);

// s.forEach(i=>console.log(i));

// for (const iterator of s) {
//     console.log(iterator);
// }

console.log(s.size);//4

console.log(s.has(100));//has 是否包含某个值 false

console.log(s.delete(3));//删除集合某一个值 true

s.clear();//清除集合
console.log(s);

//Set 主要用于数组去重
const arr = [1,2,1,3,4,1];
const result = Array.from(new Set(arr));//去重的数组 转换为一个新的数组
//也可以通过展开运算符
console.log([...new Set(arr)]);//[ 1, 2, 3, 4 ]
console.log(result);//[ 1, 2, 3, 4 ]

Map

Map 数据结构 键值对集合 可以用其他类型的数据最为键

代码语言:javascript复制
//如Object的问题
const obj = {};

obj[true] = 'value';
obj[123] = 'value';
obj[{a:1}] = 'value'

console.log(Object.keys(obj));//[ '123', 'true', '[object Object]' ] 全部转换为了字符串 以toString的结果作为键

console.log(obj['[object Object]']);//value
console.log(obj[{}]);//value
console.log(obj['true']);//value

//Map 是键值对映射集合 键可以是任意类的数据
const m = new Map();
const tom = {name:'tom'};
m.set(tom,90);//键可以是任意类型的数据

console.log(m);//Map { { name: 'tom' } => 90 }

console.log(m.get(tom));//90

m.forEach((value,key)=>{
    console.log(value,key);//90 { name: 'tom' }
})

console.log(m.has(tom));//true
console.log(m.delete(tom));//true
m.clear();
console.log(m);

Symbol

主要作用:为对象添加独一无二的属性名

ES6 之前对象的属性名都是字符串 而字符串都是有可能会重复的 重复的话就会冲突

如下例子 缓存的对象 约定的方式解决

代码语言:javascript复制
const cache = {};
//a.js
cache['a_foo'] = Math.random();

//b.js 
//不知道之前存在
cache['b_foo'] = '123';

console.log(cache);//{ foo: '123' }

Symbol 表示一个独一无二的值 ES6 支持Symbol作为属性名

代码语言:javascript复制
const s = Symbol();
console.log(s);//Symbol()
console.log(typeof s);//symbol
console.log(Symbol() === Symbol());//false

console.log(Symbol('foo'));//Symbol(foo)
console.log(Symbol('bar'));//Symbol(bar)
console.log(Symbol('baz'));//Symbol(baz)

//对象支持symbol作为键
const ss=Symbol('foo');
const aa = Symbol('foo');
const obj = {
    [ss]:123,
};
obj[aa]='test'
console.log(obj[ss],obj[aa]);//123 test
  • 实现对象的私有成员
代码语言:javascript复制
//a.js==================
//对外暴露对象
const name = Symbol();
const person = {
    [name]:'ace',
    say(){
        console.log(this[name]);
    }
}
exprot person;
//b.js ==============
import person from 'a.js';
console.log(person.say());//ace
//b.js 就无法获取name的属性因为没有对外暴露,而且是Symbol独一无二的值
  • Symbol 每次调用都是一个全新的一个值,for() 相同的字符串返回相同的Symbol值 维护的是字符串和Symbol的对应关系
代码语言:javascript复制
const s1 = Symbol.for('foo');
const s2 = Symbol.for('foo');
//for() 相同的字符串返回相同的Symbol值 维护的是字符串和Symbol的对应关系
console.log(s1===s2);//true 
//如果for() 方法传入的不是字符串就会 默认转换为字符串 注意如果传入true和'true' 结果是一样的 注意!!
console.log(Symbol.for(true)===Symbol.for('true'));// true 

console.log(Symbol.iterator);
console.log(Symbol.hasInstance);
  • Symbol 定义的对象属性名,不能够被迭代找到
代码语言:javascript复制
console.log(Symbol.iterator);//Symbol(Symbol.iterator)
console.log(Symbol.hasInstance);//Symbol(Symbol.hasInstance)

const obj2 = {
    //为对象实现迭代器会经常用到
    [Symbol.toStringTag]:"XObject", //[object XObject]
}
console.log(obj2.toString());//[object XObject] [object Object] 自定义对象的toString标签

//symbol 不能被迭代获取到 如下面的方式都无法获取symbol属性名
const obj3 = {
    [Symbol()]:'Symbol value',
    foo:'normal value'
}
//for..in..无法获取Symbol
for (const key in obj3) {
    console.log(key);//foo
}
//keys无法获取到Symbol
console.log(Object.keys(obj3));//[ 'foo' ]
//即使将对象转换为json字符串 依然找不到Symbol
console.log(JSON.stringify(obj3));//{"foo":"normal value"}

//可以通过getOwnPropertySymbols获取symbol属性,注意他只能获取symbol类型的属性名 普通的无法获取
console.log(Object.getOwnPropertySymbols(obj3));//[ Symbol() ]

for..of 循环

for...of... 可以作为所有遍历的方式

代码语言:javascript复制
//for
//for..in.. 
//forEach

//for..of.. 循环遍历所有数据结构的统一方式

const arr = [100,200,300,400];

//拿到的数组每一个元素
for (const iterator of arr) {
    console.log(iterator);
    if(iterator > 200){
        break;
    }
}

//forEach 不能跳出循环 终止循环
//arr.some()
//arr.every()

const s= new Set(['foo','bar']);

for (const iterator of s) {
    console.log(iterator);
}

const m = new Map();

m.set('foo',123);
m.set('bar',456);

for (const [key,value] of m) {//解构数组
    console.log(key,value);//[ 'foo', 123 ] [ 'bar', 456 ]
}

Object 对象不能被for_of迭代 需要自定义迭代器,也就是说只要实现了迭代器就可以被for..of循环,ES6这个新特性主要是方便统一所有的循环,只要循环的对象实现了迭代器就可循环迭代

代码语言:javascript复制
//Object 对象不能被for_of迭代 需要自定义迭代器
const obj = {foo:123,bar:456}

for (const iterator of object) {//object is not defined
    
}

可迭代接口 用于for..of 循环 提供了Iterable 接口,只要数据结构实现了Iterable接口就可以被for..of 遍历Symbol.iterator 方法实现

for..of 遍历的原理 内部实现Symbol.iterator方法,如下代码比如Set对象

代码语言:javascript复制
const set = new Set(['foo', 'bar', 'baa']);

const iterator = set[Symbol.iterator]();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
/* 
{ value: 'foo', done: false }
{ value: 'bar', done: false }
{ value: 'baa', done: false }
{ value: undefined, done: true }
*/

下面我们将Object实现[Symbol.iterator]属性

代码语言:javascript复制
const obj = {
    store:['foo','bar','baz'],
    [Symbol.iterator]: function () {
        let index = 0;
        const self = this;
        return {//iterable
            //约定内部必须要有一个next方法 iterator
            next: function () {
                //约定的迭代结果接口:IterationResult value 当前迭代的数据 done 用来表示迭代有没有结束
                const result = {
                    value: self.store[index],
                    done: index >= self.store.length
                }
                index  ;
                return result;
            }
        }
    }
};
for (const iterator of obj) {
    console.log(iterator);
}
  • 迭代器模式 场景:协同开发一个任务清单应用
代码语言:javascript复制
//a同学的代码 ====================================
const todos = {
    life:['吃饭','睡觉','打豆豆'],
    learn:['语文','数学','外语'],
    //一般提供一个迭代的回调函数 但是这样并不好用 需要根据某个key来进行迭代 如果key值改变以后 其他调用的代码都需要改变
    each:function(callback){
        const all = [].concat(this.life,this.learn,this.work);
        for (const iterator of all) {
            callback(iterator);
        }
    },
    //对外提供统一遍历的接口 直接使用for..of循环即可 不用关系内部的结构是什么样的 从语言层面实现的迭代器模式 可以适用任何的数据结构
    [Symbol.iterator]:function(){
        const all =[...this.life,...this.learn];
        let index = 0;
        return{
            next:function(){
                return{
                    value:all[index],
                    done:index   >= all.length
                }
            }
        }
    }
}

// b同学的代码 =======================
// for (const iterator of todos.life) {
//     console.log(iterator);
// }

// for (const iterator of todos.learn) {
//     console.log(iterator);
// }
//不推荐使用回调函数的方式
todos.each(item=>{
    console.log(item);
});
console.log('-------------------------------------');
//推荐统一的循环迭代 不会被对象的属性key改变所影响
for (const iterator of todos) {
    console.log(iterator);
}

生成器 Generator

生成器 Generator 避免异步编程中回调嵌套过深提供更好异步编程解决方案

代码语言:javascript复制
function * foo() {
    console.log('zce');
    return 100;
}
const result = foo();
console.log(result);//Object [Generator] {} 调用函数返回一个生成器对象
console.log(result.next());//zce     { value: 100, done: true }

生成器对象也实现了iterable接口

代码语言:javascript复制
function * f(){
    console.log(1111);
    yield 100
    console.log(222);
    yield 200
    console.log(3333);
    yield 300
}
const generator = f();
//一旦遇到yield就会停下来不往下执行 当再调用next()才会继续执行
console.log(generator.next());//1111 { value: 100, done: false }
console.log(generator.next());//2222 { value: 200, done: false }
console.log(generator.next());//3333 { value: 300, done: false }
console.log(generator.next());//{ value: undefined, done: true }

使用Generator 函数实现iterator方法

代码语言:javascript复制
// 生成器的应用:发号器
function * createIdMaker(){
    let id = 1;
    while(true){
        yield id  ;//不必担心死循环
    }
}

const idMaker=createIdMaker();
console.log(idMaker.next().value);
console.log(idMaker.next().value);
console.log(idMaker.next().value);
console.log(idMaker.next().value);

//使用Generator 函数实现iterator方法
const todos = {
    life:['吃饭','睡觉','打豆豆'],
    learn:['语文','数学','外语'],
    //通过Generator函数进行改造
    [Symbol.iterator]:function * (){
        const all =[...this.life,...this.learn];
        for (const iterator of all) {
            yield iterator;
        }
    }
}
for (const iterator of todos) {
    console.log(iterator);
}

Array 的扩展方法

代码语言:javascript复制
const arr = ['foo',1,NaN,false];

//一般查找数组元素通过find()方法找到元素的下标 但是他不能查找NaN
//includes可以查找NaN的数值 直接返回true/false
console.log(arr.includes(NaN));//true

//指数运算符----------------------------------------
console.log(Math.pow(2,10));//2的10次方

console.log(2 ** 10);//2的10次方

ECMAScript 2017

代码语言:javascript复制
const obj={
    foo:'foo',
    bar:'bar'
}
//Object.values 返回值数组-----------------
console.log(Object.values(obj));//[ 'foo', 'bar' ]

//Object.entries 返回键值对数组--------------
console.log(Object.entries(obj));// [ [ 'foo', 'foo' ], [ 'bar', 'bar' ] ]
for (const [key,value] of Object.entries(obj)) {
    console.log(key,value);//foo foo
}
//将一个对象转换成Map对象
console.log(new Map(Object.entries(obj)));//Map { 'foo' => 'foo', 'bar' => 'bar' }

//Object.getOwnPropertyDescriptors-----------------
const p1 = {
    firstName:'Lei',
    lastName:'Wang',
    get fullName(){
        return this.firstName ' ' this.lastName;
    },
    test(){
        console.log(this.firstName ' ' this.lastName);
    }
}
console.log(p1.fullName);//Lei Wang

const p2 = Object.assign({},p1);//Object.assign 存在的问题 get属性方法无法复制
p2.firstName = 'zce';
console.log(p2.fullName);//Lei Wang

//配合getter setter 属性使用
const descriptors = Object.getOwnPropertyDescriptors(p1);
//Object.defineProperties() 方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
const p3 = Object.defineProperties({},descriptors);
p3.firstName = 'zce';
console.log(p3.fullName);//zce Wang

//String.prototype.padStart/String.prototype.padEnd------------------

const books ={
    html:5,
    css:16,
    js:128
}

for (const [name,count] of Object.entries(books)) {
    console.log(`${name.padEnd(16,'-')}|${count.toString().padStart(3,'0')}`);
}

//在函数参数中添加尾逗号
const arr=[
    100,
    200,
    300,
    400,
]

0 人点赞