您所在的位置:小祥子 » 编程 » JavaScript » 正文

面向对象的JS理解(JavaScript中级)

时间:2015-04-25 编辑:velly.zhou 来源:本站整理
参考 http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html

网上关于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的所有属性包括函数与方法。

所以这就给我继承提供了实现方法: 如果函数的构造函数指向自己,且构造函数的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

这是,我们看到继承已经实现了,那么就准备开始实现多态了。 多态就是要求子类有能力复写父类的方法。 要实现复写,那么我们必须要全局掌控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矫正
    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){
        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){
        if(prop.hasOwnProperty(name)) {
            // 实现在子类中漂亮的调用父类函数
            //
            //如果有父类,且父类(prototype)有这个函数,Sub (prop子类也有这个函数
            //那么,我们更新ClsTemplate.prototype[name]这个函数为
            // (修改_super属性为函数对应的baseClass的函数)
            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){
                //包闭:改函数会直接返回一个函数(该函数修改了_supper为父函数,并使用这个this调用了函数
                    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());
/**
 * 当sprite 调用 getArea()时,
 * 会调用 Sprite.getArea()
 *  this._super()
 * 当Call This.super时,数据结构是:
 * _supper是属于Prototype的,所以
 * 这时候调用的这个函数,就是Prototype我们已经替换过的函数了。
* */
print(sprite.getPng());


// output

  //{ _super: [Function] }
  //Sprite getArea
  //Rect getArea
  //4
  //~/sb.png

我们Javascript 面向对象的实现也就到此结束了。