JavaScript中的原型继承如何真正起作用?

时间:2017-04-20 20:16:36

标签: javascript reactjs inheritance

我仍然没有完全理解JavaScript中的继承dychotomy(prototypal与经典)。

如果class只是原型的语法糖,那么我该如何去糖呢?

您是否可以向我展示使用类和原型创建React元素的不同方法(即没有class& React.createClass)?

那么,有没有办法使用原生Object.create获取有状态组件?

像这样:

const Hello = Object.create(React.Component.prototype, {
  componentDidMount: {
    value: function() {
      alert('Mounted');
    }
  },
  render: {
    value: function() {
      return <div>I love StackOverflow community! It is so helpful and friendly</div>;
    }
  }
});

ReactDOM.render(<Hello />, document.getElementById('root'));

由于内部lib的限制,似乎这样的东西不起作用。 但是为什么我们不能在更自然的原型JavaScript中使用它?

官方文档中有一条说明:https://facebook.github.io/react/docs/composition-vs-inheritance.html#so-what-about-inheritance

  

[...]我们还没有找到任何建议创建组件继承层次结构的用例

class主要是关于继承?

我很困惑,想听听你对我在做什么和想错的看法?

我在Reactiflux问了这个问题,而Brendan Hurley提出了这个问题:https://codepen.io/niechea/pen/xdVEvv?editors=0010

function MyComponent(props) {
  this.props = props;
  this.state = {
    clickCount: 0,
  };
}

MyComponent.prototype = Object.create(React.Component.prototype);

MyComponent.prototype.clickHandler = function() {
  this.setState({
    clickCount: this.state.clickCount + 1,
  });
}

MyComponent.prototype.render = function() {
  return (
    <div>
      <p>Hello, {this.props.name}.</p>
      <p>You have clicked {this.state.clickCount} time(s)!</p>
      <button onClick={this.clickHandler.bind(this)}>Click me</button>
    </div>
  );
}

ReactDOM.render(<MyComponent name="Bogdan" />, document.getElementById('app'));

他的解决方案真的是原型吗?

以下是一些参考资料:

*问题主要是关于继承,而不是关于React。这里的反应只是一个参考。

6 个答案:

答案 0 :(得分:2)

  

如果这个类只是原型的语法糖,那我就是这样   应该去糖吗?

例如,这是关于此问题的good article。因此,如果您使用Animal创建实体class

class AnimalES6 {
    constructor(name) {
        this.name = name;
    }

    doSomething() {
        console.log("I'm a " + this.name);
    }
}

var lionES6 = new AnimalES6("Lion");
lionES6.doSomething();

Prototypal版本看起来像这样:

var Animal = (function () {
    function Animal(name) {
        this.name = name;
    }
    // Methods
    Animal.prototype.doSomething = function () {
        console.log("I'm a " + this.name);
    };
    return Animal;
})();

var lion = new Animal("Lion");
lion.doSomething();

extend功能更加复杂(例如继承的TypeScript模拟):

var __extends = this.__extends || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
  

您可以向我展示创建React元素的不同方法   有类和原型(即没有class&amp; React.createClass)?

对于这个问题,还有一些问题已经回答,例如: this one

但在我看来,真正的问题是:你想要吗?

由于您链接了Eric Elliot的文章,您可能已经注意到在javascript世界中围绕EC6的class es存在一种争议。除了您发布的示例之外,还有一些来自更多开发人员的意见汇总,例如: this github repohere等等。还有一些文章捍卫了class ......

的目的

无论如何,React的创造者似乎已经接受了class es的“邪恶”,正如你所指出的那样,在尝试使用React的原型方法时会遇到问题。所以在我看来:为什么要烦恼呢?我也喜欢javascript的原型性质,我也喜欢ReactJS框架,但在我看来,最好能够提出一些新的框架,它结合了两者的优点,比如“Protypal React”,而不是试图强制进行原型设计。当React不打算用于此类用途时。

  

但主要不是关于继承的课程吗?

这可能在评论中得到了答案,但没有。类具有优势,因为Composition设计概念优于继承。它depends on the needs但许多框架/库建立在面向对象的语言上,这些语言使用类包含组合而不是继承,例如统一。

无论如何,非常好的问题,我也想分享我对此事的看法。希望它能帮助你形成一个观点。

答案 1 :(得分:1)

所以,就像其他人所说的那样,在Javascript中思考类的最佳方式是作为原型继承的语法糖。最好避免将经典继承与其他语言的课程联系起来,特别是如果你在大学/学校教过它。

原型继承可以被认为是more expressive而不是经典继承。

使用&#39;类&#39; JS中的关键字在语法上更接近于经典继承。

例如,您在UI中有这些组件:Link,MediaLink,ImageLink&amp;视频链接。在经典继承中,您可能想要一个Link类,一个扩展Link的MediaLink类,以及一个VideoLink&amp; amp;扩展MediaLink类的ImageLink类,其中MediaLink是一个抽象类(不进行实例化)。如果你使用原型继承来实现具有有状态React组件的这个层次结构,理论上可以通过调用super(或在上面的答案中调用React.Component.apply(this, props);)来轻松操作状态,但是React的核心,即&#39 ;渲染&#39;会有点抽象,难以扩展。链接可能会返回<a ...>link</a>,MediaLink会返回什么? VideoLink如何与其父级的回归一起工作?或者你是否开始否定父渲染函数并完全替换它们?它在这一点上变得尴尬,它看起来有点像意大利面条。

相反,您可以撰写组件。在经典继承中,您可以将Component类视为继承自抽象类,React.Component的 final class 。为了实现上述功能,您可以向类中添加许多类可能常见的行为。例如,组件TextLink,VideoLink&amp; ImageLink都有一个布尔状态&#39;点击&#39;。我认为this article很好地总结了这些想法。

组合组件的另一种方法是将组件与其他组件包装在一起,并通过props将状态传递给子组件。一个非常粗糙的例子可能是:Link,SwitchedLink。 Link类有一个prop&#39; active&#39;它决定了它是否有效。 Switched链接将根据其自身状态将<Link /><Link active />呈现为单个子项。孩子是无国籍的。父母有州。这些是React的常见模式,因此不需要经典的多级继承结构。

我希望能解决你的问题。

答案 2 :(得分:0)

抱歉,我不能发表任何评论。我想你必须在下面初始化超类构造函数。

/* semaphore increment operation */
void sem_signal(int semid, int val) {
    struct sembuf semaphore;
    semaphore.sem_num = 0;
    semaphore.sem_op = val;
    semaphore.sem_flg = 1;
    semop(semid, &semaphore, 1);
}

/* semaphore decrement operation */
void sem_wait(int semid, int val) {
    struct sembuf semaphore;
    semaphore.sem_num = 0;
    semaphore.sem_op = (-1* val);
    semaphore.sem_flg = 1;
    semop(semid, &semaphore, 1);
}

答案 3 :(得分:0)

在React中,有两种类型的组件;功能组件和基于类的组件。以下是解释差异的反应文档的link

有趣的是,React中的Component本质上是一个返回一定数量JSX的函数。所以你可以编写一个函数并让它返回JSX,这将是一个组件,但没有lifecycle methods之类的关键内部细节,例如componentWillMountthis.propsthis.state Class Component会为您提供开箱即用的功能。

所以你可以创建一个功能组件(即一个返回JSX的函数)并在其中包含原型

const Card = (props) => {
this.sayHello = function () {
  console.log('hello hello hello');
};

return (  
  <View style={styles.containerStyle}>
    {props.children}
    { this.sayHello() }
  </View>
 );
};

这将传递在React中创建的Component,而不使用您习惯的常规术语。

希望这是有帮助的

答案 4 :(得分:0)

我认为以易于理解的方式分解事物很重要。您是在问JavaScript中的原型继承,然后问它如何应用于React。首先,我不得不说原型并不容易解释,也没有很好的文档记录,而是一次只关注一件事。因此,是的,JavaScript没有对象继承的概念,而是原型继承。让我们在汽车上创建一个函数:

function Car() {

}

然后我们可以制造一个将继承Car的Toyota。因此,让我们看看不使用类时构造函数的外观。

function Car(options) {
  this.title = options.title;
}

因此每个Car都必须传递options,而这个options则有一个title

现在,我们可以创建一辆新车,并将其传递给options的{​​{1}}对象,如下所示:

title

现在让我们在此const car = new Car({ title: 'Focus' }); car; 类上添加一个方法,因此我们将这种方法添加到构造函数的原型对象中,如下所示:

Car

现在,当我们调用Car.prototype.drive = function() { return 'vroom!'; } 时,会得到car.drive();的结果。

这是使用JavaScript创建对象的基本方法。我们先创建构造函数对象,然后在其上使用vroom!关键字,然后可以向该对象添加方法,但可以在构造函数的prototype属性上添加它:

new

现在,我将添加一个将从constructorFunction.prototype.methodWeAdd = function() { return something; }; 继承的对象来建立原型链接。

因此,我将制造一个Toyota,并且希望它从Car对象继承,如下所示:

Car

所以我通过了一个function Toyota(options) { } 对象作为参数,该对象包含汽车的options

color

因此,我希望Toyota继承function Toyota(options) { this.color = options.color; } const toyota = new Toyota({ color: 'grey', title: 'Prius' }); console.log(toyota); 的所有属性和方法,因为Cartoyota。那么,如何在两者之间建立链接?如何委托Car来调用toyota拥有的所有方法?

因此,每当我调用Car的构造函数时,我都想确保我运行Toyota中发生的所有初始化,就像这样:

Car

我还想确保可以像这样从function Toyota(options) { Car.call(this.options); this.color = options.color; } 对象调用Car驱动方法:

Toyota

现在,我应该能够像这样向Toyota.prototype = Object.create(Car.prototype); Toyota.prototype.constructor = Toyota; 原型添加一个方法:

Toyota

现在我应该可以拨打以下电话了:

Toyota.prototype.honk = function() {
  return 'beep beep!';
};

因此const toyota = new Toyota({ color: 'grey', title: 'Prius' }); console.log(toyota); console.log(toyota.drive()); console.log(toyota.honk()); 应该继承来自toyota的某种级别的设置。因此,我刚刚经历的上述痛苦过程就是对您说的,这就是您如何使用纯净的原始JavaScript对其进行脱糖。不知道为什么要这么做,但是就在这里。

因此,以上就是在JavaScript中完成原型继承的方式。

类的想法不仅是为了使用语法糖,还在于从工作量中消除了这一艰苦的过程,以便我们可以专注于应用程序开发中的其他挑战。

当您查看诸如ES6之类的新开发内容时,是的,其中有些属于语法语法,但其思想是从日常任务中删除或解决艰苦的过程,例如创建类或从中访问属性/方法。诸如具有破坏性的对象。

在实践中,JavaScript社区接受了类的使用,您会看到大量使用类的库,而模板字符串和解构是一个不错的添加,类极大地改变了我们编写JavaScript代码的方式。

很多原因是因为编写原型继承非常困难,而使用Carclass关键字使用类则容易得多。

当React第一次问世时,在整个地方都使用原型继承来创建组件的想法被认为是可笑的,因为在创建子类时,您必须做很多设置(如上所示),从另一个对象继承。相反,要创建组件,我们将使用extend帮助器。这是React的一部分,但我想向您展示一些现有库向类迁移的实例。

因此,在React中,我们曾经有一个名为createClass的助手,如下所示:

createClass

您将传入一个对象,它可能具有如下方法:

React.createClass({

});

这并不是世界上最糟糕的事情,但是,每当您想要对组件进行子类化或创建具有某种行为的组件并将其在其他地方重用时,这无疑使它变得非常困难。如今,React已迁移为使用类语法,例如:

React.createClass({
   doSomething() {

   },

   doSomethingElse() {

    }
});

这种方法的好处是,如果我想制作一些通用的,可重用的组件以在我的代码库中的其他地方使用,与使用class MyComponent extends Component { doSomething() { } doSomethingElse() { } } 相比,使用基于类的方法可以更轻松地完成语法,而且绝对比原型继承语法更容易。

您还将注意到继承链更加明显。因此,这个Component关键字是React库提供的通用组件。

任何看过此类的人都可以看到它创建了一个组件并从createClass()借用了功能。

这样做的好处是,如果您是面向对象编程的狂热者并使用React,那么与较旧的Component语法相比,使用类语法通常会更容易。

答案 5 :(得分:0)

原型继承是当您在此处将方法放置在原始构造函数上时

function Cat(name,breed){
   this.name = name;
   this.breed = breed;
}

它将被其他人继承

const susie = new Cat('Susie', 'Persian');

如果我创建一个新数组,const names = ['Gilbert','Mia']我刚刚创建了一个全新的数组,但是现在names.join()是一个方法names.pop)()是一个方法。

enter image description here

我们在名称之上拥有所有这些方法。他们来自哪里? 好吧,我们有母亲Array 大写字母A

如果您查看其中的内容,就会知道Array有很多原型方法,

enter image description here

这意味着您创建Cat

const susie = new Cat('Susie', 'Persian');

从母数组或从母Cat

function Cat(name,breed){
   this.name = name;
   this.breed = breed;
}

的每个实例都继承了这些方法。

function Cat(name,breed){
   this.name = name;
   this.breed = breed;
}
Cat.prototype.meow = function() {
   Console.log(`Meow meow! My name is ${this.name}`);
}

const susie = new Cat('Susie', 'Persian');
const mimi = new Cat('Mimi', 'Persian');

现在您可以看到原型Cat.prototype.meow现在已被每个实例猫继承。

const susie = new Cat('Susie', 'Persian');
const mimi = new Cat('Mimi', 'Persian');

susie.meow(); ->喵喵!我叫苏茜 mimi.meow(); ->喵喵!我叫咪咪