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

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

    博客分类:
  • Dojo
阅读更多

接着第四章....

(二)   getInherited和inherited方法

  这两个方法是对外提供的扩展接口,可以用declare出来的类型的实例调用。它们的用途是返回父类中曾经出现的方法,或者对其进行调用。具体的用法在第二章中已经描述,这里不再举例说明。

  1. getInherited方法主要用来返回父类中的方法;

  2. inherited方法主要用来调用父类中的方法;

  首先来看getInherited方法,该方法的实现比较简单:

//getInherited的实现,调用了inherited
function getInherited(name, args){
	//如果传入name,则返回指定name的方法
	if(typeof name == "string"){
		return this.inherited(name, args, true);
	}
	//否则返回的是父类中的同名方法
	return this.inherited(name, true);
}

  上面的实现中可以看出,在getInherited中其实还是调用inherited来做进一步处理,只不过将inherited的最后一个参数设置为true,在inherited里会判断该参数,为true就是返回函数,不为true则是调用函数。

 

  接下来看inherited方法,可以分成个3步骤依次来看inherited具体的实现细节。第一步 依然是crack arguments,这是由于dojo提供的API的灵活性所致,就要求这样也能调用,那样也能调用,很蛋疼。第二步 就是根据name参数来寻找父类中的同名方法,这里的name有可能是constructor,也就是说我们可以利用inherited去调用父类中的构造函数,因此在inherited的实现里做了分情况讨论,一种情况是调用父类的非构造函数,还有一种是调用构造函数。想想在第三章末尾曾讲述过,可以把某继承链的构造器调用顺序设置为manual,这样将会破坏默认的自动调用父类构造函数,用户可以根据自己手动去调用父类的构造函数,用的正是this.inherited('constructor',arguments)....在第二步完成之后,第三步 所做的工作很简单,即决定是返回找到的同名方法还是调用这个同名方法。

function inherited(args, a, f){
	var name, chains, bases, caller, meta, base, proto, opf, pos,
		cache = this._inherited = this._inherited || {};	
	// 首先是crack arguments
	// 最后传入的参数f可能是true,也可能是一个替代args的数组,还有可能是默认的undefined
	if(typeof args == "string"){
		name = args;
		args = a;
		a = f;
	}
	f = 0;

	//args是子类方法的参数列表,args.callee代表在子类的哪个方法中调用了inherited
	caller = args.callee;	

	//获取欲调用的父类的方法的名称,如果没有传入name参数,那么就是调用父类中的同名方法
	name = name || caller.nom;
	if(!name){
		err("can't deduce a name to call inherited()");
	}
	//这里获取到的是子类型的meta信息,下面接着通过meta信息来进一步获取子类型的MRO链
	meta = this.constructor._meta;
	bases = meta.bases;

	//第一次调用inherited的时候,由于缺少this._inherited信息,
	//所以cache是一个空的Object,这里pos是undefined
	//但是如果第二回及以后用到了inherited
	//那么在cache中记录了之前一次利用inherited寻找的方法和位置
	//注意实际上整个实现中并未利用cache,这里的cache疑似某个实现版本遗留下的痕迹
	pos = cache.p;

	//分情况讨论
	//1.要调用的方法不是父类中的构造函数
	if(name != cname){
		// method
		if(cache.c !== caller){
			// cache bust
			pos = 0;
			base = bases[0];
			meta = base._meta;
			if(meta.hidden[name] !== caller){
				// error detection
				chains = meta.chains;
				if(chains && typeof chains[name] == "string"){
					err("calling chained method with inherited: " + name);
				}
				// find caller
				do{
					meta = base._meta;
					proto = base.prototype;
					if(meta && (proto[name] === caller && proto.hasOwnProperty(name) || meta.hidden[name] === caller)){
						break;
					}
				}while(base = bases[++pos]); // intentional assignment
				pos = base ? pos : -1;
			}
		}
		// 在正常情况下,在bases[0]中根据name寻找到的方法就是caller
		// 因此需要沿着bases继续寻找,有可能会进入while循环,找到的函数放在f中			
		base = bases[++pos];
		if(base){
			proto = base.prototype;
			if(base._meta && proto.hasOwnProperty(name)){
				f = proto[name];
			}else{
				opf = op[name];
				do{
					proto = base.prototype;
					f = proto[name];
					if(f && (base._meta ? proto.hasOwnProperty(name) : f !== opf)){
						break;
					}
				}while(base = bases[++pos]); // intentional assignment
			}
		}
		//这个写法太高级了....就是在bases中没有去就Object.prototype里找
		//不过很可能Object.prototype中依然没有名为name的方法,这时候f就是undefined
		f = base && f || op[name];
	}
	//2.要调用的方法是父类中的构造函数
	else{
		if(cache.c !== caller){
			//如果name是constructor,依然是沿着bases依次寻找
			pos = 0;
			meta = bases[0]._meta;
			if(meta && meta.ctor !== caller){
				// error detection
				chains = meta.chains;
				if(!chains || chains.constructor !== "manual"){
					err("calling chained constructor with inherited");
				}
				// find caller
				while(base = bases[++pos]){ // intentional assignment
					meta = base._meta;
					if(meta && meta.ctor === caller){
						break;
					}
				}
				pos = base ? pos : -1;
			}
		}
		// 这里找到的父类型的构造函数是base._meta.ctor
		// 即当初declare该类型时props中自定义的constructor函数
		while(base = bases[++pos]){	// intentional assignment
			meta = base._meta;
			f = meta ? meta.ctor : base;
			if(f){
				break;
			}
		}
		f = base && f;
	}

	// 把f和pos放进cache中,即this._inherited
	// 这样下次再次利用inherited寻找name方法的时候就很方便了
	cache.c = f;
	cache.p = pos;

	// 决定是返回f还是调用f
	if(f){
		return a === true ? f : f.apply(this, a || args);
	}
}

  上面不仅贴出了整个inherited的实现,也标注了一些关键步骤的解释。其中的cache疑似是一个过期的实现,因为实在想不出会有什么情况下cache.c === caller,除非人为的故意设置成如此,不过幸好即使忽略掉cache的作用也不会影响整段代码的理解。还有一个颇为有趣的地方在于如果发现了一个方法处于chains之中,那么会抛出异常,因为对一个已经chains的方法再去手动调用是毫无意义的。

(三)  isInstanceOf方法

  在第二章中已经提及,为了弥补JS自带的instanceof运算符无法判断Dojo中的继承,所以才有了isInstanceOf扩展。该方法由Dojo中类型的实例来调用。

function isInstanceOf(cls){
	//获取该类型meta信息中的bases(即MRO的结果)
	var bases = this.constructor._meta.bases;
	//遍历bases
	for(var i = 0, l = bases.length; i < l; ++i){
		if(bases[i] === cls){
			return true;
		}
	}
	return this instanceof cls;
}

  整个实现也很清晰很简单,在第三章曾经描述过,不论Dojo中一个继承结构多么的复杂,归根结底还是一个单继承的形式,外加mixin进了许多类型的属性而已。那么在判断instanceof的时候,只要顺着这样一条继承链从低向高处遍历,沿途无论是发现了mixin的class,或者直接就是父类,这里都会返回true。

 

如果对JS中原生instanceof的判断机制感兴趣,可以参考

1. http://www.iteye.com/topic/461096

2. http://ejohn.org/blog/objectgetprototypeof

 

(四)  chainedConstructor方法

  第四章中提过,在declare中构建ctor的时候,针对不同的情况分别调用了三个函数。这里只研究其中的chainedConstructor,因为该函数最为复杂,另外两个simpleConstructor和singleConstructor函数不作详细分析。chainedConstructor是当declare的类型存在继承,且未设置constructor="manual"时调用的函数,返回值是一个用以充当declare类型的函数。

 

  整个chainedConstructor可以看作三大步。第一步 就是就是执行在继承结构链上所有类型中定义的preamble函数,第二步 是调用所有类型的constructor函数,第三步 是执行当前类型中定义的postscript方法。如果撇开preamble和postscript,那么整个chainedConstructor的实现就只需要调用所有类型的constructor函数,相应的实现也可以被压缩成:

// 遍历bases中的类型,未指明constructor的调用顺序,因此默认是after,即
// 从bases[i]---->bases[0]依次调用constructor
for(i = bases.length - 1; i >= 0; --i){
	f = bases[i];
	m = f._meta;
	f = m ? m.ctor : f;//可能是raw class
	if(f){
		f.apply(this, arguments);
	}
}

 

  可惜必须要面对存在preamble和postscript的情况,而且这两种情况的处理方式还不一样。其实preamble的调用也很简单,就是遍历bases,找出其中定义过的preamble函数,然后依次执行。

function chainedConstructor(bases, ctorSpecial){
	return function(){
		var a = arguments, args = a, a0 = a[0], f, i, m,l = bases.length, preArgs;

		//如果不是利用new的时候调用该函数,那要强迫new出实例
		if(!(this instanceof a.callee)){
			// not called via new, so force it
			return applyNew(a);
		}

		// ctorSpecial=true仅发生在:没有定义chains,或者chains中的constructor
		// 一旦有了chains并且设置了constructor的顺序,则无需执行preamble
		// 有两种设置preamble的方式:
		// 一是在new的时候将preamble作为参数传递给该类型
		// 二是在declare一个类型的时候定义好
		if(ctorSpecial && (a0 && a0.preamble || this.preamble)){
			preArgs = new Array(bases.length);
			preArgs[0] = a;
			for(i = 0;;){
				// 如果是在参数中定义了preamble并传第给ctor
				a0 = a[0];
				if(a0){
					f = a0.preamble;
					if(f){
						a = f.apply(this, a) || a;
					}
				}
				// 如果是在类型的declare中定义preamble
				f = bases[i].prototype;
				f = f.hasOwnProperty("preamble") && f.preamble;
				if(f){
					a = f.apply(this, a) || a;
				}
				// for循环再遍历完了bases之后会结束
				if(++i == l){
					break;
				}
				//会记录下每次调用preamble之后的arguments
				//主要是为了防止某个preamble对arguments作出修改
				preArgs[i] = a;
			}
		}
		
		//第二步开始
		……

}

  上面对第一步preamble的执行作出了一些解释。现在还剩最后一步,只针对当前的类型来调用postscript:

f = this.postscript;
if(f){
     f.apply(this, args);
}

  这个实在没什么好讲的。

 

  从整个chainedConstructor的三大步骤实现来看,其实dojo的源码写的还是很通俗易懂的,结构也很清楚,是不错的学习材料^_^至此declare.js中比较重要的函数基本都已经讲完了,只缺少一个关于c3mro函数的剖析,但是前面讲mro已经花了大量的篇幅,便不打算再写下去了。以前都是仅仅停留在参阅Dojo的API的说明上,这是我第一次花力气去阅读Dojo的源码,可惜目前的工作中已经没有机会再使用Dojo了。

 

0
0
分享到:
评论

相关推荐

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

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

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

    VUE+ ESRI 实现山东天地图加载展示, 批量添加标注点; 鼠标移入移除标注点,浮层显示隐藏功能; ... 地图放大缩小、平移事件; 天地图样式 &lt;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-release-1.1.2-src

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

    DOJO-DEMO官网提取版

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

    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