Javascript is a prototype based programming language, which is very different from our usual class based programming. I will list the important points as follows:
1. Function is first class object, which means that function and object have the same language status
2. There are no classes, only objects
3. A function is also a kind of object, the so-called function object
4. Objects are passed by reference
So how does this prototype based programming language implement inheritance (a basic element of OO), which is the origin of prototype.
Look at the code snippet below:
function foo(a, b, c) { return a*b*c; } alert(foo.length); alert(typeof foo.constructor); alert(typeof foo.call); alert(typeof foo.apply); alert(typeof foo.prototype);
For the above code, after running it in a browser you will find:
1.length: Provides the number of parameters of the function
2.prototype: is an object
3. The other three are functions
For the declaration of any function, it will have the 5 properties (methods or attributes) mentioned above.
Let’s take a look at the prototype.
// prototype function Person(name, gender) { this.name = name; this.gender = gender; this.whoAreYou = function(){//这个也是所谓的closure, 内部函数可以访问外部函数的变量 var res = "I'm " + this.name + " and I'm a " + this.gender +"."; return res; }; } // 那么在由Person创建的对象便具有了下面的几个属性 Person.prototype.age = 24; Person.prototype.getAge = function(){ return this.age; }; flag = true; if (flag) { var fun = new Person("Tower", "male"); alert(fun.name); alert(fun.gender); alert(fun.whoAreYou()); alert(fun.getAge()); } Person.prototype.salary = 10000; Person.prototype.getSalary = function(){ return this.name + " can earn about " + this.salary + "RMB each month." ; }; // 下面就是最神奇的地方, 我们改变了Person的prototype,而这个改变是在创建fun之后 // 而这个改变使得fun也具有了相同的属性和方法 // 继承的意味即此 if (flag) { alert(fun.getSalary()); alert(fun.constructor.prototype.age);//而这个相当于你直接调用 Person.prototype.age alert(Person.prototype.age); }
From the above example, we can find that we can dynamically add methods or attributes of the prototype, and the objects created by it will automatically inherit the relevant methods and attributes.
In addition, each object has a constructor attribute, which is used to point to the function object that created it. For example, the fun.constructor in the above example points to Person.
Then a question naturally arises, what is the difference between the methods and attributes declared by the function object and the object declared by the prototype?
There are several differences:
1. The methods and attributes declared by yourself are static, which means that if you try to add new methods or modify existing methods after declaring them, it will not affect the objects created by them, nor will it affect the objects created by them. That is, inheritance failed
2. The prototype can dynamically add new methods or modify existing methods, so it is dynamic. Once the parent function object declares the relevant prototype attributes, the object created by it will automatically inherit these prototype attributes.
Continuing with the above example:
flag = true; // 函数内部声明的方法是静态的,无法传递的 Person.school = "ISCAS"; Person.whoAreYou = function(){ return "zhutao"; };//动态更改声明期的方法,并不会影响由其创建的对象的方法, 即所谓的 静态 if (flag) { alert(Person.school); alert(fun.school);//输出的是 "undefined" alert(Person.whoAreYou()); //输出 zhutao alert(fun.whoAreYou()); // I'm Tower and I'm a male. } Person.prototype.getSalary = function(){ return "I can earn 1000000 USD"; }; if (flag) { alert(fun.getSalary());//已经继承了改变, 即所谓的 动态 }
Since there are attributes of the function object itself and attributes of the prototype, how does the object created by it search for the corresponding attributes?
Basically follow the process and sequence below.
1. First search for the properties of the function object itself, and execute it immediately if found
2. If 1 is not found, it will search for the prototype attribute. There are two results. If it is found, it will be executed directly. Otherwise, it will continue to search for the prototype of the parent object's parent object until it is found or reaches the end of the prototype chain (the end will be the Object object )
The above also answers the solution if the properties of the function object itself are the same as the prototype properties (duplicate names), the object of the function itself takes precedence.
Typical example of prototype
Friends who have used jQuery or Prototype libraries may know that these libraries usually have the trim method.
Example:
String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); };
trim usage:
' foo bar '.trim(); // 'foo bar'
But there is another disadvantage in doing this, because the JavaScript engine in newer versions of the browser itself provides the trim method in the String object, so the trim we define will overwrite its own trim. In fact, before we define the trim method, we can do a simple test to see if we need to add this method ourselves:
if(!String.prototype.trim) { String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); }; }
Prototype Chain
When any object is defined or instantiated in JavaScript, it will be appended with a hidden attribute called __proto__, and the prototype chain relies on this attribute to form. But never access the __proto__ attribute directly, because some browsers do not support direct access to it. In addition, __proto__ and the prototype property of an object are not the same thing. They each have their own purposes.
How to understand? In fact, when we create the myObject function, we actually create an object of type Function:
console.log(typeof myObject); // function
It should be noted here that Function is a predefined object in JavaScript, so it also has its own predefined attributes (such as length and arguments) and methods (such as call and apply). Of course, it also has __proto__ to implement the prototype. chain. In other words, there may be code snippets similar to the following within the JavaScript engine:
Function.prototype = { arguments: null, length: 0, call: function() { // secret code }, apply: function(){ // secret code }, ... };
In fact, the JavaScript engine code cannot be so simple. Here is just a description of how the prototype chain works.
We define a function myObject, which also has a parameter name, but does not give it any other properties, such as length or other methods, such as call. So why does the following code execute normally?
console.log(myObject.length); // 结果:1,是参数的个数
这是因为我们定义 myObject 时,同时也给它定义了一个 __proto__ 属性,并赋值为 Function.prototype(参考前面的代码片段),所以我们能够像访问其它属性一样访问 myObject.length,即使我们并没有定义这个属性,因为它会顺着 __proto__ 原型链往上去找 length,最终在 Function 里面找到了。
那为什么找到的 length 属性的值是 1,而不是 0 呢,是什么时候给它赋值的呢?由于 myObject 是 Function 的一个实例:
console.log(myObject instanceof Function); // true console.log(myObject === Function); // false
当实例化一个对象的时候,对象的 __proto__ 属性会被赋值为其构造者的原型对象,在本示例中就是 Function,此时构造器回去计算参数的个数,改变 length 的值。
console.log(myObject.__proto__ === Function.prototype); // true
而当我们用 new 关键字创建一个新的实例时,新对象的 __proto__ 将会被赋值为 myObject.prototype,因为现在的构造函数为 myObject,而非 Function。
var myInstance = new myObject('foo'); console.log(myInstance.__proto__ === myObject.prototype); // true
新对象除了能访问 Function.prototype 中继承下来的 call 和 apply 外,还能访问从 myObject 中继承下来的 getName 方法:
console.log(myInstance.getName()); // foo var mySecondInstance = new myObject('bar'); console.log(mySecondInstance.getName()); // bar console.log(myInstance.getName()); // foo
其实这相当于把原型对象当做一个蓝本,然后可以根据这个蓝本创建 N 个新的对象。
再看一个多重prototype链的例子:
// 多重prototype链的例子 function Employee(name) { this.name = ""; this.dept = "general"; this.gender = "unknown"; } function WorkerBee() { this.projects = []; this.hasCar = false; } WorkerBee.prototype = new Employee; // 第一层prototype链 function Engineer() { this.dept = "engineer"; //覆盖了 "父对象" this.language = "javascript"; } Engineer.prototype = new WorkerBee; // 第二层prototype链 var jay = new Engineer("Jay"); if (flag) { alert(jay.dept); //engineer, 找到的是自己的属性 alert(jay.hasCar); // false, 搜索到的是自己上一层的属性 alert(jay.gender); // unknown, 搜索到的是自己上二层的属性 }
上面这个示例的对象关系如下: