本文目录

[[toc]]

原型

介绍

  • 实例的 constructor 是构造函数,构造函数的 constructorFunction ,包括 FunctionFunction.__proto__ === Function.prototype
  • prototype: 指向给子类共享的自身的实例 。 只有继承 Function 才有 prototype ,指向当前函数作为构造函数时, new 出来的共享对象,如: Person.prototypenew 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

普通 ObjectFunction 实例

// 普通对象链路
let obj = {};
obj.__proto__Object.prototypenull

// 函数对象链路
function Foo() {}
Foo.__proto__Function.prototypeObject.prototypenull

Object 对象与 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 无法跨 windowiframe 或者 global 工作,从其他地方 transfer 过来的会被视为不同对象
  • isPrototypeOf 不受来源限制,跨 iframe 有效

从字面量角度:

  • instanceof 对字面量、基本类型不生效,如 123 instanceof Number // false
  • isPrototypeOf 可以对字面量、基本类型生效

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 classnew function 的区别

  • 虽然说 classfunction 的语法糖,但是 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) // 1
class 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) // undefined

JavaScript 继承的多种方式

原型链继承

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
-