JS继承机制的深入理解--动态原型存在的问题与解决
准备知识:
构造函数:即是函数也是类,当new 构造函数时,它就是类;当直接使用构造函数名时,它就是普通函数的调用。那是不是所有的函数都可以是构造函数?本人觉得是,语法上没错,但要理解函数最初始创建的目的是作为普通函数还是为了创建对象。
对象原型:对象创建(new)时所依赖的模板,简单来说就是模板里面的原型属性(方法)是所有对象共享的。注意,原型的引用(prototype)默认指向的是一个new Object()对象;改变默认指向:类名(构造函数名).prototype = new 类名()。
继承机制的实现:http://www.w3school.com.cn/js/pro_js_inheritance_implementing.asp,这里讲得还是比较简单清楚的。
类定义的方法:http://www.w3school.com.cn/js/pro_js_object_defining.asp,请注意动态原型方式,下面讲的是这种方式引申出来的问题。
有了这些知识,就可以继续下面的内容。
正文(重点):
下面是正确的代码,也是本人比较推荐实现继承的方式。
function change() {//自行创建一个按钮事件进行调用
try {
var obj = new Man("Curry", 18);
obj.showDetail();
} catch(Exception) {
alert("Err: " + Exception);
}
}
function Person(myName, myAge) {
this.myName = myName;
this.myAge = myAge;
if ((typeof Person._init) == "undefined") {
Person.prototype.showDetail = function() {
alert("I am " + this.myName + ", my age is " + this.myAge);
}
}
Person._init = true;
alert("Person");
}
Man.prototype = new Person();//<span style="color:#FF0000;">1</span>
//new Man();//<span style="color:#FF0000;">2</span>
function Man(myName, myAge){
Person.call(this, myName, myAge);//对象冒充
if ((typeof Man._inited) == "undefined") {
//Man.prototype = new Person();//<span style="color:#FF0000;">3</span>
/*
Man.prototype.showDetail = function() {
alert("I am " + this.myName + ", my age is " + this.myAge + " and sex is man");
}//<span style="color:#FF0000;">4</span>
*/
}
}
请注意图中的1 2 3 4标注,4只是实现原型方法的覆盖,不做继续的讨论。
问题:
第一点, 当1, 2注掉3打开,这是动态原型法的标准形式,第一次点击按钮执行change()方法时,第四行会报找不到showDetail()的错误,再去点击时代码运行又正常了。
第二点, 当1注掉2, 3打开,代码运行正常。
疑问:第一点怎么那么奇怪呢?运行机制是什么?怎样把这些统一为一个运行机制呢?
我的回答:首先new的时候(还没有进入到构造函数内部),环境会为拿到默认的对象原型,然后去创建这个对象(也就是该新建的对象会得到原型的属性和方法,”得到”不是很准确,实际是共享的),然后再去执行构造函数内部去实例化其他属性。
问题分析:
1. 源代码为什么是正确的?因为在代码导入到你点击按钮的时候,标注1的代码会执行一次,也就是说Man类的默认原型引用由Object指向了Person,所以你点击按钮执行change()方法时,创建的obj对象就是以Person为原型。
2. 第一点问题的原因(1,2注掉3打开)?在第一次点击按钮的时候,由于默认的原型引用指向的是Object,所以该对象不会获取到Person里面的showDetail()方法,接着执行标注3的代码去改变Man类的默认原型引用,但这个跟该对象并没有关系(本人猜想该对象持有一个引用指向创建它的那个原型对象),这个只会跟下一次创建Man对象有关,下一个对象是基于默认的Person原型。这就可以解析为什么第二次点击按钮有变得正常执行。
3. 关于第二点问题,相信不用多说大家也会明白了。实际上就是你第一次点击按钮调用change()方法时,已经是第二次(文件加载是第一次)使用Man这个类去创建对象了。
总起,上面仅仅是个人理解,个人也有些疑问没有解决,不对之处请指正。