序言

  在JavaScript的大世界里讨论面向对象,都要提到两点:1.JavaScript是一门基于原型的面向对象语言 2.模拟类语言的面向对象方式。对于为什么要模拟类语言的面向对象,我个人认为:某些情况下,原型模式能够提供一定的便利,但在复杂的应用中,基于原型的面向对象系统在抽象性与继承性方面差强人意。由于JavaScript是唯一一个被各大浏览器支持的脚本语言,所以各路高手不得不使用各种方法来提高语言的便利性,优化的结果就是其编写的代码越来越像类语言中的面向对象方式,从而也掩盖了JavaScript原型系统的本质。

  

基于原型的面向对象语言

  原型模式如类模式一样,都是是一种编程泛型,即编程的方法论。另外最近大红大紫的函数编程也是一种编程泛型。JavaScript之父Brendan Eich在设计JavaScript时,从一开始就没打算为其加入类的概念,而是借鉴了另外两门基于原型的的语言:Self和Smalltalk。

  既然同为面向对象语言,那就得有创建对象的方法。在类语言中,对象基于模板来创建,首先定义一个类作为对现实世界的抽象,然后由类来实例化对象;而在原型语言中,对象以克隆另一个对象的方式创建,被克隆的母体称为原型对象。

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

var person = {
name: "tree",
age: 25,
say: function(){
console.log("I'm tree.")
}
}; var cloneTree = Object.create(person);
console.log(cloneTree);

  原型模式的目的并不在于得到一个一模一样的对象,而提供了一种便捷的方式去创建对象(出自《JavaScript设计模式与开发实践》)。但是由于语言设计的问题,JavaScript的原型存在着诸多矛盾,它的某些复杂的语法看起来就那些基于类的语言,这些语法问题掩盖了它的原型机制(出自《JavaScript语言精粹》)。如:

function Person(name, age){
this.name = name;
this.age = age;
} var p = new Person('tree', 25)

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

this.prototype = {constructor: this}

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

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属性里。

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节——继承)在高三中作者探索了各种关于继承的模拟,如:组合继承、原型继承、寄生继承、寄生组合继承,最终寄生组合式成为所有模拟类式继承的基础。

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)。原理很简单:

 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:

 (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);

  类创建方式如下:

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);

  继承:

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);

  参考文章:

  [原创]JavaScript继承详解

  Douglas Crockford - Prototypal Inheritance in JavaScript

  Douglas Crockford - Classical Inheritance in JavaScript

  JavaScript实现继承的几种方式

  A Base Class for JavaScript Inheritance

  DOJO中的面向对象__第三章 Dojo中的多继承

  《JavaScript语言精粹》

  《JavaScript设计模式与开发实践》

  《JavaScript高级程序设计第三版》

JavaScript面向对象之我见的更多相关文章

  1. JavaScript学习笔记(三)——this、原型、javascript面向对象

    一.this 在JavaScript中this表示:谁调用它,this就是谁. JavaScript是由对象组成的,一切皆为对象,万物皆为对象.this是一个动态的对象,根据调用的对象不同而发生变化, ...

  2. JavaScript学习总结(三)——this、原型、javascript面向对象

    一.this 在JavaScript中this表示:谁调用它,this就是谁. JavaScript是由对象组成的,一切皆为对象,万物皆为对象.this是一个动态的对象,根据调用的对象不同而发生变化, ...

  3. Javascript面向对象特性实现封装、继承、接口详细案例——进级高手篇

    Javascript面向对象特性实现(封装.继承.接口) Javascript作为弱类型语言,和Java.php等服务端脚本语言相比,拥有极强的灵活性.对于小型的web需求,在编写javascript ...

  4. javascript面向对象系列第四篇——选项卡的实现

    前面的话 面向对象的应用并非只是读几本书那么容易,需要有大量的工程实践做基础才能真正理解并学会使用它.本文将用面向对象的技术来制作一个简单的选项卡 图示说明 由图示结果看到,这是一个非常简单的选项卡. ...

  5. javascript面向对象系列第三篇——实现继承的3种形式

    × 目录 [1]原型继承 [2]伪类继承 [3]组合继承 前面的话 学习如何创建对象是理解面向对象编程的第一步,第二步是理解继承.本文是javascript面向对象系列第三篇——实现继承的3种形式 [ ...

  6. javascript面向对象系列第一篇——构造函数和原型对象

    × 目录 [1]构造函数 [2]原型对象 [3]总结 前面的话 一般地,javascript使用构造函数和原型对象来进行面向对象编程,它们的表现与其他面向对象编程语言中的类相似又不同.本文将详细介绍如 ...

  7. Javascript面向对象(封装、继承)

    Javascript 面向对象编程(一):封装 作者:阮一峰 Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象.但是,它又不是一种真正的面向对象编程( ...

  8. 《javascript面向对象精要》读书笔记

    <javascript面向对象精要> 买这本书的原因主要是因为作者,Nicholas C. Zakas 牛X闪闪的js专家,读过js高程的应该都知道他,而这本书是他的最新力作,感觉也是js ...

  9. 【转】javascript面向对象编程

    摘要:本文本来是想自己写的,奈何花了好长时间写好之后忘记保存,还按了刷新键,一键回到解放前,索性不写了,所以本文是转载的. 面向对象编程是用抽象方式创建基于现实世界模型的一种编程模式,主要包括模块化. ...

随机推荐

  1. 剑指Offer-【面试题03:二维数组中的查找】

    package com.cxz.question3; /* * 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序. * 请完成一个函数,输入这样的一个二维数组和 ...

  2. Flume(1)使用入门

    一.概述: Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集.聚合和传输的系统. 当前Flume有两个版本Flume 0.9X版本的统称Flume-og,Flume1.X ...

  3. CozyRSS开发记录20-CanResizeWithGrip

    CozyRSS开发记录20-CanResizeWithGrip 1.窗口样式 首先,WindowStyle有四种: 然后,对于窗口缩放的ResizeMode,也有四种,CanResize和CanRes ...

  4. CozyRSS开发记录15-获取和显示RSS内容

    CozyRSS开发记录15-获取和显示RSS内容 1.内容列表 我们先给RSSContentFrame增加一个ViewModel,里面和RSS源列表一样,提供一个ObservableCollectio ...

  5. Java 之 集合框架(JCF)

    1.集合框架 a.框架:为了实现某一目的或功能,而预先提供的一系列封装好的.具有继承或实现关系的类与集合 b.集合:①定义:Java中对一些数据结构和算法进行封装,即封装(集合也是一种对象) ②特点: ...

  6. Beginning Scala study note(5) Pattern Matching

    The basic functional cornerstones of Scala: immutable data types, passing of functions as parameters ...

  7. c:out标签和el表达式与跨域攻击XSS

    很多时候,在JSP中我们喜欢用EL表达式输出信息,但是最近发现这个确实存在个问题:XSS即跨域攻击. 下面看个例子: <c:out value="${student.name}&quo ...

  8. Gym 100646 F Tanks a Lot RMQ

    Problem F: Tanks a Lot Imagine you have a car with a very large gas tank - large enough to hold what ...

  9. java 随机生成身份证代码

    import java.util.Calendar; import java.util.Collection; import java.util.HashMap; import java.util.I ...

  10. Xmarks Hosts

    使用 Chrome 浏览器,但实在是没精力去各种FQ和寻找 Google 的 hosts 来同步书签,没办法只好折中使用 Xmarks 来单独同步书签,没想到最近 Xmarsks 又不好使了.再次标记 ...