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

js 怎么来写一个基类

  •  
  •   xiaomeimei · 2017-09-12 10:25:59 +08:00 · 5312 次点击
    这是一个创建于 2658 天前的主题,其中的信息可能已经有所发展或是发生改变。
    • 最近想整理一下 js 代码,想用 java 或者 C# 的思想去写,发现好麻烦啊,写一个基类都痛苦。

    • 虽然 python 也是脚本语言,但它写起基类也没什么难度,接口形式的编程也很方便。

    • 设计模式在 js 上很难套用。什么工厂模式、外观模式统统都写不了

    • 比如,我想实现一个接口或者抽象基类,要求所有继承此类的子类都必须实现所有的方法。js 不知道怎么来写

    67 条回复    2017-09-13 09:11:38 +08:00
    mooncakejs
        1
    mooncakejs  
       2017-09-12 10:33:51 +08:00
    在 es 里接口声明比较麻烦,抽象基类还有办法,定义一个方法,实现直接 throw
    mooncakejs
        2
    mooncakejs  
       2017-09-12 10:34:08 +08:00
    上 typescript,和 c# 基本差不了多少了。
    thisisgpy
        3
    thisisgpy  
       2017-09-12 10:34:08 +08:00   ❤️ 1
    class gay {}
    arzusyume
        4
    arzusyume  
       2017-09-12 10:34:53 +08:00
    class Base {}
    class A extends Base {}
    murmur
        5
    murmur  
       2017-09-12 10:35:18 +08:00   ❤️ 1
    都上了 js 还在想设计模式 你中毒了。。js 要什么设计模式。。不都是推翻重来。。还要继承。。
    SuperMild
        6
    SuperMild  
       2017-09-12 10:36:15 +08:00
    js 本身不合适,如果不愿意享受原型链的乐趣,最好上 typescript
    donlxn22
        7
    donlxn22  
       2017-09-12 10:36:57 +08:00
    TypeScript
    lamada
        8
    lamada  
       2017-09-12 10:37:46 +08:00
    TypeScript 你值得拥有
    xiaomeimei
        9
    xiaomeimei  
    OP
       2017-09-12 10:42:43 +08:00
    @mooncakejs @thisisgpy @SuperMild @donlxn22 @lamada TypeScript 写完后还要「编译」成 javascript,而且还比较麻烦
    xiaomeimei
        10
    xiaomeimei  
    OP
       2017-09-12 10:43:33 +08:00
    @murmur 但有些基础的东西确实感觉可以用设计模式
    murmur
        11
    murmur  
       2017-09-12 10:45:50 +08:00
    @xiaomeimei 就算是 ES6/TS 背后还是丑陋的原型链
    SuperMild
        12
    SuperMild  
       2017-09-12 10:48:10 +08:00
    @xiaomeimei 不麻烦,比手写模拟真 OO 方便很多了。
    xiaomeimei
        13
    xiaomeimei  
    OP
       2017-09-12 10:49:35 +08:00
    @murmur 是啊,背后相当丑陋。throw 的那种方法我想过了,但感觉好丑
    murmur
        14
    murmur  
       2017-09-12 10:50:22 +08:00
    @xiaomeimei 那就学习 vue,用 mixin 的概念代替 extend

    react 不也改成 createComponent 了么
    xiaomeimei
        15
    xiaomeimei  
    OP
       2017-09-12 10:50:57 +08:00
    @SuperMild 又要我装一下编辑器了
    zjsxwc
        16
    zjsxwc  
       2017-09-12 10:51:08 +08:00
    es5 基本不会去用继承(其实语法上也实现不了), 而是和 golang 一样通过组合来实现继承的作用, 比如我想要 obj2 拥有 obj1 的 methodB 能力, 可以这么写:

    ```
    function Class1(constructParamA, constructParamB){
    this.constructParamA = constructParamA;
    this.constructParamB = constructParamB;
    }
    Class1.prototype.methodA = function (){};
    Class1.prototype.methodB = function (){
    console.log(this.constructParamA+this.constructParamB);
    };


    function Class2(constructParamA){
    this.constructParamA = constructParamA;
    }
    Class2.prototype.methodB = function (){
    this.constructParamA.methodB();
    };


    var obj1 = new Class1("foo","bar");
    var obj2 = new Class2(obj1);
    obj2.methodB();

    ```
    mooncakejs
        17
    mooncakejs  
       2017-09-12 10:51:45 +08:00
    @xiaomeimei 配上 vscode 就不麻烦了。
    xiaomeimei
        18
    xiaomeimei  
    OP
       2017-09-12 10:52:17 +08:00
    @murmur vue 已经在用了,用在网站上。而我们的浏览器扩展上用的是纯 js,直接换 vue,时间成本太高,划不来
    xiaomeimei
        19
    xiaomeimei  
    OP
       2017-09-12 10:58:18 +08:00
    @zjsxwc 这种方式只可以实现子类使用了父类的 MethodB 方法,但不能强制子类也拥有 MethodA 方法
    otakustay
        20
    otakustay  
       2017-09-12 11:00:12 +08:00
    @murmur react 改成 createComponent 了?我感觉要么我穿越回了以前,要么我刚从火星回来

    另外原型链丑陋在哪里了?
    Biwood
        21
    Biwood  
       2017-09-12 11:06:59 +08:00
    原型链继承怎么就不能用了?看了这么多层回复没一个像样的,根本不需要什么 ES6 语法,老老实实写代码会死么
    mooncakejs
        22
    mooncakejs  
       2017-09-12 11:12:54 +08:00
    @Biwood 来个 abstruct method 或者 interface 看看
    zjsxwc
        23
    zjsxwc  
       2017-09-12 11:13:25 +08:00
    @xiaomeimei #19 实现你这种需求的写法很多的,比如这么写:
    ```
    function Class1(constructParamA, constructParamB){
    this.constructParamA = constructParamA;
    this.constructParamB = constructParamB;
    }
    Class1.prototype.methodA = function (){
    console.log("methodA >>"+this.constructParamA+this.constructParamB);
    };
    Class1.prototype.methodB = function (){
    console.log(this.constructParamA+this.constructParamB);
    };


    function Class2(objOfClass1){
    this.objOfClass1 = objOfClass1;
    for (prop in objOfClass1) {
    this[prop] = objOfClass1[prop];
    //if ((typeof objOfClass1[prop]) == "function") {
    // this[prop] = objOfClass1[prop];
    //}
    }
    }


    var obj1 = new Class1("foo","bar");
    var obj2 = new Class2(obj1);
    obj2.methodA();
    obj2.methodB();
    ```
    bramblex
        24
    bramblex  
       2017-09-12 11:14:59 +08:00   ❤️ 2
    在很多语言里面,基类就是破裤子上面的补丁,用来补泛型这个坑的,你现在拿着一条没有破洞的裤子问补丁在哪?你让我们怎么回答?
    learnshare
        25
    learnshare  
       2017-09-12 11:17:10 +08:00
    TypeScript 认可度不低嘛
    maomaomao001
        26
    maomaomao001  
       2017-09-12 11:18:11 +08:00 via Android
    原型链明明很好的呀,哪里丑陋了,不过这个时代,直接用 ts, es6.es7 不更好嘛
    otakustay
        27
    otakustay  
       2017-09-12 11:23:07 +08:00
    另外几乎所有的设计模式都是面向接口来做的,基类?为什么一定要有基类?
    murmur
        28
    murmur  
       2017-09-12 11:51:17 +08:00
    @otakustay 打错了。。我以为新版改成 createReactComponent 了 我还没升级用的还是 15 的 es5 语法

    至于你说原型链丑陋不丑陋 你和正统的 oo 比一下看父子那些关系不用 trick 能实现不就可以了
    Tunar
        29
    Tunar  
       2017-09-12 11:53:06 +08:00 via Android
    js 怎样写一个鸡肋😂
    wobuhuicode
        30
    wobuhuicode  
       2017-09-12 11:57:33 +08:00
    JS 写继承:原型链继承 -> 构造函数继承 -> 组合继承 -> 寄生组合继承
    MinonHeart
        31
    MinonHeart  
       2017-09-12 11:58:22 +08:00
    @Tunar 鸡肋😂
    otakustay
        32
    otakustay  
       2017-09-12 12:10:57 +08:00
    @murmur 弱的是语言本身而不是原型链啊,class based OO 的所有功能在原型链上都可以做,trick 是语法层面上的工作,你不能怪罪于底下的概念对吧
    murmur
        33
    murmur  
       2017-09-12 12:18:40 +08:00
    @otakustay 我是有点不太喜欢语法糖的,浏览器里跑的不还都是 es5 语法么,super 字我记得是 es6 的糖

    也不太相信 code map,我都升级到 chrome 60.0 了,动不动 code map 断点打不上,断点起飞,所以我宁可用 es5 语法,至少出错的时候停住的位置就是出错的位置

    webpack1 打出来的东西还能停住断点,webpack2 的模块就全变成 eval("xxxxxxxxx")了,出错了断点都停不了。。

    回楼上一些,不好意思扯远了,我是不太喜欢前端盲目继承的,

    但是也不能说基类是泛型的坑,ts 是强类型,不喜欢基类 List<Object>你也可以,越强的约束管水平不好的越好,所以 java 能让一群水平一般的人堆起来做个大项目还能跑,在不考虑性能的前提下,当然对大牛无所谓

    见过最恶心的继承就是 EasyUI,dialog->window->panel,但是实际上谁会用 window 和 panel ?我需要的是 dialog=panel+button+header,而不是 dialog extends window extends panel
    Hanggi
        34
    Hanggi  
       2017-09-12 12:27:31 +08:00
    class Gay {
    SorM() {

    }
    }
    qiqico
        35
    qiqico  
       2017-09-12 12:32:00 +08:00
    @murmur webpack2 打断点也没问题的,看你选哪种 sourcemap 方式了
    murmur
        36
    murmur  
       2017-09-12 12:35:20 +08:00
    @qiqico 可以让 hmr 的时候和 webpack1 一样展开全部代码不用 eval 那种么

    sourcemap 是没问题 就是 50%的概率没用 不要说版本不一致我当然知道要刷新

    现在是不打断点怎么让他停在可以看出是那句出错的地方 就是 pause on exceptions
    SilentDepth
        37
    SilentDepth  
       2017-09-12 12:37:22 +08:00
    拿 OOP 的经验生套到 JS 上,不觉得这样有多高效率,JS 本身也不是干这个的。如果实在要接口,写 TS 就好了,背后被转成了什么样何须操那么大的心。

    @murmur #33
    ES 版本更迭这事儿就扯远了,历史包袱是客观存在的,但咱还得向前看不是。至少箭头函数在 Chrome 里跑得飞起(尽管那不是一个语法糖)。Sourcemap 的问题,确实有断点打不准的情况,但通常不会差太多,不至于开发调试进行不下去(除非你一行代码套了 N 层 Lambda )。Webpack 2 出来的也是可以调的,我确信是你的配置有问题或你的代码风格需要适应一下。

    最后说一句:Vue 大法好~
    Mutoo
        38
    Mutoo  
       2017-09-12 12:50:12 +08:00
    abstract 和 interface 是静态编译的语言在编译阶段的约束,作为脚本语言,只有在运行时才去处理这些东西没有什么意义。
    X41822N
        39
    X41822N  
       2017-09-12 13:03:14 +08:00
    ts
    wangyufeng
        40
    wangyufeng  
       2017-09-12 13:14:27 +08:00
    @SuperMild 乐趣?
    SuperMild
        41
    SuperMild  
       2017-09-12 13:21:16 +08:00
    @wangyufeng 哈哈哈,对,就是乐趣,对于很多情况来说,原型链已经够用,甚至比 OO 更简单、清晰。
    mcfog
        42
    mcfog  
       2017-09-12 13:42:36 +08:00
    都是图灵完备的语言,只有 JS 上不需要用的设计模式,不存在 JS 上实现不了的设计模式

    楼主需要抛开其他所有 OO 语言的经验来看 JS,市面上基于原型链的语言几乎只有 JS 这一家,而几乎没有设计模式或者讨论 OO 的文章会基于 JS 来聊,太多的关于 JS 的 OO 的文章也都是生搬硬套其他语言的做法来误人子弟

    回到楼主的比如,比如别的语言里你可能写 interface MyInterface {methodA(); methodB();} class MyClass implements MyInterface {.....} ,实际上在表达的是“给我 methodA 和 methodB 这两个方法,我帮你对接剩余的逻辑”,那么换到 JS 里,直接把接口能力作为依赖就行了

    class EsClass {
    constructor(methodA, methodB){}
    ...
    }
    sensui7
        43
    sensui7  
       2017-09-12 14:31:56 +08:00
    很讨厌楼主跟风用这个头像
    qiuyk
        44
    qiuyk  
       2017-09-12 14:47:01 +08:00
    @sensui7 我还以为你刚吐槽完 py 又来吐槽 js 哈哈哈

    老实说以前写 C++写 C#,老是纠结于怎么定义好基类呀接口

    自从写 js 以来好像就没想过这些事情
    xiaomeimei
        45
    xiaomeimei  
    OP
       2017-09-12 15:53:33 +08:00
    @mooncakejs 我试试吧。今天上午被禁了 1800s,回复太频繁了
    xiaomeimei
        46
    xiaomeimei  
    OP
       2017-09-12 15:54:13 +08:00
    @otakustay 面向接口是对的
    xiaomeimei
        47
    xiaomeimei  
    OP
       2017-09-12 15:57:39 +08:00
    @sensui7 哈哈~~~来咬我
    xiaomeimei
        48
    xiaomeimei  
    OP
       2017-09-12 16:02:24 +08:00
    @mcfog 是啊,不能生搬硬套,要根据语言特点来写
    sensui7
        49
    sensui7  
       2017-09-12 16:22:49 +08:00
    @xiaomeimei 深刻诠释了抄袭者的嘴脸, 我无所谓, 这本来就是恶搞的头像, 对比两个图片, 看细节
    yunying
        50
    yunying  
       2017-09-12 17:10:05 +08:00
    js 为啥不能用设计模式?我天天写代码都在用
    otakustay
        51
    otakustay  
       2017-09-12 17:29:02 +08:00
    @murmur 你说的这些都很有道理,但是却和你的问题无关
    原型链 - JavaScript/ES - JS 引擎,这是 3 个层面的东西,你说的所有问题,都存在于语言和引擎之上,因此去推导原型链本身丑陋是不合适的,JS 不是唯一的基于原型的语言,比如 LUA 就要好看不少,R 和 Perl 加上原型相关的包后也不丑
    lizhenda
        52
    lizhenda  
       2017-09-12 18:29:10 +08:00
    ```javascript
    // 刚好我之前项目想用写了个,供参考
    ```
    lizhenda
        53
    lizhenda  
       2017-09-12 18:30:22 +08:00
    /**
    * JavaScript Inheritance Class
    *
    * 实现了继承和定义对象,初始化自动调用 ctor() 构造函数,自带一个简单的 clone() 拷贝函数
    * 每个 Class 都带有一个唯一 ID : _pid,每个 new 出来的对象也带一个唯一 ID : _uid (可以用于相同 Class 的不同对象)
    *
    * @author : zhenda.li
    */

    /**
    * @name ClassManager
    */
    var ClassManager = {
    pid: (0 | (Math.random() * 998)), // 每个 Class 的唯一 ID
    uid: (0 | (Math.random() * 998)), // 每个 New 出来的对象唯一 ID

    getPID: function () {
    return this.pid++;
    },

    getUID: function () {
    return this.uid++;
    }
    };

    (function () {
    var fnTest = /\b_super\b/;

    this.Class = function () {
    };

    /**
    * Create a new Class that inherits from this Class
    * @static
    * @param {object} prop
    * @return {function}
    */
    Class.extend = function (prop) {
    var _super = this.prototype;
    var prototype = Object.create(_super);
    var desc = {writable: true, enumerable: false, configurable: true};

    function Class() {
    this._uid = ClassManager.getUID();
    if (this.ctor) {
    this.ctor.apply(this, arguments);
    }
    }

    desc.value = ClassManager.getPID();
    Object.defineProperty(prototype, '_pid', desc);

    for (var name in prop) {
    var isFunc = isFunction(prop[name]);
    var override = isFunction(_super[name]);
    var hasSuperCall = fnTest.test(prop[name]);

    if (isFunc && override && hasSuperCall) {
    desc.value = (function (name, fn) {
    return function () {
    var tmp = this._super;
    this._super = _super[name];
    var ret = fn.apply(this, arguments);
    this._super = tmp;
    return ret;
    };
    })(name, prop[name]);
    Object.defineProperty(prototype, name, desc);
    } else {
    prototype[name] = prop[name];
    }
    }

    Class.prototype = prototype;

    desc.value = Class;
    Object.defineProperty(Class.prototype, 'constructor', desc);

    /**
    * 快捷设置属性方法
    * e.g : obj.attr({ x : 1, y : 2 });
    */
    Class.prototype.attr = function (attrs) {
    for (var key in attrs) {
    if (!isUndefined(this[key]))
    this[key] = attrs[key];
    }
    };

    /**
    * 每个对象自带拷贝方法
    * @return Class
    */
    Class.prototype.clone = function () {
    var newObj = (this.constructor) ? new this.constructor : {};
    for (var name in this) {
    var copy = this[name];
    if ((typeof copy) === "Class") {
    newObj[name] = copy.clone();
    } else {
    newObj[name] = copy;
    }
    }
    return newObj;
    };

    Class.extend = arguments.callee;

    return Class;
    };
    })();
    lizhenda
        54
    lizhenda  
       2017-09-12 18:32:32 +08:00
    评论不能写 MD 么,至于用法,

    var Test = Class.extend({

    /**
    * 构造函数, 初始化
    */
    ctor: function () {
    this.func();
    },

    func: function () {

    }
    });
    lizhenda
        55
    lizhenda  
       2017-09-12 18:33:28 +08:00
    当然,最好的办法是用 es6,直接上 class
    azh7138m
        56
    azh7138m  
       2017-09-12 18:37:34 +08:00 via Android
    工厂模式怎么就不能用了。。。。阿里云国际的控制台字段校验那个部分就是工厂模式啊
    lijsh
        57
    lijsh  
       2017-09-12 18:45:32 +08:00
    littleshy
        58
    littleshy  
       2017-09-12 18:51:46 +08:00
    JS 是原型语言,不要用面向对象的思想去使用它。
    chegde
        59
    chegde  
       2017-09-12 19:35:53 +08:00 via iPhone
    TypeScript 大法好
    aleung
        60
    aleung  
       2017-09-12 20:13:31 +08:00 via Android
    不能说 js 不是 OO,js 是 prototype based OO
    aleung
        61
    aleung  
       2017-09-12 20:18:23 +08:00 via Android
    大家说面向对象的时候是不是只是在说 GOF 的设计模式呢?设计模式只是基于 OO 思想的一些使用场景,部分与语言特性相关。
    exoticknight
        62
    exoticknight  
       2017-09-12 20:19:49 +08:00
    请面向需求编程,不要为了编程而编程
    springmarker
        63
    springmarker  
       2017-09-12 20:27:36 +08:00
    用 js 写一个 jvm,然后在上面开发
    Pastsong
        64
    Pastsong  
       2017-09-12 20:33:41 +08:00
    原型链是非常吊的设计,不要用 Java 和 C++ 的思想写 JS
    libook
        65
    libook  
       2017-09-13 01:00:17 +08:00
    “想用 java 或者 C# 的思想去写 JS ”这注定会导致痛苦的体验。就像:
    “想用开火车的思想去开轮船”、
    “想用做馒头的思想做面包”……
    软件工程学中的面向对象编程思想是一种抽象思想,为了实现运用这种思想,Java 有 Java 的实现方式,C#有 C#的实现方式,JS 有 JS 的实现方式,为何不用 JS 自己的方式来实现这一思想呢?
    simo
        66
    simo  
       2017-09-13 08:57:01 +08:00
    理解设计思想,知道取舍;熟悉 js 语言特性,可自定义设计。
    其实 js 越来越适合 c#/java 等开发人员转入了。
    同时,js 语言特性的改变,逐渐的工程化,也增高入门门槛,可以筛掉很多前端混子
    rwecho
        67
    rwecho  
       2017-09-13 09:11:38 +08:00
    传统设计模式针对的是面向对象编程语言
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   871 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 20:46 · PVG 04:46 · LAX 12:46 · JFK 15:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.