处于安全的考虑,本地类和宿主类不能作为基类。JavaScript中的继承是通过模仿实现的。
对象冒充:构造函数方法使用this关键为对象添加属性和方法,并且赋值。但构造函数A只是
一个方法,是一个对象,因此可以将A赋值给其他对象B的方法。那么B对象将成为A中的this指向
的对象,那么B就可以获得A构造函数中的方法和属性
<script type="text/javascript">
function PersonA(name) {
//如果是用new关键字生成PersonA对象,那么this指向new生成的当前对象
//如果将PersonA函数对象赋值给对象B的方法属性,那么this指向B对象,
//即this关键字指向PersonA方法所属的对象
this.name = name;
this.sayName = function() { alert(this.name); }
}
function PersonB(name, age) {
//当前对象的method属性引用PersonA方法
this.method = PersonA;
//当前对象调用method方法。由于PersonA中的this指向当前对象,那么PersonA中语句就为
//当前对象添加了name属性和sayName方法,并且初始化
this.method(name);
//删除当前对象的method属性,因为该属性再也没有用处了。
delete this.method;
//为当前对象添加属性和方法
this.age = age;
this.sayAge = function() { alert(this.age); }
}
var obj1 = new PersonA("Koji");
var obj2 = new PersonB("Luo", 22);
obj1.sayName(); //Koji
obj2.sayName(); //Luo 当前对象也有了name属性和sayName方法
obj2.sayAge(); //22
</script>
这只是表面实现了继承的能力,子类获得父类的属性和方法。在JavaScript中,
当将PersonA
方法赋值给PersonB的this的method属性时,PersonA中this指向PersonB中的this引用的对象。而当
通过new生成PersonB的对象时,PersonB中的this指向new关键字生成的对象。此时,PersonA中的
this和PersonB中的this都指向了通过new关键字生成的PersonB 的当前对象,那么就可以通过PersonA
中的this和PersonB中的this为当前对象添加属性和方法。
对象冒充可以实现多继承。可以通过重复上面的获得构造函数方法并且调用该方法实现。但如
果子类拥有相同的属性或方法,那么前面的构造方法为当前对象的属性赋的值会被后面的构造方法
为相同属性赋予的值替换掉,因为父类子类中的this都指向当前对象,属性的取值取决于最后为该
属性赋值的语句。
call()方法
<script type="text/javascript">
function PersonA(name) {
this.name = name;
this.sayName = function() { alert(this.name); }
}
function PersonB(name, age) {
//call方法的第一个参数是赋值给PersonA中的this的,后面的参数依次赋值给PersonA方法的
//参数列表中对应的参数。那么传入当前对象this使得PersonA中的this引用当前对象。所以
//PersonA中的语句可以为当前对象添加属性和方法。因此PersonB继承了PersonA的属性和方法
PersonA.call(this, name);
//为当前对象添加属性和方法
this.age = age;
this.sayAge = function() { alert(this.age); }
}
var obj1 = new PersonA("Koji");
var obj2 = new PersonB("Luo", 22);
obj1.sayName(); //Koji
obj2.sayName(); //Luo 当前对象也有了name属性
obj2.sayAge(); //22
</script>
上面的call方法的第一个参数this使得PersonA中的this获得当前对象的引用。这样一来,
PersonA中的this和PersonB中的this又都指向了new生成的PersonB的当前对象。
上面实现继承的实质是让“父类”构造函数和“子类”构造函数中的this关键字都引用子类的当前对
象(同一个对象),这样就可以通过“父类”的this和“子类”的this对当前对象添加属性和方法。
原型链(prototype chain)
<script type="text/javascript">
function PersonA() {} //构造函数没有任何参数,这是使用原型链的标准模式
PersonA.prototype.name = "Koji";
PersonA.prototype.sayName = function() { alert(this.name); }
function PersonB() {}
//将PersonB对象的prototype属性设置为一个PersonA的对象,而PersonA对象本身拥有其
//原型的属性和方法,那么PersonB的prototype便拥有了PersonA的属性和方法
PersonB.prototype = new PersonA();
//子类的属性和方法的添加必须在为PersonB的prototype改变值之后进行。如果在之前进行,
//当PersonB的prototype引用别的对象时,先前设置的属性和方法都丢失了。
PersonB.prototype.age = 22;
PersonB.prototype.sayAge = function() { alert(this.age); }
var obj1 = new PersonA();
var obj2 = new PersonB();
obj1.sayName(); //Koji
obj2.sayName(); //Koji 当前对象也有了name属性
obj2.sayAge(); //22
alert(obj1 instanceof PersonA); //true
alert(obj1 instanceof PersonB); //false
alert(obj2 instanceof PersonA); //true
alert(obj2 instanceof PersonB); //true
</script>
原型链的缺点是不支持多继承,因为一个对象的prototype不能指向多个对象。同时使用原型链就
无法使用带参构造函数。原型链的关键是使子类构造函数的prototype属性引用父类的一个对象。
混合方式:使用对象冒充继承构造函数的属性,用原型链继承prototype对象的方法
<script type="text/javascript">
function PersonA(name) { //构造函数定义属性
this.name = name;
}
//原型定义方法
PersonA.prototype.sayName = function() { alert(this.name); }
function PersonB(name, age) {
//对象冒充继承属性,该属性是添加到当前对象中
PersonA.call(this, name);
this.age = age; //通过子类构造函数添加属性
}
//原型链继承父类方法,因为PersonA构造函数没有传参,实际上PersonB构造函数的prototype中
//添加了name属性,只是没有赋值,其值为undefined
PersonB.prototype = new PersonA();
//通过子类的原型添加方法
PersonB.prototype.sayAge = function() { alert(this.age); }
var obj1 = new PersonA("Koji");
var obj2 = new PersonB("Luo", 22);
obj1.sayName(); //Koji
obj2.sayName(); //Luo
obj2.sayAge(); //22
//下面的alert代码能够得到执行,证明PersonB的prototype对象中确实有name属性。
for (var ele in PersonB.prototype) {
if (ele == "name") {
alert(true);
}
}
</script>
JavaScript依照原型链向上的方式搜索属性,即当访问当前对象的属性时,首先在当前对象中查询,
若找到,则返回属性值;若没有找到,则顺着原型链向上找。如果直到根对象的原型,即Object对
象的prototype中都没有找到该属性,则返回undefined。因为PersonB的构造函数即使没有传值,它
也会为当前对象添加一个name属性,设置默认值undefined。那么,当需要查看当前对象的name属
性时绝不会受到其构造函数的prototype中的name属性的影响。因为按照原型链搜索方式,总是先找
到当前对象的name属性。