关于JS中OOP的具体实现,许多大神级的JS专家都给出了自己的方案。

一:Douglas Crockford

1.1 Douglas Crockford实现的类继承

  1. /**
  2. * 原文地址:http://javascript.crockford.com/inheritance.html
  3. */
  4. Function.prototype.method = function (name, func) {
  5. this.prototype[name] = func;
  6. return this;
  7. };
  8.  
  9. Function.method('inherits', function (parent) {
  10. var d = {},
  11. p = (this.prototype = new parent());
  12. this.prototype.constructor = parent;
  13. this.method('uber', function uber(name) {
  14. if (!(name in d)) {
  15. d[name] = 0;
  16. }
  17. var f, r, t = d[name], v = parent.prototype;
  18. if (t) {
  19. while (t) {
  20. v = v.constructor.prototype;
  21. t -= 1;
  22. }
  23. f = v[name];
  24. } else {
  25. f = p[name];
  26. if (f == this[name]) {
  27. f = v[name];
  28. }
  29. }
  30. d[name] += 1;
  31. r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
  32. d[name] -= 1;
  33. return r;
  34. });
  35. return this;
  36. });

DC实现的这个类继承的思路是:

1. this.prototype = new parent()通过重写构造函数的原型对象来实现继承

2. 增加实例方法uber(),通过instance.uber()可以访问父类中的某个方法。当然uber方法有点复杂,分析之前我们要先明白以下几点:

a. 闭包变量不会被销毁,同时函数的作用域是在函数定义时确定的。

b. 每个函数都有prototype属性,指向一个对象,这个对象会在函数作为构造函数创建对象时,作为创建对象的模板,这个对象我们称之为原型对象。

c. 每个对象都有一个__proto__指针,指向此对象在创建时所依赖的原型对象。Object.prototype.__proto__ === null;

d. 在对一个对象求某个键值时,如果对象本身不存在这个键,则会在对象的原型(链)上面寻找

  1. Function.method('inherits', function (parent) {
  2. //d, p, parent都是一直存在的闭包变量,可供uber()方法使用
  3.  
  4. var d = {},
  5.  
  6. //通过 this.prototype = new parent() 实现继承
  7. p = (this.prototype = new parent());
  8. this.prototype.constructor = parent;
  9.  
  10. //通过增加原型方法uber()实现对象父类方法的调用(特别是子父类中的同名方法)
  11. //类似Java中的super,调用其直接父类中的方法
  12. this.method('uber', function uber(name) {
  13. //d[name]是标识某方法在原型链中的层级,以便在原型链中正确得到相应的父类方法
  14. //使用d[name]这样的map是因为在一个方法中可能调用多个父类的同名方法
  15. if (!(name in d)) {
  16. d[name] = 0;
  17. }
  18. var f, r, t = d[name], v = parent.prototype;
  19. if (t) {
  20. //如果层级标识不为0,则要循环在原型链上找到对象的原型对象,再确定对应的父方法name
  21. while (t) {
  22. v = v.constructor.prototype;
  23. t -= 1;
  24. }
  25. f = v[name];
  26. } else {
  27. f = p[name]; // p === this.prototype === new parent;当前类的原型对象
  28.  
  29. //如果当前类的原型对象上的方法name与对象实例的name方法相等。
  30. //这里要注意,对于引用类型的相等,比较的是指针。引用类型值a == 引用类型值b 只能说明a,b都指向同一块内存
  31.  
  32. //原型对象上的方法name与对象实例的方法name相等,只能说明对象本身没有这个方法,这个方法是存放在对象原型对象中的
  33. if (f == this[name]) {
  34. f = v[name]; // v = parent.prototype 是父类的原型对象。上面的属性会被父类的实例与子类的实例共享
  35. }
  36. }
  37.  
  38. //uber()方法层级+1
  39. d[name] += 1;
  40.  
  41. //使用apply调用父类的方法f,并设置其上下文为this,即子类实例
  42. r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
  43.  
  44. //层级还原
  45. d[name] -= 1;
  46. return r;
  47. });
  48. return this;
  49. });
  1. //测试
  2. function Animal() {}
  3.  
  4. Animal.prototype.getInfo = function() {
  5. return 'Animal';
  6. }
  7. Animal.prototype.getAge = function() {
  8. return '10';
  9. }
  10.  
  11. function Dog() { }
  12. //注意Dog.prototype上新增的方法一定要写在调用了inherits()后面
  13. Dog.inherits(Animal);
  14. Dog.prototype.getInfo = function() {
  15. var a = 'dogName:' + this.uber('getInfo');
  16. var b = 'dogAge:' + this.getAge();
  17. return a +'|'+ b;
  18. }
  19.  
  20. function Collie() {}
  21. Collie.inherits(Dog);
  22. Collie.prototype.getInfo = function() {
  23. return this.uber('getInfo') + '|Collie';
  24. }
  25.  
  26. var c = new Collie();
  27. console.log(c.getInfo()); //dogName:Animal|dogAge:10|Collie
  28. console.log(c instanceof Collie);// true
  29. console.log(c instanceof Dog); // true
  30. console.log(c instanceof Animal); //true
  31.  
  32. console.log(c.constructor === Animal); //false
  33. console.log(c.constructor === Dog); //true
  34. console.log(c.constructor === Collie); //false
  35.  
  36. //c.constructor 应该等于Collie才对,现在却指向了Dog.错误原因:this.prototype.constructor = parent;

在玉伯早期的一篇文章中(http://www.iteye.com/topic/248933),有下面一个例子。至于结果,找出完整的调整栈,看一下就完成明白了。结果与原文有点不一样,可能DC修改过他的代码吧。

  1. // 例2
  2. function D1() {}
  3. D1.prototype.getName = function() { return 'D1' }; // @4
  4.  
  5. function D2() {}
  6. D2.inherits(D1);
  7. D2.prototype.getName = function () { return this.uber('getName') + ',D2'; }; // @5
  8.  
  9. function D3() {}
  10. D3.inherits(D2);
  11.  
  12. function D4() {}
  13. D4.inherits(D3);
  14.  
  15. function D5() {}
  16. D5.inherits(D4);
  17. D5.prototype.getName = function () { return this.uber('getName') + ',D5'; }; // @6
  18.  
  19. function D6() {}
  20. D6.inherits(D5);
  21.  
  22. var d6 = new D6();
  23. println(d6.getName()); // => D1,D2,D2,D2,D5,D5
  24. println(d6.uber('getName')); // => D1,D2,D2,D2,D5

发现结果是D1,D2,D2…这样。上面已经说过:在对一个对象求某个键值时,如果对象本身不存在这个键,则会在对象的原型(链)上面寻找。这就是产生多个D2的原因。但这结果与我们期望的super效果不一样。下面是玉伯加了patch的代码:

  1. // patched by lifesinger@gmail.com 2008/10/4
  2. Function.method('inherits', function (parent) {
  3. var d = { },
  4. p = (this.prototype = new parent());
  5. // 还原constructor
  6. p.constructor = this;
  7. // 添加superclass属性
  8. p.superclass = parent;
  9.  
  10. this.method('uber', function uber(name) {
  11. if (!(name in d)) {
  12. d[name] = 0;
  13. }
  14. var f, r, t = d[name], v = parent.prototype;
  15. if (t) {
  16. while (t) {
  17. // 利用superclass来上溯,避免contructor陷阱。要注意parent没有被修改过,所以v在每次进入uber时的值是一样的。始终指向父类原型对象
  18. v = v.superclass.prototype;
  19. // 跳过“断层”的继承点。不会因为“对象本身没有向原型(链)拿”造成重复执行
  20. if(v.hasOwnProperty(name)) {
  21. t -= 1;
  22. }
  23. }
  24. f = v[name];
  25. } else {
  26. f = p[name];
  27. if (f == this[name]) {
  28. f = v[name];
  29. }
  30. }
  31. d[name] += 1;
  32. if(f == this[name]) { // this[name]在父类中的情景
  33. r = this.uber.apply(this, Array.prototype.slice.apply(arguments));
  34. } else {
  35. r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
  36. }
  37. d[name] -= 1;
  38. return r;
  39. });
  40. return this;
  41. });
  1.  
  1. // 例3
  2. function F1() { }
  3. F1.prototype.getName = function() { return 'F1'; };
  4.  
  5. function F2() { }
  6. F2.inherits(F1);
  7. F2.prototype.getName = function() { return this.uber('getName') + ',F2'; };
  8.  
  9. function F3() { }
  10. F3.inherits(F2);
  11. F3.prototype.getName = function() { return this.uber('getName') + ',F3'; };
  12.  
  13. function F4() { }
  14. F4.inherits(F3);
  15. F4.prototype.getName = function() { return this.uber('getName') + ',F4'; };
  16.  
  17. document.write('<hr />')
  18. var f3 = new F3();
  19. document.write(f3.getName()); // => F1,F2,F3
  20. document.write('<hr />')
  21. var f4 = new F4();
  22. document.write(f4.getName()); // => F1,F2,F3,F4
  23.  
  24. console.log(f3 instanceof F3);//true
  25. console.log(f3 instanceof F2);//true
  26. console.log(f3 instanceof F1);//true
  27. console.log(f3.constructor === F3);//true
  28. console.log(f3.constructor === F2);//false
  29. console.log(f3.constructor === F1);//false
  30. console.log(f4.constructor === F4);//true
  31. console.log(f4.constructor === F3);//false
  32. console.log(f4.constructor === F2);//false
  33. console.log(f4.constructor === F1);//false

至此,可以发现已经实现:

》实现了继承

》修正了实例的constructor属性指向错误;

》instanceof能正常运行

》能使用uber调用父类的方法

当然也会有缺点:

》每一次继承都会创建多个闭包变量,内存占用多

》每一次继承都要先创建一个父类的实例,(new parent())

》子类与父类必须先定义好,为子类增加实例方法也必须放到inherits()方法调用之后。

参考文章:

http://javascript.crockford.com/inheritance.html

http://www.iteye.com/topic/248933

1.2 Douglas Crockford实现的基于原型的继承

  1. if (typeof Object.create !== 'function') {
  2. Object.create = function (o) {
  3. //定义类
  4. function F() {}
  5. //重写原型,实现继承
  6. F.prototype = o;
  7. //返回类的实例
  8. return new F();
  9. };
  10. }

这样也可以实现继承。不过没有类,实例相关的概念。当然也不具有什么uber()方法能力。不过DC说, 这才是JavaScript的“本性”。JavaScript本身就是无类的,基于原型的。ES5已经实现这个方法。

二:John Resig

jQuery的作者John Resig的继承实现思路是:实现一个全局对象Class(其实是一个函数,但在JS中函数也是对象),这个对象具有静态方法extends()。extends()需要传入一个对象作为返回(构造)函数的原型对象依据。同时也实现了与uber()类似的_super()方法。

  1. /* Simple JavaScript Inheritance
  2. * By John Resig http://ejohn.org/
  3. * MIT Licensed.
  4. */
  5. // Inspired by base2 and Prototype
  6. (function(){
  7. var initializing = false,
  8. // 摘自http://www.cnblogs.com/sanshi/archive/2009/07/14/1523523.html
  9. // fnTest是一个正则表达式,可能的取值为(/\b_super\b/ 或 /.*/)
  10. // - 对 /xyz/.test(function() { xyz; }) 的测试是为了检测浏览器是否支持test参数为函数的情况
  11. // - 不过我对IE7.0,Chrome2.0,FF3.5进行了测试,此测试都返回true。
  12. // - 所以我想这样对fnTest赋值大部分情况下也是对的:fnTest = /\b_super\b/;
  13. fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
  14.  
  15. //这里的this指向window, 相当于window.Class = function(){}
  16. this.Class = function(){}; //@1
  17.  
  18. /**
  19. * 向全局对象Class(其实是一个函数,但在JS中函数也是特殊的对象,可以在上面挂属性或方法)上面增加静态方法extend
  20. * extend方法需要一个对象作为返回类的原型对象的模板
  21. * @param {*} prop
  22. * @returns {Function}
  23. */
  24. Class.extend = function(prop) {
  25. //上面说过,Class是一个(函数)对象,函数作为对象的方法调用时,this指向函数所属的对象
  26. //所以this指向Class对象:@1。其实就是一个匿名函数
  27. var _super = this.prototype;
  28.  
  29. initializing = true;
  30.  
  31. //new this()其实就是包含父类原型的一个空对象。这个为继承打了基础。保证了instanceof能得到正确的结果
  32. var prototype = new this(); //@2
  33.  
  34. initializing = false;
  35.  
  36. /**
  37. * 把传进来的prop对象上面的属性复制到@2对象上
  38. * 这时候有三个对象:
  39. * prop:用户作为extend方法的参数传进来的对象
  40. * _super:父类的原型对象
  41. * prototype: 要返回的类的原型对象
  42. * 它们之间的关系是:prop用来扩展prototype; prototype用于实现继承;
  43. * _super用于实现调用父类的方法
  44. */
  45. for (var name in prop) {
  46. /**
  47. * 注意:true && true && 1 > 0 ? 1: -1; === (true && true && 1 > 0 ) ? 1: -1;
  48. * 所以下面的赋值过程为:
  49. * 1. 如果prop[name]不是一个函数: prototype[name] = prop[name]
  50. * 2. 如果prop[name]是一个函数,但_super[name]不是一个函数:prototype[name] = prop[name]
  51. * 3. 如果prop[name], _super[name]都是一个函数,且fn.Test.test(prop[name])为假:prototype[name] = prop[name]
  52. * 4. 其它情况:prototype[name] = 匿名函数自执行的结果
  53. *
  54. * 备注:/\b_super\b/.test(function () {}) === > /\b_super\b/.test((function() {}).toString());
  55. * 即要测试的函数代码中包含_super这个字符串都会返回true;
  56. * /\b_super\b/.test(function() {var a = '_super'}) === true
  57. * /\b_super\b/.test(function() {var a = '_supe'}) === false
  58. */
  59.  
  60. //在prototype上面加入自己的原型属性
  61. prototype[name] =
  62. typeof prop[name] == "function" &&
  63. typeof _super[name] == "function" &&
  64. //如果传入对象的某个属性中包含'_super',则要做特殊处理
  65. fnTest.test(prop[name]) ? (function(name, fn){
  66. //这个name, fn会成为闭包变量
  67. return function() {
  68. var tmp = this._super;
  69.  
  70. //把所有的父类中有'_super'字符串的方法都用闭包变量name保存起来
  71. //同时_super也是一个闭包变量,这样就可以找到在调用this._super()时要调用父类的那个方法
  72. //uber()方法要手动传入一个方法名,但_super()方法却能自动找到父类的同名方法
  73. this._super = _super[name];
  74.  
  75. //在前面已经准备好this._super的指向,然后调用包含this._super的实例方法,
  76. //就会直接转到父类方法
  77. var ret = fn.apply(this, arguments);
  78. //将this._super还原。this._super也可能包含其它值
  79. this._super = tmp;
  80.  
  81. return ret;
  82. };
  83. })(name, prop[name]) : prop[name];
  84. }
  85.  
  86. //定义类Klass。Jhon Resig原代码是用的Class,增加了理解难度
  87. function Klass() {
  88. //所有的初始化都是在构建函数的init方法里面进行的。
  89. if ( !initializing && this.init )
  90. this.init.apply(this, arguments);
  91. }
  92.  
  93. //重写类的原型,实现继承
  94. Klass.prototype = prototype;
  95.  
  96. //修正类原型对象的constructor指向
  97. Klass.prototype.constructor = Klass;
  98.  
  99. //为类增加静态方法extend
  100. Klass.extend = arguments.callee;
  101.  
  102. //返回类,其实是构造方法
  103. return Klass;
  104. };
  105. })();

分析:

1. 在Object的上增加了一层:实现了一个Class类,这个类会作为所有使用extend产生类的第二基类,上面有一个extend方法。这个方法会返回一个类(构造函数),返回时已经设置好原型对象。这个原型对象由 extend传入的参数对象与此类父类的原型共同生成

2. 当调用new Constructor()时,会检测对象是否有init方法,如果有,会调用这个init方法对实例进行初始化,否则返回一个空对象

3. 可以使用返回的类再产生一个类

  1. var Person = Class.extend({
  2. init: function(isDancing){
  3. this.dancing = isDancing;
  4. },
  5. flag:'PersonProp'
  6. });
  7.  
  8. var Ninja = Person.extend({
  9. init: function(){
  10. //能自动找到父类中的init方法
  11. this._super( false );
  12. },
  13. flag:'NinjaProp'
  14. });
  15.  
  16. var p = new Person(true);
  17. p.dancing; // => true
  18.  
  19. var n = new Ninja();
  20. n.dancing; // => false
  21.  
  22. p instanceof Object; //=> true
  23. p instanceof Class;//=> true
  24. p instanceof Person; //=> true
  25. p.constructor === Person;//=>true

参考文章:

http://ejohn.org/blog/simple-javascript-inheritance/

http://www.cnblogs.com/sanshi/archive/2009/07/14/1523523.html

小结JS中的OOP(下)的更多相关文章

  1. 小结JS中的OOP(中)

    此篇文章主要是提炼<JavaScript高级程序设计>中第六章的一些内容. 一:JS中OOP相关的概念 开始之前先总结JS中OOP相关的一些概念: 构造函数:JS中的构造函数就是普通的函数 ...

  2. 小结JS中的OOP(上)

    前言:大家都知道,OOP有三大特性:封装,继承,多态.下面是自己对这三个特性的理解: 封装:把属性与方法整合到某种数据类型中.目的是让类的使用者按类的编写者的意愿去使用类.在封装过程中会一般会做两件事 ...

  3. 不会JS中的OOP,你也太菜了吧!(第二篇)

    一.你必须知道的 1> 原型及原型链在继承中起到了关键的作用.所以你一定要理解他们.2> 不会JS中的OOP,你也太菜了吧!(第一篇) 二.继承的6种方法 1> 原型链继承 原型链继 ...

  4. JS中的继承(下)

    JS中的继承(下) 在上一篇 JS中的继承(上) 我们介绍了3种比较常用的js继承方法,如果你没看过,那么建议你先看一下,因为接下来要写的内容, 是建立在此基础上的.另外本文作为我个人的读书笔记,才疏 ...

  5. 不会JS中的OOP,你也太菜了吧!(第一篇)

    一.你必须知道的 1) 字面量 2) 原型 3) 原型链 4) 构造函数 5) 稳妥对象(没有公共属性,而且其方法也不引用this的对象.稳妥对象适合用在安全的环境中和防止数据被其它程序改变的时候) ...

  6. JavaScript面向对象(一)——JS OOP基础与JS 中This指向详解

      前  言 JRedu 学过程序语言的都知道,我们的程序语言进化是从"面向机器".到"面向过程".再到"面向对象"一步步的发展而来.类似于 ...

  7. js中给easyUI年份,月份选择下拉框赋值

    sp中定义 js中初始化 //年度下拉框初始化 $("#yearChoose").combobox({    valueField:'year',     textField:'y ...

  8. JS中的继承(上)

    JS中的继承(上) 学过java或者c#之类语言的同学,应该会对js的继承感到很困惑--不要问我怎么知道的,js的继承主要是基于原型(prototype)的,对js的原型感兴趣的同学,可以了解一下我之 ...

  9. js中各种跨域问题实战小结(二)

    这里接上篇:js中各种跨域问题实战小结(一) 后面继续学习的过程中,对上面第一篇有稍作休整.下面继续第二部分: -->5.利用iframe和location.hash -->6.windo ...

随机推荐

  1. Java 常用数据结构深入分析(Vector、ArrayList、List、Map)

    线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构.这些类均在java.util包中.本文试图通过简单的描述,向读者阐述各个类的作用以 ...

  2. 在linux/unix中查找大文件

    在linux/unix中查找大文件,如查找大于100M文件的位置路径,查找等于10M文件的位置路径等等,下面就介绍几个实现快速查找的命令: 1. 查找指定目录下所有大于100M的文件,命令为 find ...

  3. 物联网操作系统Hello China V1.76(PC串口版)版本发布

    作为向ARM平台移植的基线版本,经过三个多月的努力,Hello China V1.76终于完成并发布.相对原来发布的V1.75版本,该版本主要做了如下修改: 彻底去掉了原来版本源代码中的C++特性,采 ...

  4. 【重走Android之路】【Java面向对象基础(二)】细说String、StringBuffer和StringBuilder

    [重走Android之路][基础篇(二)][Java面向对象基础]细说String.StringBuffer和StringBuilder   1.String String是Java中的一个final ...

  5. Android 软键盘弹出时把布局顶上去,控件乱套解决方法

    解决办法:方法一:在你的activity中的oncreate中setContentView之前写上这个代码getWindow().setSoftInputMode(WindowManager.Layo ...

  6. 可视化MNIST之降维探索Visualizing MNIST: An Exploration of Dimensionality Reduction

    At some fundamental level, no one understands machine learning. It isn’t a matter of things being to ...

  7. (4)FTP服务器下载文件

    上一篇中,我们提到了怎么从FTP服务器下载文件.现在来具体讲述一下. 首先是路径配置.. 所以此处我们需要一个app.config来设置路径. <?xml version="1.0&q ...

  8. Vim 新用法

    daw , delete a word cw , delete from cursor to the end then insert mode a word 移动: f ; Aa Oo Cc Ii S ...

  9. leetcode:Multiply Strings

    Given two numbers represented as strings, return multiplication of the numbers as a string. Note: Th ...

  10. Hadoop集群(第2期)_机器信息分布表

    1.分布式环境搭建 采用4台安装Linux环境的机器来构建一个小规模的分布式集群. 图1 集群的架构 其中有一台机器是Master节点,即名称节点,另外三台是Slaver节点,即数据节点.这四台机器彼 ...