首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ES的新特性

ES的新特性

作者头像
用户3045442
发布2020-08-06 23:52:06
1K0
发布2020-08-06 23:52:06
举报

❝掌握ES的新特性 ❞

let与块作用域

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

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

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

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

if (true) {
    var foo = "zce";
}
console.log(foo);

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

if (true) {
    let foo = "zce";
}
console.log(foo);//foo is not defined

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

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嵌套循环即使循环变量名字相同也不会有影响。「一般推荐不要使用同名的计数器」

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 它并没有保存在事件函数中。

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保存在闭包的函数中,这样打印结果是正常的。

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声明的块级作用域解决

for(let i = 0;i<eles.length;i++){
    eles[i].onclick=function(){
        console.log(i);
    }
}
eles[2].onclick();//2

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

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

for(let i =0;i<3;i++){
    let i = 'foo';
    console.log(i);//foo
}

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

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会对变量进行提升

//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 一旦声明赋值后就不允许修改
const name ='jake';
//const 一旦声明赋值后就不允许修改
name = '1223';
  • const 声明的变量必须有初始值否则会报错
//const 声明变量必须有初始值否则报错
const name;
name = 'zce';
  • const 只是不允许修改内存地址 如对象的属性是可以修改的
//const 只是不允许修改内存地址 如下对象的属性修改是可以的
const obj = {};
obj.name = 'zce';

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

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

数组的解构

const arr = [100,200,300];

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

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

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

对象的解构

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

//对象解构
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();

模板字符串

  • 支持换行
//支持换行
const str = `hello es2015,

            this is a string`;
  • 支持嵌入变量以及表达式
const name= 'tom';
const msg = `hey,${name} --- ${1 + 2 }===${Math.random()}`;
console.log(msg);
  • 支持代表标签的模板字符串
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 男

字符串的扩展

//es6 字符串的扩展 判断字符串当中是否包含指定的内容

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

默认参数值及剩余参数

一般的参数写法如下

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

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

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

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

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

function foo(first,...args){
    console.log(first,args);
}
foo(1,2,3,4);//[ 1, 2, 3, 4 ]

展开数组

// ... 操作符展开数组 而不用在根据下标进行展开数组
const arr= ['foo','bar','baz'];

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

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

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

箭头函数

传统定义的函数

//传统定义函数
function inc(number){
    return number + 1;
}
  • 箭头函数 没有括号 则作为结果返回
const inc = n => n + 1;
console.log(inc(100));
  • 多行代码 需要使用括号 并且需要些return进行返回
//多行代码 需要使用括号 并且需要些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就是什么 不会发生改变

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都可以使用箭头函数解决」

对象字面量新特性

/* 对象字面量新特性 */

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 将多个源对象中的属性复制到目标对象中
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 扩展方法 判断两个值是否相等
//== 会在比较之前自动转换类型
//=== 严格判断类型
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 是以非侵入的方式监管了对象的读写.

//代理对象 监视对象读和写
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 只能监视对象属性的读写
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 更好的支持数组对象的监视
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对象侵入了。

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无操作转发代理
let target = {};
let p = new Proxy(target, {});

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

console.log(target.a);    // 37. 操作已经被正确地转发
  • 操作 DOM 节点
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处理对象的默认实现

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」

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

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

function Person(name){
    this.name = name;
}
Person.prototype.say = function(){
    console.log(`hi my name is ${this.name}`);
}
Person.t = "123";

Class写法

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内部成员不允许重复

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 数据结构 键值对集合 可以用其他类型的数据最为键

//如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 之前对象的属性名都是字符串 而字符串都是有可能会重复的 重复的话就会冲突

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

const cache = {};
//a.js
cache['a_foo'] = Math.random();

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

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

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

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
  • 实现对象的私有成员
//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的对应关系
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 定义的对象属性名,不能够被迭代找到
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... 可以作为所有遍历的方式

//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这个新特性主要是方便统一所有的循环,只要循环的对象实现了迭代器就可循环迭代

//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对象

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]属性

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);
}
  • 迭代器模式 场景:协同开发一个任务清单应用
//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 避免异步编程中回调嵌套过深提供更好异步编程解决方案

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接口

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方法

// 生成器的应用:发号器
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 的扩展方法

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

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,
]
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android研究院 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • let与块作用域
  • const 常量
  • 数组的解构
  • 对象的解构
  • 模板字符串
    • 字符串的扩展
    • 默认参数值及剩余参数
    • 展开数组
    • 箭头函数
    • 对象字面量新特性
    • 对象扩展方法
    • Proxy
      • Proxy 对比 VS Object.defineProperty
      • Reflect
      • Class
      • Set
      • Map
      • Symbol
      • for..of 循环
      • 生成器 Generator
      • Array 的扩展方法
      • ECMAScript 2017
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档