Skip to content

Object.create 方法:JavaScript 原型式继承的利器

有时候,我们希望创建一个新对象并直接指定它的原型,而不需要通过构造函数和 new 关键字的中间过程。Object.create() 方法正是为此设计的。它提供了一种底层而强大的方式来控制对象的原型链,让我们能够以更精细的粒度实现对象继承和属性共享。

Object.create 的基本概念

Object.create()方法创建一个新对象,使用现有对象来提供新创建的对象的原型。

基本语法

javascript
Object.create(proto, [propertiesObject]);
  • proto:新创建对象的原型对象
  • propertiesObject:可选参数,定义新对象的属性描述符

最简单的使用

javascript
// 创建一个基础对象
const animal = {
  type: "animal",
  speak() {
    console.log(`${this.type} makes a sound`);
  },
  eat() {
    console.log(`${this.type} is eating`);
  },
};

// 基于animal创建新对象
const dog = Object.create(animal);

// 添加或覆盖属性
dog.type = "dog";
dog.bark = function () {
  console.log("Woof! Woof!");
};

// dog继承了animal的方法
dog.speak(); // dog makes a sound
dog.eat(); // dog is eating

// dog也有自己的方法
dog.bark(); // Woof! Woof!

// 验证原型关系
console.log(dog.__proto__ === animal); // true
console.log(Object.getPrototypeOf(dog) === animal); // true

Object.create 与构造函数的对比

构造函数方式

javascript
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayName = function () {
  console.log(this.name);
};

const person = new Person("John", 25);

Object.create 方式

javascript
const personProto = {
  sayName() {
    console.log(this.name);
  },
};

function createPerson(name, age) {
  const person = Object.create(personProto);
  person.name = name;
  person.age = age;
  return person;
}

const person = createPerson("John", 25);

对比分析

javascript
// 构造函数方式
function ConstructorPerson(name) {
  this.name = name;
}
ConstructorPerson.prototype.greet = function () {
  return `Hello, I'm ${this.name}`;
};

// Object.create方式
const personProto = {
  greet() {
    return `Hello, I'm ${this.name}`;
  },
};

function createPerson(name) {
  return Object.assign(Object.create(personProto), { name });
}

// 测试两种方式
const constructorPerson = new ConstructorPerson("John");
const createPersonInstance = createPerson("Sarah");

console.log(constructorPerson.greet()); // Hello, I'm John
console.log(createPersonInstance.greet()); // Hello, I'm Sarah

// 原型关系
console.log(constructorPerson.__proto__ === ConstructorPerson.prototype); // true
console.log(createPersonInstance.__proto__ === personProto); // true

使用 Object.create 实现继承

基本继承模式

javascript
// 父类原型
const vehicleProto = {
  init(type, wheels) {
    this.type = type;
    this.wheels = wheels;
    this.speed = 0;
    return this;
  },

  start() {
    console.log(`${this.type} is starting`);
    this.speed = 10;
  },

  stop() {
    console.log(`${this.type} is stopping`);
    this.speed = 0;
  },

  getInfo() {
    return `${this.type} with ${this.wheels} wheels, current speed: ${this.speed} km/h`;
  },
};

// 子类原型
const carProto = Object.create(vehicleProto);
carProto.honk = function () {
  console.log("Beep beep!");
};

carProto.getInfo = function () {
  // 调用父类方法
  const baseInfo = Object.getPrototypeOf(this).getInfo.call(this);
  return `${baseInfo} (car)`;
};

// 创建工厂函数
function createCar(brand, model) {
  const car = Object.create(carProto);
  car.init("Car", 4);
  car.brand = brand;
  car.model = model;
  return car;
}

// 使用
const toyota = createCar("Toyota", "Camry");
toyota.start();
toyota.honk();
console.log(toyota.getInfo());

toyota.stop();
console.log(toyota.getInfo());

多重继承混入

javascript
// 定义多个功能模块
const canFly = {
  fly() {
    console.log(`${this.name} is flying`);
  },
  land() {
    console.log(`${this.name} is landing`);
  },
};

const canSwim = {
  swim() {
    console.log(`${this.name} is swimming`);
  },
  dive() {
    console.log(`${this.name} is diving`);
  },
};

const canWalk = {
  walk() {
    console.log(`${this.name} is walking`);
  },
  run() {
    console.log(`${this.name} is running`);
  },
};

// 创建复合原型
const amphibianProto = Object.assign(Object.create(canWalk), canSwim);

const flyingCarProto = Object.assign(Object.create(canWalk), canFly);

const superProto = Object.assign(Object.create(amphibianProto), canFly);

// 创建具体对象
function createDuck(name) {
  const duck = Object.create(superProto);
  duck.name = name;
  return duck;
}

function createAmphibiousCar(name) {
  const car = Object.create(amphibianProto);
  car.name = name;
  car.drive = function () {
    console.log(`${this.name} is driving on water`);
  };
  return car;
}

// 使用
const duck = createDuck("Donald");
duck.walk(); // Donald is walking
duck.swim(); // Donald is swimming
duck.fly(); // Donald is flying

const amphiCar = createAmphibiousCar("AmphiCar");
amphiCar.walk(); // AmphiCar is walking
amphiCar.swim(); // AmphiCar is swimming
amphiCar.drive(); // AmphiCar is driving on water

属性描述符的使用

Object.create()的第二个参数允许我们定义新对象的属性描述符。

javascript
const proto = {
  greet() {
    console.log(`Hello from ${this.name}`);
  },
};

const obj = Object.create(proto, {
  name: {
    value: "John",
    writable: true,
    enumerable: true,
    configurable: true,
  },

  age: {
    value: 25,
    writable: false, // 不可写
    enumerable: true,
    configurable: false, // 不可配置
  },

  id: {
    value: Math.random().toString(36).substr(2, 9),
    writable: false,
    enumerable: false, // 不可枚举
    configurable: false,
  },

  // 访问器属性
  info: {
    enumerable: true,
    get() {
      return `${this.name} is ${this.age} years old`;
    },
    set(value) {
      // 解析并设置name和age
      const match = value.match(/^(.*) is (\d+) years old$/);
      if (match) {
        this.name = match[1];
        this.age = parseInt(match[2]);
      }
    },
  },
});

console.log(obj.name); // John
console.log(obj.age); // 25
console.log(obj.info); // John is 25 years old

obj.info = "Sarah is 30 years old";
console.log(obj.name); // Sarah
console.log(obj.age); // 30 (如果writable为false,这里会失败)

// 验证属性特性
console.log(Object.keys(obj)); // ['name', 'age', 'info'] (id不可枚举)
console.log(obj.id); // 某个随机字符串

实际应用场景

1. 创建纯净对象

javascript
// 创建一个没有原型的对象
const cleanObject = Object.create(null);

cleanObject.name = "John";
cleanObject.age = 25;

// 这个对象没有任何继承的属性
console.log(cleanObject.toString); // undefined
console.log(cleanObject.hasOwnProperty); // undefined

// 适合用作字典或映射
const dictionary = Object.create(null);
dictionary.apple = "a fruit";
dictionary.car = "a vehicle";

// 可以安全地使用for...in循环
for (const key in dictionary) {
  console.log(key, dictionary[key]);
}

2. 实现私有属性

javascript
function createCounter() {
  // 私有变量存储在闭包中
  let count = 0;

  // 创建原型对象
  const counterProto = {
    increment() {
      count++;
      return count;
    },

    decrement() {
      count--;
      return count;
    },

    reset() {
      count = 0;
      return count;
    },

    getCount() {
      return count;
    },
  };

  // 创建计数器实例
  return Object.create(counterProto);
}

const counter1 = createCounter();
const counter2 = createCounter();

console.log(counter1.increment()); // 1
console.log(counter1.increment()); // 2
console.log(counter1.getCount()); // 2

console.log(counter2.increment()); // 1 (独立计数)
console.log(counter2.getCount()); // 1

// count变量完全私有,无法从外部访问
console.log(counter1.count); // undefined

3. 创建不可变对象

javascript
const configProto = {
  get(key) {
    return this._config[key];
  },

  has(key) {
    return key in this._config;
  },

  getAll() {
    return { ...this._config };
  },
};

function createConfig(config) {
  const instance = Object.create(configProto);

  // 创建私有配置对象
  const privateConfig = Object.freeze(Object.assign({}, config));

  // 使用getter提供只读访问
  Object.defineProperty(instance, "_config", {
    value: privateConfig,
    enumerable: false,
    configurable: false,
    writable: false,
  });

  return Object.freeze(instance);
}

const appConfig = createConfig({
  apiUrl: "https://api.example.com",
  timeout: 5000,
  retries: 3,
});

console.log(appConfig.get("apiUrl")); // https://api.example.com
console.log(appConfig.has("timeout")); // true
console.log(appConfig.getAll()); // { apiUrl: 'https://api.example.com', timeout: 5000, retries: 3 }

// 尝试修改
try {
  appConfig.apiUrl = "new-url"; // 静默失败(对象被冻结)
} catch (e) {
  console.log(e.message);
}

// 无法获取私有配置
console.log(appConfig._config); // undefined (enumerable: false)

4. 实现装饰器模式

javascript
function withLogging(obj) {
  const proto = Object.getPrototypeOf(obj);
  const loggedProto = Object.create(proto);

  // 为每个方法添加日志
  for (const key of Object.getOwnPropertyNames(proto)) {
    const descriptor = Object.getOwnPropertyDescriptor(proto, key);
    if (descriptor.value && typeof descriptor.value === "function") {
      const originalMethod = descriptor.value;
      descriptor.value = function (...args) {
        console.log(`Calling ${key} with args:`, args);
        const result = originalMethod.apply(this, args);
        console.log(`${key} returned:`, result);
        return result;
      };
      Object.defineProperty(loggedProto, key, descriptor);
    }
  }

  return Object.create(loggedProto, Object.getOwnPropertyDescriptors(obj));
}

// 使用装饰器
const calculator = {
  add(a, b) {
    return a + b;
  },
  multiply(a, b) {
    return a * b;
  },
};

const loggedCalculator = withLogging(calculator);
loggedCalculator.add(2, 3); // 会输出日志
loggedCalculator.multiply(4, 5); // 会输出日志

性能考虑

Object.create vs 构造函数性能

javascript
// 性能测试
function createUsingConstructor() {
  function Person(name, age) {
    this.name = name;
    this.age = age;
  }

  Person.prototype.greet = function () {
    return this.name;
  };

  return new Person("John", 25);
}

function createUsingObjectCreate() {
  const proto = {
    greet() {
      return this.name;
    },
  };

  const obj = Object.create(proto);
  obj.name = "John";
  obj.age = 25;
  return obj;
}

// 性能测试
console.time("Constructor");
for (let i = 0; i < 100000; i++) {
  createUsingConstructor();
}
console.timeEnd("Constructor");

console.time("Object.create");
for (let i = 0; i < 100000; i++) {
  createUsingObjectCreate();
}
console.timeEnd("Object.create");

内存优化技巧

javascript
// 缓存原型对象
const sharedProto = {
  commonMethod() {
    // 共享的方法逻辑
  },
  anotherMethod() {
    // 另一个共享方法
  },
};

function createOptimizedInstance(data) {
  return Object.assign(Object.create(sharedProto), data);
}

// 所有实例共享同一个原型对象
const instance1 = createOptimizedInstance({ id: 1, name: "John" });
const instance2 = createOptimizedInstance({ id: 2, name: "Sarah" });

console.log(instance1.__proto__ === instance2.__proto__); // true

常见陷阱和解决方案

1. 原型污染

javascript
// ❌ 危险:修改共享原型
const baseProto = {
  data: [],
};

const obj1 = Object.create(baseProto);
const obj2 = Object.create(baseProto);

obj1.data.push("shared");
console.log(obj2.data); // ['shared'] - 意外共享!

// ✅ 解决方案1:在实例上初始化
function createSafeInstance() {
  const instance = Object.create(baseProto);
  instance.data = []; // 每个实例有自己的数组
  return instance;
}

// ✅ 解决方案2:使用工厂函数
const factoryProto = {
  init() {
    this.data = [];
    return this;
  },
  addItem(item) {
    this.data.push(item);
  },
};

function createInstance() {
  return Object.create(factoryProto).init();
}

2. 循环引用

javascript
const obj1 = Object.create(Object.prototype);
const obj2 = Object.create(obj1);

// ❌ 创建循环引用
// obj1.__proto__ = obj2; // 这会导致无限循环

// ✅ 检查循环引用
function hasCycle(obj) {
  const seen = new WeakSet();

  while (obj && typeof obj === "object") {
    if (seen.has(obj)) {
      return true;
    }
    seen.add(obj);
    obj = Object.getPrototypeOf(obj);
  }
  return false;
}

3. 忘记处理 null 原型

javascript
// ❌ 忘记处理null原型对象
const obj = Object.create(null);
// obj.toString(); // TypeError: obj.toString is not a function

// ✅ 安全的方法调用
function safeToString(obj) {
  if (obj === null) return "null";
  const proto = Object.getPrototypeOf(obj);
  if (proto === null) {
    return "[Object null prototype]";
  }
  return Object.prototype.toString.call(obj);
}

console.log(safeToString(Object.create(null))); // [Object null prototype]

总结

Object.create()方法是 JavaScript 中实现原型式继承的强大工具,它提供了比构造函数更灵活的对象创建方式:

  • 直接控制原型链:可以精确指定新对象的原型
  • 支持纯净对象:可以创建没有原型的对象
  • 灵活的属性描述:第二个参数支持详细的属性配置
  • 适合继承和混入:轻松实现多重继承和功能组合
  • 函数式风格:与工厂函数配合使用,代码更清晰

虽然现代 JavaScript 提供了 Class 语法,但Object.create()仍然是理解 JavaScript 原型机制的重要工具,在某些场景下提供了更灵活、更强大的对象创建能力。掌握Object.create()有助于深入理解 JavaScript 的原型继承体系,编写出更优雅、更高效的代码。