原型链相关
1.引入原型对象
1.1构造函数实例化存在的问题
在构造函数中,存在浪费内存的问题,在每一次实例化构造函数(该构造函数里有方法),就会在堆里单独创造一片空间存放该方法,代码如下:
function Star(name, age) {
this.name = name
this.age = age
this.sing = function () {
console.log('唱歌')
}
}
const ldh = new Star('刘德华', 18)
const zjl = new Star('周杰伦', 18)
const zxy = new Star('张学友', 18)
console.log(ldh.sing === zxy.sing) // false
该构造函数在实例化过程中,数据存放如图所示:
在每一次实例化的过程中,都会在堆中开辟一个单独的空间来储存方法,其方法路径也存放在相应的实例化对象中。如果需要实例化的对象比较多,需要开辟空间来储存方法的数目也较多,浪费内存。
1.2 引入原型对象的概念
如果把构造函数里的方法单独拿出来,放在原型对象中,那么在实例化过程中,就不用再单独开辟空间来储存这个方法了,节省了内存。为了更好地理解为什么能够在原型对象中能够访问到方法,我们需要知道相应的原型规则。
1.3 原型规则
- 所有的引用类型(数组,对象,函数),都具有对象的属性,并且可以自由扩展属性;
- 所有的对象都有
__proto__(或者[[prototype]])属性,称为隐式原型,属性值是一个普通对象; - 所有的函数都有
prototype属性,称为显式原型,属性值也是一个普通的对象; - 所有对象的隐式原型
__proto__都指向其构造函数的显式原型prototype - 当试图得到对象的某个属性时,而这个对象没有该属性,那么就会通过其
__proto__隐式原型去原型对象中寻找;
1.4原型对象 (通过函数的prototype属性指向)
在JS中,原型对象也称为原型,它本质就是一个对象。字面意思就是原始的模型,原始祖先。
概念:
1.所有的函数,都有一个prototype属性(显式原型),这个属性是一个指针,指向了原型对象;
2.原型对象默认有一个叫做constructor的属性,该属性指向了这个构造函数本身;
3.我们可以往这个原型对象上添加属性和方法;
所以所有听过构造函数创建的实例,都共享原型对象上包含的属性和方法;
function Person() {}
// dir : 查看对象的所有属性和方法
console.dir(Person)
举例:
function Star(name, age) {
this.name = name
this.age = age
}
// 给对象原型添加方法
Star.prototype.sing = function () {
console.log('唱歌')
}
// 给对象原型添加属性
Star.prototype.cheer = '加油'
const ldh = new Star('刘德华', 18)
const zxy = new Star('张学友', 18)
console.log(ldh)
console.log(ldh.__proto__ === Star.prototype) // 他们都指向原型对象
1.5 构造函数和原型方法中 this 的指向
我们知道,在进行构造函数实例化的过程中,new的执行机制分为以下四步:
- 在构造函数内部创建一个空对象;
- 让
this指向这个空对象; - 执行构造函数里面的代码,给这个空对象添加相应的属性和方法(引入原型对象后构造函数里尽量没有方法)
- 返回
this
在构造函数实例化后,其内部的this会被清空,很难判断this的具体指向,所以我们在实例化前,声明一个全局变量that来接收this,代码如下:
let that // 全局变量
function Star(name, age) {
this.name = name
// that = this
// console.log(this)
}
Star.prototype.sing = function () {
console.log('唱歌')
that = this
}
const ldh = new Star('刘德华')
console.log(that === ldh) // true
执行以上代码,我们可以知道:
- 构造函数里的
this,指向的是实例(对象); - 原型对象方法里的
this,指向的是实例(对象);
即构造函数里的this和原型对象方法里的this,都指向我们new出来的实例对象。
1.6 constructor 属性
function Star(name, age) {
this.name = name
this.age = age
}
const ldh = new Star('刘德华', 20)
console.dir(Star) // 构造函数
console.log(Star.prototype) // 原型
console.log(Star.prototype.constructor === Star) // true
console.log(ldh.__proto__ === Star.prototype) // true ===> 都指向原型
执行以上代码,在控制台中我们可以看到,其原型对象有一个constructor属性,其指向了这个构造函数。
该属性表示该原型和哪个构造函数相关联,是哪个构造函数的原型。
应用
因为我们前面为了避免构造函数在实例化的过程中浪费内存,将方法添加到原型对象中,但是如果需要添加的方法比较多,一个个添加十分麻烦。
所以可以将需要添加的方法结合成一个对象,再加到原型中,但是!如果我们直接给原型对象赋值一个对象的话,会整个替换了原型对象,这个时候,原型中的constructor属性消失,我们也就不清楚这个原型是哪一个构造函数的原型。所以,我们可以手动添加一个constructor属性,指回原来的构造函数。
function Star(name, age) {
this.name = name
}
Star.prototype.sing = function () {
console.log('唱歌')
}
Star.prototype.dance = function () {
console.log('跳舞')
}
console.log(Star.prototype)
Star.prototype = {
constructur: Star, // 指回原来的构造函数
dance: function () {
console.log('跳舞')
},
sing: function () {
console.log('唱歌')
},
}
console.log(Star.prototype)
原型的constructor指向相应的构造函数,可以方便批量给构造函数添加相应的方法,只要在添加方法的对象里将constructor指回原来的构造函数即可。
1.7 通过对象的__proto__属性指向原型
前面我们了解到可以通过函数的prototype属性可以指向原型对象,但是我们可以从原型规则第四条中可以得知,可以通过实例对象的__proto__属性(隐式原型)指向原型对象。
function Star(name, age) {
this.name = name
this.age = age
}
Star.prototype.sing = function () {
console.log('唱歌')
}
const ldh = new Star('刘德华', 18)
ldh.__proto__ === Star.prototype
每一个对象都默认有一个 __proto__ 属性,指向它的构造函数的prototype显式原型;
__proto__ 相当于是一个桥梁,链接,实例通过它访问原型对象;
所以,方法属性的查找规则可以为:
首先看ldh 对象身上本身有没有sing这个方法,如果有,就执行这个对象上的sing方法;
如果没有,就会通过__proto__ 去实例的原型上查找;
举例:
function Animal() {
this.color = 'orange'
}
const cat = new Animal()
console.log(cat.__proto__ === Animal.prototype) // true
console.log(cat.__proto__) // 得到原型
console.log(Animal.prototype) // 得到原型
console.log(Animal.prototype.constructor === Animal)
console.log(cat.__proto__.constructor === Animal)
// 实例.__proto__ === 构造函数.prototype
2.原型链
2.1构造函数,原型和实例之间的关系
function Person(name) {
this.name = name
}
const person = new Person()
三者关系如图所示:
四条线:
- 构造函数 有一个
prototype属性,指向原型对象; - 原型对象默认有一个
constructor属性,指向构造函数(构造函数和原型 是相互指向); - 构造函数通过
new创建一个实例; - 实例通过
__proto__访问到原型;
实例与构造函数原型之间有直接的联系(__proto__),但实例与构造函数之间没有;
2.2 原型链
function Person() {
this.name = name
}
const person = new Person()
1.Person.prototype也是一个对象,所有,他也有__proto__属性 (原型规则2)
那么Person.prototype的构造函数是谁呢?
Object() 所有的对象,都可以理解为是Object()这个构造函数创建的
根据原型规则4可以知道:
Person.prototype 的隐式原型 指向 它的构造函数的显式原型
console.log(Person.prototype.__proto__ === Object.prototype) // true
2.Object.prototype 是什么? 是原型,是谁的原型? Object()构造函数的原型
2.1 Object.prototype 原型,默认有一个constructor属性,指向构造函数Object
2.2 Object构造函数通过 prototype 访问到这个原型 Object.prototype
console.log(Person.prototype.__proto__ === Object.prototype) // true
以下两个,都指向Object() 这个构造函数的原型
console.log(Person.prototype.__proto__)
console.log(Object.prototype)
3.Object是构造函数吗? 是 所以Object也有prototype属性 (原型规则3)
Object.prototype 他是Object的原型对象,所以Object.prototype有__proto__属性 (原型规则2)
但是 Object.prototype.__proto__ 指向 null空!
Object.prototype.__proto__ === null
正常的原型链都会终止于 Object 的原型对象
Object 原型的 原型 是 null
我们说的原型, 可以说是构造函数的原型, 也可以说是实例的原型
(Object.prototype) ==> Object的原型
Object.prototype.__proto__ 能访问到它Object.prototype的原型
obj.__proto__ === Object.prototype obj的原型
2.3 原型链小结
- 每个对象能够通过
__proto__属性访问到它的原型对象,原型对象也有他的原型对象 - 当访问一个对象的属性或方法时,先在自身中寻找
- 如果没有,就会沿着
__proto__这条链向上查找,一直找到最顶层的Object.prototype为止 object.prototype.__proto__ === null
关系如图所示:
主要是通过隐形原型__proto__进行查找!
2.4 数组与函数的原型链
2.4.1 数组的原型链
const arr = new Array(1, 2, 3, 4)
arr --> Array.prototype --> Object.prototype --> null
2.4.2 函数的原型链
fn 相当于是 Function() 创建的
const fn = new Function()
fn --> Function.prototype --> Object.prototype --> null
\