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); // trueObject.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); // undefined3. 创建不可变对象
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 的原型继承体系,编写出更优雅、更高效的代码。