【问题标题】:Method Inheritance in JavaScriptJavaScript 中的方法继承
【发布时间】:2014-11-16 07:51:07
【问题描述】:

JavaScript 使用原型系统,它与类系统根本不同。这是我第一次认真接触这门语言。之前我也玩过,但这是我第一次用正确的面向对象、继承、多态等构建系统。

根据我的阅读,似乎有一些常用的方法可以在 Javascript 中进行成员函数继承。假设你有一个父 foo 如下

foo = function(){ this.one = 1; }
foo.prototype.func1 = function(){return this.one;}

MDN Introduction to JavaScript Inheritance 建议在子上下文中调用父方法的幼稚方法,如下所示。

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

这具有易于理解的优点,但如this Salsify Blog post 中所指出的那样会变得很麻烦。该博客文章概述了一种替代方法,其中在子原型中定义了 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'); }

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

  1. new bar().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

【问题讨论】:

  • 我猜你的意思是bar.prototype.func1 = function () { ... } ? ;)

标签: javascript inheritance polymorphism prototype


【解决方案1】:

问题 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.prototype 或将 Foo 分配给 new Bar() 或 @ 的属性,否则无法访问 Foo 987654336@.

问题 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();
  }
}

【讨论】:

  • Javascript 不能简单地摆脱它的原型结构,所以我认为显式的“类”结构会在幕后采取一些行动来让 super() 引用正确的方法?
【解决方案2】:

当你以你的方式使用 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/

【讨论】:

  • 我看到使用“this”的问题,我将重新审视我的策略,但是我在问题中链接的 Salsify 博客调用了你提到的 ejohn.org 链接,我同意它的缺点方法已经足够想找到替代方法了。
  • @Huckle 提到的缺点是不存在的。没有try and catch,只有在需要时才创建闭包。使用 caller 对性能的负面影响要大得多,并且已经确定 this 也不能使用,因此使用闭包是唯一的方法。我通常不使用super,因为没有看到文章作者提到的问题
猜你喜欢
  • 2023-03-17
  • 2012-06-21
  • 1970-01-01
  • 2013-05-09
  • 1970-01-01
  • 2011-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多