首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >JS:关于继承的困惑

JS:关于继承的困惑
EN

Stack Overflow用户
提问于 2011-10-30 12:27:29
回答 4查看 531关注 0票数 6

通过C++、Java等语言,我熟悉OOP的概念。现在我正试着把JavaScript作为一种爱好来学习,主要是因为我对WebGL很感兴趣。但我在基于原型的继承方面遇到了麻烦。

假设我有一个基类,它接受构造函数中的参数。我需要延长这个期限。我这样做的方式如下所示。

代码语言:javascript
复制
function Base(n) {
    this._n = n;
}

Base.prototype.print = function() {
    console.log(this._n);
}

function Derived(n) {
    Base.call(this, n);
}

Derived.prototype = new Base;
Derived.prototype.constructor = Derived;

这就是我所理解的:一个Base对象作为Derived的原型。因此,Derived的所有实例都将继承来自这个Base对象的属性,例如print方法。当我调用new Derived(10)然后创建一个新对象时,函数Derived在这个新创建的对象的上下文中被调用,即this指向新创建的对象,函数Base从函数Derived调用,然后创建_n并分配值10。因此,如果创建5个Derived对象,它们都将拥有自己的_n属性。到目前为止还可以。

但我不喜欢这句话:

代码语言:javascript
复制
Derived.prototype = new Base;

函数Base需要一个参数,但这里没有传递任何内容。在这里传递参数是没有意义的,因为这个对象将充当Derived的原型。对于这个原型对象,我不需要任何_n值。但是,如果函数Base依赖于参数呢?比方说,Base加载一个资源,路径作为参数传递。那该怎么办呢?

总之,我的问题是:

  1. 如何处理原型对象中的数据成员(本例中为_n)?
  2. Derived.prototype = new Base;正在创建一个Base实例,这将始终保留在内存中(假设Derived是在全局空间中定义的)。如果Base类非常昂贵,而且我不想要额外的对象,该怎么办?
EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2011-10-30 12:53:56

但我不喜欢这句话:

Derived.prototype = new Base;

然后用

Derived.prototype = Object.create(Base.prototype);

请参阅Object.create只返回一个新对象,它的[[Prototype]]是您给它的第一个参数。

它基本上是说Derived是从Base继承的,但不要把它称为该死的构造函数!

如何处理原型对象中的数据成员(本例中为_n)?

当您的链接原型不调用构造函数时!我在JS OO第3部分上写了一篇关于这个的文章。

它基本上是说,当您创建对象时,您实例化和初始化。

代码语言:javascript
复制
// instantiate
var o = Object.create(Base.prototype);
// o now inherits all of Bases methods because o.[[Prototype]] === Base.prototype
// o also inherits the constructor function (Base.prototype.constructor === Base)
// initialize
o.constructor(10);

当然,现在new X两者都做了。这里有一个关于new做什么的透视图

代码语言:javascript
复制
var new = function (constructor, ...args) {
  var instance = Object.create(constructor.prototype);
  instance.constructor(...args);
  return instance;
}

如您所见,您不需要new,因为您不希望调用该构造函数(不需要初始化Derived.prototype)。

Derived.prototype = new;正在创建Base的一个实例,这将始终保留在内存中(假设派生是在全局空间中定义的)。如果基类非常昂贵,而且我不想要额外的对象,该怎么办?

Object.create的这种关注是无效的。实例化对象很便宜。它只生成一个新的对象,其内部[[Prototype]]属性是指向您传入的原型的指针。

唯一昂贵的是构造函数,而不调用构造函数。

次要免责声明:

Object.create是ES5,一些遗留浏览器(主要是IE8)不支持。然而,有一个可爱的东西叫做ES5-shim,它修复了这些浏览器,并使它们的行为像ES5一样。

票数 1
EN

Stack Overflow用户

发布于 2011-10-30 12:38:38

首先,非常好地理解JavaScript的原型继承。很明显你已经做好了作业。大多数来自Java或C++背景的人都会遇到困难,但你已经度过了最糟糕的时期。

函数Base需要一个参数,但这里没有传递任何内容。如何处理原型对象中的数据成员(本例中为_n)?

如果需要使用Base作为基,则需要将其设计为合理地接受零参数,或者在为Derived创建基本对象时使用参数调用它。这基本上是你唯一的两个选择。

Derived.prototype = new Base;正在创建一个Base实例,这将始终保留在内存中(假设Derived是在全局空间中定义的)。如果Base类非常昂贵,而且我不想要额外的对象,该怎么办?

它与Java类中的static数据完全相同:加载类加载数据。如果您打算使用Base作为基础,您可能希望设计它,这样它就不会加载一些它不需要的东西(也许可以通过与带参数版本不同的方式来处理零参数版本)。

而且,在JavaScript的“类”系统中通常看到的是最后一种方法(处理零参数构造的方式与-参数构造不同)。通常,您将看到只用于构造原始对象的实际构造函数,以及用于实际初始化实例的其他命名函数(initialize是原型使用的名称,我在执行我对样机机构的更换/修改时使用了)。因此,实际的构造函数不带参数,但随后将通过调用initialize函数(该函数反过来调用其基的initialize函数)来初始化实例。在大多数包装中,这都是在封面下为你处理的。

要使构造函数-vs-初始化机制在实践中发挥作用,需要一些复杂的管道,因为它需要“超级调用”(对基函数版本的调用),而且超级调用在JavaScript中是很尴尬的。(超级调用实际上就是链接文章的主要内容,但探索有效的方法也包括创建/更新整个继承系统。我确实需要更新这篇文章,这样它就不会使用基于类的术语;它仍然是原型,它只是提供了我刚才提到的管道。)

由于外部资源可能消失/被移动/等等,而且堆栈溢出通常是独立的,所以上文所列文章中的迭代的最终结果如下

代码语言:javascript
复制
// Take IV: Explicitly handle mixins, provide a mixin for calling super when
// working with anonymous functions.
// Inspired by Prototype's Class class (http://prototypejs.org)
// Copyright (C) 2009-2010 by T.J. Crowder
// Licensed under the Creative Commons Attribution License 2.0 (UK)
// http://creativecommons.org/licenses/by/2.0/uk/
var Helper = (function(){
    var toStringProblematic,    // true if 'toString' may be missing from for..in
        valueOfProblematic;     // true if 'valueOf' may be missing from for..in

    // IE doesn't enumerate toString or valueOf; detect that (once) and
    // remember so makeClass can deal with it. We do this with an anonymous
    // function we don't keep a reference to to minimize what we keep
    // around when we're done.
    (function(){
        var name;

        toStringProblematic = valueOfProblematic = true;
        for (name in {toString: true, valueOf: true}) {
            if (name == 'toString') {
                toStringProblematic = false;
            }
            if (name == 'valueOf') {
                valueOfProblematic = false;
            }
        }
    })();

    // This function is used to create the prototype object for our generated
    // constructors if the class has a parent class. See makeConstructor for details.
    function protoCtor() { }

    // Build and return a constructor; we do this with a separate function
    // to minimize what the new constructor (a closure) closes over.
    function makeConstructor(base) {

        // Here's our basic constructor function (each class gets its own, a
        // new one of these is created every time makeConstructor is called).
        function ctor() {
            // Call the initialize method
            this.initialize.apply(this, arguments);
            }

        // If there's a base class, hook it up. We go indirectly through `protoCtor`
        // rather than simply doing "new base()" because calling `base` will call the base
        // class's `initialize` function, which we don't want to execute. We just want the
        // prototype.
        if (base) {
            protoCtor.prototype = base.prototype;
            ctor.prototype = new protoCtor();
            protoCtor.prototype = {};   // Don't leave a dangling reference
        }

        // Set the prototype's constructor property so `this.constructor` resolves
        // correctly
        ctor.prototype.constructor = ctor;

        // Flag up that this is a constructor (for mixin support)
        ctor._isConstructor = true;

        // Return the newly-constructed constructor
        return ctor;
    }

    // This function is used when a class doesn't have its own initialize
    // function; since it does nothing and can only appear on base classes,
    // all instances can share it.
    function defaultInitialize() {
    }

    // Get the names in a specification object, allowing for toString and
    // valueOf issues
    function getNames(members) {
        var names,      // The names of the properties in 'members'
            name,       // Each name
            nameIndex;  // Index into 'names'

        names = [];
        nameIndex = 0;
        for (name in members) {
            names[nameIndex++] = name;
        }
        if (toStringProblematic && typeof members.toString != 'undefined') {
            names[nameIndex++] = 'toString';
        }
        if (valueOfProblematic && typeof members.valueOf != 'undefined') {
            names[nameIndex++] = 'valueOf';
        }
        return names;
    }

    // makeClass: Our public "make a class" function.
    // Arguments:
    // - base: An optional constructor for the base class.
    // - ...:  One or more specification objects containing properties to
    //         put on our class as members; or functions that return
    //         specification objects. If a property is defined by more than one
    //         specification object, the last in the list wins.
    // Returns:
    //     A constructor function for instances of the class.
    //
    // Typical use will be just one specification object, but allow for more
    // in case the author is drawing members from multiple locations.
    function makeClass() {
        var base,       // Our base class (constructor function), if any
            argsIndex,  // Index of first unused argument in 'arguments'
            ctor,       // The constructor function we create and return
            members,    // Each members specification object
            names,      // The names of the properties in 'members'
            nameIndex,  // Index into 'names'
            name,       // Each name in 'names'
            value,      // The value for each name
            baseValue;  // The base class's value for the name

        // We use this index to keep track of the arguments we've consumed
        argsIndex = 0;

        // Do we have a base?
        if (typeof arguments[argsIndex] == 'function' &&
            arguments[argsIndex]._isConstructor) {
            // Yes
            base = arguments[argsIndex++];
        }

        // Get our constructor; this will hook up the base class's prototype
        // if there's a base class, and mark the new constructor as a constructor
        ctor = makeConstructor(base);

        // Assign the members from the specification object(s) to the prototype
        // Again, typically there's only spec object, but allow for more
        while (argsIndex < arguments.length) {
            // Get this specification object
            members = arguments[argsIndex++];
            if (typeof members == 'function') {
                members = members();
            }

            // Get all of its names
            names = getNames(members);

            // Copy the members
            for (nameIndex = names.length - 1; nameIndex >= 0; --nameIndex) {
                name = names[nameIndex];
                value = members[name];
                if (base && typeof value == 'function' && !value._isMixinFunction) {
                    baseValue = base.prototype[name];
                    if (typeof baseValue == 'function') {
                            value.$super = baseValue;
                    }
                }
                ctor.prototype[name] = value;
            }
        }

        // If there's no initialize function, provide one
        if (!('initialize' in ctor.prototype)) {
            // Note that this can only happen in base classes; in a derived
            // class, the check above will find the base class's version if the
            // subclass didn't define one.
            ctor.prototype.initialize = defaultInitialize;
        }

        // Return the constructor
        return ctor;
    }

    // makeMixin: Our public "make a mixin" function.
    // Arguments:
    // - ...:  One or more specification objects containing properties to
    //         put on our class as members; or functions that return
    //         specification objects. If a property is defined by more than one
    //         specification object, the last in the list wins.
    // Returns:
    //     A specification object containing all of the members, flagged as
    //     mixin members.
    function makeMixin() {
        var rv,         // Our return value
            argsIndex,  // Index of first unused argument in 'arguments'
            members,    // Each members specification object
            names,      // The names in each 'members'
            value;      // Each value as we copy it

        // Set up our return object
        rv = {};

        // Loop through the args (usually just one, but...)
        argsIndex = 0;
        while (argsIndex < arguments.length) {
            // Get this members specification object
            members = arguments[argsIndex++];
            if (typeof members == 'function') {
                members = members();
            }

            // Get its names
            names = getNames(members);

            // Copy its members, marking them as we go
            for (nameIndex = names.length - 1; nameIndex >= 0; --nameIndex) {
                name = names[nameIndex];
                value = members[name];
                if (typeof value == 'function') {
                    value._isMixinFunction = true;
                }
                rv[name] = value;
            }
        }

        // Return the consolidated, marked specification object
        return rv;
    }

    // Return our public members
    return {
        makeClass: makeClass,
        makeMixin: makeMixin
        };
})();

用法:

代码语言:javascript
复制
var Parent = Helper.makeClass(function(){
    function hierarchy() {
        return "P";
    }
    return {hierarchy: hierarchy};
});
var Child = Helper.makeClass(Parent, function(){
    function hierarchy() {
        return hierarchy.$super.call(this) + " < C";
    }
    return {hierarchy: hierarchy};
});
var GrandChild = Helper.makeClass(Child, function(){
    function hierarchy() {
        return hierarchy.$super.call(this) + " < GC";
    }
    return {hierarchy: hierarchy};
});
var gc = new GrandChild();
alert(gc.hierarchy()); // Alerts "P < C < GC"

如果dn不喜欢超级调用的funcname.$super.call(...)表示法,那么可以使用更短/更清晰的版本(但要付出运行时的代价):

代码语言:javascript
复制
// Define our CallSuper mixin
Helper.CallSuperMixin = makeMixin(function() {
    function callSuper(ref) {
        var f,          // The function to call
            args,       // Arguments to pass it, if we have any
            len,        // Length of args to pass
            srcIndex,   // When copying, the index into 'arguments'
            destIndex,  // When copying args, the index into 'args'
            rv;         // Our return value

        // Get the function to call: If they pass in a function, it's the
        // subclass's version so look on $super; otherwise, they've passed
        // in 'arguments' and it's on arguments.callee.$super.
        f = typeof ref == 'function' ? ref.$super : ref.callee.$super;

        // Only proceed if we have 'f'
        if (f) {
            // If there are no args to pass on, use Function#call
            if (arguments.length == 1) {
                rv = f.call(this);
            } else {
                // We have args to pass on, build them up.
                // Note that doing this ourselves is more efficient on most
                // implementations than applying Array.prototype.slice to
                // 'arguments', even though it's built in; the call to it
                // is expensive (dramatically, on some platforms).
                len = arguments.length - 1;
                args = new Array(len);
                srcIndex = 1;
                destIndex = 0;
                while (destIndex < len) {
                    args[destIndex++] = arguments[srcIndex++];
                }

                // Use Function#apply
                rv = f.apply(this, args);
            }
        }

        // Done
        return rv;    // Will be undefined if there was no 'f' to call
    }

    return {callSuper: callSuper};
});

再说一遍,我真的需要更新术语,这样它就不是基于类的。(看看ECMAScript5如何让我们做一些稍微不同的事情,因为它增加了一些有用的东西,比如对原型的直接控制。)

票数 3
EN

Stack Overflow用户

发布于 2013-02-15 09:11:23

@ 2. Derived.prototype = new;正在创建Base的一个实例,这将始终保留在内存中(假设派生是在全局空间中定义的)。如果基类非常昂贵,而且我不想要额外的对象,该怎么办?

是啊。这个例子是一种继承学习风格。要在应用程序中使用,请尝试:

代码语言:javascript
复制
    function F() {}
    F.prototype = Base.prototype; // Linking to Base's prototype

    Derived.prototype = new F(); // The least memory-consumption object.
    Derived.prototype.constructor = Base; // Constructor reference correction

@ 1.如何处理原型对象中的数据成员(本例中为_n)?

使用上面的原型链,我们不会创建任何Base实例。因此,这个问题是无效的。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7944880

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档