论坛首页 Web前端技术论坛

《仔仔细细分析Ext》 第一章 必须理解Ext.extend函数

浏览 12282 次
精华帖 (14) :: 良好帖 (4) :: 新手帖 (0) :: 隐藏帖 (3)
作者 正文
   发表时间:2009-06-03  
block 写道

damoqiongqiu 写道
所有代码均在IE和FF测试通过,多谢关注,如果在你的浏览器报错请截图发上。

那楼主能否给我发送一个上面那段代码运行正确的截图给我看看呢。
我不是无理取闹,只是楼主的态度让人很不爽。
指出你文章中有问题的地方,我觉得你怎么也得自己去测试一下吧。


Sorry,这段代码确实有误,多谢指正。这里附上备份的代码:
RectAngle = function(width, height) {

	this.getWidth = function() {

		var haha = function() {

			return width;

		}

		return haha;

	}();

	this.getHeight = function() {

		var haha = function() {

			return height;

		}

		return haha;

	}()

}



测试截图附到原文后,请查看。
另外,如有兴趣,邀请你加到我们的群来参与解析源代码的工作。
0 请登录后投票
   发表时间:2009-06-08  
Extjs 是一个非常好的做页面的工具,期待楼主的下几个部分
0 请登录后投票
   发表时间:2009-08-23  
引用

言归正传,对于每个RectAngle的实例来说,例如var rect=new RectAngle(10,10); rect.prototype会指向构造函数RectAngle的prototype,也就是说所有的实例都会共享同一份 RectAngle.prototype,


表述有误,实例 rect 无 prototype 属性,有的是隐藏的一个原型属性,指向 RectAngle.prototype. 在 Firefox 里,是 rect.__proto__ = RectAngle.prototype

下面凡是出现 rect.prototype 的地方,皆有误
0 请登录后投票
   发表时间:2009-09-19  
文章分析得很细致,支持一下。

如果觉得读起来有如天书,建议修炼一下JavaScript: The Definitive Guide宝典(第5版)的
Chapter 8. Functions
Chapter 9. Classes, Constructors, and Prototypes
两章,再回头来看这个帖子,相信会轻松很多。

此外有两点想和作者商榷:
1 [原文]这时候sb.prototype.constructor是F(),所以再来一句sbp.constructor=sb ...
我认为此时sb.prototype.constructor应该sp(),因为执行完F.prototype=sp.prototype这句,F构造函数(Constructor)的prototype已经被偷换成sp构造函数的prototype,这个prototype的constructor属性应该是指向sp构造函数的。
当然,这点差别无关紧要,因为被创建出来的sbp(sb prototype对象)的constructor属性最终都是要被重置成sb构造函数的。

2 关于if(typeof sp == 'object')这块语句的作用,我提供一种自认为更易理解的说法供楼主和各位参考:
因为extend方法有两个使用版本,分别是:
[版本1] 参数1:子类构造函数sb,参数2:父类构造函数sp,参数3:覆盖内容overrides
[版本2] 参数1:父类构造函数sp,参数2:覆盖内容overrides
而extend方法的形参是按照版本1来命名的,这样在用户使用版本2的调用方式时,会发生参数传递“错位”(即:形参sb位置传入的是sp实参,形参sp位置传入的则是overrides实参),
所以需要一个机制来改变这种“错位”,这就是这个If块的作用。

判断机制很简单,直接通过typeof判断实参2的类型,对于版本1,实参类型是"function",对于版本2,实参类型是"object"。
0 请登录后投票
   发表时间:2009-09-19   最后修改:2009-09-19
引用
好了,属性拷贝完成之后就要拷贝父类prototype里面的方法了。来看看Ext又有什么精彩的写法:

     var F = function(){}, sbp, spp = sp.prototype;

     F.prototype = spp;

     sbp = sb.prototype = new F();

     sbp.constructor=sb;

     这几句要连起来看哦。

     按照前面《指南》里面的写法的话,应该是这样的:

     第一步:把子类的prototype赋值为父类的实例对象。sbp=sb.prototype=new sp();

     第二步:删除不要的废属性,因为前面的if判断里面sp.apply(this,arguments)已经完成了属性的拷贝。

     第三步:把constructor重新手动指回来。sbp.constructor=sb


关于这段还有点话想说
这段内容本质上是为子类sb(构造函数)“重塑”一个新的prototype对象,对该对象有两点要求:
1 它原则上必须是一个父类sp的实例(更专业的说法:它应该和父类sp构造函数共享同一个prototype对象)
2 该prototype对象的constructor属性应该指回子类sp构造函数本身
既然如此,那么可不可以一次到位,也不用通过什么F函数,直接构造出我们需要的prototype对象呢?
sbp = sb.prototype = {
    prototype: sp.prototype,
    constructor: sb
}

想法比较大胆,希望高手指正
0 请登录后投票
   发表时间:2009-09-19   最后修改:2009-09-19
自我否定一把....
前面提出的思路大概是正确的,但从实现手段上确实存在问题:
普通对象是没有prototype属性(正如楼上lifesinger同学所说),只能说它应该有这样一个“隐藏属性”,但这个“隐藏属性”应该是无法从代码直接访问和赋值的,只能通过构造函数的prototype间接复制,当然,构造函数则是明确有prototype属性并可以公开操作的,所以才需要F函数这样一个“过渡者”。
0 请登录后投票
   发表时间:2009-09-23  
askwhy 写道
自我否定一把....
前面提出的思路大概是正确的,但从实现手段上确实存在问题:
普通对象是没有prototype属性(正如楼上lifesinger同学所说),只能说它应该有这样一个“隐藏属性”,但这个“隐藏属性”应该是无法从代码直接访问和赋值的,只能通过构造函数的prototype间接复制,当然,构造函数则是明确有prototype属性并可以公开操作的,所以才需要F函数这样一个“过渡者”。

哥们我服了你了,你真执着啊,还在考虑这个问题 
0 请登录后投票
   发表时间:2009-12-23   最后修改:2009-12-23
首先感谢LZ的分析, 站在LZ的基础上, 我自己也分析了一遍, 希望大家能更明白.
extend : function(){
	// inline overrides, 临时函数用于覆盖属性到this上.
	var io = function(o){
		for(var m in o){
			this[m] = o[m];
		}
	};
	var oc = Object.prototype.constructor; //保留Object的构造器的引用.

	//上面的部分为Ext.extend函数的私有变量区, 使用闭包.
	//下面的部分是Ext.extend调用者实际调用的函数.
	return function(sb, sp, overrides){
		/**
		 * 在这个if语句中完成了对Ext.extend方法的重载. Ext.extend有两种调用方式.
		 * 1. var Subclass = function(){...};
		 *    Ext.extend(Subclass, Superclass, {...});
		 * 2. var Subclass = Ext.extend(Superclass, {...});
		 * 
		 * 方式1和方式2的不同在于, 参数顺序的错位. 其中子类的引用在方式2中没有, 导致原本在2,3位置的父类
		 * 和属性集变成了1,2位.
		 * 为了屏蔽两种调用方式的差异, if(Ext.isObject(sp)) 用来判断2号参数的类型, 如果不是"Object"
		 * 类型就是第一种调用方式, 否则就是第二种调用方式.
		 * 当知晓是第二种调用方式之后, 需要修正参数的位置, 于是出现了一下两个语句:
		 * overrides = sp; //修正3号参数(原2号)
		 * sp = sb; //修正2号参数(原1号)
		 * 那么空出来的1号参数怎么办呢? sb对应第一种调用方式应该是子类引用. 
		 * 而第二种的子类引用正是Ext.extend返回的.
		 * 所以这里就给了一个默认的构造函数给子类.(这也是1,2两种方式的不同: 是否自定义构造函数.)
		 * 默认的构造函数的内容就是在子类对象的作用域内调用父类的构造函数, 即获取父类的非原型属性(当然
		 * 这个获取的过程是new子类对象时产生的, 而现在只是产生了这个能够调用父类函数的引用作为子类
		 * 的构造函数).
		 * 自此, 两种方式的重载完成修正, 下面可以正常的按照3个参数(即第一种方式)建立继承关系.
		 */
		if(Ext.isObject(sp)){
			overrides = sp;
			sp = sb;
			sb = overrides.constructor != oc ? overrides.constructor : function(){sp.apply(this, arguments);};
		}
		
		/**
		 * 上面说到了子类的构造方法能够获取父类的非原型属性, 那么最关键的父类原型的内容就在下面.
		 * 值得注意的是: 要准确的区分静态属性(方法)与实例属性(方法).
		 * 静态属性(方法)指的是类(构造器)的直接属性(方法), 这里就是sb.[...]. 这种方式书写, 不会作为
		 * 对象的属性(方法), 只能通过类的引用(类名)来访问.
		 * 实例属性(方法)指的是类(构造器)原型对象的属性(方法), 这里就是sbp.[...]. 这种方式的
		 * 属性(方法)才会被对象(实例)所拥有.
		 * 同样, 构造器函数内部定义的, 附加到this之上的属性也是实例属性(方法).
		 */
		//这里建立一个空函数F是去除父类的非原型属性, 防止多拷贝一次非原型属性, 影响性能.
		var F = function(){}, 
			sbp,	//子类原型的引用. subclass-prototype 的缩写.
			spp = sp.prototype;	//父类原型的引用. superclass-prototype 的缩写

		F.prototype = spp;
		//获取在父类原型基础上产生的对象作为子类原型, 这里建立了原型继承关系.
		sbp = sb.prototype = new F(); 
		sbp.constructor=sb; //修正子类的构造函数指向子类(原本指向F, 因为是new F()出来的原型.
		sb.superclass=spp; //设置子类引用父类原型对象, 静态属性.
		if(spp.constructor == oc){ //这里修正了下父类的构造函数, 若为Object就扭转到自身.
			spp.constructor=sp;
		}
		//子类的override静态方法, o为属性集, 用于覆盖原本的子类原型的属性.
		sb.override = function(o){ 
			Ext.override(sb, o);
		};
		//子类的实例方法, 可以获取到父类原型的引用.
		sbp.superclass = sbp.supr = (function(){	
			return spp;
		});
		//子类的实例方法, 覆盖对象的属性. 实际上此方法依赖js的动态语言特性实现.
		sbp.override = io; 
		//最后, 将继承时需要的属性(方法)覆盖到子类的原型, 即增加/修改了子类的实例属性(方法).
		Ext.override(sb, overrides); 
		sb.extend = function(o){return Ext.extend(sb, o);};
		return sb;
	};
}(),

0 请登录后投票
   发表时间:2009-12-23  
中间有一个对父类构造函数的修正是我不明白的

还未明白何种情况下父类的构造器会是Object, 除非父类是Object?


if(spp.constructor == oc){ //这里修正了下父类的构造函数, 若为Object就扭转到自身.
      spp.constructor=sp;
}
0 请登录后投票
   发表时间:2009-12-23  
另, 这是最新的Extjs 3.1版本的, 2.2的有些许不同. 但相差不大
0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics