1.原型对象
在JS中,构造函数体现了面向对象的封装特征
但是,在构造函数构造不同实例对象中创建了多个静态方法,存在内存浪费问题
// 构造函数
function Students(num) {
this.num = num
this.doing = () => {
console.log('learning');
}
}
// 实例化
const stu1 = new Students(1)
const stu2 = new Students(2)
// 造成内存浪费
console.log(stu1.doing === stu2.doing); // false
- 为解决上面问题,我们将共用的实例方法写进构造函数的
原型对象
中,然后通过构造函数创建的实例对象可以共用原型对象中的方法(类比与共享单车) - 构造函数和原型对象中的
this
都指向 实例化的对象
// 构造函数
function Students(num) {
this.num = num
}
// 原型对象
Students.prototype.doing = () => {
console.log('learning');
}
// 实例化
const stu1 = new Students(1)
const stu2 = new Students(2)
console.log(stu1.doing === stu2.doing); // true
原型对象中的this指向:
let temp
// 构造函数
function Students() { }
// 原型对象
Students.prototype.doing = function () {
temp = this
}
// 实例化
const stu1 = new Students()
stu1.doing()
console.log(temp == stu1); // true
2.constuctor属性
constuctor属性用于原型对象指向构造函数
- 前面我们用的使用原型对象都是以给原型对象追加方法的方式,这样给原型对象追加一种方法当然没有问题,但是当我们给原型追加对象追加多种方法时,就可以一起追加给原型对象,减少代码量,提高代码性能。
function Students() { }
console.log(Students.prototype); // 在控制台可以看到constructor:ƒ Students()
Students.prototype = {
sing: function () {
console.log('鸡你太美');
},
reading: function () {
console.log('春眠不觉晓');
}
}
console.log(Students.prototype); // 在控制台看不到constructor指向Students()
- 但是我们所说的一起追加,实则是将原型对象给重写(原型对象以前的一些配置代码被覆盖了),导致我们的原型对象指不回构造函数(因为指回构造函数的配置代码也被覆盖了),这时候,我们需要用
constuctor
属性手动重新指回构造函数。
function Students() { }
console.log(Students.prototype)
Students.prototype = {
constructor: Students, // 用constuctor属性手动重新指回构造函数
sing: function () {
console.log('鸡你太美');
},
reading: function () {
console.log('春眠不觉晓');
}
}
console.log(Students.prototype); // 在控制台又能看见constructor指向Students()
3.对象原型
每一个对象都有[[proto]]属性指向构造函数的原型对象,之所以构造函数构造的实例对象能使用原型对象的方法,也是因为它。
- [[proto]]对象原型中也有一个 constructor属性,指向该实例对象的构造函数
- 因为实例对象必须指向原型对象,所以[[proto]]对象原型是只读的属性,不能更改
// __proto__是JS非标准属性,[[prototype]]和__proto__意义相同
function Students() { }
const stu3 = new Students()
// 实例对象通过对象原型指向原型对象
console.log(stu3.__proto__ === Students.prototype); //constructor: ƒ Students()
4.原型继承
原型对象也可以继承
const Person = {
eyes: 2
}
function Woman() { }
Woman.prototype = Person // 覆盖
Woman.prototype.constructor = Woman // 手动指回
// 实例化
const Xiaohong = new Woman
console.log(Xiaoming.eyes); // 2
但是多个原型对象继承于一个对象时,问题出现了:
- 以上面代码举例,Woman的原型对象虽然继承与Person,但是他也应该有自己的私有原型对象(这些原型对象是其他继承于Person所没有的,例如做饭cook)
- 当我们给Woman添加私有的原型对象,由于Woman和Man都继承于Person,所以给Woman添加私有原型对象(cook),其实是对Person进行了修改,因此,Man也会通过继承而拥有Woman私有原型对象(cook)。
const Person = {}
function Woman() { }
Woman.prototype = Person // 覆盖
Woman.prototype.constructor = Woman // 手动指回
// 给 Woman 添加私有原型对象(cook)
Woman.prototype.cook = function () {
console.log('我会点外卖');
}
function Man() { }
Man.prototype = Person // 覆盖
Man.prototype.constructor = Man // 手动指回
// 实例化
const Xiaohong = new Woman
const Xiaoming = new Man
Xiaoming.cook() // 我会点外卖
// 打印Person对象,我们可以在Person对象上找到cook方法
// 说明我们给 Woman 添加私有原型对象(cook),实则是将原型添加到了Person上
console.log(Person);
这个问题我们可以通过将被继承的Person对象设置成
构造函数
解决
- 将Person设置成构造函数后,通过
new
关键字我们可以为Man和Woman分别创建一个对象,而Woman和Man再分别继承于这两个对象。- 由于封装性,创建的对象不同(这里的不同指是对象不同,内容是相同的,且两个对象互不影响,因为都是通过new创建而来)
- 这样他们就不会继承于一个对象,而是继承于一个构造函数创建的两个不同对象,就不会出现设置了私有原型,但是却出现了共用的问题了。
// 将Person设置成构造函数,而不是对象
// 再通过构造函数生成对象
function Person() { }
function Woman() { }
Woman.prototype = new Person()
Woman.prototype.constructor = Woman // 手动指回
Woman.prototype.cook = function () {
console.log('我会点外卖');
}
function Man() { }
Man.prototype = new Person() // 覆盖
Man.prototype.constructor = Man // 手动指回
// 实例化
const Xiaohong = new Woman
const Xiaoming = new Man
// 小红正常调用
Xiaohong.cook() // 我会点外卖
// 小明调用,会报错‘Xiaoming.cook is not a function’,
// 说明给小红设置的私有原型并没有和小米共用
Xiaoming.cook()
5.原型链
接上面所说,每一个对象都有[[proto]]属性指向构造函数的原型对象,那么原型对象也会有相应[[proto]]属性
console.log(Object.prototype);
function Person() { }
const Xiaozhuang = new Person()
console.log(Xiaozhuang.__proto__ === Person.prototype); // true
console.log(Xiaozhuang.__proto__.constructor === Person); // true
// Person原型对象的__proto__属性指向
console.log(Person.prototype.__proto__); // 输出发现,指向Object的原型对象
// 那么Object的原型对象也是对象,他的__proto__属性指向
console.log(Object.prototype.__proto__); // null
// 说明Object的原型对象的__proto__属性指向为空,Object是最大的对象
- 像这样以[[proto]]链接
原型对象
的链就叫做原型链
,原型继承也是沿着原型链向下的。 - 继承是沿着原型链向下的,查找是沿着原型链向上的。
- 当访问一个对象的属性或方法时,首先查找这个对象自身有没有该属性,如果没有就沿着原型链向上查找。
[[proto]]
对象原型的意义就在与将不同级的原型对象相连成链,方便了原型的继承和查找。
像我们上面讲的原型继承所举例的Person,Woman,Man也是如此,不过为了让他们互不影响,让他们继承于用Person构造函数创建的不同的实例对象而已,这里就不再过多赘述了。
6.instanceof运算符
我们可以通过 instanceof 运算符来检测构造函数的原型对象属性是否出现在某个实例对象的原型链上(或者说检查一个构造函数是否属于另一个构造函数)
function Woman() { }
function Man() { }
const Xiaohong = new Woman()
const Xiaoming = new Man()
console.log(Xiaoming instanceof Woman); // false
console.log(Xiaohong instanceof Woman); // true
console.log(Xiaohong instanceof Object); // true
console.log(Xiaoming instanceof Object); // true
console.log([1, 2, 3] instanceof Array); // true
// 万物皆对象
console.log(Array instanceof Object); // true