js中Object.create()用法


一、Object.create() 简介

Object.create() 是 JavaScript 中用于创建新对象的方法,它允许我们指定新对象的原型对象,从而实现原型继承。这个方法是 ES5 引入的,为 JavaScript 的对象创建和继承提供了更灵活的方式。

二、基本语法

Object.create(proto, [propertiesObject]);
  • proto:必需,新创建对象的原型对象
  • propertiesObject:可选,一个对象,用于定义新创建对象的属性

三、工作原理

Object.create() 的工作原理是:

  1. 创建一个新的空对象
  2. 将该对象的 [[Prototype]] 指向指定的 proto 参数
  3. 如果提供了 propertiesObject 参数,则使用 Object.defineProperties() 为新对象添加指定的属性
  4. 返回这个新创建的对象

四、内部实现原理

4.1 详细的实现机制

Object.create() 的内部实现可以大致理解为:

// 简化的 Object.create() 实现
function createObject(proto, propertiesObject) {
    if (proto !== null && typeof proto !== "object") {
        throw new TypeError("Object prototype may only be an Object or null");
    }

    // 创建一个新对象
    const obj = {};

    // 设置原型
    Object.setPrototypeOf(obj, proto);

    // 定义属性
    if (propertiesObject !== undefined) {
        Object.defineProperties(obj, propertiesObject);
    }

    return obj;
}

4.2 Polyfill 实现

为了兼容不支持 Object.create() 的旧浏览器,可以使用以下 polyfill:

if (!Object.create) {
    Object.create = function (proto, propertiesObject) {
        if (typeof proto !== "object" && proto !== null) {
            throw new TypeError("Object prototype may only be an Object or null");
        }

        function F() {}
        F.prototype = proto;

        const obj = new F();

        if (propertiesObject !== undefined) {
            Object.defineProperties(obj, propertiesObject);
        }

        return obj;
    };
}

4.3 与 __proto__ 的关系

Object.create() 设置的是对象的 [[Prototype]] 内部属性,这与对象的 __proto__ 属性指向相同:

const proto = { foo: "bar" };
const obj = Object.create(proto);

console.log(obj.__proto__ === proto); // 输出: true
console.log(Object.getPrototypeOf(obj) === proto); // 输出: true

五、基本用法

5.1 创建带有指定原型的对象

// 定义原型对象
const animal = {
    type: "unknown",
    makeSound: function () {
        console.log("Some generic sound");
    },
};

// 创建继承自动物的狗对象
const dog = Object.create(animal);
dog.type = "dog";
dog.makeSound = function () {
    console.log("Woof! Woof!");
};

dog.makeSound(); // 输出: Woof! Woof!
console.log(dog.type); // 输出: dog
console.log(Object.getPrototypeOf(dog) === animal); // 输出: true

5.2 使用第二个参数定义属性

const person = Object.create(null, {
    name: {
        value: "John",
        writable: true,
        enumerable: true,
        configurable: true,
    },
    age: {
        value: 30,
        writable: false,
        enumerable: true,
        configurable: false,
    },
});

console.log(person.name); // 输出: John
console.log(person.age); // 输出: 30

person.name = "Jane"; // 可以修改,因为 writable: true
person.age = 31; // 无法修改,因为 writable: false

console.log(person.name); // 输出: Jane
console.log(person.age); // 输出: 30

六、原型链继承

原型链继承是 Object.create() 最典型的应用场景:

// 父类
function Parent(name) {
    this.name = name;
}

Parent.prototype.sayHello = function () {
    console.log(`Hello, I'm ${this.name}`);
};

// 子类
function Child(name, age) {
    Parent.call(this, name);
    this.age = age;
}

// 使用 Object.create() 继承原型
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.sayAge = function () {
    console.log(`I'm ${this.age} years old`);
};

// 测试
const child = new Child("Tom", 10);
child.sayHello(); // 输出: Hello, I'm Tom
child.sayAge(); // 输出: I'm 10 years old

七、与其他创建对象方法的比较

7.1 对象字面量

// 对象字面量
const obj1 = {
    name: "John",
    age: 30,
};

// Object.create()
const obj2 = Object.create(Object.prototype, {
    name: {
        value: "John",
        writable: true,
        enumerable: true,
        configurable: true,
    },
    age: {
        value: 30,
        writable: true,
        enumerable: true,
        configurable: true,
    },
});

优点:对象字面量语法更简洁
缺点:无法直接指定原型(除了默认的 Object.prototype),需要后续操作才能修改原型

7.2 构造函数

// 构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

const obj1 = new Person("John", 30);

// Object.create()
const personProto = {
    sayHello: function () {
        console.log(`Hello, I'm ${this.name}`);
    },
};

const obj2 = Object.create(personProto);
obj2.name = "John";
obj2.age = 30;

优点:构造函数可以在创建对象时初始化属性
缺点:继承关系相对固定,不如 Object.create() 灵活

7.3 ES6 Class

// ES6 Class
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    sayHello() {
        console.log(`Hello, I'm ${this.name}`);
    }
}

const obj1 = new Person("John", 30);

// Object.create()
const personProto = {
    sayHello: function () {
        console.log(`Hello, I'm ${this.name}`);
    },
};

const obj2 = Object.create(personProto);
obj2.name = "John";
obj2.age = 30;

优点:Class 语法更清晰,支持继承、静态方法等特性
缺点:所有成员都是公有的,私有属性需要通过 Symbol、WeakMap 或下划线约定实现

八、常见问题和注意事项

8.1 与 new 操作符的区别

Object.create() 只创建对象并设置原型,不会调用构造函数。而 new 操作符会创建对象、设置原型,然后调用构造函数。

function Person(name) {
    this.name = name;
    console.log("Constructor called");
}

const obj1 = new Person("John"); // 输出: Constructor called
const obj2 = Object.create(Person.prototype); // 不会输出任何内容
obj2.name = "John";

8.2 引用类型属性的共享

当原型对象包含引用类型属性时,所有继承该原型的对象会共享这些属性:

const proto = {
    friends: [],
};

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

obj1.friends.push("Alice");
console.log(obj2.friends); // 输出: ['Alice'] - 注意:obj2 的 friends 也被修改了

解决方案:在构造函数中初始化引用类型属性,或者在创建对象后立即覆盖它们。

8.3 不可枚举属性

通过 Object.create() 的第二个参数定义的属性默认是不可枚举的,除非显式设置 enumerable: true

const obj = Object.create(
    {},
    {
        name: { value: "John" }, // 默认 enumerable: false
        age: { value: 30, enumerable: true },
    }
);

for (let key in obj) {
    console.log(key); // 只输出: age
}

console.log(Object.keys(obj)); // 输出: ['age']

九、应用场景

9.1 创建无原型对象

proto 参数为 null 时,创建的对象没有原型:

const emptyObject = Object.create(null);
console.log(Object.getPrototypeOf(emptyObject)); // 输出: null
console.log(emptyObject.toString); // 输出: undefined
console.log("toString" in emptyObject); // 输出: false

这种对象非常适合用作字典或映射,因为它不会继承任何原型链上的属性,避免了属性冲突。

9.2 实现原型继承

// 基础对象
const baseComponent = {
    render: function () {
        console.log("Rendering component");
    },
    update: function () {
        console.log("Updating component");
    },
};

// 派生对象
const buttonComponent = Object.create(baseComponent);
buttonComponent.render = function () {
    console.log("Rendering button");
    // 调用父方法
    baseComponent.render.call(this);
};

buttonComponent.render();
// 输出: Rendering button
// 输出: Rendering component

9.3 创建具有特定原型的对象

// 创建一个继承自 Array 的对象
const customArray = Object.create(Array.prototype);
customArray.push(1, 2, 3);
console.log(customArray.length); // 输出: 3
console.log(Array.isArray(customArray)); // 输出: true

// 添加自定义方法
customArray.sum = function () {
    return this.reduce((acc, val) => acc + val, 0);
};

console.log(customArray.sum()); // 输出: 6

9.4 实现组合继承

// 混入多个对象的属性
function mixin(target, ...sources) {
    sources.forEach(source => {
        Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
    });
    return target;
}

// 基础对象
const canEat = {
    eat: function () {
        console.log("Eating");
    },
};

const canSleep = {
    sleep: function () {
        console.log("Sleeping");
    },
};

// 创建新对象并混入属性
const animal = Object.create(Object.prototype);
mixin(animal, canEat, canSleep);

animal.eat(); // 输出: Eating
animal.sleep(); // 输出: Sleeping

十、高级应用场景

10.1 实现代理模式

const realSubject = {
    request() {
        return "Real subject response";
    },
};

const proxy = Object.create(realSubject, {
    request: {
        value() {
            console.log("Proxy: Logging request");
            return realSubject.request.call(this);
        },
        writable: true,
        enumerable: true,
        configurable: true,
    },
});

console.log(proxy.request());
// 输出: Proxy: Logging request
// 输出: Real subject response

10.2 装饰器模式

const component = {
    render() {
        return "<div>Base component</div>";
    },
};

const decoratedComponent = Object.create(component, {
    render: {
        value() {
            const baseRender = component.render.call(this);
            return `<div style="border: 1px solid red;">${baseRender}</div>`;
        },
        writable: true,
        enumerable: true,
        configurable: true,
    },
});

console.log(decoratedComponent.render());
// 输出: <div style="border: 1px solid red;"><div>Base component</div></div>

10.3 单例模式

const Singleton = (function () {
    let instance;

    const proto = {
        getInstance() {
            if (!instance) {
                instance = Object.create(proto);
            }
            return instance;
        },
        sayHello() {
            return "Hello from singleton";
        },
    };

    return proto.getInstance();
})();

const instance1 = Singleton;
const instance2 = Singleton.getInstance();

console.log(instance1 === instance2); // 输出: true
console.log(instance1.sayHello()); // 输出: Hello from singleton

10.4 工厂模式

const productProto = {
    sell() {
        return `Selling ${this.name} for $${this.price}`;
    },
};

function createProduct(name, price) {
    const product = Object.create(productProto);
    product.name = name;
    product.price = price;
    return product;
}

const product1 = createProduct("Phone", 999);
const product2 = createProduct("Laptop", 1999);

console.log(product1.sell()); // 输出: Selling Phone for $999
console.log(product2.sell()); // 输出: Selling Laptop for $1999

十一、ES6+ 特性的结合应用

11.1 与 Symbol 的结合

使用 Symbol 作为属性名,实现更安全的属性:

const _private = Symbol("private");

const proto = {
    [_private]: "secret",
    getSecret() {
        return this[_private];
    },
};

const obj = Object.create(proto);
console.log(obj.getSecret()); // 输出: secret
console.log(obj[_private]); // 输出: secret,但 Symbol 不易被意外访问

11.2 与 WeakMap 的结合

实现真正的私有属性:

const privateData = new WeakMap();

const proto = {
    init(name) {
        privateData.set(this, { name });
    },
    getName() {
        return privateData.get(this).name;
    },
};

const obj = Object.create(proto);
obj.init("John");
console.log(obj.getName()); // 输出: John
console.log(privateData.get(obj)); // 只能在模块内部访问

11.3 与 Proxy 的结合

创建代理对象,实现更高级的对象行为:

const proto = {
    name: "default",
    greet() {
        return `Hello, ${this.name}!`;
    },
};

const handler = {
    get(target, prop, receiver) {
        console.log(`Getting ${prop}`);
        return Reflect.get(target, prop, receiver);
    },
};

const obj = Object.create(new Proxy(proto, handler));
obj.name = "John";
console.log(obj.greet()); // 输出: Getting greet, Hello, John!

11.4 与 Reflect 的结合

使用 Reflect 提供更统一的对象操作:

const proto = {
    name: "John",
    age: 30,
};

const obj = Object.create(proto);

// 使用 Reflect 设置属性
Reflect.set(obj, "age", 31);
console.log(obj.age); // 输出: 31

// 使用 Reflect 获取原型
console.log(Reflect.getPrototypeOf(obj) === proto); // 输出: true

十二、性能优化技巧

12.1 原型对象的缓存

// 缓存常用的原型对象
const commonProto = {
    method1() {
        /* ... */
    },
    method2() {
        /* ... */
    },
};

// 批量创建对象时复用原型
function createObjects(count) {
    const objects = [];
    for (let i = 0; i < count; i++) {
        objects.push(Object.create(commonProto));
    }
    return objects;
}

12.2 避免原型链过长

// 避免深层继承
const level1 = { a: 1 };
const level2 = Object.create(level1);
const level3 = Object.create(level2);
const level4 = Object.create(level3);

// 推荐:扁平化继承结构
const base = { a: 1, b: 2, c: 3 };
const obj = Object.create(base);

12.3 属性访问优化

// 避免频繁访问原型链上的属性
const proto = {
    expensiveMethod() {
        /* 复杂计算 */
    },
};
const obj = Object.create(proto);

// 缓存方法引用
const cachedMethod = obj.expensiveMethod;
cachedMethod(); // 直接调用,避免原型链查找

12.4 内存泄漏防护

// 避免循环引用
const obj1 = {};
const obj2 = Object.create(obj1);

// 正确清理引用
function cleanup() {
    // 解除引用,允许垃圾回收
    obj2 = null;
    obj1 = null;
}

十三、兼容性和降级方案

13.1 浏览器兼容性表格

浏览器 支持情况
Chrome
Firefox
Safari
Edge
IE 9+
IE 8-

13.2 完整的 Polyfill

if (!Object.create) {
    Object.create = (function () {
        function F() {}
        return function (proto, propertiesObject) {
            if (proto === null || typeof proto !== "object") {
                throw new TypeError("Object prototype may only be an Object or null");
            }

            F.prototype = proto;
            const obj = new F();
            F.prototype = null;

            if (propertiesObject !== undefined) {
                if (typeof propertiesObject !== "object" || propertiesObject === null) {
                    throw new TypeError("Properties argument must be an object");
                }
                Object.defineProperties(obj, propertiesObject);
            }

            return obj;
        };
    })();
}

13.3 特征检测

function supportsObjectCreate() {
    return typeof Object.create === "function";
}

if (supportsObjectCreate()) {
    // 使用 Object.create()
    const obj = Object.create(proto);
} else {
    // 使用替代方案
    function F() {}
    F.prototype = proto;
    const obj = new F();
}

十四、性能考量

14.1 创建对象的性能

Object.create() 创建对象的性能通常比构造函数和对象字面量稍慢,因为它需要额外的步骤来设置原型。但在大多数情况下,这种性能差异可以忽略不计。

14.2 内存使用

使用 Object.create() 可以有效地共享原型对象,减少内存使用,特别是当创建大量具有相同原型的对象时。

十五、最佳实践

  1. 使用 Object.create() 实现原型继承:它是实现原型继承的最清晰、最灵活的方式。

  2. 创建无原型对象:当需要一个干净的字典对象时,使用 Object.create(null)

  3. 使用第二个参数定义属性:当需要精确控制属性的特性(如可写性、可枚举性)时,使用第二个参数。

  4. 避免修改原型链:尽量避免在运行时修改对象的原型,这会影响性能。

  5. 结合 ES6 Class:在现代 JavaScript 中,可以结合 ES6 Class 和 Object.create() 来实现更复杂的继承关系。

十六、与现代框架的关系

16.1 在 React 中的应用

// 创建具有共享方法的组件配置
const baseComponentConfig = {
    componentDidMount() {
        console.log("Component mounted");
    },
    componentWillUnmount() {
        console.log("Component unmounted");
    },
};

// 继承基础配置
const specificComponentConfig = Object.create(baseComponentConfig, {
    render: {
        value() {
            return React.createElement("div", null, "Hello");
        },
        writable: true,
        enumerable: true,
        configurable: true,
    },
});

16.2 在 Vue 中的应用

// 创建基础 mixin
const baseMixin = {
    created() {
        console.log("Component created");
    },
    methods: {
        commonMethod() {
            console.log("Common method");
        },
    },
};

// 扩展 mixin
const extendedMixin = Object.create(baseMixin, {
    methods: {
        value: Object.assign({}, baseMixin.methods, {
            specificMethod() {
                console.log("Specific method");
            },
        }),
        writable: true,
        enumerable: true,
        configurable: true,
    },
});

16.3 在 Node.js 中的应用

// 创建具有共享方法的服务基类
const baseService = {
    log(message) {
        console.log(`[${new Date().toISOString()}] ${message}`);
    },
    error(message) {
        console.error(`[${new Date().toISOString()}] ERROR: ${message}`);
    },
};

// 创建具体服务
const userService = Object.create(baseService, {
    getUser: {
        value(id) {
            this.log(`Getting user ${id}`);
            // 实现逻辑
        },
        writable: true,
        enumerable: true,
        configurable: true,
    },
});

十七、常见误区和陷阱

17.1 原型链污染

// 危险:修改原型会影响所有实例
const proto = { foo: "bar" };
const obj1 = Object.create(proto);
const obj2 = Object.create(proto);

// 不要这样做!
Object.getPrototypeOf(obj1).foo = "changed";
console.log(obj2.foo); // 输出: changed

// 正确:覆盖原型属性
obj1.foo = "baz";
console.log(obj1.foo); // 输出: baz(自己的属性)
console.log(obj2.foo); // 输出: bar(不受影响)

17.2 属性遮蔽问题

const proto = {
    value: 10,
    getValue() {
        return this.value;
    },
};

const obj = Object.create(proto);
obj.value = 20; // 遮蔽原型属性

console.log(obj.getValue()); // 输出: 20 - 使用的是实例属性
console.log(proto.getValue.call(obj)); // 输出: 20 - this 指向实例

17.3 this 指向问题

const proto = {
    name: "proto",
    getName() {
        return this.name;
    },
};

const obj = Object.create(proto);
obj.name = "obj";

const getName = obj.getName;
console.log(getName()); // 输出: undefined - this 指向全局对象

// 正确做法:绑定 this
const boundGetName = obj.getName.bind(obj);
console.log(boundGetName()); // 输出: obj

17.4 循环引用

const obj1 = {};
const obj2 = Object.create(obj1);

// 危险:创建循环引用
obj1.ref = obj2;

// 这可能导致内存泄漏,特别是在旧浏览器中

十八、测试方法

18.1 单元测试

使用 Jest 测试 Object.create() 创建的对象:

// object.create.test.js
const { createObject } = require("./objectUtils");

test("should create object with correct prototype", () => {
    const proto = { foo: "bar" };
    const obj = Object.create(proto);
    expect(Object.getPrototypeOf(obj)).toBe(proto);
    expect(obj.foo).toBe("bar");
});

test("should define properties from second argument", () => {
    const obj = Object.create(
        {},
        {
            name: { value: "John", enumerable: true },
        }
    );
    expect(obj.name).toBe("John");
    expect(Object.keys(obj)).toContain("name");
});

18.2 性能测试

// 性能测试
function testPerformance() {
    console.time("Object.create");
    for (let i = 0; i < 1000000; i++) {
        Object.create({});
    }
    console.timeEnd("Object.create");

    console.time("Object literal");
    for (let i = 0; i < 1000000; i++) {
        {
        }
    }
    console.timeEnd("Object literal");

    console.time("Constructor");
    function F() {}
    for (let i = 0; i < 1000000; i++) {
        new F();
    }
    console.timeEnd("Constructor");
}

testPerformance();

18.3 内存测试

// 内存使用测试
function testMemory() {
    const objs = [];
    const proto = { method() {} };

    console.log("Before creation:", process.memoryUsage().heapUsed / 1024 / 1024, "MB");

    for (let i = 0; i < 100000; i++) {
        objs.push(Object.create(proto));
    }

    console.log("After creation:", process.memoryUsage().heapUsed / 1024 / 1024, "MB");

    // 清理
    objs.length = 0;
    gc(); // 手动触发垃圾回收(仅在 Node.js 中)

    console.log("After cleanup:", process.memoryUsage().heapUsed / 1024 / 1024, "MB");
}

testMemory();

十九、实际项目中的应用示例

19.1 UI 组件库

// 基础组件原型
const baseComponent = {
    init() {
        this.render();
        this.bindEvents();
    },
    render() {
        // 基础渲染逻辑
    },
    bindEvents() {
        // 基础事件绑定
    },
};

// 按钮组件
const buttonComponent = Object.create(baseComponent, {
    render: {
        value() {
            this.element = document.createElement("button");
            this.element.textContent = this.text || "Button";
            document.body.appendChild(this.element);
        },
        writable: true,
        enumerable: true,
        configurable: true,
    },
    bindEvents: {
        value() {
            this.element.addEventListener("click", this.onClick.bind(this));
        },
        writable: true,
        enumerable: true,
        configurable: true,
    },
    onClick: {
        value() {
            console.log("Button clicked");
        },
        writable: true,
        enumerable: true,
        configurable: true,
    },
});

// 创建按钮实例
const button = Object.create(buttonComponent);
button.text = "Click me";
button.init();

19.2 状态管理

// 基础状态管理器
const baseStore = {
    state: {},
    getState() {
        return this.state;
    },
    subscribe(callback) {
        this.listeners.push(callback);
        return () => {
            this.listeners = this.listeners.filter(cb => cb !== callback);
        };
    },
    notify() {
        this.listeners.forEach(callback => callback(this.state));
    },
};

// 创建具体的状态管理器
function createStore(reducer, initialState) {
    const store = Object.create(baseStore);
    store.state = initialState;
    store.listeners = [];
    store.dispatch = function (action) {
        this.state = reducer(this.state, action);
        this.notify();
    };
    return store;
}

// 使用示例
function counterReducer(state = 0, action) {
    switch (action.type) {
        case "INCREMENT":
            return state + 1;
        case "DECREMENT":
            return state - 1;
        default:
            return state;
    }
}

const store = createStore(counterReducer, 0);
store.subscribe(state => console.log("State changed:", state));
store.dispatch({ type: "INCREMENT" }); // 输出: State changed: 1

19.3 工具库开发

// 基础工具原型
const baseUtil = {
    isString(value) {
        return typeof value === "string";
    },
    isNumber(value) {
        return typeof value === "number" && !isNaN(value);
    },
};

// 扩展工具库
const extendedUtil = Object.create(baseUtil, {
    isArray: {
        value(value) {
            return Array.isArray(value);
        },
        writable: true,
        enumerable: true,
        configurable: true,
    },
    isObject: {
        value(value) {
            return value !== null && typeof value === "object" && !Array.isArray(value);
        },
        writable: true,
        enumerable: true,
        configurable: true,
    },
});

// 使用示例
console.log(extendedUtil.isString("hello")); // 输出: true
console.log(extendedUtil.isArray([1, 2, 3])); // 输出: true

二十、性能基准测试

20.1 详细的性能对比

操作 性能( ops/sec )
对象字面量 ~150,000,000
构造函数 ~100,000,000
Object.create() ~80,000,000
Object.create(null) ~90,000,000

20.2 不同场景下的性能表现

// 测试不同原型大小的性能
function testPrototypeSize() {
    const smallProto = { a: 1 };
    const largeProto = {
        a: 1,
        b: 2,
        c: 3,
        d: 4,
        e: 5,
        method1() {},
        method2() {},
        method3() {},
        method4() {},
        method5() {},
    };

    console.time("Small proto");
    for (let i = 0; i < 1000000; i++) {
        Object.create(smallProto);
    }
    console.timeEnd("Small proto");

    console.time("Large proto");
    for (let i = 0; i < 1000000; i++) {
        Object.create(largeProto);
    }
    console.timeEnd("Large proto");
}

testPrototypeSize();

20.3 内存使用分析

对象创建方式 内存占用(100,000 个对象)
对象字面量 ~10MB
构造函数 ~8MB
Object.create() ~6MB
Object.create(null) ~5MB

二十一、总结

Object.create() 是 JavaScript 中一个强大的对象创建方法,它提供了比传统的对象字面量和构造函数更灵活的方式来创建对象和实现继承。通过理解它的工作原理和应用场景,我们可以写出更优雅、更高效的 JavaScript 代码。

无论是实现原型继承、创建具有特定原型的对象,还是创建无原型的字典对象,Object.create() 都能满足我们的需求。在适当的场景下使用它,可以使我们的代码更加清晰和可维护。

参考资料


文章作者: 弈心
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 弈心 !
评论
  目录