打造属于自己的underscore系列 ( 一 )
underscore作為開發中比較常用的一個javascript工具庫,提供了一套豐富的函數式編程功能,該庫并沒有拓展原有的javascript原生對象,而是在自定義的
_對象上,提供了100多個方法函數。在這個系列中,將從uderscore源碼角度, 打造一個自己的underscore框架
一,框架設計
1.1 自執行函數
現代js 庫的框架設計,一般都是以自執行函數的形式,自執行函數一般有兩種形式
(function(){// 形式一
}()) (function(){// 形式二
})() 我們知道,函數聲明的形式會掛載到window對象作為方法存在,而函數表達式的形式則會掛載在window對象作為屬性存在,這都會造成變量污染,而自執行函數的好處在于可以防止變量的污染,函數執行完后便立刻銷毀掉。
1.2 使用風格
underscore有兩種風格形式可以使用,一種是面向對象類型,另一種是函數類型。
// 例子
_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; }); 因此,在定義underscore類的時候需要考慮對象和函數兩種場景。當以函數的形式調用時需要把 _ 當作一個構造函數并返回他的實例化。代碼如下
(function(root){var _ = function (obj) {if (!(this instanceof _)) {return new _(obj)}}root._ = _
}(this)) 1.3 使用環境
現在前端開發重視模塊化,以node服務端而論, 我們有commonjs規范,以客戶端而論,我們有AMD 和 CMD規范,對應的模塊加載器為 requirejs 和 seajs。目前通行的javascript模塊規范主要集中在commonjs 和 AMD,因此為了讓定義的underscore庫能夠適用于各種規范。在框架的定義時需檢測使用環境并兼容各種規范。
- 服務端:commonjs規范,檢測module.exports 是否存在,滿足時通過 module.exports = {} 將 underscore暴露出來,不滿足則 通過window對象暴露出來。
- 客戶端: AMD 規范, 檢測 define.amd 是否存在,滿足時通過
define('**', [], function(){ return '***' })暴露模塊
(function (root) {var _ = function (obj) {if (!(this instanceof _)) {return new _(obj)}}// commonjs 規范 檢測 module.exports 是否存在if ((typeof module !== 'undefined' && module.exports)) {module.exports = {_: _}} else {root._ = _;// window 對象暴露方法}// amd 規范,檢測 define.amd 是否存在if (typeof define == 'function' && define.amd) {define('underscore', [], function () {return _;});}}(this)) 1.3.1 服務端使用
// commonjs
const _ = require('./underscore.js')
console.log(_) 1.3.2 客戶端使用
// AMD
require(['underscore'], function (underscore) {console.log(underscore)
}) 1.4 方法定義
underscore的調用,既可以通過_.unique(),也可以通過 _().unique(),兩種方法效果相同卻需要在框架設計時定義兩套方法,一套是定義 _ 對象的靜態方法,另一套是擴展 _對象原型鏈上的方法。
_.uniqe = function() {}_.prototype.unique = function() {} 為了避免冗余代碼,可以將定義好的靜態方法復制一份成為原型鏈上的方法
(function(root){···_.mixins = function() {// 復制靜態方法到原型上}_.mixins() // 執行方法
}(this))
mixins 方法的實現,需要遍歷 underscore 對象上所有的靜態方法,因此需要先完成對 遍歷方法 _.each 的實現
1.41 _.each
_.each(list, iteratee, [context]) Alias: forEach
遍歷list中的所有元素,按順序用每個元素當做參數調用 iteratee 函數。如果傳遞了context參數,則把iteratee綁定到context對象上。每次調用iteratee都會傳遞三個參數:(element, index, list)。如果list是個JavaScript對象,iteratee的參數是 (value, key, list))。返回list以方便鏈式調用。
each 的第一個參數按照文檔可以支持 數組,類數組,對象三種類型,數組類數組和對象在遍歷時的處理方式不同。前者回調函數處理的是 值和下標,后者處理的是 值和屬性。
// 判斷數組,類數組方法
(function(root) {···_.each = function (list, callback, context) {// context 存在會改變callback 中this 的指向var i = 0;var key;if (isArrayLikeLike(list)) { // 數組,類數組for (var i = 0; i < list.length; i++) {context ? callback.call(context, list[i], i, list) : callback(list[i], i, list)}} else { // 對象for (key in list) {context ? callback.call(context, list[key], key) : callback(list[key], key)}}}var isArrayLike = function (collection) {// 返回參數 collection 的 length 屬性值var length = collection.length;// length是數值,非負,且小于等于MAX_ARRAY_INDEX// MAX_ARRAY_INDEX = Math.pow(2, 53) - 1return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;}
}(this)) 1.4.2 _.mixin
mixin方法的設計,目的是為了在underscore原型對象上擴展更多的方法,它既可以用來擴展用戶自定義的方法,比如
_.mixin({capitalize: function(string) {return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();}
});
_("fabio").capitalize();
=> "Fabio" 當然也可以用來內部拷貝靜態方法到原型鏈的方法上。
(function(root){···var push = Array.prototype.pushvar _ = function (obj) {if (!(this instanceof _)) {return new _(obj)}this.wrap = obj // 存儲實例對象傳過來的參數}_.mixins = function (obj) {_.each(obj, function (value, key) {_.prototype[key] = function () {var args = [this.wrap]push.apply(args, arguments)return value.apply(this, args)}})}_.mixins(_)
}(this)) 其中關注點在arguments 的處理上,靜態方法需要傳遞目標源作為方法的參數 即_.unique(目標源, 回調函數),而實例方法的目標源存儲在構造對象的屬性中 ,即_(目標源).unique(回調函數),因此定義實例方法時需要合并屬性和回調函數。即Array.prorotype.push.apply([this.wrap], arguments),之后將他作為參數傳遞給靜態方法并返回處理結果。
將類數組轉成數組的方法
Array.prototype.slice.call(類數組)var a = []; Array.prototype.push.apply(a, 類數組); console.log(a);var a = []; Array.prototype.concat.apply(a, 類數組); console.log(a);- ES6方法
Array.from(類數組) - ES6擴展運算符
var args = [...類數組]
1.5 鏈式調用
1.5.1 _.chain()
返回一個封裝的對象. 在封裝的對象上調用方法會返回封裝的對象本身, 直道 value 方法調用為止。
underscore中方法的調用返回的是處理后的值,因此無法支持方法的鏈式調用。如果需要鏈式調用,需要使用chain()方法,chain的使用會使每次調用方法后返回underscore的實例對象,直到 調用value方法才停止返回。
(function(root){···// chain方法會返回 _ 實例,并且標注該實例是否允許鏈式調用的_.chain = function(obj) {var instance = _(obj);instance.chain = true; return instance}// 增加是否支持鏈式調用的判斷,如果支持,則返回該實例,不支持則直接返回結果,var chainResult = function (instance, obj) {return instance.chain ? _(obj).chain() : obj}_.mixins = function (obj) {_.each(obj, function (value, key) {_.prototype[key] = function () {var args = [this.wrap]push.apply(args, arguments)return chainResult(this, value.apply(this, args)) // 修改實例方法的返回值,返回值通過chainResult 包裝,根據chainResult的判斷結果改變返回值}})}
}(this)) 1.5.2 value()
因為鏈式調用會使underscore的方法返回他的實例對象,所以當需要結束這一調用行為時,需要使用value()。 value()方法會返回調用的結果。
(function(root){···_.value = function(instance) {return instance.wrap}
}(this)) 未完待續。。。
轉載于:https://www.cnblogs.com/kidflash/p/10077643.html
總結
以上是生活随笔為你收集整理的打造属于自己的underscore系列 ( 一 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 求告知这部电影叫啥
- 下一篇: 构造函数以及this