JavaScript继承

JavaScript继承

一般很多面向对象语言都支持两种继承:接口继承和实现继承。前者只继承方法签名,后者继承实际方法。接口继承在ECMAScript中是不可能的,因为没有签名。实现继承时ECMAScript唯一支持的继承方式,而这主要是通过原型链实现的。

1.原型链

ECMAScript把原型链定义为ECMAScript的主要继承方式。其基本思想就是通过原型继承多个引用类型的属性和方法。每个构造函数都有一个原型对象,原型有一个属性指会构造函数,而实例有一个内部指针指向原型。如果原型是另一个构造函数的实例呢?那就意味着这个原型本身又一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本构想。

// 创建Animal
function Animal(){
  this.name = 'animal'
}
Animal.prototype.getAnimalName = function(){
  console.log(this.name + '  getAnimalName');
}
// 创建Dog
function Dog(){
  this.name = 'dog'
}
/**
 Dog继承自Animal 将Animal的实例赋值给Dog的原型对象,相当于将Animal的实例中的__proto__赋值给了
 Dog原型对象
 */
/**
 如此Dog原型对象 就能通过Animal对象的实例中的[[prototype]](__proto__)来访问到Animal
 原型对象中的属性和方法了。
 */
Dog.prototype = new Animal()
// 不建议使用Dog.prototype.__proto__ === Animal.prototype,因为双下划线的属性是js中的内部
// 属性,各个浏览器兼容不一,不建议直接操作属性,ES6中提供了操作属性的方法可以实现

console.log(Dog.prototype.__proto__ === Animal.prototype);//true
	
// 在使用原型链继承的时候,要在继承之后再去原型对象上定义自己所需的属性和方法
Dog.prototype.getDogName = function(){
  console.log(this.name + '  getDogName');
}
var d1 = new Dog()
d1.getAnimalName() //dog  getAnimalName
d1.getDogName() //dog  getDogName

上面代码通过创建Animal的实例并将其赋值给Dog的原型对象,所以Dog.prototype实现了对Animal的继承。这个赋值重写了Dog最初的原型,将其替换为Animal的实例。这意味着Animal实例可以访问的所有属性和方法也会存在于Dog.prototype。这样实现继承之后,代码紧接着又给Dog.prototype,也就是Animal的实例添加了一个新方法。最后又创建了Dog的实例并调用了它继承的getAnimalName方法。

案例中Dog没有使用默认原型,而是将其替换成一个新的对象。这个新的对象恰好是Animal的实例。这样一来,Dog的实例就不仅能从Animal的实例中继承属性和方法,而且还与Animal的原型挂钩。于是d1(通过内部属性[prototype]__protp__)指向Dog.prototype,而Dog.prototype(作为Animal的实例又通过内部的[[Prototype]])指向Animal.prototype。注意getAnimalName()方法还在Animal.prototype对象上,而name属性则在Dog.prototype上,这是因为getAnimalName()是一个原型方法,而name是一个实例属性。Dog.prototype现在是一个Animal实例,因此name才会存储在它上面。

还要注意,由于Dog.prototype的constructor属性被重写为指向Animal,所以d1.constructor也指向Animal,想要指回Dog可以修改Dog.prototype.constructor。在读取实例上的属性时,首先会在实例上搜索这个Animal属性。如果没找到,则会继承搜索实例的原型,这就是原型搜索机制。在通过原型链实现继承后,搜索就可以继承向上,搜索原型的原型。对前面的例子而言,调用d1.getAnimalName()经过了3不搜索:d1、Dog.protorype和Animal.prototype,最后一步找到这个方法。搜索机制会一直搜索至原型链的末端。

2.经典继承

经典继承的思路很简单:在子类构造函数中调用父类构造函数。因为函数就是在特定上下文中执行代码的简单对象,所以可以使用apply()和call()方法以创造函数的对象为上下文执行构造函数。

// 经典继承
function Animal(){
  this.categorys = ['cat','rabbit']
}
function Dog(){
  // 继承Animal
  Animal.call(this)
}
/*
  在var d1 = new Dog()时,是d1调用构造函数,所以其内部this的值指向的是d1,
  所以Animal.call(this)就相当于Animal.call(d1),就相当于d1.Animal().最后,
  d1去调用Animal方法时,Animal内部的this就指向了d1,。那么Animal内部this上的所有
  属性和方法,都被拷贝到了d1上。所以,每个实例都具有自己的category属性副本。他们互不影响
*/
var d1 = new Dog()
d1.categorys.push('dog')
console.log(d1.categorys);//[ 'cat', 'rabbit', 'dog' ]
var d2 = new Dog()
console.log(d2.categorys);//[ 'cat', 'rabbit' ]

我们在Dog的构造函数中展示了经典继承函数的调用。通过使用call()(或apply())方法,Animal构造函数在为Dog的实例创建的新对象的上下文中执行了。这相当于新的Dog对象上运行了Animal()函数中的所有初始化代码。结果就是每个实例都会有自己的categorys属性。

  • 传递参数

    相比于使用原型链,经典继承函数的一个优点就是可以在子类构造函数中向父类构造函数传参。

    function Animal(name){
    	this.name = name
    }
    function Dog(){
        //继承 Animal并传参
        Animal.call(this,'zhangsan')
      //实例属性
        this.age = 29
    }
    var d = new Dog()
    console.log(d.name)//zhangsan
    console.log(d.age)//29
    

    在这个例子中,Animal构造函数接收一个参数name,然后将它赋值给一个属性。在Dog构造函数中调用Animal构造函数时传入这个函数,实际上会在Dog的实例上定义name属性。为确保Animal构造函数不会覆盖Dog定义的属性,可以在调用父类构造函数之后再给子类实例添加额外的属性。

3.组合继承

组合继承综合了原型链继承和经典继承函数,将两者的优点集中了起来。基本的思路是使用原型链继承上的属性和方法,而通过经典继承函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。

// 组合继承
function Animal(name){
  this.name = name
  this.categorys = ['cat','rabbit']
}
Animal.prototype.sayName = function(){
  console.log(this.name);
}
function Dog(name,age){
  // 继承属性
  Animal.call(this,name)
  this.age = age
}
// 继承方法
Dog.prototype = new Animal()
Dog.prototype.sayAge = function(){
  console.log(this.age);
}
var d1 = new Dog('zhangsan',29)
d1.categorys.push('dog')
console.log(d1.categorys);//[ 'cat', 'rabbit', 'dog' ]
d1.sayName() //zhangsan
d1.sayAge()//29
var d2 = new Dog('lisi',27)
console.log(d2.categorys);//[ 'cat', 'rabbit' ]
d2.sayName()//lisi
d2.sayAge()//27

在这个例子中,Animal构造函数定义了两个属性,name和categorys,而它的原型上也定义了一个方法叫sayName()。dog构造函数调用了Animal构造函数,传入了name参数,然后又定义了自己的属性age。此外,Dog.prototype也被赋值为Animal的实例。原型赋值之后,又在这个原型上添加了新方法sayAge().这样,就可以创建两个Dog实例,让这两个实例都有自己的属性,包括categorys,同时还共享相同的方法。组合继承弥补了原型链继承和经典继承函数的不足,是JavaScript中使用最多的继承模式。而且组合继承也保留了instanceof操作符和isPrototypeOf()方法识别合成对象的能力。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值