Home > Article > Web Front-end > JavaScript object creation patterns and best practices
"Creating objects" in JavaScript is a complex topic. This language provides many ways to create objects, and both newbies and experienced users may feel confused about which one to choose. However, although there are many ways to create objects, and the syntax may seem very different, they may actually be more similar than you think. This article will take you on a journey to sort out object creation methods, revealing the dependencies and progressive relationships between different methods.
Our first stop is undoubtedly the simplest way to create objects, object literals. JavaScript always preaches that it can create objects "out of nothing" - no classes, no templates, no prototypes - and "pop", an object with methods and data appears.
var o = { x: 42, y: 3.14, f: function() {}, g: function() {} };
But this method has a disadvantage: if we want to create an object of the same type elsewhere, we have to copy and paste the methods, data and initialization of this object. We need a way to create objects of the same type in batches instead of just one.
Our next stop is the factory function. Obviously, it is easiest to use this method to create a class of objects with the same structure, interface, and implementation. We do not create an object literal directly, but use the object literal as the return value of the function. When we need to create objects of the same type multiple times or in multiple places, we only need to call this function.
function thing() { return { x: 42, y: 3.14, f: function() {}, g: function() {} }; } var o = thing();
But this method also has a disadvantage: it will cause memory expansion, because each object contains an independent copy of the factory function. In theory we want all objects to share a copy of the factory function.
JavaScript provides a built-in mechanism for sharing data between objects, called the prototype chain. When we access a property of an object, it delegates to some other object to complete the request. We can use this to modify the factory function so that each object it creates contains only its own unique data, while requests for other properties are all delegated to a common object on the prototype chain.
var thingPrototype = { f: function() {}, g: function() {} }; function thing() { var o = Object.create(thingPrototype); o.x = 42; o.y = 3.14; return o; } var o = thing();
In fact, JavaScript itself has built-in mechanisms to support this common pattern. We don't need to create this shared object (prototype object) ourselves. JavaScript will automatically create a prototype object for each function. We can put the shared data directly in this object.
thing.prototype.f = function() {}; thing.prototype.g = function() {}; function thing() { var o = Object.create(thing.prototype); o.x = 42; o.y = 3.14; return o; } var o = thing();
But this method also has a disadvantage: it will lead to duplication. The first and last lines of the above thing function are repeated in every "factory function of the delegate prototype", with almost no difference.
We can extract those repetitive codes and put them into a custom function. This function will create an object and establish a delegation (inheritance) relationship with the prototype of some other arbitrary function (parameter function). Then we use the newly created object as a parameter, call this function (parameter function), and finally return this new object.
function create(fn) { var o = Object.create(fn.prototype); fn.call(o); return o; } // ... Thing.prototype.f = function() {}; Thing.prototype.g = function() {}; function Thing() { this.x = 42; this.y = 3.14; } var o = create(Thing);
In fact, JavaScript also has built-in support for this method. The create function we defined is actually a basic implementation of the new keyword, so we can easily replace create with new.
Thing.prototype.f = function() {}; Thing.prototype.g = function() {}; function Thing() { this.x = 42; this.y = 3.14; } var o = new Thing();
The station we have arrived at is often referred to as the ES5 category. It creates objects through functions, delegates the data that needs to be shared to prototype objects, and uses the new keyword to handle repeated logic.
But this method also has a disadvantage: it is verbose and ugly, and it will be even more verbose and ugly when implementing inheritance.
JavaScript最新的相关改进是ES6 类,用新语法来实现上述功能要简洁得多。
class Thing { constructor() { this.x = 42; this.y = 3.14; } f() {} g() {} } var o = new Thing();
多年以来,JavaScript开发者们与原型链的关系总是若即若离,纠缠不清。而今天我们最有可能遇到的两种创建对象的方式,一种是强烈依赖原型链的class语法,另一种则是完全不依赖原型链的工厂函数语法。这两种方式在性能上和特点上是不一样的——尽管差别不太大。
今天的JavaScript引擎已经经过了大幅度的优化,以至于很难通过JavaScript代码来推断怎样会比较快。关键在于测量方法。然而测量方法有时也会失灵。通常每六周就会有更新的JavaScript引擎发布,而在这之前采取的测量方法,和基于这种测量方法做出的决策都有可能失去意义。因此,我的经验法则是选择最官方、最广泛使用的语法,因为大多数时候它经历的实践检验最多,因而性能是最高的。目前来说class语法最符合这一点,在我写这篇文章时,class语法大约比返回字面量的工厂函数快3倍。
随着ES6的发布,类与工厂函数之间曾经存在的几点差异消失了。现在,工厂函数和类都能够强制实现真正的私有数据——工厂函数通过闭包实现,类通过WeakMap实现。两者都能实现多重继承——工厂函数可以将其他属性混入自己的对象,类也可以将其他属性混入自己的原型,或者通过类工厂,通过代理也能实现。工厂函数和类也都可以在需要的时候返回任意对象,语法也都很简单。
综合考虑,我更倾向于class语法。它标准、简单、干净、快速,还提供了所有曾经只有函数工厂才具备的特点。
以上就是JavaScript 创建对象模式与最佳实践的内容,更多相关内容请关注PHP中文网(www.php.cn)!