0%

原型链、继承| JavaScript高级学习(三)

对象的继承

原型链

实例对象和原型对象之间的关系,关系是通过原型(__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>

附组合继承|构造函数继承图解

继承的写法四:拷贝继承

//拷贝继承:把一个对象中属性,方法通过遍历复制到另一个对象中

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高级