摘要:第三篇腳手架依賴的核心庫的源碼解析。該篇是這個系列文章的第三篇主要是對的源碼進行分析講解。的源碼十分簡單但實現的功能卻是十分的強大。源碼概括源碼主要包含了兩部分公共方法和私有方法。
react作為當前十分流行的前端框架,相信很多前端er都有蠢蠢欲動的學習它的想法。工欲善其事,必先利其器。這篇文章就簡單的給大家介紹一下如何我快速的搭建一個react前端開發環境。主要針對于react小白,大神不喜勿噴。
從標題可以看出,這里不會僅僅只介紹一下react的開發環境如何搭建。我將這個系列分成三篇介紹:
第一篇--快速搭建一個react開發環境。
第二篇--快速開發一個react開發環境腳手架工具。有了這個工具,在任何地方都能夠一鍵生成環境。
第三篇--腳手架依賴的核心庫co的源碼解析。
該篇是這個系列文章的第三篇,主要是對co的源碼進行分析講解。co的源碼十分簡單,但實現的功能卻是十分的強大。不了解的同學可以通過co自行學習,也可以通過我這篇源碼分析的文章進行更深入的學習。
co源碼概括co源碼主要包含了兩部分:公共方法和私有方法。
1、公共方法
co
co.wrap
2、私有方法
isObject
isGeneratorFunction
isGenerator
isPromise
objectToPromise
arrayToPromise
thunkToPromise
toPromise
源碼的閱讀順序建議先閱讀私有方法的部分,然后在閱讀公共方法的部分。各個部分的閱讀順序也按照上面列舉的順序進行閱讀。
co源碼分析/** * slice() reference. */ var slice = Array.prototype.slice; /** * Expose `co`. */ module.exports = co["default"] = co.co = co; co.wrap = function (fn) { // 這個方法的主要作用就是將generator函數轉化成普通的函數調用 有點類似于thunk函數的轉化 /** * function* a(val) { * return val * } * * console.log(co(a,"pavoooo")) * console.log(co.wrap(a)("pavoooo")) 就可以這樣調用了 * */ createPromise.__generatorFunction__ = fn; return createPromise; function createPromise() { return co.call(this, fn.apply(this, arguments)); } }; function co(gen) { var ctx = this; var args = slice.call(arguments, 1) // co的調用結果是一個promise對象 return new Promise(function(resolve, reject) { // 如果co的第一個參數是函數的話 就將第二個以及后續的參數傳遞給這個函數 // 并將gen的調用結果賦給gen /** * co(function(){ * console.log(arguments) * }, 1, 2, 3) * 不考慮下面轉化的情況 這個函數運行之后 會打印出{ "0": 1, "1": 2, "2": 3 } * 同時gen的值就是undefined */ if (typeof gen === "function") gen = gen.apply(ctx, args); // 這個條件判斷的就是 如果gen調用之后的返回值是undefined或者不是一個generator函數 直接將promise的狀態轉化成resolved // 同時將返回值作為resolved的狀態值釋放 也就是說co函數的參數應該是一個generator函數 if (!gen || typeof gen.next !== "function") return resolve(gen); // 調用onFulfilled函數--遞歸的調用generator函數的next方法 onFulfilled(); function onFulfilled(res) { var ret; try { // 這個語句有兩重作用 // 一、接收上一個yield返回的值 // 二、將調用之后的遍歷器賦值給ret并傳遞到next函數中以判斷gen調用是否結束 ret = gen.next(res); } catch (e) { return reject(e); } // 遞歸的調用next 也是是遞歸的執行gen函數的yield語句 next(ret); } function onRejected(err) { var ret; try { // 這個函數主要是當yield后的語句不符合規定的類型的時候 向外拋出一個錯誤 ret = gen.throw(err); } catch (e) { return reject(e); } next(ret); } function next(ret) { // 如果generator函數運行結束 直接釋放結果 這個結果就是gen函數中return的結果 這就可以在外部通過then方法接收 if (ret.done) return resolve(ret.value); // 否則將遍歷器對應的value轉化成promise var value = toPromise.call(ctx, ret.value); // 如果能夠成功的轉化成promise 調用then方法 將值釋放出來 并將其作為onFulfilled函數的參數 而在onFulfilled函數內部 又通過 // gen.next()接收 這樣 就可以把每次gen.next().value保存在gen函數內部的變量 if (value && isPromise(value)) return value.then(onFulfilled, onRejected); // 這里表示傳遞給co函數的generator函數的yield后的語句必須是一個function, promise, generator, array, or object return onRejected(new TypeError("You may only yield a function, promise, generator, array, or object, " + "but the following object was passed: "" + String(ret.value) + """)); } }); } function toPromise(obj) { // 這個函數其實是對下面幾種將元素轉化成promise對象的幾個函數的集合 這樣做就不需要在各個函數中分別判斷值的類型然后 // 調用不同的方法 統一交給這個函數根據不同的值的類型調用不同的轉換函數 // obj是假值 if (!obj) return obj; // obj是promise if (isPromise(obj)) return obj; // obj是generation if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); // obj是thunk函數 if ("function" == typeof obj) return thunkToPromise.call(this, obj); // obj是數組 if (Array.isArray(obj)) return arrayToPromise.call(this, obj); // obj是對象 if (isObject(obj)) return objectToPromise.call(this, obj); // obj是普通類型的數據且為真 如字符串 數字等 return obj; } function thunkToPromise(fn) { // thunk函數轉換成promise // js的thunk函數就是將多參數函數轉換成單參數函數的一種方式 // 約定俗成的也是第一個參數是error對象 后續的參數是函數的返回值 var ctx = this; return new Promise(function (resolve, reject) { fn.call(ctx, function (err, res) { // error不為空 直接將promise轉化成rejected狀態 if (err) return reject(err); // 否則將函數轉化成resolve狀態 if (arguments.length > 2) res = slice.call(arguments, 1); resolve(res); }); }); } function arrayToPromise(obj) { // 數組轉化成promise-- // 先將數組中的各個元素轉化成promise 然后通過Promise.all進行包裝 轉化成一個新的promise實例并返回 return Promise.all(obj.map(toPromise, this)); } /** * 這個函數是將一個對象轉換成promise對象 從isPromise函數的內部可知 * 把對象轉換成promise對象的前提就是 這個對象必須具有then方法 也是是必須是一個thenable對象 */ function objectToPromise(obj){ // 通過obj的constructor 創建出一個新的對象 這個對象擁有obj所有繼承的屬性 這樣就可以在這個對象上進行轉化 從而防止了更改源對象 var results = new obj.constructor(); //獲取到obj的所有非繼承屬性的鍵組成的數組 var keys = Object.keys(obj); // 定義一個promise容器 并傳遞給Promise.all這個方法 var promises = []; for (var i = 0; i < keys.length; i++) { var key = keys[i]; // 根據值的類型調用對應的轉換函數轉換成promise對象 var promise = toPromise.call(this, obj[key]); // 這個if條件中的第一個條件 是容錯處理 如果isPromise用的很多的情況下 建議將這個容錯處理 // 放在isPromise函數中 轉換之后的值是promise 就調用then方法 取出promise對象中返回的值 // 然后其設置為對應鍵的值 /** * 也就是說 如果一個對象是如下的形式: * var a = { * p: new Promise((resolve, reject) => { * resolve(2) * }) * } * * 經過defer函數的轉換之后a.p = 3 */ if (promise && isPromise(promise)) defer(promise, key); // 如果不是promise就直接返回對應的值 else results[key] = obj[key]; } // 通過Promise.all將多個promise實例轉化成一個新的實例 并返回 return Promise.all(promises).then(function () { return results; }); function defer(promise, key) { // predefine the key in the result results[key] = undefined; promises.push(promise.then(function (res) { results[key] = res; })); } } /** * 判斷obj是不是一個promise對象 * 根據promise A+的規范 * 一個合格的promise必須是一個thenable對象也就是其必須提供一個then方法來獲取值 * 所以我們可以通過判斷一個對象是否具有then方法來判斷是不是promise對象 但這不是絕對準確的方法 co內部通過Promise.all這個 * 方法對isPromise()返回true的對象進行了封裝 都可以將其轉化成promise對象 所以在使用的時候不需要過多的擔心 */ function isPromise(obj) { return "function" == typeof obj.then; } function isGenerator(obj) { return "function" == typeof obj.next && "function" == typeof obj.throw; } /** * 判斷參數obj是不是一個generator函數 */ function isGeneratorFunction(obj) { var constructor = obj.constructor; if (!constructor) return false; /** * 判斷是不是一個generator函數 * function* gen() {} ====> gen.constructor.name = "GeneratorFunction" * 同時這個if條件也是對以下的這種情況作的判斷 * function* gen() {} * var g = gen() ====> g.constructor.name = "GeneratorFunction" * * displayName是一個非標準的屬性 用于返回函數顯示的名稱 不推薦使用 * */ if ("GeneratorFunction" === constructor.name || "GeneratorFunction" === constructor.displayName) return true; /** * obj是通過原生的generator函數操作得出 即 * obj = generator() * obj = Object.create(generator) * obj = Object.create(generator()) * * 上面if條件都會返回true * * 下面的這個isGenerator函數 筆者猜測 * 一是對原生generator函數調用之后返回的迭代器的判斷 * 而是對自定義的generator函數的判斷 * 比如這種形式的返回結果也是true * function A() {} * A.prototype.next = function() { * return { * value: 1, * done: false * } * } * Aa.prototype.throw = function() {} * * var a = new A() * console.log(isGeneratorFunction(a)) * */ return isGenerator(constructor.prototype); } /** * 用于判斷一個對象是不是純粹的js對象 * js中純粹的對象一般有三種創建方式 * var obj = {} * var obj = new Object * var obj = Object.create(Object.prototype) */ function isObject(val) { return Object == val.constructor; }
以上就是對co源碼的大致分析,不理解的或者有異議的同學歡迎留言討論。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81318.html
摘要:第三篇腳手架依賴的核心庫的源碼解析。這三篇文章都是我在日常學習中總結出來的,文章中涉及到的所有代碼可以從我的上找到。 react作為當前十分流行的前端框架,相信很多前端er都有蠢蠢欲動的學習它的想法。工欲善其事,必先利其器。這篇文章就簡單的給大家介紹一下如何我快速的搭建一個react前端開發環境。主要針對于react小白,大神不喜勿噴。從標題可以看出,這里不會僅僅只介紹一下react的...
摘要:安裝這個預設主要包含了如下兩個插件實現熱加載捕獲中的方法并展現在界面上修改上述的文件文件通過上面的幾個步驟我們就大致完成了開發環境的基本搭建。應該在中進行配置以上就是簡單的環境搭建后面會推出后續的文章。 react作為當前十分流行的前端框架,相信很多前端er都有蠢蠢欲動的學習它的想法。工欲善其事,必先利其器。這篇文章就簡單的給大家介紹一下如何我快速的搭建一個react前端開發環境。主要...
摘要:插件開發前端掘金作者原文地址譯者插件是為應用添加全局功能的一種強大而且簡單的方式。提供了與使用掌控異步前端掘金教你使用在行代碼內優雅的實現文件分片斷點續傳。 Vue.js 插件開發 - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins譯者:jeneser Vue.js插件是為應用添加全局功能的一種強大而且簡單的方式。插....
閱讀 3949·2021-11-22 13:53
閱讀 1676·2021-08-25 09:39
閱讀 2410·2019-08-29 18:36
閱讀 1469·2019-08-26 13:35
閱讀 1215·2019-08-26 11:57
閱讀 1678·2019-08-23 15:57
閱讀 803·2019-08-23 14:55
閱讀 1163·2019-08-23 14:51