zoukankan      html  css  js  c++  java
  • 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(/^ss*/, '').replace(/ss*$/, ''); }

      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元素也排除在外,这点我还不是很明白,希望有哪位兄台能够解惑。

  • 相关阅读:
    用 Web 实现一个简易的音频编辑器
    TypeScript教程
    他的独,殇情沉醉
    重新拎一遍js的正则表达式
    网络流
    Xmind最新的安装与破解教程
    adb的端口号5037被占用的解决方法
    使用Fiddler模拟弱网测试教程
    web的前台、后台、前端、后端
    常见面试笔试题
  • 原文地址:https://www.cnblogs.com/dojo-lzz/p/5205842.html
Copyright © 2011-2022 走看看