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
等進入函數,就可以愉快的調試源碼了。

3. 工具函數
本文主要按照源碼 `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);?//?0
3.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;//?除了壓縮代碼的好處外。
//?一直返回?false
3.6 isOn 判斷字符串是不是 on 開頭,并且 on 后首字母不是小寫字母
const?onRE?=?/^on[^a-z]/;
const?isOn?=?(key)?=>?onRE.test(key);//?例子:
isOn('onChange');?//?true
isOn('onchange');?//?false
isOn('on3change');?//?true
onRE
是正則。^
符號在開頭,則表示是什么開頭。而在其他地方是指非。
與之相反的是:$
符合在結尾,則表示是以什么結尾。
[^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);?//?true
3.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);?//?true
ES6 提供了 Map 數據結構。它類似于對象,也是鍵值對的集合,但是“鍵”的范圍不限于字符串,各種類型的值(包括對象)都可以當作鍵。也就是說,Object 結構提供了“字符串—值”的對應,Map 結構提供了“值—值”的對應,是一種更完善的 Hash 結構實現。如果你需要“鍵值對”的數據結構,Map 比 Object 更合適。
3.13 isSet 判斷是不是 Set 對象
const?isSet?=?(val)?=>?toTypeString(val)?===?'[object?Set]';//?例子:
const?set?=?new?Set();
isSet(set);?//?true
ES6
提供了新的數據結構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('')?//?true
3.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?其實?是?object
3.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());?//?true
3.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?與數組形式不同的是?取不到值會返回空字符串'',數組形式取值取不到則是?undefined
3.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');?//?true
3.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;?//?false
ES5
可以通過以下代碼部署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);?//?true
3.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年內前端人走向前列。
識別上方二維碼加我微信、拉你進源碼共讀群
今日話題
略。歡迎分享、收藏、點贊、在看我的公眾號文章~