简体   繁体   English

为什么要避免在 JavaScript 中创建基元对象?

[英]Why to avoid creating objects of primitives in JavaScript?

I am following a JavaScript tutorial on W3Schools.我正在关注 W3Schools 上的 JavaScript 教程。 While reading almost on each page they give note to user to "Avoid creating objects" and to use primitive data types instead.几乎在每一页上阅读时,他们都会提醒用户“避免创建对象”并改用原始数据类型。 They give reason for this as "code becomes difficult to understand or execution speed will be decreased if object are used".他们给出的理由是“如果使用 object,代码变得难以理解或执行速度将降低”。 Is it true that we should avoid creating objects in JavaScript?我们是否应该避免在 JavaScript 中创建对象?

For example:例如:

var value = new Number(1);  // Avoid this
var value = 1;              // do something like this instead.

The statement "avoid creating objects" on its own is absurd in JavaScript, which has objects everywhere and is one of the most object-oriented languages in existence. “避免创建对象”这句话本身在 JavaScript 中是荒谬的,JavaScript 到处都有对象,并且是现存最面向对象的语言之一。 But "avoid creating object versions of primitives," which is what the code you quote does, is valid.但是“避免创建基元的对象版本”,这就是您引用的代码所做的,是有效的。 That is, avoid new String , new Number , and new Boolean .也就是说,避免使用new Stringnew Numbernew Boolean

JavaScript has both primitive and object versions of strings, numbers, and booleans. JavaScript 有字符串、数字和布尔值的原始版本和对象版本。 There's almost never any reason to create the object version of any of them explicitly, and doing so can indeed lead to confusion;几乎没有任何理由明确地创建它们中的任何一个的对象版本,这样做确实会导致混淆; see inline comments:见内嵌评论:

 var s1, s2, n1, n2; // These are false because with ===, an object is never equal to a non-object s1 = new String("hi"); s2 = "hi"; console.log(s1 === s2); // false n1 = new Number(42); n2 = 42; console.log(n1 === n2); // also false // These are false because even with ==, two *different* objects are never equal // (even if they're equivalent) s1 = new String("what the..."); s2 = new String("what the..."); console.log(s1 == s2); // also false n1 = new Number(42); n2 = new Number(42); console.log(n1 == n2); // also false

The object versions of strings, numbers, and booleans largely exist to enable methods on primitives to be provided using the same mechanism that provides methods to object types.字符串、数字和布尔值的对象版本主要是为了能够使用为对象类型提供方法的相同机制来提供基元上的方法。 When you do当你做

console.log("foo".toUpperCase()); // "FOO"

a temporary object is created for the primitive string "foo" , and then the toUpperCase property is read from that object.原始字符串"foo"创建一个临时对象,然后从该对象读取toUpperCase属性。 Since the object inherits from String.prototype , it has toUpperCase and all is well.由于对象继承自String.prototype ,因此它具有toUpperCase并且一切正常。 Once the operation is done, the temporary object is thrown away (unless something keeps a reference to it, but nothing does and nothing can with toUpperCase , you'd have to add a method to String.prototype that returned the object in order for it to be kept around).操作完成后,临时对象将被丢弃(除非某些东西保留对它的引用,但没有任何东西,也不能使用toUpperCase ,您必须向String.prototype添加一个方法,该方法返回对象以便它保持在周围)。

It changes the intuitive way the operators behave with numbers, strings and booleans:它改变了运算符处理数字、字符串和布尔值的直观方式:

  • the strict comparison ( === ) breaks when any of the numbers is constructed, so 42 === 42 is true, while 42 === new Number(42) is not,当构造任何数字时,严格比较 ( === ) 会中断,因此42 === 42为真,而42 === new Number(42)不是,
  • the abstract comparison ( == ) breaks when both numbers are objects, so 42 == new Number(42) is true, while new Number(42) == new Number(42) is not,当两个数字都是对象时,抽象比较 ( == ) 会中断,因此42 == new Number(42)为真,而new Number(42) == new Number(42)不是,
  • the typeof operator gives different result when a number is constructed, so typeof(42) is number , but typeof(new Number(42)) is object , typeof运算符在构造数字时给出不同的结果,因此typeof(42)number ,但typeof(new Number(42))object
  • when converted to a boolean, 0 is false, but new Number(0) is true, so the following two will have different behavior:转换为布尔值时, 0为假,但new Number(0)为真,因此以下两个将有不同的行为:

 var a = 0; if (a) console.log("not zero"); else console.log("zero!"); // "zero!" var b = new Number(0); if (b) console.log("not zero"); // "not zero" else console.log("zero!");

So, avoid new Number , new String and new Boolean .因此,避免使用new Numbernew Stringnew Boolean

Apart from that, there is the issue of using / not using new with constructors.除此之外,还有在构造函数中使用/不使用new的问题。 It stems from several facts:这源于几个事实:

  • in JS, a constructor is a regular function, using this.foo syntax to add new properties and methods;在 JS 中,构造函数是一个普通函数,使用this.foo语法来添加新的属性和方法;
  • when invoked without the new keyword, this becomes the global object, leading to side effects.在没有new关键字的情况下调用时, this成为全局对象,导致副作用。

As a result, a tiny mistake can have catastrophic effects:因此,一个微小的错误可能会产生灾难性的影响:

 color = "blue"; var Fruit = function(color) { this.color = color; return this; }; var apple = new Fruit("green"); console.log(apple.color); // "green" -- okay console.log(color); // "blue" -- okay var banana = Fruit("yellow"); console.log(banana.color); // "yellow" -- okay console.log(color); // "yellow" -- wait, what? console.log(banana.apple); // "{ color: 'green' }" -- what?? console.log(banana.document); // "{ location: [Getter/Setter] }" -- what???

(That's why some people resort to adding explicit checks in the constructor, or using closures instead. But that's for another story.) (这就是为什么有些人诉诸于在构造函数中添加显式检查,或使用闭包代替的原因。但那是另一回事了。)

Do not use new when invoking a Number() .调用Number()时不要使用new Source: JSLint...资料来源:JSLint...

The "Do not use {a} as a constructor" error is thrown when JSLint, JSHint or ESLint encounters a call to String, Number, Boolean, Math or JSON preceded by the new operator.当 JSLint、JSHint 或 ESLint 遇到对以 new 运算符开头的 String、Number、Boolean、Math 或 JSON 的调用时,将抛出“不要使用 {a} 作为构造函数”错误。 (Source: LintErrors.com: Do not use {a} as a constructor (来源: LintErrors.com:不要使用 {a} 作为构造函数

 console.log(Number("3") == Number("3"))

Everybody only say "avoid using", "it can lead to confusion", "typeof x behaves weird", and for the most part they are right.每个人都只会说“避免使用”、“它会导致混乱”、“typeof x 的行为很奇怪”,而且大多数情况下他们是对的。 But nobody is able to give you one single reason as to why you would want to use the constructor instead.但是没有人能给你一个单一的理由来说明你为什么要使用构造函数。

When you have many variables that has the same value, then you will allocate much more memory, if you instead construct a new class instances and use that one instead, then you would only allocate 1 single item.当您有许多具有相同值的变量时,您将分配更多的 memory,如果您改为构建一个新的 class 实例并使用该实例,那么您将只分配 1 个单个项目。 and the rest of your variables would just be pointers to the same object.并且变量的 rest 将只是指向相同 object 的指针。

This could technically increase speed when you use structuredClone (but i don't know, haven't bench tested it)当您使用结构化克隆时,这可以从技术上提高速度(但我不知道,还没有对它进行测试)

Something that i have tested out at least is to see how much disc space you allocate when using IndexedDB.我至少测试过的东西是看看你在使用 IndexedDB 时分配了多少磁盘空间。

// small simple kv indexeddb storage
let p,query=(e,...r)=>(p??=new Promise((e=>{const r=indexedDB.open("kv");r.onupgradeneeded=()=>r.result.createObjectStore("kv"),r.onsuccess=()=>{const t=r.result;query=(e,...r)=>{const n=t.transaction("kv","readwrite").objectStore("kv")[e](...r);return new Promise(((e,r)=>{n.onsuccess=()=>e(n.result),n.onerror=()=>r(n.error)}))},e()}}))).then((()=>query(e,...r)));
var kv=(...e)=>query(...e);

var arr = Array(1024).fill('a'.repeat(1024))

await kv('put', arr, 'stuff')
await kv('get', 'stuff')

var est = await navigator.storage.estimate()
est.usageDetails.indexedDB // chrome 105 allocated 1055761 bytes

now if we do the same thing but using slightly different thing by using string constructor instead:现在,如果我们做同样的事情,但使用字符串构造函数略有不同:

// Now you are using the same instance instead.
var arr = Array(1024).fill(new String('a'.repeat(1024)))

await kv('put', arr, 'stuff')
await kv('get', 'stuff')

var est = await navigator.storage.estimate()
est.usageDetails.indexedDB // chrome 105 allocated 7353 bytes

now you saved about 1055761-7353 = 1048408 bytes...现在您节省了大约 1055761-7353 = 1048408 字节...

If you want to test this out for yourself, always open up a new inkognito window and await both put/get operators, estimate can maybe give wrong value otherwise.如果您想自己测试一下,请始终打开一个新的 inkognito window 并await两个 put/get 运算符,否则估计可能会给出错误的值。 and deleting it may not always clean up properly, that's why you should create a new inkognito window every time you want to compare stuff.并且删除它可能并不总是正确清理,这就是为什么你每次想要比较东西时都应该创建一个新的inkognito window。

But in the end: yeaa... maybe don't use the constructor after all.但最后:是的......也许毕竟不要使用构造函数。 it's almost never a good thing.这几乎从来都不是一件好事。

Just wanted you to know what the "real" differences is by using objects instead只是想让您通过使用对象来了解“真正的”差异是什么

...also if you use NodeJS v8.serialize(value) then the same example will yield a smaller Buffer when you use the same object instances (as the rest will just be pointers) ...此外,如果您使用v8.serialize(value) ,那么当您使用相同的 object 实例时,相同的示例将产生更小的缓冲区(因为 rest 将只是指针)

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

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