本文目录
[[toc]]
原型
介绍
- 实例的
constructor是构造函数,构造函数的constructor是Function,包括Function(Function.__proto__ === Function.prototype) prototype: 指向给子类共享的自身的实例 。 只有继承Function才有prototype,指向当前函数作为构造函数时,new出来的共享对象,如:Person.prototype是new Person()实例的共享原型,所以任意类的.prototype.constructor都是其本身__proto__: 指向当前对象需要使用的共享实例,也就是父类的共享实例 。 任何对象包括(Function)都拥有__proto__,指向构造函数的prototype,如new Person()的__proto__指向Person.prototype- 几乎所有对象都继承自
Object.prototype,但是也可以通过Object.create(null)来将原型链指向null(会丢失toString等内置方法)
关系
构造函数与实例
如果对象 obj 由构造函数 A 创建,则 obj.__proto__ === A.prototype
function Foo() {}
const f = new Foo()
f.__proto__ === Foo.prototype // true普通 Object 与 Function 实例
// 普通对象链路
let obj = {};
obj.__proto__ → Object.prototype → null
// 函数对象链路
function Foo() {}
Foo.__proto__ → Function.prototype → Object.prototype → nullObject 对象与 Function 对象的闭环
以下结果都为 true
// Function 对象由 函数 构造,所以有如下等式
Function.__proto__ === Function.prototype
// Object 对象也由 函数 构造,所以有如下等式
Object.__proto__ === Function.prototype
// 根据上述等式可以推出如下等式
Function.__proto__ === Object.__proto__
// Function 函数对象的父级是 Object 对象,所以有如下等式
Function.prototype.__proto__ === Object.prototype特殊案例
Function.prototype虽然是函数,但是为了规避死循环,它并没有prototype- 箭头函数不能作为类,没有
prototype,如:(() => {}).prototype === undefined,但是有__proto__,如:(() => {}).__proto__ === Function.prototype
typeof Function.prototype // "function"
Function.prototype.prototype // undefined总结
---
title: 原型链引用关系图
---
flowchart TD
subgraph 自定义类
Person[Person函数]
PersonProtoType[Person.prototype]
PersonProto[Person.\_\_proto\_\_]
person[new Person]
end
subgraph 顶层 Object
Object[Object函数]
ObjectProtoType[Object.prototype]
ObjectProto[Object.\_\_proto\_\_]
end
subgraph 顶层 Function
Function[Function函数]
FunctionProtoType[Function.prototype]
FunctionProto[Function.\_\_proto\_\_]
end
Person --> |.prototype| PersonProtoType
person --> |.\_\_proto\_\_| PersonProtoType
person --> |.constructor| Person
PersonProtoType --> |.constructor| Person
Person --> |.\_\_proto\_\_| PersonProto
PersonProto --> |等价于| FunctionProtoType
PersonProtoType --> |.\_\_proto\_\_| ObjectProtoType
Object --> |.prototype| ObjectProtoType
ObjectProtoType --> |.constructor| Object
ObjectProto --> |等价于|FunctionProtoType
Object --> |.\_\_proto\_\_|ObjectProto
Function --> |.prototype| FunctionProtoType
Function --> |.\_\_proto\_\_| FunctionProto
FunctionProto --> |等价于| FunctionProtoType
FunctionProtoType --> |\.\_\_proto\_\_| ObjectProtoType
ObjectProtoType --> |.\_\_proto\_\_| null
classDef construct fill:#e1f5fe,stroke:#039be5;
classDef proto fill:#f0f4c3,stroke:#9e9d24;
classDef instance fill:#f8bbd0,stroke:#e91e63;
class Person,Object construct;
class PersonProtoType,ObjectProtoType proto;
class person instance;console.log(Function.prototype.__proto__ === Object.prototype) // true ✅
// Function.prototype 是对象,其 __proto__ 指向 Object.prototype
console.log(Function.__proto__ === Object.__proto__) // true ✅
// 所有构造函数(包括 Function 和 Object)的 __proto__ 都指向 Function.prototype
console.log(Function.__proto__.__proto__ === Object.prototype) // true ✅
// Function.__proto__ === Function.prototype
// Function.prototype.__proto__ === Object.prototype
console.log(Object.constructor.prototype === Object.prototype) // false ❌
// Object.constructor 实际是 Function ,其 prototype 是 Function.prototype
console.log(Function.constructor === Function) // true ✅
// 所有类的 constructor 都是 Function
console.log(Object.constructor === Object) // false ❌
// 所有类的 constructor 都是 Function
console.log(Array.__proto__ === Function.__proto__) // true ✅
// 所有内置类(如 Array )的 __proto__ 都指向 Function.prototype
console.log(Array.constructor === Function) // true ✅
// 所有类的 constructor 都是 Function
console.log(Object.__proto__ === Function) // false ❌
// Object.__proto__ 实际指向 Function.prototype(非 Function 本身)
console.log(Function.__proto__ === Function.prototype) // true ✅
// Function 的 __proto__ 指向自己的 prototype (特殊设计)
console.log(Object instanceof Object) // true ✅
// Object 的原型链最终指向 Object.prototype ,具体查找链路如下:
// Object.__proto__ === Function.prototype
// Function.prototype.__proto__ === Object.prototype
console.log(typeof Function === 'function') // true ✅
// Function 的原型链包含 Function.prototype ,具体查找链路如下:
// Function.__proto__ === Function.prototype
console.log(Map instanceof Map) // false ❌
// Map 的 prototype 不在自身原型链上(无法自证)原型链
理解原型对象与原型链
JS 中由于最初设计的时候没有面向对象相关内容,只能使用原型链模拟继承机制,本质上就是为了实现共享。
需要区分以下几个概念
- 构造函数、类: 用于创建共享实例的函数
- 实例: 通过
new创建的实例 - 共享实例: 用于复用函数、代码等内容的共享实例,通过
__proto__、prototype等指向的都是共享实例,而非构造函数、类本身,共享函数的constructor属性才会指向构造函数、类
---
title: 实例、类、共享实例关系图
---
flowchart LR
ins[实例]
ctor[构造函数、类]
sins[共享实例]
ins --> |.\_\_proto\_\_| sins
sins --> |.constructor| ctor
classDef ctor fill:#e1f5fe,stroke:#039be5;
class ctor ctor
classDef sins fill:#f0f4c3,stroke:#9e9d24;
class sins sins
classDef ins fill:#f8bbd0,stroke:#e91e63;
class ins ins手动修改 __proto__ 失效
如果一个对象的 __proto__ 属性被赋值为 null,这个时候它的原型确实已经被修改为 null 了,但是你想再通过对 __proto__ 赋值的方式设置原型时是无效的,这个时候 __proto__ 和一个普通属性没有区别,只能通过 Reflect.setPrototypeOf 或 Object.setPrototypeOf 才能修改原型。原型是对象内部的一个属性 [[prototype]],而 Reflect.setPrototypeOf 之所以能修改原型是因为它是直接修改对象的原型属性,也就是内部直接对对象的 [[prototype]] 属性赋值,而不会通过 __proto__ 的 getter。
示例代码
const obj = { name: 'xiaoming' }
obj.__proto__ = null
console.log(obj.__proto__) // => undefined
console.log(Reflect.getPrototypeOf(obj)) // => null
obj.__proto__ = null
console.log(obj.__proto__) // => null
console.log(Reflect.getPrototypeOf(obj)) // => null
obj.__proto__ = { a: 1 }
console.log(obj.__proto__) // => { a: 1 }
console.log(Reflect.getPrototypeOf(obj)) // => null参考资料: TC39 __proto__ 标准
模拟的 proto 如下:
const weakMap = new WeakMap()
Object.prototype = {
get __proto__() {
return this['[[prototype]]'] === null ? weakMap.get(this) : this['[[prototype]]']
},
set __proto__(newPrototype) {
if (!Object.isExtensible(newPrototype)) {
throw new TypeError(`${newPrototype} is not extensible`)
}
const isObject = typeof newPrototype === 'object' || typeof newPrototype === 'function'
if (newPrototype === null || isObject) {
// 如果之前通过 __proto__ 设置成 null
// 此时再通过给 __proto__ 赋值的方式修改原型都是徒劳
/// 表现就是 obj.__proto__ = { a: 1 } 就像一个普通属性 obj.xxx = { a: 1 }
if (this['[[prototype]]'] === null) {
weakMap.set(this, newPrototype)
} else {
this['[[prototype]]'] = newPrototype
}
}
},
// ... 其它属性如 toString,hasOwnProperty 等
}instanceof
实现原理
基本形式: 左值 instanceof 右值
追溯原型链,判断原型链上是否存在右值,替代方式如下:
function myInstanceof(left, right) {
// 这里先用typeof来判断基础数据类型,如果是,直接返回false
if (typeof left !== 'object' || left === null) {
return false
}
// getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
let proto = Object.getPrototypeOf(left)
while (true) {
if (proto === null) {
return false
}
if (proto === right.prototype) {
return true
}// 找到相同原型对象,返回true
proto = Object.getPrototypeof(proto)
}
}覆盖内置的 instanceof 行为
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance)
}
}
console.log([] instanceof MyArray) // true与 isPrototypeOf 的区别
isPrototypeOf 也是用来判断一个对象是否存在与另一个对象的原型链上。
// 判断 f 是否是 Foo 类的实例 ,
// 并且是否是其父类型的实例
function Aoo() {}
function Foo() {}
// JavaScript 原型继承
Foo.prototype = new Aoo()
const foo = new Foo()
console.log(Foo.prototype.isPrototypeOf(foo)) // true
console.log(foo instanceof Foo) // true
console.log(Aoo.prototype.isPrototypeOf(foo)) // true
console.log(foo instanceof Aoo) // true两者的区别是:
instanceof: 判断一个对象是否是某个构造函数的实例,即检查构造函数的prototype属性是否出现在对象的原型链上isPrototypeOf: 只能在原型对象上调用,判断一个对象(原型对象)是否存在于另一个对象的原型链中,直接验证原型关系,而非构造函数
从形式上来说:
子对象 instanceof 类类.prototype.isPrototypeOf(子对象)
从作用域角度:
instanceof无法跨window、iframe或者global工作,从其他地方transfer过来的会被视为不同对象isPrototypeOf不受来源限制,跨iframe有效
从字面量角度:
instanceof对字面量、基本类型不生效,如123 instanceof Number // falseisPrototypeOf可以对字面量、基本类型生效
与 typeof 差异
typeof无法区分不同的类对象,只能区分基本类型差异,instanceof可以明确区分不同的类实例typeof检查未定义变量时,只有没有暂时性死区,就不会报错,instanceof检查时变量必须定义,否则报错
继承
new 的实现原理
function myNew(Func, ...args) {
// 1.创建一个新对象
const obj = {}
// 2.新对象原型指向构造函数原型对象
obj.__proto__ = Func.prototype
// 3.将构建函数的this指向新对象
const result = Func.apply(obj, args)
// 4.根据返回值判断
return result instanceof Object ? result : obj
}new class 与 new function 的区别
- 虽然说
class是function的语法糖,但是function会进行变量提升,但是class不会
const a = new A() // Uncaught ReferenceError: A is not defined
class A {
b() {}
}super
在 JS 中,只有对象内部方法才能访问 super ,即使是对象内部属性也不能访问,会抛出错误
// case 1
const obj1 = {
foo() {
console.log(super.foo()) // 定义的是方法,能够正常访问 super
}
}
Object.setPrototypeOf(obj1, {
foo() {
return 'bar'
}
})
obj1.foo()
// case 2
const obj2 = {
foo: function() {
console.log(super.foo()) // 这里定义的是 foo 属性,属性值为一个普通函数,而并非方法,所以无法访问 `super` ,会抛出异常
}
}
Object.setPrototypeOf(obj2, {
foo() {
return 'bar'
}
})
obj2.foo()super 的可访问性检查是在编译时进行,所以上述代码即使 obj1.foo() 能正常输出,但是在编译的时候已经报错,所以整段代码其实不会输出内容,而是直接抛出错误
override
getter / setter
在继承的时候, getter 与 setter 要一起覆盖,只覆盖其中之一会导致无法读/写
let val = 0
class A {
set foo(_val) {
val = _val
}
get foo() {
return val
}
}
class B extends A { }
class C extends A {
get foo() {
return val
}
}
const b = new B()
console.log(b.foo) // 0
b.foo = 1
console.log(b.foo) // 1
const c = new C()
console.log(c.foo) // 1
c.foo = 2 // 没有 setter ,无法更新 全局的 val
console.log(c.foo) // 1 前面的 set 更新失败,所以还是 1
console.log(b.foo) // 1class A {
val = 1
get foo() {
return this.val
}
}
class B {
val = 2
set foo(val) {
this.val = val
}
}
const a = new A()
const b = new B()
console.log(a.foo) // 1
console.log(b.foo) // undefined, 没有 getter ,只能读到 undefined
b.foo = 3 // setter 还在,所以 b.val 会被更新
console.log(b.val) // 3
console.log(b.foo) // undefinedJavaScript 继承的多种方式
原型链继承
function Parent() {
this.name = 'Naparte'
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child() {}
// 挂载prototype上;多个实例会共用
Child.prototype = new Parent()
// Usage
const child1 = new Child()
console.log(child1.getName()) // Naparte借用构造函数(经典继承)
function Parent() {
this.names = ['Naparte', 'SXF']
}
function Child() {
// 这里调用父类构造函数
Parent.call(this)
}
// Usage
const child1 = new Child()
child1.names.push('VFrank')
console.log(child1.names) // ["Naparte", "SXF", "VFrank"]
const child2 = new Child()
console.log(child2.names) // ["Naparte", "SXF"]组合继承
function Parent(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child(name, age) {
// 第二次执行 Parent;这一步可以消除 prototype 共用带来的问题
Parent.call(this, name)
this.age = age
}
// 第一次执行 Parent
Child.prototype = new Parent()
Child.prototype.constructor = Child
// Usage
const child1 = new Child('Naparte', '18')
child1.colors.push('black')
console.log(child1.name) // Naparte
console.log(child1.age) // 18
console.log(child1.colors) // ["red", "blue", "green", "black"]
const child2 = new Child('SXF', '20')
console.log(child2.name) // SXF
console.log(child2.age) // 20
console.log(child2.colors) // ["red", "blue", "green"]原型式继承
function createObj(o) {
function F() {}
F.prototype = o
return new F()
}
// Usage
// 包含引用类型的属性值始终都会共享相应的值
const person = {
name: 'kevin',
friends: ['daisy', 'kelly'],
}
const person1 = createObj(person)
const person2 = createObj(person)
person1.name = 'person1'
console.log(person2.name) // kevin
person1.friends.push('taylor')
console.log(person2.friends) // ["daisy", "kelly", "taylor"]寄生式继承
function createObj(o) {
const clone = Object.create(o)
clone.sayName = function () {
console.log('hi')
}
return clone
}寄生组合式继承
function object(o) {
// 关键的三步
function F() {}
F.prototype = o
return new F()
}
function prototype(child, parent) {
const prototype = object(parent.prototype)
prototype.constructor = child
child.prototype = prototype
}
// 当我们使用的时候:
prototype(Child, Parent)总结
| 继承方式 | 优点 | 缺点 |
|---|---|---|
| 原型链继承 | - | 引用类型的属性被所有实例共享 |
| 借用构造函数 | 1. 避免了引用类型的属性被所有实例共享 2. 可以在 Child 中向 Parent 传参 | 方法都在构造函数中定义,每次创建实例都会创建一遍方法 |
| 组合继承 | 融合原型链继承和构造函数的优点 | - |
| 原型式继承 | - | 包含引用类型的属性值始终都会共享相应的值 |
| 寄生式继承 | - | 每次创建对象都会创建一遍方法 |
| 寄生组合式继承 | 1. 高效率体现它只调用了一次 Parent 构造函数 2. 原型链还能保持不变 3. 还能够正常使用 instanceof 和 isPrototypeOf | - |