什么是原型?
一个生活化的例子:iPhone16 pro max这款手机在生产时使用的模具应是完全相同的,正是因为他们都共有同样的长宽
在JS中,这种提取共有资源的办法,就是原型这一概念的核心思想。
function iPhone16ProMax (color){
this.color = color
}
iPhone16ProMax.prototype = { // 原型对象
constructor: iPhone16ProMax,
price: 9999,
size: '6.7英寸',
system: 'iOS 17',
call: function () {
console.log('打电话')
},
takePhoto: function () {
console.log('拍照')
}
}
上述的prototype就是构造函数iPhone16ProMax的原型对象,存放着所有同款手机共有的资源和方法
原型对象 (显式原型)--prototype
-
函数天生拥有一个属性
prototype, 它的指向是一个对象 -
挂载在原型上的属性和方法,在实例对象中可以直接访问到
-
实例对象无法修改和删除原型上的属性和方法
意义:
-
将一些固定的属性和方法挂载到原型上,在创建实例的时候,就不需要重复执行这些资源
-
构造函数和原型对象中的
this都指向实例化的对象
代码示例:
let that1, that2 //方便后续判断this指向
function Star (uname) {
that1 = this
this.uname = uname
}
Star.prototype.sing = function () {
that2 = this
console.log('唱歌')
}
const ldh = new Star('刘德华')
const zxy = new Star('张学友')
// 检验两个完全不同的实例对象是否都共享同一个原型上的sing方法
console.log(ldh === zxy) // false
console.log(ldh.sing === zxy.sing) //true
// 检验构造函数和原型对象中的 this 是否都指向实例化的对象
const FWB = new Star('FWB')
FWB.sing()
console.log(FWB === that1) // true 构造函数中的 this 就是 实例对象 FWB
console.log(FWB === that2) // true 原型对象中的函数 this 还是 实例对象 FWB
对象原型 (隐式原型)--__proto__
- 每一个实例对象都拥有一个属性
__proto__, 它完全等于 构造函数的prototype
function Star () {
}
Star.prototype.sing = function () {
console.log('唱歌')
}
const ldh = new Star()
ldh.sing()
// 对象原型__proto__ 指向 该构造函数的原型对象 --是JS非标准属性,存在实例对象里
console.log(ldh.__proto__) // { sing: [Function (anonymous)] }
console.log(ldh.__proto__ === Star.prototype) // true
// 对象原型里面有constructor 指向 构造函数 Star
console.log(ldh.__proto__.constructor === Star) // true
v8在访问对象中的属性时,会先访问对象上的显示拥有的属性,如果找不到,就会去对象的隐式原型中查找,也就是__proto__中查找
追问为什么obj.__proto__ === Object.prototype?
其实是new 的原理导致的
new 的原理
- 创建一个空对象
- 让构造函数的 this 指向这个空对象
- 执行构造函数中的代码
- 将这个空对象的隐式原型
__proto__赋值成 构造函数的原型对象prototype - 返回这个对象
- 这么设计的意义是什么?
让实例对象继承构造函数原型上的属性和方法,方便我们为某一个数据类型添加属性和方法
constructor属性 --区分原型对象到底属于哪个构造函数
function Star () {
}
// prototype第一种写法:
Star.prototype.sing = function () {
console.log('唱歌')
}
Star.prototype.dance = function () {
console.log('跳舞')
}
// 使用constructor 属性写法: --constructor属性指向原型对象的构造函数
Star.prototype = {
// 重新指回创造这个原型对象的 构造函数 --可以用于区分原型对象到底属于哪个构造函数
constructor: Star,
sing: function () {
console.log('唱歌')
},
dance: function () {
console.log('跳舞')
},
}
const ldh = new Star()
console.log(Star.prototype) //原型对象
console.log(Star.prototype.constructor) //指向原型对象的构造函数
console.log(Star.prototype.constructor === Star) // true
console.log(ldh.__proto__.constructor === Star) // true
图解原型对象、对象原型、构造函数指向:
原型继承
这个时候我来考考你,现在有三个构造函数
Person、Women、Men。现在Women、Men构造函数中都未定义具体方法和属性。需求希望
Women和Men的实例有专属的方法,同时共用Person的属性请你用原型帮我写出来
分析
- 子类构造函数中不存在方法和属性
- 男人、女人的实例对象需要有独立的方法
- 男人、女人的实例对象共同拥有人类的可复用属性
实现思路
- 实例对象可以共用/继承构造函数的原型上的属性和方法 --可实现男人、女人有独立方法
- 逆向使用原型对象,将子类构造函数的原型对象变为父类的实例 --可实现继承父类的方法和属性
- 同时将子类的
constructor重新指向子类的构造函数 --以防混淆原型对象对数的构造函数
代码示范:
// 人类 构造函数
function Person () {
this.eyes = 2
this.head = 1
}
//女人 构造函数
function Woman () {
}
// 男人 构造函数
function Man () {
}
// 通过原型来继承 Person
// 父构造函数(父类) 子构造函数(子类)
// 子类的原型 = new 父类
Woman.prototype = new Person() // {eyes: 2, head: 1}
Man.prototype = new Person() // 由于new出来的对象完全不同,所以后续对各自的原型进行操作不共享
// 指回原来的构造函数
Woman.prototype.constructor = Woman // 防止混淆原型对象归属的构造函数
Man.prototype.constructor = Man
// 给女人添加一个方法 生孩子
Woman.prototype.baby = function () {
console.log('宝贝')
}
const red = new Woman()
const FWB = new Man()
const person = new Person()
// 检验是否成功继承父类方法,添加生孩子方法后除Women外的对象与函数是否被影响
console.log(Woman.prototype) // 成功指向Person
console.log(red) // 成功继承父类属性并添加专属baby方法
console.log(FWB) // 成功继承父类属性,没被误加方法
console.log(person) //没被误加方法
运行结果
此时
Women实例对象拥有的专属方法,Men和Person没有被影响,同时也继承到了父类的属性
做出来这个其实你也已经掌握原型链的规则了🎉
原型链
v8 在访问对象中的属性时,会先访问对象上的显示拥有的属性,如果找不到,就会去对象的对象原型.__proto__也就是构造函数的原型对象prototype中查找,如果还找不到,就会去 对象原型的对象原型(原型对象的原型).prototype.__proto__ 中找,层层往上,直到找到 null (Object) 为止
v8 的这种查找的链状关系就叫原型链
// function Object() {}
console.log(Object.prototype) // Object{}
console.log(Object.prototype.__proto__) // null
function Person () {
}
const ldh = new Person()
console.log(ldh.__proto__ === Person.prototype) // true
console.log(Person.prototype.__proto__ === Object.prototype) // true
原型链示意图
instanceof
instanceof可以用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上instanceof的原理- 获取实例对象的原型链
- 获取构造函数的
prototype属性 - 判断构造函数的
prototype属性是否在实例对象的原型链上
console.log(ldh instanceof Person) // true
console.log(ldh instanceof Object) // true
console.log(ldh instanceof Array) // false --ldh不在数组的原型链上
console.log([1, 2, 3] instanceof Array) // true
console.log(Array instanceof Object) // true
没有原型的对象
Object.create(obj)创建一个新对象,让这个新对象的隐式原型等于传入的objObject.create(null)得到一个没有原型的对象
// 创建一个没有原型的对象
const obj = Object.create(null)
console.log(obj.__proto__) // undefined
// 创建一个指定原型的对象
const parent = { name: 'parent' }
const child = Object.create(parent)
console.log(child.name) // 'parent' - 来自指定的原型