class
keyword and the so-called factory functions (Factory Function). The author of the material, the translation of which we publish today, explores and compares these two concepts in search of an answer to the question of the pros and cons of each of them.class
keyword appeared in ECMAScript 2015 (ES6), with the result that we now have two competing object creation patterns. In order to compare them, I will describe the same object ( TodoModel
), using the syntax of the classes, and applying the factory function.TodoModel
description using the class
keyword: class TodoModel { constructor(){ this.todos = []; this.lastChange = null; } addToPrivateList(){ console.log("addToPrivateList"); } add() { console.log("add"); } reload(){} }
function TodoModel(){ var todos = []; var lastChange = null; function addToPrivateList(){ console.log("addToPrivateList"); } function add() { console.log("add"); } function reload(){} return Object.freeze({ add, reload }); }
class
keyword are publicly available. var todoModel = new TodoModel(); console.log(todoModel.todos); //[] console.log(todoModel.lastChange) //null todoModel.addToPrivateList(); //addToPrivateList
var todoModel = TodoModel(); console.log(todoModel.todos); //undefined console.log(todoModel.lastChange) //undefined todoModel.addToPrivateList(); //taskModel.addToPrivateList is not a function
class
keyword. todoModel.reload = function() { console.log("a new reload"); } todoModel.reload(); //a new reload
Object.freeze(TodoModel.prototype)
after declaring a class, or using the decorator to “freeze” classes when it is supported.Object.freeze()
command to process the returned object, which contains only the public methods of the new object. Private data of this object can be modified, but this can be done only through these public methods. todoModel.reload = function() { console.log("a new reload"); } todoModel.reload(); //reload
class
keyword are subject to the long-term problem of losing context this
. For example, this
loses context in nested functions. This not only complicates the programming process, such behavior is also a constant source of errors. class TodoModel { constructor(){ this.todos = []; } reload(){ setTimeout(function log() { console.log(this.todos); //undefined }, 0); } } todoModel.reload(); //undefined
this
loses context when using the corresponding method in a DOM event: $("#btn").click(todoModel.reload); //undefined
this
not used here. function TodoModel(){ var todos = []; function reload(){ setTimeout(function log() { console.log(todos); //[] }, 0); } } todoModel.reload(); //[] $("#btn").click(todoModel.reload); //[]
this
context when using classes, but at the same time they create a new problem. Namely, when using arrow functions in classes, the keyword this
no longer loses context in nested functions. However, this
loses context when dealing with DOM events.TodoModel
class using pointer functions. It is worth noting that in the process of refactoring, when replacing ordinary functions with pointer ones, we lose something important for the readability of the code: the names of functions. Take a look at the following example. // setTimeout(function renderTodosForReview() { /* code */ }, 0); // setTimeout(() => { /* code */ }, 0);
var renderTodosForReview = () => { /* code */ }; setTimeout(renderTodosForReview, 0);
new
. And when creating objects using factory functions, new
not required. However, if the use of new
improves the readability of the code, this operator can also be used with factory functions, there will be no harm. var todoModel= new TodoModel();
new
with a factory function, the function simply returns the object created to it.User
object to work with authorization mechanisms. I created a couple of such objects using both approaches described here.User
object using the class: class User { constructor(){ this.authorized = false; } isAuthorized(){ return this.authorized; } } const user = new User();
function User() { var authorized = false; function isAuthorized(){ return authorized; } return Object.freeze({ isAuthorized }); } const user = User();
class
keyword are vulnerable to attacks if the attacker has an object reference. Since all properties of all objects are public, an attacker can use other objects to gain access to the object in which he is interested.user
variable is global. To verify this, open the example code and modify the user
variable from the console.top
to plunkerPreviewTarget(run.plnkr.co/)
. user.authorized = true; // user.isAuthorized = function() { return true; } // API console.log(user.isAuthorized()); //true
SpecialService
is a descendant of the class Service
. class Service { log(){} } class SpecialService extends Service { logSomething(){ console.log("logSomething"); } } var specialService = new SpecialService(); specialService.log(); specialService.logSomething();
Object.assign()
command to copy all properties from existing objects. For example , suppose we need to reuse all members of the Service
object in the SpecialService
object. function Service() { function log(){} return Object.freeze({ log }); } function SpecialService(args){ var standardService = args.standardService; function logSomething(){ console.log("logSomething"); } return Object.freeze(Object.assign({}, standardService, { logSomething })); } var specialService = SpecialService({ standardService : Service() }); specialService.log(); specialService.logSomething();
TodoModel
object TodoModel
you already know to clarify the difference between objects and data structures. function TodoModel(){ var todos = []; function add() { } function reload(){ } return Object.freeze({ add, reload }); }
TodoModel
object TodoModel
responsible for storing and managing the list of todo
objects. TodoModel
is an OOP object, the one that provides the behavior and hides the data. The application will have only one copy of it, so when you create it using the factory function, no additional memory costs are required.todos
array are data structures. There can be many such objects in the program, but these are regular JavaScript objects. We are not interested in making their methods private. Rather, we strive to ensure that all their properties and methods would be publicly available. As a result, all these objects will be built using a prototype system, so we can save memory. They can be created using a regular object literal or by Object.create()
command.instanceof
. All objects are created using object literals, some of them with public methods (OOP objects), some with public properties (data structures).Object.assign()
.this
, leading to permanent errors due to the loss of context, put classes in second place compared to factory functions. As an exception, classes are resorted to when they are used in the framework used, for example, in React.Source: https://habr.com/ru/post/352198/
All Articles