对象文字和javascript中构造函数中带有值的类之间有什么区别?

问题描述:

我一直在进行testcafe的端到端测试,在他们的文档中,我发现以下针对页面模型的解决方案:

I've been working on end to end test in testcafe and in their documentation I found following solution for Page Model:

class Page {
    constructor () {
        this.nameInput = Selector('#developer-name');
    }
}

export default new Page();

我一直在做一些研究,但我无法理解为什么不能使用对象文字:

I've been doing some research and I cannot get my head around why it is not resolved with an object literal:

export const Page = {
    nameInput: Selector('#developer-name');
}

使用它们每个会有什么后果?

What are consequences of using each of them?

差异很大,但从本质上讲都是JavaScript对象,尽管它们具有不同的属性和值。列出语言级别上的所有差异将是一个漫长的故事,您最好在基于JavaScript原型的继承,但最重要的区别是:

The difference is significant but fundamentally both are JavaScript objects, albeit with different properties and values. Listing all differences on the level of the language would be a long story and you'd be wise to read and understand on JavaScript prototype-based inheritance, but the most important differences are:


  • Page 是原型对象:每当使用 new Page创建具有 Page 类的对象时(),使用 this 来引用正在创建的对象,而不是 Page 本身。您在对象*问的任何属性都沿着所谓的原型链进行搜索,包括所谓的原型对象。可以使用 Page.prototype 访问该原型对象,实际上,您在 Page 类中定义的所有方法也都可以访问。此原型对象的属性。与旨在引用唯一对象或特定于对象的原语的自身属性不同,JavaScript在对象或函数创建期间不必将功能绑定到对象,并且可以在对象之间共享(属于相同的类),并且在实际实例上调用,而不是它们可能属于的原型。换句话说,构造函数中的 this.nameInput 实际上会向使用 nameInput 的属性。 c $ c> new Page(),而不是原型,而构造函数本身( constructor )和您可能添加到的任何非静态方法 Page 将作为 Page.prototype 的属性添加。顺便提一句,您可以通过 Page.prototype.constructor 访问该构造函数。顺便说一下, Page.prototype.constructor ===页面的评估结果为 true

  • Page is a prototype object: whenever you create an object of class Page with new Page(), the constructor function is called with this referring to the object being created, not Page itself. Any property you access on the object is searched along the so-called "prototype chain", including the so-called prototype object. This prototype object can be accessed with Page.prototype and in fact, all methods you define in the class Page are also properties of this prototype object. Unlike own properties designed to refer to unique objects or primitives specific to an object, functions in JavaScript don't have to be bound to an object during object or function creation and can be shared between objects (belonging to the same class, for instance) and are called on the actual instance, not the prototype to which they may belong. In other words, this.nameInput in your constructor actually adds a property named nameInput to the object being created with new Page(), not the prototype, while the constructor itself (constructor) and any non-static methods you might add to Page will be added as properties of Page.prototype. The constructor is accessed as Page.prototype.constructor, by the way, as you'd naturally expect. Page.prototype.constructor === Page evaluates to true, by the way.

{nameInput:...} 这样的形式的表达式创建一个对象,其原型为 Object .prototype ,实际上是对象的最基本形式,带有没有原型,因此没有超类或超出基本对象原型对象所能提供的任何特征。此类 {...} 对象在其原型链中似乎具有的任何属性,包括方法,都是 Object.prototype $的属性。 c $ c>。这就是为什么您可以执行({})。toString()({})。hasOwnProperty( foobar)$ c $的原因c>在对象中实际上没有 toString hasOwnProperty 属性- toString hasOwnProperty Object.prototype 的属性,指的是称为的两种不同方法toString hasOwnProperty ,JavaScript在对象上创建了一个 特殊属性,称为 __ proto__ 指的是 Object.prototype 。这就是它知道如何走原型链的方式。顺便说一句,函数本身的 names 并不重要,我可以在引用匿名函数的对象上添加一个属性: var foo =({}) ; foo.bar = function(){}; 并使用 foo.bar()调用未命名的函数。

An expression of the form like { nameInput: ... } creates an object which prototype is Object.prototype, in practice the most basic form of object with "no prototype" and thus no superclass or any traits beyond what the fundamental object prototype object could provide. Any properties any such { ... } object may seem to have through its prototype chain, including methods, are properties of Object.prototype. This is why you can do ({}).toString() or ({}).hasOwnProperty("foobar") without actually having toString or hasOwnProperty properties in your object -- toString and hasOwnProperty are properties of Object.prototype referring to two distinct methods called toString and hasOwnProperty, respectively, and JavaScript creates a special property on your object called __proto__ referring to Object.prototype. This is how it knows how to "walk the prototype chain". The names of functions themselves do not matter like that, by the way -- I may add a property on an object referring to an anonymous function: var foo = ({}); foo.bar = function() { }; and call said unnamed function with foo.bar().

您似乎犯的一个错误是将类的对象与类混淆,否则您将不会比较 export默认类Page {...} export const Page = {nameInput:Selector(...)} -前者创建一个类可作为 Page 访问,在创建该类的对象时将用作原型对象,而后者创建的对象可作为 Page $ c访问$ c>包含 nameInput ,引用计算表达式 Selector(#developer-name)的结果(调用选择器和唯一的参数#developer-name )。 完全不是是同一件事,更不用说前者具有 Page 是指一个类(在JavaScript中始终是原型),而后者具有 Page 引用的对象似乎与类的模式不符。

One mistake you appear to be making is confusing an object of a class with the class, otherwise you wouldn't compare export default class Page { ... } to export const Page = { nameInput: Selector(...) } -- the former creates a class accessible as Page which is used as the prototype object whenever objects of the class are created, while the latter creates an object accessible as Page which contains nameInput referring to result of evaluating expression Selector("#developer-name") (calling Selector with the sole argument "#developer-name"). Not the same thing at all, not to mention that former has Page refer to a class (invariably a prototype in JavaScript), while latter has Page refer to an object that does not seem to fit the pattern of a class.

有趣的事情始于您请意识到,由于类是与JavaScript中的其他对象一样的对象,因此如果您知道基于原型的继承是如何工作的,则可以将任何对象用作类:

The interesting things start when you realize that since a class is an object like any other in JavaScript, any object can be used as a class if you know how prototype-based inheritance works:

new (function() { this.nameInput = Selector("#developer-name"); })();

这里会发生什么?您创建一个具有未命名函数的新对象作为对象构造函数。这种效果绝对等同于用 new Page 创建对象,而 Page 是您的原始ES6类(ECMAScript 6是在JavaScript中添加 class 语法的语言规范。)

What happens here? You create a new object with an unnamed function as the object constructor. The effect is absolutely equivalent to otherwise creating the object with new Page with Page being your original ES6 class (ECMAScript 6 is the language specification that adds class syntax to JavaScript).

您也可以执行此操作,再次等同于您定义了 Page class Page ...

You can also do this, again equivalent to if you defined Page with class Page ...:

function Page() {
    this.nameInput = Selector("#developer-name");
}

var foo = new Page();

Page.prototype 将是原型对象for foo ,可以通过 foo .__ proto __ 访问,否则可以在 foo 像 foo.bar(),只要您在以下位置定义 bar 属性至少 Page.prototype

Page.prototype will be the prototype object for foo, accessible as foo.__proto__ and otherwise making it possible for you to call instance methods on foo like foo.bar(), provided you define bar property on at least Page.prototype:

function Page() {
    this.nameInput = Selector("#developer-name");
}

Page.prototype.bar = function() {
    console.log(this.nameInput);
}

var foo = new Page();
foo.bar();

事实上,以上是浏览器在内部必须解释以下代码的情况:

In fact, the above is what browser would do internally if it had to interpret the following code:

class Page {
    constructor() {
        this.nameInput = Selector("#developer-name");
    }
    bar() {
        console.log(this.nameInput);
    }
}

这超出了我列出差异的答案的范围最后两种方法之间的区别(与您提出的两种方法不同),但区别在于使用 class Page ... 页面在某些用户代理中不是窗口的属性,而具有功能页面... 是的。部分原因是历史原因,但请放心,到目前为止,尽管我可以想象更智能的JavaScript运行时将能够更好地优化后者的形式,但使用这两种方法定义构造函数和原型几乎是相同的(因为这是一个原子声明,而不仅仅是

It is beyond the scope of my answer to list differences between the two last approaches (isn't the same thing as the two approaches you proposed), but one difference is that with class Page ..., Page is not a property of window in some user agents while with function Page ... it is. It's partly historical reasons, but rest assured that so far defining constructors and prototype using either approach is pretty much the same, although I can imagine smarter JavaScript runtimes will be able to optimize the latter form better (because it's an atomic declaration, and not just a sequence of expressions and statements).

如果您了解基于原型的继承是所有这些内容的核心,那么有关此问题的所有问题都将自己解决。几乎没有JavaScript的基本机制支持其99%的特性。您还可以优化对象设计和访问模式,知道何时选择ES6类,何时不选择ES6类,何时使用对象文字( {prop:value,...} ),何时不使用以及如何在属性之间共享较少的对象。

If you understand prototype-based inheritance at the heart of all of this, all your questions about this will fall away by themselves as very few fundamental mechanisms of JavaScript support 99% of its idiosyncrasies. You'll also be able to optimize your object design and access patterns, knowing when to choose ES6 classes, when not to, when using object literals ({ prop: value, ... }) and when not to, and how to share fewer objects between properties.