用描写对象的特征来实现面向对象的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

posted @ 2015-04-25 20:01  velly.zhou  阅读(553)  评论(4编辑  收藏  举报