<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          初學(xué)者也能看懂的 Vue3 源碼中那些實(shí)用的基礎(chǔ)工具函數(shù)

          共 28131字,需瀏覽 57分鐘

           ·

          2021-08-23 00:12

          1. 前言

          大家好,我是若川。最近組織了源碼共讀活動(dòng)。每周讀 200 行左右的源碼。很多第一次讀源碼的小伙伴都感覺(jué)很有收獲,感興趣可以加我微信ruochuan12,拉你進(jìn)群學(xué)習(xí)。

          寫(xiě)相對(duì)很難的源碼,耗費(fèi)了自己的時(shí)間和精力,也沒(méi)收獲多少閱讀點(diǎn)贊,其實(shí)是一件挺受打擊的事情。從閱讀量和讀者受益方面來(lái)看,不能促進(jìn)作者持續(xù)輸出文章。

          所以轉(zhuǎn)變思路,寫(xiě)一些相對(duì)通俗易懂的文章。其實(shí)源碼也不是想象的那么難,至少有很多看得懂。比如工具函數(shù)。本文通過(guò)學(xué)習(xí)Vue3源碼中的工具函數(shù)模塊的源碼,學(xué)習(xí)源碼為自己所用。歌德曾說(shuō):讀一本好書(shū),就是在和高尚的人談話。同理可得:讀源碼,也算是和作者的一種學(xué)習(xí)交流的方式。

          閱讀本文,你將學(xué)到:

          1. 如何學(xué)習(xí) JavaScript 基礎(chǔ)知識(shí),會(huì)推薦很多學(xué)習(xí)資料
          2. 如何學(xué)習(xí)調(diào)試 vue 3 源碼
          3. 如何學(xué)習(xí)源碼中優(yōu)秀代碼和思想,投入到自己的項(xiàng)目中
          4. Vue 3 源碼 shared 模塊中的幾十個(gè)實(shí)用工具函數(shù)
          5. 我的一些經(jīng)驗(yàn)分享

          shared模塊中57個(gè)工具函數(shù),本次閱讀其中的30余個(gè)。

          2. 環(huán)境準(zhǔn)備

          2.1 讀開(kāi)源項(xiàng)目 貢獻(xiàn)指南

          打開(kāi) vue-next[1], 開(kāi)源項(xiàng)目一般都能在 README.md 或者 .github/contributing.md[2] 找到貢獻(xiàn)指南。

          而貢獻(xiàn)指南寫(xiě)了很多關(guān)于參與項(xiàng)目開(kāi)發(fā)的信息。比如怎么跑起來(lái),項(xiàng)目目錄結(jié)構(gòu)是怎樣的。怎么投入開(kāi)發(fā),需要哪些知識(shí)儲(chǔ)備等。

          我們可以在 項(xiàng)目目錄結(jié)構(gòu)[3] 描述中,找到shared模塊。

          shared: Internal utilities shared across multiple packages (especially environment-agnostic utils used by both runtime and compiler packages).

          README.mdcontributing.md 一般都是英文的??赡軙?huì)難倒一部分人。其實(shí)看不懂,完全可以可以借助劃詞翻譯,整頁(yè)翻譯和百度翻譯等翻譯工具。再把英文加入后續(xù)學(xué)習(xí)計(jì)劃。

          本文就是講shared模塊,對(duì)應(yīng)的文件路徑是:`vue-next/packages/shared/src/index.ts`[4]

          也可以用github1s訪問(wèn),速度更快。github1s packages/shared/src/index.ts[5]

          2.2 按照項(xiàng)目指南 打包構(gòu)建代碼

          為了降低文章難度,我按照貢獻(xiàn)指南中方法打包把ts轉(zhuǎn)成了js。如果你需要打包,也可以參考下文打包構(gòu)建。

          你需要確保 Node.js[6] 版本是 10+, 而且 yarn 的版本是 1.x Yarn 1.x[7]。

          你安裝的 Node.js 版本很可能是低于 10。最簡(jiǎn)單的辦法就是去官網(wǎng)重新安裝。也可以使用 nvm等管理Node.js版本。

          node -v
          # v14.16.0
          # 全局安裝 yarn
          # 克隆項(xiàng)目
          git clone https://github.com/vuejs/vue-next.git
          cd vue-next

          # 或者克隆我的項(xiàng)目
          git clone https://github.com/lxchuan12/vue-next-analysis.git
          cd vue-next-analysis

          npm install --global yarn
          yarn # install the dependencies of the project
          yarn build

          可以得到 vue-next/packages/shared/dist/shared.esm-bundler.js,文件也就是純js文件。也接下就是解釋其中的一些方法。

          當(dāng)然,前面可能比較啰嗦。我可以直接講 3. 工具函數(shù)。但通過(guò)我上文的介紹,即使是初學(xué)者,都能看懂一些開(kāi)源項(xiàng)目源碼,也許就會(huì)有一定的成就感。另外,面試問(wèn)到被類似的問(wèn)題或者筆試題時(shí),你說(shuō)看Vue3源碼學(xué)到的,面試官絕對(duì)對(duì)你刮目相看。

          2.3 如何生成 sourcemap 調(diào)試 vue-next 源碼

          熟悉我的讀者知道,我是經(jīng)常強(qiáng)調(diào)生成sourcemap調(diào)試看源碼,所以順便提一下如何配置生成sourcemap,如何調(diào)試。這部分可以簡(jiǎn)單略過(guò),動(dòng)手操作時(shí)再仔細(xì)看。

          其實(shí)貢獻(xiàn)指南[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執(zhí)行,即可生成sourcemap,或者直接 build。

          // package.json
          {
              "version""3.2.1",
              "scripts": {
                  "dev:sourcemap""node scripts/dev.js --sourcemap"
              }
          }

          會(huì)在控制臺(tái)輸出類似vue-next/packages/vue/src/index.ts → packages/vue/dist/vue.global.js的信息。

          其中packages/vue/dist/vue.global.js.map 就是sourcemap文件了。

          我們?cè)?Vue3官網(wǎng)找個(gè)例子,在 vue-next/examples/index.html。其內(nèi)容引入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>

          然后我們新建一個(gè)終端窗口,yarn serve,在瀏覽器中打開(kāi)http://localhost:5000/examples/,如下圖所示,按F11等進(jìn)入函數(shù),就可以愉快的調(diào)試源碼了。

          vue-next-debugger

          3. 工具函數(shù)

          本文主要按照源碼 `vue-next/packages/shared/src/index.ts`[9] 的順序來(lái)寫(xiě)。也省去了一些從外部導(dǎo)入的方法。

          我們也可以通過(guò)ts文件,查看使用函數(shù)的位置。同時(shí)在VSCode運(yùn)行調(diào)試JS代碼,我們比較推薦韓老師寫(xiě)的code runner插件。

          3.1 babelParserDefaultPlugins  babel 解析默認(rèn)插件

          /**
           * 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'
          ];

          這里就是幾個(gè)默認(rèn)插件。感興趣看英文注釋查看。

          3.2 EMPTY_OBJ 空對(duì)象

          const EMPTY_OBJ = (process.env.NODE_ENV !== 'production')
              ? Object.freeze({})
              : {};

          // 例子:
          // Object.freeze 是 凍結(jié)對(duì)象
          // 凍結(jié)的對(duì)象最外層無(wú)法修改。
          const EMPTY_OBJ_1 = Object.freeze({});
          EMPTY_OBJ_1.name = '若川';
          console.log(EMPTY_OBJ_1.name); // undefined

          const 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 項(xiàng)目中的一個(gè)環(huán)境變量,一般定義為:developmentproduction。根據(jù)環(huán)境寫(xiě)代碼。比如開(kāi)發(fā)環(huán)境,有報(bào)錯(cuò)等信息,生產(chǎn)環(huán)境則不需要這些報(bào)錯(cuò)警告。

          3.3 EMPTY_ARR 空數(shù)組

          const EMPTY_ARR = (process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];

          // 例子:
          EMPTY_ARR.push(1// 報(bào)錯(cuò),也就是為啥生產(chǎn)環(huán)境還是用 []
          EMPTY_ARR.length = 3;
          console.log(EMPTY_ARR.length); // 0

          3.4 NOOP 空函數(shù)

          const NOOP = () => { };

          // 很多庫(kù)的源碼中都有這樣的定義函數(shù),比如 jQuery、underscore、lodash 等
          // 使用場(chǎng)景: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 永遠(yuǎn)返回 false 的函數(shù)

          /**
           * Always return false.
           */

          const NO = () => false;

          // 除了壓縮代碼的好處外。
          // 一直返回 false

          3.6 isOn 判斷字符串是不是 on 開(kāi)頭,并且 on 后首字母不是小寫(xiě)字母

          const onRE = /^on[^a-z]/;
          const isOn = (key) => onRE.test(key);

          // 例子:
          isOn('onChange'); // true
          isOn('onchange'); // false
          isOn('on3change'); // true

          onRE 是正則。^符號(hào)在開(kāi)頭,則表示是什么開(kāi)頭。而在其他地方是指非。

          與之相反的是:$符合在結(jié)尾,則表示是以什么結(jié)尾。

          [^a-z]是指不是az的小寫(xiě)字母。

          同時(shí)推薦一個(gè)正則在線工具。

          regex101[10]

          另外正則看老姚的迷你書(shū)就夠用了。

          老姚:《JavaScript 正則表達(dá)式迷你書(shū)》問(wèn)世了![11]

          3.7 isModelListener 監(jiān)聽(tīng)器

          判斷字符串是不是以onUpdate:開(kāi)頭

          const isModelListener = (key) => key.startsWith('onUpdate:');

          // 例子:
          isModelListener('onUpdate:change'); // true
          isModelListener('1onUpdate:change'); // false
          // startsWith 是 ES6 提供的方法

          ES6入門教程:字符串的新增方法[12]

          很多方法都在《ES6入門教程》中有講到,就不贅述了。

          3.8 extend 繼承 合并

          說(shuō)合并可能更準(zhǔn)確些。

          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 移除數(shù)組的一項(xiàng)

          const remove = (arr, el) => {
              const i = arr.indexOf(el);
              if (i > -1) {
                  arr.splice(i, 1);
              }
          };

          // 例子:
          const arr = [123];
          remove(arr, 3);
          console.log(arr); // [1, 2]

          splice 其實(shí)是一個(gè)很耗性能的方法。刪除數(shù)組中的一項(xiàng),其他元素都要移動(dòng)位置。

          引申`axios InterceptorManager` 攔截器源碼[13] 中,攔截器用數(shù)組存儲(chǔ)的。但實(shí)際移除攔截器時(shí),只是把攔截器置為 null 。而不是用splice移除。最后執(zhí)行時(shí)為 null 的不執(zhí)行,同樣效果。axios 攔截器這個(gè)場(chǎng)景下,不得不說(shuō)為性能做到了很好的考慮。

          看如下 axios 攔截器代碼示例:

          // 代碼有刪減
          // 聲明
          this.handlers = [];

          // 移除
          if (this.handlers[id]) {
              this.handlers[id] = null;
          }

          // 執(zhí)行
          if (h !== null) {
              fn(h);
          }

          3.10 hasOwn 是不是自己本身所擁有的屬性

          const hasOwnProperty = Object.prototype.hasOwnProperty;
          const hasOwn = (val, key) => hasOwnProperty.call(val, key);

          // 例子:

          // 特別提醒:__proto__ 是瀏覽器實(shí)現(xiàn)的原型寫(xiě)法,后面還會(huì)用到
          // 現(xiàn)在已經(jīng)有提供好幾個(gè)原型相關(guān)的API
          // Object.getPrototypeOf
          // Object.setPrototypeOf
          // Object.isPrototypeOf

          // .call 則是函數(shù)里 this 顯示指定以為第一個(gè)參數(shù),并執(zhí)行函數(shù)。

          hasOwn({__proto__: { a1 }}, 'a'// false
          hasOwn({ aundefined }, 'a'// true
          hasOwn({}, 'a'// false
          hasOwn({}, 'hasOwnProperty'// false
          hasOwn({}, 'toString'// false
          // 是自己的本身?yè)碛械膶傩裕皇峭ㄟ^(guò)原型鏈向上查找的。

          對(duì)象API可以看我之前寫(xiě)的一篇文章JavaScript 對(duì)象所有API解析,寫(xiě)的還算全面。

          3.11 isArray 判斷數(shù)組

          const isArray = Array.isArray;

          isArray([]); // true
          const fakeArr = { __proto__Array.prototype, length0 };
          isArray(fakeArr); // false
          fakeArr instanceof Array// true
          // 所以 instanceof 這種情況 不準(zhǔn)確

          3.12 isMap 判斷是不是 Map 對(duì)象

          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 數(shù)據(jù)結(jié)構(gòu)。它類似于對(duì)象,也是鍵值對(duì)的集合,但是“鍵”的范圍不限于字符串,各種類型的值(包括對(duì)象)都可以當(dāng)作鍵。也就是說(shuō),Object 結(jié)構(gòu)提供了“字符串—值”的對(duì)應(yīng),Map 結(jié)構(gòu)提供了“值—值”的對(duì)應(yīng),是一種更完善的 Hash 結(jié)構(gòu)實(shí)現(xiàn)。如果你需要“鍵值對(duì)”的數(shù)據(jù)結(jié)構(gòu),Map 比 Object 更合適。

          3.13 isSet 判斷是不是 Set 對(duì)象

          const isSet = (val) => toTypeString(val) === '[object Set]';

          // 例子:
          const set = new Set();
          isSet(set); // true

          ES6 提供了新的數(shù)據(jù)結(jié)構(gòu) Set。它類似于數(shù)組,但是成員的值都是唯一的,沒(méi)有重復(fù)的值。

          Set本身是一個(gè)構(gòu)造函數(shù),用來(lái)生成 Set 數(shù)據(jù)結(jié)構(gòu)。

          3.14 isDate 判斷是不是 Date 對(duì)象

          const isDate = (val) => val instanceof Date;

          // 例子:
          isDate(new Date()); // true

          // `instanceof` 操作符左邊是右邊的實(shí)例。但不是很準(zhǔn),但一般夠用了。原理是根據(jù)原型鏈向上查找的。

          isDate({__proto__ : new Date()); // true
          // 實(shí)際上是應(yīng)該是 Object 才對(duì)。
          // 所以用 instanceof 判斷數(shù)組也不準(zhǔn)確。
          // 再比如
          ({__proto__: [] }) instanceof Array// true
          // 實(shí)際上是對(duì)象。
          // 所以用 數(shù)組本身提供的方法 Array.isArray 是比較準(zhǔn)確的。

          3.15 isFunction 判斷是不是函數(shù)

          const isFunction = (val) => typeof val === 'function';
          // 判斷數(shù)組有多種方法,但這個(gè)是比較常用也相對(duì)兼容性好的。

          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 是函數(shù),不需要用 new 調(diào)用。

          ES6 引入了一種新的原始數(shù)據(jù)類型Symbol,表示獨(dú)一無(wú)二的值。

          3.18 isObject 判斷是不是對(duì)象

          const isObject = (val) => val !== null && typeof val === 'object';

          // 例子:
          isObject(null); // false
          isObject({name'若川'}); // true
          // 判斷不為 null 的原因是 typeof null 其實(shí) 是 object

          3.19 isPromise 判斷是不是 Promise

          const isPromise = (val) => {
              return isObject(val) && isFunction(val.then) && isFunction(val.catch);
          };

          // 判斷是不是Promise對(duì)象
          const p1 = new Promise(function(resolve, reject){
            resolve('若川');
          });
          isPromise(p1); // true

          // promise 對(duì)于初學(xué)者來(lái)說(shuō)可能比較難理解。但是重點(diǎn)內(nèi)容,JS異步編程,要著重掌握。
          // 現(xiàn)在 web 開(kāi)發(fā) Promise 和 async await 等非常常用。

          可以根據(jù)文末推薦的書(shū)籍看Promise相關(guān)章節(jié)掌握。同時(shí)也推薦這本迷你書(shū)JavaScript Promise迷你書(shū)(中文版)[14]

          3.20 objectToString 對(duì)象轉(zhuǎn)字符串

          const objectToString = Object.prototype.toString;

          // 對(duì)象轉(zhuǎn)字符串

          3.21 toTypeString  對(duì)象轉(zhuǎn)字符串

          const toTypeString = (value) => objectToString.call(value);

          // call 是一個(gè)函數(shù),第一個(gè)參數(shù)是 執(zhí)行函數(shù)里面 this 指向。
          // 通過(guò)這個(gè)能獲得 類似  "[object String]" 其中 String 是根據(jù)類型變化的

          3.22 toRawType  對(duì)象轉(zhuǎn)字符串 截取后幾位

          const toRawType = (value) => {
              // extract "RawType" from strings like "[object RawType]"
              return toTypeString(value).slice(8-1);
          };

          // 截取到
          toRawType('');  'String'

          可以 截取到 String Array 等這些類型

          JS 判斷數(shù)據(jù)類型非常重要的知識(shí)點(diǎn)。

          JS 判斷類型也有  typeof ,但不是很準(zhǔn)確,而且能夠識(shí)別出的不多。

          這些算是基礎(chǔ)知識(shí)

          mdn typeof 文檔[15],文檔比較詳細(xì),也實(shí)現(xiàn)了一個(gè)很完善的type函數(shù),本文就不贅述了。

          // typeof 返回值目前有以下8種 
          'undefined'
          'object'
          'boolean'
          'number'
          'bigint'
          'string'
          'symobl'
          'function'

          3.23 isPlainObject 判斷是不是純粹的對(duì)象

          const objectToString = Object.prototype.toString;
          const toTypeString = (value) => objectToString.call(value);
          // 
          const isPlainObject = (val) => toTypeString(val) === '[object Object]';

          // 前文中 有 isObject 判斷是不是對(duì)象了。
          // isPlainObject 這個(gè)函數(shù)在很多源碼里都有,比如 jQuery 源碼和 lodash 源碼等,具體實(shí)現(xiàn)不一樣
          // 上文的 isObject([]) 也是 true ,因?yàn)?nbsp;type [] 為 'object'
          // 而 isPlainObject([]) 則是false
          const Ctor = function(){
              this.name = '我是構(gòu)造函數(shù)';
          }
          isPlainObject({}); // true
          isPlainObject(new Ctor()); // true

          3.24 isIntegerKey 判斷是不是數(shù)字型的字符串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 第二個(gè)參數(shù)是進(jìn)制。
          // 字符串能用數(shù)組取值的形式取值。
          //  還有一個(gè) charAt 函數(shù),但不常用 
          'abc'.charAt(0// 'a'
          // charAt 與數(shù)組形式不同的是 取不到值會(huì)返回空字符串'',數(shù)組形式取值取不到則是 undefined

          3.25 makeMap && isReservedProp

          傳入一個(gè)以逗號(hào)分隔的字符串,生成一個(gè) map(鍵值對(duì)),并且返回一個(gè)函數(shù)檢測(cè) key 值在不在這個(gè) map 中。第二個(gè)參數(shù)是小寫(xiě)選項(xiàng)。

          /**
           * 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));
              });
          };

          這個(gè)函數(shù)也是和上面 MakeMap 函數(shù)類似。只不過(guò)接收參數(shù)的是函數(shù)?!禞avaScript 設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐》書(shū)中的第四章 JS單例模式也是類似的實(shí)現(xiàn)。

          var getSingle = function(fn)// 獲取單例
              var result;
              return function(){
                  return result || (result = fn.apply(thisarguments));
              }
          };

          以下是一些正則,系統(tǒng)學(xué)習(xí)正則推薦老姚:《JavaScript 正則表達(dá)式迷你書(shū)》問(wèn)世了![16],看過(guò)的都說(shuō)好。所以本文不會(huì)過(guò)多描述正則相關(guān)知識(shí)點(diǎn)。

          // \w 是 0-9a-zA-Z_ 數(shù)字 大小寫(xiě)字母和下劃線組成
          // () 小括號(hào)是 分組捕獲
          const camelizeRE = /-(\w)/g;
          /**
           * @private
           */

          // 連字符 - 轉(zhuǎn)駝峰  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
           */

          // 首字母轉(zhuǎn)大寫(xiě)
          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);
          // 例子:
          // 認(rèn)為 NaN 是不變的
          hasChanged(NaNNaN); // false
          hasChanged(11); // false
          hasChanged(12); // true
          hasChanged(+0-0); // false
          // Obect.is 認(rèn)為 +0 和 -0 不是同一個(gè)值
          Object.is(+0-0); // false           
          // Object.is 認(rèn)為  NaN 和 本身 相比 是同一個(gè)值
          Object.is(NaNNaN); // true
          // 場(chǎng)景
          // watch 監(jiān)測(cè)值是不是變化了

          // (value === value || oldValue === oldValue)
          // 為什么會(huì)有這句 因?yàn)橐袛?NaN 。認(rèn)為 NaN 是不變的。因?yàn)?NaN === NaN 為 false

          根據(jù) hasChanged 這個(gè)我們繼續(xù)來(lái)看看:Object.is API。

          Object.is(value1, value2) (ES6)

          該方法用來(lái)比較兩個(gè)值是否嚴(yán)格相等。它與嚴(yán)格比較運(yùn)算符(===)的行為基本一致。不同之處只有兩個(gè):一是+0不等于-0,而是 NaN 等于自身。

          Object.is('若川''若川'); // true
          Object.is({},{}); // false
          Object.is(+0-0); // false
          +0 === -0// true
          Object.is(NaNNaN); // true
          NaN === NaN// false

          ES5可以通過(guò)以下代碼部署Object.is。

          Object.defineProperty(Object'is', {
              valuefunction({x, y} {
                  if (x === y) {
                     // 針對(duì)+0不等于-0的情況
                     return x !== 0 || 1 / x === 1 / y;
                  }
                  // 針對(duì) NaN的情況
                  return x !== x && y !== y;
              },
              configurabletrue,
              enumerablefalse,
              writabletrue
          });

          根據(jù)舉例可以說(shuō)明

          3.28 invokeArrayFns  執(zhí)行數(shù)組里的函數(shù)

          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('微信搜索 若川視野 可以找到關(guān)注' + val);
              },
          ]
          invokeArrayFns(arr, '我');

          為什么這樣寫(xiě),我們一般都是一個(gè)函數(shù)執(zhí)行就行。

          數(shù)組中存放函數(shù),函數(shù)其實(shí)也算是數(shù)據(jù)。這種寫(xiě)法方便統(tǒng)一執(zhí)行多個(gè)函數(shù)。

          3.29 def 定義對(duì)象屬性

          const def = (obj, key, value) => {
              Object.defineProperty(obj, key, {
                  configurabletrue,
                  enumerablefalse,
                  value
              });
          };

          Object.defineProperty 算是一個(gè)非常重要的API。還有一個(gè)定義多個(gè)屬性的APIObject.defineProperties(obj, props) (ES5)

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

          value——當(dāng)試圖獲取屬性時(shí)所返回的值。
          writable——該屬性是否可寫(xiě)。
          enumerable——該屬性在for in循環(huán)中是否會(huì)被枚舉。
          configurable——該屬性是否可被刪除。
          set()——該屬性的更新操作所調(diào)用的函數(shù)。
          get()——獲取屬性值時(shí)所調(diào)用的函數(shù)。

          另外,數(shù)據(jù)描述符(其中屬性為:enumerable,configurablevalue,writable)與存取描述符(其中屬性為enumerable,configurableset(),get())之間是有互斥關(guān)系的。在定義了set()get()之后,描述符會(huì)認(rèn)為存取操作已被 定義了,其中再定義valuewritable會(huì)引起錯(cuò)誤。

          以下是ES3風(fēng)格的屬性定義方式:

          var person = {};
          person.legs = 2;

          以下是等價(jià)的ES5通過(guò)數(shù)據(jù)描述符定義屬性的方式:

          var person = {};
          Object.defineProperty(person, 'legs', {
              value2,
              writabletrue,
              configurabletrue,
              enumerabletrue
          });

          其中, 除了value的默認(rèn)值為undefined以外,其他的默認(rèn)值都為false。這就意味著,如果想要通過(guò)這一方式定義一個(gè)可寫(xiě)的屬性,必須顯示將它們?cè)O(shè)為true。或者,我們也可以通過(guò)ES5的存儲(chǔ)描述符來(lái)定義:

          var person = {};
          Object.defineProperty(person, 'legs', {
              set:function(v{
                  return this.value = v;
              },
              getfunction(v{
                  return this.value;
              },
              configurabletrue,
              enumerabletrue
          });
          person.legs = 2;

          這樣一來(lái),多了許多可以用來(lái)描述屬性的代碼,如果想要防止別人篡改我們的屬性,就必須要用到它們。此外,也不要忘了瀏覽器向后兼容ES3方面所做的考慮。例如,跟添加Array.prototype屬性不一樣,我們不能再舊版的瀏覽器中使用shim這一特性。另外,我們還可以(通過(guò)定義nonmalleable屬性),在具體行為中運(yùn)用這些描述符:

          var person = {};
          Object.defineProperty(person, 'heads', {value1});
          person.heads = 0// 0
          person.heads; // 1  (改不了)
          delete person.heads; // false
          person.heads // 1 (刪不掉)

          其他本文就不過(guò)多贅述了。更多對(duì)象 API 可以查看這篇文章JavaScript 對(duì)象所有API解析。

          3.30 toNumber 轉(zhuǎn)數(shù)字

          const toNumber = (val) => {
              const n = parseFloat(val);
              return isNaN(n) ? val : n;
          };

          toNumber('111'); // 111
          toNumber('a111'); // 'a111'
          parseFloat('a111'); // NaN
          isNaN(NaN); // true

          其實(shí) isNaN 本意是判斷是不是 NaN 值,但是不準(zhǔn)確的。比如:isNaN('a')true。所以 ES6 有了 Number.isNaN 這個(gè)判斷方法,為了彌補(bǔ)這一個(gè)API。

          Number.isNaN('a')  // false
          Number.isNaN(NaN); // true

          3.31 getGlobalThis 全局對(duì)象

          let _globalThis;
          const getGlobalThis = () => {
              return (_globalThis ||
                  (_globalThis =
                      typeof globalThis !== 'undefined'
                          ? globalThis
                          : typeof self !== 'undefined'
                              ? self
                              : typeof window !== 'undefined'
                                  ? window
                                  : typeof global !== 'undefined'
                                      ? global
                                      : {}));
          };

          獲取全局 this 指向。

          初次執(zhí)行肯定是 _globalThisundefined。所以會(huì)執(zhí)行后面的賦值語(yǔ)句。

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

          如果存在self,就用self。在 Web Worker 中不能訪問(wèn)到 window 對(duì)象,但是我們卻能通過(guò) self 訪問(wèn)到 Worker 環(huán)境中的全局對(duì)象。

          如果存在window,就用window。

          如果存在global,就用global。Node環(huán)境下,使用global。

          如果都不存在,使用空對(duì)象??赡苁俏⑿判〕绦颦h(huán)境下。

          下次執(zhí)行就直接返回 _globalThis,不需要第二次繼續(xù)判斷了。這種寫(xiě)法值得我們學(xué)習(xí)。

          4. 最后推薦一些文章和書(shū)籍

          先推薦我認(rèn)為不錯(cuò)的JavaScript API的幾篇文章和幾本值得讀的書(shū)。

          JavaScript字符串所有API全解密[18]

          【深度長(zhǎng)文】JavaScript數(shù)組所有API全解密[19]

          正則表達(dá)式前端使用手冊(cè)[20]

          老姚:《JavaScript 正則表達(dá)式迷你書(shū)》問(wèn)世了![21]

          JavaScript 對(duì)象所有API解析 https://lxchuan12.gitee.io/js-object-api/

          MDN JavaScript[22]

          《JavaScript高級(jí)程序設(shè)計(jì)》第4版[23]

          《JavaScript 權(quán)威指南》第7版[24]

          《JavaScript面向?qū)ο缶幊?》[25] 面向?qū)ο笾v的很詳細(xì)。

          阮一峰老師:《ES6 入門教程》[26]

          《現(xiàn)代 JavaScript 教程》[27]

          《你不知道的JavaScript》上中卷[28]

          《JavaScript 設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐》[29]

          我也是從小白看不懂書(shū)經(jīng)歷過(guò)來(lái)的。到現(xiàn)在寫(xiě)文章分享。

          我看書(shū)的方法:多本書(shū)同時(shí)看,看相同類似的章節(jié),比如函數(shù)??赐赀@本可能沒(méi)懂,看下一本,幾本書(shū)看下來(lái)基本就懂了,一遍沒(méi)看懂,再看幾遍,可以避免遺忘,鞏固相關(guān)章節(jié)。當(dāng)然,剛開(kāi)始看書(shū)很難受,看不進(jìn)。這些書(shū)大部分在微信讀書(shū)都有,如果習(xí)慣看紙質(zhì)書(shū),那可以買來(lái)看。

          這時(shí)可以看些視頻和動(dòng)手練習(xí)一些簡(jiǎn)單的項(xiàng)目。

          比如:可以自己注冊(cè)一個(gè)github賬號(hào),分章節(jié)小節(jié),抄寫(xiě)書(shū)中的代碼,提交到github,練習(xí)了才會(huì)更有感覺(jué)。

          再比如 freeCodeCamp 中文在線學(xué)習(xí)網(wǎng)站[30] 網(wǎng)站。看書(shū)是系統(tǒng)學(xué)習(xí)非常好的方法。后來(lái)我就是看源碼較多,寫(xiě)文章分享出來(lái)給大家。

          5. 總結(jié)

          文中主要通過(guò)學(xué)習(xí) shared 模塊下的幾十個(gè)工具函數(shù),比如有:isPromise、makeMapcacheStringFunction、invokeArrayFnsdef、getGlobalThis等等。

          同時(shí)還分享了vue源碼的調(diào)試技巧,推薦了一些書(shū)籍和看書(shū)籍的方法。

          源碼也不是那么可怕。平常我們工作中也是經(jīng)常能使用到這些工具函數(shù)。通過(guò)學(xué)習(xí)一些簡(jiǎn)單源碼,拓展視野的同時(shí),還能落實(shí)到自己工作開(kāi)發(fā)中,收益相對(duì)比較高。

          參考資料

          [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]

          更多參考資料可以點(diǎn)擊 閱讀原文 查看


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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  最近中文字幕mv第一季歌词免费 | 免费观看黄色毛片 | 少妇大战28厘米黑人 | 天天撸视频 | 中文字幕无码一区二区三区一本久 |