解读sencha touch移动框架的核心架构(二)

简介:

本来这行要详解Ext.extend的,但是发现网站有很详细的,那么就跳过去吧

为保持一个系列的分析,还是先搬过来吧,下章开始分析Ext4.0的新架构

 


在Java中,我们在实现继承的时候存在下面几个事实:

1, 准备两个类,他们用extends关键字链接起来

2, 如果超类没有默认构造函数,需要在子类构造函数中显式的super并传参,如果都是默认构造函数也可以super,不super虚拟机是自动的

3, 子类可追加,覆盖,重载方法,子类可以有自己的私有属性,他们在子类构造函数中被构造

4, 字段是数据,方法在代码区,和类建立方法表,同一个类的对象有自己的数据但是共享方法代码

比如有两个类,Plane和Space,Plane表示平面,Space表示空间,Space是Plane的子类,在java中

复制代码
/** 
 * 根据字段数量分配内存块 
 * 实例化的时候虚拟机调用Plane.Plane方法把这个内存块作为this和构造参数传进去,初始化完数据字段。 
 * 建立方法表映射 
 */ 
class Plane {  
    protected int x;  
    protected int y;  
    Plane(int x, int y) {  
        this.x = x;  
        this.y = y;  
    }  
    public void XY() {  
        System.out.println(x * y);  
    }  
}  
/** 
 * 自动拥有了超类的行为,但是超类的属性需要超类去构造 
 * 子类可构造自己的属性,添加自己的方法,覆盖超类的方法 
 * <p/> 
 * 按照继承结构的所有字段分配内存块,调用Space.Space将这个内存块作为this和参数一起传进去 
 * 把超类的那部分给超类,然后自己初始化自己的。 
 * <p/> 
 * 建立方法表 
 */ 
class Space extends Plane {  
    private int z;  
    Space(int x, int y, int z) {  
        super(x, y);  
        this.z = z;  
    }  
    public void XYZ() {  
        System.out.println(x * y * z);  
    }  
}  
public class Test {  
    public static void main(String[] args) {  
        Plane plane = new Plane(2,3);  
        plane.XY();  
        Space space = new Space(2, 3, 4);  
        space.XYZ();  
    }  
}
复制代码

那么在js中也一样,区别是代码要放到构造函数(可以理解为Java中的类)的原型上,原型是放置方法和不变属性的理想场所,原型是一个对象,它和普通对象唯一不同的就是他有一个constructor属性指向它所依附的构造器,在java中子类查找属性和方法是通过虚拟机来完成,但是在js中需要通过原型链来完成。也就是说继承关系对程序员是不透明的,需要了解这个原型机制,原型机制上存在两条链,一是原型链,二是构造函数链。

仿照上面java的代码,我们可以完成js的写法,如下:

 
复制代码
var Plane = function(x, y) {  
    this.x = x;  
    this.y = y;  
};  
Plane.prototype.XY = function() {  
    alert(this.x * this.y);  
};  
var Space = function(x, y, z) {  
    //用this调用超类构造函数,没有java的super自动调用,所以要手动调用 
    Plane.call(this, x, y);  
    //Space.superclass.constructor.call(this, x, y); 可以用一个统一的语法 
    //构造自己的数据 
    this.z = z;  
};  
Space.prototype.XYZ = function() {  
    alert(this.x * this.y * this.z);  
}
复制代码

JS中函数的this指函数的调用者,不管是java还是js,this都可理解为新分配的那段容纳对象的内存。在java 中通过Space extends Plane,虚拟机就维护好了他们的继承关系以完成继承关系的自动查找,但是在js中需要我们手动的处理,在这个时候Space是调用不到XY这个方法的,因为他们没有在原型链上。我们可以开发一个函数来模拟java的关键字extends,比如这个函数叫做extend,通过执行extend(Plane,Space)完成原型链的组装。

那么extend怎么实现呢?首先要明白原型链,子类和父类在原型链上的关系是Space.prototype._proto_ == Plane.prototype,如果你理解不了,那就看String这个类吧,String.prototype._proto_ == Object.prototype,即String的原型会链接到Object的原型上,链接是通过_proto_这个属性来完成的。_proto_是一个只读的属性,只能通过构造函数写入,所以String是Object的子类。

现在Plane的prototype._proto_ 等于Object,Space的prototype._proto_也等于Object,我们要在extend函数变换这个关系,即完成Space.prototype._proto_ == Plane.prototype,我们知道一个对象的_proto_要指向某个构造函数的原型,需要让这个对象由那个构造函数构造,那么我们只需要让Space.prototype = new Plane()就可以了,这个时候Space.prototype._proto_ == Plane.prototype,而不再指向Object,原型还有一个属性constructor指向原型所在的构造器,由于Space.prototype刚被Plane创建出来,还没有这个属性,我们要手动赋值上去,代码是Space.prototype. constructor = Space。这样extend的责任就完成了。

但是这里有两个问题:

1, 由于Space的原型在extend中被替换了,那么它原有的方法就没有了。

2, Space的原型是Plane构造的,虽然做到了Space.prototype._proto_ == Plane.prototype,但是Plane也在原型上写入了x,y这两个垃圾数据,他们都是undefined,没有意义,所以要手动删除掉,这样extend这个方法就不能通用了。

首先解决第一个问题,我们要变化一点思路,利用js中函数也是数据的特性,我们把Space的那些方法拷贝到一个对象中,比如

 
var sbm= { XYZ  : function() {  
    alert(this.x * this.y * this.z);  
}  
 };

把这个sbm也传递给extend,extend在替换完原型后将sbm上的所有方法复制到Space的原型上即可,sbm是一个对象直接量,用json语法。现在的extend就变为了三个参数,即extend(sb,sp,sbm),sb是子类,sp是超类,sbm是子类要放到原型上的方法。

对于第二个问题,本质原因是Plane这个函数要完成一些数据初始化,它是超类,我们不能控制,我们只关心Plane的原型而不关心它构造什么数据,所以我们可以把它的原型单独拿出来,再定义一个干净的函数,这个函数不做任何事,将这个干净函数的原型设置为Plane的原型,再用这个干净函数构造一个对象,这样出来的对象就是是干净的,也完成了_proto_指向了Plane.prototype,完美!有了这两个方法,我们就可以开始实现这个extend,代码如下:

 
复制代码
var extend = function(sb, sp, sbm) {  
    var F = function() {  
    },sbp,spp = sp.prototype;  
 
    F.prototype = spp;  
 
    //用干净函数嫁接得到子类原型  
    sbp = sb.prototype = new F();   
 
    sbp.constructor = sb; //然后指定一个constructor指回子类   
 
         //把sbm的上的属性拷贝到子类的原型上  
        for (var p in sbm) {  
        sbp[p] = sbm[p];  
    }  
};
复制代码

那么完成Space继承Plane的代码如下:

 
复制代码
extend(Space, Plane, {  
            XYZ : function() {  
                alert(this.x * this.y * this.z);  
            }  
        });  
 
var spaceObject = new Space(2, 3, 4);  
 
spaceObject.XY();//成功调用超类方法  
spaceObject.XYZ();
复制代码

OK,到了这里,我们基本上就完成任务了,完全从java的方向搞定的。我们现在利用js的特性来优化,让使用extend更加简单。

我们说在java中必须写两个类,每个类都写自己的字段 ,构造函数,方法等,在我们实现的extend函数中也确实把子类,父类都传递了进来,但是我们多了一个参数,那就是子类的方法集合即sbm,第一个参数sb本身也是函数,那是不是可以将这个函数也放进sbm传进来呢?这样extend就变为两个参数,即extend(sp,sbm),现在extend返回一个函数,返回的这个函数就是sp的子类,这是完全可行的,我们叫做extend2吧。

 
复制代码
var extend2 = function(sp, sbm) {  
    var sb = sbm.constructor;  
    //如果说没有显式的构造函数,那么子类就是直接调用超类构造函数  
    if (sb == Object) {  
        sb = function() {  
            sp.apply(this, arguments);  
        };  
    }  
    extend(sb, sp, sbm);  
    return sb;  
}
复制代码

我们说要把子类的构造函数放到sbm上,放上去的key叫做constructor,就表示构造器,js中每一个对象都一个constructor属性,它指向构造了这个对象构造函数。sbm本来是个Object对象,它的constructor就指向Object,这个constructor是在sbm关联的那个原型上的,现在我们在sbm上设置某个子类的构造函数,这个时候表示sbm有个自己的constructor。

现在我们在extend2中要做的事情就是提取出构造函数,然后还原为三个参数去调用之前的extend,在java中我们的子类是可以不用构造器的,只要父类也有默认的构造器,那么在这里一样,sbm可能不包含constructor,那么我们需要做一个函数,它调用父类的构造函数,在java中这种情况过程是自动的。所以当sbm.constructor为Object的时候表示sbm没有指定构造函数,这个时候将

 
sb = function() {  
            sp.apply(this, arguments);  
        };

即调用父类构造函数。这样将sb,sp,sbm传递给extend就可以了。

这个时候我们新的继承语法如下:

 
复制代码
var NewSpace = extend2(Plane, {  
            constructor : function(x, y, z) {  
                Plane.call(this, x, y);  
                this.z = z;  
            },  
            XYZ : function() {  
                alert(this.x * this.y * this.z);  
            }  
        });  
 
var newObject = new NewSpace(3, 4, 5);  
newObject.XY();  
newObject.XYZ(
复制代码

和prototype.js和mootolls的实现比较,大同小异

复制代码
// properties are directly passed to `create` method  
var Person = Class.create({  
  initialize: function(name) {  
    this.name = name;  
  },  
  say: function(message) {  
    return this.name + ': ' + message;  
  }  
});
复制代码

 

复制代码
var Animal = new Class({  
                initialize: function(age) {  
                    this.age = age;  
                },  
                say : function() {  
                    alert(this.age);  
                }  
            });  
 
    var Cat = new Class({  
                Extends: Animal,  
                initialize: function(name, age) {  
                    this.parent(age); // calls initalize method of Animal class  
                    this.name = name;  
                }  
            });
复制代码
 

到了这里其实已经差不多了,但是细心的读者会发现,我们在extend中会把sbm的所有属性拷贝到子类的原型上,这里岂不是就要把constructor也拷贝到原型上?如果sbm包含了这个constructor其实就无所谓,因为子类的原型的constructor本来就是需要指向这个构造函数的,但是sbm上没有constructor那岂不是要把Object拷贝到子类原型上,答案是不会的,我们在拷贝的时候用的for in循环是迭代不出默认的那个constructor的。

现在我们来看看Ext.extend,应该完全没有问题了。我们用了两个方法extend,extend2,Ext把它合并为了一个方法Ext.extend,所以它会判断传进来的参数然后进行变换,这样Ext.extend就支持两个参数和三个参数进行调用。对于前面用到拷贝属性,Ext做了一个工具函数叫做Ext.apply,对于将一个对象的属性拷贝到一个类的原型上,Ext做了一个工具类叫做Ext.override。

 
复制代码
Ext.extend = function() {  
    // inline overrides 把传入的对象属性复制到到this中  
    var io = function(o) {  
        for (var m in o) {  
            this[m] = o[m];  
        }  
    };  
    //oc其实就是Object函数  
    var oc = Object.prototype.constructor;  
 
    return function(sb, sp, overrides) {  
        //如果第二个参数是个对象而不是类,那么是用两个参数调用的,第一个参数是父类,第二个参数是对象  
        if (typeof sp == 'object') {  
            overrides = sp;  //将第三个参数换为对象  
            sp = sb; //把第一个参数赋值第二个当成父类  
            sb = overrides.constructor != oc ? overrides.constructor : function() {  
                sp.apply(this, arguments);  
            }; //子类这个构造函数要么是外界传入的名字为constructor,要么就是直接调用超类构造函数的一个函数  
            //传入的constructor除了构造自己还要调用超类的构造函数  
        }  
 
        /**  
         * 继承的两种参数  
         * 1,自己写一个构造函数,初始化一些字段,然后调用超类构造函数,再写一个json对象,里面是要覆盖超类的方法或者追加的方法  
         *   然后这样调用extend(sub,sup,{over1:f,over2:f,addf:f}),就像java的语法  
         *   SubClass extend SuperClass {  
         *      SubClass(){  
         *        super();  
         *      }  
         *   }  
         *  
         *   2,第一种可以理解为模拟java,但是因为构造函数也是数据,所以完全可以把构造函数也放进那个jdon对象,只不过约定好一个名字  
         *   比如constructor,然后这样调用  
         *   extend(sup,{constructor:f,over1:f,over2:f,addf:f})  
         */ 
        var F = function() {  
        },  
                sbp,  
                spp = sp.prototype;  
 
        F.prototype = spp;  
        sbp = sb.prototype = new F();  
        //以上用干净函数嫁接得到子类原型  
 
        sbp.constructor = sb; //然后指定一个constructor指回子类,这样就大工告成  
 
        sb.superclass = spp; //在子类上指定一个静态字段指向超类原型,这样在子类构造函数中可访问超类构造函数sub.superclass.constructor.call(this, config)  
 
        /**  
         * 这段代码是防御性的,在自己实现继承的时候,可能会出现原型上的构造函数指向问题,所以如果发现某个超类  
         * 的构造函数是object,要么这个超类却是Object,要么出现了失误,所以这里再一次重设置一下,以防万一,这个代码我们在分析Ext的Observable的时候会提到的它的作用  
         */ 
        if (spp.constructor == oc) {  
            spp.constructor = sp;  
        }  
 
        //子类上方一个静态的重写方法,注意js没有重载,可以用来重写子类原型上的函数  
        sb.override = function(o) {  
            Ext.override(sb, o);  
        };  
 
        //用一个闭包在子类原型上引用一个超类原型,引用的是一个函数  
        sbp.superclass = sbp.supr = (function() {  
            return spp;  
        });  
 
        //子类原型上放置一个重写函数,可以用来覆盖具体实例对象  
        sbp.override = io;  
 
        //在子类原型上重写或添加函数  
        Ext.override(sb, overrides);  
 
        //子类上直接放一个静态继承方法,貌似实现多继承  
        sb.extend = function(o) {  
            return Ext.extend(sb, o);  
        };  
 
        return sb;  
    };  
}();
复制代码

现在使用Ext的extend来实现我们之前的继承代码就如下

复制代码
var Plane = function(o) {  
        this.x = o.x;  
        this.y = o.y;  
    };  
 
 
    Plane.prototype.XY = function() {  
        alert(this.x * this.y);  
    };  
 
    var Space = Ext.extend(Plane, {  
                constructor : function(o) {  
                    Space.superclass.constructor.call(this, o);  
                    this.z = o.z;  
                },  
                XYZ : function() {  
                    alert(this.x * this.y * this.z);  
                }  
            });  
 
    var space = new Space({ x:2,y:3,z:4});  
 
    space.XY();  
    space.XYZ();
复制代码

现在我们来分析一下Ext中的继承重头戏Observable,它位于Ext.util这个包下,它的意思即是观察者,使用观察者模式,EDA模式,UI组件就利用这种基于观察和事件的机制进行通信和渲染。

所有的UI组件都继承这个类,我们看看它的构造函数

 
复制代码
EXTUTIL.Observable = function(){    
    var me = this, e = me.events;  
    if(me.listeners){  
        me.on(me.listeners);  
        delete me.listeners;  
    }  
    me.events = e || {};  
};
复制代码

这个构造函数不需要参数,在java中,这种父类的构造可以自动的调用默认构造函数,但是这里要注意,if(me.listeners)依赖了子类的构造行为,这在面向对象原则中似乎是一个禁忌,但是如果一个继承体现完全由一个团队维护,他们同时制定继承规则和继承规范,这也无可厚非,这里的listeners可以在子类中不提供,可以让构造出来的对象自己调用on方法来添加监听器,同理这里的events,如果子类没构造会被赋值为一个空对象。那么这个Observable构造器做了两个事,一个是看子类是否在对象上放了监听器,如果放了,就调用对象的on方法进行事件和监听的绑定,二是看子类是否在对象上放置了events,如果没有就把对象的events属性设置为一个空对象。也就是说子类是完全可以不做任何事的,子类只负责自己的数据构造和行为覆盖或追加,events在和监听器绑定之后就是一个Ext.util.Event对象的容器,见这行代码: me.events[eventName] = ce = new EXTUTIL.Event(me, eventName);也就是说,在Ext中,一个活生生的能够响应事件的对象有一个Event容器,它保存了这个对象可以响应什么事件以及事件被触发后被调用的监听器。

Observable原型上放置的方法都是子类继承的方法,子类的对象就可以在运行时调用这些方法,如下:

原型上放置了一个静态变量和一些方法,这些方法都是和事件以及监听有关,注意Observable的原型是一个新的对象直接量,它的constructor属性肯定指向的是Object,不是指向的Observable,这岂不是存在bug,我通过代码检测发现alert(Ext.util.Observable.prototype.constructor == Ext.util.Observable);的结果确实又是true,怎么回事呢?答案就在Ext.extend的那段防御性代码,大家回过去看看吧!

现在我们写一个继承Obervable的类,不过不是UI组件,而是一个领域模型,比如论坛帖子,它在被修改之后会跑出一个被修改的事件,监听器捕获这个事件将修改保存到数据库中,代码如下:

 
复制代码
ForumThread = Ext.extend(Ext.util.Observable, {  
                constructor: function(config) {  
                    this.name = config.name;  
                    //把监听器放进超类的属性  
                    this.listeners = config.listeners;  
 
                    this.events = {"change" : true};  
 
                    //给领域模型设置事件,通过上面的写法也可以  
 /*                   this.addEvents({  
                                "change" : true  
                            });*/ 
 
                    //调用超类构造超类不变量  
                    ForumThread.superclass.constructor.call(this, config)  
                },  
                //领域行为,会触发事件  
                changeName : function(newName) {  
                    alert("原主题名字是:" + this.name);  
                    this.name = newName;  
                    alert("更改后主题名字是:" + this.name);  
                    this.fireEvent("change", this);//触发事件  
                }  
            });  
 
    Ext.onReady(function() {  
        var forumThread = new ForumThread({  
                    name : '关于将Jdon框架提升为DCI框架的设想',  
                    //构造领域模型时注入监听处理程序  
                    listeners : {  
                        change : function(thread) {  
                            alert('接受到事件,将异步保存新的名字:' + thread.name);  
                        }  
                    }  
                });  
        //领域行为调用  
        forumThread.changeName("关于将Jdon框架提升为DCI框架的设想,整合JdonMVC");  
    });
复制代码

 

如果事件设置和监听绑定直接在子类完成,那么就不必显式调超类构造函数

 
复制代码
ForumThread = Ext.extend(Ext.util.Observable, {  
            constructor: function(config) {  
                this.name = config.name;  
                this.events = {"change" : true};  
                this.on(config.listeners);  
            },  
            //领域行为,会触发事件  
            changeName : function(newName) {  
                alert("原主题名字是:" + this.name);  
                this.name = newName;  
                alert("更改后主题名字是:" + this.name);  
                this.fireEvent("change", this);//触发事件  
            }  
        });
复制代码

写的真的很详细,真心赞一个,大家慢慢看吧


本文转自艾伦 Aaron博客园博客,原文链接:http://www.cnblogs.com/aaronjs/p/3580633.html,如需转载请自行联系原作者

相关文章
|
1月前
|
算法 数据挖掘 调度
隐语实训营-第3讲:详解隐私计算框架的架构和技术要点
主要介绍隐语的隐私计算架构,并对每个模块进行拆解、分析,以期望不同使用者找到适合自己的模块,快速入手。
46 4
|
2月前
|
监控 负载均衡 Dubbo
Dubbo 框架揭秘:分布式架构的精髓与魔法【一】
Dubbo 框架揭秘:分布式架构的精髓与魔法【一】
161 0
|
1月前
|
分布式计算 算法 调度
课3-详解隐私计算框架的架构和技术要点
隐语架构涵盖产品、算法、计算、资源和硬件五层,旨在实现互联互通和跨域管控。产品层包括SecretPad等,简化用户和集成商体验。算法层涉及PSI/PIR、SCQL和联邦学习,提供隐私保护的数据分析和学习。计算层如RayFed、SPU、HEU等,支持分布式计算和密态处理。资源层的KUSCIA用于跨机构任务编排,硬件层涉及FPGA等加速器。互联互通支持黑盒和白盒模式,确保不同平台协作。跨域管控则强调数据流转控制,保护数据权益。
|
7天前
|
敏捷开发 监控 前端开发
深入理解自动化测试框架Selenium的架构与实践
【4月更文挑战第16天】 在现代软件开发过程中,自动化测试已成为确保产品质量和加快迭代速度的关键手段。Selenium作为一种广泛使用的自动化测试工具,其开源、跨平台的特性使得它成为业界的首选之一。本文旨在剖析Selenium的核心架构,并结合实际案例探讨其在复杂Web应用测试中的高效实践方法。通过详细解读Selenium组件间的交互机制以及如何优化测试脚本,我们希望为读者提供深入理解Selenium并有效运用于日常测试工作的参考。
13 1
|
1月前
|
算法
隐私计算实训营 第1期-详解隐私计算框架的架构和技术要点
本文简要介绍了隐语技术架构的五层结构:产品层、算法层、计算层、资源层和硬件层。每层分别涉及模块功能、定位和人群画像,旨在使不同角色的用户能轻松理解和使用,降低隐私计算的入门难度。此外,隐语产品设计具有开放性和前瞻性,易于集成。
|
1月前
|
SQL API 数据处理
新一代实时数据集成框架 Flink CDC 3.0 —— 核心技术架构解析
本文整理自阿里云开源大数据平台吕宴全关于新一代实时数据集成框架 Flink CDC 3.0 的核心技术架构解析。
704 0
新一代实时数据集成框架 Flink CDC 3.0 —— 核心技术架构解析
|
2月前
|
前端开发 JavaScript API
|
3天前
|
敏捷开发 监控 数据管理
构建高效微服务架构的五大关键策略
【4月更文挑战第20天】在当今软件开发领域,微服务架构已经成为一种流行的设计模式,它允许开发团队以灵活、可扩展的方式构建应用程序。本文将探讨构建高效微服务架构的五大关键策略,包括服务划分、通信机制、数据管理、安全性考虑以及监控与日志。这些策略对于确保系统的可靠性、可维护性和性能至关重要。
|
15天前
|
API 数据库 开发者
构建高效可靠的微服务架构:后端开发的新范式
【4月更文挑战第8天】 随着现代软件开发的复杂性日益增加,传统的单体应用架构面临着可扩展性、维护性和敏捷性的挑战。为了解决这些问题,微服务架构应运而生,并迅速成为后端开发领域的一股清流。本文将深入探讨微服务架构的设计原则、实施策略及其带来的优势与挑战,为后端开发者提供一种全新视角,以实现更加灵活、高效和稳定的系统构建。
18 0
|
3天前
|
消息中间件 监控 持续交付
构建高效微服务架构:后端开发的进阶之路
【4月更文挑战第20天】 随着现代软件开发的复杂性日益增加,传统的单体应用已难以满足快速迭代和灵活部署的需求。微服务架构作为一种新兴的分布式系统设计方式,以其独立部署、易于扩展和维护的特点,成为解决这一问题的关键。本文将深入探讨微服务的核心概念、设计原则以及在后端开发实践中如何构建一个高效的微服务架构。我们将从服务划分、通信机制、数据一致性、服务发现与注册等方面入手,提供一系列实用的策略和建议,帮助开发者优化后端系统的性能和可维护性。