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框架的源代码,可以查阅相关的函数实现方式。

      

原文地址:https://www.cnblogs.com/avicha/p/2537483.html