JS는 소위 클래스 상속을 제공하지 않습니다. 이 상속 방법은 2.0에 추가될 예정이라고 하는데, 모든 브라우저가 2.0의 기능을 구현하는 데는 확실히 N년이 걸릴 것입니다.
JS는 소위 클래스 상속을 제공하지 않습니다. 이 상속 방식은 2.0에 추가될 예정이라고 하는데, 모든 브라우저가 2.0의 기능을 구현하려면 분명 N년이 걸릴 것입니다. 어제 JS의 상속 방법을 설명하는 crockford의 동영상을 봤습니다. PPT에 따르면 Prototypal, pseudoclassical, Parasitic Inheritance의 세 가지 범주로 나뉩니다.
다음은 주로 프로토타입 상속을 소개합니다. 함수 객체가 생성되면 함수 객체에 대한 참조인 생성자 멤버를 포함하는 객체인 프로토타입 멤버가 제공됩니다. 🎜>
여기서 먼저 프로토타입 속성과 생성자 속성을 구분합니다. 즉, 생성자, 함수, 객체 인스턴스를 구별해야 합니다.
사실 JS에서는 생성자는 함수, 함수는 생성자, 객체 인스턴스는 var obj=new function(); 형태로 새로 생성된 인스턴스입니다. 이들의 차이점은 프로토타입과 생성자입니다. 위 영어에서 알 수 있듯이, 프로토타입은 객체이고, 그 안에 생성자가 정의되어 있습니다. 그러면 생성자는 객체 인스턴스의 속성이라는 것을 추론할 수 있습니다! 함수(생성자)의 속성이 아닌 결과적으로 프로토타입은 인스턴스가 아닌 함수(생성자)의 속성입니다!
MyObj에는 JS 의미에서 생성자 속성이 없다는 것을 알 수 있습니다. 다음 코드 줄에 다음 내용이 있습니다. MyObj가 Function으로 구성되었다고 가정해 보겠습니다. 하지만 이는 더 이상 JS 수준이 아니기 때문에 우리에게는 큰 의미가 없습니다. 따라서 MyObj에는 JS 의미의 생성자 속성이 없다고 간주할 수 있습니다.
//在下面这个代码示例中,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) //undefinedalert(obj.prototype) 이 줄에서 obj 인스턴스에 프로토타입 속성이 없음을 알 수 있습니다. 자, 이제 차이점이 명확해졌으니 프로토타입 상속을 살펴보겠습니다. 이것을 명확히 구분하지 않으시면 아래 내용을 읽으실 때 혼란스러우실 거라 생각합니다.
다음 줄에 주목하세요: Hoozit.prototype = new Gizmo(); 이 줄은 프로토타입 상속의 핵심 코드입니다.
또한 테스트 및 기타 메서드는 새 Gizmo() 뒤에만 추가할 수 있습니다. 이 순서는 되돌릴 수 없습니다. 테스트 및 기타 메소드를 먼저 추가한 후 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; };
위의 그림을 잘 보세요. 프로토타입 상속에 대한 그림입니다. 왼쪽 하단에 있는 새로운 Hoozit(stirng)은 새로 생성된 개체를 나타냅니다. 다음 설명의 편의를 위해 그를 objH1이라고 부릅니다. 맨 오른쪽의 회색 화살표는 프로토타입 상속 체인입니다.
기사 시작 부분의 영어 단락에 따르면 각 함수에는 객체인 프로토타입이 있고 객체에는 생성자 속성이 포함되어 있음을 알 수 있습니다. 그 중 Object, Gizmo, Hoozit 등이 함수인데 프로토타입 항목이 있는데 이 프로토타입도 객체를 가리키고 있고 생성자는 자신을 다시 가리킨다는 것을 알 수 있습니다.
Hoozit.prototype.test = function (id) { return this.id === id; }; Hoozit.prototype = new Gizmo(2); var h=new Hoozit(); alert(h.test(3)); //这里会报错!!
그런데 여기서 사고가 발생했습니다. Hoozit 프로토타입 객체에 생성자 속성이 없지만 이 함수의 오른쪽에 다음을 포함하는 빈 객체가 있다는 것을 발견했습니다. 생성자 속성? 왜? 이 문제는 프로토타입 상속 중에 발생합니다. 주로 Hoozit.prototype = new Gizmo(); 문장 때문입니다. 이 문장이 의미하는 바는 새로운 Gizmo 객체가 생성되어 Hoozit의 프로토타입에 할당된다는 것입니다! 그럼 잘 생각해보면 Hoozit의 원래 프로토타입 객체는 연결이 끊어진 걸까요? ? 예, 그게 다입니다. 따라서 생성자 속성이 있는 빈 개체에 더 이상 액세스할 수 없습니다!
이제 또 다른 질문이 있습니다. Hoozit.prototype = new Gizmo(); 코드 줄을 전달한 후 Hoozit.prototype.constructor는 어디를 가리킵니까? 매우 간단합니다. (new Gizmo()).constructor가 어디를 가리키는지 아시나요? 위 그림을 보면 기즈모(Gizmo) 기능을 가리키는 것을 확실히 알 수 있습니다. 그래서 우리는 현재 Hoozit.prototype.constructor도 그곳을 가리킨다고 결론을 내립니다!
위의 코드 줄은 우리의 추측을 검증합니다! 자, 위에서 함수(생성자 측)에 대한 이야기를 마쳤고 인스턴스 객체에 대해 이야기해 보겠습니다. 각 인스턴스 객체는 생성자 속성을 가지며 생성자(함수)를 가리킵니다. 그리고 new의 모든 인스턴스는 프로토타입 생성자의 인스턴스입니다.
alert(Gizmo.prototype.constructo===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(){} */