【12月8日】原型链和原型链继承

原型链相关

每个引用类型(除了null)都有一个隐式原型__proto__
每个函数都有一个显式原型prototype

prototype

function Person() {

}
// 虽然写在注释里面,但是需要注意的是
// prototype 是函数才会有的属性 (哈哈哈,看来在JavaScript中函数果然是有特权的……)
Person.prototype.name = "Kevin";
var person1 = new Person();
var person2 = new Person();

console.log(person1.name) // Kevin
console.log(person2.name) // Kevin

这个Person.prototype到底是什么,为什么在上面添加属性,在构造函数的实例化对象上都能访问到呢?

其实 Person这个函数的prototype属性指向了一个对象,即:Person.prototype也是一个对象。(真是好多对象)这个对象正是调用该构造函数而创建的实例的原型。也就是这个例子中的person1和person2的原型。

  • 1.调用的构造函数: Person
  • 2.使用什么调用: new关键字
  • 3.得到了什么: 两个实例化对象person1、person2
  • 4.实例化对象和原型是什么关系: person1和person2的原型就是 Person.prototype


    image.png

proto

这个属性会指向该对象的原型

function Person() {

}

var person = new Person();
console.log(person.__proto__ === Person.prototype); //true;

image.png

构造函数Person 和实例对象person 分别通过prototype和proto 和实例原型Person.prototype进行关联,根据箭头指向我们不禁要有疑问:
实例原型是否有属性指向构造函数或者实例呢?

constructor

实例原型指向实例的属性倒是没有,因为一个构造函数可能会生成很多个实例,但是原型指向构造函数的属性倒是有的,这就是我们的constructor——每一个原型都有一个constructor属性指向关联的构造函数。
[图片上传中...(image.png-d6aa3c-1607417922586-0)]

我们再来看一个示例:

function Person() {

}
console.log(Person === Person.prototype.constructor); // true
image.png

继承是什么

继承就是子类可以使用父类的所有功能,并且对这些功能进行扩展

方法一 通过原型链继承

核心:将子类的原型对象指向父类的实例。

function Parent () {
  this.name = 'Parent'
  this.sex = 'boy'
}
Parent.prototype.getName = function () {
  console.log(this.name)
}
function Child () {
  this.name = 'child'
}
Child.prototype = new Parent()

var child1 = new Child()
child1.getName()
console.log(child1)
image.png

原型链继承的缺点

function Parent (name) {
  this.name = name
  this.sex = 'boy'
  this.colors = ['white', 'black']
}
function Child () {
  this.feature = ['cute']
}
var parent = new Parent('parent')
Child.prototype = parent

var child1 = new Child('child1')
child1.sex = 'girl'
child1.colors.push('yellow')
child1.feature.push('sunshine')

var child2 = new Child('child2')

console.log(child1)//Parent { feature: [ 'cute', 'sunshine' ], sex: 'girl' }
console.log(child2)//Parent { feature: [ 'cute' ] }

console.log(child1.name)//parent
console.log(child2.colors)//[ 'white', 'black', 'yellow' ]

console.log(parent)//Parent {name: 'parent',sex: 'boy', colors: [ 'white','black', 'yellow' ]
}

  • child1.sex = 'girl'这段代码相当于是给child1这个实例对象新增了一个sex属性。相当于是:原本我是没有sex这个属性的,我想要获取就得拿原型对象parent上的sex,但是现在你加了一句child1.sex就等于是我自己也有了这个属性了,就不需要你原型上的了,所以并不会影响到原型对象parent上😊。
  • 但是child1.colors这里,注意它的操作,它是直接使用了.push()的,也就是说我得先找到colors这个属性,发现实例对象parent上有,然后就拿来用了,之后执行push操作,所以这时候改变的是原型对象parent上的属性,会影响到后续所有的实例对象。(这里你会有疑问了,凭什么sex就是在实例对象child上新增,而我colors不行,那是因为操作的方式不同,sex那里是我不管你有没有,反正我就直接用=来覆盖你了,可是push它的前提是我得先有colors且类型是数组才行,不然你换成没有的属性,比如一个名为clothes的属性,child1.clothes.push('jacket')它直接就报错了,如果你使用的是child1.colors = ['yellow']这样才不会影响parent
  • child1.name是原型对象parent上的name,也就是'parent',虽然我们在new Child的时候传递了'child1',但它显然是无效的,因为接收name属性的是构造函数Parent,而不是Child。
  • child2.colors由于用的也是原型对象parent上的colors,又由于之前被child1给改变了,所以打印出来的会是['white', 'black', 'yellow']
    将最后的原型对象parent打印出来,name和sex没变,colors却变了。

总结

Child.prototype = new Parent()
优点
继承了父类的模板,又继承了父类的原型对象

缺点

  • 如果要给子类的原型上新增属性和方法,就必须放在Child.prototype = new Parent()这样的语句后面
  • 无法实现多继承(因为已经指定了原型对象了)
  • 来自原型对象的所有属性都被共享了,这样如果不小心修改了原型对象中的引用类型属性,那么所有子类创建的实例对象都会受到影响(这点从修改child1.colors可以看出来)
  • 创建子类时,无法向父类构造函数传参数(这点从child1.name可以看出来)
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容