1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        手摸手教你用 TypeScript 寫(xiě)一個(gè)基于 Proxy 的緩存庫(kù)

        共 15009字,需瀏覽 31分鐘

         ·

        2021-05-26 00:55

        作者:wsafight

        原文:https://github.com/wsafight/personBlog/issues/34

        兩年前,我寫(xiě)了一篇關(guān)于業(yè)務(wù)緩存的博客 前端 api 請(qǐng)求緩存方案[1], 這篇博客反響還不錯(cuò),其中介紹了如何緩存數(shù)據(jù),Promise 以及如何超時(shí)刪除(也包括如何構(gòu)建修飾器)。如果對(duì)此不夠了解,可以閱讀博客進(jìn)行學(xué)習(xí)。

        但之前的代碼和方案終歸還是簡(jiǎn)單了些,而且對(duì)業(yè)務(wù)有很大的侵入性。這樣不好,于是筆者開(kāi)始重新學(xué)習(xí)與思考代理器 Proxy。

        Proxy 可以理解成,在目標(biāo)對(duì)象之前架設(shè)一層“攔截”,外界對(duì)該對(duì)象的訪問(wèn),都必須先通過(guò)這層攔截,因此提供了一種機(jī)制,可以對(duì)外界的訪問(wèn)進(jìn)行過(guò)濾和改寫(xiě)。Proxy 這個(gè)詞的原意是代理,用在這里表示由它來(lái)“代理”某些操作,可以譯為“代理器”。關(guān)于 Proxy 的介紹與使用,建議大家還是看阮一峰大神的 ECMAScript 6 入門(mén) 代理篇[2]。

        項(xiàng)目演進(jìn)

        任何項(xiàng)目都不是一觸而就的,下面是關(guān)于 Proxy 緩存庫(kù)的編寫(xiě)思路。希望能對(duì)大家有一些幫助。

        proxy handler 添加緩存

        當(dāng)然,其實(shí)代理器中的 handler 參數(shù)也是一個(gè)對(duì)象,那么既然是對(duì)象,當(dāng)然可以添加數(shù)據(jù)項(xiàng),如此,我們便可以基于 Map 緩存編寫(xiě) memoize 函數(shù)用來(lái)提升算法遞歸性能。

        type TargetFun<V> = (...args: any[]) => V

        function memoize<V>(fn: TargetFun<V>) {
        return new Proxy(fn, {
        // 此處目前只能略過(guò) 或者 添加一個(gè)中間層集成 Proxy 和 對(duì)象。
        // 在對(duì)象中添加 cache
        // @ts-ignore
        cache: new Map<string, V>(),
        apply(target, thisArg, argsList) {
        // 獲取當(dāng)前的 cache
        const currentCache = (this as any).cache

        // 根據(jù)數(shù)據(jù)參數(shù)直接生成 Map 的 key
        let cacheKey = argsList.toString();

        // 當(dāng)前沒(méi)有被緩存,執(zhí)行調(diào)用,添加緩存
        if (!currentCache.has(cacheKey)) {
        currentCache.set(cacheKey, target.apply(thisArg, argsList));
        }

        // 返回被緩存的數(shù)據(jù)
        return currentCache.get(cacheKey);
        }
        });
        }

        我們可以嘗試 memoize fibonacci 函數(shù),經(jīng)過(guò)了代理器的函數(shù)有非常大的性能提升(肉眼可見(jiàn)):

        const fibonacci = (n: number): number => (n <= 1 ? 1 : fibonacci(n - 1) + fibonacci(n - 2));
        const memoizedFibonacci = memoize<number>(fibonacci);

        for (let i = 0; i < 100; i++) fibonacci(30); // ~5000ms
        for (let i = 0; i < 100; i++) memoizedFibonacci(30); // ~50ms

        自定義函數(shù)參數(shù)

        我們?nèi)耘f可以利用之前博客介紹的的函數(shù)生成唯一值,只不過(guò)我們不再需要函數(shù)名了:

        const generateKeyError = new Error("Can't generate key from function argument")

        // 基于函數(shù)參數(shù)生成唯一值
        export default function generateKey(argument: any[]): string {
        try{
        return `${Array.from(argument).join(',')}`
        }catch(_) {
        throw generateKeyError
        }
        }

        雖然庫(kù)本身可以基于函數(shù)參數(shù)提供唯一值,但是針對(duì)形形色色的不同業(yè)務(wù)來(lái)說(shuō),這肯定是不夠用的,需要提供用戶(hù)可以自定義參數(shù)序列化。

        // 如果配置中有 normalizer 函數(shù),直接使用,否則使用默認(rèn)函數(shù)
        const normalizer = options?.normalizer ?? generateKey

        return new Proxy<any>(fn, {
        // @ts-ignore
        cache,
        apply(target, thisArg, argsList: any[]) {
        const cache: Map<string, any> = (this as any).cache

        // 根據(jù)格式化函數(shù)生成唯一數(shù)值
        const cacheKey: string = normalizer(argsList);

        if (!cache.has(cacheKey))
        cache.set(cacheKey, target.apply(thisArg, argsList));
        return cache.get(cacheKey);
        }
        });

        添加 Promise 緩存

        在之前的博客中,提到緩存數(shù)據(jù)的弊端。同一時(shí)刻多次調(diào)用,會(huì)因?yàn)檎?qǐng)求未返回而進(jìn)行多次請(qǐng)求。所以我們也需要添加關(guān)于 Promise 的緩存。

        if (!currentCache.has(cacheKey)){
        let result = target.apply(thisArg, argsList)

        // 如果是 promise 則緩存 promise,簡(jiǎn)單判斷!
        // 如果當(dāng)前函數(shù)有 then 則是 Promise
        if (result?.then) {
        result = Promise.resolve(result).catch(error => {
        // 發(fā)生錯(cuò)誤,刪除當(dāng)前 promise,否則會(huì)引發(fā)二次錯(cuò)誤
        // 由于異步,所以當(dāng)前 delete 調(diào)用一定在 set 之后,
        currentCache.delete(cacheKey)

        // 把錯(cuò)誤衍生出去
        return Promise.reject(error)
        })
        }
        currentCache.set(cacheKey, result);
        }
        return currentCache.get(cacheKey);

        此時(shí),我們不但可以緩存數(shù)據(jù),還可以緩存 Promise 數(shù)據(jù)請(qǐng)求。

        添加過(guò)期刪除功能

        我們可以在數(shù)據(jù)中添加當(dāng)前緩存時(shí)的時(shí)間戳,在生成數(shù)據(jù)時(shí)候添加。

        // 緩存項(xiàng)
        export default class ExpiredCacheItem<V> {
        data: V;
        cacheTime: number;

        constructor(data: V) {
        this.data = data
        // 添加系統(tǒng)時(shí)間戳
        this.cacheTime = (new Date()).getTime()
        }
        }

        // 編輯 Map 緩存中間層,判斷是否過(guò)期
        isOverTime(name: string) {
        const data = this.cacheMap.get(name)

        // 沒(méi)有數(shù)據(jù)(因?yàn)楫?dāng)前保存的數(shù)據(jù)是 ExpiredCacheItem),所以我們統(tǒng)一看成功超時(shí)
        if (!data) return true

        // 獲取系統(tǒng)當(dāng)前時(shí)間戳
        const currentTime = (new Date()).getTime()

        // 獲取當(dāng)前時(shí)間與存儲(chǔ)時(shí)間的過(guò)去的秒數(shù)
        const overTime = currentTime - data.cacheTime

        // 如果過(guò)去的秒數(shù)大于當(dāng)前的超時(shí)時(shí)間,也返回 null 讓其去服務(wù)端取數(shù)據(jù)
        if (Math.abs(overTime) > this.timeout) {
        // 此代碼可以沒(méi)有,不會(huì)出現(xiàn)問(wèn)題,但是如果有此代碼,再次進(jìn)入該方法就可以減少判斷。
        this.cacheMap.delete(name)
        return true
        }

        // 不超時(shí)
        return false
        }

        // cache 函數(shù)有數(shù)據(jù)
        has(name: string) {
        // 直接判斷在 cache 中是否超時(shí)
        return !this.isOverTime(name)
        }

        到達(dá)這一步,我們可以做到之前博客所描述的所有功能。不過(guò),如果到這里就結(jié)束的話,太不過(guò)癮了。我們繼續(xù)學(xué)習(xí)其他庫(kù)的功能來(lái)優(yōu)化我的功能庫(kù)。

        添加手動(dòng)管理

        通常來(lái)說(shuō),這些緩存庫(kù)都會(huì)有手動(dòng)管理的功能,所以這里我也提供了手動(dòng)管理緩存以便業(yè)務(wù)管理。這里我們使用 Proxy get 方法來(lái)攔截屬性讀取。

         return new Proxy(fn, {
        // @ts-ignore
        cache,
        get: (target: TargetFun<V>, property: string) => {

        // 如果配置了手動(dòng)管理
        if (options?.manual) {
        const manualTarget = getManualActionObjFormCache<V>(cache)

        // 如果當(dāng)前調(diào)用的函數(shù)在當(dāng)前對(duì)象中,直接調(diào)用,沒(méi)有的話訪問(wèn)原對(duì)象
        // 即使當(dāng)前函數(shù)有該屬性或者方法也不考慮,誰(shuí)讓你配置了手動(dòng)管理呢。
        if (property in manualTarget) {
        return manualTarget[property]
        }
        }

        // 當(dāng)前沒(méi)有配置手動(dòng)管理,直接訪問(wèn)原對(duì)象
        return target[property]
        },
        }

        export default function getManualActionObjFormCache<V>(
        cache: MemoizeCache<V>
        ): CacheMap<string | object, V>
        {
        const manualTarget = Object.create(null)

        // 通過(guò)閉包添加 set get delete clear 等 cache 操作
        manualTarget.set = (key: string | object, val: V) => cache.set(key, val)
        manualTarget.get = (key: string | object) => cache.get(key)
        manualTarget.delete = (key: string | object) => cache.delete(key)
        manualTarget.clear = () => cache.clear!()

        return manualTarget
        }

        當(dāng)前情況并不復(fù)雜,我們可以直接調(diào)用,復(fù)雜的情況下還是建議使用 Reflect[3] 。

        添加 WeakMap

        我們?cè)谑褂?cache 時(shí)候,我們同時(shí)也可以提供 WeakMap ( WeakMap 沒(méi)有 clear 和 size 方法),這里我提取了 BaseCache 基類(lèi)。

        export default class BaseCache<V> {
        readonly weak: boolean;
        cacheMap: MemoizeCache<V>

        constructor(weak: boolean = false) {
        // 是否使用 weakMap
        this.weak = weak
        this.cacheMap = this.getMapOrWeakMapByOption()
        }

        // 根據(jù)配置獲取 Map 或者 WeakMap
        getMapOrWeakMapByOption<T>(): Map<string, T> | WeakMap<object, T> {
        return this.weak ? new WeakMap<object, T>() : new Map<string, T>()
        }
        }

        之后,我添加各種類(lèi)型的緩存類(lèi)都以此為基類(lèi)。

        添加清理函數(shù)

        在緩存進(jìn)行刪除時(shí)候需要對(duì)值進(jìn)行清理,需要用戶(hù)提供 dispose 函數(shù)。該類(lèi)繼承 BaseCache 同時(shí)提供 dispose 調(diào)用。

        export const defaultDispose: DisposeFun<any> = () => void 0

        export default class BaseCacheWithDispose<V, WrapperV> extends BaseCache<WrapperV> {
        readonly weak: boolean
        readonly dispose: DisposeFun<V>

        constructor(weak: boolean = false, dispose: DisposeFun<V> = defaultDispose) {
        super(weak)
        this.weak = weak
        this.dispose = dispose
        }

        // 清理單個(gè)值(調(diào)用 delete 前調(diào)用)
        disposeValue(value: V | undefined): void {
        if (value) {
        this.dispose(value)
        }
        }

        // 清理所有值(調(diào)用 clear 方法前調(diào)用,如果當(dāng)前 Map 具有迭代器)
        disposeAllValue<V>(cacheMap: MemoizeCache<V>): void {
        for (let mapValue of (cacheMap as any)) {
        this.disposeValue(mapValue?.[1])
        }
        }
        }

        當(dāng)前的緩存如果是 WeakMap,是沒(méi)有 clear 方法和迭代器的。個(gè)人想要添加中間層來(lái)完成這一切(還在考慮,目前沒(méi)有做)。如果 WeakMap 調(diào)用 clear 方法時(shí),我是直接提供新的 WeakMap 。

        clear() {
        if (this.weak) {
        this.cacheMap = this.getMapOrWeakMapByOption()
        } else {
        this.disposeAllValue(this.cacheMap)
        this.cacheMap.clear!()
        }
        }

        添加計(jì)數(shù)引用

        在學(xué)習(xí)其他庫(kù) memoizee[4] 的過(guò)程中,我看到了如下用法:

        memoized = memoize(fn, { refCounter: true });

        memoized("foo", 3); // refs: 1
        memoized("foo", 3); // Cache hit, refs: 2
        memoized("foo", 3); // Cache hit, refs: 3
        memoized.deleteRef("foo", 3); // refs: 2
        memoized.deleteRef("foo", 3); // refs: 1
        memoized.deleteRef("foo", 3); // refs: 0,清除 foo 的緩存
        memoized("foo", 3); // Re-executed, refs: 1

        于是我有樣學(xué)樣,也添加了 RefCache。

        export default class RefCache<V> extends BaseCacheWithDispose<V, V> implements CacheMap<string | object, V> {
        // 添加 ref 計(jì)數(shù)
        cacheRef: MemoizeCache<number>

        constructor(weak: boolean = false, dispose: DisposeFun<V> = () => void 0) {
        super(weak, dispose)
        // 根據(jù)配置生成 WeakMap 或者 Map
        this.cacheRef = this.getMapOrWeakMapByOption<number>()
        }

        // get has clear 等相同。不列出

        delete(key: string | object): boolean {
        this.disposeValue(this.get(key))
        this.cacheRef.delete(key)
        this.cacheMap.delete(key)
        return true;
        }

        set(key: string | object, value: V): this {
        this.cacheMap.set(key, value)
        // set 的同時(shí)添加 ref
        this.addRef(key)
        return this
        }

        // 也可以手動(dòng)添加計(jì)數(shù)
        addRef(key: string | object) {
        if (!this.cacheMap.has(key)) {
        return
        }
        const refCount: number | undefined = this.cacheRef.get(key)
        this.cacheRef.set(key, (refCount ?? 0) + 1)
        }

        getRefCount(key: string | object) {
        return this.cacheRef.get(key) ?? 0
        }

        deleteRef(key: string | object): boolean {
        if (!this.cacheMap.has(key)) {
        return false
        }

        const refCount: number = this.getRefCount(key)

        if (refCount <= 0) {
        return false
        }

        const currentRefCount = refCount - 1

        // 如果當(dāng)前 refCount 大于 0, 設(shè)置,否則清除
        if (currentRefCount > 0) {
        this.cacheRef.set(key, currentRefCount)
        } else {
        this.cacheRef.delete(key)
        this.cacheMap.delete(key)
        }
        return true
        }
        }

        同時(shí)修改 proxy 主函數(shù):

        if (!currentCache.has(cacheKey)) {
        let result = target.apply(thisArg, argsList)

        if (result?.then) {
        result = Promise.resolve(result).catch(error => {
        currentCache.delete(cacheKey)
        return Promise.reject(error)
        })
        }
        currentCache.set(cacheKey, result);

        // 當(dāng)前配置了 refCounter
        } else if (options?.refCounter) {
        // 如果被再次調(diào)用且當(dāng)前已經(jīng)緩存過(guò)了,直接增加
        currentCache.addRef?.(cacheKey)
        }

        添加 LRU

        LRU 的英文全稱(chēng)是 Least Recently Used,也即最不經(jīng)常使用。相比于其他的數(shù)據(jù)結(jié)構(gòu)進(jìn)行緩存,LRU 無(wú)疑更加有效。

        這里考慮在添加 maxAge 的同時(shí)也添加 max 值 (這里我利用兩個(gè) Map 來(lái)做 LRU,雖然會(huì)增加一定的內(nèi)存消耗,但是性能更好)。

        如果當(dāng)前的此時(shí)保存的數(shù)據(jù)項(xiàng)等于 max ,我們直接把當(dāng)前 cacheMap 設(shè)為 oldCacheMap,并重新 new cacheMap。

        set(key: string | object, value: V) {
        const itemCache = new ExpiredCacheItem<V>(value)
        // 如果之前有值,直接修改
        this.cacheMap.has(key) ? this.cacheMap.set(key, itemCache) : this._set(key, itemCache);
        return this
        }

        private _set(key: string | object, value: ExpiredCacheItem<V>) {
        this.cacheMap.set(key, value);
        this.size++;

        if (this.size >= this.max) {
        this.size = 0;
        this.oldCacheMap = this.cacheMap;
        this.cacheMap = this.getMapOrWeakMapByOption()
        }
        }

        重點(diǎn)在與獲取數(shù)據(jù)時(shí)候,如果當(dāng)前的 cacheMap 中有值且沒(méi)有過(guò)期,直接返回,如果沒(méi)有,就去 oldCacheMap 查找,如果有,刪除老數(shù)據(jù)并放入新數(shù)據(jù)(使用 _set 方法),如果都沒(méi)有,返回 undefined.

        get(key: string | object): V | undefined {
        // 如果 cacheMap 有,返回 value
        if (this.cacheMap.has(key)) {
        const item = this.cacheMap.get(key);
        return this.getItemValue(key, item!);
        }

        // 如果 oldCacheMap 里面有
        if (this.oldCacheMap.has(key)) {
        const item = this.oldCacheMap.get(key);
        // 沒(méi)有過(guò)期
        if (!this.deleteIfExpired(key, item!)) {
        // 移動(dòng)到新的數(shù)據(jù)中并刪除老數(shù)據(jù)
        this.moveToRecent(key, item!);
        return item!.data as V;
        }
        }
        return undefined
        }

        private moveToRecent(key: string | object, item: ExpiredCacheItem<V>) {
        // 老數(shù)據(jù)刪除
        this.oldCacheMap.delete(key);

        // 新數(shù)據(jù)設(shè)定,重點(diǎn)?。。。∪绻?dāng)前設(shè)定的數(shù)據(jù)等于 max,清空 oldCacheMap,如此,數(shù)據(jù)不會(huì)超過(guò) max
        this._set(key, item);
        }

        private getItemValue(key: string | object, item: ExpiredCacheItem<V>): V | undefined {
        // 如果當(dāng)前設(shè)定了 maxAge 就查詢(xún),否則直接返回
        return this.maxAge ? this.getOrDeleteIfExpired(key, item) : item?.data;
        }

        private getOrDeleteIfExpired(key: string | object, item: ExpiredCacheItem<V>): V | undefined {
        const deleted = this.deleteIfExpired(key, item);
        return !deleted ? item.data : undefined;
        }

        private deleteIfExpired(key: string | object, item: ExpiredCacheItem<V>) {
        if (this.isOverTime(item)) {
        return this.delete(key);
        }
        return false;
        }

        整理 memoize 函數(shù)

        事情到了這一步,我們就可以從之前的代碼細(xì)節(jié)中解放出來(lái)了,看看基于這些功能所做出的接口與主函數(shù)。

        // 面向接口,無(wú)論后面還會(huì)不會(huì)增加其他類(lèi)型的緩存類(lèi)
        export interface BaseCacheMap<K, V> {
        delete(key: K): boolean;

        get(key: K): V | undefined;

        has(key: K): boolean;

        set(key: K, value: V): this;

        clear?(): void;

        addRef?(key: K): void;

        deleteRef?(key: K): boolean;
        }

        // 緩存配置
        export interface MemoizeOptions<V> {
        /** 序列化參數(shù) */
        normalizer?: (args: any[]) => string;
        /** 是否使用 WeakMap */
        weak?: boolean;
        /** 最大毫秒數(shù),過(guò)時(shí)刪除 */
        maxAge?: number;
        /** 最大項(xiàng)數(shù),超過(guò)刪除 */
        max?: number;
        /** 手動(dòng)管理內(nèi)存 */
        manual?: boolean;
        /** 是否使用引用計(jì)數(shù) */
        refCounter?: boolean;
        /** 緩存刪除數(shù)據(jù)時(shí)期的回調(diào) */
        dispose?: DisposeFun<V>;
        }

        // 返回的函數(shù)(攜帶一系列方法)
        export interface ResultFun<V> extends Function {
        delete?(key: string | object): boolean;

        get?(key: string | object): V | undefined;

        has?(key: string | object): boolean;

        set?(key: string | object, value: V): this;

        clear?(): void;

        deleteRef?(): void
        }

        最終的 memoize 函數(shù)其實(shí)和最開(kāi)始的函數(shù)差不多,只做了 3 件事

        • 檢查參數(shù)并拋出錯(cuò)誤
        • 根據(jù)參數(shù)獲取合適的緩存
        • 返回代理
        export default function memoize<V>(fn: TargetFun<V>, options?: MemoizeOptions<V>): ResultFun<V> {
        // 檢查參數(shù)并拋出錯(cuò)誤
        checkOptionsThenThrowError<V>(options)

        // 修正序列化函數(shù)
        const normalizer = options?.normalizer ?? generateKey

        let cache: MemoizeCache<V> = getCacheByOptions<V>(options)

        // 返回代理
        return new Proxy(fn, {
        // @ts-ignore
        cache,
        get: (target: TargetFun<V>, property: string) => {
        // 添加手動(dòng)管理
        if (options?.manual) {
        const manualTarget = getManualActionObjFormCache<V>(cache)
        if (property in manualTarget) {
        return manualTarget[property]
        }
        }
        return target[property]
        },
        apply(target, thisArg, argsList: any[]): V {

        const currentCache: MemoizeCache<V> = (this as any).cache

        const cacheKey: string | object = getKeyFromArguments(argsList, normalizer, options?.weak)

        if (!currentCache.has(cacheKey)) {
        let result = target.apply(thisArg, argsList)

        if (result?.then) {
        result = Promise.resolve(result).catch(error => {
        currentCache.delete(cacheKey)
        return Promise.reject(error)
        })
        }
        currentCache.set(cacheKey, result);
        } else if (options?.refCounter) {
        currentCache.addRef?.(cacheKey)
        }
        return currentCache.get(cacheKey) as V;
        }
        }) as any
        }

        完整代碼在 memoizee\-proxy[5] 中。大家自行操作與把玩。

        下一步

        測(cè)試

        測(cè)試覆蓋率不代表一切,但是在實(shí)現(xiàn)庫(kù)的過(guò)程中,JEST[6] 測(cè)試庫(kù)給我提供了大量的幫助,它幫助我重新思考每一個(gè)類(lèi)以及每一個(gè)函數(shù)應(yīng)該具有的功能與參數(shù)校驗(yàn)。之前的代碼我總是在項(xiàng)目的主入口進(jìn)行校驗(yàn),對(duì)于每個(gè)類(lèi)或者函數(shù)的參數(shù)沒(méi)有深入思考。事實(shí)上,這個(gè)健壯性是不夠的。因?yàn)槟悴荒軟Q定用戶(hù)怎么使用你的庫(kù)。

        Proxy 深入

        事實(shí)上,代理的應(yīng)用場(chǎng)景是不可限量的。這一點(diǎn),ruby 已經(jīng)驗(yàn)證過(guò)了(可以去學(xué)習(xí)《ruby 元編程》)。

        開(kāi)發(fā)者使用它可以創(chuàng)建出各種編碼模式,比如(但遠(yuǎn)遠(yuǎn)不限于)跟蹤屬性訪問(wèn)、隱藏屬性、阻止修改或刪除屬性、函數(shù)參數(shù)驗(yàn)證、構(gòu)造函數(shù)參數(shù)驗(yàn)證、數(shù)據(jù)綁定,以及可觀察對(duì)象。

        當(dāng)然,Proxy 雖然來(lái)自于 ES6 ,但該 API 仍需要較高的瀏覽器版本,雖然有 proxy-pollfill[7] ,但畢竟提供功能有限。不過(guò)已經(jīng) 2021,相信深入學(xué)習(xí) Proxy 也是時(shí)機(jī)了。

        深入緩存

        緩存是有害的!這一點(diǎn)毋庸置疑。但是它實(shí)在太快了!所以我們要更加理解業(yè)務(wù),哪些數(shù)據(jù)需要緩存,理解那些數(shù)據(jù)可以使用緩存。

        當(dāng)前書(shū)寫(xiě)的緩存僅僅只是針對(duì)與一個(gè)方法,之后寫(xiě)的項(xiàng)目是否可以更細(xì)粒度的結(jié)合返回?cái)?shù)據(jù)?還是更往上思考,寫(xiě)出一套緩存層?

        小步開(kāi)發(fā)

        在開(kāi)發(fā)該項(xiàng)目的過(guò)程中,我采用小步快跑的方式,不斷返工。最開(kāi)始的代碼,也僅僅只到了添加過(guò)期刪除功能那一步。

        但是當(dāng)我每次完成一個(gè)新的功能后,重新開(kāi)始整理庫(kù)的邏輯與流程,爭(zhēng)取每一次的代碼都足夠優(yōu)雅。同時(shí)因?yàn)槲也痪邆涞谝淮尉帉?xiě)就能通盤(pán)考慮的能力。不過(guò)希望在今后的工作中,不斷進(jìn)步。這樣也能減少代碼的返工。

        其他

        函數(shù)創(chuàng)建

        事實(shí)上,我在為當(dāng)前庫(kù)添加手動(dòng)管理時(shí)候,考慮過(guò)直接復(fù)制函數(shù),因?yàn)楹瘮?shù)本身是一個(gè)對(duì)象。同時(shí)為當(dāng)前函數(shù)添加 set 等方法。但是沒(méi)有辦法把作用域鏈拷貝過(guò)去。

        雖然沒(méi)能成功,但是也學(xué)到了一些知識(shí),這里也提供兩個(gè)創(chuàng)建函數(shù)的代碼。

        我們?cè)趧?chuàng)建函數(shù)時(shí)候基本上會(huì)利用 new Function 創(chuàng)建函數(shù),但是瀏覽器沒(méi)有提供可以直接創(chuàng)建異步函數(shù)的構(gòu)造器,我們需要手動(dòng)獲取。

        AsyncFunction = (async x => x).constructor

        foo = new AsyncFunction('x, y, p', 'return x + y + await p')

        foo(1,2, Promise.resolve(3)).then(console.log) // 6

        對(duì)于全局函數(shù),我們也可以直接 fn.toString() 來(lái)創(chuàng)建函數(shù),這時(shí)候異步函數(shù)也可以直接構(gòu)造的。

        function cloneFunction<T>(fn: (...args: any[]) => T): (...args: any[]) => T {
        return new Function('return '+ fn.toString())();
        }

        參考資料

        前端 api 請(qǐng)求緩存方案[8]

        ECMAScript 6 入門(mén) 代理篇[9]

        memoizee[10]

        memoizee-proxy[11]

        參考資料

        [1]

        前端 api 請(qǐng)求緩存方案: https://github.com/wsafight/personBlog/issues/2

        [2]

        ECMAScript 6 入門(mén) 代理篇: https://es6.ruanyifeng.com/#docs/proxy

        [3]

        Reflect: https://es6.ruanyifeng.com/#docs/reflect

        [4]

        memoizee: https://github.com/medikoo/memoizee

        [5]

        memoizee-proxy: https://github.com/wsafight/memoizee-proxy

        [6]

        JEST: https://www.jestjs.cn/

        [7]

        proxy-pollfill: https://github.com/GoogleChrome/proxy-polyfill

        [8]

        前端 api 請(qǐng)求緩存方案: https://github.com/wsafight/personBlog/issues/2

        [9]

        ECMAScript 6 入門(mén) 代理篇: https://es6.ruanyifeng.com/#docs/proxy

        [10]

        memoizee: https://github.com/medikoo/memoizee

        [11]

        memoizee-proxy: https://github.com/wsafight/memoizee-proxy

        瀏覽 33
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            一级黄色生活片 | 超碰人人操人人妻 | 护士扒下内裤让我爽一夜电影 | 成人黄片18 | 国产做爱xxxⅹ性视频播放量 | 精品秘 无码一区二区三区老师 | 成人一卡二卡 | 91黄色免费视频 | 亚洲精品网站在线播放gif | 成人午夜精品福利 |