`
driftcloudy
  • 浏览: 132218 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

DOJO中的面向对象__第四章 Dojo/_base/declare.js源码剖析(1)

    博客分类:
  • Dojo
阅读更多

  declare.js中包含了整个dojo面向对象中最重要的代码,即对类型表达和扩展的一些封装。功能虽然强大,但是幸好文件并不复杂,拥有清晰的脉络。整个declare.js一共定义了15个函数,14个具名函数,1个匿名函数。这14个具名函数中又有一些是declare.js内部使用的函数,外部无法调用,还有一些是由dojo提供的接口,可以供dojo.declare声明的类型来调用。具体函数如下所示:

//declare.js的结构(来源于Dojo1.5版):
(function(){
	//9个内部函数,外部无法调用	
	function err(){...}
	function c3mro(){...}
	function mixOwn(){...}
	function chainedConstructor(){...}
	function singleConstructor(){...}
	function simpleConstructor(){...}
	function chain(){...}
	function forceNew(){...}
	function applyNew(){...}
	//5个对外提供的接口
	function inherited(){...}
	function getInherited(){...}
	function isInstanceOf(){...}
	function extend()	{...}
	function safeMixin(){...}
	//1个匿名函数:Dojo.declare
	d.declare = function(){...}
})();

 

  其中最为核心的即匿名函数,用户在使用dojo.declare的时候即是使用的该函数。该函数负责产生一个实实在在的可以new出实例的JS构造器。本章打算从该函数开始,逐渐分析整个declare.js文件。

(一)    dojo.declare

1)    crack  parameters

  先来看declare的参数列表以及函数的开头部分。其中的d在整个declare.js的开始即被赋值为dojo,至于传入的三个参数className,superclass,props曾经在第二章中有过解释。其中className代表要声明的类型的名称,superclass指定了父类型列表,最后的props对象包含了类型的所有构造函数、字段、方法。

d.declare = function(className, superclass, props){
		// crack parameters
		if(typeof className != "string"){
			props = superclass;
			superclass = className;
			className = "";
		}
		props = props || {};
		……
}
 

  由于dojo.declare支持声明一个匿名类型,因此参数列表中的第一个参数className可以省略,所以才有了crack parameters 这一步骤。其实最后一个props参数也可以省略,如果这样dojo会自动用一个空的对象代替。

//正常的类型声明
dojo.declare('A',null,null);
//匿名类,继承自类型A
var B = dojo.declare(A);

 2)    计算MRO、指定父类型

  在处理完传入的参数之后,紧接着的一步就是计算MRO继承序列。当然,前提是用户在使用dojo.declare声明的时候传入了superclass。之前一直superclass说是一个包含了多个类型的数组,其实superclass也可以不是数组类型,在单继承的时候,superclass就是唯一的一个父类型,另外在没有继承的时候,superclass一定要写成null。
  所以在代码的实现中,首先判断了superclass是不是一个数组。这里判断数组的时候利用了 Object.prototype.toString函数(这里的opts),去年我在求职的时候不少公司都问到了这个问题:JS中如何检测数组类型,可惜当时没有好好研究过,所以都不能算答上。
  如果这里确定了superclass是一个数组,那么则调用c3mro函数来计算MRO。注意这里返回的MRO数组中第一个保存的并非某类型本身,而是一个偏移量,表示实际上的唯一一个父类在MRO结果中距离顶端的偏移量。有了这个偏移量,自然很方便的可以从MRO结果中获取父类型。

  如果superclass不是一个数组,而是一个构造器,那么肯定这是一个单继承的情况。这里的判断也很有意思,同样是利用的Object.prototype.toString函数,如果返回的结果为object Function,那么superclass肯定是一个函数(构造器)。接下来同样是构造存放MRO的bases数组。

// 如果传入的superclass是一个数组,那么调用c3mro来计算MRO
// 计算出来的结果放在bases数组中
if(opts.call(superclass) == "[object Array]"){
	// bases中保存了根据superclass计算出来的MRO	
	bases = c3mro(superclass);
	// 其中bases[0]保存了真正的那个父类在MRO序列中距离顶端的距离
	t = bases[0];
	mixins = bases.length - t;
	superclass = bases[mixins];
} 
// 如果传入的superclass是不是数组,那么判断它是构造器还是null
// 既不是构造器也不是null则会抛出异常
else{
	bases = [0];
	if(superclass){
		// 如果传入的superclass是一个构造器
		if(opts.call(superclass) == "[object Function]"){
			// 判断superclass是raw class还是Dojo中的类
			t = superclass._meta;
			// 加入superclass的bases
			bases = bases.concat(t ? t.bases : superclass);
		}else{
			err("base class is not a callable constructor.");
		}
	}else if(superclass !== null){
		err("unknown base class. Did you use dojo.require to pull it in?")
	}
}
 

  这里还有一个值得注意的地方是,superclass既可能是一个原生态的JS构造器,也可能是利用dojo.declare声明出来的构造器。如果是Dojo中的构造器,那么可以直接获取superclass._meta.bases,这里记载了superclass的MRO序列。如果仅仅是JS原生的构造器,那么会将superclass本身加入bases。

3)    构造单继承链

  有了MRO序列之后就应该构造一条单继承结构,整个过程大概类似于从MRO中的父类开始,逐渐向前取出MRO中的类型,mixin进一条继承链。这个过程之前已经用过一个实例来表示出来,如第三章第四小节中的图所示。

// 构造完整的继承链
if(superclass){
	// 从bases中的父类型开始向前扫描,直到i=0时为止
	for(i = mixins - 1;; --i){
		// superclass是目前已经处理到的类型
		// proto是从superclass.prototype继承来的一个对象
		proto = forceNew(superclass);
		if(!i){
			break;
		}
		// t是bases中位于superclass前的类型
		t = bases[i];
		// 在proto中mixin进t.prototype中的属性
		(t._meta ? mixOwn : mix)(proto, t.prototype);
		// 创建新的superclass,其prototype被设为proto
		// 等于该superclass类型继承自原先的superclass
		ctor = new Function;
		ctor.superclass = superclass;
		ctor.prototype = proto;
		superclass = proto.constructor = ctor;
	}
}
else{
	proto = {};
}
// 这里的props是dojo.declare时传入的参数
// safeMixin会将props中除了constructor的属性都mixin进proto中
// 由于proto最后充当了ctor.prototype,因此这些属性都会被当做static属性
// 被所有ctor的实例共享,ctor即为dojo.declare最终产生的类型。
safeMixin(proto, props);
// 如果在props中有自定义的constructor,则赋给proto.constructor
t = props.constructor;
if(t !== op.constructor){
	t.nom = cname;
	proto.constructor = t;
}
 

  这里代码中的for循环会从父类型的前一个元素开始依次遍历,mixins代表了父类型在bases(MRO的计算结果)中的位置,整个循环的结束会在i=0时结束。3.4章节的这个例子中,父类型位于bases的最末尾,在继承链中也是出于最高点,所以for循环也完整的遍历了一次bases数组。但是并非所有的继承结构都会导致完整的遍历bases数组,既然是在bases中从父类型开始向前遍历,那么只要父类型如果不处于bases的末尾,就无需整个遍历bases了。举例来说:

dojo.declare('A',null,{});
dojo.declare('B',A,{});
dojo.declare('C',B,{});
dojo.declare("D",[C,B,A],{});
 

  对于这样一个继承结构,理论上计算出来的D类的MRO为L(D)= DCBA。实际上通过c3mro方法返回的bases数组是[3,C,B,A],那么指定出的父类型就是C(右起第3个)。当superclass为C的时候,mixins=1,i=0,这里的for循环就不会再处理到B和A,这时候仅仅执行了一条proto = forceNew(superclass)语句就跳出了循环。这样的处理也是有道理的,因为C其实已经继承自B和A,也就是说如D仅仅需要继承了C,就会自动继承B和A中的属性,所以再处理B和A是没有意义的。

4)    处理chains

  这一部分的内容相对简单,代码也很容易理解,但有一些逻辑上的细节需要注意一下。

// 依然是从bases中的superclass前一个位置开始
// 依次将各个类型中的chains定义加入到chains中
for(i = mixins - 1; i; --i){ 
	t = bases[i]._meta;
	if(t && t.chains){
		chains = mix(chains || {}, t.chains);
	}
}
// 将proto中定义的chains加入chains
if(proto["-chains-"]){
	chains = mix(chains || {}, proto["-chains-"]);
}
 

  这里chains是declare.js开头就定义好的一个对象,专门用来存储一个类型中的chains。代码中首先会从i = mixins - 1开始遍历,逐步将bases中类的chains加入到这里的chains来。在for循环结束之后,会最后再加入proto里的chains。

  代码中似乎是少了对superclass类型的处理,是的。。。。。的确没处理。。。。。这会造成一个诡异的问题,依然是前面出现过的继承结构:

dojo.declare('A',null,{
	"-chains-": {bar: "after"},
	foo:function(){console.log('A')},
	bar:function(){console.log('A')}
});
dojo.declare('B',null,{});
dojo.declare('C',null,{});
dojo.declare("D",[A,B,C],{
	"-chains-": {foo: "before"},
	foo:function(){console.log('D')},
	bar:function(){console.log('D')}
}); 
var d = new D();
d.foo();
d.bar();
 

  根据上面代码的意思,我们预期在执行bar函数的时候,会输出A、D。但是实际上,在执行bar的时候,仅仅会输出一个D。这是因为A中定义的chains失效了,理由其实很简单,因为在处理chains的时候for循环中并不处理A,因为A是D的真正父类。然后再处理proto["-chains-"]的时候,又没有处理到A,因为D自带了一个proto["-chains-"],覆盖掉了A中的chains。这就造成了A中定义chains失效了的局面。因此最正确的定义chains的方式是只在一个类型中定义,比如上例统一在D中进行定义,只有这样才可以避免上述情况。

5)    创建ctor

  这边的ctor就是最后dojo.declare定义出来的类型,其本质上是JS中的一个构造器。

t = !chains || !chains.hasOwnProperty(cname);
bases[0] = ctor = 
	(chains&&chains.constructor==="manual") ? 	simpleConstructor(bases) :
	(
		bases.length == 1 ? singleConstructor(props.constructor, t) :chainedConstructor(bases, t)
	);
 

  这里的t是一个flag,如果在之前的chains中已经包含了constructor 的设置,则为false,别的情况下都是true。下面则是分情况调用三个不同的函数来构造ctor:

  • 如果chains中设置了constructor==="manual",则调用simpleConstructor
  • 如果bases.length == 1即没有父类,谈不上继承,调用singleConstructor
  • 最后如果不是上面两种情况,调用chainedConstructor

  如果没有chains,这边的代码将美妙许多,既不需要为了构造chains去遍历bases,也不用调用chainedConstructor这样令人费解的函数。这边暂且不管上述三个铸成ctor的函数。接下来的代码就已经很容易理解了,虽然不短,但是都是常规的一些做法。具体解释如下:

// 添加meta信息
ctor._meta  = {
	bases: bases, 
	hidden: props, 
	chains: chains,
	parents: parents, 
	ctor: props.constructor// 这是用户定义时写的constructor函数
};
// 这边的superclass是最后一个被mixin进继承链的类型
ctor.superclass = superclass && superclass.prototype;
// 这边的superclass是最后一
ctor.extend = extend;
// 将ctor的原型设置成proto
ctor.prototype = proto;
proto.constructor = ctor;

// 在proto中添加三个函数,用于new出来的对象调用
proto.getInherited = getInherited;
proto.inherited = inherited;
proto.isInstanceOf = isInstanceOf;

// 如果有className,那么就不是一个匿名类型,需要调用到Dojo.setObject向
// dojo.global来注册,在浏览器中就是把ctor添加在window对象中。
if(className){
	proto.declaredClass = className;
	d.setObject(className, ctor);
}
 

  meta信息可以用来判断一个类型是由dojo.declare生成的还是raw class。如果是raw class,那么肯定不会包含这些meta信息。这里对外提供了四个接口函数可以用供调用,一个是extend,还有三个是getInherited、inherited、isInstanceOf,之前都已经提过它们的用法,这里不再赘述。

6)    处理chains并返回ctor

  如果没有chains这样的功能,那么刚开始的if语句中的代码也是可以省略的。如果chains中已经设置了一些函数的调用顺序,那么这里是需要进一步作出处理的。具体的处理方法是将原来的函数替换成执行chain后返回的同名函数,从实现可以看出来为什么对于非after的方法即看作为before。

// 针对chains中除了constructor意外的函数作出处理
if(chains){
	for(name in chains){
		if(proto[name] && typeof chains[name] == "string" && name != cname){
			t = proto[name] = chain(name, bases, chains[name] === "after");
			t.nom = name;
		}
	}
}
return ctor;
 

最后需要将ctor返回出去,因为dojo.declare允许进行匿名类型的定义,这时候不会再向dojo.global注册。

 

 

 

0
5
分享到:
评论

相关推荐

    dojo官网的源码dojo官网的源码

    3. **对象和类**:Dojo提供了一套面向对象的编程模型,包括`dojo/_base/lang`中的函数增强、对象创建和继承。`dojo/_base/declare`用于创建类,支持多重继承。 4. **DOM操作**:`dojo/dom`和`dojo/query`模块提供了...

    vue+esri 山东天地图加载、标注、鼠标移入移除浮层显示隐藏

    VUE+ ESRI 实现山东天地图加载展示, 批量添加标注点; 鼠标移入移除标注点,浮层显示隐藏功能; ... 地图放大缩小、平移事件; 天地图样式 <link rel="stylesheet" href="https://js.arcgis....

    精通Dojo 随书源码

    在源码中,你可以看到`dojo/_base/declare`、`dojo/_base/lang`等模块,这些都是Dojo的基础架构,用于定义类、处理语言特性等。 深入剖析Dojo工作原理,首先应关注其模块加载机制。Dojo的`dojo/require`和`dojo/...

    图书:Dojo入门

    此外,Dojo Toolkit提供了`dojo.declare`用于类的定义,支持面向对象编程。 Dojo中的Widget系统是另一个重要部分,它包含了大量的UI组件,如按钮、表单元素、日历等,方便开发者构建富客户端应用。这些组件基于dojo...

    dojo 一个小的例子

    3. **数据绑定**:Dojo 提供了`dojo/Stateful` 和 `dojo/_base/declare` 模块,支持面向对象编程和数据绑定。通过声明式绑定,我们可以轻松实现视图与模型的同步。 4. **AJAX通信**:`dojo/xhr` 模块提供了与服务器...

    DOJO 基础学习

    DOJO的`dojo/_base/declare`和`dojo/_base/lang`提供了类和函数的声明,有助于编写可维护的代码。此外,DOJO的`dojo/ready`和`dojo/domReady!`可以帮助确保脚本在DOM完全加载后执行,避免加载顺序问题。 9. **DOJO...

    dojo学习笔记

    Dojo 提供了面向对象编程的支持,`dojo/declare` 可以创建类,而`dojo/_base/lang` 提供了面向对象的一些辅助函数,如`lang.mixin`用于混合对象属性。 4. **Dijit UI组件** Dijit库提供了一系列可复用的UI组件,...

    dojo类机制实现原理分析

    Dojo通过操纵原型链来实现继承,这与其他面向对象的JavaScript库相似。当定义了一个类时,它会自动继承自指定的父类,并可以通过`superclass`属性访问父类。此外,Dojo还支持多重继承,但需要注意的是,在多重继承中...

    实战dojo工具包

    2. **dojo/_base**:这是Dojo的基础模块,包含了核心功能,如事件处理(dojo/on)、DOM操作(dojo/dom和dojo/dom-geometry)和类系统(dojo/_base/declare)等。 3. **dojo/ready**:用于确保DOM加载完成和所有模块...

    DOJO-DEMO官网提取版

    DOJO 允许开发者通过 `dojo/has` 功能检测和 `dojo/extend` 扩展对象来适应不同浏览器的差异,增强框架的兼容性。 9. **DOJO 工具集** 这个版本的 DOJO-DEMO 中包含了官方提供的各种示例,可以帮助开发者理解 ...

    dojo-release-1.1.2-src

    1. **dojo.declare**:Dojo提供了声明式的方式创建类,`dojo.declare`函数用于定义类,支持类的继承和混入,使得面向对象编程在JavaScript中变得简单。 2. **dojo.connect**与**dojo.disconnect**:Dojo的事件系统...

    Dojo 1.01 Book

    Dojo是开源的JavaScript工具包,它提供了丰富的功能,包括AJAX、DOM操作、事件处理、动画效果、模块化开发以及面向对象的编程支持,广泛应用于构建高性能的Web应用程序。 这本书主要分为以下几个部分: 1. **Dojo...

    dojo create custome widget

    在JavaScript的世界里,Dojo Toolkit是一个强大的开源JavaScript库,它为开发者提供了丰富的功能,包括UI组件、数据管理、异步操作等。本话题主要聚焦于如何利用Dojo创建自定义的Widget,这是一个对于提高代码复用性...

    Dojo入门指南-中文版

    通过`dojo/_base/declare`,你可以创建复杂的类层次结构,并利用Mixins机制实现多继承,提高代码复用性。 Dojo的数据绑定和模板系统是提升用户体验的关键。`dojo/parser`允许动态解析HTML中的数据绑定和行为,而`...

    dojo-release-1.0.3-src.zip

    《Dojo Toolkit 1.0.3 源码解析》 Dojo Toolkit,简称Dojo,是一款功能强大的JavaScript库,旨在提供丰富的Web应用程序开发工具。这个“dojo-release-1.0.3-src.zip”压缩包包含了Dojo Toolkit 1.0.3版本的源代码,...

    dojo处理三级连动

    5. **dojo/_base/declare**:声明模块,用于创建自定义类,结合了面向对象和函数式编程的特点。 实现步骤如下: 1. **创建数据**:首先,你需要定义三级联动的数据结构。这可能是一个嵌套的对象数组,每个级别都有...

    Dojo笔记,翻译整理的重点

    2. **declare**:Dojo的`declare`函数用于创建面向对象的类,它支持继承、接口实现和混合对象等功能,是实现面向对象编程的关键。例如: ```javascript declare('MyWidget', [dijit._WidgetBase], { // 定义方法...

    Dojo基础资料

    这可能包括如何引入Dojo库、使用dojo.require加载模块、理解和应用dojo.declare来创建面向对象的JavaScript类,以及了解dojo/on模块用于事件监听的方法。此外,可能还会讲解Dojo的模块化系统,如dojo/_base/xhr用于...

    dojo组件资料

    2. **dojo.declare**: 用于创建类的函数,支持面向对象编程。 3. **dojo.connect**: 事件处理机制,用于绑定事件处理器。 4. **dojo.require**: 模块加载机制,引入所需的功能模块。 5. **dojo.on**: 更现代的事件...

    Dojo之Widget标签开发 - 我为人人,人人为我 - BlogJava

    在IT行业中,Dojo是一个强大的JavaScript库,它专注于提供丰富的用户界面和应用程序开发工具。本文主要探讨的是在Dojo框架下进行Widget标签开发的技术细节,旨在帮助开发者更好地理解和利用Dojo构建可重用、模块化的...

Global site tag (gtag.js) - Google Analytics