V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
bramblex
V2EX  ›  JavaScript

大家喜欢用 JavaScript 中哪一种继承方式?大家来说说自己的看法 OwO

  •  1
     
  •   bramblex · 2015-06-12 21:47:09 +08:00 · 4500 次点击
    这是一个创建于 3241 天前的主题,其中的信息可能已经有所发展或是发生改变。

    JavaScript继承主要是两种形式:
    1. 原型链继承
    2. 拷贝继承

    注:ES7标准中的class就暂时不考虑了,因为还没有具体去了解过,希望以后能完善到不需要自己实现那么那么蛋疼的东西了。

    我个人倾向于原型链继承,因为原型链继承是JavaScript规范里面推荐的继承,并且最重要的是,可以用原生的 instanceof 判断类型!而拷贝继承不行!


    因为以上的一些问题,所以我就自己做了一个简单的JavaScript面的对象库。大概有实现这么几个功能:
    1. 封装好的构造函数不管写不写new操作符都不会产生错误了,当然我是倾向于不写new操作符。
    2. 返回的构造函数是命名的,方便调试的时候追踪函数栈。
    3. 封装了一个parent方法,可以方便访问父原型的构造函数或者父原型的方法。
    4. 封装了一个alias方法,可以创建一个方法别名。

    具体实现在具体实现在github上 >> 代码传送门

    接下来来看一些简单的示例来看看怎么用。

    // 首先定义一个Animal类(姑且称为类吧),继承自Objec,有一个构造函数。
    var Animal = BlxClass.extend('Animal', Object, function(name, sound){
      this.name = name || null;  
      this.sound = sound || null;
    });
    
    Animal.prototype.call = function(){
      console.log(this.sound);
    };
    
    // 现在我希望定义一个doge类,继承自Animal。第一个参数 ‘Doge’ 是用来给构造函数命名的。
    var Doge = BlxClass.extend('Doge', Animal, function(name){
      var sound = 'wang';
      BlxClass.parent(this, Doge, [name, sound]); // 调用父类的构造函数,因为初始化对象属性在父类构造函数里。
    });
    
    Doge.prototype.wang = BlxClass.alias('call');
    
    // 现在实例化一个doge对象
    var doge = Doge('Tom'); // 有没有 new 都没关系,结果相同。
    doge.call(); // =>'wang'
    doge.wang(); // =>'wang'
    

    然后这样就跟其他语言类的继承大致相似了。然后大家更喜欢哪一种继承方式呢?都来说说自己看法吧。

    54 条回复    2015-06-15 21:29:47 +08:00
    bramblex
        1
    bramblex  
    OP
       2015-06-12 21:48:16 +08:00
    @yahoo21cn 展开讨论在这里 OwO
    exoticknight
        2
    exoticknight  
       2015-06-12 21:56:19 +08:00
    大神 Douglas Crockford 的代码
    if ( typeof Object.create !== 'function' ) {
    Object.create = function ( o ) {
    function F() {}
    F.prototype = o;
    return new F();
    };
    }
    http://javascript.crockford.com/prototypal.html
    bramblex
        3
    bramblex  
    OP
       2015-06-12 22:02:53 +08:00
    @exoticknight

    这个思路是从一个对象复制出另一个对象,OwO,然后再加工新对象吗?感觉有点坑……
    hbkdsm
        4
    hbkdsm  
       2015-06-12 22:03:51 +08:00   ❤️ 1
    论茴香豆的茴有几种写法
    icymorn
        5
    icymorn  
       2015-06-12 22:08:11 +08:00
    我没用到这种专门搞一个继承的方法,js太灵活了,很多问题在c++首先想到继承解决,而在js中奇技淫巧耍耍就解决了。当然,要我写一个继承,首先从原型链上搞起。
    bramblex
        6
    bramblex  
    OP
       2015-06-12 22:08:43 +08:00
    @hbkdsm

    茴香豆有几种写法都无所谓,能表意即可。但是对于几种不同继承方式,可远远没有那么简单了。如果只是写写jQuery,那当然无所谓。
    bramblex
        7
    bramblex  
    OP
       2015-06-12 22:09:59 +08:00
    @icymorn

    有什么好的实现方案吗?我就是从原形链开始搞起的。OwO
    hbkdsm
        8
    hbkdsm  
       2015-06-12 22:18:18 +08:00
    @bramblex 最简洁的应该是 ES6 的 class-extends-super 这一套语法糖,Nodejs 的 util.inherits 也很好啊。你掌握之后应该就很厉害了,因为你终于知道茴有几种写法了!
    yangff
        9
    yangff  
       2015-06-12 22:22:37 +08:00
    class 的继承方式我记得就是原型继承的语法糖? <求确认
    bramblex
        10
    bramblex  
    OP
       2015-06-12 22:26:55 +08:00
    @hbkdsm

    然而我已经实现了类似你所说的那些东西。
    bramblex
        11
    bramblex  
    OP
       2015-06-12 22:27:39 +08:00
    @yangff

    不知道哎,如果是原型继承的语法糖,那么坑爹的问题还是依旧存在的……
    yangmls
        12
    yangmls  
       2015-06-12 22:35:45 +08:00   ❤️ 1
    @yangff 是的,原型链的语法糖


    @bramblex 粗略看了下代码

    1. 为何要用 eval,似乎只是为了处理 name 是个字符串,虽然这玩意不一定evil,但是代码可读性就很差。

    2. child 的 prototype.constructor 没有处理?直接 new 了 parent

    3. Backbone 的那个 extend 不是更好的方案?
    bramblex
        13
    bramblex  
    OP
       2015-06-12 22:52:06 +08:00
    @yangmls

    1. eval 是两个作用:
    1.1. 命名。如果没有命名在chrome下输出的东西太难看了,受不了。虽然不命名也可以。
    1.2. 没有办法 new func.apply(this, args),所以只能事先把从constructor里面读出来有几个参数,然后用arguments[0], arguments[1] ....这样写进去。

    2. childe 的 prototype 直接 new 了 parent。然而是会出点问题,现在马上解决掉。

    3. Backbone那么个方案我没看过……但是不能光会用别人的东西嘛,对吧。
    pinxue
        14
    pinxue  
       2015-06-12 22:52:16 +08:00
    在 JavaScript 里仿 Class 的都是异端!

    ES5之前:
    var o = { foo:bar, proto: p };

    ES5加了:
    var o5 = Object.create(p);
    bramblex
        15
    bramblex  
    OP
       2015-06-12 23:04:22 +08:00
    @pinxue

    然而我就是异端 /w\ 。因为真心比原型继承好用,总感觉原型继承是半残的……其实只要达到目的,无所谓什么异端不异端啦
    YuJianrong
        16
    YuJianrong  
       2015-06-12 23:11:13 +08:00 via iPhone   ❤️ 2
    我在公司做的class系统是原型链继承,还算好用吧。

    对于构造函数没加new这个我以前是赞成不写也new出来的做法,不过现在不赞成了,如果新做一个我一定要抛异常。原因很简单:我不希望做一件事情有多种不一样的做法,如果大家做法一样的话,重构或者静态代码分析都会简单很多,这对于大规模协同开发很重要(jQ是反例)。

    我在这个之上还做了destroy方法,这个方法会自动迭代调用父类的destroy方法,并会清空对象成员,保证已销毁对象在大多数情况下使用会崩溃,以第一时间找到问题点。
    bramblex
        17
    bramblex  
    OP
       2015-06-12 23:22:11 +08:00
    @YuJianrong

    很有道理啊!
    lrvy
        18
    lrvy  
       2015-06-12 23:24:14 +08:00
    JJ的思念
    bramblex
        19
    bramblex  
    OP
       2015-06-12 23:36:53 +08:00
    @lrvy 快说,你是哪个基佬?
    yangmls
        20
    yangmls  
       2015-06-12 23:40:45 +08:00
    借鉴一下把

    var extend = function(protoProps, staticProps) {
    var parent = this;
    var child;

    // The constructor function for the new subclass is either defined by you
    // (the "constructor" property in your `extend` definition), or defaulted
    // by us to simply call the parent constructor.
    if (protoProps && _.has(protoProps, 'constructor')) {
    child = protoProps.constructor;
    } else {
    child = function(){ return parent.apply(this, arguments); };
    }

    // Add static properties to the constructor function, if supplied.
    _.extend(child, parent, staticProps);

    // Set the prototype chain to inherit from `parent`, without calling
    // `parent` constructor function.
    var Surrogate = function(){ this.constructor = child; };
    Surrogate.prototype = parent.prototype;
    child.prototype = new Surrogate;

    // Add prototype properties (instance properties) to the subclass,
    // if supplied.
    if (protoProps) _.extend(child.prototype, protoProps);

    // Set a convenience property in case the parent's prototype is needed
    // later.
    child.__super__ = parent.prototype;

    return child;
    };

    // 如何用

    var BaseObject = function() {};

    BaseObject.extend = extend;

    var Dog = BaseObject.extend({
    call: function() {}...
    });

    //当然,new是必须的

    var dog = new Dog
    yangmls
        21
    yangmls  
       2015-06-12 23:41:23 +08:00
    neoblackcap
        22
    neoblackcap  
       2015-06-12 23:56:17 +08:00
    是我的理解有问题吗?原型继承不就是指所有的新对象都是靠拷贝原型来的嘛?楼主你提这两个东西在底层真的有很大的区别吗?你确定不是跟class一样是个语法糖?
    bramblex
        23
    bramblex  
    OP
       2015-06-13 00:08:52 +08:00
    @neoblackcap

    这个不是你理解有问题,而是……你基本概念不清晰……
    neoblackcap
        24
    neoblackcap  
       2015-06-13 01:19:43 +08:00
    @bramblex 若是可以的话,我想知道是如何划分这两种继承方式,因为我在网上也找不到“拷贝继承”这基本概念,翻GoF里面也没见过。看mdn都是说的是原型链继承
    hbkdsm
        25
    hbkdsm  
       2015-06-13 02:01:19 +08:00
    @neoblackcap 我理解的 @bramblex 所谓的拷贝继承应该就是 mixin,不知道对不对。可问题是 ... mixin 根本就不是继承啊!
    bramblex
        26
    bramblex  
    OP
       2015-06-13 08:05:06 +08:00
    @hbkdsm

    https://zh.wikipedia.org/wiki/%E7%BB%A7%E6%89%BF_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)

    首先,拷贝继承是mixin没错了。但是你好像对继承的概念理解有些问题……
    bramblex
        27
    bramblex  
    OP
       2015-06-13 08:08:12 +08:00
    @neoblackcap
    @hbkdsm

    不对,拷贝继承不是mixin……或者只能说有些时候mixin是拷贝继承的一个步骤而已……

    http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance_continued.html
    Zhang
        28
    Zhang  
       2015-06-13 10:42:59 +08:00
    没必要写javascript了,可以写其他语言再transcompile成javascript就行了。
    bramblex
        29
    bramblex  
    OP
       2015-06-13 10:50:13 +08:00
    @Zhang 嗯,等我先把JavaScript折腾到极致了我再去转coffee,type之类的好了。不过我觉得应该折腾不到机制 OwO。因为折腾完了JavaScript语言本身,还有v8可以折腾
    Zhang
        30
    Zhang  
       2015-06-13 11:15:39 +08:00
    @bramblex java、c/c++、php、objective-c、c#都可以翻译成javascript,没有必要折腾coffee,type之类的了。
    Zhang
        31
    Zhang  
       2015-06-13 11:21:38 +08:00
    @bramblex 前几天发现几乎所有语言都可以transcompile成javascript,看来不知有多少人对javascript不满啊!
    lrvy
        32
    lrvy  
       2015-06-13 12:14:18 +08:00
    @bramblex 莫与在人间 2333
    neone
        33
    neone  
       2015-06-13 12:16:32 +08:00
    各有各的用处吧。
    《Javascript高级程序设计 第三版》中 6.3.4 ‘原型式继承’ 一节中提到
    “他(Douglas Crockford)的想法是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型”,
    “ECMAScript5通过新增Object.create()方法规范了原型式继承”。
    所以我理解为如果想继承一个对象的同时又不想创建自定义类型的时候可以使用‘原型式继承’,其他的时候应该考虑‘寄生组合式继承’。
    bramblex
        34
    bramblex  
    OP
       2015-06-13 13:10:36 +08:00
    @Zhang JavaScript有问题,但是不能否认JavaScript各种很好的特性。比如函数式的特性,比如闭包等等。

    至于有那么多语言翻译成JavaScript,只能说明JavaScript火了。然后那些并不了解JavaScript又不想学习的人开始需要用自己熟悉的语言来实现JavaScript的项目了。

    但是当然啦,JavaScript这货虽然有很多优秀的特性,但是坑也是一堆一堆的。说的难听点,JavaScript现在还根本就是断手断脚的残缺品嘛…… TnT
    neoblackcap
        35
    neoblackcap  
       2015-06-13 13:58:03 +08:00
    @bramblex 我觉得你说的拷贝继承根本不能算是继承。所以才提出JS里面有继承也就是原型继承的论调。这个所谓的拷贝继承根本就是原型继承的那套思想,但是实现却用浅拷贝,我觉得只能算是一种实现方式,按道理实现原型拷贝要用深拷贝才对,浅拷贝的话,你修改子类对象一样会修改到父类对象,这样的效果就明显跟继承的初衷不符,跟OO思想不符。
    jings
        36
    jings  
       2015-06-13 14:29:13 +08:00
    bramblex
        37
    bramblex  
    OP
       2015-06-13 14:31:22 +08:00
    @neoblackcap

    没错,就是继承的实现方式…仅此而已…
    magicdawn
        38
    magicdawn  
       2015-06-13 15:16:25 +08:00
    原型呀。。。

    之前面试,我直接甩了 util.inherits 源码 https://github.com/nodejs/io.js/blob/v2.3.0/lib/util.js#L714
    sodatea
        39
    sodatea  
       2015-06-13 15:25:04 +08:00
    首先,class 是 ES6 标准不是 ES7
    bramblex
        40
    bramblex  
    OP
       2015-06-13 15:46:58 +08:00
    @sodatea 手抖写错了,然后并不能改……
    arbipher
        41
    arbipher  
       2015-06-13 15:49:27 +08:00
    我建议使用ES6的class关键字和babel
    bramblex
        42
    bramblex  
    OP
       2015-06-13 16:08:20 +08:00
    @arbipher

    谢谢。然而我的目的并不是要别人告诉我用什么工具,我的目的是自己实现一个。类似babel这类的complier之后我也会自己实现一个。反正作为学生党,我觉得玩玩也挺有意思的。
    exoticknight
        43
    exoticknight  
       2015-06-14 18:55:53 +08:00
    我觉得讨论这么多,都老是想用类的继承来写 javascript,然而 js 中的原型机制根本就不是类的机制,而是能够模拟实现类的机制。
    所以要怎么用就怎么写,需要查找原型链那么就直接将 new function 的 prototype 直接等于被继承对象的 prototype。希望被继承对象不会被修改就深复制其 prototype 作为新对象的 prototype。
    比如我在自己的项目中需要的只是原型链的查询实现变量作用域的嵌套
    https://github.com/exoticknight/simpleTemplate.js/blob/master/simpleTemplate.advanced.js
    所以用大神 Douglas Crockfor 的代码就够了。
    bramblex
        44
    bramblex  
    OP
       2015-06-14 19:20:10 +08:00
    @exoticknight

    但是为什么不能总是想着类继承来些JavaScript呢?类继承机制在绝大多数情况下确实比原型机制更直观也更好用。其实为什么一定要拘泥于语言本身所谓“够了”的特性,而不能追求更直观更方便更强大的特性呢?

    我觉得永远都没有什么 xxxx 就够了这么一说。
    exoticknight
        45
    exoticknight  
       2015-06-14 21:37:49 +08:00
    @bramblex
    看清楚我的表达,什么什么就够了是有条件的,不要歪曲了。不用原型链而非要用其模拟类继承并非不可以,然而若写简洁的代码就能完成任务为什么还要增其实体?
    另外,我不认为“类继承机制在绝大多数情况下确实比原型机制更直观也更好用”,那是可能因为你先学了 OOP 而已。
    而你所谓的“更直观更方便更强大的特性”也只是主观因素。
    我也没有认为 js 超越其他语言,因为<del>PHP才是最好的语言</del>。我只是想说 js 的原型链能解决,就不要用类继承了。
    当然如果你只写ES6,我无话可说。
    bramblex
        46
    bramblex  
    OP
       2015-06-14 23:29:48 +08:00
    @exoticknight

    如果js原型链能解决?嗯,那请问,在计算机上有什么能够用0和1解决呢?既然能解决,那干嘛还需要语言?

    问题不是能不能解决,而是能不能更好的解决,尽量避免重复,尽量避免接触丑陋的底层细节,而不是“能解决”。

    抱歉,我觉得我们还是就此打断这种毫无意义的价值观争端吧。欢迎讨论技术问题
    exoticknight
        47
    exoticknight  
       2015-06-15 19:52:16 +08:00
    @bramblex
    貌似价值观问题是你先提出的
    “问题不是能不能解决,而是能不能更好的解决”。我就一句话,js 上用原型链比类继承更“尽量避免重复,尽量避免接触丑陋的底层细节,而不是“能解决”。 ”
    在想如何用类的时候你已经自我矛盾了。
    bramblex
        48
    bramblex  
    OP
       2015-06-15 20:08:54 +08:00
    @exoticknight

    然而只有论点却没有论据……
    bramblex
        49
    bramblex  
    OP
       2015-06-15 20:19:48 +08:00
    @exoticknight

    所以还是就此打住吧,别让这种没有营养的口水战继续下去了好吗?自始至终你根本拿不出任何证据来支撑你的论点——原形链继承比类继承好。同样的,我根本也找不出任何证据来反驳你。所以我们能不讨论这种圣战性质的谁比谁好的问题吗?

    所以我们能单纯的讨论原形链继承和类继承分别有啥好有啥不好不行吗?如果不好,那我只能说一句了,PHP是最好的编程语言。
    exoticknight
        50
    exoticknight  
       2015-06-15 20:31:02 +08:00
    @bramblex
    行,本来打算继续补充,怕你已经 block 了我所以没发。
    1、原型链本身就是原生的方式,js 本身并不支持类继承,写类的 polyfill 本身就是强行加细节。
    2、原型链机制在最少代码情况下用之前我贴出的代码就 ok,相反你看类继承包括你在内建了多少楼?又用了多少代码?
    我也只是呼吁一下 js 有其独特的方式,写的时候多运用,而不要老想着上其他语言的那一套而已。
    我在写 node 的时候觉得 inherit 也是很好的,但反对所有地方都用。
    exoticknight
        51
    exoticknight  
       2015-06-15 20:33:19 +08:00
    @bramblex
    喂喂,分明你自己也没有提出证据全是问句啊……还直接说我了唉……
    bramblex
        52
    bramblex  
    OP
       2015-06-15 21:03:02 +08:00
    @exoticknight

    1. 首先我已经直说了我提不出证据反驳你啊……这个我记得我是清清楚楚写在上面了吧,不然您再仔细看看?

    2. 即便你说的全都对,但是依旧没有证明你所说的“原型链继承比类继承更优”啊。能得出的结论只是我的实现比你多了几十行代码而已。

    3. “我也只是呼吁一下 js 有其独特的方式,写的时候多运用,而不要老想着上其他语言的那一套而已。 ”这句话真的一点意义都没有,不行我反着给你说一遍:

    我也只是呼吁一下大家要多想想其他的解决方案,写的时候多思考,而不要老是被js那一套限制住了思维而已。

    4. 我不反对任何人在任何情况下写任何代码,除非我是他boss。不然我觉得我真的没有任何权力去反对别人写怎么样的代码唉,我能想象我反对别人写怎么样的代码的时候,别人会白我一眼然后淡淡回我一句:管你屁事。
    exoticknight
        53
    exoticknight  
       2015-06-15 21:20:37 +08:00
    @bramblex
    1、我说的是更前的地方。43楼我表达了“哪里用什么就ok了没必要硬要用某模式”,44楼你自己说“类继承机制在绝大多数情况下确实比原型机制更直观也更好用。”,并没有提出证据,我说的是这里。之后我提出并非如此你反而要我提证据了。

    2、如果全都对,*在使用 js 情况下*,为什么就没有证明更优?你这种情况不就是尝试在 java 中用原型链的行为不是?

    3、这句话我承认自己脑子的确被驴踢了。

    4、我从来没有指明反对你写什么代码,也承认类继承有其可用之处,你要写 babel 也好写 compiler 也好,管我屁事。难道我在论坛上提出观点也不行了?

    还是打住吧

    虽然说“如果是两个理性而真诚的真理追求者争论问题,争论的结果必然是二人达成一致。”,但是我还有其他事情要做……
    bramblex
        54
    bramblex  
    OP
       2015-06-15 21:29:47 +08:00
    @exoticknight

    我在46楼的时候已经说过要打住了吧……
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2870 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 07:31 · PVG 15:31 · LAX 00:31 · JFK 03:31
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.