Preface
JavaScript does not contain a traditional class inheritance model, but uses a prototypal prototype model.
Although this is often mentioned as a shortcoming of JavaScript, the prototype-based inheritance model is actually more powerful than traditional class inheritance. Implementing the traditional class inheritance model is easy, but implementing prototypal inheritance in JavaScript is much more difficult.
Since JavaScript is the only widely used language based on prototypal inheritance, it takes some time to understand the difference between the two inheritance models. Today we will learn about prototypes and prototype chains.
Prototype
10 years ago, when I first learned JavaScript, I usually wrote code in the following way:
var decimalDigits = 2,
tax = 5;
function add(x, y) {
return x y ;
}
function subtract(x, y) {
return x - y;
}
//alert(add(1, 3));
Get the results by executing each function. After learning the prototype, we can use the following methods to beautify the code.
Prototype usage method 1:
Before using the prototype, we need to make small modifications to the code:
var Calculator = function (decimalDigits, tax) {
this.decimalDigits = decimalDigits;
this.tax = tax;
};
Then, set the prototype of the Calculator object by assigning an object literal to the prototype property of the Calculator object.
Calculator.prototype = {
add: function ( x, y) {
return x y;
},
subtract: function (x, y) {
return x - y;
}
};
//alert((new Calculator()).add(1, 3));
In this way, we can call the add method to calculate the result after new Calculator object.
Prototype usage method 2:
The second method is to use an expression that the function will execute immediately when assigning the prototype prototype, which is the following format:
Calculator.prototype = function () { } ();
Its advantage is already known in the previous post, that is, it can encapsulate private functions and expose simple usage names in the form of return to achieve public/private effects. Modify The final code is as follows:
Calculator.prototype = function () {
add = function (x, y) {
return x y;
},
subtract = function (x, y) {
return x - y;
}
return {
add: add,
subtract: subtract
}
} ();
//alert((new Calculator()).add(11 , 3));
In the same way, we can create a new Calculator object and later call the add method to calculate the result.
One more point
Step-by-step statement:
When using the prototype above, there is a limitation that the prototype object is set at once. Let’s talk about how to set each attribute of the prototype separately.
var BaseCalculator = function () {
// Declare a decimal digit for each instance
this.decimalDigits = 2;
};
//Use the prototype to extend 2 object methods for BaseCalculator
BaseCalculator.prototype.add = function (x, y) {
return x y;
};
BaseCalculator.prototype.subtract = function (x, y) {
return x - y;
};
First, declare a BaseCalculator object. In the constructor, a decimal number attribute decimalDigits will be initialized, and then two functions are set through the prototype attribute, namely add(x,y) and subtract( x, y), of course you can also use any of the two methods mentioned above. Our main purpose is to see how to set the BaseCalculator object to the prototype of the real Calculator.
var BaseCalculator = function() {
this. decimalDigits = 2;
};
BaseCalculator.prototype = {
add: function(x, y) {
return x y;
},
subtract: function( x, y) {
return x - y;
}
};
After creating the above code, let’s start:
var Calculator = function () {
//Declare a tax number for each instance
this.tax = 5;
};
Calculator.prototype = new BaseCalculator() ;
We can see that the prototype of Calculator points to an instance of BaseCalculator, in order to allow Calculator to integrate its two functions: add(x,y) and subtract(x,y) , and one more thing to say is that since its prototype is an instance of BaseCalculator, no matter how many Calculator object instances you create, their prototypes point to the same instance.
var calc = new Calculator();
alert (calc.add(1, 1));
//The decimalDigits attribute declared in BaseCalculator can be accessed in Calculator
alert(calc.decimalDigits);
After running the above code, we can see that because the prototype of Calculator points to the instance of BaseCalculator, its decimalDigits attribute value can be accessed. If I don't want Calculator to access the attribute value declared in the constructor of BaseCalculator, then What to do? Do this:
var Calculator = function () {
this.tax= 5;
};
Calculator.prototype = BaseCalculator.prototype;
By assigning the prototype of BaseCalculator to the prototype of Calculator, so that you are in Calculator The decimalDigits value cannot be accessed on the instance. If you access the following code, an error will occur.
var calc = new Calculator();
alert (calc.add(1, 1));
alert(calc.decimalDigits);
Rewrite the prototype:
When using third-party JS libraries, sometimes The prototype methods they defined cannot meet our needs, but they are inseparable from this class library, so at this time we need to rewrite one or more properties or functions in their prototypes. We can continue to declare the same The form of add code is to overwrite and rewrite the previous add function. The code is as follows:
//Override the previous Calculator’s add() function
Calculator.prototype.add = function (x, y) {
return x y this.tax;
};
var calc = new Calculator();
alert(calc.add(1, 1));
In this way, the result we calculated will have one more tax than the original one value, but there is one thing to note: the rewritten code needs to be placed at the end so that it can overwrite the previous code.
Prototype chain
Before chaining the prototype, we first put a piece of code:
function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
};
function Bar() {}
// Set the prototype attribute of Bar to the instance object of Foo
Bar.prototype = new Foo();
Bar.prototype .foo = 'Hello World';
// Correct Bar.prototype.constructor to Bar itself
Bar.prototype.constructor = Bar;
var test = new Bar() / / Create a new instance of Bar
// Prototype chain
test [Instance of Bar]
Bar.prototype [Instance of Foo]
{ foo: 'Hello World' }
Foo.prototype
{method: ...};
Object.prototype
{toString: ... /* etc. */};
In the above example, the test object inherits from Bar.prototype and Foo.prototype; therefore, it can access Foo’s prototype method method. At the same time, it can also access the Foo instance property value defined on the prototype. Note that new Bar() does not create a new Foo instance, but reuses the one on its prototype; therefore, all Bar instances will share the same value property.
Property lookup:
When looking for a property of an object, JavaScript will traverse the prototype chain upwards until it finds a property with a given name, until the search reaches the top of the prototype chain - that is, Object.prototype - But if the specified attribute is still not found, undefined will be returned. Let’s look at an example:
function foo() {
this.add = function (x, y) {
return x y;
}
}
foo.prototype. add = function (x, y) {
return x y 10;
}
Object.prototype.subtract = function (x, y) {
return x - y;
}
var f = new foo();
alert(f.add(1, 2)); //The result is 3, not 13
alert(f.subtract(1, 2)); //The result is -1
By running the code, we find that subtract installs what we call upward search to get the result, but the add method is a little different, which is what I think What I emphasize is that when searching for attributes, they first search for their own attributes. If there is no prototype, then search for the prototype. If there is no more, then go up and insert it into the prototype of Object. So in a certain level, use the for in statement. When traversing properties, efficiency is also an issue.
Another thing we need to note is that we can assign any type of object to the prototype, but we cannot assign atomic type values. For example, the following code is invalid:
function Foo() {}
Foo.prototype = 1; // Invalid
hasOwnProperty function:
hasOwnProperty is a method of Object.prototype. It is a good thing. It can determine whether an object contains custom properties instead of properties on the prototype chain, because hasOwnProperty is in JavaScript. The only function that handles properties without looking up the prototype chain.
// Modify Object.prototype
Object.prototype .bar = 1;
var foo = {goo: undefined};
foo.bar; // 1
'bar' in foo; // true
foo. hasOwnProperty('bar'); // false
foo.hasOwnProperty('goo'); // true
Only hasOwnProperty can give the correct and expected results when traversing the object properties can be useful. There is no other way to exclude properties on the prototype chain than properties defined on the object itself.
But there is a disgusting thing: JavaScript does not protect hasOwnProperty from being illegally occupied, so if an object happens to have this property, you need to use an external hasOwnProperty function to get the correct result.
var foo = {
hasOwnProperty: function() {
return false;
},
bar: 'Here be dragons'
};
foo.hasOwnProperty('bar'); // always returns false
// Use the hasOwnProperty of the {} object and set its top and bottom to foo
{}.hasOwnProperty.call(foo, 'bar'); // true
hasOwnProperty is the only method available when checking whether a property exists on an object. At the same time, when using for in loop to traverse objects, it is recommended to always use the hasOwnProperty method, which will avoid interference caused by prototype object expansion. Let’s take a look at an example:
// Modify Object.prototype
Object.prototype.bar = 1;
var foo = { moo: 2};
for(var i in foo) {
console.log(i); // Output two attributes: bar and moo
}
us There is no way to change the behavior of the for in statement, so if you want to filter the results, you can only use the hasOwnProperty method. The code is as follows:
// The foo variable is the one in the above example
for(var i in foo) {
if (foo.hasOwnProperty(i)) {
console.log(i);
}
}
This version of the code is the only correct way to write it. Since we used hasOwnProperty, only moo is output this time. If hasOwnProperty is not used, this code may break when native object prototypes (such as Object.prototype) are extended.
Summary: It is recommended to use hasOwnProperty, do not make any assumptions about the environment in which the code runs, and do not assume whether the native object has been extended.
Summary
Prototypes have greatly enriched our development code, but we must pay attention to some of the precautions mentioned above during daily use.