Skip to content

JavaScript从原型到原型链 #28

@wangjing013

Description

@wangjing013

原型

  • 构造函数、原型、实例的关系
  • 原型链
  • instanceof
  • 如何实现一个 instanceof

构造函数创建对象

在 JavaScript 中创建对象的方式通常是字面量或构造函数的方式. 下面通过构造函数来创建对象:

function Student(name, age) {
  this.name = name;
  this.age = age;
}
const studentObj1 = new Student('张三', 28);
console.log(studentObj1);

上面很简单, 通过构造函数 Student 来创建 studentObj1 对象. 接着我们讲下主要的内容:

prototype

在 JavaScript 中每个函数有一个原型对象属性 prototype. 也就是函数可以通过下面访问去访问原型对象.

function Student() {}
console.log(Student.prototype); // {}

__proto__

同样在 JavaScript 每个对象(除了null)中包含一个 __proto__ 属性, 指向的是生成它的构造函数的原型. 这句话有点绕, 我们结合上面代码来理解:

function Student(){}
const a = new Student();
console.log(a.__proto__ === Student.prototype); // true

注意:

proto 属性只是部分浏览器中存在该属性(非标准属性), 主要是方便上面去查看. proto 是其对象内部属性 [[Prototype]] 的引用, 如果要获取原型对象可以通过ES5中提供的 Object.getPrototypeOf(obj) 标准方法替代.

constructor

前面说到 prototype 指向的是函数的原型. 有没有能说明原型对象与函数关联的属性? constructor.

// 结合上面案例
function Student(){}
console.log(Student.prototype.constructor === Student); // true

通过上面我们可以知道的构造函数、原型、实例之间的关系. 如下图:

原型、实例、构造函数关系

原型链

用大白话讲原型的原型就形成了原型链. 前面讲到 JavaScript 中每个对象都有原型对象, 那么原型对象的原型是谁?

function Student(name, age) {
  this.name = name;
  this.age = age;
}
const studentObj1 = new Student('张三', 28);
console.log(studentObj1.__proto__); // studentObj1.__proto__ === Student.prototype

下面获取原型对象的原型(p):

//
console.log(Student.prototype.__proto__ === studentObj1.__proto__.__proto__)
console.log(studentObj1.__proto__.__proto__); // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}

前面提及原型对象与构造函数关系, 如果要想知道原型对象(p)是谁创建可以通过 constructor 属性:

console.log(studentObj1.__proto__.__proto__.constructor); // [Function: Object]

也就是说明原型对象(p)通过构造函数 Object 创建生成的. 按照构造函数、原型、实例之间的关系, 那么下面的关系同样成立:

console.log(studentObj1.__proto__.__proto__ === Object.prototype)
console.log(studentObj1.__proto__.__proto__.constructor == Object)

依此此类推, 那么 Object.prototype 指向的原型对象的原型对象是什么?

console.log(Object.prototype.__proto__); // null

其也是原型链的未端. 上面关系汇聚如下这个图:

原型链

对象属性的查找方式

实例对象与原型通过对象内部 [[Prototype]] 关联着, 那么属性查找时跟原型及原型链有关系? 还是基于上面的案例:

function Student(name, age) {
  this.name = name;
  this.age = age;
}
Student.prototype.getName = function(){
  return this.name;
}
const studentObj1 = new Student('张三', 28);
console.log(studentObj1.name); // '张三'
console.log(studentObj1.getName()); // '张三'

上面基于前面案例作了细微调整, 在原型对象 Student.prototype 添加一个方法 getName, 然后通过实例去访问这个方法.
从代码层面可以看到, 在 Student 的实例对象中不存在 getName 方法, 但是当用实例去访问 getName 时是可以访问到. 这说明访问实例对象某个属性时, 当实例对象上不存在时会从其原型对象上去查找.

也就是说当实例对象中包含与原型对象中属性时, 则原型对象上的属性会被遮蔽.

function Student(name, age) {
  this.name = name;
  this.age = age;
  this.getName = function(){
    return "实例上的方法";
  }
}
Student.prototype.getName = function(){
  return this.name;
}
const studentObj1 = new Student('张三', 28);
console.log(studentObj1.name); // '张三'
console.log(studentObj1.getName()); // '实例上的方法'

instanceof

前面知道构造函数与原型对象关系(prototype)、 原型对象与构造函数关系(constructor)、 实例与原型对象的关系(proto), 在 JavaScript 怎么判断实例是不是由某个构造函数的实例? instanceof

function Student(name, age) {
  this.name = name;
  this.age = age;
}
const studentObj1 = new Student('张三', 28);
console.log(studentObj1 instanceof Student); // true

再来看看下面的例子:

console.log(studentObj1 instanceof Object); // true

从输出结果来看, studentObj1 也是 Object 的实例, 为什么? 回答问题就要知道 instanceof 的背后原理:

只要能在操作符左侧(studentObj1)的原型链上, 能找到右侧构造的原型(Object.prototype), 那么则返回 true.

那回顾讲解原型链, 看看 Object.prototype 是不是存在 studentObj1的原型链上.

知道其原理, 那么可以自己实现 instanceof 操作符的功能:

  const getPrototypeOf = Object.getPrototypeOf;
  const instanofFun = function(left, right) {
    if (left === null) {
      return false;
    }
    const proto = getPrototypeOf(left);
    if (proto === right.prototype) {
      return true;
    }
    return instanofFun(proto,right)
  }

  function Student(name, age) {
    this.name = name;
    this.age = age;
  }
  const studentObj1 = new Student('张三', 28);
  console.log(instanofFun(studentObj1, Student)); // true
  console.log(instanofFun(studentObj1, Object)); // true
  console.log(instanofFun(studentObj1, Array)); // false

上面通过简单的递归, 完成相应的功能. 这里说句题外话, 编写一个递归有两个因素: 递归英子(递归条件) 、递归体(循环的内容)

总结

  • 原型对象、实例对象、构造函数之间的关系.

    • 构造函数与原型对象关系(prototype)
    • 原型对象与构造函数关系(constructor)
    • 实例与原型对象的关系(__proto__)
  • 原型链: 原型的原型构成的关系我们称为原型链.

  • 对象属性查找方式: 先从自身查找、找不到则从原型链上查找

  • 自身实例属性会遮蔽原型上的同名属性,这中方式应用很广, 比如 函数、数组都实现了 toString 方法.

  • instanof 的使用以及实现原理.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions