用Object.create()来实现继承
首先了解下这个方法
Object.create()
是什么?
Object.create(A, propertiesObject)
用来创建一个新对象 ,并且把它的原型(__proto__
)设置为 A
。
ini
const person = {
greet() {
console.log(`Hi, I am ${this.name}`);
}
};
const tom = Object.create(person); // 以 person 作为tom的原型
tom.name = "Tom";
tom.greet(); // Hi, I am Tom
这里:
tom.__proto__ === person
✅tom
自己没有greet
,所以会顺着原型链去person
找。### 第二个参数(可选)
你还可以传入属性描述符:
js
const obj = Object.create({}, {
name: {
value: "Alice",
writable: false, // 不可修改
enumerable: true, // 可枚举
configurable: false // 不可删除
}
});
console.log(obj.name); // Alice
obj.name = "Bob"; // 修改无效
console.log(obj.name); // Alice
常见用途
-
继承
jsfunction Animal(name) { this.name = name; } Animal.prototype.sayHi = function() { console.log(`I am ${this.name}`); }; function Dog(name) { Animal.call(this, name); } Dog.prototype = Object.create(Animal.prototype); // 继承 Dog.prototype.constructor = Dog; const d = new Dog("Lucky"); d.sayHi(); // I am Lucky
-
创建纯净对象(无原型)
iniconst dict = Object.create(null); dict.apple = "苹果"; console.log(dict); // { apple: "苹果" } console.log(dict.toString); // undefined (因为没有继承 Object.prototype)
对于Object.create()继承中的几个问题
问题一:Dog.prototype = Object.create(Animal.prototype);这里如果写成 Dog.prototype = Animal.prototype可不可以
区别对比
✅ 推荐写法
ini
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype
是一个全新的对象 ,它的__proto__
指向Animal.prototype
。Dog
和Animal
各自的原型对象是独立的。- 即使你在
Dog.prototype
上新增方法,也不会影响Animal.prototype
。
❌ 如果写成
ini
Dog.prototype = Animal.prototype;
-
这表示
Dog.prototype
和Animal.prototype
变成了同一个对象。 -
那么:
- 给
Dog.prototype
添加方法时,其实就是在改Animal.prototype
。 - 所有
Animal
的实例都会意外地拥有Dog
的方法,破坏继承结构。
- 给
举个例子
javascript
function Animal() {}
Animal.prototype.sayHi = function() {
console.log("I am an animal");
};
function Dog() {}
// 错误写法
Dog.prototype = Animal.prototype;
Dog.prototype.bark = function() {
console.log("Woof!");
};
const a = new Animal();
a.bark(); // ❌ 本来不该有 bark 方法
这里 a
是 Animal
的实例,结果却能调用 bark
,说明原型链被"污染"了。
✅ 正确写法的好处
ini
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.bark = function() {
console.log("Woof!");
};
const a = new Animal();
a.bark(); // ❌ 报错,符合预期
Dog
的实例可以继承Animal
的方法。Animal
的实例不会被"污染"。
总结
Dog.prototype = Object.create(Animal.prototype)
→ 正确、安全的继承方式Dog.prototype = Animal.prototype
→ 错误,会导致Animal
和Dog
共用一个原型对象,互相影响
问题二:Dog.prototype.constructor = Dog;为什么要加上这句话
1. 默认情况下
当你定义一个函数时,JS 会自动给它附带一个原型对象 prototype
,并且这个原型对象自带一个 constructor
属性,指向这个函数本身。
javascript
function Dog() {}
console.log(Dog.prototype.constructor === Dog); // true
所以默认情况下,constructor
是"指回自己"的。
2. 当你覆盖原型时
在继承时我们常常写:
ini
Dog.prototype = Object.create(Animal.prototype);
这会用一个新的对象 替换掉原本的 Dog.prototype
。
而这个新对象是通过 Object.create(Animal.prototype)
创建的,所以它的 constructor
来自 Animal.prototype.constructor
,而不是 Dog
。
ini
console.log(Dog.prototype.constructor === Animal); // true ❌
这就出现了问题:
Dog
的实例原型链是对的(能继承Animal
的方法),- 但
Dog.prototype.constructor
却错误地指向了Animal
。
3. 手动修复
因此我们需要手动把它修正回来:
ini
Dog.prototype.constructor = Dog;
这样做是为了让原型对象的 constructor
指向正确的构造函数。
4. 为什么重要?
虽然在日常开发中 constructor
用得不多,但它能反映某个实例最初是由哪个构造函数创建的。
ini
const d = new Dog("Lucky");
console.log(d.constructor === Dog); // true ✅
console.log(d.constructor === Animal); // false
如果不修复,可能导致一些依赖 constructor
的逻辑出错,比如序列化、类型检查等。
✅ 总结
- 每个函数自带的
prototype
都有一个constructor
,默认指向该函数。 - 一旦你用
Object.create
替换了Dog.prototype
,它的constructor
就会错指向Animal
。 - 所以需要
Dog.prototype.constructor = Dog
来修复。