亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

跟underscore一起學(xué)如何寫函數(shù)庫(kù)

ephererid / 3093人閱讀

摘要:支持兩種不同風(fēng)格的函數(shù)調(diào)用在中我們可以使用以下兩種方式調(diào)用函數(shù)式的調(diào)用對(duì)象式調(diào)用在中,它們返回的結(jié)果都是相同的。

原文:https://zhehuaxuan.github.io/...  
作者:zhehuaxuan
目的

Underscore 是一個(gè) JavaScript 工具庫(kù),它提供了一整套函數(shù)式編程的實(shí)用功能,但是沒(méi)有擴(kuò)展任何 JavaScript 內(nèi)置對(duì)象。

本文主要梳理underscore內(nèi)部的函數(shù)組織與調(diào)用邏輯的方式和思想。

通過(guò)這篇文章,我們可以:

了解underscore在函數(shù)組織方面的巧妙構(gòu)思;

為自己書寫函數(shù)庫(kù)提供一定思路;

我們開(kāi)始!

自己寫個(gè)函數(shù)庫(kù)

前端的小伙伴一定不會(huì)對(duì)jQuery陌生,經(jīng)常使用$.xxxx的形式進(jìn)行調(diào)用,underscore使用_.xxxx,如果自己在ES5語(yǔ)法中寫過(guò)自定義模塊的話,就可以寫出下面一段代碼:

//IIFE函數(shù)
(function(){
    //獲取全局對(duì)象
    var root = this;
    //定義對(duì)象
    var _ = {};
    //定義和實(shí)現(xiàn)函數(shù)
    _.first = function(arr,n){
        var n = n || 0;
        if(n==0) return arr[0];
        return arr.slice(0,n);
    }
    //綁定在全局變量上面
    root._ = _;
})();
console.log(this);

在Chrome瀏覽器中打開(kāi)之后,打印出如下結(jié)果:

我們看到在全局對(duì)象下有一個(gè)_屬性,屬性下面掛載了自定義函數(shù)。
我們不妨使用_.first(xxxx)在全局環(huán)境下直接調(diào)用。

console.log(_.first([1,2,3,4]));
console.log(_.first([1,2,3,4],1));
console.log(_.first([1,2,3,4],3));

輸出結(jié)果如下:

沒(méi)問(wèn)題,我們的函數(shù)庫(kù)制作完成了,我們一般直接這么用,也不會(huì)有太大問(wèn)題。

underscore是怎么做的?

underscore正是基于上述代碼進(jìn)行完善,那么underscore是如何接著往下做的呢?容我娓娓道來(lái)!

對(duì)兼容性的考慮

首先是對(duì)兼容性的考慮,工具庫(kù)當(dāng)然需要考慮各種運(yùn)行環(huán)境。

// 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函數(shù)中的源碼,對(duì)應(yīng)于我們上面自己寫的var root = this;。

在源碼中作者也作了解釋:
創(chuàng)建root對(duì)象,并且給root賦值。怎么賦值呢?

瀏覽器端:window也可以是window.self或者直接self

服務(wù)端(node):global

WebWorker:self

虛擬機(jī):this

underscore充分考慮了兼容性,使得root指向?qū)謱?duì)象。

支持兩種不同風(fēng)格的函數(shù)調(diào)用

在underscore中我們可以使用以下兩種方式調(diào)用:

函數(shù)式的調(diào)用:console.log(_.first([1,2,3,4]));

對(duì)象式調(diào)用:console.log(_([1,2,3,4])).first();

在underscore中,它們返回的結(jié)果都是相同的。

第一種方式我們現(xiàn)在就沒(méi)有問(wèn)題,難點(diǎn)就是第二種方式的實(shí)現(xiàn)。

對(duì)象式調(diào)用的實(shí)現(xiàn)

解決這個(gè)問(wèn)題要達(dá)到兩個(gè)目的:

_是一個(gè)函數(shù),并且調(diào)用返回一個(gè)對(duì)象;

這個(gè)對(duì)象依然能夠調(diào)用掛載在_對(duì)象上聲明的方法。

我們來(lái)看看underscore對(duì)于_的實(shí)現(xiàn):

var _ = function(obj) {
      if (obj instanceof _) return obj;
      if (!(this instanceof _)) return new _(obj);
      this._wrapped = obj;
};

不怕,我們不妨調(diào)用_([1,2,3,4]))看看他是怎么執(zhí)行的!

第一步if (obj instanceof _) return obj;傳入的對(duì)象及其原型鏈上有_類型的對(duì)象,則返回自身。我們這里的[1,2,3,4]顯然不是,跳過(guò)。

第二步if (!(this instanceof _)) return new _(obj);,如果當(dāng)前的this對(duì)象及其原型鏈上沒(méi)有_類型的對(duì)象,那么執(zhí)行new操作。調(diào)用_([1,2,3,4]))時(shí),thiswindow,那么(this instanceof _)false,所以我們執(zhí)行new _([1,2,3,4])。

第三步:執(zhí)行new _([1,2,3,4]),繼續(xù)調(diào)用_函數(shù),這時(shí)

obj[1,2,3,4]

this為一個(gè)新對(duì)象,并且這個(gè)對(duì)象的__proto__指向_.prototype(對(duì)于new對(duì)象執(zhí)行有疑問(wèn),請(qǐng)猛戳此處)

此時(shí)

(obj instanceof _)為false

(this instanceof _)為true

所以此處會(huì)執(zhí)行this._wrapped = obj;,在新對(duì)象中,添加_wrapped屬性,將[1,2,3,4]掛載進(jìn)去。

綜合上述函數(shù)實(shí)現(xiàn)的效果就是:

_([1,2,3,4]))<=====>new _([1,2,3,4])

然后執(zhí)行如下構(gòu)造函數(shù):

var _ = function(obj){
    this._wrapped = obj
}

最后得到的對(duì)象為:

我們執(zhí)行如下代碼:

console.log(_([1,2,3,4]));
console.log(_.prototype);
console.log(_([1,2,3,4]).__proto__ == _.prototype);

看一下打印的信息:

這表明通過(guò)_(obj)構(gòu)建出來(lái)的對(duì)象確實(shí)具有兩個(gè)特征:

下面掛載了我們傳入的對(duì)象/數(shù)組

對(duì)象的_proto_屬性指向_prototype

到此我們已經(jīng)完成了第一個(gè)問(wèn)題。

接著解決第二個(gè)問(wèn)題:

這個(gè)對(duì)象依然能夠調(diào)用掛載在_對(duì)象上聲明的方法

我們先來(lái)執(zhí)行如下代碼:

_([1,2,3,4]).first();

此時(shí)JavaScript執(zhí)行器會(huì)先去找_([1,2,3,4])返回的對(duì)象上是否有first屬性,如果沒(méi)有就會(huì)順著對(duì)象的原型鏈上去找first屬性,直到找到并執(zhí)行它。

我們發(fā)現(xiàn)_([1,2,3,4])返回的對(duì)象屬性和原型鏈上都沒(méi)有first!

那我們自己先在_.prototype上面加一個(gè)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._ = _;
})();

我們?cè)趫?zhí)行打印一下:

console.log(_([1,2,3,4]));

效果如下:

原型鏈上找到了first函數(shù),我們可以調(diào)用first函數(shù)了。如下:

console.log(_([1,2,3,4]).first());

可惜報(bào)錯(cuò)了:

于是調(diào)試一下:

我們發(fā)現(xiàn)arrundefined,但是我們希望arr[1,2,3,4]。

我們馬上改一下_.prototype.first的實(shí)現(xiàn)

(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._ = _;
})();

我們?cè)趫?zhí)行一下代碼:

console.log(_([1,2,3,4]).first());

效果如下:

我們的效果似乎已經(jīng)達(dá)到了!

現(xiàn)在我們執(zhí)行下面的代碼:

console.log(_([1,2,3,4]).first(2));

調(diào)試一下:

涼涼了。

其實(shí)我們希望的是:

[1,2,3,4]2arguments的形式傳入first函數(shù)

我們?cè)賮?lái)改一下:

    //_.prototype.first = function(arr,n=0){
        // arr = this._wrapped;
        // if(n==0) return arr[0];
        // return arr.slice(0,n);
    //}
    _.prototype.first=function(){
        /**
         * 搜集待傳入的參數(shù)
         */
        var that = this._wrapped;
        var args = [that].concat(Array.from(arguments));
        console.log(args); 
    }

我們?cè)賵?zhí)行下面代碼:

_([1,2,3,4]).first(2);

看一下打印的效果:

參數(shù)都已經(jīng)拿到了。

我們調(diào)用函數(shù)一下first函數(shù),我們繼續(xù)改代碼:

_.prototype.first=function(){
     /**
      * 搜集待傳入的參數(shù)
     */
     var that = this._wrapped;
     var args = [that].concat(Array.from(arguments));
     /**
      * 調(diào)用在_屬性上的first函數(shù)
      */
     return _.first(...args);
}

這樣一來(lái)_.prototype上面的函數(shù)的實(shí)現(xiàn)都省掉了,相當(dāng)于做一層代理;而且我們不用再維護(hù)兩套代碼,一旦修改實(shí)現(xiàn),兩邊都要改。

一舉兩得!

執(zhí)行一下最初我們的代碼:

console.log(_.first([1,2,3,4]));
console.log(_.first([1,2,3,4],1));
console.log(_.first([1,2,3,4],3));

現(xiàn)在好像我們所有的問(wèn)題都解決了。

但是似乎還是怪怪的。
我們每聲明一個(gè)函數(shù)都得在原型鏈上也聲明一個(gè)同名函數(shù)。形如下面:

_.a = function(args){
    //a的實(shí)現(xiàn)
}
_.prototype.a = function(){
    //調(diào)用_.a(args)
}
_.b = function(args){
    //b的實(shí)現(xiàn)
}
_.prototype.b = function(){
    //調(diào)用_.b(args)
}
_.c = function(args){
    //c的實(shí)現(xiàn)
}
_.prototype.c = function(){
    //調(diào)用_.c(args)
}
.
.
.
1000個(gè)函數(shù)之后...

會(huì)不會(huì)覺(jué)得太恐怖了!

我們能不能改成如下這樣呢?

_.a = function(args){
    //a的實(shí)現(xiàn)
}
_.b = function(args){
    //b的實(shí)現(xiàn)
}
_.c = function(args){
    //c的實(shí)現(xiàn)
}
1000個(gè)函數(shù)之后...
_.mixin = function(){
    //將_屬性中聲明的函數(shù)都掛載在_prototype上面
}
_.mixin(_);

上面這么做好處大大的:

我們可以專注于函數(shù)庫(kù)的實(shí)現(xiàn),不用機(jī)械式的復(fù)寫prototype上的函數(shù)

underscore也正是這么做的!

我們看看mixin函數(shù)在underscore中的源碼實(shí)現(xiàn):

// 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(_);

有了上面的鋪墊,這個(gè)代碼一點(diǎn)都不難看懂,首先調(diào)用_.each函數(shù),形式如下:

 _.each(arrs, function(item) {
     //遍歷arrs數(shù)組中的每一個(gè)元素
 }

我們一想就明白,我們?cè)?b>_對(duì)象屬性上實(shí)現(xiàn)了自定義函數(shù),那么現(xiàn)在要把它們掛載到—_.prototype屬性上面,當(dāng)然先要遍歷它們了。

我們可以猜到_.functions(obj)肯定返回的是一個(gè)數(shù)組,而且這個(gè)數(shù)組肯定是存儲(chǔ)_對(duì)象屬性上面關(guān)于我們實(shí)現(xiàn)的各個(gè)函數(shù)的信息。

我們看一下_.function(obj)的實(shí)現(xiàn):

_.functions = _.methods = function(obj) {
  var names = [];
  /**
   **  遍歷對(duì)象中的屬性
   **/
  for (var key in obj) {
      //如果屬性值是函數(shù),那么存入names數(shù)組中
     if (_.isFunction(obj[key])) names.push(key);
  }
  return names.sort();
};

確實(shí)是這樣的!

我們把上述實(shí)現(xiàn)的代碼整合起來(lái):

(function(){
    /**
     * 保證兼容性
     */
    var root = typeof self == "object" && self.self === self && self ||
    typeof global == "object" && global.global === global && global ||
    this ||
    {};

    /**
     * 在調(diào)用_(obj)時(shí),讓其執(zhí)行new _(obj),并將obj掛載在_wrapped屬性之下
     */
    var _ = function(obj) {
        if (obj instanceof _) return obj;
        if (!(this instanceof _)) return new _(obj);
        this._wrapped = obj;
      };
    
    //自己實(shí)現(xiàn)的first函數(shù)
    _.first = function(arr,n){
        var n = n || 0;
        if(n==0) return arr[0];
        return arr.slice(0,n);
    }

    //判斷是否是函數(shù)
    _.isFunction = function(obj) {
        return typeof obj == "function" || false;
    };

    //遍歷生成數(shù)組存儲(chǔ)_對(duì)象的函數(shù)值屬性
    _.functions = _.methods = function(obj) {
        var names = [];
        for (var key in obj) {
          if (_.isFunction(obj[key])) names.push(key);
        }
        return names.sort();
      };
  
    //自己實(shí)現(xiàn)的遍歷數(shù)組的函數(shù)
    _.each = function(arrs,callback){
        for(let i=0;i

我們看一下_.functions(obj)返回的打印信息:

確實(shí)是_中自定義函數(shù)的屬性值。

我們?cè)賮?lái)分析一下each中callback遍歷各個(gè)屬性的實(shí)現(xiàn)邏輯。

var func = _[name] = obj[name];
 _.prototype[name] = function() {
     var args = [this._wrapped];
      push.apply(args, arguments);
     return func.apply(_, args);
};

第一句:func變量存儲(chǔ)每個(gè)自定義函數(shù)

第二句: _.prototype[name]=function();_.prototype上面聲明相同屬性的函數(shù)

第三句:args變量存儲(chǔ)_wrapped下面掛載的值

第四句:跟var args = [that].concat(Array.from(arguments));作用相似,將兩邊的參數(shù)結(jié)合起來(lái)

第五句:執(zhí)行func變量指向的函數(shù),執(zhí)行apply函數(shù),將上下文對(duì)象_和待傳入的參數(shù)args`傳入即可。

我們?cè)賵?zhí)行以下代碼:

console.log(_.first([1,2,3,4]));
console.log(_.first([1,2,3,4],1));
console.log(_.first([1,2,3,4],3));

結(jié)果如下:

Perfect!

這個(gè)函數(shù)在我們的瀏覽器中使用已經(jīng)沒(méi)有問(wèn)題。

但是在Node中呢?又引出新的問(wèn)題。

再回歸兼容性問(wèn)題

我們知道在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中執(zhí)行。

所以root._ = _是不夠的。

underscore是怎么做的呢?

如下:

if (typeof exports != "undefined" && !exports.nodeType) {
    if (typeof module != "undefined" && !module.nodeType && module.exports) {
        exports = module.exports = _;
    }
    exports._ = _;
} else {
    root._ = _;
}

我們看到當(dāng)全局屬性exports不存在或者不是DOM節(jié)點(diǎn)時(shí),說(shuō)明它在瀏覽器中,所以:

root._ = _;

如果exports存在,那么就是在Node環(huán)境下,我們?cè)賮?lái)進(jìn)行判斷:

如果module存在,并且不是DOM節(jié)點(diǎn),并且module.exports也存在的話,那么執(zhí)行:

exports = module.exports = _;

在統(tǒng)一執(zhí)行:

exports._ = _;

附錄

下面是最后整合的閹割版underscore代碼:

(function(){
    /**
     * 保證兼容性
     */
    var root = typeof self == "object" && self.self === self && self ||
    typeof global == "object" && global.global === global && global ||
    this ||
    {};

    /**
     * 在調(diào)用_(obj)時(shí),讓其執(zhí)行new _(obj),并將obj掛載在_wrapped屬性之下
     */
    var _ = function(obj) {
        if (obj instanceof _) return obj;
        if (!(this instanceof _)) return new _(obj);
        this._wrapped = obj;
      };
    
    //自己實(shí)現(xiàn)的first函數(shù)
    _.first = function(arr,n){
        var n = n || 0;
        if(n==0) return arr[0];
        return arr.slice(0,n);
    }

    //判斷是否是函數(shù)
    _.isFunction = function(obj) {
        return typeof obj == "function" || false;
    };

    //遍歷生成數(shù)組存儲(chǔ)_對(duì)象的函數(shù)值屬性
    _.functions = _.methods = function(obj) {
        var names = [];
        for (var key in obj) {
          if (_.isFunction(obj[key])) names.push(key);
        }
        return names.sort();
      };
  
    //自己實(shí)現(xiàn)的遍歷數(shù)組的函數(shù)
    _.each = function(arrs,callback){
        for(let i=0;i

歡迎各位大佬拍磚!同時(shí)您的點(diǎn)贊是我寫作的動(dòng)力~謝謝。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/102528.html

相關(guān)文章

  • underscore一起學(xué)數(shù)組去重

    摘要:引子數(shù)組去重是一個(gè)老生常談的話題,在面試中也經(jīng)常會(huì)被問(wèn)道。其中如果數(shù)組是排序的,去重運(yùn)算效率更高,因?yàn)榕判蚰軌驅(qū)⑾嗤臄?shù)排列在一起,方便前后比較。當(dāng)數(shù)組有序?qū)τ趯?duì)象的去重,我們知道為,所以使用比較對(duì)象在實(shí)際場(chǎng)景中沒(méi)有意義。 引子 數(shù)組去重是一個(gè)老生常談的話題,在面試中也經(jīng)常會(huì)被問(wèn)道。對(duì)于去重,有兩種主流思想: 先排序,線性遍歷后去重,時(shí)間復(fù)雜度O(n*log2n); 使用哈希,空間換...

    flybywind 評(píng)論0 收藏0
  • 如何學(xué)JavaScript

    摘要:書籍如下面向?qū)ο缶幊讨改?,風(fēng)格輕松易懂,比較適合初學(xué)者,原型那塊兒講得透徹,種繼承方式呢。還有另一件事情是,比如發(fā)現(xiàn)自己某個(gè)知識(shí)點(diǎn)不太清楚,可以單獨(dú)去百度。 作者:小不了鏈接:https://zhuanlan.zhihu.com/p/...來(lái)源:知乎著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。 鑒于時(shí)不時(shí),有同學(xué)私信問(wèn)我(老姚,下同)怎么學(xué)前端的問(wèn)題。這里統(tǒng)一回...

    light 評(píng)論0 收藏0
  • JavaScript專題系列20篇正式完結(jié)!

    摘要:寫在前面專題系列是我寫的第二個(gè)系列,第一個(gè)系列是深入系列。專題系列自月日發(fā)布第一篇文章,到月日發(fā)布最后一篇,感謝各位朋友的收藏點(diǎn)贊,鼓勵(lì)指正。 寫在前面 JavaScript 專題系列是我寫的第二個(gè)系列,第一個(gè)系列是 JavaScript 深入系列。 JavaScript 專題系列共計(jì) 20 篇,主要研究日常開(kāi)發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖、節(jié)流、去重、類型判斷、拷貝、最值、扁平、柯里...

    sixleaves 評(píng)論0 收藏0
  • 前端之從零開(kāi)始系列

    摘要:只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙只有動(dòng)手,你才能真正掌握一門技術(shù)持續(xù)更新中項(xiàng)目地址求求求源碼系列跟一起學(xué)如何寫函數(shù)庫(kù)中高級(jí)前端面試手寫代碼無(wú)敵秘籍如何用不到行代碼寫一款屬于自己的類庫(kù)原理講解實(shí)現(xiàn)一個(gè)對(duì)象遵循規(guī)范實(shí)戰(zhàn)手摸手,帶你用擼 Do it yourself!!! 只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙 只有動(dòng)手,你才能真正掌握一門技術(shù) 持續(xù)更新中…… 項(xiàng)目地址 https...

    Youngdze 評(píng)論0 收藏0
  • underscore 的源碼該如何閱讀?

    摘要:所以它與其他系列的文章并不沖突,完全可以在閱讀完這個(gè)系列后,再跟著其他系列的文章接著學(xué)習(xí)。如何閱讀我在寫系列的時(shí)候,被問(wèn)的最多的問(wèn)題就是該怎么閱讀源碼我想簡(jiǎn)單聊一下自己的思路。感謝大家的閱讀和支持,我是冴羽,下個(gè)系列再見(jiàn)啦 前言 別名:《underscore 系列 8 篇正式完結(jié)!》 介紹 underscore 系列是我寫的第三個(gè)系列,前兩個(gè)系列分別是 JavaScript 深入系列、...

    weknow619 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<