zoukankan      html  css  js  c++  java
  • Javascript面向对象编程的实现(二)

          上一篇温习了传统的Javascript实现面向对象编程的实现方式,但这种实现方式有点麻烦,感觉看上去功能上是实现了Object-Oriented了,但形式上却不像,所以现在我们用另一种方法去实现同样的需求,效果一样,只是我们会对类的构造和继承的实现做一定的封装。

          首先,在写正文之前,为了参考怎样封装Javascript的面向对象特征,特意找了一些JS框架查看源代码,可惜资历尚浅,还是很难看懂,主要参考了prototype.js,mootools.js和base.js这几个框架对class的实现,基本方式类似,现在就据我所知,说说一种容易理解一点的实现方法吧。

          首先,我们定义一个总类Class。

    1 //总的父类
    2 Class = function () {
    3 };

          然后我们向Class添加一个extend方法,用于创建一个新的类,extend的参数即我们要添加的属性和方法的一个json对象了。例如我们要定义一个点类Point和一个圆形Circle继承自图形父类Shape,可能写出来是这样子的:

     1 //定义图形父类Shape
     2 Shape = Class.extend({
     3     type:'',
     4     draw:function () {
     5         console.log('I am drawing a shape of ' + this.type + '!');
     6     }
     7 });
     8 //定义点Point
     9 Point = Shape.extend({
    10     x:100,
    11     y:100,
    12     type:'Point',
    13     init:function (x, y) {
    14         this.x = x || 100;
    15         this.y = y || 100;
    16     },
    17     set:function (x, y) {
    18         this.x = x || 100;
    19         this.y = y || 100;
    20     },
    21     draw:function (ctx) {
    22         this.super();
    23         var ctx = ctx || document.getElementById('canvas').getContext('2d');
    24         ctx.beginPath();
    25         ctx.arc(this.x, this.y, 5, 0, 2 * Math.PI, true);
    26         ctx.closePath();
    27         ctx.fill();
    28     }
    29 });
    30 //定义圆Circle
    31 Circle = Shape.extend({
    32     type:'Circle',
    33     center:new Point(150, 150),
    34     radius:10,
    35     init:function (r, x, y) {
    36         this.radius = r;
    37         this.center.set(x, y);
    38     },
    39     draw:function () {
    40         this.super();
    41         var ctx = ctx || document.getElementById('canvas').getContext('2d');
    42         ctx.beginPath();
    43         ctx.arc(this.center.x, this.center.y, this.radius, 0, 2 * Math.PI, true);
    44         ctx.closePath();
    45         ctx.fill();
    46     }
    47 });

          但怎样实现这种清晰的面向对象编程呢?我们一步一步来。我们预想extend函数大概结构是这样子的,这是第一个版本的初始模样:

     1 Class.extend = function (props) {
     2     subclass = function () {
     3         for (var key in props) {
     4             this[key] = props[key];
     5         }
     6         if (this.init) {
     7             this.init.apply(this, arguments);
     8         }
     9     }
    10     subclass.prototype = new this();
    11     subclass.prototype.constructor = subclass;
    12     subclass.extend = arguments.callee;
    13     return subclass;
    14 }

          这如何解释呢(原谅我没有写注释)?传入的参数props是类的属性和方法,然后我们创建了一个子类(第2行),让子类继承父类(第10行),修改子类的构造函数(第11行),让子类像父类一样也拥有extend方法(第12行),最后返回新创建的子类subclass(第13行)。当我们创建一个对象的时候,则会把对象拥有的属性和方法props赋值一次给当前新创建的对象,如果对象拥有init初始化方法,则调用初始化方法。

          注意,这是第一个版本,只是为以后的优化搭建一个原型,但明显它是有问题的。首先,这种做法在每次new一个对象的时候才去进行赋值,而且是全部赋值,这样子每一个新创建的对象都拥有相同的所有属性和方法,显然一方面共享了props里面的属性和方法,某个对象改变这些值,都会引起所有对象改变,另一方面函数不应该重复赋值的。另一方面,这种方法直接覆盖掉了父类的方法了,不能再调用父类的方法,例如Java中调用this.super()这样子。于是,我们对此进行改进:

     1 //用来创建一个新的子类
     2 Class.extend = function (props) {
     3     //存储着父类的prototype ;
     4     var parent = this.prototype;
     5     //标志这时候是在初始化子类的prototype,不要执行父类的属性复制和构造函数;
     6     var prototyping = true;
     7     //初始化子类的prototype,用于实现继承
     8     var prototype = new this();
     9     //初始化完再把标志修改过来;
    10     prototyping = false;
    11     //对每一个新属性和方法添加到子类的prototype中
    12     for (var name in props) {
    13         //如果新添加的方法和父类拥有相同名称,则可以通过this.super()临时指向父类的方法,执行完整个子类函数后恢复super方法。
    14         if (typeof(props[name]) == "function" && typeof(parent[name]) == "function") {
    15             prototype[name] = (function (name, fn) {
    16                 return function () {
    17                     var temp = this.super || null;
    18                     this.super = parent[name];
    19                     var ret = fn.apply(this, arguments);
    20                     if (temp) {
    21                         this.super = temp;
    22                     }
    23                     return ret;
    24                 };
    25             })(name, props[name]);
    26         }
    27         //否则只是简单的复制属性,浅复制。
    28         else {
    29             prototype[name] = props[name];
    30         }
    31     }
    32     //子类的定义
    33     function c() {
    34         //如果不是在prototyping
    35         if (!prototyping) {
    36             //对每一个属性和方法,如果属性是个object,包括可能是json对象,可能是一个类的实例,也可能是数组,因为他们都是prototype的属性,所以必须进行复制,避免影响其它对象
    37             for (var p in this) {
    38                 if (this[p] && typeof(this[p]) === 'object') {
    39                     this[p] = this[p].clone();
    40                 }
    41             }
    42             //如果类拥有init初始化方法,则调用初始化函数进行初始化
    43             if (this.init) {
    44                 this.init.apply(this, arguments);
    45             }
    46         }
    47         return this;
    48     }
    49     //定义子类的prototype,保证继承。
    50     c.prototype = prototype;
    51     //修改构造器,不然子类的构造器默认是Class父类
    52     c.prototype.constructor = c;
    53     //让子类也跟父类一样拥有extend方法,用于实现继承
    54     c.extend = arguments.callee;
    55     //最后返回子类,创建成功
    56     return c;
    57 };

          对了,这就是最后的版本了,相关解释可以一点一点参看代码的注释。简单来说,extend函数做了几件事:

    1.复制新的属性和方法;

    2.当父类存在相同名字的方法时,通过创建一个临时函数super来实现方法的覆盖;

    3.每次创建类的实例时,对prototype中的每一个object属性进行深复制,以免影响了其他对象。

          下面,我简单说说object的深复制吧。浅复制很容易实现,很多框架的extend方法都可以实现,但深复制需要把object类型的属性都一层一层去复制。Jquery拥有clone方法,但它是用来复制DOM的,不要误会,我们可以使用Jquery的extend方法实现复制,本来是没问题的,但Jquery中处理深度复制的object并不能复制我们自己用Class.extend创建出来的对象,只能复制纯对象,即使用{}或者new Object()创建出来的对象,所以当我们的某个属性例如位置position为一个点Point对象时,就不会进行深度复制,结果某一个对象的position修改了,所有对象position都变成一样的了。为了处理这个麻烦,我们写了一个clone函数,即上一篇提到的clone函数,添加到Object的prototype上,代码如下:

     1 Object.prototype.clone = function () {
     2     if (!this || this instanceof HTMLElement || this instanceof Function) {
     3         return this;
     4     }
     5     var objClone;
     6     if (this.constructor == Object) {
     7         objClone = new this.constructor();
     8     }
     9     else {
    10         objClone = new this.constructor(this.valueOf());
    11     }
    12     for (var key in this) {
    13         if (objClone[key] != this[key]) {
    14             if (this[key] && typeof(this[key]) === 'object') {
    15                 objClone[key] = this[key].clone();
    16             } else {
    17                 objClone[key] = this[key];
    18             }
    19         }
    20     }
    21     objClone.toString = this.toString;
    22     objClone.valueOf = this.valueOf;
    23     return objClone;
    24 }

         这段代码的解释会留到下次解读valueOf,constructor,toString这些方法属性时再解释。最后我们写一段测试代码,跟上次的一样,结果也一样,但定义类的方式改变了而已。

     1 Test = function () {
     2     var c1 = new Circle(10);
     3     var c2 = c1.clone();
     4     c2.center.set(200, 250);
     5 //    console.log(c1);
     6 //    console.log(c2);
     7     c1.draw();
     8     c2.draw();
     9     var p1 = new Point(50, 70);
    10     var p2 = new Point(150, 200);
    11 //    console.log(p1);
    12 //    console.log(p2);
    13     p1.draw();
    14     p2.draw();
    15 
    16     p3 = p1.clone();
    17     p3.set(150, 350);
    18 //    console.log(p3);
    19     p3.draw();
    20 }
    21 window.onload = Test;

          到此,Javascript面向对象编程的实现基本完结了,大家使用时遇到什么bug可以留言给我。不过下一篇文章会解读Javascript里面的instanceof,typeof,toString,valueOf,constructor这些方法对于不同类型的参数会返回什么值,可以结合克隆函数一起解读。

    代码下载地址:https://files.cnblogs.com/avicha/Javascript面向对象编程的实现(2).rar 压缩包包含了几个JS框架的源代码,可以查阅相关的函数实现方式。

          

  • 相关阅读:
    (五)Spring Cloud教程——Config(F版本)
    (四)Spring Cloud教程——Zuul(F版本)
    (三)Spring Cloud教程——Hystrix(F版本)
    (二)Spring Cloud教程——Ribbon 和 Feign(F版本)
    (一)Spring Cloud教程——Eureka(F版本)
    3年的坚持,最终造就著作——《Learninghard C#学习笔记》
    WPF快速入门系列(9)——WPF任务管理工具实现
    WPF快速入门系列(8)——MVVM快速入门
    [WPF实用技巧]如何使WPF的TreeView节点之间有连线
    WPF快速入门系列(7)——深入解析WPF模板
  • 原文地址:https://www.cnblogs.com/avicha/p/2537483.html
Copyright © 2011-2022 走看看