用描写对象的特征来实现面向对象的JS设计 (JS 中级)
解决问题之前,我们要弄清楚问题是什么?那什么是面向对象?
定义我也就不讲了,我脑子里只是记得他的3个基本特征:
1. 封装: 程序中涉及的概念都必须要有一个对应的封装(对象)来解耦合。
2. 继承: 子类可以继承父类的方法。
2. 多态:我们的自己可以Override父类的的方法
基于上面的3个基本特征的认知,那么我们JS 怎么来实现呢?
封装:在javascript的世界里面,全部都是对象。包括primitive type 原始数据对象与Object 对象(封装出类型)
Primitive :undefined、null、boolean、number和string
object :Array,Date,{} .....
print(typeof({})) print(typeof(new Array()))
你可以看到输出的类型都是object. 那么我们要怎么在这些对象上实现封装?
JavaScript支持的封装创建的方法调用构造函数(constructor),也就是new 某个Function.
/** * Created by velly on 2015/4/25. */ function print(msg){ console.log(msg); } function Test(x ,y ,z){ this. x = x; this.y = y; this.z = z; } var t = new Test("Hello", "good", "body"); print(t.constructor); print(t.prototype); print(t); print(typeof(t.constructor)); print(typeof(t.constructor.prototype));
//out put
// [Function: Test] [ref1]
// undefined 注意,t实例本身是没有prototype的哦,
// { x: 'Hello', y: 'good', z: 'body' }
// function
// object [ref2]
我们实现封装之后就需要实现继承了。
ECMA规定,每个Function的构造函数都执行Fucntion自己,如[ref1]。每个构造函数都有一个protoptye,如[ref2],且默认继承 Prototype的所有属性包括函数与方法。
所以这就给我继承提供了实现方法: 如果函数的构造函数(constructor)指向自己,且构造函数的prototype指向父对象实例,那么父对象的所有属性都被继承了。 只是,如果需要一个父类的实例,那势必会带来实例化的字段的参数,这很别扭。本来是子类构造来完成所有参数实例化才对,凭什么prototype在创建时就要初始化某些属性呢?
我们的答案是“不!”,我不要你初始化,我只要你的字段和方法。我们需要做2件事:
1. 剥离参数的初始化。 所以我们需要加入init 方法(你叫其他方法也可以)。
2. 辨识是否现在是子类的初始化。所以我们在子类和父类的初始化过程中引入全局变量initializing. 如果为true,表示正在子类正在初始化父类,请不要进行初始化操作。
//global initializing filed , identify where should this constructor should call init. initilizing= false; function print(msg){ console.log(msg) } function Rect(){ print("Rect 构造 ------------ Start"); if(!initilizing){ print("需要Call init"); this.init.apply(this,arguments); }else{ print("不需要Call init: 在子类Call就好") } print("Rect 构造 ------------ End") } Rect.prototype.init = function(x, y ,w ,h){ this.x = x; this.y = y; this.w = w; this.h = h; } Rect.prototype.getArea = function (){ return this.w * this.h } function Sprite(){ print("Sprite 构造 ------------ Start"); if(!initilizing){ print("需要Call init"); this.init.apply(this,arguments); }else{ print("不需要Call init: 在子类Call就好"); } print("Sprite 构造 ------------ End"); } initilizing = true; //don't call Rect init when use prototype to implement inheritance. Sprite.prototype = new Rect() Sprite.prototype.init = function(x, y ,w ,h, png){ this.x = x; this.y = y; this.w = w; this.h = h; this.png = png } Sprite.prototype.getPng = function(){ return this.png; } Sprite.prototype.constructor = Sprite // why not typo as Sprite.constructor = Spite? initilizing = false; //init parent done. var s = new Sprite(0,0,2, 2,"~/sb.png"); print(s.getArea()); // call method from parent print(s.getPng()); // call new method own
这时,我们看到继承已经实现了,那么就准备开始实现多态了。
多态就是要求子类有能力override父类的方法。 要实现override,那么我们必须要全局掌控Function 类型的创建(封装类型的创建)。需要什么样的封装,是我们这个方法说了算。那这个方法要干什么事呢? 回到当初的问题,当然是创建3个基本特征:
1. 方法可以创建封装 2. 方法可以实现继承 3. 方法可以实现复写
知道要干什么了,那就是RD的事情了,如下是我的一个实现:
//global initializing filed , identify where should this constructor should call init. initilizing= false; function print(msg){ console.log(msg) } // Create Constructor Function. var jClass = function(baseClass, prop){ if(typeof(baseClass) === "object"){ //Not A New type. prop = baseClass; baseClass = null; } // def template. default call init(common) var ClsTemplate = function(){ if(!initilizing){ this.init.apply(this,arguments) } }; //如果是子类,那么重定义Prototype和constructor矫正,contructor指向自己而不是父类,不然类型就错了。 if(baseClass){ initilizing = true; ClsTemplate.prototype = new baseClass(); ClsTemplate.prototype.constructor = ClsTemplate; //顺便添加了一个_super的引用到prototype. ClsTemplate.prototype._super = baseClass.prototype; initilizing = false; } // implement Override for(var name in prop){ print(name); if(prop.hasOwnProperty(name)) { ClsTemplate.prototype[name] = prop[name]; } } return ClsTemplate; }
有了创建封装的方法,那就创建点实例试试:
var Rect = jClass({ init:function(x,y,w,h){ this.x = x; this.y = y; this.w = w; this.h = h; }, getArea:function(){ return this.h * this.w; } }); var Sprite = jClass(Rect, { init:function(x, y ,w ,h, png){ //this.x = x; //this.y = y; //this.w = w; //this.h = h; this._super.init.apply(this,[x, y ,w ,h]) this.png = png; }, getPng:function(){ return this.png; } }); var sprite = new Sprite(0,0,2,2, "~/sb.png"); print(sprite.getArea()); print(sprite.getPng());
// output
//init
//getArea
//init
//getPng
//4
//~/sb.png
至此为止,我们已经实现了Javascript 面向对象。 唯一的问题是我们怎么能在子类的方法里面Call 父类的方法呢? 比如Java的super(), python的_super。
那现在的问题是如何Call 父类的方法? 答案肯定是如果有继承关系,那么我们就在创建的封装中存储一个父类的引用。但是单纯的应用还不行,我们引用的父类方法,他也只是方法而已,要Call 这个方法,那真是丑:
this._super.someMethod.apply(this, augment) VS super(xx,xx) 真是差太多了.那么怎么办?
我们反过来想:
如果要实现this._super( xxx,xx ) ,我们看到2个特点:
1. 子类的自动有_super字段。(这个好实现,因为封装是我们创建,给返回的封装中加入_super属性即可)
2. 这个方法要怎么才能自动找到到?(如果一个方法在自己的属性中找不到,就会用Prototype的去找。哦也!我们把_super放在Prototype好了。)
3. 这个方法要怎么自动执行?(this.supper(xx),这个属性不能是Function,而必须是包庇方法(自己执行的方法)
//global initializing filed , identify where should this constructor should call init. initilizing= false; function print(msg){ console.log(msg) } // Create Constructor Function. var jClass = function(baseClass, prop){ if(typeof(baseClass) === "object"){ //Not A New type. prop = baseClass; baseClass = null; } // def template. default call init(common) var ClsTemplate = function(){ if(!initilizing){ this.init.apply(this,arguments) } }; //如果是子类,那么重定义Prototype和constructor矫正 if(baseClass){ initilizing = true; ClsTemplate.prototype = new baseClass(); ClsTemplate.prototype.constructor = ClsTemplate; //顺便添加了一个base的引用到prototype. ClsTemplate.prototype._super = baseClass.prototype; initilizing = false; } // implement Override for(var name in prop){
//如果name属性是自己的,而不是prototype的。 if(prop.hasOwnProperty(name)) { // 实现在子类中漂亮的调用父类函数 // // 如果有父类,且父类(prototype)有这个函数,Sub的prop的方法中也有这个函数)
// 那么我应该把子类的prototype中的这个函数做修改
// 我修改函数,除了可以调自己外,还额外修正this._super函数指向父类函数以供调用 if(baseClass && "function" === typeof (prop[name])
&& "function" === typeof (ClsTemplate.prototype[name])){ //print(ClsTemplate.prototype); //print(baseClass.prototype); //ClsTemplate.prototype._super === baseClass.prototype ClsTemplate.prototype[name] = (function(name, fn){ return function(){ this._super = baseClass.prototype[name]; return fn.apply(this, arguments) }; })(name, prop[name]); }else{ //用同名的prop属性替换掉Prototype的属性 ClsTemplate.prototype[name] = prop[name]; } } } return ClsTemplate; }; var Rect = jClass({ init:function(x,y,w,h){ this.x = x; this.y = y; this.w = w; this.h = h; }, getArea:function(){ print("Rect getArea"); return this.h * this.w; } }); var Sprite = jClass(Rect, { init:function(x, y ,w ,h, png){ //this.x = x; //this.y = y; //this.w = w; //this.h = h; //this._super.init.apply(this,[x, y ,w ,h]) //替代上下文环境: print(this) this._super(x, y ,w ,h); this.png = png; }, getPng:function(){ return this.png; }, getArea:function(){ print("Sprite getArea"); return this._super(); //return("Nothing") } }); var sprite = new Sprite(0,0,2,2, "~/sb.png"); print(sprite.getArea()); print(sprite.getPng());
// output
//{ _super: [Function] }
//Sprite getArea
//Rect getArea
//4
//~/sb.png
我们Javascript 面向对象的实现也就到此结束了。
参考 http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html