什么是构造函数
constructor 是一种用于创建和初始化 class 对象实例的特殊方法,构造函数其实就是一个普通的函数,当使用 new 关键字调用时他就是一个构造函数, 用于生成一个对象的模板, 描述了对象内部的基本结构。
原型的概念
原型就是每个对象都拥有一个属性(他是一个对象,它包含了原型链上共享的属性和方法),被称为原型,原型指向的对象称为“原型对象 或 对象原型”,原型可以分为两种类型:隐式原型 和 显示原型。
原型对象的理解:
是指没有 constructor 属性的函数,也可以理解成就是一个对象,指的是非函数对象(如通过字面量或new关键字创建的对象)。
const a = {
b: 1
}
const obj = Object.create(a) // 这里的这个 a 被称为原型对象
对象原型的理解:
指的是函数对象的原型,即函数的.prototype属性, 在 JavaScript 中函数也是一种对象
function MyObject () {}
const obj = new MyObject()
obj.__proto__ = MyObject.prototype
MyObject.prototype = {
constructor: MyObject
....
}
// 指的是这个对象
{
constructor: MyObject
....
}
原型的理解:
原型:prototype
又称显示原型
1、原型是一个普通对象
2、只有构造函数才具备该属性
3、公有属性可操作
隐式原型:__proto__
1、只有对象(普通对象、函数对象)具备
2、私有的对象属性,不可操作
-
隐式原型:
每一个实例对象都有一个隐式原型[[prototype]], 我们可以借助浏览器提供功能__proto__(非官方提供)去访问他, 也可以使用官方提供的方法 Object.getPrototypeOf() 去访问
-
显示原型:
javascript 中每一个函数都有个 prototype 属性,它是函数对象的一个特殊属性,用于定义构造函数创建的实例对象的公共祖先。这意味着,通过构造函数产生的对象可以继承到该原型的属性和方法。
特点:
-
构造函数通过原型分配的函数是所有对象所共享的
-
javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象,所以我们也称为原型对象这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约了内存
我们可以把那些不变的方法,直接定义在prototype对象上,这样所有的对象的实例就可以共享这些方法
-
构造函数和原型对象中方法的this都指向实例化的对象
原型链的概念
js 并不是传统意义上的面向对象语言,但是他想引用面向对象这种思维去写,这导致 js 有一个原型链机制,原型链机制本身是 js 对面向对象特征的实现。
原型链的本质: 就是实例对象的隐式原型与构造函数的显示原型的连接
连接方式: 通过 new 关键字
new 关键字做了什么
- 绑定一个this指向空对象
- 会给对象添加一个 [[prototype]] 的隐式属性 ---> 指向函数对象的 prototype
- 执行函数上下文 this ---> 空对象
- 把this的引用返回(默认是返回一个undefined) 返回基本类型会把this的引用返回
// 简单实现, 大概意思
function myNew (fn, ...args) {
const empty = Object.create(null)
this = empty
this.__proto__ = fn.prototype // 实现对象的隐式原型和函数的显示原型的连接
fn.apply(this, args)
return this // 注意如果返回的是基本数据类型, 会返回 this, 如果是引用类型, 那他会返回当前指向的引用对象
}
原型链的查找机制
分为两种
- [[GET]] 右查询(RHS), 会沿着原型链往上查找, 如果找到原型链的终点还没有找到, 会返回一个 undefinde
- [[PUT]] 左查询(LHS),执行左查询的时候, 会执行 put
[[GET]]: RHS 查找对象中有没有属性
这个查询比较简单, 就是沿着原型链往上查找, 先看对象身上是否存在(包含自身和构造函数中的), 如果没有, 在对象的原型身上查找, 如果没有在构造函数的原型身上查找, 直到终点 null
[[PUT]]: LHS 判断对象中有没有属性
-
对象中有该属性
如果对象身上有这个属性, 那就找到了, 修改内存中的数据
-
对象身上没有该属性
沿着作用域链往上查找, 直到原型链终点
分两种情况:
-
没找到
没有找到就给对象自身创建一个
-
找到了
-
基本数据类型
新增, 在对象身上创建这个属性, 不会修改原型链上的数据
-
引用数据类型
如果是对引用数据的引用, 那么新增, 在对象自身创建这个属性, 不会修改原型链上的数据 如果是对引用数据的访问, 那么覆盖, 会修改原型链上对应引用数据中对应的值
-
// [[PUT]] 操作的辅助理解
function MyObj () {
}
MyObj.prototype.a = 1
MyObj.prototype.b = {
val: 12
}
const obj = new MyObj()
obj.a = 11212 // 新增
obj.c = 10 // 新增
console.log('🚀 ~ This is a result of console.log ~ ✨: ', obj); // {a: 11212, c: 10}
obj.b.val = 11 // 这一步执行覆盖, 修改原型链上的数据
console.log('🚀 ~ This is a result of console.log ~ ✨: ', obj); // {a: 11212, c: 10}
画出原型链经典图
原型和原型链的意义何在
-
原型: 是实现对象继承以及共享属性和方法的关键机制, 原型是一个对象, 里面包含了原型链上所有共享的属性和方法, 通过原型指向的对象我们能够实现方法和属性的共享
-
原型链:实现给类添加属性(方法, 值), 能够实现 javascrip 的继承, 通过原型链, 对象可以继承原型对象身上的所有共享的方法和属性, 实现了继承