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
        }

唯一需要说一下的是isArrayLike函数,isArrayLike用来判断类数组对象的。一般情况下length属性为数字的对象都被认为是类数组对象,如:arguments、NodeList、{length:5}等,但像String(涉及到JavaScript中的包装对象)、function(function对象的length代表形参的个数)都具有length属性这些需要排除,而且dojo中把form元素也排除在外,这点我还不是很明白,希望有哪位兄台能够解惑。
目录
相关文章
|
3月前
|
JavaScript 前端开发
【面试题】 JS手写ES6的Object.create方法
【面试题】 JS手写ES6的Object.create方法
|
1月前
|
Java
java base64转doc文件
以下是使用Java进行Base64转换的例子: 1. 使用Java Base64工具类进行转换 ```java // 导入相关包 import java.util.Base64; import java.io.FileOutputStream; import java.io.IOException; // 将Base64字符串转换为字节数组 byte[] decodedBytes = Base64.getDecoder().decode(base64String); // 将字节数组写入到文件中 try (FileOutputStream fos = new FileOutputStrea
|
JavaScript 前端开发 Java
【ES6】JS类的用法class
【ES6】JS类的用法class
129 0
|
JavaScript Java
Java Web——jQuery中的过滤器与第一组函数(val、text、attr)
Java Web——jQuery中的过滤器与第一组函数(val、text、attr)
Java Web——jQuery中的过滤器与第一组函数(val、text、attr)
|
前端开发 Android开发
React Native之提示Unable to load script from assets ‘index.android.bundle
React Native之提示Unable to load script from assets ‘index.android.bundle
94 0
How is correct index.html served by ui5 handler from BSP repository
Created by Jerry Wang, last modified on Aug 26, 2015 This wiki explains the process how the index.html of Fiori application is served by UI5 resource handler. The url of index looks like below: https://:/sap/bc/ui5_ui5/sap/zfiori150320/index.html?sap-client=001&sap-ui-language=EN&sap-ui-appcache=fal
73 0
How is correct index.html served by ui5 handler from BSP repository
|
Web App开发 移动开发 JavaScript
使用Google App Engine、Google Closure Library与Clojure编写HTML5应用
上周,Freiheit.com的CTO Stefan Richter在慕尼黑举办的Google开发者日上谈到了他对于使用HTML 5与Google App Engine编写富Internet应用的愿景。
900 0