前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >干货 | ES6 系列之我们来聊聊装饰器

干货 | ES6 系列之我们来聊聊装饰器

作者头像
腾讯NEXT学位
发布于 2020-02-11 01:40:33
发布于 2020-02-11 01:40:33
64100
代码可运行
举报
文章被收录于专栏:腾讯NEXT学位腾讯NEXT学位
运行总次数:0
代码可运行

       点击上方“腾讯NEXT学院”关注我们

Decorator

装饰器主要用于:

1. 装饰类

2. 装饰方法或属性

1 .装饰类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@annotationclass MyClass { }
function annotation(target) {   target.annotated = true;}

2. 装饰方法或属性

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyClass {  @readonly  method() { }}
function readonly(target, name, descriptor) {  descriptor.writable = false;  return descriptor;}

Babel

安装编译

我们可以在 Babel 官网的 Try it out,查看 Babel 编译后的代码。

不过我们也可以选择本地编译:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
npm init
npm install --save-dev @babel/core @babel/cli
npm install --save-dev @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties

新建 .babelrc 文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{  "plugins": [    ["@babel/plugin-proposal-decorators", { "legacy": true }],    ["@babel/plugin-proposal-class-properties", {"loose": true}]  ]}

再编译指定的文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
babel decorator.js --out-file decorator-compiled.js

装饰类的编译

编译前:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@annotationclass MyClass { }
function annotation(target) {   target.annotated = true;}

编译后:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var _class;
let MyClass = annotation(_class = class MyClass {}) || _class;
function annotation(target) {  target.annotated = true;}

我们可以看到对于类的装饰,其原理就是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@decoratorclass A {}
// 等同于
class A {}A = decorator(A) || A;

装饰方法的编译

编译前:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyClass {  @unenumerable  @readonly  method() { }}
function readonly(target, name, descriptor) {  descriptor.writable = false;  return descriptor;}
function unenumerable(target, name, descriptor) {  descriptor.enumerable = false;  return descriptor;}

编译后:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var _class;
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context ) {    /**     * 第一部分     * 拷贝属性     */    var desc = {};    Object["ke" + "ys"](descriptor).forEach(function(key) {        desc[key] = descriptor[key];    });    desc.enumerable = !!desc.enumerable;    desc.configurable = !!desc.configurable;
    if ("value" in desc || desc.initializer) {        desc.writable = true;    }
    /**     * 第二部分     * 应用多个 decorators     */    desc = decorators        .slice()        .reverse()        .reduce(function(desc, decorator) {            return decorator(target, property, desc) || desc;        }, desc);
    /**     * 第三部分     * 设置要 decorators 的属性     */    if (context && desc.initializer !== void 0) {        desc.value = desc.initializer ? desc.initializer.call(context) : void 0;        desc.initializer = undefined;    }
    if (desc.initializer === void 0) {        Object["define" + "Property"](target, property, desc);        desc = null;    }
    return desc;}
let MyClass = ((_class = class MyClass {    method() {}}),_applyDecoratedDescriptor(    _class.prototype,    "method",    [readonly],    Object.getOwnPropertyDescriptor(_class.prototype, "method"),    _class.prototype),_class);
function readonly(target, name, descriptor) {    descriptor.writable = false;    return descriptor;}

装饰方法的编译源码解析

我们可以看到 Babel 构建了一个 _applyDecoratedDescriptor 函数,用于给方法装饰。

Object.getOwnPropertyDescriptor()

在传入参数的时候,我们使用了一个Object.getOwnPropertyDescriptor() 方法,我们来看下这个方法:

Object.getOwnPropertyDescriptor() 方法返回指定对象上的一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)

顺便注意这是一个 ES5 的方法。

举个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const foo = { value: 1 };const bar = Object.getOwnPropertyDescriptor(foo, "value");// bar {//   value: 1,//   writable: true//   enumerable: true,//   configurable: true,// }
const foo = { get value() { return 1; } };const bar = Object.getOwnPropertyDescriptor(foo, "value");// bar {//   get: /*the getter function*/,//   set: undefined//   enumerable: true,//   configurable: true,// }

第一部分源码解析

在 _applyDecoratedDescriptor 函数内部,我们首先将 Object.getOwnPropertyDescriptor() 返回的属性描述符对象做了一份拷贝:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 拷贝一份 descriptorvar desc = {};Object["ke" + "ys"](descriptor).forEach(function(key) {    desc[key] = descriptor[key];});desc.enumerable = !!desc.enumerable;desc.configurable = !!desc.configurable;
// 如果没有 value 属性或者没有 initializer 属性,表明是 getter 和 setterif ("value" in desc || desc.initializer) {    desc.writable = true;}

那么 initializer 属性是什么呢?Object.getOwnPropertyDescriptor() 返回的对象并不具有这个属性呀,确实,这是 Babel 的 Class 为了与 decorator 配合而产生的一个属性,比如说对于下面这种代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyClass {  @readonly  born = Date.now();}
function readonly(target, name, descriptor) {  descriptor.writable = false;  return descriptor;}
var foo = new MyClass();console.log(foo.born);

Babel 就会编译为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// ...(_descriptor = _applyDecoratedDescriptor(_class.prototype, "born", [readonly], {    configurable: true,    enumerable: true,    writable: true,    initializer: function() {        return Date.now();    }}))// ...

此时传入 _applyDecoratedDescriptor 函数的 descriptor 就具有 initializer 属性。

第二部分源码解析

接下是应用多个 decorators:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** * 第二部分 * @type {[type]} */desc = decorators    .slice()    .reverse()    .reduce(function(desc, decorator) {        return decorator(target, property, desc) || desc;    }, desc);

对于一个方法应用了多个 decorator,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyClass {  @unenumerable  @readonly  method() { }}

Babel 会编译为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
_applyDecoratedDescriptor(    _class.prototype,    "method",    [unenumerable, readonly],    Object.getOwnPropertyDescriptor(_class.prototype, "method"),    _class.prototype)

在第二部分的源码中,执行了 reverse() 和 reduce() 操作,由此我们也可以发现,如果同一个方法有多个装饰器,会由内向外执行。

第三部分源码解析

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** * 第三部分 * 设置要 decorators 的属性 */if (context && desc.initializer !== void 0) {    desc.value = desc.initializer ? desc.initializer.call(context) : void 0;    desc.initializer = undefined;}
if (desc.initializer === void 0) {    Object["define" + "Property"](target, property, desc);    desc = null;}
return desc;

如果 desc 有 initializer 属性,意味着当装饰的是类的属性时,会将 value 的值设置为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
desc.initializer.call(context)

而 context 的值为 _class.prototype,之所以要 call(context),这也很好理解,因为有可能

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyClass {  @readonly  value = this.getNum() + 1;
  getNum() {    return 1;  }}

最后无论是装饰方法还是属性,都会执行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Object["define" + "Property"](target, property, desc);

由此可见,装饰方法本质上还是使用 Object.defineProperty() 来实现的。

应用

1.log

为一个方法添加 log 函数,检查输入的参数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Math {  @log  add(a, b) {    return a + b;  }}
function log(target, name, descriptor) {  var oldValue = descriptor.value;
  descriptor.value = function(...args) {    console.log(`Calling ${name} with`, args);    return oldValue.apply(this, args);  };
  return descriptor;}
const math = new Math();
// Calling add with [2, 4]math.add(2, 4);

再完善点:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let log = (type) => {  return (target, name, descriptor) => {    const method = descriptor.value;    descriptor.value =  (...args) => {      console.info(`(${type}) 正在执行: ${name}(${args}) = ?`);      let ret;      try {        ret = method.apply(target, args);        console.info(`(${type}) 成功 : ${name}(${args}) => ${ret}`);      } catch (error) {        console.error(`(${type}) 失败: ${name}(${args}) => ${error}`);      }      return ret;    }  }};

2.autobind

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Person {  @autobind  getPerson() {    return this;  }}
let person = new Person();let { getPerson } = person;
getPerson() === person;// true

我们很容易想到的一个场景是 React 绑定事件的时候:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Toggle extends React.Component {
  @autobind  handleClick() {      console.log(this)  }
  render() {    return (      <button onClick={this.handleClick}>        button      </button>    );  }}

我们来写这样一个 autobind 函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const { defineProperty, getPrototypeOf} = Object;
function bind(fn, context) {  if (fn.bind) {    return fn.bind(context);  } else {    return function __autobind__() {      return fn.apply(context, arguments);    };  }}
function createDefaultSetter(key) {  return function set(newValue) {    Object.defineProperty(this, key, {      configurable: true,      writable: true,      enumerable: true,      value: newValue    });
    return newValue;  };}
function autobind(target, key, { value: fn, configurable, enumerable }) {  if (typeof fn !== 'function') {    throw new SyntaxError(`@autobind can only be used on functions, not: ${fn}`);  }
  const { constructor } = target;
  return {    configurable,    enumerable,
    get() {
      /**       * 使用这种方式相当于替换了这个函数,所以当比如       * Class.prototype.hasOwnProperty(key) 的时候,为了正确返回       * 所以这里做了 this 的判断       */      if (this === target) {        return fn;      }
      const boundFn = bind(fn, this);
      defineProperty(this, key, {        configurable: true,        writable: true,        enumerable: false,        value: boundFn      });
      return boundFn;    },    set: createDefaultSetter(key)  };}

3.debounce

有的时候,我们需要对执行的方法进行防抖处理:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Toggle extends React.Component {
  @debounce(500, true)  handleClick() {    console.log('toggle')  }
  render() {    return (      <button onClick={this.handleClick}>        button      </button>    );  }}

我们来实现一下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function _debounce(func, wait, immediate) {
    var timeout;
    return function () {        var context = this;        var args = arguments;
        if (timeout) clearTimeout(timeout);        if (immediate) {            var callNow = !timeout;            timeout = setTimeout(function(){                timeout = null;            }, wait)            if (callNow) func.apply(context, args)        }        else {            timeout = setTimeout(function(){                func.apply(context, args)            }, wait);        }    }}
function debounce(wait, immediate) {  return function handleDescriptor(target, key, descriptor) {    const callback = descriptor.value;
    if (typeof callback !== 'function') {      throw new SyntaxError('Only functions can be debounced');    }
    var fn = _debounce(callback, wait, immediate)
    return {      ...descriptor,      value() {        fn()      }    };  }}

4.time

用于统计方法执行的时间:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function time(prefix) {  let count = 0;  return function handleDescriptor(target, key, descriptor) {
    const fn = descriptor.value;
    if (prefix == null) {      prefix = `${target.constructor.name}.${key}`;    }
    if (typeof fn !== 'function') {      throw new SyntaxError(`@time can only be used on functions, not: ${fn}`);    }
    return {      ...descriptor,      value() {        const label = `${prefix}-${count}`;        count++;        console.time(label);
        try {          return fn.apply(this, arguments);        } finally {          console.timeEnd(label);        }      }    }  }}

5.mixin

用于将对象的方法混入 Class 中:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const SingerMixin = {  sing(sound) {    alert(sound);  }};
const FlyMixin = {  // All types of property descriptors are supported  get speed() {},  fly() {},  land() {}};
@mixin(SingerMixin, FlyMixin)class Bird {  singMatingCall() {    this.sing('tweet tweet');  }}
var bird = new Bird();bird.singMatingCall();// alerts "tweet tweet"

mixin 的一个简单实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function mixin(...mixins) {  return target => {    if (!mixins.length) {      throw new SyntaxError(`@mixin() class ${target.name} requires at least one mixin as an argument`);    }
    for (let i = 0, l = mixins.length; i < l; i++) {      const descs = Object.getOwnPropertyDescriptors(mixins[i]);      const keys = Object.getOwnPropertyNames(descs);
      for (let j = 0, k = keys.length; j < k; j++) {        const key = keys[j];
        if (!target.prototype.hasOwnProperty(key)) {          Object.defineProperty(target.prototype, key, descs[key]);        }      }    }  };}

6.redux

实际开发中,React 与 Redux 库结合使用时,常常需要写成下面这样。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);

有了装饰器,就可以改写上面的代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@connect(mapStateToProps, mapDispatchToProps)export default class MyReactComponent extends React.Component {};

相对来说,后一种写法看上去更容易理解。

7.注意

以上我们都是用于修饰类方法,我们获取值的方式为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const method = descriptor.value;

但是如果我们修饰的是类的实例属性,因为 Babel 的缘故,通过 value 属性并不能获取值,我们可以写成:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const value = descriptor.initializer && descriptor.initializer();

参考

1.  ECMAScript 6 入门

2. core-decorators

3. ES7 Decorator 装饰者模式

4. JS 装饰器(Decorator)场景实战

原文作者:冴羽

原文链接:https://zhuanlan.zhihu.com/p/49843870

扫描二维码

关注我们吧

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-02-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯NEXT学院 微信公众号,前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
JS 装饰器解析
随着 ES6 和 TypeScript 中类的引入,在某些场景需要在不改变原有类和类属性的基础上扩展些功能,这也是装饰器出现的原因。 装饰器简介 作为一种可以动态增删功能模块的模式(比如 redux 的中间件机制),装饰器同样具有很强的动态灵活性,只需在类或类属性之前加上 @方法名 就完成了相应的类或类方法功能的变化。 不过装饰器模式仍处于第 2 阶段提案中,使用它之前需要使用 babel 模块 transform-decorators-legacy 编译成 ES5 或 ES6。 在 TypeScrip
牧云云
2018/04/28
3K0
JS 装饰器解析
一文读懂 @Decorator 装饰器——理解 VS Code 源码的基础
作者:easonruan,腾讯 CSIG 前端开发工程师 1. 装饰器的样子 我们先来看看 Decorator 装饰器长什么样子,大家可能没在项目中用过 Decorator 装饰器,但多多少少会看过下面装饰器的写法: /* Nest.Js cats.controller.ts */ import { Controller, Get } from '@nestjs/common'; @Controller('cats') export class CatsController {   @Get()  
腾讯技术工程官方号
2021/08/09
1.2K0
Decorator 从原理到实践
ES6 已经不必在过多介绍,在 ES6 之前,装饰器可能并没有那么重要,因为你只需要加一层 wrapper 就好了,但是现在,由于语法糖 class 的出现,当我们想要去在多个类之间共享或者扩展一些方法的时候,代码会变得错综复杂,难以维护,而这,也正式我们 Decorator 的用武之地。
Nealyang
2019/09/29
5610
Decorator 从原理到实践
2020的最后一天,不妨了解下装饰器
举一个非常常见的需求。假设我们有一个类Network,它有一个异步getList方法
ACK
2021/01/05
1.1K0
【翻译】ECMAScript装饰器的简单指南
简要介绍JavaScript中的“装饰器”的提案的一些基础示例以及ECMAScript相关的内容
腾讯IVWEB团队
2020/06/28
7310
用故事解读 MobX源码(四) 装饰器 和 Enhancer
按照步骤,这篇文章应该写 观察值(Observable)的,不过在撰写的过程中发现,如果不先搞明白装饰器和 Enhancer(对这个单词陌生的,先不要着急,继续往下看) ,直接去解释观察值(Observable)会很费劲。因为在 MobX 中是使用装饰器设计模式实现观察值的,所以说要先掌握装饰器,才能进一步去理解观察值。
JSCON简时空
2020/03/31
9490
用故事解读 MobX源码(四) 装饰器 和 Enhancer
一文读懂 JS 装饰器,这是一个会打扮的装饰器
装饰器是最新的 ECMA 中的一个提案,是一种与类(class)相关的语法,用来注释或修改类和类方法。装饰器在 Python 和 Java 等语言中也被大量使用。装饰器是实现 AOP(面向切面)编程的一种重要方式。
用户1462769
2020/03/30
1.4K0
一文读懂 JS 装饰器,这是一个会打扮的装饰器
Decorator 装饰器
大家在前端开发过程中有遇到过 @ + 方法名 这种写法吗?当我第一次看到的时候,直接懵了,这是什么东东……
政采云前端团队
2022/03/29
4280
Decorator 装饰器
Javascript 装饰器极速指南
Decorators 是ES7中添加的JavaScript新特性。熟悉Typescript的同学应该更早的接触到这个特性,TypeScript早些时候已经支持Decorators的使用,而且提供了ES5的支持。本文会对Decorators做详细的讲解,相信你会体验到它给编程带来便利和优雅。 我在专职做前端开发之前, 是一名专业的.NET程序员,对.NET中的“特性”使用非常熟悉。在类、方法或者属性上写上一个中括号,中括号里面初始化一个特性,就会对类,方法或者属性的行为产生影响。这在AOP编程,以及ORM
用户1631416
2018/04/12
9590
Javascript 装饰器极速指南
Javascript装饰器的妙用
最近新开了一个Node项目,采用TypeScript来开发,在数据库及路由管理方面用了不少的装饰器,发觉这的确是一个好东西。 装饰器是一个还处于草案中的特性,目前木有直接支持该语法的环境,但是可以通过 babel 之类的进行转换为旧语法来实现效果,所以在TypeScript中,可以放心的使用@Decorator。
贾顺名
2019/12/09
1.1K0
浅谈JS中的装饰器模式
装饰器(Decorator)是ES7中的一个新语法,使用可参考阮一峰的文章。正如其字面意思而言,它可以对类、方法、属性进行修饰,从而进行一些相关功能定制。它的写法与Java的注解(Annotation)非常相似,但是功能还是有很大区别。
IMWeb前端团队
2019/12/03
1.3K0
浅谈JS中的装饰器模式
在JavaScript中使用装饰器
Decorator装饰器是ES7的时候提案的特性,目前处于Stage 3候选阶段(2022年10月)。
luciozhang
2023/04/22
6150
在JavaScript中使用装饰器
大型前端如何分析用户行为和追踪函数调用链
在很多时候我们项目越来越大的时候,我们希望去监听局部某些类方法的性能,这个时候我们既不想影响源代码的功能,但又想借助某些方案去窥探类方法内部的运行效能,此时我们就可以考虑使用装饰器对类方法性能进行监听。装饰器相信大家都不陌生了,虽然在 Javasript 里面它仍处于提议阶段,但是我们已经可以 TypeScript 里面运用这个特性,也可以借助 babel 的语法转换在 Javasript 里面使用。
wscats
2020/06/18
2K0
大型前端如何分析用户行为和追踪函数调用链
ES6装饰器Decorator的实现原理
NOTE Decorators are an experimental feature that may change in future releases.
伯爵
2019/10/11
2.1K0
ES6装饰器Decorator的实现原理
设计模式(11)[JS版]-JavaScript中的注解之装饰器模式
装饰器模式模式动态地扩展了(装饰)一个对象的行为,同时又不改变其结构。在运行时添加新的行为的能力是由一个装饰器对象来完成的,它 "包裹 "了原始对象,用来提供额外的功能。多个装饰器可以添加或覆盖原始对象的功能。装饰器模式属于结构型模式。和适配器模式不同的是,适配器模式是原有的对象不能用了,而装饰器模式是原来的对象还能用,在不改变原有对象结构和功能的前提下,为对象添加新功能。
AlbertYang
2020/09/08
9040
设计模式(11)[JS版]-JavaScript中的注解之装饰器模式
JS 完美的 AOP 编程
看到 decorator这个词的时候,让我回想起了python中的decorator.而,当我看到 decorator中的 @的时候, 我tm确定,这尼玛不就是python吗? 但, too young too naive. es6中的decorator和python很相似,但却又非常的不一样.因为,在js中,decorator是不能用来装饰函数的.(因为有函数提升) so, decorator在js中是用来干嘛的呢? 神马是decorator decorator是以一种近乎trick的方式,让你写更少的代
villainhr
2018/07/03
9690
初探ES7 Decorator
装饰器是 ES7 新有的特性,它允许我们使用简洁的方式,为已有的类、类的方法、类的属性 添加有趣的修饰。 可使用如下:
用户1394570
2018/08/08
4410
JavaScript设计模式之装饰器模式
手机壳就是装饰器,没有它手机也能正常使用,原有的功能不变,手机壳可以减轻手机滑落的损耗。
FinGet
2019/06/28
4750
一文读懂@Decorator装饰器——理解VS Code源码的基础(上)
导语 | 本人在读VS Code源码的时候,发现其用了大量的@Decorator装饰器语法,由于对装饰器的语法比较陌生,它成为了我理解VS Code的拦路虎。其实不止VS Code,Angular、Node.js框架Nest.js、TypeORM、Mobx(5) 和Theia等都深度用到了装饰器语法,为了读懂各大优秀开源项目,让我们先一起来把@Decorator装饰器的原理以及用法彻底弄懂。 一、装饰器的样子 我们先来看看Decorator装饰器长什么样子,大家可能没在项目中用过Decorator装饰
腾讯云开发者
2021/08/27
6210
NestJs:深入浅出装饰器
最近工作上刚好有接触部分 nest 相关的内容,之前对于 nest 了解的并不是特别深入。
19组清风
2023/10/31
4020
相关推荐
JS 装饰器解析
更多 >
LV.1
这个人很懒,什么都没有留下~
目录
  • Decorator
  • 1 .装饰类
  • 2. 装饰方法或属性
  • Babel
  • 安装编译
  • 装饰类的编译
  • 装饰方法的编译
  • 装饰方法的编译源码解析
  • Object.getOwnPropertyDescriptor()
  • 第一部分源码解析
  • 第二部分源码解析
  • 第三部分源码解析
  • 应用
  • 1.log
  • 2.autobind
  • 3.debounce
  • 4.time
  • 5.mixin
  • 6.redux
  • 7.注意
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档