你的位置:首页 > Java教程

[Java教程]面向对象的JS实现(JavaScript中级)


参考 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的所有属性包括函数与方法。

所以这就给我继承提供了实现方法: 如果函数的构造函数(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 parentprint(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){    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 面向对象的实现也就到此结束了。