【js】ES6进阶学习

1.Symbol

Symbol是ES6引入的,继undefind,null,String,Number,Boolean,Object之后的第七种原始数据类型。Symbol变量通过Symbol函数生成,可以传入一个字符串用于描述(主要是方便在控制台打印和遍历识别)symbol.Symbol表示独一无二的值。

//由于 Symbol 表示独一无二的值,则使用Symbol()生成的值不存在和别的值相等
const s1 = Symbol('s1');
const s2 = Symbol('s2');
console.log('s1:',s1);
console.log('s2:',s2);
console.log('s1 === s2', s1 === s2);//false

1.Symbol作为属性存在

Symbol独一无二的特性很适合将其用作标识符,比如作为对象的属性名存在。 

const sy = Symbol();

let a = {};
a[sy] = '你好';
a['name'] = '名称';

// let a = {
//     // []代表用sy这个变量作为key值,否则key值为字符串
//     [sy]: '你好'
// };

// let a = {};
// Object.defineProperty(a, sy, { value: '你好defineProperty'} );

console.log('a:',a);

// 通过Object.getOwnPropertySymbols方法来遍历Symbol,且只可以遍历出其中的Symbol
const arr = Object.getOwnPropertySymbols(a);
console.log('symbol arr:', arr);

 tips:

Symbol只可以通过Object.getOwnPropertySymbols()方法遍历出来,且只可以遍历出其中的Symbol.

for...in,for...of,Object.keys(),Object.getOwnPropertyNames(),JSON.stringify()等方法均不可以用于获取到Symbol.其实这点已经表示了Symbol不会在日常开发中被常用

2.Symbol.for

当希望使用同一个Symbol值的时候,可以使用Symbol.for(str) 方法查找到已全局注册的Symbol值进行使用.

当Symbol.for(str)查找不到时则新建str标识的Symbol并全局注册,否则返回str标识的Symbol.

// 没有时则新建
// const s1 = Symbol.for('foo');

// // 查找到之前新建的(s1用for时则表示已在全局注册,s2通过字符串描述查找到上s1)
// const s2 = Symbol.for('foo');

// console.log('s1',s1);
// console.log('s2',s2);
// console.log('s1 === s2', s1 === s2);// true

// 新建symbol,但未在全局注册,下面s2查找不到则新建了symbol
const s1 = Symbol('你好');

const s2 = Symbol.for('你好');
console.log('s1 === s2', s1 === s2);// false

总结起来就是Symbol.for(str)可以生成Symbol,并依赖str标识进行查找. 

tips:ES6提供了11个内置的Symbol值,指向语言内部使用的方法。例如Symbol.iterator。

3.Symbol日常开发中的一些用途

当Symbol表示独一无二的值时,就意味着它并不接地气。我们通常会对它的获取,使用场景,及用途感到疑问。

用于消除魔术字符串

const shapeOption = {
// 不需要关心value值
    triangle: Symbol(),
    rectangle: Symbol()
;

function getArea(shape, options){
    let area = 0;

     switch(shape){
     // 直接使用字符串会和代码形成强耦合,不利于维护
     case shapeOption.triangle:
             area = .5 * options.width * options.height;
             break;
     }
     return area;
}

   getArea(shapeOption.triangle, { width: 100, height: 100 });

 用于实现私有属性

 但依然可以获取,通过Object.getOwnPropertySymbols()方法。前端的私有属性属实难以界定。

var Person = (function(){
     var nameSy = Symbol('name');

     function Person(name){
          this[nameSy] = name
     }

     Person.prototype.getName = function(){
          return this[nameSy];
     }
            
     return Person;
}());

2.Set和WeakSet

1.Set 

ES6提供新的数据结构Set,类似于数组,成员唯一,无法重复,利用这一特性常用于数组去重。

通过new构造函数生成Set 

let set = new Set();

操作

通过 new Set().size 属性获取set的长度

共有四个操作方法

  • add(value)  添加元素,可链式调用
  • delete(value)  删除元素,返回boolean值
  • has(value)   判断元素是否存在,返回boolean值
  • clear()  清空Set
let s = new Set();
console.log('s.size:', s.size);// 0
s.add(1);
s.add('a').add(1);//add可以链式调用
console.log('变更后s.size:',s.size);// 2
s.delete(1);
console.log('变更后s.size:',s.size);// 1
console.log(s.has(1));
s.clear();
console.log('s.size:',s.size);// 0

遍历

共有四个遍历方法

  • keys()  遍历键名
  • values()  遍历键值
  • entries() 遍历键值对

  • forEach((key,value) => {})

tips:Set 键名同时也是键值,则keys也是values,确实有些鬼畜.  set去重很常见,这里不赘叙

let s = new Set().add(1).add(2);

// 键名遍历
// for(let key of s.keys()){
//     console.log(key);
// }

// 键值遍历
// for(let value of s.values()){
//     console.log(value);
// }

// 键值对遍历
// for(let entry of s.entries()){
//     console.log(entry);//[1,1] 每次打印结果是数组,第一个元素是key,第二个元素是value
// }

s.forEach((key, value) => console.log(key, value));

2.WeakSet

WeakSet与Set,成员也是唯一不重复,它和Set有以下区别

  • WeakSet成员只能是对象
  • WeakSet中的对象是弱引用  
  • WeakSet由于弱引用,不能遍历

 tips:当一个对象无任何引用时,会尽快被垃圾回收机制回收. 弱引用是垃圾回收机制不考虑WeakSet对该对象的引用。所以当一个对象是有弱引用且无其他对象引用该对象时,此时垃圾回收机制依然会尽快回收。

操作

  • add(value)  添加元素
  • delete(value)  删除元素
  • has(value) 判断元素是否存在,返回boolean值
<div>我是一个div</div>
    <script>
        let div = document.querySelector('div');
        // let s = new Set();
        let s = new WeakSet();//弱引用,无需删除,内存自会回收
        s.add(div);
        document.body.removeChild(div);
        // dom对象依然在内存中,因为Set中依然引用此对象
        div = null;
     </script>

3.Map和WeakMap

1.Map

ES6提供Map数据结构,类似于对象,是键值对的集合。键和值可以是任何类型。通过new构造函数来创建。 

操作

通过 new Map().size 属性获取set的长度

共有五个操作方法

  • set(key, value)  设置键值对
  • get(key)  根据键获取对应值
  • delete(key)  删除键值对
  • has(key)   判断键值对是否存在,返回boolean值
  • clear()  清空Map
// 初始化
// let m = new Map();

let m = new Map([['name', 'jack'],['age',18]]);

m.set('name','rose');
m.set('birth','1998');

m.set({},1);
m.set(undefined,1);
m.set(1,1);
m.set('1',1);
m.set(['jack','rose'],1);

console.log(m);
console.log(m.size);

console.log('name:',m.get('name'));
console.log('has age:',m.has('age'));

m.delete('name');
console.log(m);
        
m.clear();
console.log('清空后size:',m.size);

 遍历

共有四个遍历方法

  • keys()  遍历键名
  • values()  遍历键值
  • entries() 遍历键值对

  • forEach((key,value) => {})

tips:和Set遍历方法一致 

let m = new Map([['name', 'jack'],['age',18]]);

m.set('name','rose');
m.set('birth','1998');

m.set({},1);
m.set(undefined,1);
m.set(1,1);
m.set('1',1);
m.set(['jack','rose'],1);

// keys()
// for(let key of m.keys()){
//     console.log(key);
// }

// values()
// for(let value of m.values()){
//     console.log(value);
// }

//entries()
// for(let entry of m.entries()){
//     console.log(entry); //为数组,其中只包含key,value 二个元素
// } 

// forEach
m.forEach((key, value) => {
     console.log(key, value);
});

使用Map实现私有属性

在闭包的基础上使用Map来保存私有字段,但有弊端,使用该类new出来的实例必须全部清除,才不会导致Map占用内存

var Person = (function(){
      var map = new Map();

      function Person(name){
          var privateProperty = {
                name
          };

          // 示例对应私有属性
            map.set(this, privateProperty);
          }

          Person.prototype.getName = function(){
                return map.get(this).name;
          }

        return Person;
}());

2.WeakMap

WeakMap和Map有以下区别

  • WeakMap的键只能是对象
  • WeakMap的键是弱引用
  • WeakMap不能遍历 
  • WeakMap只有四个操作方法:get(),set(),has(),delete()
const map = new WeakMap();
let key = {};
let obj = { foo: 1 };

map.set(key, obj);
// obj置为空时
obj = null;
// 依然能通过key获取到
console.log(map.get(key));

 WeakMap实现私有属性

使用WeakMap解决了需要清除所有实例才能去除内存占用的问题

var Person = (function(){
      var map = new WeakMap();

      function Person(name){
          var privateProperty = {
                name
          };

          // 示例对应私有属性
            map.set(this, privateProperty);
          }

          Person.prototype.getName = function(){
                return map.get(this).name;
          }

        return Person;
}());

4.Proxy

Proxy一词意为 `代理` ,它用于代理对代理对象的一些增删改操作。Proxy在目标对象之前架设一层`拦截`,外界对该Proxy对象的访问,都必须先通过这层拦截,其实就是Proxy提供了一种机制来对外界的访问进行过滤和改写。Proxy并不对原对象进行拦截操作,而是对原对象代理后所生成的Proxy示例来进行拦截操作

而Object.defineProperty()是拦截对象属性的读取和修改操作。两者不同的地方是:Proxy拦截的是原对象,而Object.defineProperty()拦截的是原对象的属性

//target: 被代理对象
//handler: 拦截函数(操作)的集合
let proxy = new Proxy(target, handler)

tips:handler为空时则代表不拦截任何操作

let obj = {};
        
// handler为空对象
// let proxy = new Proxy(obj, {});
// // proxy代理需要直接操作proxy对象
// proxy.a = 1;
// proxy.b = 2;

// console.log('obj:', obj);

let proxy = new Proxy(obj, {
    // get拦截
    get(target, key, receiver){
         //get中直接修改返回值
         return target.a + 1;
    }
});

proxy.a = 1;
console.log('proxy:',proxy.a);//35

Proxy可以拦截的13种操作

  • get(target, key, receiver)  拦截对象属性的读取,比如proxy.foo和proxy['foo']
  • set(target, key, value, receiver)  拦截对象属性的设置,比如proxy.foo = v或者proxy['foo'] = v,返回boolean值

  • has(target, propKey)  拦截 propKey in proxy的操作,返回boolean值

  • deleteProperty(target, propKey)  拦截 delete proxy[propKey]的操作,返回boolean值

  • ownKeys(target)  拦截Object.getOwnPropertyNames(proxy),Object.getOwnPropertySymbols(proxy),Object.keys(proxy),for...in,返回一个数组,该方法返回目标对象所有自身属性的属性名,而Object.keys()的返回结果仅包括墓表对象自身的可遍历属性.

  • getOwnPropertyDescriptor(target, propKey)  拦截Object.getOwnPropertyDescriptor(proxy,propKey),返回属性的描述对象

  • defineProperty(target, propKey, propDesc)  拦截Object.defineProperty(proxy,propKey,propDesc),Object.defineProperties(proxy,propDesc),返回boolean值

  • getPrototypeOf(target)  拦截Object.getPrototypeOf(proxy),返回对象

  • setPrototypeOf(target, proto)  拦截Object.setPrototypeOf(proxy, proto) ,返回boolean值

  • apply(target, object, args)  拦截Proxy实例作为函数调用的操作,比如proxy(...args),proxy.call(object,...args),proxy.apply(...).

  • construct(target, args)  拦截Proxy实例作为构造函数调用的操作,比如new proxy(...args).

  • isExtensible(target)  拦截Object.isExtensible(proxy),返回boolean值

  • preventExtensions(target)  拦截Object.preventExtensions(proxy),返回boolean值 

Proxy给了开发者拦截语言默认行为的权限,可以不改变原有对象或函数的情况下,轻松运用在很多场景。例如:统计函数调用次数,实现响应式数据观测(Vue3.0),实现不可变数据(Immutable)等等.

let obj = {};
        let func = function(){

        }

        let proxy = new Proxy(obj, {
            get(target, key, receiver){
                console.log(`get拦截 ${key}`);
            },
            set(target, key, value, receiver){
                console.log(`set拦截 ${key}`);
            },
            has(target, propKey){
                console.log(`has拦截`);
            },
            deleteProperty(target, propKey){
                console.log(`deleteProperty拦截`);
            },
            ownKeys(target){
                console.log('ownKeys拦截');
            },
            getOwnPropertyDescriptor(target, propKey){
                console.log('getOwnPropertyDescriptor拦截');
            },
            defineProperty(target, propKey, propDesc){
                console.log('defineProperty拦截');
            },
            getPrototypeOf(target){
                console.log('target拦截');
            },
            setPrototypeOf(target, proto){
                console.log('setPrototypeOf拦截');
            },
            apply(target, object, args){
                console.log('apply拦截');
            },
            construct(target, args){
                console.log('construct拦截');
            },
            isExtensible(target){
                console.log('isExtensible拦截');
            },
            preventExtensions(target){
                console.log('preventExtensions拦截');
            }

        });

tips:面试常问,Proxy和Object.defineProperty()有什么区别及优势?

5.Reflect

反射机制指的是程序在运行时能够获取自身的信息

 ES6提供了Reflect,把原先版本中很多语言层面的API,比如Object.defineProperty(), delete in

等几种放在Reflect的静态方法上。

将Object对象的一些明显属于语言内部的方法(如Object.defineProperty()),放到Reflect对象上。现阶段,某些对象上同时在Object和Reflect对象上部署,未来的新方法则只在Reflect对象上,也就是说,从Reflect对象上可以拿到语言内部的方法。

Reflect较之前方法使用的一些好处:

  • 修改某些Object方法的返回结果,让其更合理
    let target = {};
            let property = 'a';
    
            Object.defineProperty(target, property, {
                configurable: false,
                value: 1
            });
            
            // 旧写法
            // try {
            //     Object.defineProperty(target, property, {
            //         configurable: true
            //     });
            // } catch (error) {
            //     console.log('异常:', error);
            // }
    
            // 新写法
            Reflect.defineProperty(target, property,{ configurable: true }) ? '' : console.log('reflect失败');

  • 将命令式操作转变为函数调用,避免更多保留字占用
    // 老写法
    'assign' in Object
    
    // 新写法
    Reflect.has(Object, 'assign');

  • Reflect对象的方法与Proxy对象的方法一一对应,想要调用默认行为,直接在Reflect上调用方法,省去人工写默认行为的代码

 tips:关于Reflect的背景,使用场景,用途查了一些资料,但是和我上述这段描述相差无几。找不到更多的资料去描述,以下提供一些用于参考:

JS的Reflect学习和应用 - 知乎

JS 反射机制及 Reflect 详解 - Leophen - 博客园

Reflect - JavaScript | MDN

JS 中的 Reflect 和 Proxy - 掘金

6.Iterator

Iterator(迭代器)是一个对象. Iterator对象需要包含一个next方法,该方法返回一个包含两个属性(value表示当前结果,done表示是否继续迭代)的对象. ES6规定:如果数据结构的`Symbol.iterator`是一个返回iterator的对象的方法,是认为该数据结构是`可遍历的`.

tips:ES6提供的11个内置Symbol值,指向语言内部使用的方法. Symbol.iterator是其中之一,对象的Symbol.iterator属性,指向该对象的默认生成遍历器的方法

let it = makeIterator();

// 返回遍历器对象
function makeIterator(){
    var nextIndex = 0;
    return {
        // 闭包
        next: function (){
              return nextIndex < 5 ? 
              { value: nextIndex++, done: false } : { value: undefined, done: true };
              }
       };
}
        
        
let obj = {
    [Symbol.iterator]: makeIterator
};

// for...of循环
// for(let value of obj){
//   console.log('value:',value);
// }

ES6中以下场景默认调用Iterator接口(Symbol.iterator方法)

  • for...of循环
  • 数组解构
  • 扩展运算符
  • yield*
    let arr = [];
    arr[Symbol.iterator] = makeIterator;
    // yield
    function* f(){
         // 相当于把arr展开
         yield* arr
    }
    for(let value of f()){
         console.log(value);
    }
  • 其他隐式调用,如new Set([a, b]),Promise.all()等

ES6以下数据结构默认为可遍历对象,即默认部署了Symbol.iterator方法

  • Array
  • Map
  • Set
  • String
  • 函数的arguments对象
    function f1(){
       console.log('arguments:',arguments[Symbol.iterator]);
    }
    f1()
  • NodeList对象
    // divs  NodeList对象
    let divs = document.querySelectorAll('div');
    console.log(divs[Symbol.iterator]);

7.Generator 

8.async await  

async函数是Generator函数的语法糖

1.async

执行async函数相当于执行了一个自动运行的Generator函数,async函数的结果返回如果不是promise,会将其包装为一个promise返回

// 单独 async
// async function f(){
//     // 控制台执f()会发现返回一个包裹undefined的Promise
//     console.log(1);
// }
        
// f()返回Promise对象,后面可以接then()
// f().then(() => {
//     console.log(2);
// });

async function f(){
      console.log(1);
      return 'done';
      // 相当于返回Promise.resolve('done');
}

2.await

async函数中使用await关键字。await关键字后面一般会跟一个Promise实例,async函数执行过程中,每次遇到await关键字,会将控制权转回外部环境。 

  1. 如果await后面是Promise实例,则会等到该Promise实例被resolve后,才会把本次await到下次await之间的代码推到MircoTask(微任务)中等候执行,且await的返回值是该Promise实例resolve的值
  2. 如果await后面不是Promise实例,则会立即将本次await到下次await之间的代码推到mircoTask(微任务)中等候执行,且await的返回值就是await后面表达式的值
// await Promise实例
// async function f(){
//     let data = await new Promise((resolve, reject) => {
//         setTimeout(() => {
//             resolve('a');
//         }, 2000);
//     });
//     console.log(data);
// }

//await 非Promise实例
async function f(){
      let data = await 'a';
       console.log(data);
}

f();
console.log('end');

 

3.async函数的错误处理

若Promise被reject或抛出异常,await之后的代码不会被执行,那么就通过try...catch对await进行异常捕获

// 未捕获异常
        // async function f(){
        //     let data = await new Promise((resolve, reject) => {
        //         setTimeout(() => {
        //             reject('123');
        //         }, 2000);
        //     });

        //     // reject后,后续代码无法执行
        //     console.log('done');
        // }

        // 捕获异常
        async function f(){
            try {
                let data = await new Promise((resolve, reject) => {
                    // setTimeout(() => {
                    //     reject('123');
                    // }, 2000);
                    throw new Error('1234');
                });

                // reject后,后续代码无法执行
                console.log('done');
            } catch (error) {
                console.log('异常:', error);
            }
        }

tips:实际上,try...catch并不能完美处理async函数的异常抛出,那么如何优雅的处理async函数的异常抛出呢?

4.async函数处理并发异步任务

 因为async函数中的每一个await都是等到前面await resolve后才会执行,如果想并发执行,可以使用Promise.all.但此情况仅限于多个异步任务之间无数据关联

        // 不并发的情况
        // async function f(){
        //     let time1 = new Date();
        //     let data1 = await new Promise((resolve, reject) => {
        //         setTimeout(() => {
        //             resolve('123')
        //         }, 2000);
        //     });
        //     let data2 = await new Promise((resolve, reject) => {
        //         setTimeout(() => {
        //             resolve('1234')
        //         }, 3000);
        //     });
        //     // 123 1234 用时:5002  该用时处理无法接受
        //     console.log(data1, data2, '用时:' + (new Date() - time1));
        // }

        // 并发处理异步  Promise.all  但该情况限于两个异步任务无数据关联的情况下使用
        async function f(){
            let time1 = new Date();
            let [data1, data2] = await Promise.all([
                new Promise((resolve, reject) => {
                    setTimeout(() => {
                        resolve('123')
                    }, 2000);
                }),
                new Promise((resolve, reject) => {
                    setTimeout(() => {
                        resolve('1234')
                    }, 3000)
                })    
            ]);

            // 123 1234 用时:3000
            console.log(data1, data2, '用时:' + (new Date() - time1));
        }

 tips:当多个异步任务之间有数据关联时,如何处理?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值