一、原型的核心概念与本质
定义:原型是JavaScript中对象的一个内置属性,用于实现对象间的属性共享。每个函数创建时都会自动生成一个prototype
属性,而每个对象(除null外)都有一个__proto__
(隐式原型)指向其构造函数的原型对象。
二、原型链的工作机制
1. 属性查找流程
当访问对象的属性时,JavaScript会按以下顺序查找:
- 对象自身是否有该属性;
- 若没有,查找对象的
__proto__
(隐式原型); - 继续向上查找原型链,直到
Object.prototype
; - 若仍未找到,返回
undefined
。
示例:
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
return `Hello, ${this.name}`;
};
const person = new Person('Alice');
console.log(person.sayName()); // 查找流程:person自身→person.__proto__(Person.prototype)→找到sayName
2. 原型链的终点
Object.prototype.__proto__ === null
,标志着原型链的结束。
三、构造函数、原型对象、实例的关系
角色 | 作用 | 关键属性 |
---|---|---|
构造函数 | 创建实例的函数(如Person ) | prototype :指向原型对象 |
原型对象 | 共享属性的载体(如Person.prototype ) | constructor :反向指向构造函数 |
实例 | 构造函数创建的对象(如person ) | __proto__ :指向原型对象 |
四、原型的核心方法与应用
1. 原型链的核心方法
-
Object.create(proto[, propertiesObject])
:
创建新对象,其__proto__
指向指定原型。const animal = { eat: function() { console.log('Eating...'); } }; const dog = Object.create(animal); dog.bark = function() { console.log('Woof!'); }; dog.eat(); // 继承自animal原型
-
instanceof
操作符:
检查实例的原型链中是否存在某个构造函数的原型对象。console.log(person instanceof Person); // true console.log(person instanceof Object); // true(原型链最终指向Object.prototype)
-
hasOwnProperty(key)
:
检测属性是否为对象自身所有(不查找原型链)。person.hasOwnProperty('name'); // true(自身属性) person.hasOwnProperty('sayName'); // false(原型属性)
2. 原型链的应用场景
- 类的继承:通过原型链实现“继承”(如
Child.prototype = new Parent()
); - 属性共享:避免重复定义方法(如所有数组实例共享
Array.prototype.push
); - polyfill实现:通过修改原型链添加浏览器缺失的方法(如
Object.assign
的polyfill)。
五、原型链的缺陷与ES6类的改进
1. 原型链的主要问题
-
共享引用类型属性的风险:
function Parent() { this.friends = ['Alice', 'Bob']; } function Child() {} Child.prototype = new Parent(); // 继承 const child1 = new Child(); const child2 = new Child(); child1.friends.push('Charlie'); console.log(child2.friends); // ['Alice', 'Bob', 'Charlie'](共享引用导致意外修改)
-
创建实例时无法向原型构造函数传参:
原型链继承中,Child.prototype = new Parent()
无法传递参数给Parent
。
2. ES6类(class)对原型链的封装
// ES6类语法(底层仍基于原型链)
class Parent {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(`Hi, ${this.name}`);
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类构造函数
this.age = age;
}
}
const child = new Child('David', 18);
child.sayHi(); // Hi, David(通过原型链调用Parent的方法)
改进点:
- 通过
class
和extends
语法简化原型链操作; super
关键字明确调用父类方法,解决参数传递问题;- 语法更接近传统面向对象编程,降低原型链使用门槛。
六、问题
1. 问:如何理解原型链的工作原理?请用代码示例说明。
原型链是对象查找属性的路径,当访问`obj.prop`时,JS先查自身属性,再沿`__proto__`向上查找。例如:
```javascript
const obj = { x: 1 };
obj.__proto__ = { y: 2 };
obj.__proto__.__proto__ = { z: 3 };
console.log(obj.x); // 1(自身属性)
console.log(obj.y); // 2(原型属性)
console.log(obj.z); // 3(原型的原型属性)
```
2. 问:构造函数的prototype与实例的__proto__有什么关系?
构造函数的`prototype`属性指向原型对象,而实例的`__proto__`(隐式原型)默认指向该构造函数的原型对象。即:
```javascript
function F() {}
const f = new F();
f.__proto__ === F.prototype; // true
F.prototype.constructor === F; // true
```
3. 问:为什么null没有原型?
`null`是JS中的特殊值,设计上不希望对其进行属性操作,因此`null.__proto__`为`undefined`,原型链在`Object.prototype.__proto__ === null`处终止,形成闭环。
七、总结
原型是对象间共享属性的机制,原型链是属性查找的路径;
构造函数的prototype
与实例的__proto__
指向同一原型对象;
instanceof
检查原型链,hasOwnProperty
仅查自身属性;
ES6类是原型链的语法糖,未改变底层机制;
面试中结合具体场景(如继承、属性查找)说明,避免死记硬背。