使用 .prototype 和 __proto__ 创建子类

7

我最近通过编写一些gnome shell扩展来学习javascript,因此我的javascript理解是由我在gnome-shell javascript源代码中观察到的示例所塑造的。我感觉我可能对类的理解不正确,只是想要一些澄清。

我已经写了一些自己的子类,在每种情况下,我都是通过遵循gnome-shell javascript源代码中的类似代码来定义它们的:

Subclass = function() {
    this._init.apply(this,arguments);
}
Subclass.prototype = {
    __proto__: Superclass.prototype,
    _init: function() {
        Superclass.prototype._init.call(this);
    },
    // add other methods of Subclass here.
}

到目前为止,我认为这是创建一个基本上是Superclass加上额外内容的类Subclass的标准方式。我假设每个对象都有一个_init方法。
最近我尝试使用相同的方法来创建一个Clutter.Actor的子类(重要的是它不是GNOME-shell定义的类),并意识到上述对象的子类化方式不是标准方式。首先,并不是我所想象的每个类都有一个_init函数;这只是GNOME-shell在他们的javascript类中所做的事情。
因此,我的问题是:
1. 是否有关于创建子类的上述方法的任何文档?我看过的所有教程都说要设置Subclass.prototype = new Superclass(),而不是采用Subclass.prototype = { __proto__:Superclass.prototype, define_prototype_methods_here }方法,但我的想法是,如果gnome-shell一直使用这种方法,肯定有某种方法? 2. 如果我想尽可能接近定义类的上述方式(只是为了保留一些与我编写扩展程序的GNOME-shell代码相似的代码),我应该用什么替换Superclass.prototype._init.call(this)在Subclass._init中,以确保Subclass.prototype获得Superclass的所有方法/属性(然后我在Subclass.prototype的定义中添加它们),如果Superclass没有_init函数(即它是否有一些等效的构造函数我可以调用)?
我对此非常困惑,所以请原谅我如果我的问题不太合理;这将是因为我对此的误解和困惑!

这并不是真正的答案,但我强烈推荐这个网站来提升你的JS知识:http://yuiblog.com/crockford/ - Ja͢ck
3个回答

10
正如之前所说,不要使用__proto__(现在已经在浏览器中为JavaScript标准化了。但仍然不要使用它。) 但是,请注意:
Subclass.prototype = new Superclass(); // Don't do this

这也不是一个很好的方法。如果Superclass需要参数怎么办?

你有更好的选择。

ES2015及以上版本

class会为您处理所有的管道工作;完整的示例:

class Superclass {
    constructor(superProperty) {
        this.superProperty = superProperty;
    }
    method() {
        console.log("Superclass's method says: " + this.superProperty);
    }
}
class Subclass extends Superclass {
    constructor(superProperty, subProperty) {
        super(superProperty);
        this.subProperty = subProperty;
    }
    method() {
        super.method(); // Optional, do it if you want super's logic done
        console.log("Subclass's method says: " + this.subProperty);
    }
}

let o = new Subclass("foo", "bar");
console.log("superProperty", o.superProperty);
console.log("subProperty", o.subProperty);
console.log("method():");
o.method();

ES5及更早版本

仅让Subclass.prototypeSuperclass.prototype继承。例如,可以使用ES5的Object.create实现:

Subclass.prototype = Object.create(Superclass.prototype);
Subclass.prototype.constructor = Subclass;

然后在Subclass中,您使用this来调用Superclass,以便它有机会初始化:

function Subclass() {
    Superclass.call(this); // Pass along any args needed
}

完整示例:

function Superclass(superProperty) {
    this.superProperty = superProperty;
}
Superclass.prototype.method = function() {
    console.log("Superclass's method says: " + this.superProperty);
};
function Subclass(superProperty, subProperty) {
    Superclass.call(this, superProperty);
    this.subProperty = subProperty;
}
Subclass.prototype = Object.create(Superclass.prototype);
Subclass.prototype.constructor = Subclass;

Subclass.prototype.method = function() {
    Superclass.prototype.method.call(this); // Optional, do it if you want super's logic done
    console.log("Subclass's method says: " + this.subProperty);
};

var o = new Subclass("foo", "bar");
console.log("superProperty", o.superProperty);
console.log("subProperty", o.subProperty);
console.log("method():");
o.method();

ES3及其之前版本

ES3及其之前版本没有Object.create,但是您可以很容易地编写一个函数来为您设置原型,方法如下:

function objectCreate(proto) {
    var ctor = function() { };
    ctor.prototype = proto;
    return new ctor;
}

(注意:您可以通过创建一个仅接受一个参数的对象来半补 Object.create,但是不能补全多个参数版本的Object.create,因此如果页面上的其他代码也使用了Object.create,那么这样做会给它们带来错误的想法。)
然后您可以像我们ES5示例中所做的一样:
完整示例:

function objectCreate(proto) {
    var ctor = function() { };
    ctor.prototype = proto;
    return new ctor;
}

function Superclass(superProperty) {
    this.superProperty = superProperty;
}
Superclass.prototype.method = function() {
    console.log("Superclass's method says: " + this.superProperty);
};
function Subclass(superProperty, subProperty) {
    Superclass.call(this, superProperty);
    this.subProperty = subProperty;
}
Subclass.prototype = objectCreate(Superclass.prototype);
Subclass.prototype.constructor = Subclass;

Subclass.prototype.method = function() {
    Superclass.prototype.method.call(this); // Optional, do it if you want super's logic done
    console.log("Subclass's method says: " + this.subProperty);
};

var o = new Subclass("foo", "bar");
console.log("superProperty", o.superProperty);
console.log("subProperty", o.subProperty);
console.log("method():");
o.method();


好的,我明白你的意思。这是否意味着我必须定义inherits函数来创建子类(/使用Tmp进行调整以设置原型)?(__proto__不是标准并不会影响我,因为我的代码仅用于基于Mozilla JavaScript引擎的gnome javascript,它从未打算用于浏览器)。 - mathematical.coffee
假设这是一种更清晰的方式...从长远来看,我认为它会得到回报,而且你可以轻松地将这种模式转移到其他环境中。那么,为什么不学习/使用一件可以应用于任何地方的东西呢?;) 你并不局限于使用__proto__,但我建议不要使用它。这基本上与使用Object.create相同,后者是更好的方式(如果环境支持ES5)。 - Felix Kling
谢谢。我将学习一般情况下的“更清晰的方法”。鉴于我编写的代码无论如何都不会在GNOME JavaScript之外工作(因为其他代码特性支持__proto__),如果我确实想坚持使用__proto__,我该如何调用超类构造函数?在Subclass()中执行this._init.apply(this,arguments),并在Subclass.prototype._init中执行__proto__: Superclass.prototype, _init: function() { Superclass.apply(this,arguments); // rest of initialisation } - mathematical.coffee
是的,你可以这样做。但是没有必要再添加一个 _init 方法。函数本身就已经是构造函数了。不要让它变得太复杂 ;) - Felix Kling
好的,我明白了。谢谢!(我只是为了符合GNOME Shell编码风格而添加_init方法 - 所有GNOME Shell代码都应该具有相同的“外观”,因为我正在编写一个GNOME Shell扩展程序,而且所有的GNOME Shell都有_init方法,所以我感觉也得加一个)。 - mathematical.coffee

1
尽管在Web浏览器中使用时,__proto__现已被标准化为JavaScript的必需扩展,但在设置继承层次结构时没有使用它的理由。

相反,使用Object.create(一个ES5函数,在这里我们关键部分的目的是可以模拟,如果你真的需要支持过时的浏览器)。

这里有一个例子:

var BaseClass = function() {
};

var SubClass = function() {
    // Important that SubClass give BaseClass a chance to init here
    BaseClass.call(this/*, args, if, necessary, here*/);

    // ...
};

// Make `SubClass`'s `prototype` an object that inherits from
// `BaseClass`'s `prototype`:
SubClass.prototype = Object.create(BaseClass.prototype);

// Fix up the `constructor` property
SubClass.prototype.constructor = SubClass;

就这样。

如果BaseClass的构造函数需要一个参数,你可以在SubClass中传递这个参数:

下面是一个示例:

var BaseClass = function(arg) {
    this.prop = arg;
};

var SubClass = function(baseArg) {   // Can accept it, or provide a
    BaseClass.call(this, baseArg);   // hardcoded one here

    // ...
};

SubClass.prototype = Object.create(BaseClass.prototype);
SubClass.prototype.constructor = SubClass;

当然,从ES2015(也称为“ES6”)开始,您可以直接使用(如果需要,可以进行转译)。
class BaseClass {
    constructor(arg) {
        this.prop = arg;
    }
}

class SubClass extends BaseClass {
    constructor(baseArg) {
        super(baseArg);
    }
}

0

__proto__在您的代码中不应该被使用,因为它被JavaScript引擎实现用于指向对象类定义原型。参考:MDN:__proto__

1>

 Subclass.prototype = new Superclass();

继承超类的正确方法是什么。

2> 如果您想调用子类上的_init方法,该方法实际上将执行Superclass _init方法,则不要在SubClass类中定义它。当您尝试访问subClass(SubClass实例对象)上的_init方法时,JS引擎会尝试在当前obj上查找它,如果未找到该方法,则会在SubClass原型链(即SuperClass)中搜索

如果您想在子类方法中调用超类方法,请使用

Superclass.prototype._init.call(this)

在当前对象范围内执行super函数。这样可以在SubClass方法中调用super方法。


注意 - 我没有在任何浏览器中运行代码。对于第2点,我知道如何调用父类的方法。我的问题是如何从Subclass.prototype的构造函数中获得调用Superclass.prototype._init.call(this)效果(即Subclass.prototype获取Superclass的所有属性和方法),Superclass中没有Superclass._init函数时。 - mathematical.coffee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接