JavaScript 中的方法继承

Method Inheritance in JavaScript

本文关键字:继承 方法 JavaScript      更新时间:2023-09-26

JavaScript 使用原型系统,这与类系统有着根本的不同。这是我第一次认真接触这种语言。我以前玩过它,但这是我第一次构建具有适当 OO、继承、多态等的系统。

从我读到的内容来看,似乎有一些常用的方法可以在 Javascript 中进行成员函数继承。假设您有一个父foo

如下所示
foo = function(){ this.one = 1; }
foo.prototype.func1 = function(){return this.one;}

MDN JavaScript 继承简介提出了在子上下文中调用父方法的幼稚方法,如下所示。

bar = function(){ foo.call(this); }
bar.prototype = Object.create(foo.prototype);
bar.prototype.func1 = function(){ return this.one + foo.prototype.func1();}

这具有易于理解的优点,但正如此Salsify博客文章中指出的那样,可能会变得繁琐。此博客文章概述了一种替代方法,其中在子原型中定义super属性,并将每个成员函数的名称作为属性附加到该方法。但是,此方法依赖于方法的caller属性,本文指出该方法将很快被弃用。与其重复整篇文章,我认为重要观点的总结是这些

Object.defineProperty(bar.prototype, "super", {
 get: function get() {
  ...
  // methodName is set as a property on each method in an omitted code segment
  methodName = get.caller.methodName; 
  ...
  Object.getPrototypeOf(this.prototype)[methodName]
 }
}

也就是说,您可以在原型的原型中找到具有相同名称的方法。我想知道这是否可以以更简单的方式完成,而无需将方法名称附加为参数,也无需Function.caller.

foo.prototype.super = function(method) {
  superMethod = Object.getPrototypeOf(this.constructor.prototype)[method];
  return superMethod.call(this, Array.prototype.slice.call(arguments, 1));
}
bar.prototype.func1 = function(){ return this.one + super('func1'); }

我在上面做了一些假设,我想验证一些假设。

    new bar().
  1. constructor.prototype === Object.getPrototypeOf(new bar())
  2. 如果上述情况总是正确的,那么一个比另一个更可取吗?
  3. Parent 的成员函数将始终存在于子原型的原型中(假设在创建对象后两个原型都没有发生突变)
  4. Object.getPrototypeOf()不是博客所指的ES6中添加的"访问超级方法的语言支持"。
  5. 如果 Object.getPrototypeOf() 不是该语言支持,那是什么?

在看到使用 this 的错误后,它在整个执行过程中不会改变并且始终引用子类的实例,我重新审视并认为我需要这样的东西

Grandfather = function(){};
Grandfather.prototype.function1 = function(){console.log("I am the Grandfather");};
Father = function(){Grandfather.apply(this);};
Father.prototype = Object.create(Grandfather.prototype);
Father.prototype.function1 = function f(){ f.super(); console.log("I am the Father");};
Father.prototype.function1.super = Grandfather.prototype.function1;
Child = function(){Father.apply(this);}
Child.prototype = Object.create(Father.prototype);
Child.prototype.function1 = function f(){ f.super(); console.log("I am the Child");};
Child.prototype.function1.super = Father.prototype.function1;
c = new Child();
c.function1();
// I am the Grandfather
// I am the Father
// I am the Child

所以问题就变成了,如何以某种自动方式为每个函数设置 super 属性?


下面显示了一种方法,它的好处是在对象实例化后添加到原型中的函数仍然能够调用superFunc,而在类扩展时设置super属性的方法如果稍后将函数添加到原型中,则不会设置这样的属性。

此方法的缺点是它只能在单线程环境中工作,并且需要从公共基类继承的功能。它不是线程安全的,因为某些状态保存在实际上是函数的静态变量中。这对我的目的来说很好,因为浏览器仍然有单线程的JavaScript。所有类都继承自包含此方法的某个基类的要求并不是一个巨大的障碍(特别是如果你做了一件"坏事"并将其插入到 Object 的原型中)。

Grandfather.prototype.superFunc = function f(funcName){
  currentPrototype = Object.getPrototypeOf(f.startingPrototype || Object.getPrototypeOf(this));
  f.startingPrototype = currentPrototype;
  return currentPrototype[funcName]();
}
Child.prototype.function2 = function(){this.superFunc('function2'); console.log("Still in the Child");};
Father.prototype.function2 = function(){this.superFunc('function2'); console.log("Still in the Father");};
GrandFather.prototype.function2 = function(){console.log("Still in the Grandfather");};
c = new Child();
c.function2();
// Still in the Grandfather
// Still in the Father
// Still in the Child

问题 1

new Bar().constructor.prototype应该等于Object.getPrototypeOf(new Bar()),前提是您没有覆盖Bar.prototype.constructorBar.prototype,或者在Bar构造函数中返回不同的对象。下面是一个示例:

function Bar() {}
var foo = new Bar();
foo.constructor.prototype === Object.getPrototypeOf(foo); // true
function Bar2() {}
var foo2 = new Bar2();
Bar2.prototype = {};
foo2.constructor.prototype === Object.getPrototypeOf(foo2); // false
function Bar3() {}
var foo3 = new Bar3();
Bar3.prototype.constructor = function NotBar3() {};
foo3.constructor.prototype === Object.getPrototypeOf(foo3); // false

问题2

如果要获取对象的实际原型,请使用 Object.getPrototypeOf ,因为它不受上面显示的任何更改的影响。

问题3

不,您将无法从new Bar()访问Foo。在您的示例中,new Bar()不会从Foo.prototype继承,因此,除非您使Foo继承Foo.prototype或将Foo分配给 new Bar()Bar.prototype 的属性,否则无法访问。

问题 4/5

不,这不是他们所指的。ES6 将引入一个单独的class构造,其中super具有特殊含义(类似于super在其他语言中使用类的工作方式)。下面是类在 ES6 中如何工作的示例:

class Foo {
  constructor() {
    this.one = 1;
  }
  func1() {
    return this.one;
  }
}
class Bar extends Foo {
  func1() {
    return this.one + super();
  }
}

当你以你的方式使用 super 时,当继承超过 2 级时它会中断。

假设您将按以下方式使用它:

//changed super to this.super since super is not shown to exist in global scope
bar.prototype.func1(){ return this.one + this.super('func1'); }

请参阅以下示例:

function GrandFather(){
  this.i = 0;
};
GrandFather.prototype.test = function(){
  console.log('test in GrandFather');
};
function Father(){
  GrandFather.call(this);
};
Father.prototype = Object.create(GrandFather.prototype);
Father.prototype.constructor = Father;
Father.prototype.super = GrandFather.prototype;
Father.prototype.test = function(){
  console.log('test in Father');
  //prevent too many recursions
  this.i++;
  if(this.i>5){
    return;
  }
  this.super.test.call(this);//because test in child was called
   // with Child instance as invoking object this will be Child
   // and this.super will be Father.prototype
};
function Child(){
  Father.call(this);
}
Child.prototype = Object.create(Father.prototype);
Child.prototype.constructor = Child;
Child.prototype.super = Father.prototype;
Child.prototype.test = function(){
  console.log('test in Child');
  this.super.test.call(this);//because invoking object is Child
    //this.super in Father is Child
};
var c = new Child();
c.test();

使用大写字母启动构造函数也是常见的做法,因此最好使用 Foo 和 Bar 作为构造函数名称。

如果你想经历在JavaScript中模拟super的所有麻烦,那么以下方式会稍微健壮一些: http://ejohn.org/blog/simple-javascript-inheritance/