我通过C ++,Java等语言熟悉OOP概念。现在我正在尝试学习JavaScript作为一种爱好,主要是因为对WebGL的兴趣。但我在基于原型的继承方面遇到了麻烦。
假设我有一个基类,它接受构造函数中的参数。我需要扩展这一点。我这样做的方式如下所示。
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
指向新创建的对象,函数{{1}从函数Base
调用,然后创建Derived
并赋值10.因此,如果我创建5个_n
个对象,则所有对象都将拥有自己的Derived
属性。到目前为止,这没关系。
但我不喜欢这句话:
_n
函数Derived.prototype = new Base;
期待一个参数,但我在这里没有传递任何东西。没有必要在此处传递参数,因为此对象将充当Base
的原型。对于这个原型对象,我不需要Derived
的任何值。但是如果函数_n
取决于参数呢?比如,Base
加载资源,路径作为参数传递。该怎么办?
总结一下,我的问题是:
Base
)? _n
正在创建Derived.prototype = new Base;
的实例,这将始终保留在内存中(假设在全局空间中定义了Base
)。如果Derived
类非常昂贵并且我不想要额外的对象,该怎么办? 答案 0 :(得分:3)
首先,我非常了解JavaScript的原型继承。你已经完成了你的功课。大多数来自Java或C ++背景的人往往都很挣扎,但是你已经过了最糟糕的事情。
函数
Base
期待一个参数,但我在这里没有传递任何东西。 如何处理原型对象中的数据成员(本例中为_n
)?
如果需要使用Base
作为基础,则需要将其设计为合理地接受零参数,或者在为Derived
创建基础对象时需要使用参数调用它。这些基本上只是你的两个选择。
Derived.prototype = new Base;
正在创建Base
的实例,这将始终保留在内存中(假设在全局空间中定义了Derived
)。如果Base
类非常昂贵并且我不想要额外的对象,该怎么办?
它与Java类中的static
数据相同:加载类会加载该数据。如果您要使用Base
作为基础,那么您需要设计它,以便不加载一堆它不需要的东西(可能是通过处理零参数版本与with-argument版本不同。)
这是你通常在JavaScript的“类”系统中看到的最后一种方法(处理零参数构造而不是带参数构造)。通常,您会看到实际构造函数仅用于构造原始对象,而其他一些用于实际初始化实例的命名函数(initialize
是Prototype使用的名称,而我在执行my replacement/revision of Prototype's mechanism时使用的名称)。所以实际的构造函数不带参数,但是你可以通过调用initialize
函数来初始化一个实例(后者又调用它的基类initialize
函数)。在大多数包装中,这是为你处理的。
使构造函数与初始化程序机制在实践中起作用需要一些棘手的管道,因为它需要“超级调用”(调用函数的基本版本),并且超级调用在JavaScript中很尴尬。 (那 - 超级调用 - 实际上是链接文章的主要内容,但是探索一种有效的方法也涉及创建/更新整个继承系统。我真的需要更新那篇文章所以它没有'使用基于类的术语;它仍然是典型的,它只是提供我正在谈论的管道。)
因为外部资源可能会消失/被移动/等等而Stack Overflow主要是独立的,这里是the article linked above中呈现的迭代的最终结果:
// 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
};
})();
用法:
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"
如果您不喜欢超级调用的funcname.$super.call(...)
符号,这里有一个混合使用,可让您使用更短/更清晰的版本(但运行成本):
// 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如何让我们做的事情略有不同,因为它增加了一些有用的东西,比如直接控制原型。)
答案 1 :(得分:3)
@@@ 2. Derived.prototype = new Base;正在创建Base的实例 这将始终保留在内存中(假设Derived定义于 全球空间)。如果基类成本很高而我不这样做该怎么办 想要一个额外的对象?
是的。这个例子是继承学习风格。要在您的应用程序中使用,请尝试:
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实例。所以,这个问题无效。
答案 2 :(得分:1)
但我不喜欢这句话:
Derived.prototype = new Base;
然后用
替换它 Derived.prototype = Object.create(Base.prototype);
请参阅Object.create
只返回一个新对象,其[[Prototype]]
是您提供的第一个参数。
基本上说Derived
继承自Base
,但不要称之为该死的构造函数!
如何处理原型对象中的数据成员(本例中为_n)?
当您的链接原型不调用构造函数时!我写了一篇关于此事的JS OO part 3文章。
它基本上表示在创建对象时进行实例化和初始化。
// 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
同时做到了。以下是新功能的概述
var new = function (constructor, ...args) {
var instance = Object.create(constructor.prototype);
instance.constructor(...args);
return instance;
}
正如您所看到的,您不希望new
,因为您不希望调用该构造函数(您不需要初始化Derived.prototype)。
Derived.prototype = new Base;正在创建Base的实例,这将始终保留在内存中(假设Derived在全局空间中定义)。如果Base类非常昂贵并且我不想要额外的对象该怎么办?
对Object.create
的这种担忧是无效的。实例化一个对象很便宜。它只是生成一个新的对象,其内部[[Prototype]]
属性是指向您传入的原型的指针。
唯一可能很昂贵的是构造函数,而不是调用构造函数。
次要免责声明:
Object.create
是ES5,一些旧版浏览器(主要是IE8)不支持。然而,有一个名为ES5-shim的可爱东西修复了这些浏览器并使它们像ES5一样。
答案 3 :(得分:-1)
JavaScript与其他“面向对象”语言完全不同,请先尝试忘记您对其他语言的了解,然后了解JavaScript的工作原理。
Douglas Crockford在JavaScript: The World's Most Misunderstood Programming Language上可以找到一个很好的介绍,他还描述了继承在JS here中是如何工作的。
编辑:试着真正回答你的问题: 问题1,我根本不明白...抱歉。 问题2:JavaScript中的经典继承相当丑陋,我也从未发现它的需要。 Crockford所说的“寄生遗传”(第二个环节)我认为解决这个问题。这里“父”对象在构造函数的范围内调用。