原生js面向对象

在JavaScript的大世界里讨论面向对象,都要提到两点:1.JavaScript是一门基于原型的面向对象语言 2.

模拟类语言的面向对象方式。对于为什么要模拟类语言的面向对象,我个人认为:某些情况下,原

型模式能够提供一定的便利,但在复杂的应用中,基于原型的面向对象系统在抽象性与继承性方面差强人意。

由于JavaScript是唯一一个被各大浏览器支持的脚本语言,所以各路高手不得不使用各种方法来提高语言的便利性,

优化的结果就是其编写的代码越来越像类语言中的面向对象方式,从而也掩盖了JavaScript原型系统的本质。  

基于原型的面向对象语言
  原型模式如类模式一样,都是是一种编程泛型,即编程的方法论。另外最近大红大紫的函数编程也是一种编程泛型

。JavaScript之父Brendan Eich在设计JavaScript时,从一开始就没打算为其加入类的概念,而是借鉴了另外两门基于原型

的的语言:Self和Smalltalk。

  既然同为面向对象语言,那就得有创建对象的方法。在类语言中,对象基于模板来创建,首先定义一个类作为对

现实世界的抽象,然后由类来实例化对象;而在原型语言中,对象以克隆另一个对象的方式创建,被克隆的母体称为原型对象。

  克隆的关键在于语言本身是否为我们提供了原生的克隆方法。在ECMAScript5中,Object.create可以用来克隆对象。

?
1
2
3
4
5
6
7
8
9
10
var person = {
   name: "tree" ,
   age: 25,
   say: function (){
     console.log( "I'm tree." )
   }
};
 
var cloneTree = Object.create(person);
console.log(cloneTree);


  原型模式的目的并不在于得到一个一模一样的对象,而提供了一种便捷的方式去创建对象(出自

《JavaScript设计模式与开发实践》)。但是由于语言设计的问题,JavaScript的原型存在着诸多矛盾,

它的某些复杂的语法看起来就那些基于类的语言,这些语法问题掩盖了它的原型机制(出自《JavaScript语言精粹》)

。如:

?
1
2
3
4
5
6
function Person(name, age){
   this .name = name;
   this .age = age;     
}
 
var p = new Person( 'tree' , 25)

  实际上,当一个函数对象呗创建时,Function构造器产生的函数对象会运行类似这样的一些代码:

?
1
this .prototype = {constructor: this }

  新的函数对象被赋予一个prototype属性,它的值是一个包含constructor属性且属性值为该新函数的对象。

当对一个函数使用new运算符时,函数的prototype的属性的值被作为原型对象来克隆出新对象。如果new运算符是一个方法,它的执行过程如下:

?
1
2
3
4
5
6
7
8
9
10
Function.prorotype. new = function () {
   //以prototype属性值作为原型对象来克隆出一个新对象
   var that = Object.create( this .prorotype);
   
   //改变函数中this关键指向这个新克隆的对象
   var other = this .apply(that, arguments);
   
   //如果返回值不是一个对象,则返回这个新克隆对象
   return (other && typeof other === 'object' ) ? other : that;
}

  从上面可以看出,虽然使用new运算符调用函数看起来像是使用模板实例化的方式来创建对象,但本质还是以原型对象来克隆出新对象。

   由于新克隆的对象能否访问到原型对象的一切方法和属性,加上new运算符的特性,这便成了利用原型模拟类式语言的基石。 

利用原型模拟类式语言
抽象

  用原型模式来模拟类,首先是抽象方式。根据JavaScript语言的特点,通常一个类(实际上是伪类)通常是将字段

放置于构造函数(实际上是new 运算符调用的函数,JavaScript本身并没有构造函数的概念)中,而将方法放置于函数的prototype属性里。

?
1
2
3
4
5
6
7
8
function Person(name, age) {
   this .name = name;
   this .age = age;
};
 
Person.prototype.say = function (){
   console.log( "Hello, I'm " + this .name);
};

继承

  继承是OO语言中的一个最为人津津乐道的概念。许多OO语言都支持两种继承方式:接口继承和实现继承。

接口继承之继承方法签名,而实现继承则继承实际的方法。

但是ECMAScript中无法实现接口继承,只支持实现继承,而且其实现继承主要是依靠原型链来实现的。(出自

《JavaScript高级程序设计》 6.3节——继承)在高三中作者探索了各种关于继承的模拟,如:组合继承

、原型继承、寄生继承、寄生组合继承,最终寄生组合式成为所有模拟类式继承的基础。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Person(name, age) {
   this .name = name;
   this .age = age;
};
 
Person.prototype.say = function (){
   console.log( "Hello, I'm " + this .name);
};
 
function Employee(name, age, major) {
   Person.apply( this , arguments);
   this .major = major;
};
 
Employee.prototype = Object.create(Person.prototype);
Employee.prorotype.constructor = Employee;
 
Employee.prorotype.sayMajor = function (){
   console.log( this .major);
}

  高三中只给出了单继承的解决方案,关于多继承的模拟我们还得自己想办法。由于多继承有其本身的困难:面向对象语言如果支持了多继承的话,都会遇到著名的菱形问题(Diamond Problem)。假设存在一个如左图所示的继承关系,O中有一个方法foo,被A类和B类覆写,但是没有被C类覆写。那么C在调用foo方法的时候,究竟是调用A中的foo,还是调用B中的foo?

  所以大多数语言并不支持多继承,如Java支持单继承+接口的形式。JavaScript并不支持接口,要在一个不支持接口的语言上去模拟接口怎么办?答案是著名的鸭式辨型。放到实际代码中就是混入(mixin)。原理很简单:

?
1
2
3
4
5
function mixin(t, s) {
    for ( var p in s) {
      t[p] = s[p];
    }
  }

  值得一提的是dojo利用MRO(方法解析顺序(Method Resolution Order),即查找被调用的方法所在类时的搜索顺序)方式解决了多继承的问题。  

  到此,我们已经清楚了模拟类语言的基本原理。作为一个爱折腾的程序员,我希望拥有自己的方式来简化类的创建:

  • 提供一种便利的方式去创建类,而不暴露函数的prototype属性
  • 在子类中覆盖父类方法时,能够像Java一样提供super函数,来直接访问父类同名方法
  • 以更方便的方式添加静态变量和方法而不去关心prototype
  • 像C#那样支持Attribute   

最终,在借鉴各位大牛的知识总结,我编写了自己的类创建工具O.js:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
( function (global) {
   var define = global.define;
   if (define && define.amd) {
     define([], function (){
       return O;
     });
   } else {
     global.O = O;
   }
 
   function O(){};
 
   O.derive = function (sub) {
     debugger;
     var parent = this ;
     sub = sub ? sub : {};
 
     var o = create(parent);
     var ctor = sub.constructor || function (){}; //如何调用父类的构造函数?
     var statics = sub.statics || {};
     var ms = sub.mixins || [];
     var attrs = sub.attributes || {};
 
     delete sub.constructor;
     delete sub.mixins;
     delete sub.statics;
     delete sub.attributes;
 
     //处理继承关系
     ctor.prototype = o;
     ctor.prototype.constructor = ctor;
     ctor.superClass = parent;
     //利用DefineProperties方法处理Attributes
     //for (var p in attrs) {
       Object.defineProperties(ctor.prototype, attrs);
     //}
     //静态属性
     mixin(ctor, statics);
     //混入其他属性和方法,注意这里的属性是所有实例对象都能够访问并且修改的
     mixin(ctor.prototype, sub);
     //以mixin的方式模拟多继承
     for ( var i = 0, len = ms.length; i < len; i++) {
       mixin(ctor.prototype, ms[i] || {});
     }
 
     ctor.derive = parent.derive;
     //_super函数
     ctor.prototype._super = function (f) {
       debugger;
       return parent.prototype[f].apply( this , Array.prototype.slice.call(arguments, 1));
     }
 
     return ctor;
   }
 
   function create(clazz) {
     var F = function (){};
     F.prototype = clazz.prototype;
     //F.prototype.constructor = F; //不需要
     return new F();
   };
 
   function mixin(t, s) {
     for ( var p in s) {
       t[p] = s[p];
     }
   }
})(window);

类创建方式如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var Person = O.derive({
   constructor: function (name) { //构造函数
     this .setInfo(name);
   },
   statics: { //静态变量
     declaredClass: "Person"
   },
   attributes: { //模拟C#中的属性
     Name: {
       set: function (n) {
         this .name = n;
         console.log( this .name);
       },
       get: function () {
         return this .name + "Attribute" ;
       }
     }
   },
   share: "asdsaf" , //变量位于原型对象上,对所有对象共享
   setInfo: function (name) { //方法
     this .name = name;
   }
});
var p = new Person( 'lzz' );
console.log(p.Name); //lzzAttribute
console.log(Person);

继承:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var Employee = Person.derive({ //子类有父类派生
   constructor: function (name, age) {
     this .setInfo(name, age);
   },
   statics: {
     declaredClass: "Employee"
   },
   setInfo: function (name, age) {
     this ._super( 'setInfo' , name); //调用父类同名方法
     this .age = age;
   }
});
 
var e = new Employee( 'lll' , 25);
console.log(e.Name); //lllAttribute
console.log(Employee);


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值