对象的继承
原型链
实例对象和原型对象之间的关系,关系是通过原型(__proto__
)来联系的,这个关系就是原型链
原型指向改变
实例对象的原型对象__proto__
指向的是该对象所在的构造函数的原型对象
构造函数的原型对象prototype
如果指向改变了,实例对象的原型__proto__
指向也会发生改变
代码1
function Person(age){
this.age = age
}
//人的原型对象方法
Person.prototype.sayHello = function(){
console.log('你好,我是个人')
}
//学生的构造函数
function Student(){
}
//学生的原型对象方法
Student.prototype.sayHi = function(){
console.log("我是学生,我好帅啊")
}
//学生的原型,指向一个人的实例对象
Student.prototype = new Person(10)
var stu = new Student()
stu.sayHello()
//stu.sayHi() //报错
console.dir(Person)
console.dir(Student)
console.dir(stu)
上图是修改了构造函数Student的原型prototype
的指向,指向了Person的实例对象,实例对象通过原型__proto__
指向Person的原型对象,所以可以访问Person的原型对象方法sayHello,而原来的sayHi丢失。
构造函数、原型对象和实例对象三者的关系
构造函数的prototype
指向原型对象,
原型对象的constructor
指向构造函数,
实例对象的__proto__
指向原型对象的prototype
原型的最终指向
任何对象都有原型,原型对象也是对象,所以原型对象中也有__proto__
原型
//创建Person构造函数
function Person(age){
this.age = age
}
var per = new Person()
console.dir(per)
console.dir(Person)
console.log(per.__proto__.__proto__)
console.log(Person.prototype.__proto__)
console.log(per.__proto__.__proto__.__proto__)
console.dir(Object.prototype.__proto__)
实例对象per的原型__proto__
—指向—> 构造函数的原型prototype
—指向—>
构造函数的原型prototype
中的__proto__
—指向—>Object的原型prototype
Object的原型prototype
中的__proto__
—指向—>null
指向和构造函数无关
即如图所示
原型指向改变后向原型中添加方法
把上面代码1的代码
把方法在原型指向改变后再添加
function Person(age){
this.age = age
}
//人的原型对象方法
Person.prototype.sayHello = function(){
console.log('你好,我是个人')
}
//学生的构造函数
function Student(){
}
//学生的原型,指向一个人的实例对象
Student.prototype = new Person(10)
/*把方法在原型指向改变后再添加*/
//学生的原型对象方法
Student.prototype.sayHi = function(){
console.log("我是学生,我好帅啊")
}
var stu = new Student()
stu.sayHello()
stu.sayHi() //不报错
console.dir(Person)
console.dir(Student)
console.dir(stu)
使用对象的属性或者方法的优先次序|覆盖(overriding)
原型对象也是对象,所以它也有原型,
当我们使用一个对象的属性或方法时,会在自身中寻找,
自身如果有,则直接使用
如果没有则去原型对象中寻找
如果还没有则去原型的原型中寻找,直到找到Object对象的原型,
Object对象的原型没有原型,如果Object中依然没有找到,则返回undefined
function Person(name){
this.name = name
}
Person.prototype.name = function(){
console.log("我是原型中的name")
}
per = new Person("张三")
console.log(per.name)
console.log(per.age) //undefined
由于js是一门动态类型语言,对象没有什么,只要点了,对象就有了这个东西(属性),
只要对象.属性名字,对象就有这个属性了,但是没有赋值,所以为undefined
继承
大部分面向对象语言,都有类(Class)实现对象之间的继承,JS是基于对象语言,没有类的概念,对象与对象之间的关系是通过原型对象(prototype)来实现继承
继承的写法一(不推荐):原型继承
//父类的构造函数
function Sup(name,age){
this.name = name
this.age = age
}
//子类的构造函数
function Sub(){
}
//继承
Sub.prototype = new Sup('张三',11)
//子类的原型对象中添加方法
Sub.prototype.play = function(){
console.log("我是子,孩子爱玩是天性")
}
var sub0 = new Sub()
console.log(sub0.name) //张三
console.log(sub0.age) //11
sub0.play() //我是子,孩子爱玩是天性
var sub1 = new Sub()
console.log(sub1.name) //张三
console.log(sub1.age) //11
sub1.play() //我是子,孩子爱玩是天性
即通过修改子类原型的指向,指向父类的一个实例化对象,来实现继承
缺点:
因为改变原型指向的同时实现继承—-直接继承属性,所以继承过来的属性值都一样。
改进:
//继承
Sub.prototype = new Sup('张三',11)
//子类的原型对象中添加方法
Sub.prototype.play = function(){
console.log("我是子,孩子爱玩是天性")
}
var sub0 = new Sub()
console.log(sub0.age)
sub0.play()
var sub1 = new Sub()
sub1.name = "李四"
sub1.age = 15
console.log(sub1.name)
console.log(sub1.age)
sub1.play()
重新把 属性赋值,但是太麻烦
继承的写法二:借用构造函数
一、
/**
* 借用构造函数来继承
* 在子类构造函数中使用
* 父类构造函数.call(当前实例对象,属性值,属性值,属性值,属性值,...)
*
*/
function Person(name,age,sex){
this.name = name
this.age = age
this.sex = sex
}
function Student(name,age,sex,score){
Person.call(this,name,age,sex)
this.score = score
}
Person.prototype.sayHi = function(){
console.log("ni hao ")
}
var stu = new Student("张三",12,"男",100)
console.log(stu.name,stu.age,stu.sex,stu.score)
stu.sayHi() //报错
//缺点,不能调用父级原型对象的方法
二、最好的方法
function Person(name){
this.name = name
}
Person.prototype.eat = function(){
console.log("不吃饭不行")
}
//第一步,子类继承父类实例
function Student(name,age){
Person.call(this,name)
this.age = age
}
//第二步,子类继承父类原型
//子类的原型,要将它赋值为Object.create(Person.prototype)
Student.prototype = Object.create(Person.prototype)
//Student.prototype = new Person() //上一步亦可以写成这样,即学生原型指向空的父类实例化对象
Student.prototype.constructor = Student
var stu = new Student("张三",19)
console.log(stu.name)
stu.eat()
实际上就是组合继承
继承的写法三:组合继承
<script type="text/javascript">
/**
* 组合继承
* 把改变原型链继承和构造函数继承组合到一起
*
*/
function Person(name,age,sex){
this.name = name
this.age = age
this.sex = sex
}
function Student(name,age,sex,score){
//借用构造函数,属性值重复问题
Person.call(this,name,age,sex)
this.score = score
}
Person.prototype.sayHi = function(){
console.log("ni hao ")
}
//原型继承
Student.prototype = new Person() //不传值
//别忘了指回Student.prototype.constructor,指回的作用是知道是由谁构造出来的。
Student.prototype.constructor = Student
var stu = new Student("张三",12,"男",100)
console.log(stu.name,stu.age,stu.sex,stu.score)
stu.sayHi() //成功调用父级方法
</script>
附组合继承|构造函数继承图解
继承的写法四:寄生式组合继承
组合继承虽然很好的实现了继承,但是也存在效率问题。主要效率问题是父类构造函数始终会被调用两次:
- 第一次是在创建子类原型时调用
- 第二次是在子类构造函数中调用
寄生式组合继承
function Super(name){
this.name = name
this.color = ['red','blue','green']
}
Super.prototype.sayName = function(){
console.log(this.name)
}
function Sub(name,age){
Super.call(this,name)
this.age = age
}
Sub.prototype = Object.create(Super.prototype)// 不使用Sub.prototype = new Super(),减少了一次父类构造函数的调用。
Sub.prototype.constructor = Sub
寄生式组合继承可以算是引用类型继承的最佳模式。
继承的写法五:拷贝继承
//拷贝继承:把一个对象中属性,方法通过遍历复制到另一个对象中
var obj1 = {
name: "张三",
age: 19,
sayHi: function(){
console.log("我是张三")
}
}
var obj2 = obj1 //只是把obj1的指针指向赋给obj2
//拷贝继承
var obj3 = {}
for(var key in obj1){
obj3[key] = obj1[key]
}
obj3.sayHi()
console.log("分隔符——————————")
function Person(){
}
Person.prototype.name = "人类"
Person.prototype.sayHi = function(){
console.log("你好我是人")
}
//Person的构造中有原型prototype,prototype是的对象,name、age是该对象的属性或方法
var obj4 = {}
for(var key in Person.prototype){
obj4[key] = Person.prototype[key]
}
console.dir(obj4)
obj4.sayHi()
一个小题
function F1(age){
this.age = age
}
function F2(age){
this.age = age
}
F2.prototype = new F1(10)
function F3(age){
this.age = age
}
F3.prototype = new F2(20)
var f3 = new F3(30)
console.log(f3.age) //30
以上内容参考学习于黑马教程,阮一峰js高级