一、继承的概念
继承是所有面向对象语言中最重要的一个特征。而执行环境也是JS最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据的范围,决定了它们各自的行为。
主要继承方式:接口继承和实现继承
JS主要支持实现继承,而且是在原型链的基础上实现的,继承是发生在对象与对象之间
二、原型链的概念
原型链作为实现继承的主要方法,是利用原型让一个引用类型继承另一个引用类型的属性和方法
构造函数、原型(对象)和构造函数创造的对象之间的关系:每个构造函数都有一个属性 prototype 指向一个原型对象,每个原型对象也有一个属性constructor指向函数,通过new 构造函数()创建出来的对象内部有一个不可见的属性[[proto]]指向构造函数的原型。
1、更换构造函数的原型
原型其实就是一个对象,只是默认情况下原型对象是浏览器会自动帮我们创建的,而且自动让构造函数的prototype属性指向这个自动创建的原型对象。我们完全可以将原型对象更换成我们自定义类型的对象。
//定义一个构造函数。
function Father () {
// 添加name属性. 默认直接赋值了。当然也可以通过构造函数传递过来
this.name = "马云";
}
//给Father的原型添加giveMoney方法
Father.prototype.giveMoney = function () {
alert("我是Father原型中定义的方法");
}
//再定义一个构造函数。
function Son () {
//添加age属性
this.age = 18;
}
//关键地方:把Son构造方法的原型替换成Father的对象。 因为原型是对象,任何对象都可以作为原型
Son.prototype = new Father();
//给Son的原型添加getMoney方法
Son.prototype.getMoney = function () {
alert("我是Son的原型中定义的方法");
}
//创建Son类型的对象
var son1 = new Son();
以上代码即实现了Son继承了Father的过程
注意:继承过程中的访问属性和方法的过程为:
对象本身->原型->原型的原型->...->原型链的顶端(Object原型对象)这也是为什么我们随意创建一个对象,就有很多方法可以调用,其实这些方法都是来自Object的原型对象(Object原型对象的原型有人说是null,也有人说是它本身)
2、测试数据类型的四种方法
2.1、typeof:一般用来测试简单数据类型和函数的类型。如果用来测试对象,则会一直返回object,没有太大意义。
2.2、instanceof:用来测试一个对象是不是属于某个类型。结果为boolean值。
function Father () {
}
function Son () {
}
Son.prototype = new Father();
var son = new Son();
alert(son instanceof Son); // true
// Son通过原型继承了Father
alert(son instanceof Father); // true
//Father又默认继承了Objcet
alert(son instanceof Object); // true
2.3、isPrototypeOf( 对象 ) : 这是个 原型对象 的方法,参数传入一个对象,判断参数对象是不是由这个原型派生出来的。 即判断这个原型是不是参数对象原型链中的一环。
function Father () {
}
function Son () {
}
Son.prototype = new Father();
var son = new Son();
alert(Son.prototype.isPrototypeOf(son)); // true
alert(Father.prototype.isPrototypeOf(son)); // true
alert(Son.prototype.isPrototypeOf(Father)); //false
alert(Object.prototype.isPrototypeOf(Father)) //true
alert(Object.prototype.isPrototypeOf(son)); // true
//从参数对象原型开始
2.4、Object.prototype.toString.call(对象或基本类型)
function Foo(){
}
var arr = new Array("a");
var s = arr.toString();
console.log(s);
console.log(Object.prototype.toString.call(foo)) //测试数据类型
三、 原型链在继承中的缺陷
1、在原型链中,父类型的构造函数创建的对象,会成为子类型的原型。则父类型中定义的实例属性,就会成为子类型的原型属性。对子类型来说,这和我们以前说的在原型中定义方法,构造函数中定义属性是违背的。子类型原型(父类型对象)中的属性被所有的子类型的实例所共有,如果有一个实例(即构造函数创建的对象)去更改,则会很快反应的其他的实例上。
function Father () {
this.girls = ["志玲", "凤姐"];
}
function Son () {
}
// 子类的原型对象中就有一个属性 girls ,是个数组
Son.prototype = new Father();
var son1 = new Son();
var son2 = new Son();
//给son1的girls属性的数组添加一个元素
son1.girls.push("亦非");
//这时,发现son2中的girls属性的数组内容也发生了改变
alert(son2.girls); // "志玲", "凤姐", "亦非"
2、向父类型的构造函数中传递参数问题
如以上,父对象的属性和方法会被子对象(实例所共享)则父类型传递的参数也会被子对象(实例所共享):父类型传递的地方:Son.prototype = new Father();
四、借用构造函数调用"继承"(函数借调)
为解决以上属性共享问题:
借用构造函数调用继承,又叫伪装调用继承或冒充调用继承。虽然有了继承两个字,但是这种方法从本质上并没实现继承,只是完成了构造方法的调用而已。
1、借调方法
主要有call和apply方法:功能都是更改一个构造方法内部的this指向到指定的对象上。(后面详细有介绍)
function Father(x , y ){
this.x = x;
this.y = y;
}
function Son(x , y , z){
Father.apply(this , [x , y]);//将数组展开,但是call不会
this.z = z;
}
var s1 = new Son(1 , 2 , 3);
console.log(s1);//Son {x: 1, y: 2, z: 3}
call和apply应用
var name = "a"
function foo(a, b){
console.log(a + " " + b);
this.name = "d"
}
var obj = {
name : "c"
}
foo.call(obj, 1, 3); //不影响后面内容
console.log(obj.name); //"d"
//计算一个数组中的最大值和最小值
var arr = [30, 6, 9, 30, 50];
var max = Math.max.apply(Math, arr)//50
console.log(max);
console.log(Math.min.apply(Math, arr));//6
注意:函数借调其实并没有真的继承,仅仅是调用了Father构造函数而已。也就是说,son对象和Father没有任何的关系。
网友评论