JS does not provide so-called class inheritance. It is said that this inheritance method will be added in 2.0, but it will definitely take N years for all browsers to implement the features of 2.0.
JS does not provide so-called class inheritance. It is said that this inheritance method will be added in 2.0, but it will definitely take N years for all browsers to implement the features of 2.0. Yesterday I watched a video by crockford, which explained the inheritance method of JS. According to the PPT, it is divided into three categories: Prototypal, pseudoclassical, and Parasitic Inheritance.
The following mainly introduces prototypal inheritance: When a function object is created, it is given a prototype member which is an object containing a constructor member which is a reference to the function object.
Here we first differentiate between the prototype attribute and the constructor attribute. That is to say, we need to distinguish between constructors, functions, and object instances.
In fact, in JS, a constructor is a function, a function is a constructor, and an object instance is a newly created instance in the form of var obj=new function ();. The difference between these is prototype and constructor. As can be seen from the above English, prototype is an object, and a constructor is defined in it. Then we can deduce that constructor is an attribute of the object instance! rather than properties of a function (constructor). In turn, prototype is a property of the function (constructor), not of the instance!
//在下面这个代码示例中,MyObj是函数(构造器),obj是实例 function MyObj(id){ this.id=id; } var obj=new MyObj(1); alert(MyObj.constructor) //本地代码 alert(obj.constructor) //MyObj.toString() alert(MyObj.prototype) //[object Object] alert(obj.prototype) //undefined
We can see that MyObj does not have the constructor attribute in the JS sense. Why do we say this? There is something in this line of code alert(MyObj.constructor):
This is because MyObj is a function, so its constructor is the local Function object, that is to say MyObj is constructed from Function. But this doesn't mean much to us, because it's no longer at the JS level. So it can be considered that MyObj does not have the constrcutor attribute in the JS sense.
alert(obj.prototype) From this line we can see that the obj instance does not have prototype attributes. OK, now that the differences are clear, we can look at prototype inheritance. If you don't distinguish this clearly, I'm afraid you will be confused when reading below.
function Gizmo(id) { this.id = id; } Gizmo.prototype.toString = function () { return "gizmo " + this.id; }; function Hoozit(id) { this.id = id; } Hoozit.prototype = new Gizmo(); Hoozit.prototype.test = function (id) { return this.id === id; };
Pay attention to this line: Hoozit.prototype = new Gizmo(); This line is the core code of prototypal inheritance.
Also note that test and other methods can only be added after new Gizmo(). This order cannot be reversed! If you add test and other methods first, and then in new Gizmo(), the methods originally added will not be found. The specific reasons will become clear after the analysis below.
Hoozit.prototype.test = function (id) { return this.id === id; }; Hoozit.prototype = new Gizmo(2); var h=new Hoozit(); alert(h.test(3)); //这里会报错!!
Look at the picture above carefully. This is an illustration of prototypal inheritance. The new Hoozit(stirng) in the lower left corner represents a newly created object. For the convenience of the following description, we call him objH1. The gray arrow on the far right is the prototypal inheritance chain.
According to the English paragraph at the beginning of the article, we know that each function has a prototype, which is an object, and the object contains a constructor attribute. Among them, Object, Gizmo, and Hoozit are functions. It can be seen that there is a prototype item in them, and this prototype points to an object. This object also has a constructor attribute, and the constructor points back to itself.
alert(Gizmo.prototype.constructo===Gizmo) //true
But there is an accident here. We found that there is no constructor attribute in the Hoozit prototype object, but there is an empty object on the right side of this function, which contains a constructor attribute? Why?
This problem will occur during prototypal inheritance. Mainly because of the sentence Hoozit.prototype = new Gizmo();. What this sentence means is that a new Gizmo object is created and assigned to Hoozit's prototype! So, if you think about it carefully, has Hoozit's original prototype object been disconnected? ? Yes, that's it. So the empty object with the constructor attribute can no longer be accessed!
Now I have another question. After passing the line of code Hoozit.prototype = new Gizmo();, where does Hoozit.prototype.constructor point to? It's very simple, do you know where (new Gizmo()).constructor points to? From the picture above, you can clearly see that it points to the Gizmo function. So we conclude that the current Hoozit.prototype.constructor also points there!
alert(Hoozit.prototype.constructor===Gizmo); //true
The above line of code verifies our guess! OK, we have finished talking about functions (constructor side) above, and then let’s talk about instance objects: each instance object has a constructor attribute and points to the constructor (function). And every instance of new is an instance of a prototype constructor:
var objG1=new Gizmo() alert(objG1 instanceof Gizmo.prototype.constructor) //true alert(Gizmo.prototype.constructor===objG1.constructor); //true
Why not take objH1 as an example above, because his constructor is no longer his own, but the Gizmo object's. So let’s verify it:
alert(objH1.constructor===objG1.constructor) //true alert(objH1 instanceof Gizmo.prototype.constructor) //true
看到了吗?其实这个问题也不算什么大问题,如果你要不使用instanceof检查父对象或者使用constructor进行原型回溯的话,这个问题可以不解决了。如果想解决这个问题怎么办呢?在Prototype框架的Class.create方法里面给出了一种方法
下面我简单说一下两种方法:
//方法一 //Prototype框架采用了此种方法 Hoozit.prototype = new Gizmo(2); Hoozit.prototype.constructor = Hoozit; //方法二 function Hoozit(id) { this.id = id; this.constructor=Hoozit; } //具体两种方法有什么区别,请参考《JAVASCRIPT语言精髓与编程实践》158~159页。
有兴趣的可以结合上面的图,想想这两种方法的Hoozit.prototype.constructor应该放在图的什么位置?想不明白的可以和我在交流。
下面看一下《JAVASCRIPT语言精髓和编程实践》书上的一张图(156~157页):
个人认为这张图下面的那个constructor属性的指向是不是有问题?书上面表达的意思肯定没有问题,但这张图画的很困惑。和我上面的解释不太一样?先不说这个了,大家自己研究研究吧。
下面我们再来说一下我给出的原型继承图中的右边灰色的箭头部分。从这部分能够看出继承的关系。所有未经过变动的函数(构造器)的原型,他们都会继承自Object对象。
alert(Gizmo.prototype instanceof Object.prototype.constructor); //true
这样原型的继承关系就连接起来了。其实说白了,就是一个函数的prototype链向另一个函数实例,然后不断的这样进行下去,最上面的函数链接Object至对象实例,OK,所有函数就都连接起来了。
PS:
“还有要注意的是只有在new Gizmo()之后,才能添加test等其它方法,这个顺序不能倒过来!“ 这个问题是不是清楚了呢?
下面看几个例子,说明几个问题:
function Gizmo(id) { this.id = id; this.ask=function(){ alert("gizmo--ask:"+this.id); } function privateMethod(){ return "gizmo--privateMethod"; } privateMethod2=function(){ return "gizmo--privateMethod2"; } } Gizmo.prototype.toString = function () { return "gizmo--toString:" + this.id; }; Gizmo.prototype.id="gizmo3"; function Hoozit(id) { this.id = id; } Hoozit.prototype = new Gizmo("gizmo1"); var g=new Gizmo("gizmo2"); var h=new Hoozit("hoozit");
问题一:
h.ask=function(){ alert("h.ask"); } h.ask(); delete h.ask; //"h.ask" h.ask(); //"gizmo--ask:hoozit" delete h.id h.ask(); //"gizmo--ask:gizmo1" delete Hoozit.prototype.id h.ask(); //"gizmo--ask:gizmo3" /* 这里要说明的问题是:对象是如何找到属性和方法的? 第一步:先在实例对象上找ask方法,找到了,调用。第一个ask说明了这个问题 第二步:如果实例上没有ask方法,在自己的原型对象里面找ask方法,找到调用(没有给出这样的示例) 第三步:如果自己的原型中没有,回溯原型链,在父原型链中找ask方法,找到调用,第二个ask说明了这个问题,这里会一直递归找到Object对象,如果还没找到,那就会报错了 */ /* 再来看属性查找: 首先找实例对象上的属性,所以第二个ask输出"gizmo--ask:hoozit",即id="hoozit" 然后找自己原型中的属性,删除掉h.id之后,找到原型上的属性,所以id="gizmo1" 接着递归原型链,找父对象原型中的属性,一直找到Object对象,所以删除掉Hoozit.prototype.id之后,id="gizmo3" */
问题二:
Gizmo.prototype.question = function () { alert("gizmo--question:" + this.id); }; h.question(); /* 方法可以随时添加,添加完之后就可以调用 */
问题三:
Hoozit.prototype.toString = function () { return "hoozit--toString:" + this.id; }; alert(h); delete Hoozit.prototype.toString; alert(h); /* 这个问题和问题一有些重复,这里更清楚的看出,删除掉自己原型上的方法之后,就会找父原型中的方法 */
问题四:
h.question.call(g); alert(h.toString.call(g)); h.question.apply(g); alert(h.toString.apply(g)); /* 可以利用apply和call方法把要调用的方法绑定到其它实例。通过结果可以看出上面那种方法调用输出的id是g对象的,而不是h对象的 */
问题五:
alert(h.privateMethod()); alert(g.privateMethod2()); /* 上面的任意一个调用都会报错,也就是说通过显示命名函数或者匿名函数但是不加this的声明方式定义在函数之内的函数,是不能被外界访问的。这里一定注意第二种private方法声明,省略了this外面就访问不到了! */ /* 命名函数:(函数名字为func1) function func1(){} 匿名函数:(注意这里,func1不是函数的名字,仅仅是个别名而已,可以通过func()来调用这个匿名函数) func1=function(){} */