閱讀代碼是提高編碼水平的好方法,優秀的源代碼就像一部文學巨作,開拓思維,提供啟示。最近在閱讀vue2的源代碼,學到了很多JS的編碼技巧,后續陸續分享出來供參考,順便總結一下代碼閱讀成果。

1. 緩存函數

先來看一個需求,假設有一個邏輯復雜的函數 superComputed 執行很費時間,如果每次使用都去計算一次,就會給用戶帶來很長的等待。這個時候需要考慮將計算結果緩存起來供后續程序調用,緩存函數需要實現當參數相同的情況下,直接取緩存結果。這跟服務器端為避免過多的查詢數據庫而用文件緩存查詢結果相似,在前端如何實現呢?

const superComputed = (str) => {
    // 假設這個函數執行時間很長
    console.info("===> 超級計算開始了……");
    return `輸入:${str}`;
};

編寫一個 cached 函數來封裝目標函數,這個 cached 函數接受目標函數作為參數,然后返回一個封裝好的新函數。在 cached 函數的內部,可以使用 ObjectMap 緩存前一個函數調用的結果。

在VUE項目文件的第 153 行(點擊進去)。

vue/src/shared/util.js

這個 cached 的代碼如下:

/**
 * Create a cached version of a pure function.
 */
export function cached<F: Function>(fn: F): F {
    const cache = Object.create(null);
    return (function cachedFn(str: string) {
        const hit = cache[str];
        return hit || (cache[str] = fn(str));
    }: any);
}

現在將 cached 稍微改下,讓其可以執行,每次執行 superComputed 函數都會打印 ===> 超級計算開始了……,以方便查看函數是否被緩存, 如下:

const superComputed = (str) => {
    // 假設這個函數執行時間很長
    console.info("===> 超級計算開始了……");
    return `輸入:${str}`;
};
const cached = (fn) => {
    const cache = Object.create(null);
    return (str) => {
        const hit = cache[str];
        return hit || (cache[str] = fn(str));
    };
};

const cacheSuperComputed = cached(superComputed);
console.log(cacheSuperComputed("DevPoint"));
console.log(cacheSuperComputed("DevPoint"));
console.log(cacheSuperComputed("juejin"));

執行后的結果如下:

===> 超級計算開始了……
輸入:DevPoint
輸入:DevPoint
===> 超級計算開始了……
輸入:juejin

從結果不難看出,函數執行結果在參數不變的情況下,取得緩存的數據。

2. 將dev-point轉換為devPoint

在項目開發過程中,通常會出現變量風格不一致的問題,可以編寫一個函數將其轉換為統一的風格。

在VUE項目文件的第 160 行(點擊進去)。

vue/src/shared/util.js
/**
 * Camelize a hyphen-delimited string.
 */
const camelizeRE = /-(\w)/g
export const camelize = cached((str: string): string => {
  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
})

將其稍微修改,里面的 cached 函數就是之前介紹的緩存函數。

const camelizeRE = /-(\w)/g;
const camelize = cached((str) => {
    return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ""));
});

console.log(camelize("dev-point")); // devPoint

3. 自定義函數判斷

這里所說的自定義函數,指的是開發人員自定義的函數,不是Javascript原生宿主函數。可能想到原理就是將函數轉換為字符串,先來看下結果:

console.log(cached.toString());
console.log(toString.toString());

執行結果如下:

// 下面是自定義函數的結果
(fn) => {
    const cache = Object.create(null);
    return (str) => {
        const hit = cache[str];
        return hit || (cache[str] = fn(str));
    };
}
// 下面是原生宿主函數的結果
function toString() { [native code] }  

從執行結果來看,原生宿主函數 toString 的結果始終是 function fnName() { [native code] } 格式,因此就可以通過這個來區分,接下來看看VUE項目中的實現方式。

vue/src/core/util/env.js

在文件的第 58 行,代碼如下:

/* istanbul ignore next */
export function isNative (Ctor: any): boolean {
  return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
}

4. JS運行環境

在前端快速發展的今天, JavaScript 代碼可以在不同的運行環境中執行。為了更好的適應各種運行環境,需要判斷當前代碼是在哪個運行環境中執行的,下面來學習一下Vue是如何判斷運行環境的:

vue/src/core/util/env.js

在文件的第 6 行開始,代碼如下:

// Browser environment sniffing
export const inBrowser = typeof window !== 'undefined'
export const inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform
export const weexPlatform = inWeex && WXEnvironment.platform.toLowerCase()
export const UA = inBrowser && window.navigator.userAgent.toLowerCase()
export const isIE = UA && /msie|trident/.test(UA)
export const isIE9 = UA && UA.indexOf('msie 9.0') > 0
export const isEdge = UA && UA.indexOf('edge/') > 0
export const isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android')
export const isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios')
export const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge
export const isPhantomJS = UA && /phantomjs/.test(UA)
export const isFF = UA && UA.match(/firefox\/(\d+)/)

這些判斷代碼都值得借鑒的,這里不展開介紹了,之前在《Vue源碼學習 | 4個實用的Javascript技巧》介紹了瀏覽器的判斷。