你們好,我是若川。歡迎關注個人公衆號若川視野,最近組織了源碼共讀活動,感興趣的能夠加我微信 ruochuan12,長期交流學習。javascript
以前寫的《學習源碼總體架構系列》 包含jQuery
、underscore
、lodash
、vuex
、sentry
、axios
、redux
、koa
、vue-devtools
、vuex4
十篇源碼文章。html
寫相對很難的源碼,耗費了本身的時間和精力,也沒收穫多少閱讀點贊,實際上是一件挺受打擊的事情。從閱讀量和讀者受益方面來看,不能促進做者持續輸出文章。前端
因此轉變思路,寫一些相對通俗易懂的文章。其實源碼也不是想象的那麼難,至少有不少看得懂。好比工具函數。本文經過學習Vue3
源碼中的工具函數模塊的源碼,學習源碼爲本身所用。歌德曾說:讀一本好書,就是在和高尚的人談話。 同理可得:讀源碼,也算是和做者的一種學習交流的方式。vue
閱讀本文,你將學到:java
1. 如何學習 JavaScript 基礎知識,會推薦不少學習資料
2. 如何學習調試 Vue 3 源碼
3. 如何學習源碼中優秀代碼和思想,投入到本身的項目中
4. Vue 3 源碼 shared 模塊中的幾十個實用工具函數
5. 個人一些經驗分享
複製代碼
shared
模塊中57個
工具函數,本次閱讀其中的30餘個
。node
打開 vue-next, 開源項目通常都能在 README.md
或者 .github/contributing.md 找到貢獻指南。ios
而貢獻指南寫了不少關於參與項目開發的信息。好比怎麼跑起來,項目目錄結構是怎樣的。怎麼投入開發,須要哪些知識儲備等。git
咱們能夠在 項目目錄結構 描述中,找到shared
模塊。es6
shared
: Internal utilities shared across multiple packages (especially environment-agnostic utils used by both runtime and compiler packages).github
README.md
和 contributing.md
通常都是英文的。可能會難倒一部分人。其實看不懂,徹底能夠能夠藉助劃詞翻譯,整頁翻譯和百度翻譯等翻譯工具。再把英文加入後續學習計劃。
本文就是講shared
模塊,對應的文件路徑是:vue-next/packages/shared/src/index.ts
也能夠用github1s
訪問,速度更快。github1s packages/shared/src/index.ts
爲了下降文章難度,我按照貢獻指南中方法打包把ts
轉成了js
。若是你須要打包,也能夠參考下文打包構建。
你須要確保 Node.js 版本是 10+
, 並且 yarn
的版本是 1.x
Yarn 1.x。
你安裝的 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-analysis
npm install --global yarn
yarn # install the dependencies of the project
# yarn —ignore-scripts 忽略一些安裝,更快速
yarn build
複製代碼
能夠獲得 vue-next/packages/shared/dist/shared.esm-bundler.js
,文件也就是純js
文件。也接下就是解釋其中的一些方法。
固然,前面可能比較囉嗦。我能夠直接講
3. 工具函數
。但經過我上文的介紹,即便是初學者,都能看懂一些開源項目源碼,也許就會有必定的成就感。 另外,面試問到被相似的問題或者筆試題時,你說看Vue3
源碼學到的,面試官絕對對你另眼相看。
熟悉個人讀者知道,我是常常強調生成sourcemap
調試看源碼,因此順便提一下如何配置生成sourcemap
,如何調試。這部分能夠簡單略過,動手操做時再仔細看。
其實貢獻指南裏描述了。
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/packages/shared/src/index.ts
的順序來寫。也省去了一些從外部導入的方法。
咱們也能夠經過ts
文件,查看使用函數的位置。同時在VSCode
運行調試JS代碼,咱們比較推薦韓老師寫的code runner
插件。
/** * 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'
];
複製代碼
這裏就是幾個默認插件。感興趣看英文註釋查看。
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); // 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_ENV
是 node
項目中的一個環境變量,通常定義爲:development
和production
。根據環境寫代碼。好比開發環境,有報錯等信息,生產環境則不須要這些報錯警告。
const EMPTY_ARR = (process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];
// 例子:
EMPTY_ARR.push(1) // 報錯,也就是爲啥生產環境仍是用 []
EMPTY_ARR.length = 3;
console.log(EMPTY_ARR.length); // 0
複製代碼
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(){} ,不方便壓縮代碼
複製代碼
/** * Always return false. */
const NO = () => false;
// 除了壓縮代碼的好處外。
// 一直返回 false
複製代碼
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
的小寫字母。
同時推薦一個正則在線工具。
另外正則看老姚的迷你書就夠用了。
判斷字符串是否是以onUpdate:
開頭
const isModelListener = (key) => key.startsWith('onUpdate:');
// 例子:
isModelListener('onUpdate:change'); // true
isModelListener('1onUpdate:change'); // false
// startsWith 是 ES6 提供的方法
複製代碼
不少方法都在《ES6入門教程》中有講到,就不贅述了。
說合並可能更準確些。
const extend = Object.assign;
// 例子:
const data = { name: '若川' };
const data2 = entend(data, { mp: '若川視野', name: '是若川啊' });
console.log(data); // { name: "是若川啊", mp: "若川視野" }
console.log(data2); // { name: "是若川啊", mp: "若川視野" }
console.log(data === data2); // true
複製代碼
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
攔截器源碼 中,攔截器用數組存儲的。但實際移除攔截器時,只是把攔截器置爲 null
。而不是用splice
移除。最後執行時爲 null
的不執行,一樣效果。axios
攔截器這個場景下,不得不說爲性能作到了很好的考慮。
看以下 axios
攔截器代碼示例:
// 代碼有刪減
// 聲明
this.handlers = [];
// 移除
if (this.handlers[id]) {
this.handlers[id] = null;
}
// 執行
if (h !== null) {
fn(h);
}
複製代碼
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解析,寫的還算全面。
const isArray = Array.isArray;
isArray([]); // true
const fakeArr = { __proto__: Array.prototype, length: 0 };
isArray(fakeArr); // false
fakeArr instanceof Array; // true
// 因此 instanceof 這種狀況 不許確
複製代碼
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 更合適。
const isSet = (val) => toTypeString(val) === '[object Set]';
// 例子:
const set = new Set();
isSet(set); // true
複製代碼
ES6
提供了新的數據結構Set
。它相似於數組,可是成員的值都是惟一的,沒有重複的值。
Set
自己是一個構造函數,用來生成 Set
數據結構。
const isDate = (val) => val instanceof Date;
// 例子:
isDate(new Date()); // true
// `instanceof` 操做符左邊是右邊的實例。但不是很準,但通常夠用了。原理是根據原型鏈向上查找的。
isDate({__proto__ : new Date()); // true
// 其實是應該是 Object 纔對。
// 因此用 instanceof 判斷數組也不許確。
// 再好比
({__proto__: [] }) instanceof Array; // true
// 其實是對象。
// 因此用 數組自己提供的方法 Array.isArray 是比較準確的。
複製代碼
const isFunction = (val) => typeof val === 'function';
// 判斷數組有多種方法,但這個是比較經常使用也相對兼容性好的。
複製代碼
const isString = (val) => typeof val === 'string';
// 例子:
isString('') // true
複製代碼
const isSymbol = (val) => typeof val === 'symbol';
// 例子:
let s = Symbol();
typeof s;
// "symbol"
// Symbol 是函數,不須要用 new 調用。
複製代碼
ES6
引入了一種新的原始數據類型Symbol
,表示獨一無二的值。
const isObject = (val) => val !== null && typeof val === 'object';
// 例子:
isObject(null); // false
isObject({name: '若川'}); // true
// 判斷不爲 null 的緣由是 typeof null 其實 是 object
複製代碼
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迷你書(中文版)
const objectToString = Object.prototype.toString;
// 對象轉字符串
複製代碼
const toTypeString = (value) => objectToString.call(value);
// call 是一個函數,第一個參數是 執行函數裏面 this 指向。
// 經過這個能得到 相似 "[object String]" 其中 String 是根據類型變化的
複製代碼
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 文檔,文檔比較詳細,也實現了一個很完善的type
函數,本文就不贅述了。
// typeof 返回值目前有如下8種
'undefined'
'object'
'boolean'
'number'
'bigint'
'string'
'symobl'
'function'
複製代碼
const objectToString = Object.prototype.toString;
const toTypeString = (value) => objectToString.call(value);
//
const isPlainObject = (val) => toTypeString(val) === '[object Object]';
// 前文中 有 isObject 判斷是否是對象了。
// isPlainObject 這個函數在不少源碼裏都有,好比 jQuery 源碼和 lodash 源碼等,具體實現不同
複製代碼
const isIntegerKey = (key) => isString(key) &&
key !== 'NaN' &&
key[0] !== '-' &&
'' + parseInt(key, 10) === key;
// 例子:
isInegerKey('a'); // false
isInegerKey('0'); // true
isInegerKey('011'); // false
isInegerKey('11'); // true
// 其中 parseInt 第二個參數是進制。
// 字符串能用數組取值的形式取值。
// 還有一個 charAt 函數,但不經常使用
'abc'.charAt(0) // 'a'
// charAt 與數組形式不一樣的是 取不到值會返回空字符串'',數組形式取值取不到則是 undefined
複製代碼
傳入一個以逗號分隔的字符串,生成一個 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
複製代碼
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 正則表達式迷你書》問世了!,看過的都說好。因此本文不會過多描述正則相關知識點。
// \w 是 0-9a-zA-Z_ 數字 大小寫字母和下劃線組成
// () 小括號是 分組捕獲
const camelizeRE = /-(\w)/g;
/** * @private */
// 首字母轉大寫
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'
複製代碼
// 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); // false
// 場景
// watch 監測值是否是變化了
複製代碼
const invokeArrayFns = (fns, arg) => {
for (let i = 0; i < fns.length; i++) {
fns[i](arg);
}
};
// 例子:
const arr = [
function(val){
console.log(val + '的博客地址是:https://lxchuan12.gitee.io');
},
function(val){
console.log('百度搜索 若川 能夠找到' + val);
},
function(val){
console.log('微信搜索 若川視野 能夠找到關注' + val);
},
]
invokeArrayFns(arr, '我');
複製代碼
爲何這樣寫,咱們通常都是一個函數執行就行。
數組中存放函數,函數其實也算是數據。這種寫法方便統一執行多個函數。
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解析。
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
複製代碼
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
若是存在self
,就用self
。在 Web Worker
中不能訪問到 window
對象,可是咱們卻能經過 self
訪問到 Worker
環境中的全局對象。
若是存在window
,就用window
。
若是存在global
,就用global
。Node
環境下,使用global
。
若是都不存在,使用空對象。多是微信小程序環境下。
下次執行就直接返回 _globalThis
,不須要第二次繼續判斷了。這種寫法值得咱們學習。
先推薦我認爲不錯的JavaScript API
的幾篇文章和幾本值得讀的書。
JavaScript 對象全部API解析 lxchuan12.gitee.io/js-object-a…
《JavaScript面向對象編程2》 面向對象講的很詳細。
我也是從小白看不懂書經歷過來的。到如今寫文章分享。
我看書的方法:多本書同時看,看相同相似的章節,好比函數。看完這本可能沒懂,看下一本,幾本書看下來基本就懂了,一遍沒看懂,再看幾遍,能夠避免遺忘,鞏固相關章節。固然,剛開始看書很難受,看不進。這些書大部分在微信讀書都有,若是習慣看紙質書,那能夠買來看。
這時能夠看些視頻和動手練習一些簡單的項目。
好比:能夠本身註冊一個github
帳號,分章節小節,抄寫書中的代碼,提交到github
,練習了纔會更有感受。
再好比 freeCodeCamp 中文在線學習網站 網站。看書是系統學習很是好的方法。後來我就是看源碼較多,寫文章分享出來給你們。
文中主要經過學習 shared
模塊下的幾十個工具函數,好比有:isPromise
、makeMap
、cacheStringFunction
、invokeArrayFns
、def
、getGlobalThis
等等。
同時還分享了vue
源碼的調試技巧,推薦了一些書籍和看書籍的方法。
源碼也不是那麼可怕。日常咱們工做中也是常常能使用到這些工具函數。經過學習一些簡單源碼,拓展視野的同時,還能落實到本身工做開發中,收益相對比較高。
做者:常以若川爲名混跡於江湖。前端路上 | PPT愛好者 | 所知甚少,惟善學。
公衆號若川視野
若川的博客
segmentfault
若川視野專欄,開通了若川視野專欄,歡迎關注~
掘金專欄,歡迎關注~
知乎若川視野專欄,開通了若川視野專欄,歡迎關注~
github blog,求個star
^_^~