myUnderscore 配合食用gitbook小書系列

配合源碼地址:heihei12305
node

配合gitbook小書地址 my-underscoregit

寫在前面,這本小書內容大量源於

  1. 訝羽博客系列
  2. JavaScript Es6 函數式編程入門教程
  3. 對角的lodash源碼分析
  4. underscore源碼分析

1. 將代碼都掛載到_上,經過_.引用

目標:es6

  • 支持函數式風格調用: _.reverse('hello');
  • 支持面向對象風格調用: _('hello').reverse();
  • 支持鏈式調用 _.chain([1,2,3,4]).filter((num)=>!num%2).map((num)=>num*num).value();

下面咱們一點一點的說吧。
github

首先爲了達到能夠用_調用的目的,咱們須要先定義出_,也即:編程

var root = (typeof self == 'object' && self.self == self && self) ||
        (typeof global == 'object' && global.global == global && global)||
        this || {};
				
        //在瀏覽器中,除了window屬性,咱們也能夠經過self屬性直接訪問到Window對象,同時self還能夠支持Web Worker
        //Node環境中全局變量爲 global
        //node vm(沙盒模型) 中不存在window,也不存在global變量,但咱們卻能夠經過this訪問到全局變量
        //在微信小程序中,window和global都是undefined,加上強制使用嚴格模式,this爲undefined,就多了{}

var _ = function(obj){
        
        if(obj instanceof _){
            return obj;
        }
        
        if(!(this instanceof _)){ // 此處爲了實現面向對象風格調用,能夠暫時無論
            return new _(obj);
        }
        this._wrapped = obj;
    };

 //exports.nodeType 防止<div id="exports"></div>產生的window.exports全局變量。
 
    if(typeof exports != 'undefined' && !exports.nodeType){
        if((typeof module != 'undefined' && !module.nodeType && module.exports)){
				
        //在nodeJs中,exports是module.exports 的一個引用,當你使用了module.exports = function(){}
        //實際上覆蓋了module.exports,可是exports並未發生改變,爲了不後面在修改exports而致使不能正確輸出
        //寫成這樣,將二者保持統一。
				
            exports = module.exports = _;
        } 
        exports._ = _;
    }else{
       //?不太懂部分,將_掛到全局屬性_上
       root._ = _;
    }
複製代碼

上面代碼中,咱們首先定義了全局屬性,考慮node,瀏覽器,微信小程序等不一樣的環境,咱們進行了一長串的屬性選擇。最終得到了咱們須要的root屬性。而後將_掛到了root上。這一塊能夠看看訝羽大大的博客寫underscore的第一二章部分小程序


有了_,咱們就能夠開始把咱們寫的函數都掛到_上了,這個簡單,定義的時候這麼寫就行了 _.reverse = ()=>;
微信小程序

爲了實現面向對象風格調用。


示例分析:_([1,2,3]):api

var _ = function(obj){ 
    if(!(this instanceof _)){ // 此處爲了實現面向對象風格調用,能夠暫時無論
        return new _(obj);
    }
    this._wrapped = obj;
		//部分
};
複製代碼
  1. 執行this instanceof _ ,this指向 window,window instanceof _爲 false,!操做符取反,因此執行 new _(obj).
  2. new _(obj)中,this指向實例對象,window instanceof _ 爲 true,取反後,代碼接着執行。
  3. 執行 this._wrapped = obj, 函數執行結束。
  4. 總結,_([1,2,3])返回一個對象,爲{_wrapped:[1,2,3]},該對象原型指向_.prototype

也即咱們下一步目標:將_上的函數也掛到_.prototype上。
數組

先把_上的函數獲取一下吧。
瀏覽器

functions()

//將obj中全部函數均push進names中
    _.functions = function(obj){
        var names = [];
        for(var key in obj){
            if(_.isFunction(obj[key])){
                names.push(key);
            }
        }
        return names.sort();
    }
複製代碼

each()


一個源於jQuery的通用遍歷方法,可用於遍歷對象和數組
回調函數擁有兩個參數:第一個爲對象的成員或數組的索引,第二個爲對應變量或內容
並且能夠退出循環

//數組遍歷
$.each([0,1,2],function(i,n){
		console.log('item # '+ i + ": " + n)
})
//item #0:0
//item #1:1
//item #2:2

//對象遍歷
$.each({name;"John",lang:"JS"},function(i,n){
		console.log('name: '+ i + ",value: " + n)
})
//item name,value:John
//name:lang,value:JS

//退出循環
$.each([0,1,2,3,4,5],function(i,n){
	if(i>2){
		return false;
	}
	console.log("item #"+i+": " + n );
});
//item #0:0
//item #1:1
//item #2:2
複製代碼

_.each = function(obj,callback){
	var length,i = 0;

	//判斷類數組對象和數組
	if(_.isArrayLike(obj)){
		//爲數組時
		length = obj.length;
		for(;i<length;i++){
		//綁定this到當前遍歷元素上,可是call對性能有一丟丟影響
		if(callback.call(obj[i],obj[i],i) === false){
	    	//當回調函數返回false的時候,咱們就停止循環
				break;
			}
		}
		}else{
			//爲對象時
			for( i in obj){
				if(callback.call(obj[i],obj[i],i) === false){
					break;
				}
			}
		}
		return obj;
	}
}
複製代碼

有了上面兩個函數,咱們就能夠來寫咱們的主角 mixin()

回顧一下

由於_([1,2,3])返回一個爲{_wrapped:[1,2,3]}的原型指向_.prototype的對象
爲了調用_函數對象上的方法,咱們要把_上的方法複製到_.prototype

_.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 _;
}
複製代碼

這裏咱們就能夠用對象風格和函數風格來調用咱們的函數庫裏的文件了,如今,咱們來實現鏈式調用。


鏈式調用

回顧一下:
鏈式調用例子 : _.chain([1,2,3,4]).filter((num)=>!num%2).map((num)=>num*num).value();

就依照上面那個函數走好了

爲了實現鏈式調用,咱們首先須要調用_.chain()處理如下咱們的參數

_.chain = function(obj){
    var instance = _(obj);
    instance._chain = true;
    return instance;
}
複製代碼

源碼很簡單,咱們把傳入的obj處理了一下,結合上面的_函數,回到頂部,傳入[1,2,3,4],返回值會是

{
_wrapped : [1,2,3,4];
_chain : true;
}
複製代碼

而後咱們須要一個函數來判斷咱們剛添加的_chain屬性,也即 chainResult()

//爲了判斷是否有_.chain(),便是否採用鏈式調用
    var chainResult = function (instance, obj){
        return instance._chain?_(obj).chain():obj;
    }
複製代碼

而後,在咱們在mixin()中調用便可:chainResult(this,func.apply(_,args));

回顧一下:

//mixin()函數部分
_.prototype[name] = function(){
	var args = [this._wrapped];
	push.apply(args,arguments);
	return chainResult(this,func.apply(_,args));
};
        
複製代碼

咱們在調用_.prototype上的函數時,會跳轉到mixin()中也即咱們定義原型鏈函數的地方,而後return的時候會調用chainResult函數,而後返回值就會被處理,也即被調用的函數都會返回一個帶有_chain : true的對象。


可是咱們最後的函數返回值依舊是一個對象,咱們須要的是裏面的_wrapped,這裏就須要咱們的_.value()了:

_.prototype.value = function(){
    return this._wrapped;
    }
複製代碼

小節結語,這裏,咱們完成了一個函數庫最基礎的佈置,下面的章節咱們開始向咱們的函數庫填充抽象出的可複用函數啦!


類數組之isArrayLike()

類數組定義:擁有一個length 屬性和若干索引屬性的對象

例如:

  • arguments
  • 有length屬性的對象
a = {
	1:1,
	2:2,
	c:3,
	length:4
}

console.log(_.isArrayLike(a));//true
複製代碼
_.isArrayLike = function(collection){
    var length = collection.length;
    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
}
複製代碼

其中,MAX_ARRAY_INDEX指的是最大安全數

var MAX_ARRAY_INDEX = Math.pow(2,53) - 1;

關於最大安全數,不懂的話能夠看看這個JavaScript 浮點數陷阱及解法

函數判斷之isFunction()

_.isFunction = function(obj){
    return typeof obj == 'function' || false;
}
複製代碼

就是簡單判斷一個函數而已,不過能夠擴展出一個知識點嘿嘿,

你是怎麼看js中的&&,||

與其說是邏輯運算符,不如說是屬性選擇符。

  • &&執行方式(a&&b):
    • 若是a爲true,返回b
    • 不然返回a
  • ||執行方式(a || b):
    • 若是a爲true, 返回a
    • 不然返回b
'' || [1,2] //[1,2]
'' && [1,2] //""

[1] || [] //[1]
[1] && [] //[]
複製代碼

而後把js中爲false的屬性整理一下啦:
ES5規範9.2定義了抽象操做ToBoolean,列舉了布爾強制類型轉換全部可能出現的結果。

  • undefined
  • null
  • false
  • +0,-0 和 NaN
  • ""

從邏輯上說,假值列表之外的都應該是真值。

  • 除了'',其餘的字符串都是真值。
let a  = 'false';
let b = '0';
let c = "''";

let d = Boolean(a && b && c );
d; // true
複製代碼
  • [],{},fucntion(){}都不在假值列表裏,因此他們都是真值。
let a  = [];
let b = {};
let c = function(){};

let d = Boolean(a && b && c );
d; // true
複製代碼

真值列表是無限長的,咱們只能根據假值列表做爲參考,能夠理解位假值列表之外的值都是真值。


  • 經常使用的 || 用法:設置默認值
function foo(a,b){
    a = a || 'hello';
    b = b || 'world';

    console.log(a + ' ' + b);
};

foo(1);// '1 world'

複製代碼

  • js代碼壓縮工具經常使用的‘守護運算符’:
function foo(){
    console.log(a);
};

let a = 1;
a && foo(); 
//當第一個操做數爲真值時,&&纔會選擇第二個操做數做爲返回值,即前面的表達式爲後面的表達式'把關'
複製代碼

字符反轉 reverse()

_.reverse = function(string){
        return string.split('').reverse().join('');
    }
複製代碼

這是一種取巧的作法,過程至關於

'1234'=>['1','2','3','4']=>['4','3','2','1']=>'4321'
複製代碼
相關文章
相關標籤/搜索