粥里有勺糖

vuePress-theme-reco 粥里有勺糖    2018 - 2023
粥里有勺糖 粥里有勺糖

Choose mode

  • dark
  • auto
  • light
关于我
备战春秋
  • 心得总结
  • 校招考点汇总
  • 面经汇总
  • 复习自查
技术笔记
  • 技术教程
  • 模板工程
  • 源码学习
  • 技术概念
  • 个人作品
  • 学习笔记
计算机基础
  • 算法与数据结构
  • 操作系统
  • 计算机网络
  • 设计模式
  • 剑指offer
大前端
  • javascript
  • vue
  • html
  • css
  • 🌏浏览器专题
  • Web性能优化
  • regexp
  • node
面试
  • 问解
  • javascript
  • css
  • 手撕代码
  • 性能优化
  • 综合问题
  • 面经汇总
  • 小程序
手撕代码
  • 数据结构与算法
  • javascript
  • css
个人站点
  • GitHub (opens new window)
  • 博客园 (opens new window)
  • 掘金 (opens new window)
线上作品
  • 轻取(文件收集) (opens new window)
  • 个人图床 (opens new window)
  • 考勤小程序 (opens new window)
  • 时光恋人 (opens new window)
  • 在线简历生成 (opens new window)
留言板
Github (opens new window)
author-avatar

粥里有勺糖

285

文章

40

标签

关于我
备战春秋
  • 心得总结
  • 校招考点汇总
  • 面经汇总
  • 复习自查
技术笔记
  • 技术教程
  • 模板工程
  • 源码学习
  • 技术概念
  • 个人作品
  • 学习笔记
计算机基础
  • 算法与数据结构
  • 操作系统
  • 计算机网络
  • 设计模式
  • 剑指offer
大前端
  • javascript
  • vue
  • html
  • css
  • 🌏浏览器专题
  • Web性能优化
  • regexp
  • node
面试
  • 问解
  • javascript
  • css
  • 手撕代码
  • 性能优化
  • 综合问题
  • 面经汇总
  • 小程序
手撕代码
  • 数据结构与算法
  • javascript
  • css
个人站点
  • GitHub (opens new window)
  • 博客园 (opens new window)
  • 掘金 (opens new window)
线上作品
  • 轻取(文件收集) (opens new window)
  • 个人图床 (opens new window)
  • 考勤小程序 (opens new window)
  • 时光恋人 (opens new window)
  • 在线简历生成 (opens new window)
留言板
Github (opens new window)
  • source learn

    • 源码学习
    • 优秀装饰器源码学习(一):time
    • 优秀装饰器源码学习(二)
    • 优秀装饰器源码学习(三)
    • FileSaver.js源码学习,纯前端实现文件下载,防止浏览器直接打开预览
    • 源码学习:MongoDB-ObjectId生成原理
    • 源码学习:Vite中加载环境变量(loadEnv)的实现

优秀装饰器源码学习(三)

vuePress-theme-reco 粥里有勺糖    2018 - 2023

优秀装饰器源码学习(三)

粥里有勺糖 2021-08-02 技术笔记源码学习

# 优秀装饰器源码学习(三)

# 前言

通过前两篇文章《优秀装饰器源码学习(一):time》,《优秀装饰器源码学习(二)》学习了

@time,@deprecate, @readonly, @enumerable, @nonconfigurable等基础的装饰器

本文和大家一起学习几个稍微复杂一点的装饰器:

  • @mixin:混入方法到类中
  • @lazyInitialize:在使用的时候才初始化目标属性

# @mixin

混入对象中的方法到类中

# 使用示例

通过@mixin(obj1,obj2,obj3,..)就能将对象中的属性,挂载到目标类的原型上

就行Vue中通过mixin混入一些公共的方法

import { mixin } from '../index'
const obj1 = {
    logA() {
        console.log(this.a);
    }
}
const obj2 = {
    printKeys() {
        console.log(Object.keys(this));
    }
}

@mixin(obj1, obj2)
class Test {
    public a = 1
}

const t: any = new Test()

t.logA() // 1
t.printKeys() // ['a']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 函数结构

传入参数:

  • objs:rest参数,支持传入多个对象进行混入的操作
function handleClass(target, mixins) {
    if (!mixins.length) {
        throw new SyntaxError(`@mixin() class ${target.name} requires at least one mixin as an argument`);
    }
}
export default function mixin(...objs) {
    return function (target) {
        return handleClass(target, objs)
    }
}
1
2
3
4
5
6
7
8
9
10

# 实现原理

  • 类装饰器第一个参数target标识装饰的类
  • target.prototype标识类的原型
  • 遍历传入的对象,通过Object上的getOwnPropertyNames与getOwnPropertyDescriptor方法分别获取目标对象的自有属性(不包括Symbol属性)与指定属性的描述符
  • 在通过Object.defineProperty实现在Class.prototype上进行拓展

# handleClass的完整实现

/**
 * 获取对象上的每个属性的描述符
 */
function getOwnPropertyDescriptors(obj) {
    const descs = {};
    Object.getOwnPropertyNames(obj).forEach(key => {
        descs[key] = Object.getOwnPropertyDescriptor(obj, key)
    })
    return descs;
}

function handleClass(target, mixins) {
    if (!mixins.length) {
        throw new SyntaxError(`@mixin() class ${target.name} requires at least one mixin as an argument`);
    }
    for (let i = 0; i < mixins.length; i++) {
        const descs = getOwnPropertyDescriptors(mixins[i])
        const keys = Object.getOwnPropertyNames(mixins[i])

        for (let j = 0; j < keys.length; j++) {
            const key = keys[j];
            if (!target.prototype.hasOwnProperty(key)) {
                Object.defineProperty(target.prototype, key, descs[key])
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# lazyInitialize

懒加载指定属性,即在使用的时候才初始化目标属性

# 使用示例

将需要懒执行的逻辑放入到@lazyInitialize之中

import { lazyInitialize } from "..";

function getMaxArray(str=''){
    console.log(str,'init huge array');
    return new Array(100)
}

class Test{
    @lazyInitialize(()=>getMaxArray('a'))
    public a
    public b = getMaxArray('b')
}

const t = new Test()
const k = new Test()
k.a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

运行结果

b init huge array
b init huge array
a init huge array
1
2
3

# 实现原理

  • 使用闭包存储值初始化的函数
  • 修改属性的get行为,当调用get的时候再执行初始化逻辑
  • 将初始化后的内容使用中间变量暂存
  • 再次get调用属性的时候,直接返回暂存的内容

# 函数实现

function createDefaultSetter(key) {
    return function set(newValue) {
        Object.defineProperty(this, key, {
            configurable: true,
            writable: true,
            enumerable: true,
            value: newValue
        });
        return newValue;
    };
}

export default function lazyInitialize(initializer): any {
    let t
    return function (target, key) {
        return {
            get() {
                t = t === undefined? initializer.call(this) : t
                return t;
            },
            set: createDefaultSetter(key)
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 未完待续

下一篇将学习:

  • @debounce:防抖
  • @throttle:节流
Edit this page (opens new window)
Last Updated: 2022/5/15 12:46:34