Home>

Follow me on the precautions of using javascript prototype,Introduced several considerations when using prototype,

First, save the method on the prototype

It's perfectly possible to encode JavaScript without using prototypes,E.g:

function user (name, passwordhash) {
 this.name=name;
 this.passwordhash=passwordhash;
 this.tostring=function () {
  return "[user" + this.name + "]";
 };
 this.checkpassword=function (password) {
  return hash (password) === this.passwordhash;
 };
}
var u1=new user (/* ... * /);
var u2=new user (/* ... * /);
var u3=new user (/* ... * /);

When multiple instances of the user type are created,There is a problem:not only the name and passwordhash attributes exist on each instance,The tostring and checkpassword methods have a copy on each instance.It looks like this:

However, when tostring and checkpassword are defined on the prototype, the above picture becomes like this:

The tostring and checkpassword methods are now defined on the user.prototype object, which means that there is only one copy of these two methods.And shared by all user instances.

Maybe you would think of putting the method as a copy on each instance,Will save time for method query.(When a method is defined on a prototype, it first looks for the method on the instance itself.(If you can't find it, you will continue to look for prototypes)

But in modern JavaScript execution engines,Extensive optimization of method queries,So this query time is almost unnecessary to consider,Then putting methods on prototype objects saves a lot of memory.

Second, use closures to hold private data

JavaScript's object system does not encourage the use of information hiding from its syntax. Because when using such as this.name, this.passwordhash, the default access level of these properties is public, and you can access these properties through obj.name, obj.passwordhash at any location.

In the es5 environment, some methods are also provided to more easily access all the properties on an object.For example, object.keys (), object.getownpropertynames (). So, some developers use some conventions to define the private properties of javascript objects,For example, the most typical is to use an underscore as a prefix to tell other developers and users that this property should not be accessed directly.

But doing so,It does not solve the problem fundamentally.Other developers and users still have direct access to underlined properties.For situations where private attributes are really needed,This can be done using closures.

In a sense,In JavaScript, the access policy of variables to variables and the access policy of objects are two extremes.Any variable in a closure is private by default,These variables can only be accessed inside the function.For example, the user type can be implemented as follows:

function user (name, passwordhash) {
 this.tostring=function () {
  return "[user" + name + "]";
 };
 this.checkpassword=function (password) {
  return hash (password) === passwordhash;
 };
}

At this point, neither name nor passwordhash is saved as an instance attribute.Instead, they are saved via local variables.Then according to the access rules of the closure,Methods on the instance can access them,Nowhere else.

One disadvantage of using this model is thatMethods that use local variables need to be defined on the instance itself.You cannot say that these methods are defined on prototype objects. As discussed in item34,The problem is that it will increase memory consumption.But on some special occasions,It is possible to define a method even on an instance.

Third, the instance state is only saved on the instance object

There is a "one-to-many" relationship between a type's prototype and an instance of that type. Then, you need to ensure that instance-related data is not stored incorrectly on the prototype. For example, for a type that implements a tree structure,It is incorrect to save its children on this type of prototype:

function tree (x) {
 this.value=x;
}
tree.prototype={
 children:[], //should be instance state!
 addchild:function (x) {
  this.children.push (x);
 }
};
var left=new tree (2);
left.addchild (1);
left.addchild (3);
var right=new tree (6);
right.addchild (5);
right.addchild (7);
var top=new tree (4);
top.addchild (left);
top.addchild (right);
top.children;//[1, 3, 5, 7, left, right]

When the state is saved to the prototype, the state of all instances will be centrally saved.It is obviously incorrect in the above scenario:the state that originally belonged to each instance was incorrectly shared.As shown below:

The correct implementation should look like this:

function tree (x) {
 this.value=x;
 this.children=[];//instance state
}
tree.prototype={
 addchild:function (x) {
  this.children.push (x);
 }
};

At this point, the instance state is stored as follows:

It can be seen that when the state that belongs to the instance is shared to the prototype, problems may occur.Before you need to save state properties on the prototype,Be sure to ensure that this property can be shared.

Overall,When an attribute is immutable (stateless),You can save it on the prototype object (for example, methods can be saved on the prototype object because of this). Of course, stateful properties can also be placed on prototype objects, depending on the specific application scenario.A typical variable is used to record the number of instances of a type.Using the Java language as an analogy,Such variables that can be stored on prototype objects are class variables in Java (decorated with the static keyword).

Avoid inheriting standard types

The ecmascript standard library is not large,But it provides some important types such as array, function and date. On some occasions,You might consider inheriting one of these types to achieve a specific function,But this practice is not encouraged.

For example, to operate a directory,You can make the directory type inherit the array type as follows:

function dir (path, entries) {
 this.path=path;
 for (var i=0, n=entries.length;i<n;i ++) {
  this [i]=entries [i];
 }
}
dir.prototype=object.create (array.prototype);
//extends array
var dir=new dir ("/tmp/mysite", ["index.html", "script.js", "style.css"]);
dir.length;//0

But it can be found thatThe value of dir.length is 0 instead of 3 as expected.

The reason this happens is that the length property will only work if the object is a real array type.

In the ecmascript standard, an invisible internal property is defined as [[class]]. The value of this attribute is just a string,So don't be misled into thinking that JavaScript also implements its own type system.So, for an array type, the value of this property is "array";for a function type, the value of this property is "function". The following is all [[class]] values ​​defined by ecmascript:

Then when the type of the object is indeed an array, the length property is special:the value of length will be consistent with the number of indexed properties in the object.For example, for an array object arr, arr [0] and arr [1] indicate that the object has two indexed properties.Then the value of length is 2. When arr [2] is added, the length value is automatically synchronized to 3. Similarly, when the length value is set to 2, arr [2] is automatically set to undefined.

But when inheriting the array type and creating an instance,The [[class]] attribute of this instance is not an array, but an object. Therefore the length property does not work correctly.

In javascript, methods for querying the [[class]] attribute are also provided,That is, using the object.prototype.tostring method:

var dir=new dir ("/", []);
object.prototype.tostring.call (dir);//"[object object]"
object.prototype.tostring.call ([]);//"[object array]"

Therefore, a better implementation is to use composition rather than inheritance:

function dir (path, entries) {
 this.path=path;
 this.entries=entries;//array property
}
dir.prototype.foreach=function (f, thisarg) {
 if (typeof thisarg === "undefined") {
  thisarg=this;
 }
 this.entries.foreach (f, thisarg);
};

The above code will no longer use inheritance,Instead, a part of the function is delegated to the internal entries property for implementation.The value of this attribute is an array type object.

ecmascript standard library,Most constructors rely on internal property values ​​such as [[class]] for correct behavior.For subtypes that inherit these standard types,There is no guarantee that they will behave correctly.Therefore, do not inherit types from the ecmascript standard library such as:

array, boolean, date, function, number, regexp, string

  • Previous Jquery verify mobile phone number is correct
  • Next Learn with me undefined and null of javascript