Profile
GitHub

对于js原型链的一次回顾

__proto__prototype是什么?

  • __proto__指向的地方叫 隐式原型,也就是它构造对象的显式原型(prototype)
  • 原型链就是 __proto__的路径
  • 所有原型链的终点都为Object.prototype.__proto__, 即为null
  • 函数是Function构造函数的实例, 对象是Object构造函数的实例:
    • Object.__proto__ === Function.prototype
    • Function.__proto__ === Function.prototype
  • 对象、函数实例的prototype本质都是对象new Object()创建:
    • Function.prototype.__proto__Object.prototype.__proto__ 都为Object.prototype

其实可以看到,只要按照第一点的原则,后面的规则都解释的通

alt text

alt text

练习

var F = function() {};

Object.prototype.a = function() {
  console.log('a');
};

Function.prototype.b = function() {
  console.log('b');
}

var f = new F();

f.a();
f.b();

F.a();
F.b();

根据前面的知识,我们先梳理一下原型链(f.__proto__)

f --__proto__-> F.Prototype --__proto__-> Object.prototype

所以,这个整个链路都没有经过Function.prototype 所以,f.b()将会报错

f.a(); // a
f.b(); // f.b is not a function

F.a(); // a
F.b(); // b

那么Function.prototype在哪条路径上呢?

F.__proto__ -> Function.prototype

例子

这个列子解释了引入了继承后, 完整原型链的关系 alt text

解释一下

  1. f是Fo的实例对象,它会有一个__proto__属性指向Fo的原型对象prototype
  2. Fo继承自Foo,其prototype是Foo的实例,会有一个__proto__属性指向Foo的的原型prototype
  3. Foo继承自顶层对象Object,其prototype是Object的实例,会有一个__proto__属性指向Object.prototype
  4. Object.prototype已经是最顶层的对象,其__proto__属性指向null
const Foo = function (name, sex) {
    this.name = name
    this.sex = sex
}
Foo.prototype.showName = function () {
    console.log(this.name)
}
  
const Fo = function (name) {
    Foo.call(this, name)
}

Fo.prototype = Object.create(Foo.prototype)
Fo.prototype.constructor = Fo
  
const f = new Fo('Fo')
  
f.showName() // Fo

f.__proto__ === Fo.prototype // true
f.__proto__.__proto__ === Foo.prototype // true

f.__proto__.constructor === Fo // true

foo.prototype.__proto__ === Object.prototype

相关知识

instanceof 原理

A instanceof B

这个关键字的主要作用是:判断B的prototype是否在A的原型链上

手写实现

const myInstanceOf = (obj, target) => {

    if (!obj) return false

    // const proto = obj.__proto__  非Es6标准属性,建议使用下面的getPrototypeOf
    const proto = Object.getPrototypeOf(obj)
    
    if (proto === target.prototype) {
        return true
    } 

    return myInstanceOf(proto, target)
}

const a = ['test']

console.log("final res", myInstanceOf(a, Array))

继承

js的继承主要考察的是对于原型链的切换 下面给出一种相对比较完善的寄生组合式继承方式

function Person(name) {
  this.name = name
  this.getName = function() {
    console.log('name:', this.name)
  }
}

Person.prototype.listen = function(){
  console.log('listen')
}

function Student(name, grade) {
  Person.call(this, name)
  this.grade = grade
}

Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student

Student.prototype.say = function() {
  console.log("my name is: ", this.name)
}

const s = new Student('leo', '1')

s.getName()
s.listen()
s.say()

console.log(s instanceof Person)

实现一个new

首先,new做了哪些事? 篇幅有限,不再举例子分析,直接给出结论:

  • 得到一个新的Object的实例
  • 实例的方法this指向这个实例本身
  • 每个实例的__proto__指向构造函数的原型对象
  • 当构造函数return 一个Object/Function/Array/Date/RegExp/Error的实例,new操作符得到的就是return的结果
const myNew = function (constructor){
    if (typeof constructor !== "function") {
        throw "need a constructor function"
    }

    const newObj = Object.create(constructor.prototype)

    const otherParams = Array.from(arguments).slice(1)

    const retObj = constructor.call(newObj, ...otherParams)

    if (typeof retObj === 'function' || typeof retObj === 'object') {
        return retObj
    }

    return newObj
}

const A = function(name) {
    this.name = name

    return () => {
        console.log('my name:', this.name)
    }
}

const a = myNew(A, "name a")
a() 

function B(name) {
    this.name = name
    this.getName = () => {
        console.log(this.name)
    }
}

const b = myNew(B, "name b")

b.__proto___ === B.prototype // true
b.getName() // "name b"