原型链相关
每个引用类型(除了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可以看出来)
