Some precautions for prototype usage in javascript are introduced. For more information, see
1. Save methods on prototype
It is completely feasible not to use prototype for JavaScript encoding, for example:
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 User-type instances are created, there is a problem: not only the name and passwordHash attributes exist on each instance, but the toString and checkPassword methods have a copy on each instance. As shown in the following figure:
However, when toString and checkPassword are defined on prototype, they become the following:
The toString and checkPassword methods are now defined on the User. prototype object, which means that the two methods only have one copy and are shared by all User instances.
You may think that copying a method to each instance will save the time for method query. (When the method is defined on prototype, the system first searches for the method on the instance itself. If the method is not found, the system searches for the method on prototype)
However, in the modern JavaScript execution engine, the method query is greatly optimized, so this query time almost does not need to be considered, putting the method on the prototype object saves a lot of memory.
Ii. Use closures to save private data
In terms of its syntax, the JavaScript Object System does not encourage the use of Information Hiding ). Because when this. name, this. when passwordHash is used, the default access level of these attributes is public, and you can use obj at any location. name, obj. passwordHash to access these attributes.
In the ES5 environment, some methods are provided to access all attributes of an Object more conveniently, such as Object. keys () and Object. getOwnPropertyNames (). Therefore, some developers use some conventions to define the private attributes of JavaScript objects, for example, it is typical to use underscores as the prefix of attributes to tell other developers and users that this attribute should not be directly accessed.
However, this does not fundamentally solve the problem. Other developers and users can still directly access the underlined attributes. You can use closures to implement private attributes.
In a sense, in JavaScript, closures have two extremes: variable access policies and object access policies. Any variables in the closure are private by default and can be accessed only within the function. For example, you can implement the User type as follows:
function User(name, passwordHash) { this.toString = function() { return "[User " + name + "]"; }; this.checkPassword = function(password) { return hash(password) === passwordHash; }; }
In this case, both name and passwordHash are not saved as attributes of the instance, but are saved through local variables. Then, according to the access rules of the closure, the methods on the instance can access them, but not elsewhere.
One disadvantage of using this mode is that all methods that use local variables need to be defined on the instance itself, and these methods cannot be defined on the prototype object. As discussed in Item34, this problem increases memory consumption. However, in some special cases, it is feasible to define a method on an instance.
3. The instance status is only saved on the instance object.
The relationship between a prototype and an instance of this type is "one-to-many. Therefore, you must ensure that the instance-related data is not mistakenly stored on the prototype. For example, for a type that implements the tree structure, it is incorrect to save its child nodes on prototype of the type:
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 status is saved to prototype, the status of all instances is saved in a centralized manner. This is obviously incorrect in the preceding scenario: the status of each instance is incorrectly shared. As shown in:
The correct implementation should be like this:
function Tree(x) { this.value = x; this.children = []; // instance state } Tree.prototype = { addChild: function(x) { this.children.push(x); } };
In this case, the instance status is stored as follows:
It can be seen that problems may occur when the status of the instance is shared to prototype. Before saving the status attribute on prototype, make sure that the attribute can be shared.
In general, when an attribute is an unchangeable (stateless) attribute, it can be saved on the prototype object (for example, the method can be saved on the prototype object because of this ). Of course, stateful attributes can also be placed on prototype objects, depending on the specific application scenario, such as a typical variable used to record the number of instances of a type. Using the Java language as an analogy, the variables that can be stored on prototype objects are class variables in Java (modified using the static keyword ).
4. Avoid inheriting STANDARD TYPES
The ECMAScript standard library is not large, but provides some important types such as Array, Function, and Date. In some cases, you may consider inheriting a type to implement a specific function, but this approach is not encouraged.
For example, to operate a directory, the directory type can 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?1.1.15", "style.css?1.1.15"]); dir.length; // 0
However, we can find that the value of dir. length is 0, rather than the expected 3.
This occurs because the length attribute takes effect only when the object is of the true Array type.
In the ECMAScript standard, an invisible internal attribute is defined as [[class]. The value of this attribute is only a string, so do not mislead JavaScript into realizing its own type system. Therefore, for the Array type, the value of this attribute is "Array"; for the Function type, the value of this attribute is "Function ". The following table lists all [[class] values defined by ECMAScript:
When the object type is indeed an Array, the special feature of the length attribute is that the length value will be consistent with the number of attributes indexed in the object. For example, for an array object arr, arr [0] and arr [1] indicate that the object has two indexed attributes, and the length value 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.
However, when you inherit the Array type and create an instance, the [[class] attribute of the instance is not an Array, but an Object. Therefore, the length attribute cannot work correctly.
In JavaScript, the [[class] attribute query method is also provided, that is, the Object. prototype. toString method is used:
var dir = new Dir("/", []); Object.prototype.toString.call(dir); // "[object Object]" Object.prototype.toString.call([]); // "[object Array]"
Therefore, a better implementation method is to use combination instead of 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 code above will no longer use inheritance, but will delegate some functions to the internal entries attribute for implementation. The value of this attribute is an Array type object.
In the ECMAScript standard library, most constructors rely on internal attribute values such as [[class] to implement correct behavior. For child types that inherit these standard types, they cannot be guaranteed that their behavior is correct. Therefore, do not inherit the types in the standard ECMAScript library, such:
Array, Boolean, Date, Function, Number, RegExp, String
The above is a summary of the precautions for using prototype, hoping to help you use prototype correctly.