1. 原型与原型链
1. 原型和原型链是什么
const arr = [1, 2, 3, 4, 5]
arr.forEach(item => console.log(item)) //=> 1 2 3 4 5
const reg = /abc/
console.log(reg.test("abc")) //=> true
在上面的例子中, 明明 forEach 是 Array.prototype 上的方法, 为什么 arr 可以调用呢? 而 test 也是 RegExp.prototype 上的方法, 可是 reg 也可以调用. 这些我们平时写代码的时候都会遇到的现象, 其实就是由原型和原型链引起的
1. 一个对象的原型就是其 [[Prototype]]
当我们定义一个 arr 对象的时候, 这个 arr 对象会自动生成一个 [[Prototype]] 属性, 并且指向其构造函数的 prototype 对象. 这个 [[Prototype]] 就是 arr 对象的原型
- 每个对象都会有其自己的原型: [[Prototype]]
- 每个构造函数都会有其对应的构造原型: prototype 对象, 该
prototype还有一个constructor属性指向其构造函数: 比如Object.prototype.constructor === Object
2. 原型链就是由原型 [[Prototype]] 连成的链
当尝试从
obj 中获取某属性时, js 首先会在 obj 中查找该属性, 如果没有找到的话, 就会从 obj 的原型中继续找, 再没有找到的话, 就继续从 obj 的原型的原型 中查找... 直到找到对应的属性返回其值, 或者是找到了 Object.prototype(原型链的根)对象, 在该对象中也没有发现对应的属性时, 会返回 undefined
代码描述
function find(obj, property) {
while (obj !== null) {
if (obj.hasOwnProperty(property)) {
// 如果属性就在对象本身上, 那么直接返回该属性的值
return obj[property]
} else {
// 否则就沿着原型链向上查找
obj = Object.getPrototypeOf(obj)
}
}
return undefined // 最终没有找到属性 property, 则返回 undefined
}
2. 原型规则
- 哪个构造函数创建的对象, 该对象的
[[Prototype]]就指向那个构造函数的prototype:- 函数是
Function构造的, 所以函数的[[Prototype]]指向Function.prototype - 数组是
Array构造的, 所以数组的[[Prototype]]指向Array.prototype - ......
- 函数是
- 由于
[[Prototype]]是对象, 那么[[Prototype]]之间指来指去, 最终会指向Object.prototype
2. 实例对象的创建过程
通过对一个构造函数使用 new 操作符, 我们可以获得由该构造函数所创建的实例对象
如: const arr = new Array(100) , 我们可以获得一个 Array 类的实例对象
2.1 当我们在 new 构造函数的时候, 我们在做什么
以我们自己写的一个构造函数为例
function Servant(name, gender) {
this.name = name
this.gender = gender
}
const altria = new Servant("altria", "female")
graph TD
A1(构造一个实例对象 obj, <br>并使 obj 的原型指向 Servant.prototype) --> A2(将 Servant 的 this 指向 obj) --> A3(执行 Servant 函数) --> A4(Servant 的返回值是否是引用类型值)
A4 --YES--> A5(返回该引用类型值)
A4 --NO--> A6(返回 obj)
console.log(altria) //=> Servant {name: 'altria', gender: 'female'}
console.log(Object.getPrototypeOf(altria) === Servant.prototype) //=> true
当我们将 Servant 构造函数中的返回值设置为一个引用类型值
function Servant(name, gender) {
this.name = name
this.gender = gender
return [name, gender]
}
const altria = new Servant("altria", "female")
console.log(altria) //=> ['altria', 'female']
2.2 对 new 操作符的模拟实现
const myNew = (Func, ...args) => {
const obj = Object.setPrototypeOf({}, Func.prototype)
const ret = Func.call(obj, ...args)
return (typeof ret === "object" && ret !== null || typeof ret === "function") ? ret : obj
}
比较一下自己的 myNew 函数与 JS 提供的 new 操作符使用后的结果
const altria = new Servant("altria", "female")
const myAltria = myNew(Servant, "altria", "female")
console.log(altria) //=> Servant {name: 'altria', gender: 'female'}
console.log(Object.getPrototypeOf(altria) === Servant.prototype) //=> true
console.log(myAltria) //=> Servant {name: 'altria', gender: 'female'}
console.log(Object.getPrototypeOf(myAltria) === Servant.prototype) //=> true