摘要:支持兩種不同風格的函數調用在中我們可以使用以下兩種方式調用函數式的調用對象式調用在中,它們返回的結果都是相同的。
原文:https://zhehuaxuan.github.io/...目的
作者:zhehuaxuan
Underscore 是一個 JavaScript 工具庫,它提供了一整套函數式編程的實用功能,但是沒有擴展任何 JavaScript 內置對象。
本文主要梳理underscore內部的函數組織與調用邏輯的方式和思想。
通過這篇文章,我們可以:
了解underscore在函數組織方面的巧妙構思;為自己書寫函數庫提供一定思路;
我們開始!
自己寫個函數庫前端的小伙伴一定不會對jQuery陌生,經常使用$.xxxx的形式進行調用,underscore使用_.xxxx,如果自己在ES5語法中寫過自定義模塊的話,就可以寫出下面一段代碼:
//IIFE函數 (function(){ //獲取全局對象 var root = this; //定義對象 var _ = {}; //定義和實現函數 _.first = function(arr,n){ var n = n || 0; if(n==0) return arr[0]; return arr.slice(0,n); } //綁定在全局變量上面 root._ = _; })(); console.log(this);
在Chrome瀏覽器中打開之后,打印出如下結果:
我們看到在全局對象下有一個_屬性,屬性下面掛載了自定義函數。
我們不妨使用_.first(xxxx)在全局環境下直接調用。
console.log(_.first([1,2,3,4])); console.log(_.first([1,2,3,4],1)); console.log(_.first([1,2,3,4],3));
輸出結果如下:
沒問題,我們的函數庫制作完成了,我們一般直接這么用,也不會有太大問題。
underscore是怎么做的?underscore正是基于上述代碼進行完善,那么underscore是如何接著往下做的呢?容我娓娓道來!
對兼容性的考慮首先是對兼容性的考慮,工具庫當然需要考慮各種運行環境。
// Establish the root object, `window` (`self`) in the browser, `global` // on the server, or `this` in some virtual machines. We use `self` // instead of `window` for `WebWorker` support. var root = typeof self == "object" && self.self === self && self || typeof global == "object" && global.global === global && global || this || {};
上面是underscore1.9.1在IIFE函數中的源碼,對應于我們上面自己寫的var root = this;。
在源碼中作者也作了解釋:
創建root對象,并且給root賦值。怎么賦值呢?
瀏覽器端:window也可以是window.self或者直接self服務端(node):global
WebWorker:self
虛擬機:this
underscore充分考慮了兼容性,使得root指向對局對象。
支持兩種不同風格的函數調用在underscore中我們可以使用以下兩種方式調用:
函數式的調用:console.log(_.first([1,2,3,4]));
對象式調用:console.log(_([1,2,3,4])).first();
在underscore中,它們返回的結果都是相同的。
第一種方式我們現在就沒有問題,難點就是第二種方式的實現。
對象式調用的實現解決這個問題要達到兩個目的:
_是一個函數,并且調用返回一個對象;
這個對象依然能夠調用掛載在_對象上聲明的方法。
我們來看看underscore對于_的實現:
var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; };
不怕,我們不妨調用_([1,2,3,4]))看看他是怎么執行的!
第一步:if (obj instanceof _) return obj;傳入的對象及其原型鏈上有_類型的對象,則返回自身。我們這里的[1,2,3,4]顯然不是,跳過。
第二步:if (!(this instanceof _)) return new _(obj);,如果當前的this對象及其原型鏈上沒有_類型的對象,那么執行new操作。調用_([1,2,3,4]))時,this為window,那么(this instanceof _)為false,所以我們執行new _([1,2,3,4])。
第三步:執行new _([1,2,3,4]),繼續調用_函數,這時
obj為[1,2,3,4]this為一個新對象,并且這個對象的__proto__指向_.prototype(對于new對象執行有疑問,請猛戳此處)
此時
(obj instanceof _)為false(this instanceof _)為true
所以此處會執行this._wrapped = obj;,在新對象中,添加_wrapped屬性,將[1,2,3,4]掛載進去。
綜合上述函數實現的效果就是:
_([1,2,3,4]))<=====>new _([1,2,3,4])
然后執行如下構造函數:
var _ = function(obj){ this._wrapped = obj }
最后得到的對象為:
我們執行如下代碼:
console.log(_([1,2,3,4])); console.log(_.prototype); console.log(_([1,2,3,4]).__proto__ == _.prototype);
看一下打印的信息:
這表明通過_(obj)構建出來的對象確實具有兩個特征:
下面掛載了我們傳入的對象/數組
對象的_proto_屬性指向_的prototype
到此我們已經完成了第一個問題。
接著解決第二個問題:
這個對象依然能夠調用掛載在_對象上聲明的方法
我們先來執行如下代碼:
_([1,2,3,4]).first();
此時JavaScript執行器會先去找_([1,2,3,4])返回的對象上是否有first屬性,如果沒有就會順著對象的原型鏈上去找first屬性,直到找到并執行它。
我們發現_([1,2,3,4])返回的對象屬性和原型鏈上都沒有first!
那我們自己先在_.prototype上面加一個first屬性上去試試:
(function(){ //定義 var root = typeof self == "object" && self.self === self && self || typeof global == "object" && global.global === global && global || this || {}; var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; _.first = function(arr,n){ var n = n || 0; if(n==0) return arr[0]; return arr.slice(0,n); } _.prototype.first = function(arr,n){ var n = n || 0; if(n==0) return arr[0]; return arr.slice(0,n); } root._ = _; })();
我們在執行打印一下:
console.log(_([1,2,3,4]));
效果如下:
原型鏈上找到了first函數,我們可以調用first函數了。如下:
console.log(_([1,2,3,4]).first());
可惜報錯了:
于是調試一下:
我們發現arr是undefined,但是我們希望arr是[1,2,3,4]。
我們馬上改一下_.prototype.first的實現
(function(){ var root = typeof self == "object" && self.self === self && self || typeof global == "object" && global.global === global && global || this || {}; var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; _.first = function(arr,n){ var n = n || 0; if(n==0) return arr[0]; return arr.slice(0,n); } _.prototype.first = function(arr,n=0){ arr = this._wrapped; if(n==0) return arr[0]; return arr.slice(0,n); } root._ = _; })();
我們在執行一下代碼:
console.log(_([1,2,3,4]).first());
效果如下:
我們的效果似乎已經達到了!
現在我們執行下面的代碼:
console.log(_([1,2,3,4]).first(2));
調試一下:
涼涼了。
其實我們希望的是:
將[1,2,3,4]和2以arguments的形式傳入first函數
我們再來改一下:
//_.prototype.first = function(arr,n=0){ // arr = this._wrapped; // if(n==0) return arr[0]; // return arr.slice(0,n); //} _.prototype.first=function(){ /** * 搜集待傳入的參數 */ var that = this._wrapped; var args = [that].concat(Array.from(arguments)); console.log(args); }
我們再執行下面代碼:
_([1,2,3,4]).first(2);
看一下打印的效果:
參數都已經拿到了。
我們調用函數一下first函數,我們繼續改代碼:
_.prototype.first=function(){ /** * 搜集待傳入的參數 */ var that = this._wrapped; var args = [that].concat(Array.from(arguments)); /** * 調用在_屬性上的first函數 */ return _.first(...args); }
這樣一來_.prototype上面的函數的實現都省掉了,相當于做一層代理;而且我們不用再維護兩套代碼,一旦修改實現,兩邊都要改。
一舉兩得!
執行一下最初我們的代碼:
console.log(_.first([1,2,3,4])); console.log(_.first([1,2,3,4],1)); console.log(_.first([1,2,3,4],3));
現在好像我們所有的問題都解決了。
但是似乎還是怪怪的。
我們每聲明一個函數都得在原型鏈上也聲明一個同名函數。形如下面:
_.a = function(args){ //a的實現 } _.prototype.a = function(){ //調用_.a(args) } _.b = function(args){ //b的實現 } _.prototype.b = function(){ //調用_.b(args) } _.c = function(args){ //c的實現 } _.prototype.c = function(){ //調用_.c(args) } . . . 1000個函數之后...
會不會覺得太恐怖了!
我們能不能改成如下這樣呢?
_.a = function(args){ //a的實現 } _.b = function(args){ //b的實現 } _.c = function(args){ //c的實現 } 1000個函數之后... _.mixin = function(){ //將_屬性中聲明的函數都掛載在_prototype上面 } _.mixin(_);
上面這么做好處大大的:
我們可以專注于函數庫的實現,不用機械式的復寫prototype上的函數。
underscore也正是這么做的!
我們看看mixin函數在underscore中的源碼實現:
// Add your own custom functions to the Underscore object. _.mixin = function(obj) { _.each(_.functions(obj), function(name) { var func = _[name] = obj[name]; _.prototype[name] = function() { var args = [this._wrapped]; push.apply(args, arguments); return chainResult(this, func.apply(_, args)); }; }); return _; }; // Add all of the Underscore functions to the wrapper object. _.mixin(_);
有了上面的鋪墊,這個代碼一點都不難看懂,首先調用_.each函數,形式如下:
_.each(arrs, function(item) { //遍歷arrs數組中的每一個元素 }
我們一想就明白,我們在_對象屬性上實現了自定義函數,那么現在要把它們掛載到—_.prototype屬性上面,當然先要遍歷它們了。
我們可以猜到_.functions(obj)肯定返回的是一個數組,而且這個數組肯定是存儲_對象屬性上面關于我們實現的各個函數的信息。
我們看一下_.function(obj)的實現:
_.functions = _.methods = function(obj) { var names = []; /** ** 遍歷對象中的屬性 **/ for (var key in obj) { //如果屬性值是函數,那么存入names數組中 if (_.isFunction(obj[key])) names.push(key); } return names.sort(); };
確實是這樣的!
我們把上述實現的代碼整合起來:
(function(){ /** * 保證兼容性 */ var root = typeof self == "object" && self.self === self && self || typeof global == "object" && global.global === global && global || this || {}; /** * 在調用_(obj)時,讓其執行new _(obj),并將obj掛載在_wrapped屬性之下 */ var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; //自己實現的first函數 _.first = function(arr,n){ var n = n || 0; if(n==0) return arr[0]; return arr.slice(0,n); } //判斷是否是函數 _.isFunction = function(obj) { return typeof obj == "function" || false; }; //遍歷生成數組存儲_對象的函數值屬性 _.functions = _.methods = function(obj) { var names = []; for (var key in obj) { if (_.isFunction(obj[key])) names.push(key); } return names.sort(); }; //自己實現的遍歷數組的函數 _.each = function(arrs,callback){ for(let i=0;i我們看一下_.functions(obj)返回的打印信息:
確實是_中自定義函數的屬性值。
我們再來分析一下each中callback遍歷各個屬性的實現邏輯。
var func = _[name] = obj[name]; _.prototype[name] = function() { var args = [this._wrapped]; push.apply(args, arguments); return func.apply(_, args); };第一句:func變量存儲每個自定義函數
第二句: _.prototype[name]=function();在_.prototype上面聲明相同屬性的函數
第三句:args變量存儲_wrapped下面掛載的值
第四句:跟var args = [that].concat(Array.from(arguments));作用相似,將兩邊的參數結合起來
第五句:執行func變量指向的函數,執行apply函數,將上下文對象_和待傳入的參數args`傳入即可。
我們再執行以下代碼:
console.log(_.first([1,2,3,4])); console.log(_.first([1,2,3,4],1)); console.log(_.first([1,2,3,4],3));結果如下:
Perfect!
這個函數在我們的瀏覽器中使用已經沒有問題。
但是在Node中呢?又引出新的問題。
再回歸兼容性問題我們知道在Node中,我們是這樣的:
//a.js let a = 1; module.exports = a; //index.js let b = require("./a.js"); console.log(b) //打印1那么:
let _ = require("./underscore.js") _([1,2,3,4]).first(2);我們也希望上述的代碼能夠在Node中執行。
所以root._ = _是不夠的。
underscore是怎么做的呢?
如下:
if (typeof exports != "undefined" && !exports.nodeType) { if (typeof module != "undefined" && !module.nodeType && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; }我們看到當全局屬性exports不存在或者不是DOM節點時,說明它在瀏覽器中,所以:
root._ = _;
如果exports存在,那么就是在Node環境下,我們再來進行判斷:
如果module存在,并且不是DOM節點,并且module.exports也存在的話,那么執行:
exports = module.exports = _;
在統一執行:
exports._ = _;
附錄下面是最后整合的閹割版underscore代碼:
(function(){ /** * 保證兼容性 */ var root = typeof self == "object" && self.self === self && self || typeof global == "object" && global.global === global && global || this || {}; /** * 在調用_(obj)時,讓其執行new _(obj),并將obj掛載在_wrapped屬性之下 */ var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; //自己實現的first函數 _.first = function(arr,n){ var n = n || 0; if(n==0) return arr[0]; return arr.slice(0,n); } //判斷是否是函數 _.isFunction = function(obj) { return typeof obj == "function" || false; }; //遍歷生成數組存儲_對象的函數值屬性 _.functions = _.methods = function(obj) { var names = []; for (var key in obj) { if (_.isFunction(obj[key])) names.push(key); } return names.sort(); }; //自己實現的遍歷數組的函數 _.each = function(arrs,callback){ for(let i=0;i歡迎各位大佬拍磚!同時您的點贊是我寫作的動力~謝謝。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/102528.html
摘要:引子數組去重是一個老生常談的話題,在面試中也經常會被問道。其中如果數組是排序的,去重運算效率更高,因為排序能夠將相同的數排列在一起,方便前后比較。當數組有序對于對象的去重,我們知道為,所以使用比較對象在實際場景中沒有意義。 引子 數組去重是一個老生常談的話題,在面試中也經常會被問道。對于去重,有兩種主流思想: 先排序,線性遍歷后去重,時間復雜度O(n*log2n); 使用哈希,空間換...
摘要:書籍如下面向對象編程指南,風格輕松易懂,比較適合初學者,原型那塊兒講得透徹,種繼承方式呢。還有另一件事情是,比如發現自己某個知識點不太清楚,可以單獨去百度。 作者:小不了鏈接:https://zhuanlan.zhihu.com/p/...來源:知乎著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。 鑒于時不時,有同學私信問我(老姚,下同)怎么學前端的問題。這里統一回...
摘要:寫在前面專題系列是我寫的第二個系列,第一個系列是深入系列。專題系列自月日發布第一篇文章,到月日發布最后一篇,感謝各位朋友的收藏點贊,鼓勵指正。 寫在前面 JavaScript 專題系列是我寫的第二個系列,第一個系列是 JavaScript 深入系列。 JavaScript 專題系列共計 20 篇,主要研究日常開發中一些功能點的實現,比如防抖、節流、去重、類型判斷、拷貝、最值、扁平、柯里...
摘要:所以它與其他系列的文章并不沖突,完全可以在閱讀完這個系列后,再跟著其他系列的文章接著學習。如何閱讀我在寫系列的時候,被問的最多的問題就是該怎么閱讀源碼我想簡單聊一下自己的思路。感謝大家的閱讀和支持,我是冴羽,下個系列再見啦 前言 別名:《underscore 系列 8 篇正式完結!》 介紹 underscore 系列是我寫的第三個系列,前兩個系列分別是 JavaScript 深入系列、...
閱讀 3705·2021-11-25 09:43
閱讀 2605·2021-11-18 13:11
閱讀 2213·2019-08-30 15:55
閱讀 3277·2019-08-26 11:58
閱讀 2829·2019-08-26 10:47
閱讀 2234·2019-08-26 10:20
閱讀 1278·2019-08-23 17:59
閱讀 3010·2019-08-23 15:54