Crockford-style prototype story; looking for an elegant solution

I often use the Crockford prototype circuit when writing JavaScript programs. I thought I understood all the β€œsweeps”, but I discovered one that I had not thought of before. I would like to know if anyone has a best practice for handling it.

Here is a simple example:

// Here the parent object var MyObject = { registry: {}, flatAttribute: null, create: function () { var o, F = function () {}; F.prototype = this; o = new F(); return o; } }; // instance is an empty object that inherits // from MyObject var instance = MyObject.create(); // Attributes can be set on instance without modifying MyObject instance.flatAttribute = "This is going to be applied to the instance"; // registry doesn't exist on instance, but it exists on // instance.prototype. MyObject registry attribute gets // dug up the prototype chain and altered. It not possible // to tell that happening just by examining this line. instance.registry.newAttribute = "This is going to be applied to the prototype"; // Inspecting the parent object // prints "null" console.log(MyObject.flatAttribute); // prints "This is going to be applied to the prototype" console.log(MyObject.registry.newAttribute); 

I want to feel safe that any changes that can be made to an instance do not extend to a change in inheritance. This is not the case when the attribute is an object, and I set a nested property.

The solution is to reinitialize all the attributes of the object in the instance. However, one of the claimed benefits of using this template is to remove the reinitialization code from the constructor. I am thinking of cloning all the attributes of the object of the parent element and setting them in an instance inside the create () function:

 { create: function () { var o, a, F = function () {}; F.prototype = this; o = new F(); for (a in this) { if (this.hasOwnProperty(a) && typeof this[a] === 'object') { // obviously deepclone would need to be implemented o[a] = deepclone(this[a]); } } return o; } }; 

Is there a better way?

+4
source share
4 answers

Will this give you the expected result? Here I do not use the Object literal, but an instantly created constructor function for the parent object (Base):

 var Base = ( function(){ function MyObject(){ this.registry = {}, this.flatAttribute = null; if (!MyObject.prototype.create) MyObject.prototype.create = function(){ return new this.constructor(); }; } return new MyObject; } )(), // create 2 instances from Base instance1 = Base.create(), instance2 = Base.create(); // assign a property to instance1.registry instance1.registry.something = 'blabla'; // do the instance properties really belong to the instance? console.log(instance1.registry.something); //=> 'blabla' console.log(instance2.registry.something === undefined); //=> true 

But all this is a bit virtual. If you do not want to use the new operator (I think this was his general idea), the following offers you a way to do this without having to create a method:

 function Base2(){ if (!(this instanceof Base2)){ return new Base2; } this.registry = {}, this.flatAttribute = null; if (!Base2.prototype.someMethod){ var proto = Base2.prototype; proto.someMethod = function(){}; //...etc } } //now the following does the same as before: var instance1 = Base2(), instance2 = Base2(); // assign a property to instance1.registry instance1.registry.something = 'blabla'; // do the instance properties really belong to the instance? console.log(instance1.registry.something); //=> 'blabla' console.log(instance2.registry.something === undefined); //=> true 

Jsfiddle example

0
source

There is a very simple solution to ensure that they are only instance variables that should use this keyword in the constructor.

 var MyObject = { flatAttribute: null, create: function () { var o, F = function () { this.registry = {} }; F.prototype = this; o = new F(); return o; } }; 

this ensures that all the properties of "instance.registry. *" are local to the instance, because the search order for javascript options is as follows.

 object -> prototype -> parent prototype ... 

therefore, adding a variable to the instance in a constructor function called "registry", which will always be found first.

another solution, which I think is more elegant, is to not use crockford (java style) constructors and use a layout that more naturally reflects the javascripts object system. most of these gotchas are a mismatch between practice and language.

 // instance stuff var F = function () { this.registry = {} }; F.prototype = { // static attributes here flatAttribute: null, methodA: function(){ // code here 'this' is instance object this.att = 'blah'; } }; var instanceA = new F(); instanceA.registry['A'] = 'hi'; var instanceB = new F(); instanceB.registry['B'] = 'hello'; instanceA.registry.A == 'hi'; // true instanceB.registry.B == 'hello'; // true F.prototype.registry == undefined; // true 
+2
source

I always want to keep in mind that object.Create is one of the options, not the only way to achieve non-classical inheritance in javascript.

For myself, I always think that Object.create works best when I want to inherit elements from the prototype chain of the parent objects (i.e. the methods that I would like to apply to the inheriting object).

-

For simple inheritance, the Native Property of Object.create is largely unnecessary. When I want to inherit my own properties, I prefer to use the popular Mixin and Extend templates (which simply copy the properties of one object to another without worrying about the prototype or the β€œnew”).

In Stoyan Stefanov's book "Javascript Templates", he gives an example of an extended function that does what you are looking for, recursively, and includes support for properties that are arrays, as well as standard key / value objects:

 function extendDeep(parent, child){ var i, toStr = Object.prototype.toString, astr = "[object Array]"; child = child || {}; for (i in parent) { if (parent.hasOwnProperty(i)) { if (typeof parent[i] === "object") { child[i] = (toStr.call(parent[i]) === astr) ? [] : {}; extendDeep(parent[i], child[i]); } else { child[i] = parent[i]; } } } return child; } 

If you use jQuery, jQuery.extend () has an optional "deep" argument that allows you to extend the object in almost the same way.

0
source

I think you are using prototype inheritance to model classical object-oriented inheritance.

What are you trying to do is stop the search for a prototype method that limits its expressiveness , so why use it? You can achieve the same effect using this functional template :

 var MyObject = function() { // Declare here shared vars var global = "All instances shares me!"; return { 'create': function() { var flatAttribute; var register = {}; return { // Declare here public getters/setters 'register': (function() { return register; })(), 'flatAttribute': (function() { return flatAttribute; })(), 'global': (function() { return global; })() }; } }; }(); var instance1 = MyObject.create(); var instance2 = MyObject.create(); instance1.register.newAttr = "This is local to instance1"; instance2.register.newAttr = "This is local to instance2"; // Print local (instance) var console.log(instance1.register.newAttr); console.log(instance2.register.newAttr); // Print global var console.log(instance1.global); console.log(instance2.global); 

Code in jsFiddle

-1
source

Source: https://habr.com/ru/post/1347258/


All Articles