原型与原型链
原型
JavaScript中的每个对象都有一个原型(prototype),原型本身也是一个对象。对象会继承原型的属性和方法。原型是JavaScript实现继承的基础。构造函数通过prototype属性指向原型对象,实例对象通过__proto__
属性访问其原型。
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};
const person1 = new Person('Alice');
person1.sayHello(); // 输出: Hello, my name is Alice
在这个例子中,Person
是一个构造函数,它的prototype
属性指向原型对象。当我们创建person1
实例时,实例通过__proto__
链接到Person.prototype
,因此可以访问sayHello
方法。
原型链
原型链是JavaScript实现继承的机制。当访问一个对象的属性时,如果对象本身没有该属性,JavaScript会沿着原型链向上查找,直到找到该属性或到达原型链的末端(null
)。这种链式结构就是原型链。
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} is eating.`);
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
// 设置原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(`${this.name} is barking!`);
};
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.eat(); // 输出: Buddy is eating.
myDog.bark(); // 输出: Buddy is barking!
这里Dog
继承了Animal
的特性。Dog.prototype
的原型是Animal.prototype
,形成了原型链。当调用myDog.eat()
时,JavaScript先在myDog
实例上查找,没找到就沿着原型链在Dog.prototype
上查找,最后在Animal.prototype
上找到。
原型相关方法
JavaScript提供了几个与原型相关的重要方法:
Object.getPrototypeOf(obj)
:获取对象的原型Object.setPrototypeOf(obj, prototype)
:设置对象的原型obj.hasOwnProperty(prop)
:检查属性是否是对象自身的(非继承的)prop in obj
:检查对象或其原型链上是否存在属性
const obj = {};
const parent = { x: 1 };
Object.setPrototypeOf(obj, parent);
console.log(Object.getPrototypeOf(obj) === parent); // true
console.log(obj.x); // 1 (继承自parent)
console.log(obj.hasOwnProperty('x')); // false
console.log('x' in obj); // true
构造函数与原型
构造函数、原型和实例之间的关系是理解原型链的关键。每个构造函数都有一个prototype
属性指向原型对象,原型对象有一个constructor
属性指回构造函数,实例通过__proto__
链接到原型对象。
function Car(make, model) {
this.make = make;
this.model = model;
}
Car.prototype.displayInfo = function() {
console.log(`This is a ${this.make} ${this.model}`);
};
const myCar = new Car('Toyota', 'Camry');
console.log(Car.prototype.constructor === Car); // true
console.log(myCar.__proto__ === Car.prototype); // true
console.log(myCar instanceof Car); // true
原型继承的优缺点
原型继承有其独特的优势和局限性:
优点:
- 内存效率高,共享方法只在原型上定义一次
- 动态性,修改原型会立即影响所有实例
- 实现简单,不需要类语法也能实现继承
缺点:
- 共享引用类型属性可能导致问题
- 子类型无法向超类型传递参数
- 原型链过长会影响性能
function Parent() {
this.colors = ['red', 'blue'];
}
function Child() {}
Child.prototype = new Parent();
const child1 = new Child();
child1.colors.push('green');
const child2 = new Child();
console.log(child2.colors); // ['red', 'blue', 'green'] (共享了colors属性)
ES6类与原型
ES6引入了class语法,但底层仍然是基于原型的继承。class只是语法糖,让原型继承更易理解和使用。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
console.log(`${this.name} barks!`);
}
}
const d = new Dog('Mitzie', 'Poodle');
d.speak(); // Mitzie barks!
使用Babel等工具转换后,可以看到class实际上还是转换为原型继承的代码。
原型污染与防御
原型污染是指恶意修改Object.prototype等基础原型,导致所有对象都受到影响。这是一种常见的安全问题。
// 恶意代码
Object.prototype.isAdmin = true;
// 受影响代码
const user = { name: 'Alice' };
if (user.isAdmin) {
// 本不该执行这里的代码
console.log('You have admin privileges!');
}
防御原型污染的方法:
- 使用
Object.create(null)
创建无原型的对象 - 使用
Object.freeze()
冻结原型 - 使用
hasOwnProperty
检查属性
// 安全做法
const safeObj = Object.create(null);
safeObj.name = 'Safe Object';
console.log(safeObj.hasOwnProperty); // undefined
// 冻结原型
Object.freeze(Object.prototype);
Object.prototype.isAdmin = true; // 在严格模式下会报错
性能考虑
原型链查找会影响性能,过长的原型链会导致属性查找时间增加。现代JavaScript引擎会优化原型查找,但仍需注意:
- 避免过深的原型链继承
- 频繁访问的属性可以直接定义在对象上
- 使用组合而非继承来共享代码
// 性能较差的深继承链
function A() {}
function B() {}
function C() {}
function D() {}
B.prototype = new A();
C.prototype = new B();
D.prototype = new C();
const obj = new D();
// 查找属性需要遍历4层原型链
// 更好的方式:组合
const features = {
feature1() {},
feature2() {}
};
function MyObject() {
Object.assign(this, features);
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:对象枚举与迭代
下一篇:constructor属性