Ajax-Javascript-继承
继承只是在用OO的术语描述Js,不能用OO继承来理解Js的继承。Js的继承根据实现方式不同,结果也不同。如Ma的继承打破了prototype chain,和常用的继承实现方式不同。这里只谈基于prototype的继承。
Prototype链
由于继承,prototype会形成链。链从当前的prototype开始,到Object结束。Prototypechain是许多Js方法的基础,如变量定位机制:先查找构造函数内的变量,即obj自身,没有会顺着prototype chain寻找,并一直到Object。如果没有找到则undefined。
因此实现继承最好不破坏这条链,但是MA的实现破坏了他,除了这点MA实现是很完美的。
下面的例子会形成PositionedRectangle,Rectangle,Object的prototype chain,但是Ma实现没有中间的Rectangle,这是Ma实现的缺点。
Prototype
Prototype是对象(文中(以前文也是)和Js关联的对象都是指objects,不同于Object或者c#中的对象,定义在ecma有)。
但是他和其他对象的区别是,Js会为其指定一个默认constructor,他指向和Prototype关联的构造函数。没有继承的情况下,一般都是默认值(除非手动修改),但继承的实现方式可能会改变Prototype类型。
如例子中:
加上:PositionedRectangle.prototype.constructor = PositionedRectangle;
pr.constructor.prototype instanceof Rectangle//ture
去掉:PositionedRectangle.prototype.constructor = PositionedRectangle;
pr.constructor.prototype instanceof Rectangle//false
注:但是我认为不要依赖prototype的类型做任何判断,象上面的判断应该都要避免,Js没有说明prototype类型,只保证是objct 就合法。这也是prototype和其他对象不同之处,别的对象可以依赖于instanceof。
继承的属性&重新生成的属性
继承在这有两种含义:prototype 继承和通常意义继承(在Definitive guide上称为子类化和超类化,没有称为继承)。
Call,apply的使用,转换了上下文,所以他们的属性已经不再属于基类,也就是父类成员被拷贝到了子类,这不是继承。
pr.hasOwnProperty('width')//true
被继承下来的只是prototype中的属性,但是prototype的第一个值是constructor,这也导致基类私有变量被继承。也就是说,基类私有变量在子类有两份,一份是拷贝过来的,另一份是继承下来的,虽然实际上copy过来的属性会隐藏继承下来的属性,我们一般不会访问到但是不是不能访问,这可能会是安全漏洞。例子中继承下来的变量可以这样访问:
PositionedRectangle.prototype.width;//undifiend
注:Ma实现只存在拷贝,不存在继承私有变量。
属性隐藏
本质就是通过prototypechain
第一种实现的副作用
我想要的只是prototype,结果一些无用的变量被继承,一些默认实现被修改,详细看代码注释。
Ma实现的副作用
破坏prototypechain,强制准确类型判断必须用反射。Prototypechain的改变使依赖于它的Js系统函数不能正确工作,如:instanceof
My.PositionedRectangle.prototype instanceof My.Rectangle//false
pr instanceof My.PositionedRectangle//false 不能正确判断基类
可以看到实现方式不同,继承会有不同的结果。如果不看源码,而用通常实现来理解Ma的继承,会产生误解。
Instanceof
我不知道是如何实现Instanceof函数的,但是可以推测他的实现,那就是可能用prototypechain
实现的类型识别,因为这是惟一可能回溯到基类的地方。这也是Ma的类型不能正确工作的原因。
类型辨别功能的缺失,是MA必须提供其他方式来判断来型,那就是“反射”,这里的反射也和C#没有一点关系,在Ma中应该优先用反射判断类型,这是唯一准确的方式。
反射
MA极大的改进了Js的类型识别,他也必须这样做,否则没有别的办法来识别类型。用的也很巧,只是在注册namespace或者类时做个记录,却实现了这么强的功能,Ms称为 Reflector。
如果不这样,用下面的实现检测类型,会不准确,也很笨拙:
// If x is null, return "null"
if (x == null) return "null";
// Next try the typeof operator
var t = typeof x;
// If the result is not vague, return it
if (t != "object") return t;
// Otherwise, x is an object. Use the default toString( ) method to
// get the class value of the object.
var c = Object.prototype.toString.apply(x); // Returns "[object class]"
c = c.substring(8, c.length-1); // Strip off "[object" and "]"
// If the class is not a vague one, return it.
if (c != "Object") return c;
// If we get here, c is "Object". Check to see if
// the value x is really just a generic object.
if (x.constructor == Object) return c; // Okay the type really is "Object"
// For user-defined classes, look for a string-valued property named
// classname, that is inherited from the object's prototype
if ("classname" in x.constructor.prototype && // inherits classname
typeof x.constructor.prototype.classname == "string") // its a string
return x.constructor.prototype.classname;
// If we really can't figure it out, say so.
return "<unknown type>";
}
可以把上面的代码看作Ma中反射原形,虽然和MA实现不同,但都是基于给obj增加classname属性实现,MA的registerNamespace,registerclass解析了类型,是其他方法能工作的基础。
通常实现
摘自Definitive
// It has a width and height and can compute its own area
function Rectangle(w, h) {
this.width = w;
this.height = h;
}
Rectangle.prototype.area = function( ) { return this.width * this.height; }
// Here is how we might subclass it
function PositionedRectangle(x, y, w, h) {
// First, invoke the superclass constructor on the new object
// so that it can initialize the width and height.
// We use the call method so that we invoke the constructor as a
// method of the object to be initialized.
// This is called constructor chaining.
Rectangle.call(this, w, h);
// Now store the position of the upper-left corner of the rectangle
this.x = x;
this.y = y;
}
// If we use the default prototype object that is created when we
// define the PositionedRectangle( ) constructor, we get a subclass of Object.
// To subclass Rectangle, we must explicitly create our prototype object.
PositionedRectangle.prototype = new Rectangle( );
// We create this prototype object for inheritance purposes, but we
// don't actually want to inherit the width and height properties that
// each Rectangle object has, so delete them from the prototype.
delete PositionedRectangle.prototype.width;
delete PositionedRectangle.prototype.height;
// Since the prototype object was created with the Rectangle( ) constructor,
// it has a constructor property that refers to that constructor. But
// we want PositionedRectangle objects to have a different constructor
// property, so we've got to reassign this default constructor property.
PositionedRectangle.prototype.constructor = PositionedRectangle;
// Now that we've configured the prototype object for our subclass,
// we can add instance methods to it.
PositionedRectangle.prototype.contains = function(x,y) {
return (x > this.x && x < this.x + this.width &&
y > this.y && y < this.y + this.height);
}
我加注释的版本,注释不是上面的翻译:
this.width = w;
this.height = h;
}
Rectangle.prototype.area = function( ) { return this.width * this.height; }
Rectangle.prototype.constructor//Rectangle
function PositionedRectangle(x, y, w, h) {
Rectangle.call(this, w, h);
this.x = x;
this.y = y;
}
PositionedRectangle.prototype.constructor//PositionedRectangle,默认回引到prototype关联的构造函数
PositionedRectangle.prototype = new Rectangle( );//语句1 这条语句形成prototypechain,这种继承方式副作用很大,我们需要的只是Rectangle的prototype,可得到了许多无用的东西,后面要删除
PositionedRectangle.prototype.constructor//Rectangle,继承改变了默认方式
PositionedRectangle.prototype.constructor = PositionedRectangle;//语句1的副产物,因为语句一修改了默认实现,我们要把它修正过来。
delete PositionedRectangle.prototype.width;//删除无用私有变量,否则暴露了基类的私有变量,这些变量都是语句一的副产物,不删除由于Js变量调用机制也不会被调用,他们都被
delete PositionedRectangle.prototype.height;//Rectangle的私有变量隐藏,但是不删除这就是漏洞。
PositionedRectangle.prototype.contains = function(x,y) {
return (x > this.x && x < this.x + this.width &&
y > this.y && y < this.y + this.height);
}
//后面如果定义一个同名变量area,由于prototypechain,会隐藏Rectangle的area,测试prototypechain
PositionedRectangle.prototype.area=function(){return 'is test'}
var pr=new PositionedRectangle(1,2,3,4);
pr.area();//is test,隐藏Rectangle
用MA库实现的版本:
Type.registerNamespace('My');
// Here is a simple Rectangle class.
// It has a width and height and can compute its own area
My.Rectangle= function(w, h) {
this.width = w;
this.height = h;
}
My.Rectangle.prototype.area = function( ) { return this.width * this.height; }
My.Rectangle.registerClass('My.Rectangle');
// Here is how we might subclass it
My.PositionedRectangle=function(x, y, w, h) {
My.PositionedRectangle.initializeBase(this,[w,h]);
// Now store the position of the upper-left corner of the rectangle
this.x = x;
this.y = y;
}
My.PositionedRectangle.prototype.contains = function(x,y) {
return (x > this.x && x < this.x + this.width &&
y > this.y && y < this.y + this.height);
}
My.PositionedRectangle.registerClass('My.PositionedRectangle',My.Rectangle);
var pr=new My.PositionedRectangle(1,2,3,4);
MicrosoftAjax.js实现
// Here is a simple Rectangle class.
// It has a width and height and can compute its own area
My.Rectangle= function(w, h) {
this.width = w;
this.height = h;
}
My.Rectangle.prototype.area = function( ) { return this.width * this.height; }
My.Rectangle.registerClass('My.Rectangle');
// Here is how we might subclass it
My.PositionedRectangle=function(x, y, w, h) {
My.PositionedRectangle.initializeBase(this,[w,h]);
// Now store the position of the upper-left corner of the rectangle
this.x = x;
this.y = y;
}
My.PositionedRectangle.prototype.contains = function(x,y) {
return (x > this.x && x < this.x + this.width &&
y > this.y && y < this.y + this.height);
}
My.PositionedRectangle.registerClass('My.PositionedRectangle',My.Rectangle);
var pr=new My.PositionedRectangle(1,2,3,4);
继承分成了两部分:registerClass,initialbase
代码执行过程不再分析,几个要点:
this.__baseType.apply(instance);
//拷贝prototype,同时也破坏了chain
for (var memberName in baseType.prototype) {
var memberValue = baseType.prototype[memberName];
if (!this.prototype[memberName]) {
this.prototype[memberName] = memberValue;
}
}
接下来会写关于scope chain,callobject和闭包的,这些是Js中最头痛的概念,我用了一周才明白大概,可能是最后一篇,因为MA.js余下的东西都没有什么可说了