【问题标题】:extending base instance with prototype?用原型扩展基本实例?
【发布时间】:2013-03-26 07:17:20
【问题描述】:

情况就是这样;我有一个名为Car 的基类(一个函数)和一些特定的子类,如truckbuspartybus

我的脚本让用户创建一个car,然后由服务器随机更新该车的确切内容(无法预测它将是什么)。当时我已经有一个带有所有者/里程信息的car 实例,但需要用truck 扩展创建的实例,以便覆盖getTopSpeed 方法,我的carInstance 变为myExtendedInstance instanceof truck===true

基类和子类函数/扩展的创建不是问题。这是代码;

function Car(){
}
Car.prototype.getTopSpeed=function(){
    return 100;
}

function Truck(){
    Truck.uber.constructor.call(this);
}
Truck.prototype.getTopSpeed=function(){
    return 20;
}
inherit.call(this, Truck, Car);

这是 Dustin Diaz/Ross Harmes 的继承函数

inherit=function _inherit(subClass, superClass){
    var F=function(){};
    F.prototype=superClass.prototype;
    subClass.prototype=new F();
    subClass.prototype.constructor=subClass;

    subClass.uber=superClass.prototype;
    if (superClass.prototype.constructor===Object.prototype.constructor){
        superClass.prototype.constructor=superClass;
    }
}

问题是如何从一个 已经创建的 baseClass 实例 到一个工作子类,并以最佳方式返回到 baseClass 和另一个子类,而不需要创建一个新的子类实例并销毁baseClass 的实例。

我可以打电话给myExtendedInstance=inherit(carInstance, Truck)吗?这不会造成内存泄漏或模糊我的原型链吗?如果没有,如何再次破坏原型链以返回基本 carInstance?

我想我脑子里有个结。任何帮助将非常感激! :)

【问题讨论】:

  • 编辑并取消删除我的答案,我第一次误解了这个问题。

标签: javascript prototypal-inheritance


【解决方案1】:

我可以打电话给myExtendedInstance=inherit(carInstance, Truck)吗?这不会造成内存泄漏或模糊我的原型链吗?

没有。它不会造成内存泄漏,但 inherit 只期望 构造函数(类)作为参数而不是实例。

如果没有,我怎样才能再次破坏原型链以返回基本的 carInstance?

您不能修改现有对象的原型链 - 除非您使用非标准的__proto__ property(例如 IE 不支持)。

相反,您需要创建 myExtendedInstance = new Truck;,然后从旧的 carInstance 复制所有者/里程属性。

【讨论】:

  • 感谢您抽出宝贵时间回答!这是我所期望的。因此,我需要一种方法来用另一个实例替换一个实例。它比仅仅复制属性要复杂一些(无论如何我已经将其拆分为“模型”)->问题更多是关于更新其他结构持有的引用。这就是为什么我想用另一个原型扩展一个实例。 Ohwell..我想我会给自己煮点咖啡:)
【解决方案2】:

你可以让它工作,但我认为这种方法(更改对象的类)很复杂。

我会创建一个 Car 类和另一个类 CarSettings 或类似的东西。

每个 Car 对象都将包含一个 CarSettings 对象,可以对其进行修改或完全替换。

【讨论】:

    【解决方案3】:

    我看到这样的继承助手的问题是

    继承的类是使用new 创建的,并在没有调用constructor 论据subClass.prototype=new F(); 在继承过程中,如果构造函数依赖于特定的参数,那么(在我看来)很容易破坏更复杂的构造函数。

    所以为了解决这个问题并能够解决您的问题,我目前使用这样的东西:

    对不起,我误解了最初的问题,并修改了我使用的助手以支持扩展和缩减。

    var base = (function baseConstructor() {
        var obj = {
            create: function instantiation(extend) {
                var instance, args = [].slice.call(arguments);
                if (this === base) {
                    throw new SyntaxError("You can't create instances of base");
                } else if (!this.hasOwnProperty("initclosure")) {
                    throw new SyntaxError("Cannot create instances without an constructor");
                } else if (this.singleton && this.instances.length !== 0) {
                    throw new SyntaxError("You can't create more than one Instance of a Singleton Class");
                } else {
                    if (!extend._class || !extend._class.isPrototypeOf(this)) {
                        instance = Object.create(this.pub);
                        this.instances.push(instance);
                    } else {
                        args = args.slice(1);
                        extend._class.remove(extend);
                        instance = this.extend(extend);
                    }
                    this.init.apply(instance, args);
                    instance.onCreate();
                    return instance;
                }
            },
            extend: function (instance) {
                if (!instance._class.isPrototypeOf(this)) {
                    return;
                }
                var extended = Object.create(this.pub);
                for (var propInst in instance) {
                    if (instance.hasOwnProperty(propInst)) {
                        extended[propInst] = instance[propInst];
                    }
                }
                instance._class.remove(instance);
                this.instances.push(extended);
                return extended;
            },
            reduce: function (instance) {
                if (!instance.instanceOf(this)) {
                    return;
                }
                var reduced = Object.create(this.pub);
                for (var propRed in instance) {
                    if (instance.hasOwnProperty(propRed)) {
                        reduced[propRed] = instance[propRed];
                    }
                }
                instance._class.remove(instance);
                this.instances.push(reduced);
                return reduced;
            },
            remove: function (instance) {
                if (instance.instanceOf(this)) {
                    var removed = this.instances.splice(this.instances.indexOf(instance), 1)[0];
                    instance.onRemove();
                    return removed;
                }
            },
            inherit: function inheritation(specsOpt) {
                specsOpt = specsOpt || {};
                applyDefaults(specsOpt, {
                    singleton: false,
                    anonymous: false
                });
                var sub = Object.create(this);
                sub.pub = Object.create(this.pub);
                sub.pub.proto = this.pub;
                sub.pub._class = sub;
                sub.instances = [];
                sub.anonymous = specsOpt.anonymous;
                sub.sup = this;
                if (specsOpt.singleton) {
                    sub.singleton = specsOpt.singleton;
                    sub.getSingleton = getSingleton;
                    protect.call(sub, {
                        singleton: {
                            writable: false,
                            configurable: false,
                            enumerable: false
                        },
                        getSingleton: {
                            writable: false,
                            configurable: false
                        }
                    });
                }
                return sub;
            },
            initclosure: function Base() {},
            instances: [],
            pub: {
                instanceOf: function (obj) {
                    if (!obj || !obj.pub) {
                        return this.className;
                    }
                    return obj.pub.isPrototypeOf(this);
                },
                onRemove: function () {},
                onCreate: function () {},
                "_class": obj
            }
        };
        /* Helper Functions. --- Use function expressions instead of declarations to get JSHint/Lint strict mode violations
         *
         * TODO: Maybe add an obj.helper Propertie with usefull functions
         */
        var applyDefaults = function (target, obj) {
            for (var prop in obj) {
                if (obj.hasOwnProperty(prop)) {
                    target[prop] = target[prop] || obj[prop];
                }
            }
        };
        var getSingleton = function () { //To get past the strict violation
            return this.instances[0];
        };
        var protect = function (props, desc) { //Maybe change it a little
            for (var prop in props) {
                if (props.hasOwnProperty) {
                    Object.defineProperty(this, prop, props[prop] || desc);
                }
            }
            return this;
        };
        /*  End Helpers
         * 
         *  Protecting
         */
        Object.defineProperty(obj, "init", {
            set: function (fn) {
                if (typeof fn !== "function") {
                    throw new Error("Expected typeof init to be 'function'");
                } else if (Boolean(fn.name) === this.anonymous) {
                    try {
                        throw new Error("Expected the constructor " + (!this.anonymous ? "not " : "") + "to be Anonymous");
                    } catch (e) {
                        console.error(e.stack);
                    }
                }
                if (!this.hasOwnProperty("initclosure")) {
                    this.initclosure = fn;
                    this.pub.constructor = this.init;
                    this.pub.className = fn.name;
                    protect.call(this.pub, {
                        constructor: false,
                        className: false
                    }, {
                        enumerable: false
                    });
                }
            },
            get: function () {
                var that = this;
                var init = function init() {
                    if (that.pub.isPrototypeOf(this)) {
                        that.initclosure.apply(this, arguments);
                    } else {
                        throw new Error("init can't be called directly");
                    }
                };
                init.toString = function () {
                    return that.initclosure.toString();
                };
                return init;
            }
        });
        obj.toString = function () {
            return "[class " + (this.initclosure.name || "Class") + "]";
        };
        obj.pub.toString = function () {
            return "[instance " + (this.className || "Anonymous") + "]";
        };
        protect.call(obj, {
            create: false,
            inherit: false,
            toString: false,
            onRemove: {
                enumerable: false
            },
            onCreate: {
                enumerable: false
            },
            initclosure: {
                enumerable: false
            }
        }, {
            writable: false,
            configurable: false
        });
        protect.call(obj.pub, {
            instanceOf: false,
            toString: false,
            "_class": {
                enumerable: false
            }
        }, {
            writable: false,
            configurable: false,
            enumerable: false
        });
        return obj;
    })();
    

    注意:这依赖于 ECMAScript 5 中引入的 Object.create,因此旧版浏览器不支持


    给定继承帮助器,让我们创建一些“类”

    var Car = base.inherit();
    Car.pub.getTopSpeed = function () {
        return 100;
    };
    Car.init = function ClassCar(model) {
        this.model = model || this.model || "";
    };
    Car.pub.type = "car";
    

    现在我们有了一个可继承的超类,让Truck 继承自Car

    var Truck = Car.inherit();
    Truck.pub.getTopSpeed = function () {
        return 20;
    };
    Truck.pub.type = "truck";
    Truck.init = function ClassTruck(model, color) {
        Truck.sup.init.apply(this, [].slice.call(arguments));
        this.color = color;
    };
    

    然后让我们创建一个Car的实例

    var myCar = Car.create("Porsche");
    console.log(myCar.getTopSpeed(), myCar.className); //100, ClassCar
    

    现在,如果我对您的理解正确,您想扩展 Car 的现有实例 myCar 以成为 Truck 的实例。
    如果是这样,让我们​​这样做

    var myExtendedTruck = Truck.extend(myCar);
    console.log(myExtendedTruck.getTopSpeed(), myExtendedTruck.className); //20, ClassTruck
    console.log(myExtendedTruck.instanceOf(Truck)); //true
    

    这只是设置扩展原型链将实例变量复制到新的卡车实例。所以Car 实例现在是Truck 实例

    或者如果你也想使用构造函数。 create 在传递超类的实例时同样有效。
    然后它被扩展和初始化。

    var myConstructedExtendedTruck = Truck.create(myCar, myCar.model, "Yellow");
    console.log(myConstructedExtendedTruck.getTopSpeed(), myConstructedExtendedTruck.model, myConstructedExtendedTruck.color); //20 , Porsche , Yellow
    

    现在我们有一个扩展 Car 实例,它现在是 Truck 的实例,并由 Trucks 构造函数正确构造。

    现在,如果我做对了,您也希望能够回到超类实例。

    var myReducedCar = Car.reduce(myExtendedTruck);
    console.log(myReducedCar.getTopSpeed(), myReducedCar.className); //100, ClassCar
    console.log(myReducedCar.instanceOf(Truck)); //false
    

    这里有一个Example on JSBin 来摆弄一下

    编辑说明:修复代码以正确移动 Classes instances 数组中的实例

    【讨论】:

    • 感谢您的扩展回答!是否有此模式名称,或者您是否有更多资源提供更深入的分析?
    • 不客气 =) 嗯,我真的不知道这样的东西是否有模式名称,我刚刚写了这个,没有考虑特定的模式,所以恐怕只有我可以为您提供的是回答有关代码的任何其他问题。干杯
    猜你喜欢
    • 2015-07-14
    • 2021-02-24
    • 2013-05-24
    • 1970-01-01
    • 1970-01-01
    • 2015-11-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多