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

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

dojo/_base/lang源码分析的更多相关文章

  1. Vue.js 源码分析(十二) 基础篇 组件详解

    组件是可复用的Vue实例,一个组件本质上是一个拥有预定义选项的一个Vue实例,组件和组件之间通过一些属性进行联系. 组件有两种注册方式,分别是全局注册和局部注册,前者通过Vue.component() ...

  2. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  3. MyCat源码分析系列之——结果合并

    更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...

  4. MyCat源码分析系列之——BufferPool与缓存机制

    更多MyCat源码分析,请戳MyCat源码分析系列 BufferPool MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemC ...

  5. spring源码分析之spring-core总结篇

    1.spring-core概览 spring-core是spring框架的基石,它为spring框架提供了基础的支持. spring-core从源码上看,分为6个package,分别是asm,cgli ...

  6. Spring源码分析——资源访问利器Resource之实现类分析

    今天来分析Spring的资源接口Resource的各个实现类.关于它的接口和抽象类,参见上一篇博文——Spring源码分析——资源访问利器Resource之接口和抽象类分析 一.文件系统资源 File ...

  7. 《深入理解Spark:核心思想与源码分析》——SparkContext的初始化(叔篇)——TaskScheduler的启动

    <深入理解Spark:核心思想与源码分析>一书前言的内容请看链接<深入理解SPARK:核心思想与源码分析>一书正式出版上市 <深入理解Spark:核心思想与源码分析> ...

  8. spring源码分析(二)Aop

    创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...

  9. Tomcat源码分析

    前言: 本文是我阅读了TOMCAT源码后的一些心得. 主要是讲解TOMCAT的系统框架, 以及启动流程.若有错漏之处,敬请批评指教! 建议: 毕竟TOMCAT的框架还是比较复杂的, 单是从文字上理解, ...

随机推荐

  1. 用Action的属性接受参数

    版本, @Override是Java5的元数据,自动加上去的一个标志,告诉你说下面这个方法是从父类/接口 继承过来的,需要你重写一次,这样就可以方便你阅读,也不怕会忘记@Override是伪代码,表示 ...

  2. ip地址合法性

    /* * Java语法上正则化表达式的使用技巧,对于'.'要用'\\.' (2)注意空字符串“”和null的区别,判断一个字符串是不是空字符串用.equals("") * (1,判 ...

  3. ocfs2: 搭建环境

    OCFS2是基于共享磁盘的集群文件系统,它在一块共享磁盘上创建OCFS2文件系统,让集群中的其它节点可以对磁盘进行读写操作.OCFS2由两部分内容构成,一部分实现文件系统功能,位于VFS之下和Ext4 ...

  4. mac地址和ip地址、子网掩码和默认网关

    MAC地址 MAC(Media Access Control或者Medium Access Control)地址,意译为媒体访问控制,或称为物理地址.硬件地址,用来定义网络设备的位置.在OSI模型中, ...

  5. Ajax Step By Step3

    第三[.$.getScript()和$.getJSON()] jQuery 提供了一组用于特定异步加载的方法:$.getScript(),用于加载特定的 JS 文件: $.getJSON(),用于专门 ...

  6. ueditor问题简记录

    一.百度ueditor下载地址:http://ueditor.baidu.com/website/download.html. uBuilder下载,个人选了一些自用的,.net的,但是很奇怪下载响应 ...

  7. PBX220 测评一

    //纯粹个人看法,可能包含非常不恰当的主观看法,敬请见谅. 本次测试的是易用科技Speedytel 新出的产品 PBX-220.      测试环境为:华硕EeePC(IE7).Eyebeam. 先来 ...

  8. Raab判别法确定级数是否收敛

  9. eclipse 配色方案

    http://www.blogjava.net/kuuyee/archive/2013/02/26/395728.html

  10. MMC不能打开文件D:\Program Files\Microsoft SQL Server\80\Tools\BINN\SQL Server Enterprise Manager.MSC

    以上问题的解决方式如下: 1. 打开windows运行对话框.在对话框输入mmc.打开了如图所示的控制台. 2. 文件---添加/删除管理单元(M). 3. 添加.然后选择Microsoft SQL ...