Js高级API

Decorator装饰器

针对属性 / 方法的装饰器

// decorator 外部可以包装一个函数,函数可以带参数

    function Decorator (type) {
      /**
       * 这里是真正的decorator
       * @description: 装饰的对象的描述对象
       * @target:装饰的属性所述类的原型,不是实例后的类。如果装饰的是Animal的某个属性,这个target就是Animal.prototype
       * @name 装饰的属性的key
       */

      return function (target, name, desciptor) {
        // 因为babel的缘故 通过value并不能获取值,以此可以获取实例化的时候此属性的默认值
        let v = desciptor.initializer && desciptor.initializer.call(this)
        // 返回一个新的描述对象,或者直接修改desciptor也可以
        return {
          enumerable: true, //可以遍历
          configurable: true, //可以删除
          get: function () {
            return v
          },
          set: function (c) {
            v = c
          }
        }
      }
    }
    
    
    
    
    // 上面的不能和业界商用的Decorator混用

    function Check (type) {
      return function (target, name, desciptor) {
        let v = desciptor.initializer && desciptor.initializer.call(this)
        // 将属性名字以及需要的类型的对应关系记录到类的原型上
        if (!target.constructor._checkers_) {
          // 将这个隐藏属性定义成no enumerable,遍历的时候是取不到的
          Object.defineProperty(target.constructor, '_checkers_', {
            value: {},
            enumerable: false,
            writable: true,
            configurable: true
          })
        }
        target.constructor._checkers_[name] = {
          type: type
        }
        return desciptor
      }
    }
    // 装饰函数的第一个参数 target 是包装属性所属的类的原型(prototype)
    // 也就是把对应关系挂载到了开发定义的子类上。
vue中使用Decorator
  • ts开发一定对vue-property-decorator不会感到陌生,这个插件提供了许多装饰器

  • 在methods里面的方法上面使用装饰器,这时候装饰器的target对应的是methods。

  • 可以在生命周期钩子函数上面使用装饰器,这时候target对应的是整个组件对象。

  import {log,confirmation} from "./test"
  
  methods: {
    @log()
    name() {
      console.log("获取数据");
    },
    @confirmation('此操作将永久删除文件,是否继续?')
    deleteFile(data){
      //删除文件操作
    }
  },
  mounted () {
    this.name()
  }

test.js

 import {MessageBox}from "element-ui"
export function confirmation(message){
    return function(target,name,descriptor){
        let oldValue = descriptor.value
        descriptor.value = function(...args){
            MessageBox.confirm(message,'提示').then(oldValue.bind(this,...args)).catch(()=>{})
        }
        return descriptor
    }
}
export function log(){
    /**
     * @description: 
     * @param {*} target  对应methods
     * @param {*} name 对应属性方法的名称
     * @param {*} descriptor 对应属性方法的修饰符
     * @return {*}
     */      
    return function(target,name,descriptor){
      console.log(target,name,descriptor);
      // 获取实例化的时候此属性的默认值
      const fn = descriptor.value
      /* 重写 */
      descriptor.value = function(...rest){
        console.log(`调用${name}方法打印的`);
        fn.call(this,...rest)
      }
    }
  }

Iterator 迭代器

  • 目的是为不同的数据结构提供统一的数据访问机制 主要为for of 服务的 (当for of执行的时候,循环过程中会自动调用这个对象上的迭代器方法,依次执行迭代器对象的next方法,并把next返回结果赋值给for of的变量,从而得到具体的值

  • 迭代器对象,返回此对象的方法叫做迭代器方法 此对象有一个next方法 每次调用next方法都会返回一个结果值

  • 这个结果值是一个object 包含两个属性value和done

  • value表示具体的返回值 done是布尔类型的,表示集合是否遍历完成或者后续还有可用数据,没有可用返回true, 否则返回false

  • 内部会维护一个指针 用来指向当前集合的位置 每调用一次next方法 指针都会向后移动一个位置(可以想象成数组的索引)

    代码实现

  getInterator(list){
    var i = 0;
    return {
      next:function(){
        var done = (i>=list.length);
        var value = !done ? list[i++]:undefined
        return {
          done:done,
          value:value
        }
      }
    }
  }
  
  
  var it = this.getInterator(['a','b','c'])
    console.log(it.next());// {done: false, value: 'a'}
    console.log(it.next());//{done: false, value: 'b'}
    console.log(it.next());//{done: false, value: 'c'}
    console.log(it.next());//{done: true, value: undefined}
可迭代对象
  • Symbol.Iterator是一个表达式 返回Symbol的Iterator属性, 这是一个预定好的类型为Symbol的特殊值

  • ES6规定,只要在对象上部署了Iterator接口,具体实现为给对象添加Symbol.Iterator属性,此属性指向一个迭代器方法,这个迭代器会返回一个迭代器对象。

  • 而部署了这个属性,并且实现迭代器方法 返回的对象就是迭代器对象,此时这个对象就是可迭代的 可以被for for遍历

    实现一个可迭代对象

     getIterator(){
        let iteratorObj = {
          items:[100,200,300],
          [Symbol.iterator]: function(){
            var self = this
            var i =0
            return {
              next:function(){
                var done = (i>=self.items.length)
                var value = !done?self.items[i++]:undefined
                return {
                  done:done,
                  value:value
                }
              }
            }
          }
        }
        for(var item of iteratorObj){
          console.log(item); //100 200 300
        }
      }
      
      //上面的对象就是可迭代对象,可以被for of遍历
      
      this.getIterator()
    
    for of 中断

    如果for of 循环提前退出,则会自动调用return方法,需要注意的是return 方法必须有返回值,且返回值必须是一个object

     var arr = [100, 200, 300]
        arr[Symbol.iterator] = function () {
          var self = this
          var i = 0
          return {
            next: function () {
              var done = i >= self.length
              var value = !done ? self[i++] : undefined
              return {
                done: done,
                value: value
              }
            },
            return (){
              console.log('提前退出');
              return { //必须返回一个对象
                done:true
              }
            }
          }
        }
    
        for (var o of arr) {
          if(o == 200){
            break;
          }
          console.log(o) // 100   提前退出
        }
    

    除了for of 会调用对象的Iterator, 结构赋值也会

        var str = '123'
        let [a,b]=str
        console.log(a,b); // 1 2
    
        let map = new Map()
          map.set('q','1')
          map.set('a','2')
          map.set('b','3')
        let [c,d] = map
        console.log(c,d); //['q', '1'] ['a', '2']
    

    因为普通对象不是可迭代对象。

    自定义的可迭代对象进行解构赋值

     var interatorObj = {
          items: ['橙', '红', '白'],
          [Symbol.iterator]: function () {
            let i = 0
            let self = this
            return {
              next: function () {
                let done = i >= self.items.length
                let value = !done ? self.items[i++] : undefined
                return {
                  done: done,
                  value: value
                }
              }
            }
          }
        }
       let [a,b] = interatorObj
       console.log(a,b); //橙 红
    

    解构赋值的变量的值就是迭代器对象next方法的返回值 且是按顺序返回

    扩展运算符

    // 扩展运算符的执行(…)也会默认调用它的Symbol.iterator方法,可以将当前迭代对象转换为数组

    */\* 字符串 \*/*
    
      var str = "1234"
    
      console.log([...str]);*//【1,2,3,4】转换成数组*
    
    
    
      */\* map对象\*/*
    
      var map = new Map([[1,2],[3,4]])
    
      [...map]*//[[1,2],[3,4]]*
    
      */\* set对象 \*/*
    
      var set = new Set([1,2,3])
    
      [...set] *//[1,2,3]*
    

    使用普通对象是不可以转换为数组的

    var obj = {name:'zhang'}
    
    [...obj] *//报错*
    
作为数据源

作为一些数据的数据源 比如某些参数是数组的API方法,都会默认的调用自身的迭代器

例如 map set Array.from等

为了证明,先把一个数组的默认迭代器给覆盖掉

var arr= [100,200,300]
      arr[Symbol.iterator]=function(){
        var self = this
        var i = 0;
        return {
          next:function(){
            var done = (i>=self.length)
            var value = !done?self[i++]:undefined
            return {
              done:done,
              value:value + '前端'
            }
          }
        }
      }
     

用for of

 for(var o of arr){
        console.log(o);//100前端 200前端 300前端
      }

生成set对象

  var set = new Set(arr)

 set*//Set(3) {'100前端', '200前端', '300前端'}*

调用Array.from方法

  Array.from(arr) *//['100前端', '200前端', '300前端']*
*yield**关键字

​ *yield**后面跟的是一个可遍历的结构,执行时也会调用迭代器函数

 let foo = function*(){

​        *yield* 1;

​        *yield** [1,2,3];

​        *yield* 5;

​      }
判断对象是否可迭代

既然可迭代对象的规则必须在对象上部署Symbol.iterator属性,那么就可以依次来判断是否为可迭代对象,然后就知道是否能使用for of 取值了

  function isIterable(*object*){

​        *return* typeof *object*[Symbol.iterator] === 'function'

​      }

​      console.log(isIterable('asdd'));*//true*

​      console.log(isIterable([1,2,3]));*//true*

​      console.log(isIterable(new Map()));*//true*

​      console.log(isIterable(new Set()));*//true*

​      console.log(isIterable(new WeakMap()));*//false*

​      console.log(isIterable(new WeakSet()));*//false
Generate生成器*
 let obj = {

​      *[Symbol.iterator]("Symbol.iterator"){

​        *yield* 'hello';

​        *yield* 'world';

​      }

​    }

​    *for*(let x of obj){

​      console.log(x);

​    } *//hello world

Generator

Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同

Generator函数将javascript异步编程带入了一个全新的阶段

声明

与函数声明类似,不同的是function关键字与函数名之间有一个星号,以及函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是产出)

 function* foo(){
        yield 'result1'
        yield 'result2'
        yield 'result3'
      }
      const gen = foo()
      console.log(gen.next().value);
      console.log(gen.next().value);
      console.log(gen.next().value);

vue中使用

 textMy () {
      const gen = this.foo()
      console.log(gen.next().value);
      console.log(gen.next().value);
      console.log(gen.next().value);
    },
    * foo(){
        yield 'result1'
        yield 'result2'
        yield 'result3'
      }

​ 执行Generator会返回一个对象,而不是像普通函数返回return 后面的值

​ 调用指针next方法,会从函数的头部或上一次停下来的位置开始执行,直到遇到下一个yield表达式或者return语句暂停,也就是执行yeild这一行

​ value就是执行yield后面的值 done表示函数是否执行完毕

console.log(g.next()); *//{value: 7, done: false}*

​      console.log(g1.next()); *//{value: 2, done: false}*

​      console.log(g.next());*//{value: undefined, done: true}

​ 因为最后一行 return y 被执行完成 所以done为true

​ 调用Generator函数后,该函数并不执行,返回的也不是函数运行结果 而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object).必须调用遍历器对象的next方法

​ 使得指针移向下一个状态

      function* fetch(){

​        *yield* ajax('aaa')

​        *yield* ajax('bbb')

​        *yield* ajax('ccc')

​      }

​      let gen = fetch()

​      let res1 = gen.next() *//{value:'aaa',done:false}*

​      let res2 = gen.next() *//{value:'bbb',done:false}*

​      let res3 = gen.next() *//{value:'ccc',done:false}*

​      let res4 = gen.next() *//{value:undefined,done:true} //done为true表示执行结果

由于Generator函数返回的是遍历器对象 只有调用next方法才会遍历下一个内部状态 所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志

​ 遍历器对象的next方法运行逻辑:

​ 1 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的表达式的值,作为返回对象的value属性值

​ 2. 如果下次调用next方法时,再继续往下执行,直到遇到下一个yield表达式

​ 3 如果没有再遇到新的yiled表达式 就一直运行到函数结束

​ 4 如果该函数没有return 语句 则返回对象的value属性值就是underfined

​ yiled表达式本身没有返回值 或者说总返回underfined next 方法可以带一个参数,该参数就会被当做上一个yield表达式的返回值

​ 一个函数可以有多个yield,但是只能有一个return

Proxy

​ - Proxy用于创建对象的代理,用于监听对象的相关操作。代理对象可以监听我们对原对象的操作

​ - 在实例化Proxy对象时,第二个参数传入的是捕获器集合,我们在其对象内定义一个get捕获器,用于监听获取对象值的操作

​ - objProxy 对象的拦截器中set捕获器,用于监听对象的某个属性被设置时触发

// 定义一个普通的对象
        const obj = {
          name:'_isLand'
        }
        // 代理obj这个对象  并传入get捕获器
        const objProxy = new Proxy(obj,{
          // get捕获器
          get:function(target,key){
            console.log(`触发对象捕获${key}`);
            return target[key]
          },
          set:function(target,key,val){
            console.log(`捕获到对象设置${key},新值为${val}`);
            return target[key] = val
            
          }
        })
        objProxy.name = 'liming'
        // 通过代理对象操作obj对象
        console.log(objProxy.name);//触发对象捕获name  _isLand
        // 捕获到对象获取name属性值的操作

如果不想这个属性被设定成这个值,你可以抛出异常告诉开发者,这个值不能设定

 set:function(target,key,val){
          if(key==='age' && typeof val ==='number'){
            return target[key] = val
          }else{
            throw new TypeError('该属性的值必须是number类型')
          }
        }

监听对象是否调用了getPrototypeof操作,使用getPrototypeOf捕获器

 getPrototypeOf:function(){
          console.log('监听到对象的protptypeOf操作');
        }

Proxy一共有十三个捕获器,用于我们对 对象或者函数的方法调用的监听

请添加图片描述

this指向的问题

​ Proxy对象可以对我们的目标对象进行访问,但没有做任何拦截时,也不能保证与目标行为一致,因为目标对象 内部的this会自动改变为Proxy代理对象

      let obj = {
        name:'lili',
        foo:function(){
          return this === objProxy
        }
      }
      let objProxy = new Proxy(obj,{})
      console.log(obj.foo()); //false
      console.log(objProxy.foo());  //true
对象监听

​ 在vue2中的watchApi是使用的Es5的Object.defineProperty(对象属性描述符)对对象监听,将一个对象进行遍历,

​ 并设定setter。getter方法进行监听和拦截

const obj = {
        name: 'liki',
        age: 12
      }
      Object.keys(obj).forEach(key => {
        let val = obj[key]
        Object.defineProperty(obj, key, {
          get: function () {
            console.log('触发get')
            return val
          },
          set: function (newVal) {
            console.log('触发set')
            val = newVal
          }
        })
      })
      obj.age = 100
      console.log(obj.name);
  • Object.defineProperty的设计初衷并不是为了去拦截拦截一个对象中的属性,且他也实现不了更加丰富的操作,例如删除、添加属性等操作

​ - 所以在Es6中新增了Proxy,对象,用于监听Object,Function的操作。

​ - 在Vue3框架中的响应式原理也是用到了Proxy对象进行对属性的监听操作

proxy是一个代理对象 他可以代理我们对原目标的操作。相比Object.defineProperty方法 Proxy监听的事件更加方便

Reflect隐射对象

Reflect是一个对象,翻译过来就是反射的意思,它提供了很多操作Javascript对象的方法,是为弥补Object中对象的一些缺陷。且所有的属性和方法都是静态的

最初,js的一些内部方法都是放在Object这个对象上的,比如getPrototye,defineProperty等API,

in、delete操作符都放在了Object对象上。但是Object作为一个构造函数,这些方法都放在Object上并不合适,

所以ES6之后的内部新方法都在Reflect对象中。Refelect不是构造函数

使用Reflect对象操作Object对象

Reflect对象让我们操作Object对象不再是通过点语法 而是变成了函数行为

所以 获取对象属性可以使用Reflect.get方法,将对象的属性赋值使用Reflect.set方法

    const obj = {
         name:'lili',
         age:12
       }

       /* 获取对象的属性值 */
      console.log(obj.name);
      console.log(Reflect.get(obj,'name'));
     
    //  对对象的属性赋值
      obj.name = 'lala'
      Reflect.set(obj,'name','lala')
      console.log(Reflect.get(obj,'name'));
      // 判断一个对象中是否有该属性
      console.log('name' in obj);
      console.log(Reflect.has(obj,'name'));

Reflect中的方法

请添加图片描述

在返回值方面Reflect对象中的方法设计的更加合理 比如defineProperty方法,如果没有将属性设置

成功,在Reflect中会返回boolean值,而Object对象中如果没有定义成功则会抛出TypeError

Reflect搭配Proxy

​ Reflect对象中的方法和Proxy对象中的方法对应的,Proxy对象中的方法也能在Reflect对象中调用

​ 通常我们将Reflect对象搭配Proxy一起使用

在下面Proxy对象中get,set捕获器多了一个recerive参数 这是这两个捕获器特有的 代表的是当前的代理对象

​ 当Proxy和Reflect搭配使用时 Proxy对象会拦截对应的操作 后者完成对应的操作,如果传入receiver,那么Reflect.get属性

​ 会触发Proxy.definProperty捕获器。如果没有传入receive参数 则不会触发defineProperty捕获器

     let obj = {
        name:'lili'
      }
      const objProxy = new Proxy(obj,{
        get:function(target,key,receiver){
          return Reflect.get(target,key,receiver)
        },
        set:function(target,key,val,receiver){
          return Reflect.set(target,key,val,receiver)
        },
        defineProperty:function(target,key,attr){
          console.log('defineProperty',target,key,attr); //defineProperty {name: 'lili'} name {value: 'ppp'}
          return Reflect.defineProperty(target,key,attr)
        }
      })
      objProxy.name = 'ppp'
      console.log(objProxy.name);
      //传入在我们获取代理对象中的name属性时,当Reflect有receive

总结

​ - Reflect 对象中集合了javascript内部方法

​ - 操作Object对象的方式变成了函数行为

​ - Reflect 对象中的方法返回结果更加合理

ES6中的class

​ 在ES6之前,js语法是不支持类的,导致面向对象编程无法直接使用,而是通过function来实现模拟类,随着js的更新,ES6中出现了Class,

​ 用于定义类

 class Animal {}

const Animal = class {}
类的构造函数

每一个类都可以有一个自己的构造函数,这个名称是固定的construtor,当我们通过new 调用一个类时,这个类就会调用自己的constructor方法(构造函数)

​ - 它用于创建对象时给类传递一些参数

​ - 每一个类只能有一个构造函数

​ - 通过 new 调用一个类时 会调用构造函数 执行如下操作

​ 1 在内存中开辟一块新的空间用于创建新的对象

​ 2 这个对象内部的_proto_属性会被赋值为该类的prototype属性

​ 3 构造函数内部的this,指向创建出来的新对象

​ 4 执行构造函数内部的代码

​ 5 如果函数没有返回值 则返回this

 class Animal {
          constructor(name){
            this.name = name
          }
        }
        var a = new Animal("ABC")
        console.log(a); //Animal {name: 'ABC'}
       

class 中定义的constructor,这个就是构造方法

而this代表的是实例对象

这个class 可以看作是构造函数的另一种写法 因为它和它的构造函数的相等的,即是 类本身指向构造函数

console.log(Animal === Animal.prototype.constructor); *//true*

所以其实类上的所有方法都会放到prototype属性上

类中的属性

实例属性

​ 实例的属性必须定义在类的方法中

class Animal {
      constructor(name,height,weight){
        this.name = name
        this.height = height
        this.weight = weight
      }
    }

静态属性

​ 当我们把一个属性赋值给类本身,而不是赋值给它的prototype,这样子的属性被称之为静态属性(static)

​ 静态属性直接通过类来访问 无需在实例中访问

class Foo{
      static name = "liLI"
    }
      console.log(Foo.name);

私有属性

​ 私有属性只能在类中读取,写入,不能通过外部引用私有字段

   class Person{
        #age;
        constructor(age,name){
          this.#age = age
          this.name = name
        }
      }
      var a = new Person(17,'lili')
      console.log(a); //Person {name: 'lili'}
      console.log(a.name);//lili
      console.log(a.age);//undefined
      console.log(a.#age);// Private field '#age' must be declared in an enclosing class
    

console.log(Object.getOwnPropertyDescriptors(a));

通过getOwnPropertyDescriptors获取属性同样获取不到

 {
      name: {
        value: '_island',
        writable: true,
        enumerable: true,
        configurable: true
      }
      }

在ES6之前,我们定义类中的方法是类中的原型上定义的 防止类中的方法重复在多个对象中

 function Animal{}

​      Animal.prototype.eating = function(){

​        console.log(this.name + 'eating');

​      }

在Es6中定义类中的方法更简洁 直接在类中定义即可 这样的写法优雅可读性也强

  class Animal{

      eating(){

​         console.log(this.name + 'eating');

​       }

​     }
静态方法

静态方法是与静态属性是一样的 在方法前面加上stati关键字声明,之后调用这个方法时不需要通过类的实例来调用,可以直接通过类名来调用它

 class Animal{

​        static creatName(*name*){

​          *return* *name*

​        }

​      }

​      var a2 = Animal.creatName('lll')

​      console.log(a2); *//lll
私有方法

在面向对象中 私有方法是一个常见需求 ES6中没有提供,可以通过某个方法实现它

   class Foo{

​        _getBoodType(){

​          *return* '0'

​        }

​      }

需要注意的是,通过下划线开头通常我们会局限它是一个私有方法 但是在类的外部还是可以正常调用到这个方法的

类的继承

extends关键字用于扩展子类,创建一个类作为另一个类的子类

​ 它会将父类中的属性和方法一起继承到子类的,减少子类中重复的业务代码

​ 这比之前在ES5中修改原型链实现继承的方法可读性要强的多,而且写法更简洁

       extends的使用

​      class Animal{}

​      dog继承Animal类

​      class dog extends Animal{}

继承类的属性和方法

  • 定义一个dog类 通过extends关键字继承Animal类的属性和方法
  • 在子类的construtor方法中 使用super关键字,在子类中它上市必须存在的 否则新建实例会抛出异常。
  • 这是因为子类的this对象是继承父类的this对象 如果不调用super方法 子类就得不到this对象

class Animal {

​        constructor (*name*) {

​          this.name = *name*

​        }

​        eating () {

​          console.log(this.name + 'eating')

​        }

​      }

​      class dog extends Animal{

​        constructor(*name*,*legs*){

​          super(*name*)

​          this.legs = *legs*

​        }

​        speaking(){

​          console.log(this.name + "speaking");

​        }

​      }

​      var wang = new dog('tom',4)

​      wang.speaking()

​      wang.eating()

​      console.log(wang.name);
Super

super关键字用于访问调用一个对象的父对象上的函数

​ super指的是超级,顶级,父类的意思

​ 在子类的构造函数中使用this或者返回默认对象之前,必须先使用super调用父类的构造函数

​ 子类的construtor方法先调用了super方法,它代表了父类的构造函数,也就是我们把参数传递进去之后,其实它调用了父类的构造函数

 class Animal{

​        constructor(*name*)

​      }

​      class dog{

​        constructor(*name*,*type*,*weight*){}

​        super(*name*)

​        this.type = type

​        this.weight = weight

​      }

使用super调用父类的方法

				 class Animal{

​        constructor(*name*){

​          this.name = name

​        }

​        eating(){

​          console.log(this.name + 'eating');

​        }

​      }



​      *// dog继承Animal类*

​      class dog extends Animal{

​        constructor(*name*,*lengs*){

​          super(*name*)

​          this.lengs = *lengs*

​        }

​        speaking(){

        super.eating()

​          console.log(this.name + 'speaking');

​        }

​      }

​        var a = new dog('旺财',4)

​        a.speaking() *//旺财eating  旺财speaking
Getter和Setter

在类内部可以使用get和set关键字,对应某个属性设置存值和取值函数,拦截属性的存取行为

 class Animal{

​        constructor(){

​          this._age = 3

​        }

​        get age(){

​          console.log('get');

​          *return* this._age

​        }

​        set age(*val*){

​          console.log('set');

​          this._age = val

​        }

​      }

​      var a = new Animal()

​      console.log(a.age);

​      a.age = 4

​      console.log(a.age);
关于class扩展

严格模式

在类和模块的内部 默认是严格模式 所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用

name属性

Es6中的类只是Es5构造函数的一层包装,所以函数的许多属性都被class继承了,包括name属性

 class Animal{}

console.log(Animal.name);

变量提升

class不存在变量提升,这与Es5中实现类是不同的, function关键字会存在变量提升

   new Foo() * ReferenceError*

   class Foo{}

在Es6之后,在定义类以及它内部的属性方法,还有继承操作的语法变得很简洁易懂

class 是一个语法糖 其内部还是通过Es5中的语法实现的。且有些浏览器不支持class语法 我们可以通过babel来进行转换

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值