简体   繁体   English

理解为什么真正的原型继承优于经典/伪原型继承以及为什么我不应该使用“新”

[英]Understanding why true prototypal inheritance is better than classical/pseudo prototypal inheritance and why i shouldn't use “new”

Reading some articles from Aadit M Shah like Why Prototypal Inheritance Matters or Stop Using Constructor Functions in JavaScript from Eric Elliott i think i understand all of their arguments, in theoric. 阅读Aadit M Shah的一些文章,比如为什么Prototypal继承很重要或者停止在 Eric Elliott的JavaScript中使用构造函数我认为我理解他们所有的论点,在理论上。 But in practice i don't see the real advantages of this pattern. 但在实践中,我没有看到这种模式的真正优势。

Let's take a look two implementations from two snippets to make inheritance. 让我们看看两个片段中的两个实现来进行继承。

  1. First one is using augment.js it's a script from Aadit M Shah 第一个是使用augment.js,它是来自Aadit M Shah的脚本
  2. On this example we are going to use this script . 在这个例子中,我们将使用这个脚本 Is made it by Aadit M Shah as well. 也是由Aadit M Shah制作的。

Implementation 1 : 实施1

    var AugmentPerson = Object.augment(function() {
      this.constructor = function(name) {
          this.name = name;
      };
      this.setAddress = function(country, city, street) {
          this.country = country;
          this.city = city;
          this.street = street;
      };
    });
    var AugmentFrenchGuy = AugmentPerson.augment(function(base) {
      this.constructor = function(name) {
          base.constructor.call(this,name);
      };
      this.setAddress = function(city, street) {
          base.setAddress.call(this, "France", city, street);
      };
    });
    var AugmentParisLover = AugmentFrenchGuy.augment(function(base) {

      this.constructor = function(name) {
          base.constructor.call(this, name);
      };

      this.setAddress = function(street) {
          base.setAddress.call(this, "Paris", street);
      };
    });
    var t = new AugmentParisLover("Mary");
    t.setAddress("CH");
    console.log(t.name, t.country, t.city, t.street); //Mary France Paris CH

In this example we are using function constructors instead of inherit directly from a object. 在这个例子中,我们使用函数构造函数而不是直接从对象继承。

Implementation 2 : 实施2

    var CreatePerson = {
        create: function (name) {
            this.name = name;
            return this.extend();
        },
        setAddress: function(country, city, street) {
             this.country = country;
             this.city = city;
             this.street = street;
        }
    };
    var CreateFrenchGuy  = CreatePerson.extend({
        create: function (name) {
            return CreatePerson.create.call(this,name);
        },
        setAddress: function(city, street) {
            CreatePerson.setAddress('France', city, street);
        }
    });
    var CreateParisLover  = CreateFrenchGuy.extend({
        create: function (name) {
            return CreateFrenchGuy.create.call(this,name);
        },
        setAddress: function(street) {
            CreateFrenchGuy.setAddress('Paris', street);
        }
    });

    var t = CreateParisLover.create("Mary");
    t.setAddress("CH");
    console.log(t.name, t.country, t.city, t.street); //Mary France Paris CH

To be honest, i'm trying to see the benefits of the second implementation. 说实话,我试图看到第二个实现的好处。 But i am not able. 但我不能。 The only point i see is more flexible is because we can create the instance using apply: 我看到的唯一一点是更灵活,因为我们可以使用apply创建实例:

var t = CreateParisLover.create.apply(CreateParisLover, ["Mary"]);

This give us more flexibility, it's true. 这给了我们更大的灵活性,这是真的。 But we can do the same with this : 但是,我们可以做同样的这个

  Function.prototype.new = function () {
     function functor() { return constructor.apply(this, args); }
     var args = Array.prototype.slice.call(arguments);
     functor.prototype = this.prototype;
     var constructor = this;
     return new functor;
  };

Then we can: 然后我们可以:

var t = AugmentParisLover.new.apply(AugmentParisLover, ["Mary"]);

What is the real benefits in terms of flexibility, re-usability, difficulty... Because if you check the performance of both cases. 在灵活性,可重用性,难度方面有什么真正的好处......因为如果你检查两种情况的表现。 Object.create() is pretty much slower than new: http://jsperf.com/inheritance-using-create-vs-new I'm confusing. Object.create()比新的慢得多: http//jsperf.com/inheritance-using-create-vs-new我很困惑。

Programming is a lot like fashion. 编程很像时尚。 Subconsciously most programmers write code which to them looks aesthetically pleasing. 潜意识里,大多数程序员编写的代码对他们来说看起来很美观。 This is the main reason why Java programmers want to implement classical inheritance in JavaScript. 这是Java程序员想要在JavaScript中实现经典继承的主要原因。 Yes, trying to implement classical inheritance in JavaScript is a monolithic task but that doesn't stop people from doing it. 是的,尝试在JavaScript中实现经典继承是一项单一的任务,但这并不能阻止人们这样做。 It's an overkill but people still do it because they just want their code to look like classes (eg jTypes ). 这是一个矫枉过正,但人们仍然这样做,因为他们只是希望他们的代码看起来像类(例如jTypes )。

In much the same way Eric and I have been trying to popularize the use of factory functions instead of constructor functions. 就像Eric和我一直试图推广使用工厂函数而不是构造函数一样。 However this shift from factories to constructors is not just for aesthetic reasons. 然而,从工厂到施工人员的这种转变不仅仅是出于美学原因。 The two of us are trying to change the mentality of JavaScript programmers because in certain aspects we both believe that JavaScript is fundamentally flawed. 我们两个人正试图改变JavaScript程序员的心态,因为在某些方面我们都认为JavaScript存在根本缺陷。 The new operator in JavaScript is one such aspect. JavaScript中的new运算符就是这样一个方面。 Although it's broken yet it's central to the language and hence it cannot be avoided. 虽然它已经破碎但它仍然是语言的核心,因此无法避免。

The bottom line is this: 底线是这个:

If you want to create prototype chains in JavaScript then you have to use new . 如果你想在JavaScript中创建原型链,那么你必须使用new There is no other way around it (except .__proto__ which is frowned upon). 没有别的办法(除了.__proto__ ,这是.__proto__ )。

Interestingly you need neither prototypes nor classes to inherit from multiple objects. 有趣的是,您既不需要原型也不需要从多个对象继承类。 Using object composition you can achieve strong behavioral subtyping in JavaScript as Benjamin Gruenbaum describes in the following answer: https://stackoverflow.com/a/17008693/783743 使用对象组合,您可以在JavaScript中实现强大的行为子类型,正如Benjamin Gruenbaum在以下答案中所述: https//stackoverflow.com/a/17008693/783743

In this answer I'll touch upon the following topics: 在这个答案中,我将涉及以下主题:

  1. Why are we stuck with new ? 为什么我们坚持new
  2. Why are factories better than constructors? 为什么工厂比建造者更好?
  3. How do we get the best of both worlds? 我们如何充分利用这两个世界?

1. Why are we stuck with new ? 1.为什么我们坚持new

The new keyword is put on a pedestal in JavaScript. new关键字放在JavaScript的基座上。 There's no way to create a prototype chain in JavaScript without using new . 没有使用new ,就无法在JavaScript中创建原型链。 Yes you can change the .__proto__ property of an object but only after it's created, and that practice is frowned upon. 是的,你可以改变一个对象的.__proto__属性,但只有在它被创建之后才会改变,并且这种做法是不受欢迎的。 Even Object.create uses new internally: 甚至Object.create内部使用new

Object.create = function (o) {
    function F() {}
    F.prototype = o;
    return new F;
};

As Douglas Crockford mentioned : 正如Douglas Crockford 所说

The Object.create function untangles JavaScript's constructor pattern, achieving true prototypal inheritance. Object.create函数解开JavaScript的构造函数模式,实现真正的原型继承。 It takes an old object as a parameter and returns an empty new object that inherits from the old one. 它将旧对象作为参数,并返回一个从旧对象继承的空对象。 If we attempt to obtain a member from the new object, and it lacks that key, then the old object will supply the member. 如果我们尝试从新对象获取成员,并且它缺少该键,则旧对象将提供该成员。 Objects inherit from objects. 对象继承自对象。 What could be more object oriented than that? 什么可能比这更面向对象?

The point is that although the new keyword in JavaScript is "tangled" up there's no other way to create prototype chains in JavaScript. 关键是虽然JavaScript中的new关键字“纠缠不清”,但没有其他方法可以在JavaScript中创建原型链。 The Object.create function, even when implemented natively, is still slower than using new and hence for performance reasons alone most people still use new even though Object.create is a more logically sound option. Object.create函数,即使在本机实现时,仍然比使用new更慢,因此出于性能原因,大多数人仍然使用new ,尽管Object.create是一个更逻辑上合理的选项。

2. Why are factories better than constructors? 2.为什么工厂比建造者更好?

Now you might wonder whether new is really so bad. 现在你可能想知道new是不是真的那么糟糕。 After all performance wise it is indeed the best solution. 在所有表现方面,它确实是最好的解决方案。 In my opinion however it shouldn't be so. 但我认为不应该这样。 Whether you use new or Object.create performance should always be the same. 无论您使用new还是Object.create性能都应始终相同。 This is where the language implementations are lacking. 这是缺乏语言实现的地方。 They should really strive towards making Object.create faster. 他们应该努力使Object.create更快。 So besides performance does new have any other redeeming qualities? 除了表演之外, new还有其他任何赎回品质吗? In my humble opinion it doesn't. 在我的拙见中,它没有。

Oftentimes you don't really know what's wrong with a language until you start using a better language. 在你开始使用更好的语言之前,你通常不会真正知道语言有什么问题。 So let's see some other languages: 那么让我们看看其他一些语言:

a) Magpie a)喜鹊

Magpie is a hobby language created by Bob Nystrom . 喜鹊Bob Nystrom创作的一种爱好语言。 It has a bunch of very interesting features which interact very nicely with each other, namely: 它有许多非常有趣的功能,彼此之间的互动非常好,即:

  1. Patterns 模式
  2. Classes
  3. Multimethods 多方法

Classes in Magpie however are more akin to prototypes in JavaScript or data types in Haskell. 然而,Magpie中的类更类似于JavaScript中的原型或Haskell中的数据类型。

In Magpie instantiation of classes is split into two steps: 在Magpie中,类的实例化分为两个步骤:

  1. Constructing a new instance. 构建一个新实例。
  2. Initializing the newly constructed instance. 初始化新构造的实例。

In JavaScript the new keyword combines the construction and the initialization of instances. 在JavaScript中, new关键字结合了实例的构造和初始化。 This is actually a bad thing because as we'll see soon splitting construction and initialization is actually a good thing. 这实际上是一件坏事,因为我们很快就会看到拆分构造和初始化实际上是一件好事。

Consider the following Magpie code: 考虑以下Magpie代码:

defclass Point
    var x
    var y
end

val zeroPoint = Point new(x: 0, y: 0)

def (this == Point) new (x is Int, y is Int)
    match x, y
        case 0, 0 then zeroPoint
        else this new(x: x, y: y)
    end
end

var origin = Point new(0, 0)
val point = Point new(2, 3)

This is equivalent to the following JavaScript code: 这相当于以下JavaScript代码:

function Point(x, y) {
    this.x = x;
    this.y = y;
}

var zeroPoint = new Point(0, 0);

Point.new = function (x, y) {
    return x === 0 && y === 0 ?
    zeroPoint : new Point(x, y);
};

var origin = Point.new(0, 0);
var point = Point.new(2, 3);

As you can see here we've split the construction and the initialization of instances into two functions. 正如您在这里看到的,我们将实例的构造和初始化分成两个函数。 The Point function initializes the instance and the Point.new function constructs the instance. Point函数初始化实例, Point.new函数构造实例。 In essence we have simply created a factory function. 本质上,我们只是创建了一个工厂功能。

Separating construction from initialization is such a useful pattern that the good people of the JavaScript room have even blogged about it, calling it the Initializer Pattern . 将构造与初始化分离是一种非常有用的模式,JavaScript室的优秀人员甚至在博客上发表了关于它的信息,称之为初始化模式 You should read about the initializer pattern. 您应该阅读初始化模式。 It shows you that initialization in JavaScript is separate from construction. 它向您展示JavaScript中的初始化与构造是分开的。

  1. Factories like Object.create (+1): Construction is separate from initialization. Object.create (+1)这样的工厂:构造与初始化是分开的。
  2. The new operator (-1): Construction and initialization are inseparable. new运算符(-1):构造和初始化是不可分割的。

b) Haskell b)Haskell

JavaScript has been my favorite language since the past 8 years. 自从过去8年以来,JavaScript一直是我最喜欢的语言。 Recently however I started programming in Haskell and I must admit that Haskell has stolen my heart. 然而,最近我开始在Haskell编程,我必须承认Haskell已经偷走了我的心脏。 Programming in Haskell is fun and exciting. 在Haskell中编程很有趣也很有趣。 JavaScript still has a long way to go before it'll be in the same league as Haskell and there's much that JavaScript programmers can learn from Haskell. 在与Haskell处于同一个联盟之前,JavaScript还有很长的路要走,而且JavaScript程序员可以从Haskell中学到很多东西。 I would like to talk about algebraic data types from Haskell apropos to this question. 我想谈谈从Haskell apropos到这个问题的代数数据类型。

Data types in Haskell are like prototypes in JavaScript and data constructors in Haskell are like factory functions in JavaScript. Haskell中的数据类型就像JavaScript中的原型,Haskell中的数据构造函数就像JavaScript中的工厂函数。 For example the above Point class would be written as follows in Haskell: 例如,上面的Point类将在Haskell中编写如下:

data Point = Point Int Int
zeroPoint = Point 0 0
origin = zeroPoint
point = Point 2 3

Succinct isn't it? 简洁不是吗? However I'm not here to sell Haskell so let's take a look at some other features Haskell offers: 但是我不是来卖Haskell所以让我们来看看Haskell提供的其他一些功能:

data Shape = Rectangle Point Point | Circle Point Int
rectangle = Rectangle origin (Point 3 4)
circle = Circle zeroPoint 3

Here rectangle and circle are both instances of type Shape : 这里rectanglecircle都是Shape类型的实例:

rectangle :: Shape
circle :: Shape

In this case Shape is our prototype (data type in Haskell) and rectangle and circle are instances of that data type. 在这种情况下, Shape是我们的原型(Haskell中的数据类型), rectanglecircle是该数据类型的实例。 More interestingly however the Shape prototype has two constructors (data constructors in Haskell): Rectangle and Circle . 更有趣的是, Shape原型有两个构造函数(Haskell中的数据构造函数): RectangleCircle

Rectangle :: Point -> Point -> Shape
Circle :: Point -> Int -> Shape

The Rectangle data constructor is a function which takes a Point and another Point and returns a Shape . Rectangle数据构造函数是一个函数,它接受Point和另一个Point并返回一个Shape Similarly the Circle data constructor is a function which takes a Point and an Int and returns a Shape . 类似地, Circle数据构造函数是一个函数,它接受PointInt并返回一个Shape In JavaScript this would be written as follows: 在JavaScript中,这将写成如下:

var Shape = {};

Rectangle.prototype = Shape;

function Rectangle(p1, p2) {
    this.p1 = p1;
    this.p2 = p2;
}

Circle.prototype = Shape;

function Circle(p, r) {
    this.p = p;
    this.r = r;
}

var rectangle = new Rectangle(origin, Point.new(3, 4));
var circle = new Circle(zeroPoint, 3);

As you can see a prototype in JavaScript can have more than one constructor and that makes sense. 正如您所看到的,JavaScript中的原型可以有多个构造函数,这是有道理的。 It's also possible for one constructor to have different prototypes at different instances of time but that makes no sense at all. 一个构造函数也可能在不同的时间实例中具有不同的原型,但这根本没有意义。 Doing so would break instanceof . 这样做会破坏instanceof

As it turns out having multiple constructors is a pain when using the constructor pattern. 事实证明,在使用构造函数模式时,使用多个构造函数会很痛苦。 However it's a match made in heaven when using the prototypal pattern: 然而,当使用原型模式时,它是天堂般的匹配:

var Shape = {
    Rectangle: function (p1, p2) {
        var rectangle = Object.create(this);
        rectangle.p1 = p1;
        rectangle.p2 = p2;
        return rectangle;
    },
    Circle: function (p, r) {
        var circle = Object.create(this);
        circle.p = p;
        circle.r = r;
        return circle;
    }
};

var rectangle = Shape.Rectangle(zeroPoint, Point.new(3, 4));
var circle = Shape.Circle(origin, 3);

You could also use the extend function from my blog post on Why Prototypal Inheritance Matters to make the above code more succinct: 你也可以使用我的博客文章中关于为什么原型继承很重要extend函数来使上面的代码更简洁:

var Shape = {
    Rectangle: function (p1, p2) {
        return this.extend({
            p1: p1,
            p2: p2
        });
    },
    Circle: function (p, r) {
        return this.extend({
            p: p,
            r: r
        });
    }
};

var rectangle = Shape.Rectangle(zeroPoint, Point.new(3, 4));
var circle = Shape.Circle(origin, 3);

Factories written in this way look a lot like the module pattern and it feels natural to write code like this. 以这种方式编写的工厂看起来很像模块模式 ,编写这样的代码感觉很自然。 Unlike with the constructor pattern everything is wrapped up nicely in an object literal. 与构造函数模式不同,一切都很好地包含在对象文字中。 Nothing is dangling here, there and everywhere. 这里,那里和任何地方都没有任何东西悬空。

Nevertheless if performance is your main concern then stick with the constructor pattern and new . 然而,如果性能是您的主要关注点,那么坚持使用构造函数模式和new In my opinion however modern JavaScript engines are fast enough that performance is no longer the main factor. 在我看来,现代JavaScript引擎足够快,性能不再是主要因素。 Instead I think JavaScript programmers should invest more time in writing code that's maintainable and robust and the prototypal pattern is indeed more elegant and understandable than the constructor pattern. 相反,我认为JavaScript程序员应该花更多的时间编写可维护且健壮的代码,而原型模式确实比构造函数模式更优雅和易懂。

  1. Factories (+1): You can easily create multiple factories for each prototype. 工厂(+1):您可以轻松地为每个原型创建多个工厂。
  2. Constructors (-1): Creating multiple constructors for each prototype is hacky and clumsy. 构造函数(-1):为每个原型创建多个构造函数是hacky和笨拙的。
  3. Prototypal Pattern (+1): Everything is encapsulated within a single object literal. Prototypal Pattern(+1):所有内容都封装在一个对象文字中。 Looks a lot like the module pattern. 看起来很像模块模式。
  4. Constructor Pattern (-1): It's unstructured and looks incohesive. 构造函数模式(-1):它是非结构化的,看起来不连贯。 Difficult to understand and maintain. 难以理解和维护。

In addition Haskell also teaches us about pure functional programming. 此外,Haskell还教我们关于纯函数式编程。 Since factories are simply functions we can call and apply factories, compose factories, curry factories, memoize factories, make factories lazy by lifting them and much more. 由于工厂只是功能,我们可以callapply工厂,组建工厂,咖喱工厂,记忆工厂, 通过提升工厂使工厂变得懒惰等等。 Because new is an operator and not a function you can't do that using new . 因为new是一个操作符而不是一个函数,所以你不能使用new Yes you can make a functional equivalent of new but then why not just use factories instead? 是的,你可以使功能相当于new但为什么不只是使用工厂? Using the new operator in some places and the new method in other places is inconsistent. 在某些地方使用new运算符而在其他地方使用new方法是不一致的。

3. How do we get the best of both worlds? 3.我们如何充分利用这两个世界?

Alright so factories do have their advantages, but still the performance of Object.create sucks doesn't it? 好吧所以工厂确实有自己的优势,但是Object.create的表现还是糟透了吗? It does, and one of the reasons is because every time we use Object.create we create a new constructor function, set its prototype to the prototype we want, instantiate the newly created constructor function using new and then return it: 确实如此,其中一个原因是因为每次我们使用Object.create我们都会创建一个新的构造函数,将其原型设置为我们想要的原型,使用new实例化新创建的构造函数,然后返回它:

Object.create = function (o) {
    function F() {}
    F.prototype = o;
    return new F;
};

Can we do better than this? 我们能做得比这更好吗? Let's try. 我们试试吧。 Instead of creating a new constructor every time why don't we just instantiate the .constructor function of the given prototype? 每次为什么不实例化给定原型的.constructor函数而不是创建新的构造函数?

Object.create = function (o) {
    return new o.constructor;
};

This works in most cases but there are a few problems: 这在大多数情况下都有效,但存在一些问题:

  1. The prototype of o.constructor might be different from o . o.constructor的原型可能与o不同。
  2. We only want to construct a new instance of o , but o.constructor might have initialization logic as well which we can't separate from the construction. 我们只想构造一个o的新实例,但o.constructor也可能有初始化逻辑,我们无法从构造中分离出来。

The solution is pretty simple: 解决方案非常简单:

function defclass(prototype) {
    var constructor = function () {};
    constructor.prototype = prototype;
    return constructor;
}

Using defclass you can create classes as follows: 使用defclass可以创建类,如下所示:

var Shape = defclass({
    rectangle: function (p1, p2) {
        this.p1 = p1;
        this.p2 = p2;
        return this;
    },
    circle: function (p, r) {
        this.p = p;
        this.r = r;
        return this;
    }
});

var rectangle = (new Shape).rectangle(zeroPoint, Point.new(3, 4));
var circle = (new Shape).circle(origin, 3);

As you can see we've separated construction and initialization and the initialization can be deferred to multiple constructors. 正如您所看到的,我们已经分离了构造和初始化,并且初始化可以推迟到多个构造函数。 It can even be chained as follows: (new Shape).rectangle().circle() . 它甚至可以链接如下:( (new Shape).rectangle().circle() We've replaced Object.create with new which is much faster and we still have the flexibility to do whatever we want. 我们用new取代了Object.create ,速度更快,我们仍然可以灵活地做任何我们想做的事情。 In addition everything is nicely encapsulated within a single object literal. 此外,一切都很好地封装在一个对象文字中。

Conclusion 结论

As you can see the new operator is a necessary evil. 正如你所看到的那样, new算子是一种必要的恶魔。 If new was a implemented as a factory function then that would be great but it's implemented as an operator instead and operators in JavaScript are not first class. 如果new是作为工厂函数实现的那么那将是很好的但是它实现为运算符而JavaScript中的运算符不是一流的。 This makes it more difficult to do functional programming with new . 这使得使用new进行函数式编程变得更加困难。 Factories on the other hand are flexible. 另一方面,工厂是灵活的。 You can tailor make any number of factory functions for your prototypes and the ability to do whatever you want is the biggest selling point of factory functions. 您可以为您的原型定制任意数量的工厂功能,并且能够做任何您想要的工作是工厂功能的最大卖点。

In JavaScript, what people call "pseudo-classical" inheritance is prototypical inheritance. 在JavaScript中,人们称之为“伪经典”继承典型的继承。 That's the only kind of inheritance JavaScript has. 这是JavaScript唯一的继承类型。 Avoiding new is like avoiding switch statements because you can do that using if/else if instead. 避免使用new就像避免使用switch语句一样,因为你可以使用if/else if来实现。 Sure you can, and sometimes you should. 当然可以,有时你应该。 Other times, switch is exactly the right choice. 其他时候, switch是正确的选择。 Same with new and Object.create : Use the best one for what you're doing. newObject.create相同:使用最适合您正在做的事情。

To me , and this is a bit subjective (as is the whole "pseudo-classical inheritance is bad" meme, in my view): 我来说 ,这有点主观(在我看来,整个“伪经典继承是坏的”模因):

  1. new is for when I'm doing class-like things. new是,当我在做类似的东西了。 I use new and constructor functions because it fits well with how the language is designed. 我使用new和构造函数,因为它非常适合语言的设计方式。 (Yes, that design is unusual, but that's how it is.) So if I'm going to have objects that will represent people and have common behavior, I'll use a Person constructor, assign behaviors (functions) to Person.prototype , and use new Person to create them. (是的,那个设计很不寻常,但就是这样。)所以如果我要拥有代表人并具有共同行为的对象,我将使用Person构造函数,将行为(函数)分配给Person.prototype ,并使用new Person来创建它们。 (I use my Lineage script to make this more concise, and to handle some hierarchy stuff easily.) This is straightforward, familiar, clean, clear: If you see new Person you know I'm creating a new object. (我使用我的Lineage脚本使这更简洁,并轻松处理一些层次结构的东西。)这是直截了当,熟悉,干净,清晰:如果你看到new Person你知道我正在创建一个新对象。 (If I'm not — yes, it's possible to violate that expectation with a constructor function — then to my mind I shouldn't be writing a constructor function in the first place.) (如果我不是 - 是的, 可能违反了构造函数的期望 - 那么在我看来我不应该首先编写构造函数。)

    Now of course, you can define a builder function ( createPerson , buildPerson , whatever) that does the same thing using Object.create or similar. 当然,您可以定义使用Object.create或类似功能执行相同操作的构建器函数( createPersonbuildPerson等)。 I don't have a problem with people doing that if that's what they prefer (as long as the function name is clear it creates something). 如果那是他们喜欢的东西,我对那些人没有问题(只要功能名称清楚它会创造一些东西)。 I do have a problem with people saying "you shouldn't use new " as though it were objective advice; 我确实有人说“你不应该使用new ”,好像这是客观的建议; it's an opinion , it's style advice. 这是一种意见 ,这是一种风格建议。

  2. Object.create is for when I'm doing instance-level stuff. Object.create适用于我在做实例级别的东西时。 There's a project I work on that has a bunch of objects in a complex tree/graph. 我工作的项目在复杂的树/图中有一堆对象。 They're data-only, no behavior. 它们只是数据,没有行为。 Sometimes, we need to have data that's not yet verified, and so shouldn't overwrite the previous data. 有时,我们需要拥有尚未验证的数据,因此不应覆盖以前的数据。 So the container has a reference to the verified data ( verified ) and to the unverified data ( current ). 因此容器具有对已验证数据( verified )和未验证数据( current )的引用。 To avoid unnecessary branching in the code, the container always has both references, but in the normal case they refer to the same object ( container.verified = container.current = {}; ). 为了避免代码中不必要的分支,容器始终具有两个引用,但在正常情况下它们引用相同的对象( container.verified = container.current = {}; )。 Nearly all code uses that current object because nearly all code should be using the most up-to-date information. 几乎所有代码都使用current对象,因为几乎所有代码都应使用最新信息。 If we need to add pending data, we do container.current = Object.create(container.verified); 如果我们需要添加挂起的数据,我们会做container.current = Object.create(container.verified); and then add the data to container.current . 然后将数据添加到container.current Since current 's prototype is verified , there's no need to copy all the old data to it and have duplicated data all over the place. 由于verified current的原型,因此无需将所有旧数据复制到其中并且在整个地方都有重复的数据。 Eg, the classic use of facade. 例如,门面的经典用途。 new would be the wrong tool for this job, it would only get in the way. new对于这项工作来说是错误的工具,它只会妨碍你。

One of the many fantastic things about JavaScript is that you have both options. 关于JavaScript的众多奇妙之处之一就是你有两种选择。 I use both in the same projects, for different things, all the time. 我同时在同一个项目中使用它们,不同的东西。

Similar questions have been asked and answered many times before. 之前已经多次提出并回答过类似的问题。 See: 看到:

Constructor function vs Factory functions Classical Vs prototypal inheritance 构造函数vs工厂函数 Classical Vs原型继承

More learning: https://medium.com/javascript-scene/3-different-kinds-of-prototypal-inheritance-es6-edition-32d777fa16c9#.s0r3i5w6t http://vimeo.com/69255635 更多学习: https//medium.com/javascript-scene/3-different-kinds-of-prototypal-inheritance-es6-edition-32d777fa16c9#.s0r3i5w6t http://vimeo.com/69255635

tl;dr TL;博士

  • Constructors break the open / closed principle 构造者打破了开放/封闭原则
  • Constructors conflate object creation with object initialization - sometimes hampering the reusability of the code 构造函数将对象创建与对象初始化相混淆 - 有时会妨碍代码的可重用性
  • Constructors look a bit like classes, which is confusing. 构造函数看起来有点像类,这令人困惑。 JavaScript doesn't need classes (I recommend avoiding the class keyword coming in ES6). JavaScript不需要类(我建议避免使用ES6中的class关键字)。 JavaScript has something better than classes. JavaScript比类更好。
  • The combination of prototype delegation and dynamic object extension (concatenative inheritance) is much more powerful and flexible than classical inheritance. 原型委托和动态对象扩展(连接继承)的组合比经典继承更强大和灵活。
  • The connections between the Constructor.prototype and instances are frail and untrustworthy in JavaScript. 在JavaScript中,Constructor.prototype和实例之间的连接是脆弱的和不值得信任的。 Using constructors can provide the illusion of a working instanceof, which could be confusing when it doesn't work across execution contexts, or doesn't work if the constructor prototype gets swapped out. 使用构造函数可以提供工作实例的错觉,当它在执行上下文中不起作用时可能会令人困惑,或者如果构造函数原型被换出则不起作用。
  • Swapping out the prototype is harder with constructors. 构造函数更难以更换原型。 You may want to do that to enable polymorphic object construction. 您可能希望这样做以启用多态对象构造。 With factories, hot swappable prototypes are easy, and can be done using .call() and .apply(). 使用工厂,热插拔原型很容易,可以使用.call()和.apply()完成。

Edit - responding to the "answer" posted by the OP: 编辑 - 回应OP发布的“答案”:

The best thing about Object.create is that it's a dedicated, low-level tool that lets you create a new object and assign any prototype you want to it without using a constructor function. 关于Object.create的最好的事情是它是一个专用的低级工具,它允许您创建一个新对象并在不使用构造函数的情况下为其分配任何原型。 There are lots of reasons to avoid constructors, covered in-depth here: Constructor function vs Factory functions 避免构造函数有很多理由,这里有深入介绍: 构造函数与工厂函数

  1. The code you use to demonstrate "less code" doesn't really demonstrate the difference between classical and prototypal inheritance at all. 用于演示“代码较少”的代码并没有真正证明经典和原型继承之间的区别。 A more typical example might look like: 更典型的示例可能如下所示:

Classical 古典

var Animal = function Animal(name) {
  this.name = name;
};

Animal.prototype.walk = function walk() {
  console.log(this.name + ' goes for a walk.');
};

var Rabbit = function Rabbit(/* name */) {
  // Because construction and instantiation are conflated, you must call super().
  Animal.prototype.constructor.apply(this, arguments);
};

// Classical inheritance is really built on top of prototypal inheritance:
Rabbit.prototype = Object.create(Animal.prototype);

// Fix the .constructor property:
Rabbit.prototype.constructor = Rabbit;

Rabbit.prototype.jump = function jump() {
  console.log(this.name + ' hops around a bit.');
};

var myRabbit = new Rabbit('Bunny George');

myRabbit.walk();
// Bunny George goes for a walk.

Prototypal 原型

var animalMethods =  {
  walk: function walk() {
    console.log(this.name + ' goes for a walk.');
  }
};

var animal = function animal(name) {
  var instance = Object.create(animalMethods);
  instance.name = name;
  return instance;
};

var rabbitMethods = {
  jump: function jump() {
    console.log(this.name + ' hops around a bit.');
  }
};

var rabbit = function rabbit(name) {
  var proto = rabbitMethods;

  // This is more commonly done like mixin({}, animalMethods, rabbitMethods);
  // where mixin = $.extend, _.extend, mout.object.mixIn, etc... It just copies
  // source properties to the destination object (first arg), where properties from
  // the last argument override properties from previous source arguments.
  proto.walk = animalMethods.walk;
  var instance = Object.create(rabbitMethods);

  // This could just as easily be a functional mixin,
  // shared with both animal and rabbit.
  instance.name = name;
  return instance;
};

var rabbit2 = rabbit('Bunny Bob');

rabbit2.walk();
// Bunny Bob goes for a walk.

The amount of code required is pretty similar, but to me, it's a LOT more clear what the prototypal stuff is doing, and it's also a lot more flexible, and has none of the classical inheritance arthritic baggage of the first example. 所需的代码量非常相似,但对我而言,原型材料的作用更为清晰,并且它也更加灵活,并且没有第一个例子的经典继承关节炎包袱。

Thank you for your amazing answer. 谢谢你的惊人答案。 But, I am not agree with most of your afirmation. 但是,我不同意你的大部分意见。 Without see the equivalent in both patterns. 没有看到两种模式中的等价物。 Some arguments are subjetive for me. 有些论据对我来说是主观的。 So i would like to focus on facts. 所以我想关注事实。 And in this answer I am going to comment what are the best points of each. 在这个答案中,我将评论每个人的最佳观点。 And without external libraries/snippets because then we can fight about which library is better. 没有外部库/片段,因为那时我们可以争论哪个库更好。

Good points about Object.create 关于Object.create好点

1. Create the instance/object using call/apply 1.使用call / apply创建实例/对象

var Shape = {
    Rectangle: function (p1, p2) {
        var rectangle = Object.create(this);
        rectangle.p1 = p1;
        rectangle.p2 = p2;
        return rectangle;
    },
    Circle: function (p, r) {
        var circle = Object.create(this);
        circle.p = p;
        circle.r = r;
        return circle;
    }
};

var rectangle = Shape.Rectangle.call(Shape, zeroPoint, Point.new(3, 4));
var circle = Shape.Circle.call(Shape, origin, 3);

This is not possible using the new . 这是不可能使用new

Good points about new 关于new好点

1. Less code 1.减少代码

function Rectangle(p1, p2) {
    this.p1 = p1;
    this.p2 = p2;
}
function Circle(p, r) {
    this.p = p;
    this.r = r;
}

vs VS

Rectangle: function (p1, p2) {
    var rectangle = Object.create(this);
    rectangle.p1 = p1;
    rectangle.p2 = p2;
    return rectangle;
},
Circle: function (p, r) {
    var circle = Object.create(this);
    circle.p = p;
    circle.r = r;
    return circle;
}

Whatever case you always have to write more code to implement the same. 无论如何,您总是需要编写更多代码来实现相同的功能。

2. Easier to maintain 2.易于维护

var rectangle = Shape.Rectangle(zeroPoint, Point.new(3, 4));
var circle = Shape.Circle(origin, 3);

What happen if tomorrow you want to change the name of Shape to Geometry? 如果明天要将Shape的名称更改为Geometry会发生什么? You have to review all your code and change all the words Shape for each instantiation of cicle or rectangle. 您必须检查所有代码并更改每个cicle或矩形实例的所有单词Shape。 This point is more remarkable when you are doing inheritance. 当你做继承时,这一点更加引人注目。 Because you always have to call exacly the same name of the constructor to access to the super.methods 因为您总是必须使用相同的构造函数名称来访问super.methods

Bonus. 奖金。 Easier to read or understand (This is subjetive) 更容易阅读或理解(这是主观的)

If in my code I'm seeing new Rectangle(...) i know I'm creating a new instance of the object Rectangle. 如果在我的代码中我看到new Rectangle(...)我知道我正在创建一个对象Rectangle的新实例。 However Shape.Rectangle(...) don't tell me if is a new object or if is just a function or if Shape is a unique instance like var Shape = new Whatever() . 但是, Shape.Rectangle(...)不会告诉我是否是一个新对象,或者只是一个函数,或者Shape是一个唯一的实例,如var Shape = new Whatever()

3. Private properties and methods 3.私有财产和方法

var Person = function() {
  var a = 5;
  this.method = function (b) { return a*b; };
};
var obj = new Person;
obj.method(5); // return 25
obj.a; // return undefined

vs VS

var Person = {
    a: 5,
    method: function (b) { return this.a*b; }
};
var obj  = Object.create(Person);
obj.method(5); // return 25
obj.a; // return 5

You always can have private methods and properties on the new pattern. 您始终可以在new模式上拥有私有方法和属性。 On the object.create pattern you can if your implementation is specific. 如果您的实现是特定的,您可以在object.create模式上。 If you do this, your code is more difficult and verbose to write (But this is a personal opinion). 如果你这样做,你的代码写起来会更加困难和冗长(但这是个人意见)。

4. I can pass paramenter on the constructor 我可以在构造函数上传递paramenter

var Person = function(a) {
  this.method = function (b) { return a*b; };
};
var obj = new Person(5);
obj.method(5); // return 25

vs VS

var Person = {
    method: function (b) { return a*b; }
};
var obj  = Object.create(Person);
obj.method(4); //Error

5. instanceof 5. instanceof

No way to do instanceof natively with the Object.create pattern. 无法使用Object.create模式本机地执行instanceof。

6. Performance 6.表现

I leave this for the last one, because most of the other points can be solved with extra javascript. 我把它留给最后一个,因为大多数其他点可以用额外的javascript来解决。 But in this case can't be the same to the new pattern. 但在这种情况下new模式不能相同。 Also i think this is the most important advantage for new pattern. 另外我认为这是new模式最重要的优势。 Because if you are programming for browser the performance sometimes doesn't matter. 因为如果您正在为浏览器编程,性能有时无关紧要。 But if you are working of the backend and you are making a very big and scalable app, the performance matter. 但是如果你正在使用后端并且你正在制作一个非常大且可扩展的应用程序,那么性能就很重要。 Why Paypal leave java and go to node.js? 为什么Paypal离开java并转到node.js? Because the performance it's very important in big projects. 因为性能在大项目中非常重要。

Conclusion 结论

So, if new it's 10 times faster than Object.create . 所以,如果是new它比Object.create快10倍。 I think only for this reason, and only for this reason worth keep programming with new. 我想只是出于这个原因,并且只是因为这个原因值得用新的编程。 Besides, give me more flexibility and i can do things with new pattern that I can't with Object.create . 此外,给我更多的灵活性,我可以使用Object.create不能用new模式做事。 And I agree that the nature of the Javascript is to using the Object.create . 我同意Javascript的本质是使用Object.create But i think i get more benefits if i use new . 但我认为如果我使用new我会获得更多的好处。

Sorry for my English. 对不起我的英语不好。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM