初学者也能看懂的 Vue3 源码中那些实用的基础工具函数
1. 前言
大家好,我是若川。最近組織了源碼共讀活動。每周讀 200 行左右的源碼。很多第一次讀源碼的小伙伴都感覺很有收獲,感興趣可以加我微信ruochuan12,拉你進群學習。
寫相對很難的源碼,耗費了自己的時間和精力,也沒收獲多少閱讀點贊,其實是一件挺受打擊的事情。從閱讀量和讀者受益方面來看,不能促進作者持續輸出文章。
所以轉變思路,寫一些相對通俗易懂的文章。其實源碼也不是想象的那么難,至少有很多看得懂。比如工具函數。本文通過學習Vue3源碼中的工具函數模塊的源碼,學習源碼為自己所用。歌德曾說:讀一本好書,就是在和高尚的人談話。同理可得:讀源碼,也算是和作者的一種學習交流的方式。
閱讀本文,你將學到:
1.?如何學習?JavaScript?基礎知識,會推薦很多學習資料 2.?如何學習調試?vue?3?源碼 3.?如何學習源碼中優秀代碼和思想,投入到自己的項目中 4.?Vue?3?源碼?shared?模塊中的幾十個實用工具函數 5.?我的一些經驗分享shared模塊中57個工具函數,本次閱讀其中的30余個。
2. 環境準備
2.1 讀開源項目 貢獻指南
打開 vue-next[1], 開源項目一般都能在 README.md 或者 .github/contributing.md[2] 找到貢獻指南。
而貢獻指南寫了很多關于參與項目開發的信息。比如怎么跑起來,項目目錄結構是怎樣的。怎么投入開發,需要哪些知識儲備等。
我們可以在 項目目錄結構[3] 描述中,找到shared模塊。
shared: Internal utilities shared across multiple packages (especially environment-agnostic utils used by both runtime and compiler packages).
README.md 和 contributing.md 一般都是英文的。可能會難倒一部分人。其實看不懂,完全可以可以借助劃詞翻譯,整頁翻譯和百度翻譯等翻譯工具。再把英文加入后續學習計劃。
本文就是講shared模塊,對應的文件路徑是:`vue-next/packages/shared/src/index.ts`[4]
也可以用github1s訪問,速度更快。github1s packages/shared/src/index.ts[5]
2.2 按照項目指南 打包構建代碼
為了降低文章難度,我按照貢獻指南中方法打包把ts轉成了js。如果你需要打包,也可以參考下文打包構建。
你需要確保 Node.js[6] 版本是 10+, 而且 yarn 的版本是 1.x Yarn 1.x[7]。
你安裝的 Node.js 版本很可能是低于 10。最簡單的辦法就是去官網重新安裝。也可以使用 nvm等管理Node.js版本。
node?-v #?v14.16.0 #?全局安裝?yarn #?克隆項目 git?clone?https://github.com/vuejs/vue-next.git cd?vue-next#?或者克隆我的項目 git?clone?https://github.com/lxchuan12/vue-next-analysis.git cd?vue-next-analysisnpm?install?--global?yarn yarn?#?install?the?dependencies?of?the?project yarn?build可以得到 vue-next/packages/shared/dist/shared.esm-bundler.js,文件也就是純js文件。也接下就是解釋其中的一些方法。
當然,前面可能比較啰嗦。我可以直接講 3. 工具函數。但通過我上文的介紹,即使是初學者,都能看懂一些開源項目源碼,也許就會有一定的成就感。另外,面試問到被類似的問題或者筆試題時,你說看Vue3源碼學到的,面試官絕對對你刮目相看。
2.3 如何生成 sourcemap 調試 vue-next 源碼
熟悉我的讀者知道,我是經常強調生成sourcemap調試看源碼,所以順便提一下如何配置生成sourcemap,如何調試。這部分可以簡單略過,動手操作時再仔細看。
其實貢獻指南[8]里描述了。
Build with Source Maps Use the --sourcemap or -s flag to build with source maps. Note this will make the build much slower.
所以在 vue-next/package.json 追加 "dev:sourcemap": "node scripts/dev.js --sourcemap",yarn dev:sourcemap執行,即可生成sourcemap,或者直接 build。
//?package.json {"version":?"3.2.1","scripts":?{"dev:sourcemap":?"node?scripts/dev.js?--sourcemap"} }會在控制臺輸出類似vue-next/packages/vue/src/index.ts → packages/vue/dist/vue.global.js的信息。
其中packages/vue/dist/vue.global.js.map 就是sourcemap文件了。
我們在 Vue3官網找個例子,在 vue-next/examples/index.html。其內容引入packages/vue/dist/vue.global.js。
//?vue-next/examples/index.html <script?src="../../packages/vue/dist/vue.global.js"></script> <script>const?Counter?=?{data()?{return?{counter:?0}}}Vue.createApp(Counter).mount('#counter') </script>然后我們新建一個終端窗口,yarn serve,在瀏覽器中打開http://localhost:5000/examples/,如下圖所示,按F11等進入函數,就可以愉快的調試源碼了。
vue-next-debugger3. 工具函數
本文主要按照源碼 `vue-next/packages/shared/src/index.ts`[9] 的順序來寫。也省去了一些從外部導入的方法。
我們也可以通過ts文件,查看使用函數的位置。同時在VSCode運行調試JS代碼,我們比較推薦韓老師寫的code runner插件。
3.1 babelParserDefaultPlugins ?babel 解析默認插件
/***?List?of?@babel/parser?plugins?that?are?used?for?template?expression*?transforms?and?SFC?script?transforms.?By?default?we?enable?proposals?slated*?for?ES2020.?This?will?need?to?be?updated?as?the?spec?moves?forward.*?Full?list?at?https://babeljs.io/docs/en/next/babel-parser#plugins*/ const?babelParserDefaultPlugins?=?['bigInt','optionalChaining','nullishCoalescingOperator' ];這里就是幾個默認插件。感興趣看英文注釋查看。
3.2 EMPTY_OBJ 空對象
const?EMPTY_OBJ?=?(process.env.NODE_ENV?!==?'production')??Object.freeze({}):?{};//?例子: //?Object.freeze?是?凍結對象 //?凍結的對象最外層無法修改。 const?EMPTY_OBJ_1?=?Object.freeze({}); EMPTY_OBJ_1.name?=?'若川'; console.log(EMPTY_OBJ_1.name);?//?undefinedconst?EMPTY_OBJ_2?=?Object.freeze({?props:?{?mp:?'若川視野'?}?}); EMPTY_OBJ_2.props.name?=?'若川'; EMPTY_OBJ_2.props2?=?'props2'; console.log(EMPTY_OBJ_2.props.name);?//?'若川' console.log(EMPTY_OBJ_2.props2);?//?undefined console.log(EMPTY_OBJ_2); /***?*?{?*??props:?{mp:?"若川視野",name:?"若川"}*?}*?*/process.env.NODE_ENV 是 node 項目中的一個環境變量,一般定義為:development 和production。根據環境寫代碼。比如開發環境,有報錯等信息,生產環境則不需要這些報錯警告。
3.3 EMPTY_ARR 空數組
const?EMPTY_ARR?=?(process.env.NODE_ENV?!==?'production')???Object.freeze([])?:?[];//?例子: EMPTY_ARR.push(1)?//?報錯,也就是為啥生產環境還是用?[] EMPTY_ARR.length?=?3; console.log(EMPTY_ARR.length);?//?03.4 NOOP 空函數
const?NOOP?=?()?=>?{?};//?很多庫的源碼中都有這樣的定義函數,比如?jQuery、underscore、lodash?等 //?使用場景:1. 方便判斷, 2. 方便壓縮 // 1. 比如: const?instance?=?{render:?NOOP };//?條件 const?dev?=?true; if(dev){instance.render?=?function(){console.log('render');} }//?可以用作判斷。 if(instance.render?===?NOOP){console.log('i'); } // 2. 再比如: //?方便壓縮代碼 //?如果是?function(){}?,不方便壓縮代碼3.5 NO 永遠返回 false 的函數
/***?Always?return?false.*/ const?NO?=?()?=>?false;//?除了壓縮代碼的好處外。 //?一直返回?false3.6 isOn 判斷字符串是不是 on 開頭,并且 on 后首字母不是小寫字母
const?onRE?=?/^on[^a-z]/; const?isOn?=?(key)?=>?onRE.test(key);//?例子: isOn('onChange');?//?true isOn('onchange');?//?false isOn('on3change');?//?trueonRE 是正則。^符號在開頭,則表示是什么開頭。而在其他地方是指非。
與之相反的是:$符合在結尾,則表示是以什么結尾。
[^a-z]是指不是a到z的小寫字母。
同時推薦一個正則在線工具。
regex101[10]
另外正則看老姚的迷你書就夠用了。
老姚:《JavaScript 正則表達式迷你書》問世了![11]
3.7 isModelListener 監聽器
判斷字符串是不是以onUpdate:開頭
const?isModelListener?=?(key)?=>?key.startsWith('onUpdate:');//?例子: isModelListener('onUpdate:change');?//?true isModelListener('1onUpdate:change');?//?false //?startsWith?是?ES6?提供的方法ES6入門教程:字符串的新增方法[12]
很多方法都在《ES6入門教程》中有講到,就不贅述了。
3.8 extend 繼承 合并
說合并可能更準確些。
const?extend?=?Object.assign;//?例子: const?data?=?{?name:?'若川'?}; const?data2?=?extend(data,?{?mp:?'若川視野',?name:?'是若川啊'?}); console.log(data);?//?{?name:?"是若川啊",?mp:?"若川視野"?} console.log(data2);?//?{?name:?"是若川啊",?mp:?"若川視野"?} console.log(data?===?data2);?//?true3.9 remove 移除數組的一項
const?remove?=?(arr,?el)?=>?{const?i?=?arr.indexOf(el);if?(i?>?-1)?{arr.splice(i,?1);} };//?例子: const?arr?=?[1,?2,?3]; remove(arr,?3); console.log(arr);?//?[1,?2]splice 其實是一個很耗性能的方法。刪除數組中的一項,其他元素都要移動位置。
引申:`axios InterceptorManager` 攔截器源碼[13] 中,攔截器用數組存儲的。但實際移除攔截器時,只是把攔截器置為 null 。而不是用splice移除。最后執行時為 null 的不執行,同樣效果。axios 攔截器這個場景下,不得不說為性能做到了很好的考慮。
看如下 axios 攔截器代碼示例:
//?代碼有刪減 //?聲明 this.handlers?=?[];//?移除 if?(this.handlers[id])?{this.handlers[id]?=?null; }//?執行 if?(h?!==?null)?{fn(h); }3.10 hasOwn 是不是自己本身所擁有的屬性
const?hasOwnProperty?=?Object.prototype.hasOwnProperty; const?hasOwn?=?(val,?key)?=>?hasOwnProperty.call(val,?key);//?例子://?特別提醒:__proto__?是瀏覽器實現的原型寫法,后面還會用到 //?現在已經有提供好幾個原型相關的API //?Object.getPrototypeOf //?Object.setPrototypeOf //?Object.isPrototypeOf// .call 則是函數里 this 顯示指定以為第一個參數,并執行函數。hasOwn({__proto__:?{?a:?1?}},?'a')?//?false hasOwn({?a:?undefined?},?'a')?//?true hasOwn({},?'a')?//?false hasOwn({},?'hasOwnProperty')?//?false hasOwn({},?'toString')?//?false //?是自己的本身擁有的屬性,不是通過原型鏈向上查找的。對象API可以看我之前寫的一篇文章JavaScript 對象所有API解析,寫的還算全面。
3.11 isArray 判斷數組
const?isArray?=?Array.isArray;isArray([]);?//?true const?fakeArr?=?{?__proto__:?Array.prototype,?length:?0?}; isArray(fakeArr);?//?false fakeArr?instanceof?Array;?//?true //?所以?instanceof?這種情況?不準確3.12 isMap 判斷是不是 Map 對象
const?isMap?=?(val)?=>?toTypeString(val)?===?'[object?Map]';//?例子: const?map?=?new?Map(); const?o?=?{?p:?'Hello?World'?};map.set(o,?'content'); map.get(o);?//?'content' isMap(map);?//?trueES6 提供了 Map 數據結構。它類似于對象,也是鍵值對的集合,但是“鍵”的范圍不限于字符串,各種類型的值(包括對象)都可以當作鍵。也就是說,Object 結構提供了“字符串—值”的對應,Map 結構提供了“值—值”的對應,是一種更完善的 Hash 結構實現。如果你需要“鍵值對”的數據結構,Map 比 Object 更合適。
3.13 isSet 判斷是不是 Set 對象
const?isSet?=?(val)?=>?toTypeString(val)?===?'[object?Set]';//?例子: const?set?=?new?Set(); isSet(set);?//?trueES6 提供了新的數據結構 Set。它類似于數組,但是成員的值都是唯一的,沒有重復的值。
Set本身是一個構造函數,用來生成 Set 數據結構。
3.14 isDate 判斷是不是 Date 對象
const?isDate?=?(val)?=>?val?instanceof?Date;//?例子: isDate(new?Date());?//?true//?`instanceof`?操作符左邊是右邊的實例。但不是很準,但一般夠用了。原理是根據原型鏈向上查找的。isDate({__proto__?:?new?Date());?//?true //?實際上是應該是 Object 才對。 //?所以用 instanceof 判斷數組也不準確。 //?再比如 ({__proto__:?[]?})?instanceof?Array;?//?true //?實際上是對象。 //?所以用?數組本身提供的方法 Array.isArray 是比較準確的。3.15 isFunction 判斷是不是函數
const?isFunction?=?(val)?=>?typeof?val?===?'function'; //?判斷數組有多種方法,但這個是比較常用也相對兼容性好的。3.16 isString 判斷是不是字符串
const?isString?=?(val)?=>?typeof?val?===?'string';//?例子: isString('')?//?true3.17 isSymbol 判斷是不是 Symbol
const?isSymbol?=?(val)?=>?typeof?val?===?'symbol';//?例子: let?s?=?Symbol();typeof?s; //?"symbol" // Symbol 是函數,不需要用 new 調用。ES6 引入了一種新的原始數據類型Symbol,表示獨一無二的值。
3.18 isObject 判斷是不是對象
const?isObject?=?(val)?=>?val?!==?null?&&?typeof?val?===?'object';//?例子: isObject(null);?//?false isObject({name:?'若川'});?//?true //?判斷不為?null?的原因是?typeof?null?其實?是?object3.19 isPromise 判斷是不是 Promise
const?isPromise?=?(val)?=>?{return?isObject(val)?&&?isFunction(val.then)?&&?isFunction(val.catch); };//?判斷是不是Promise對象 const?p1?=?new?Promise(function(resolve,?reject){resolve('若川'); }); isPromise(p1);?//?true// promise 對于初學者來說可能比較難理解。但是重點內容,JS異步編程,要著重掌握。 //?現在 web 開發 Promise 和 async await 等非常常用。可以根據文末推薦的書籍看Promise相關章節掌握。同時也推薦這本迷你書JavaScript Promise迷你書(中文版)[14]
3.20 objectToString 對象轉字符串
const?objectToString?=?Object.prototype.toString;//?對象轉字符串3.21 toTypeString ?對象轉字符串
const?toTypeString?=?(value)?=>?objectToString.call(value);// call 是一個函數,第一個參數是?執行函數里面 this 指向。 //?通過這個能獲得?類似??"[object?String]"?其中?String?是根據類型變化的3.22 toRawType ?對象轉字符串 截取后幾位
const?toRawType?=?(value)?=>?{//?extract?"RawType"?from?strings?like?"[object?RawType]"return?toTypeString(value).slice(8,?-1); };//?截取到 toRawType('');??'String'可以 截取到 String Array 等這些類型
是 JS 判斷數據類型非常重要的知識點。
JS 判斷類型也有 ?typeof ,但不是很準確,而且能夠識別出的不多。
這些算是基礎知識
mdn typeof 文檔[15],文檔比較詳細,也實現了一個很完善的type函數,本文就不贅述了。
//?typeof?返回值目前有以下8種? 'undefined' 'object' 'boolean' 'number' 'bigint' 'string' 'symobl' 'function'3.23 isPlainObject 判斷是不是純粹的對象
const?objectToString?=?Object.prototype.toString; const?toTypeString?=?(value)?=>?objectToString.call(value); //? const?isPlainObject?=?(val)?=>?toTypeString(val)?===?'[object?Object]';//?前文中?有 isObject 判斷是不是對象了。 //?isPlainObject?這個函數在很多源碼里都有,比如?jQuery?源碼和?lodash?源碼等,具體實現不一樣 //?上文的?isObject([])?也是?true?,因為?type?[]?為?'object' //?而?isPlainObject([])?則是false const?Ctor?=?function(){this.name?=?'我是構造函數'; } isPlainObject({});?//?true isPlainObject(new?Ctor());?//?true3.24 isIntegerKey 判斷是不是數字型的字符串key值
const?isIntegerKey?=?(key)?=>?isString(key)?&&key?!==?'NaN'?&&key[0]?!==?'-'?&&''?+?parseInt(key,?10)?===?key;//?例子: isIntegerKey('a');?//?false isIntegerKey('0');?//?true isIntegerKey('011');?//?false isIntegerKey('11');?//?true //?其中 parseInt 第二個參數是進制。 //?字符串能用數組取值的形式取值。 //??還有一個?charAt?函數,但不常用? 'abc'.charAt(0)?//?'a' //?charAt?與數組形式不同的是?取不到值會返回空字符串'',數組形式取值取不到則是?undefined3.25 makeMap && isReservedProp
傳入一個以逗號分隔的字符串,生成一個 map(鍵值對),并且返回一個函數檢測 key 值在不在這個 map 中。第二個參數是小寫選項。
/***?Make?a?map?and?return?a?function?for?checking?if?a?key*?is?in?that?map.*?IMPORTANT:?all?calls?of?this?function?must?be?prefixed?with*?\/\*#\_\_PURE\_\_\*\/*?So?that?rollup?can?tree-shake?them?if?necessary.*/ function?makeMap(str,?expectsLowerCase)?{const?map?=?Object.create(null);const?list?=?str.split(',');for?(let?i?=?0;?i?<?list.length;?i++)?{map[list[i]]?=?true;}return?expectsLowerCase???val?=>?!!map[val.toLowerCase()]?:?val?=>?!!map[val]; } const?isReservedProp?=?/*#__PURE__*/?makeMap( //?the?leading?comma?is?intentional?so?empty?string?""?is?also?included ',key,ref,'?+'onVnodeBeforeMount,onVnodeMounted,'?+'onVnodeBeforeUpdate,onVnodeUpdated,'?+'onVnodeBeforeUnmount,onVnodeUnmounted');//?保留的屬性 isReservedProp('key');?//?true isReservedProp('ref');?//?true isReservedProp('onVnodeBeforeMount');?//?true isReservedProp('onVnodeMounted');?//?true isReservedProp('onVnodeBeforeUpdate');?//?true isReservedProp('onVnodeUpdated');?//?true isReservedProp('onVnodeBeforeUnmount');?//?true isReservedProp('onVnodeUnmounted');?//?true3.26 cacheStringFunction 緩存
const?cacheStringFunction?=?(fn)?=>?{const?cache?=?Object.create(null);return?((str)?=>?{const?hit?=?cache[str];return?hit?||?(cache[str]?=?fn(str));}); };這個函數也是和上面 MakeMap 函數類似。只不過接收參數的是函數。《JavaScript 設計模式與開發實踐》書中的第四章 JS單例模式也是類似的實現。
var?getSingle?=?function(fn){?//?獲取單例var?result;return?function(){return?result?||?(result?=?fn.apply(this,?arguments));} };以下是一些正則,系統學習正則推薦老姚:《JavaScript 正則表達式迷你書》問世了![16],看過的都說好。所以本文不會過多描述正則相關知識點。
//?\w?是?0-9a-zA-Z_?數字?大小寫字母和下劃線組成 //?()?小括號是?分組捕獲 const?camelizeRE?=?/-(\w)/g; /***?@private*/ //?連字符?-?轉駝峰??on-click?=>?onClick const?camelize?=?cacheStringFunction((str)?=>?{return?str.replace(camelizeRE,?(_,?c)?=>?(c???c.toUpperCase()?:?'')); }); //?\B 是指?非?\b 單詞邊界。 const?hyphenateRE?=?/\B([A-Z])/g; /***?@private*/const?hyphenate?=?cacheStringFunction((str)?=>?str.replace(hyphenateRE,?'-$1').toLowerCase());//?舉例:onClick => on-click const?hyphenateResult?=?hyphenate('onClick'); console.log('hyphenateResult',?hyphenateResult);?//?'on-click'/***?@private*/ //?首字母轉大寫 const?capitalize?=?cacheStringFunction((str)?=>?str.charAt(0).toUpperCase()?+?str.slice(1)); /***?@private*/ //?click?=>?onClick const?toHandlerKey?=?cacheStringFunction((str)?=>?(str???`on${capitalize(str)}`?:?``));const?result?=?toHandlerKey('click'); console.log(result,?'result');?//?'onClick'3.27 hasChanged 判斷是不是有變化
//?compare?whether?a?value?has?changed,?accounting?for?NaN. const?hasChanged?=?(value,?oldValue)?=>?value?!==?oldValue?&&?(value?===?value?||?oldValue?===?oldValue); //?例子: //?認為?NaN?是不變的 hasChanged(NaN,?NaN);?//?false hasChanged(1,?1);?//?false hasChanged(1,?2);?//?true hasChanged(+0,?-0);?//?false //?Obect.is?認為?+0?和?-0?不是同一個值 Object.is(+0,?-0);?//?false??????????? //?Object.is?認為??NaN?和?本身?相比?是同一個值 Object.is(NaN,?NaN);?//?true //?場景 //?watch?監測值是不是變化了//?(value?===?value?||?oldValue?===?oldValue) //?為什么會有這句?因為要判斷 NaN 。認為 NaN 是不變的。因為 NaN === NaN 為 false根據 hasChanged 這個我們繼續來看看:Object.is API。
Object.is(value1, value2) (ES6)
該方法用來比較兩個值是否嚴格相等。它與嚴格比較運算符(===)的行為基本一致。不同之處只有兩個:一是+0不等于-0,而是 NaN 等于自身。
Object.is('若川',?'若川');?//?true Object.is({},{});?//?false Object.is(+0,?-0);?//?false +0?===?-0;?//?true Object.is(NaN,?NaN);?//?true NaN?===?NaN;?//?falseES5可以通過以下代碼部署Object.is。
Object.defineProperty(Object,?'is',?{value:?function()?{x,?y}?{if?(x?===?y)?{//?針對+0不等于-0的情況return?x?!==?0?||?1?/?x?===?1?/?y;}//?針對?NaN的情況return?x?!==?x?&&?y?!==?y;},configurable:?true,enumerable:?false,writable:?true });根據舉例可以說明
3.28 invokeArrayFns ?執行數組里的函數
const?invokeArrayFns?=?(fns,?arg)?=>?{for?(let?i?=?0;?i?<?fns.length;?i++)?{fns[i](arg?"i");} };//?例子: const?arr?=?[function(val){console.log(val?+?'的博客地址是:https://lxchuan12.gitee.io');},function(val){console.log('百度搜索?若川?可以找到'?+?val);},function(val){console.log('微信搜索?若川視野?可以找到關注'?+?val);}, ] invokeArrayFns(arr,?'我');為什么這樣寫,我們一般都是一個函數執行就行。
數組中存放函數,函數其實也算是數據。這種寫法方便統一執行多個函數。
3.29 def 定義對象屬性
const?def?=?(obj,?key,?value)?=>?{Object.defineProperty(obj,?key,?{configurable:?true,enumerable:?false,value}); };Object.defineProperty 算是一個非常重要的API。還有一個定義多個屬性的API:Object.defineProperties(obj, props) (ES5)
Object.defineProperty 涉及到比較重要的知識點。
在ES3中,除了一些內置屬性(如:Math.PI),對象的所有的屬性在任何時候都可以被修改、插入、刪除。在ES5中,我們可以設置屬性是否可以被改變或是被刪除——在這之前,它是內置屬性的特權。ES5中引入了屬性描述符的概念,我們可以通過它對所定義的屬性有更大的控制權。這些屬性描述符(特性)包括:
value——當試圖獲取屬性時所返回的值。
writable——該屬性是否可寫。
enumerable——該屬性在for in循環中是否會被枚舉。
configurable——該屬性是否可被刪除。
set()——該屬性的更新操作所調用的函數。
get()——獲取屬性值時所調用的函數。
另外,數據描述符(其中屬性為:enumerable,configurable,value,writable)與存取描述符(其中屬性為enumerable,configurable,set(),get())之間是有互斥關系的。在定義了set()和get()之后,描述符會認為存取操作已被 定義了,其中再定義value和writable會引起錯誤。
以下是ES3風格的屬性定義方式:
var?person?=?{}; person.legs?=?2;以下是等價的ES5通過數據描述符定義屬性的方式:
var?person?=?{}; Object.defineProperty(person,?'legs',?{value:?2,writable:?true,configurable:?true,enumerable:?true });其中, 除了value的默認值為undefined以外,其他的默認值都為false。這就意味著,如果想要通過這一方式定義一個可寫的屬性,必須顯示將它們設為true。或者,我們也可以通過ES5的存儲描述符來定義:
var?person?=?{}; Object.defineProperty(person,?'legs',?{set:function(v)?{return?this.value?=?v;},get:?function(v)?{return?this.value;},configurable:?true,enumerable:?true }); person.legs?=?2;這樣一來,多了許多可以用來描述屬性的代碼,如果想要防止別人篡改我們的屬性,就必須要用到它們。此外,也不要忘了瀏覽器向后兼容ES3方面所做的考慮。例如,跟添加Array.prototype屬性不一樣,我們不能再舊版的瀏覽器中使用shim這一特性。另外,我們還可以(通過定義nonmalleable屬性),在具體行為中運用這些描述符:
var?person?=?{}; Object.defineProperty(person,?'heads',?{value:?1}); person.heads?=?0;?//?0 person.heads;?//?1??(改不了) delete?person.heads;?//?false person.heads?//?1?(刪不掉)其他本文就不過多贅述了。更多對象 API 可以查看這篇文章JavaScript 對象所有API解析。
3.30 toNumber 轉數字
const?toNumber?=?(val)?=>?{const?n?=?parseFloat(val);return?isNaN(n)???val?:?n; };toNumber('111');?//?111 toNumber('a111');?//?'a111' parseFloat('a111');?//?NaN isNaN(NaN);?//?true其實 isNaN 本意是判斷是不是 NaN 值,但是不準確的。比如:isNaN('a') 為 true。所以 ES6 有了 Number.isNaN 這個判斷方法,為了彌補這一個API。
Number.isNaN('a')??//?false Number.isNaN(NaN);?//?true3.31 getGlobalThis 全局對象
let?_globalThis; const?getGlobalThis?=?()?=>?{return?(_globalThis?||(_globalThis?=typeof?globalThis?!==?'undefined'??globalThis:?typeof?self?!==?'undefined'??self:?typeof?window?!==?'undefined'??window:?typeof?global?!==?'undefined'??global:?{})); };獲取全局 this 指向。
初次執行肯定是 _globalThis 是 undefined。所以會執行后面的賦值語句。
如果存在 globalThis 就用 globalThis。MDN globalThis[17]
如果存在self,就用self。在 Web Worker 中不能訪問到 window 對象,但是我們卻能通過 self 訪問到 Worker 環境中的全局對象。
如果存在window,就用window。
如果存在global,就用global。Node環境下,使用global。
如果都不存在,使用空對象。可能是微信小程序環境下。
下次執行就直接返回 _globalThis,不需要第二次繼續判斷了。這種寫法值得我們學習。
4. 最后推薦一些文章和書籍
先推薦我認為不錯的JavaScript API的幾篇文章和幾本值得讀的書。
JavaScript字符串所有API全解密[18]
【深度長文】JavaScript數組所有API全解密[19]
正則表達式前端使用手冊[20]
老姚:《JavaScript 正則表達式迷你書》問世了![21]
JavaScript 對象所有API解析 https://lxchuan12.gitee.io/js-object-api/
MDN JavaScript[22]
《JavaScript高級程序設計》第4版[23]
《JavaScript 權威指南》第7版[24]
《JavaScript面向對象編程2》[25] 面向對象講的很詳細。
阮一峰老師:《ES6 入門教程》[26]
《現代 JavaScript 教程》[27]
《你不知道的JavaScript》上中卷[28]
《JavaScript 設計模式與開發實踐》[29]
我也是從小白看不懂書經歷過來的。到現在寫文章分享。
我看書的方法:多本書同時看,看相同類似的章節,比如函數。看完這本可能沒懂,看下一本,幾本書看下來基本就懂了,一遍沒看懂,再看幾遍,可以避免遺忘,鞏固相關章節。當然,剛開始看書很難受,看不進。這些書大部分在微信讀書都有,如果習慣看紙質書,那可以買來看。
這時可以看些視頻和動手練習一些簡單的項目。
比如:可以自己注冊一個github賬號,分章節小節,抄寫書中的代碼,提交到github,練習了才會更有感覺。
再比如 freeCodeCamp 中文在線學習網站[30] 網站。看書是系統學習非常好的方法。后來我就是看源碼較多,寫文章分享出來給大家。
5. 總結
文中主要通過學習 shared 模塊下的幾十個工具函數,比如有:isPromise、makeMap、cacheStringFunction、invokeArrayFns、def、getGlobalThis等等。
同時還分享了vue源碼的調試技巧,推薦了一些書籍和看書籍的方法。
源碼也不是那么可怕。平常我們工作中也是經常能使用到這些工具函數。通過學習一些簡單源碼,拓展視野的同時,還能落實到自己工作開發中,收益相對比較高。
參考資料
[1]
vue-next: https://github.com/vuejs/vue-next
[2].github/contributing.md: https://github.com/vuejs/vue-next/blob/master/.github/contributing.md
[3]更多參考資料可以點擊 閱讀原文 查看
最近組建了一個江西人的前端交流群,如果你是江西人可以加我微信?ruochuan12?私信 江西?拉你進群。
推薦閱讀
我在阿里招前端,該怎么幫你(可進面試群)
我讀源碼的經歷
面對 this 指向丟失,尤雨溪在 Vuex 源碼中是怎么處理的
老姚淺談:怎么學JavaScript?
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》多篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,活躍在知乎@若川,掘金@若川。致力于分享前端開發經驗,愿景:幫助5年內前端人走向前列。
識別上方二維碼加我微信、拉你進源碼共讀群
今日話題
略。歡迎分享、收藏、點贊、在看我的公眾號文章~
總結
以上是生活随笔為你收集整理的初学者也能看懂的 Vue3 源码中那些实用的基础工具函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习(3163):react-hel
- 下一篇: Clickhouse 实现row_num