ホームページ > ウェブフロントエンド > jsチュートリアル > Javascript プロトタイプ継承の簡単な分析 Recommendation_js オブジェクト指向

Javascript プロトタイプ継承の簡単な分析 Recommendation_js オブジェクト指向

PHP中文网
リリース: 2016-05-16 18:49:05
オリジナル
1000 人が閲覧しました

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
ログイン後にコピー


これは、MyObj が関数であるため、そのコンストラクターはローカル Function オブジェクトです。 MyObj が Function から構築されているとします。しかし、これはもはや JS レベルではないため、私たちにとってはあまり意味がありません。したがって、MyObj には JS の意味での constructor 属性がないと考えられます。

alert(obj.prototype) この行から、obj インスタンスにはプロトタイプ属性がないことがわかります。 OK、違いが明確になったので、プロトタイプの継承を見てみましょう。これを明確に区別しないと、以下を読むときに混乱すると思います。



次の行に注目してください: Hoozit.prototype = new Gizmo(); この行はプロトタイプ継承のコア コードです。
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)); //这里会报错!!
ログイン後にコピー
上の図をよく見てください。これはプロトタイプの継承の図です。左下隅の新しい Hoozit(stirng) は、新しく作成されたオブジェクトを表します。以下の説明の便宜上、彼を objH1 と呼びます。一番右の灰色の矢印は、プロトタイプの継承チェーンです。

記事の冒頭の英語の段落によると、各関数にはオブジェクトであるプロトタイプがあり、そのオブジェクトにはコンストラクター属性が含まれていることがわかります。このうち、Object、Gizmo、Hoozit は関数であり、その中にはプロトタイプ項目があり、このプロトタイプはオブジェクトを指しており、コンストラクターはそれ自体を指しています。



しかし、ここで事故が発生しました。Hoozit プロトタイプ オブジェクトにはコンストラクター属性がありませんが、この関数の右側には空のオブジェクトがあります。コンストラクター属性?なぜ?

この問題は、プロトタイプの継承中に発生します。主に Hoozit.prototype = new Gizmo(); という文が原因です。この文が意味するのは、新しい Gizmo オブジェクトが作成され、Hoozit のプロトタイプに割り当てられるということです。では、よく考えてみると、Hoozit のオリジナルのプロトタイプ オブジェクトは切断されてしまったのでしょうか? ?はい、それだけです。したがって、コンストラクター属性を持つ空のオブジェクトにはアクセスできなくなります。
alert(Gizmo.prototype.constructo===Gizmo) //true
ログイン後にコピー
ここで別の質問があります。Hoozit.prototype = new Gizmo(); というコード行を渡した後、Hoozit.prototype.constructor はどこを指しているのでしょうか?とても簡単です。(new Gizmo()).constructor がどこを指しているかわかりますか?上の図から、Gizmo 機能を指していることがはっきりとわかります。したがって、現在の Hoozit.prototype.constructor もそこを指していると結論付けられます。



上記のコード行は、私たちの推測を検証します。さて、上で関数 (コンストラクター側) についての説明は終わりました。次に、インスタンス オブジェクトについて説明します。各インスタンス オブジェクトにはコンストラクター属性があり、コンストラクター (関数) を指します。そして、 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页):
Javascript プロトタイプ継承の簡単な分析 Recommendation_js オブジェクト指向
个人认为这张图下面的那个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(){} 
*/
ログイン後にコピー
関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート