JS にはいわゆるクラス継承がありません。この継承メソッドは 2.0 で追加されると言われていますが、すべてのブラウザが 2.0 の機能を実装するには N 年かかるのは間違いありません。
JS にはいわゆるクラス継承がありません。この継承メソッドは 2.0 で追加されると言われていますが、すべてのブラウザが 2.0 の機能を実装するには N 年かかるのは間違いありません。昨日、JS の継承方法を説明した crockford のビデオを見ました。PPT によると、プロトタイプ継承、擬似古典継承、寄生継承の 3 つのカテゴリに分類されています。
以下では主にプロトタイプの継承を紹介します。関数オブジェクトが作成されると、関数オブジェクトへの参照であるコンストラクター メンバーを含むオブジェクトであるプロトタイプ メンバーが与えられます。 🎜>
ここでは、まずプロトタイプ属性とコンストラクター属性を区別します。つまり、コンストラクター、関数、オブジェクト インスタンスを区別する必要があります。
実際、JS ではコンストラクターは関数、関数はコンストラクター、オブジェクト インスタンスは var obj=new function (); の形式で新しく作成されたインスタンスです。これらの違いはプロトタイプとコンストラクターです。上記の英語からわかるように、プロトタイプはオブジェクトであり、その中にコンストラクターが定義されているため、コンストラクターはオブジェクト インスタンスの属性であると推測できます。関数 (コンストラクター) のプロパティではなく。つまり、プロトタイプはインスタンスのプロパティではなく、関数 (コンストラクター) のプロパティです。
MyObj には JS の意味でのコンストラクター属性がないことがわかります。なぜこのように言えるのでしょうか。このコード行には、alert(MyObj.constructor) というものがあります:
//在下面这个代码示例中,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
alert(obj.prototype) この行から、obj インスタンスにはプロトタイプ属性がないことがわかります。 OK、違いが明確になったので、プロトタイプの継承を見てみましょう。これを明確に区別しないと、以下を読むときに混乱すると思います。
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; };
また、テストとその他のメソッドは new Gizmo() の後にのみ追加できることにも注意してください。この順序は逆にできません。最初に test と他のメソッドを追加し、次に new Gizmo() で追加すると、最初に追加したメソッドは見つかりません。具体的な理由は以下の分析で明らかになります。
Hoozit.prototype.test = function (id) { return this.id === id; }; Hoozit.prototype = new Gizmo(2); var h=new Hoozit(); alert(h.test(3)); //这里会报错!!
記事の冒頭の英語の段落によると、各関数にはオブジェクトであるプロトタイプがあり、そのオブジェクトにはコンストラクター属性が含まれていることがわかります。このうち、Object、Gizmo、Hoozit は関数であり、その中にはプロトタイプ項目があり、このプロトタイプはオブジェクトを指しており、コンストラクターはそれ自体を指しています。
しかし、ここで事故が発生しました。Hoozit プロトタイプ オブジェクトにはコンストラクター属性がありませんが、この関数の右側には空のオブジェクトがあります。コンストラクター属性?なぜ?
alert(Gizmo.prototype.constructo===Gizmo) //true
上記のコード行は、私たちの推測を検証します。さて、上で関数 (コンストラクター側) についての説明は終わりました。次に、インスタンス オブジェクトについて説明します。各インスタンス オブジェクトにはコンストラクター属性があり、コンストラクター (関数) を指します。そして、 new のすべてのインスタンスは、プロトタイプ コンストラクターのインスタンスです:
alert(Hoozit.prototype.constructor===Gizmo); //true
上の例として objH1 を取り上げてみませんか。なぜなら、彼のコンストラクターはもはや彼自身のものではなく、Gizmo オブジェクトのものだからです。
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(){} */