从 Object.create 到 class:JS 原型的进化论

168 阅读2分钟

原型

JS 是一门基于原型的语言,其他语言通过类描述和实例化对象,JS 通过构造函数描述和实例化对象,通过原型实现对象继承。JS 中原型存在的意义就是让实例对象可以访问到公共方法

JS 中的原型分两种,一种是挂在对象身上的隐式原型 __proto__,另一种是挂在构造函数身上的显示原型 prototype image-20211027143330933

  1. 每个对象都有一个 __proto__ 属性,指向自己的原型对象
  2. 每个构造函数都有一个 prototype 属性,是一个存放了共享的属性和方法的对象
  3. 原型对象里面的 constructor 指向构造函数本身

对象的隐式原型等于它构造函数的显式原型

当对象上找不到某个属性时,他就会往原型上寻找,原型上没有就会向原型的原型上寻找,这也就是原型链的基本概念

原型机制的发展

Object.create()

早期对象的继承是通过 Object. create ()对象来继承原始对象的属性和方法,通过不停的克隆之前已有的对象形成了一条原型链 image-20210810130602385

// 基础对象
const personProto = {
  skills: ['JavaScript'], // 引用类型属性
  greet() {
    console.log(`Hello, I'm ${this.name}`)
  }
};

// 创建实例
const person1 = Object.create(personProto);
person1.name = 'Alice';  // 手动添加属性
person1.skills.push('React');  // 修改共享属性

const person2 = Object.create(personProto);
person2.name = 'Bob';

// 所有实例的skills都被修改
console.log(person2.skills); // ['JavaScript', 'React'] 

当你使用 Object.create(someObject) 创建一个新对象时,这个新对象确实继承了 someObject 的属性和方法。但是,它共享了原型对象上的引用类型属性(如数组、对象)。如果新对象修改了这个共享的引用类型属性,所有继承自同一个原型的对象会受到影响。同时,对新对象添加实例特有属性需要在创建后手动添加,比较繁琐且不利于封装。

原型+构造函数

随着 Javascript 的发展,演变为通过原型+构造函数来模拟类批量生成对象

image-20211027143330933 **构造函数:** 负责定义和初始化实例特有的属性(状态)

构造函数的 prototype 属性 : 负责定义所有实例共享的方法(行为)。将方法放在原型上避免了每个实例重复创建相同的函数,节省内存。

// 构造函数(初始化实例属性)
function Person(name) {
  this.name = name;
  this.skills = ['JavaScript']; // 实例特有属性
}

// 原型方法(共享)
Person.prototype.greet = function() {
  console.log(`Hello, I'm ${this.name}`);
};

// 创建实例
const person1 = new Person('Alice');
person1.skills.push('React');  // 仅修改当前实例

const person2 = new Person('Bob');

console.log(person1.skills); // ['JavaScript', 'React']
console.log(person2.skills); // ['JavaScript'] 
// 不受其他实例影响

person1.greet === person2.greet // true 
// 共享同一方法

这种分离(构造函数管状态初始化,prototype 管共享方法)提供了一种更接近“类”概念的模型,使得代码结构更清晰封装性更好,更容易理解和维护,也更容易被有面向对象背景的开发者接受

class 语法糖

到后来 ES6 引入了 class 语法糖,其底层逻辑还是运用了原型+构造函数这一核心机制

class Person {
  // 构造函数逻辑
  constructor(name) {
    this.name = name;
    this.skills = ['JavaScript'];
  }
  
  // 原型方法
  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }
}

// 继承演示
class Developer extends Person {
  constructor(name, language) {
    super(name);  // 调用父类构造函数
    this.language = language;
  }
  
  code() {
    console.log(`${this.name} codes in ${this.language}`);
  }
}

// 使用类
const dev = new Developer('Alice', 'JS');
dev.greet();  // 继承方法: "Hello, I'm Alice"
dev.code();   // 自身方法: "Alice codes in JS"

// 验证原型链
console.log(dev instanceof Person); // true
console.log(Developer.prototype.greet === Person.prototype.greet); // true

其中:

  1. constructor:替代构造函数逻辑
  2. 类方法:自动绑定到原型
  3. extends:简化继承
  4. super:访问父类

原型链

V8再访问对象的属性时,会先访问对象中的属性,如果没找到,就会去这个对象的隐式原型中查找,依次类推,直到找到 Null 为止。 这种查找的链状关系就叫原型链 image-20211027152845110