初學者也能看懂的 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.mdcontributing.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-debugger

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_ENVnode 項目中的一個環境變量,一般定義為:developmentproduction。根據環境寫代碼。比如開發環境,有報錯等信息,生產環境則不需要這些報錯警告。

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]是指不是az的小寫字母。

同時推薦一個正則在線工具。

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。還有一個定義多個屬性的APIObject.defineProperties(obj, props) (ES5)

Object.defineProperty 涉及到比較重要的知識點。
ES3中,除了一些內置屬性(如:Math.PI),對象的所有的屬性在任何時候都可以被修改、插入、刪除。在ES5中,我們可以設置屬性是否可以被改變或是被刪除——在這之前,它是內置屬性的特權。ES5中引入了屬性描述符的概念,我們可以通過它對所定義的屬性有更大的控制權。這些屬性描述符(特性)包括:

value——當試圖獲取屬性時所返回的值。
writable——該屬性是否可寫。
enumerable——該屬性在for in循環中是否會被枚舉。
configurable——該屬性是否可被刪除。
set()——該屬性的更新操作所調用的函數。
get()——獲取屬性值時所調用的函數。

另外,數據描述符(其中屬性為:enumerableconfigurablevaluewritable)與存取描述符(其中屬性為enumerableconfigurableset()get())之間是有互斥關系的。在定義了set()get()之后,描述符會認為存取操作已被 定義了,其中再定義valuewritable引起錯誤

以下是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 指向。

初次執行肯定是 _globalThisundefined。所以會執行后面的賦值語句。

如果存在 globalThis 就用 globalThis。MDN globalThis[17]

如果存在self,就用self。在 Web Worker 中不能訪問到 window 對象,但是我們卻能通過 self 訪問到 Worker 環境中的全局對象。

如果存在window,就用window

如果存在global,就用globalNode環境下,使用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 模塊下的幾十個工具函數,比如有:isPromisemakeMapcacheStringFunctioninvokeArrayFnsdefgetGlobalThis等等。

同時還分享了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年內前端人走向前列。

識別方二維碼加我微信、拉你進源碼共讀

今日話題

略。歡迎分享、收藏、點贊、在看我的公眾號文章~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/275554.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/275554.shtml
英文地址,請注明出處:http://en.pswp.cn/news/275554.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Flutter RichText支持自定義文字背景

extended text 相關文章 Flutter RichText支持圖片顯示和自定義圖片效果Flutter RichText支持自定義文本溢出效果Flutter RichText支持自定義文字背景Flutter RichText支持特殊文字效果之前介紹過了Extended text&#xff0c;老規矩上圖 UI設計說&#xff0c;那個字可以加個卟呤…

細說 Vue.js 3.2 關于響應式部分的優化

大家好&#xff0c;我是若川。上一篇寫的是&#xff1a;初學者也能看懂的 Vue3 源碼中那些實用的基礎工具函數。今天再分享一篇 Vue 3.2 的文章。學習源碼整體架構系列、年度總結、JS基礎系列背景Vue 3 正式發布距今已經快一年了&#xff0c;相信很多小伙伴已經在生產環境用上了…

linux 運行apj,pxe+ris-linux實現在DELL R710上網絡安裝windows2003

一、前言網絡遠程安裝windows2003&#xff0c;目前有兩種方法&#xff1a;一就是通過windows自帶的遠程安裝服務(RIS&#xff0c;Remote Installation Service)&#xff0c;但這種方法需要用windows做為源服務器&#xff0c;需要安裝域控制器&#xff0c;dhcp&#xff0c;tftp等…

Debian Security Advisory(Debian安全報告) DSA-4411-1 firefox-esr security update

Debian Security Advisory(Debian安全報告) DSA-4411-1 firefox-esr security update Package &#xff1a;firefox-esr CVE ID&#xff1a; CVE-2018-18506 CVE-2019-9788 CVE-2019-9790 CVE-2019-9791 CVE-2019-9792 CVE-2019-9793 CVE-2019-9795 CVE-2019-9796 CVE…

Entity framework WhereInExtension

摘自 http://www.cnblogs.com/ejiyuan/archive/2009/07/20/1527224.html publicstaticclassWhereInExtension {privatestaticExpression<Func<TElement, bool>>BuildWhereInExpression<TElement, TValue>(Expression<Func<TElement, TValue>>pro…

寫給初中級前端的高級進階指南

大家好&#xff0c;我是若川。最近組織了源碼共讀活動。每周讀 200 行左右的源碼。很多第一次讀源碼的小伙伴都感覺很有收獲&#xff0c;感興趣可以加我微信ruochuan12&#xff0c;拉你進群學習。前言我曾經一度很迷茫&#xff0c;在學了 Vue、React 的實戰開發和應用以后&…

Spring Boot Log4j2 日志學習

簡介 Java 中比較常用的日志工具類&#xff0c;有&#xff1a; Log4j、SLF4j、Commons-logging&#xff08;簡稱jcl&#xff09;、Logback、Log4j2&#xff08;Log4j 升級版&#xff09;、Jdk LoggingSpring Boot 默認使用 Logback&#xff0c;但相比較而言&#xff0c;Log4j2 …

學習java過程中

今天看了一個java的代碼&#xff0c;結果出現Class bytes found but defineClass()failed for的錯誤&#xff0c;在網上google了一把&#xff0c;找到原因是&#xff1a;我的編譯環境的jdk和代碼的jdk不一致&#xff0c;比原來的jdk要高。換一下就解決了。轉載于:https://www.c…

linux系統遠程教程,Linux下實現遠程協助

一、檢查系統是否安裝有tcl和expect這2個軟件包[rootlocalhost:~]$ rpm -qa | grep tcltcl-8.4.7-2tclx-8.3.5-4[rootlocalhost:~]$ rpm -qa | grep expectexpect-5.42.1-1二、檢查是否有kibitz命令[rootlocalhost:~]$ whereis kibitzkibitz: /usr/bin/kibitz /usr/share/man/m…

圖片相似度對比原理_設計原理:對比和相似性的應用

圖片相似度對比原理You know why you are able to read this article right now apart from the availability of your eyes, internet, device, etc.? What is the font color of this text you’re reading? — Black. What is the background color of this page you’re …

學習尤雨溪寫的 Vue3 源碼中的簡單工具函數

大家好&#xff0c;我是若川。最近組織了源碼共讀活動。每周讀 200 行左右的源碼。很多第一次讀源碼的小伙伴都感覺很有收獲&#xff0c;感興趣可以加我微信ruochuan12&#xff0c;拉你進群學習。初學者也能看懂的 Vue3 源碼中那些實用的基礎工具函數本文是紀年小姐姐源碼共讀第…

APK 本地化

一個APK反編譯利器Apktool(android漢化)2010-07-19 18:52轉載自&#xff1a;http://blog.sina.com.cn/s/blog_5752764e0100kv34.html APK 本地化 [http://www.andmoto.com/viewthread.php?tid3873]說起APK的漢化&#xff0c;目前大部分教程都是讓用Hex Workshop或者Android R…

Linux manjaro系統安裝后無法連接wifi,解決方案

2019獨角獸企業重金招聘Python工程師標準>>> 筆記本為聯想 thinkpad E480 首先通過命令lspci -k看一下原因是否為缺少wifi驅動&#xff0c;如下&#xff0c;如果沒有Kernel driver in use&#xff0c;說明缺少驅動。05:00.0 Network controller: Realtek Semiconduc…

檢測輸入路徑是否存在錯誤_為什么存在用戶輸入錯誤

檢測輸入路徑是否存在錯誤Errors are a fact of life when using almost any type of software. Forms are the worst though. Nothing is more frustrating than filling out a form and getting a robotic message from the computer telling you that you have failed, plea…

若川邀你進 源碼共讀 群~長期交流學習

大家好&#xff0c;我是若川。這是一個愉快的周六~估計還是有很多讀者不知道我。若川名字由來是取自&#xff1a;上善若水&#xff0c;海納百川。順便放兩篇文章。我讀源碼的經歷&#xff0c;跟各位讀者朋友分享下公眾號運營策略加我微信進 源碼共讀 群最近組織了近200人每周源…

2005 打開 2010 項目經驗總結

下面是網上的直接復制粘貼&#xff1a;網址為 http://hi.baidu.com/zealot886/blog/item/7364d4266a2a1555ac34dea6.html/cmtid/65ff140a660e02246159f3db 這里是我自己的總結 &#xff08; 1、用vs2010 將該解決方案的所有 項目都改為 net 2.0&#xff08;方法&#xff0c;右擊…

讀取linux的運行狀態,Linux下安裝使用sar工具來獲取系統運行狀態

sar 找出系統瓶頸的利器sar是System Activity Reporter(系統活動情況報告)的縮寫。sar工具將對系統當前的狀態進行取樣&#xff0c;然后通過計算數據和比例來表達系統的當前運行狀態。它的 特點是可以連續對系統取樣&#xff0c;獲得大量的取樣數據&#xff1b;取樣數據和分析的…

說說 Spring 的事務同步管理器

Spring 將 JDBC 的 Connection、Hibernate 的 Session 等訪問數據庫的連接或者會話對象統稱為資源&#xff0c;這些資源在同一時刻是不能多線程共享的 。 為了讓 DAO 或 Service 類可以實現單例模式&#xff0c; Spring 的事務同步管理類 org.springframework.transaction.supp…

錯過校招_我們在用戶測試中容易錯過的事情

錯過校招What makes a tool well designed? As a designer, I’ve thought about this question for a long time, and over the past few years I’ve developed a system that I now use with every new project I approach, from small startups to large companies like L…

這些 JS 中強大的操作符,總有幾個你沒聽說過

大家好&#xff0c;我是若川。今天推薦一篇相對簡單些的文章。大家應該都知道了我最近組織了源碼共讀活動&#xff0c; 有小伙伴表示讀源碼上癮&#xff0c;也很有收獲。工作0-5年都可以參與。感興趣可以加我微信 ruochuan12 私信 源碼 進群。1. 數值分割符 _2. 逗號運算符 ,3.…