dojo/_base/lang源碼分析


  dojo/_base/lang模塊是一個工具模塊,但幾乎用dojo開發的app都會用到這個模塊。模塊中的方法能夠在某些開發場景中避免繁冗的代碼,接下來我們一起看看這些工具函數的使用和原理(僅僅是原理的實現,並非是dojo中的源碼)。

 

  lang.mixin(dest, sources...),這個函數的作用是將所有source中的屬性拷貝到dest中,並返回dest。例子如下:

var flattened = lang.mixin(
    {
        name: "Frylock",
        braces: true
    },
    {
        name: "Carl Brutanananadilewski"
    });

// 打印結果 "Carl Brutanananadilewski"
console.log(flattened.name);
// 打印結果 "true"
console.log(flattened.braces);

  因為dest與source都是對象,而遍歷對象中所有的屬性可以使用for-in。所以mixin的原理就是利用for-in將source中的所有屬性拷貝到dest中,如果某個屬性指向對象,我們只做淺拷貝。原理實現:

function mixin(dest, source, copyFunc) {
    var empty = {};
    for (var p in source) {
        if (!(p in empty) || empty[p] !== source[p]) {
            if (copyFunc) {
                dest[p] = copyFunc(source[p]);
            } else {
                dest[p] = source[p];
            }
        }

    }

    return dest;
}

  !(p in empty) || empty[p] !== source[p] 這句話是防止在safari的低版本瀏覽器中,將toString等Object.prototype的某些屬性拷貝過去。但如果source中重寫了toString,這個屬性是需要拷貝過去的。

  與mixin相關的還有lang.extend(ctor, props)這個函數。mixin的目的是從一個對象向另一個對象復制屬性,而extend的目的是將屬性復制到一個類的原型中。其主要的原理也是利用mixin:mixin(ctor.prototype, props)

 

 

  lang.getObject(prop, create, obj),這個函數是我很喜歡用的一個,如果要取一個對象中很深的一個屬性值,它能避免編寫繁冗的屬性層級判斷。舉個例子:

var a = {
    aa:{
        aaa: {
            aaaa: "asdaf"
        }
    }
};

console.log(lang.getObject('aa.aaa', false, a));//undefined
console.log(lang.getObject('aa.bb', true, a));//{}
console.log(lang.getObject('aa.bb.ccc.toString', true, a));//{}
console.log(lang.getObject('aa.aaa.aaaa.c', false, a));//undefined
console.log(lang.getObject('aa.aaa.aaaa.c', true, a));//{}

  如果不使用這個函數,要取得“asdaf”,我們需要寫如下的一堆判斷:

var value = a && a.aa && a.aa.aaa && a.aa.aaa.aaaa;

  這個函數的原理也比較簡單,將prop分割后,一層一層的去判斷下一層屬性是否存在。原理實現:

var getProp = function(prop, create, obj) {
    debugger;
    var parts = prop.split('.');
    if (parts.length === 0) {
        return obj
    }

    for (var i = 0; i < parts.length; i++) {
        var p = parts[i];
        try{//obj為基礎類型時,用in運算符有問題
            if (p in obj) {
                obj = obj[p];
            } else {
                if (create) {//create為true則將屬性指向一個空對象
                    obj = obj[p] = {};
                } else {
                    return;
                }
            }
        }catch(e) {
            return;
        }
    }

    return obj;
}

  lang.setObject(name, value, context)函數與getObject一個是設值一個是取值。name是屬性串(如a.b.c),setObject的實現也要利用getProp,先取至倒數第二層的屬性,然后為最后一層賦值,實現代碼如下:

var setObject = function(prop, value, obj) {
    var parts = prop.split('.');
    var p = parts.pop();

    var o = getProp(parts.join('.'), true, obj); //注意第二個參數是true,如果屬性不存在,會創建一個新對象

    return o && p ? o[p] = value : undefined;
}

  lang.exists(name, obj)主要用來判斷一個對象中是否存在某個屬性,它也利用了getProp。

exists: function(name, obj){
            return lang.getObject(name, false, obj) !== undefined; // Boolean
        }

  但我在實際的工作中,發現這個函數並不如getObject好用,因為它僅僅以undefined來做判斷,如果屬性為null,這個函數仍然返回true。所以不建議使用exists,最好還是使用getObject。

 

  下面介紹的hitch和delegate函數都要小伙伴們對閉包有足夠的了解,不了解的童鞋,推薦這篇文章:干貨分享讓你分分鍾學會 javascript 閉包

  lang.hitch(scope, method)函數目的是改變函數中this關鍵字的指向,它相當於bind函數,這個函數也是每個程序都要用到的。原理如下:

var hitch = function() {
    var scope = arguments[0];
    var method = arguments[1];
    var args = Array.prototype.slice.call(arguments, 2);

    return function() {
        var args2 = args.concat(arguments);

        return method.apply(scope, args2);
    }
}

  示例:

var a = {
    nodeType: "a",
    f: function(){
        console.log(this.nodeType);
    }
};
document.addEventListener("click", hitch(a, a.f), false);//a
document.addEventListener("click", a.f, false);//9

  lang.delegate(obj),顧名思義為一個對象做代理,該函數的作用跟Object.create函數在本質上是相同的。因為JavaScript中對象的易變性,可以很輕松的去修改一個對象的屬性。代理對象能在一定程度上保護原對象不被修改,而且代理比克隆的速度快。

var delegate = (function(){
    var T = function(){};
    return function(s) {
        T.prototype = s;

        return new T();
    };
})();

  示例:

var s = {
    a: "hello",
    b: {
        bb: "world"
    },
    c: function(){console.log(this.a);}
};

ss = delegate(s);

console.log(ss.a); //hello
console.log(ss.b); //{bb:"world"}
console.log(ss.c);// function(){console.log(this.a);}


ss.a = 5555;
console.log(ss.a, s.a);//5555, hello
ss.b.bb = "asf";
console.log(ss.b, s.b);//{bb:"asf"}, {bb:"asf"}

  通過示例我們可以明白,只能在一定程度上保護原對象不被修改。如果對象中的屬性都是基本類型,建議使用代理函數來代替克隆,否則的話還是老老實實的用克隆吧。

 

 

  lang.clone(/*anything*/ src),克隆函數也會經常被用到,dojo的克隆函數可以克隆任何變量,基本策略如下:

  • 基本類型和函數實例直接返回
  • dom對象,利用cloneNode,克隆元素和元素的子元素(cloneNode不會克隆事件)
  • Date類型:return new Date(src.getTime())
  • RegExp類型:return new RegExp(src)
  • 數組類型,依次便利每個數組元素,並對每個元素都進行克隆
  • 其他類型對象,利用src.constructor屬性

  原理實現如下:

var clone = function(src) {
    if (!src || typeof src !== "object" || typeof src === "function") {
        return src;
    } else if (src instanceof Date) {
        return new Date(src.getTime());
    } else if (src instanceof RegExp) {
        return new RegExp(src);
    } else if (src.nodeType && 'cloneNode' in src) {
        return src.cloneNode(true);
    } 

    var cp;
    if (src instanceof Array) {
        var cp = src.slice(0);
        cp.map(function(e) {
            return clone(e);
        });
    } else if (src && src.constructor) {
        cp = new src.constructor() : {};
        mixin(cpp, src);
    }

    return mixin(cp, src);
}

  注意:最好不要去克隆不同frame中的對象

  

  lang.trim和lang.replace都是處理字符串的,一個是去除字符串頭尾的空白字符,一個是替換字符串。trim比較簡單,先來看一下:

trim: String.prototype.trim ?
            function(str){ return str.trim(); } :
            function(str){ return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); }

  replace(tmpl, map, pattern),用來將tmpl中匹配pattern正則的部分用map中相應的屬性值來替換。先看幾個示例:

// example:
            //    |    // uses a dictionary for substitutions:
            //    |    lang.replace("Hello, {name.first} {name.last} AKA {nick}!",
            //    |        {
            //    |            nick: "Bob",
            //    |            name: {
            //    |                first:    "Robert",
            //    |                middle: "X",
            //    |                last:        "Cringely"
            //    |            }
            //    |        });
            //    |    // returns: Hello, Robert Cringely AKA Bob!
            // example:
            //    |    // uses an array for substitutions:
            //    |    lang.replace("Hello, {0} {2}!",
            //    |        ["Robert", "X", "Cringely"]);
            //    |    // returns: Hello, Robert Cringely!
            // example:
            //    |    // uses a function for substitutions:
            //    |    function sum(a){
            //    |        var t = 0;
            //    |        arrayforEach(a, function(x){ t += x; });
            //    |        return t;
            //    |    }
            //    |    lang.replace(
            //    |        "{count} payments averaging {avg} USD per payment.",
            //    |        lang.hitch(
            //    |            { payments: [11, 16, 12] },
            //    |            function(_, key){
            //    |                switch(key){
            //    |                    case "count": return this.payments.length;
            //    |                    case "min":        return Math.min.apply(Math, this.payments);
            //    |                    case "max":        return Math.max.apply(Math, this.payments);
            //    |                    case "sum":        return sum(this.payments);
            //    |                    case "avg":        return sum(this.payments) / this.payments.length;
            //    |                }
            //    |            }
            //    |        )
            //    |    );
            //    |    // prints: 3 payments averaging 13 USD per payment.
            // example:
            //    |    // uses an alternative PHP-like pattern for substitutions:
            //    |    lang.replace("Hello, ${0} ${2}!",
            //    |        ["Robert", "X", "Cringely"], /\$\{([^\}]+)\}/g);
            //    |    // returns: Hello, Robert Cringely!

  replace函數本質上是利用String自身的replace函數,原理實現如下:

var exp = /\{([^}])+\}/g;

var replace = function(str, map, copy) {
    debugger;
    return str.replace(exp, function(_, k) {
        return getProp(k, false, map);
    });
}

 

 

  lang模塊中,還有一部分isType函數,這些函數大家當做常識記住即可。

isString: function(it){
            // summary:
            //        Return true if it is a String
            // it: anything
            //        Item to test.
            return (typeof it == "string" || it instanceof String); // Boolean
        },

        isArray: function(it){
            // summary:
            //        Return true if it is an Array.
            //        Does not work on Arrays created in other windows.
            // it: anything
            //        Item to test.
            return it && (it instanceof Array || typeof it == "array"); // Boolean
        },

        isFunction: function(it){
            // summary:
            //        Return true if it is a Function
            // it: anything
            //        Item to test.
            return opts.call(it) === "[object Function]";
        },

        isObject: function(it){
            // summary:
            //        Returns true if it is a JavaScript object (or an Array, a Function
            //        or null)
            // it: anything
            //        Item to test.
            return it !== undefined &&
                (it === null || typeof it == "object" || lang.isArray(it) || lang.isFunction(it)); // Boolean
        },

        isArrayLike: function(it){
            // summary:
            //        similar to isArray() but more permissive
            // it: anything
            //        Item to test.
            // returns:
            //        If it walks like a duck and quacks like a duck, return `true`
            // description:
            //        Doesn't strongly test for "arrayness".  Instead, settles for "isn't
            //        a string or number and has a length property". Arguments objects
            //        and DOM collections will return true when passed to
            //        isArrayLike(), but will return false when passed to
            //        isArray().
            return it && it !== undefined && // Boolean
                // keep out built-in constructors (Number, String, ...) which have length
                // properties
                !lang.isString(it) && !lang.isFunction(it) &&
                !(it.tagName && it.tagName.toLowerCase() == 'form') &&
                (lang.isArray(it) || isFinite(it.length));
        },

        isAlien: function(it){
            // summary:
            //        Returns true if it is a built-in function or some other kind of
            //        oddball that *should* report as a function but doesn't
            return it && !lang.isFunction(it) && /\{\s*\[native code\]\s*\}/.test(String(it)); // Boolean
        }
View Code

  唯一需要說一下的是isArrayLike函數,isArrayLike用來判斷類數組對象的。一般情況下length屬性為數字的對象都被認為是類數組對象,如:arguments、NodeList、{length:5}等,但像String(涉及到JavaScript中的包裝對象)、function(function對象的length代表形參的個數)都具有length屬性這些需要排除,而且dojo中把form元素也排除在外,這點我還不是很明白,希望有哪位兄台能夠解惑。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM