继承
继承是 OOP 语言中的一个最为人津津乐道的概念。许多OOP 语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。如前所述,由于函数没有签名,在ECMAScript 中无法实现接口继承。ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的。
原型链
ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。
说明
- 定义Son构造函数后,我们没有再使用Son的默认原型,而是把他的默认原型更换成了Father类型对象。
- 这时,如果这样访问 son1.name, 则先在son1中查找name属性,没有然后去他的原型( Father对象)中找到了,所以是"马云"。
- 如果这样访问 son1.giveMoney(), 先在son1中找这个方法,找不到去他的原型中找,仍然找不到,则再去这个原型的原型中去找,然后在 Father的原型对象中 找到了。
- 从图中可以看出来,在访问属性和方法的时候,查找的顺序是这样的:对象->原型->原型的原型->...->原型链的顶端。 就像一个链条一样,这样 由原型连成的"链条",就是我们经常所说的原型链。
- 从上面的分析可以看出,通过原型链的形式就完成了JavaScript的继承
默认顶端原型
在 JavaScript 中所有的类型如果没有指明继承某个类型,则默认是继承的 Object 类型。这种 默认继承也是通过原型链的方式完成的。
下面的图就是一个完整的原型链:
说明:
- 原型链的顶端一定是Object这个构造函数的原型对象。这也是为什么我们随意创建一个对象,就有很多方法可以调用,其实这些方法都是来自Object的原型对象。
- 通过对象访问属性、方法的时候,一定是会通过原型链来查找的,直到原型链的顶端。
- 一旦有了继承,就会出现多态的情况。假设需要一个Father类型的数据,那么你给一个Father对象,或Son对象都是没有任何问题的。而在实际执行的过程中,一个方法的具体执行结果,就看在原型链中的查找过程了。给一个实际的Father对象则从Fahter的原型链中查找,给一个实际的Son则从Son的原型链中查找。
- 因为继承的存在,Son的对象,也可以看出Father类型的对象和Object类型的对象。 子类型对象可以看出一个特殊的父类型对象。
几种测试数据的类型
1.typeof:一般用来测试简单数据类型和函数的类型。
2.instanceof: 用来测试一个对象是不是属于某个类型。结果为boolean值。
3.isPrototypeOf( 对象 ) : 这是个 原型对象 的方法,参数传入一个对象,判断参数对象是不是由这个原型派生出来的。 也就是判断这个原型是不是参数对象原型链中的一环。
4.函数借调 Object.prototype.toString.call()
<script>
function Father () {
}
function Son () {
}
Son.prototype = new Father();
var son = new Son();
console.log(son instanceof Son); // true
// Son通过原型继承了Father
console.log(son instanceof Father); // true
//Father又默认继承了Objcet
console.log(son instanceof Object); // true
console.log(Son.prototype.isPrototypeOf(son))//true
console.log(typeof new Son())//object
console.log(Object.prototype.toString.call(/a/gi))//借调可以检测出它的类型 RegExp
console.log(Object.prototype.toString.call([]))//返回Array
console.log(Object.prototype.toString.call(function () {
}))//返回function
var msg=Object.prototype.toString.call([])
console.log(/[ ](\w+)]/gi.exec(msg))//用正则表达式输出类型
</script>
借用构造函数调用"继承"
借用构造函数调用 继承,又叫伪装调用继承或冒充调用继承。虽然有了继承两个字,但是这种方法从本质上并没实现继承,只是完成了构造方法的调用而已。
使用 call 或 apply 这两个方法完成函数借调。这两个方法的功能是一样的,只有少许的区别(暂且不管)。功能都是更改一个构造方法内部的 this 指向到指定的对象上。
<script>
function foo(name,age) {
this.name=name;
this.age=age;
}
var obj={};
//函数借调 借用call
foo.call(obj,"张三","20")//借调了foo的this,此处的this就指向obj,所以obj就多了
//foo的属性
console.log(obj)
</script>
组合继承
组合函数利用了原型继承和构造函数借调继承的优点,组合在一起。成为了使用最广泛的一种继承方式。
<script>
function Animal(name,color) {
this.name=name;
this.color=color;
}
Animal.prototype.eat=function () {
console.log("动物在吃")
}
function Dog(name,color,weight) {
Animal.call(this,name,color);//借调animal中的this属性
//this是Animal的this 只是指向改变 指向了dog
this.weight=weight;
}
Dog.prototype=new Animal();
Dog.prototype.constructor=Dog;
var dog=new Dog("黑子","黑色",20)
console.log(dog)
dog.eat()
console.log(dog instanceof Animal)
</script>
说明
- 组合继承是我们实际使用中最常用的一种继承方式。
- 在子类型的构造函数中借调了父类型的构造函数,也就是说,子类型的原型(也就是Father的对象)中有的属性,都会被子类对象中的属性给覆盖掉。