原型链

总结:用于在现有对象的基础上构建新类型的对象。类似于类的语言中的继承

对象实例上的原型可通过 Object.getPrototypeOf(object) 或 proto 属性获得,而构造函数上的原型可以通过 Object.prototype 获得。

虽然 Object.prototype 是原型链的最顶层,但它本身也有一个原型对象 => null 。所以原型链的顶端实际上是 null 。总的来说,JavaScript 中的原型链顶端是 null,而原型链的最顶层对象是 Object.prototype

构造函数创建对象

function Fruit() {}

var fruit = new Fruit();
fruit.name = 'Apple'
console.log(fruit.name) // Apple

Fruit 就是构造函数,fruit 是实例对象。对象通过 new 创建。

prototype

先看一段代码

function Fruit() {}
Fruit.prototype.name = 'Apple';
const fruit1 = new Fruit();
const fruit2 = new Fruit();
console.log(fruit1.name) // Apple
console.log(fruit2.name) // Apple

每个函数都有一个 prototype 属性,函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也就是这个例子中的 fruit1 的原型。

简单理解原型:每一个 JavaScript 对象 ( null 除外) 在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。

我们已经知道,函数通过 prototype 和原型关联,那么实例对象是如何与原型关联的呢。

__proto__

function Fruit(){}
var fruit = new Fruit();
console.log(fruit.__proto__ === Fruit.prototype); // true

从例子中可以发现 对象 是通过 __proto__ 与原型关联。虽然大部分浏览器都支持通过 __proto__ 访问原型,但是它并不存在于 Fruit.prototype 中。本质上是 Object.prototype 的 getter/setter。

constructor

每个原型都有一个 constructor 属性指向关联的构造函数。

function Fruit(){}
console.log(Fruit === Fruit.prototype.constructor); // true

实例与原型

读取实例属性的过程,实际是原型查找的过程。通过查找与对象关联的原型中的属性(如果查不到会继续往查找原型的原型,直到查到顶层 null )。

function Fruit(){}
Fruit.prototype.name = 'Apple';
const fruit = new Fruit();
console.log(fruit.name); //1 Apple

fruit.name = 'Banana';
console.log(fruit.name); //2 Banana

delete fruit.name;
console.log(fruit.name); //3 Apple

在 1 中给实例原型添加了 name = Apple 的属性,所以在实例化之后,fruit 中找不到 name 属性,就会从原型中 fruit.__proto__ 也就是 Fruit.prototype 中查找。3 同理,删除对象中的 name 属性后会在原型查找。而在 2 中,由于给对象赋予了 name 属性,所以不需要查找原型即可得到 name。

原型链

其中被 __proto__ 串起来的链状结构就是原型链。而原型链的顶层为 null(Object.prototype.__proto__ == null)。

从上面的例子来看,原型链大致呈:Fruit() --> fruit --__proto__--> Fruit.prototype --__proto__--> Object.prototype --__proto__--> null

node1.png

new 是如何创建实例

简单来讲,new 表达式是配合构造函数,通过 new 一个构造函数取继承属性。

  1. 创建一个新的空对象。
    • 当使用 new 操作符时,JavaScript 首先创建一个新的空对象。这个对象将成为即将创建的实例。
  2. 将新对象的 __proto__ 属性设置为构造函数的 prototype 属性。
    • 对象的内部 [[Prototype]](即 __proto__ )属性被设置为构造函数的 prototype 属性。这一步确保了通过 new 创建的对象能够访问构造函数原型链上的属性和方法。
  3. 执行构造函数并传入新对象作为 this。
    • 将新创建的对象作为 this 上下文传递给构造函数。构造函数中的代码通常会为新对象添加属性和方法。
  4. 返回新对象。
    • 如果构造函数显式地返回了一个对象,则 new 表达式的结果将是该对象;否则,new 将返回创建的对象实例。

通过原型实现 new 操作

function Fruit(name) {
    this.name = name;
}

function createInstance(Constructor, ...args) {
    // 1&2. 创建空对象,并且 __proto__ 设置为 prototype
    const obj = Object.create(Constructor.prototype);
    // 3. 改变 this 指向
    const result = Constructor.apply(obj, args);
    // 4. 返回
    return result instanceof Object ? result: obj;
}

const fruit = createInstance(Fruit, "Apple");
console.log(fruit.name); // Apple