繁体   English   中英

javascript中的构造函数中的对象字面量和具有值的类有什么区别?

[英]What is a difference between an object literal and a class with values in constructor in javascript?

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

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

export default new Page();

我一直在做一些研究,但我无法理解为什么它没有用对象文字解决:

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

使用它们的后果是什么?

区别很明显,但基本上都是 JavaScript 对象,尽管具有不同的属性和值。 列出语言级别的所有差异将是一个很长的故事,阅读和理解基于JavaScript 原型的继承是明智的,但最重要的差异是:

  • Page是一个原型对象:每当您使用new Page()创建类Page的对象时,构造函数都会被调用, this指的是正在创建的对象,而不是Page本身。 您在对象上访问的任何属性都将沿着所谓的“原型链”进行搜索,包括所谓的原型对象。 这个原型对象可以通过Page.prototype访问,事实上,你在类Page中定义的所有方法也是这个原型对象的属性。 与自己的属性旨在引用特定于对象的唯一对象或基元不同,JavaScript 中的函数不必在对象或函数创建期间绑定到对象,并且可以在对象之间共享(例如,属于同一类)并在实际实例上调用,而不是它们可能所属的原型。 换句话说,构造函数中的this.nameInput实际上向使用new Page()创建的对象添加了一个名为nameInput的属性,而不是原型,而构造函数本身( constructor )和您可能添加到Page任何非静态方法都会添加为Page.prototype属性。 顺便说一下,正如您自然期望的那样,构造函数可以作为Page.prototype.constructor访问。 Page.prototype.constructor === Page计算结果为true

  • { nameInput: ... }这样的形式的表达式创建一个对象,其原型是Object.prototype ,实际上是最基本的对象形式,“没有原型”,因此没有超类或超出基本对象原型对象的任何特征可以提供。 任何这样的{ ... }对象在其原型链中似乎具有的任何属性,包括方法,都是Object.prototype属性。 这就是为什么你可以做({}).toString()({}).hasOwnProperty("foobar")而不必实际toStringhasOwnProperty在你的对象属性- toStringhasOwnProperty是性能Object.prototype提及两个分别称为toStringhasOwnProperty不同方法,JavaScript在您的对象上创建一个特殊的属性称为__proto__引用Object.prototype 这就是它知道如何“走原型链”的方式。 顺便说一下,函数的名称本身并不重要——我可以在引用匿名函数的对象上添加一个属性: var foo = ({}); foo.bar = function() { }; var foo = ({}); foo.bar = function() { }; 并使用foo.bar()调用所述未命名函数。

您似乎犯的一个错误是将类的对象与类混淆,否则您不会将export default class Page { ... }export const Page = { nameInput: Selector(...) }前者创建一个可作为Page访问的类,每当创建该类的对象时,该类都用作原型对象,而后者创建一个可作为Page访问的对象,其中包含nameInput指的是计算表达式Selector("#developer-name") (使用唯一参数"#developer-name"调用Selector )。 根本不是一回事,更何况前者有Page指的是一个类(在 JavaScript 中总是一个原型),而后者有Page指的是一个似乎不符合类模式的对象。

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

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

这里会发生什么? 您使用未命名的函数作为对象构造函数创建一个新对象。 效果绝对等同于以其他方式使用new Page创建对象,其中Page是您原来的 ES6 类(ECMAScript 6 是将class语法添加到 JavaScript 的语言规范)。

您也可以这样做,同样等效于如果您使用class Page ...定义Page

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

var foo = new Page();

Page.prototype将是foo的原型对象,可作为foo.__proto__访问,否则您可以像foo.bar()一样在foo上调用实例方法,前提是您至少在Page.prototype上定义了bar属性:

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

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

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

事实上,如果浏览器必须解释以下代码,上面的内容就是它在内部会做的事情:

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

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

如果您了解所有这一切的核心是基于原型的继承,那么您对此的所有问题都会消失,因为 JavaScript 的基本机制很少能支持其 99% 的特性。 你还可以优化你的对象设计和访问模式,知道什么时候选择 ES6 类,什么时候不选择,什么时候使用对象文字( { prop: value, ... } ),什么时候不选择,以及如何共享属性之间的对象更少。

类可以被认为是一个蓝图,它们最终都提供了一个对象。 但正如对象字面量名称所暗示的那样,您实际上是在那里创建它,然后使用这种“字面量”语法。 但是,我们将使用一个类从 1 个基本蓝图实例化新实例。

let x = { myProp: undefined }
let y = { myProp: undefined }

x.myProp = "test";
y.myProp // undefined

在这里,我们看到我们创建了两个单独的实例,但我们将不得不重复代码。

class X { }

let x = new X();
let y = new X();

一个类不需要重复代码,因为它全部封装在X应该是什么的想法中,一个蓝图。

与上面[在文字中]类似,我们有两个单独的实例,但它更清晰、更具可读性,并且我们希望对这个“X”对象的每个实例进行的任何更改现在都可以在类中简单地更改。

还有许多其他好处,甚至是专门用于面向对象编程的范式,请阅读此处了解更多信息: https : //www.internalpointers.com/post/object-literals-vs-constructors-javascript

进一步探讨构造函数问题...在其他语言中,我们有字段。 我相信当你在构造函数中分配一个字段时,它只会创建一个类似底层的字段(我说底层是因为 JavaScript 是基于原型的,并且类语法是语法糖,以帮助熟悉其他语言的类语法的程序员更容易地编写原型)。

这是 C# 中的示例。

public class X{
   private int y;

   X() {
      this.y = 5;
   }
}

在其他语言中在构造函数中分配字段更像是一种约定,所以我认为这与 JavaScript 中的它有关。

希望这可以帮助。

通过将其声明为类,您可以稍后使用 .constructor.name 确定它是什么类型的对象:

 class Page { constructor () { this.nameInput = "something"; } // No comma anotherMethod() { } } const pageClass = new Page(); const pageLiteral = { nameInput: "something" , // must have a comma anotherMethod() { } } console.log("Name of constructor for class: ", pageClass.constructor.name); // Page console.log("Name of constructor for literal: ", pageLiteral.constructor.name); // Object

暂无
暂无

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

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