原型与继承
题目1: 什么是原型链?如何理解继承?
答案: 原型链是JavaScript实现继承的主要方法。每个对象都有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链。
继承允许我们创建新的对象,这些对象基于现有的对象,继承它们的属性和方法。在JavaScript中,继承主要通过原型链来实现。
扩展:
javascript
function Animal(name) {
this.name = name;
}
Animal.prototype.sayHello = function() {
console.log("Hello, I'm " + this.name);
};
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
const dog = new Dog("Buddy");
dog.sayHello(); // 输出: Hello, I'm Buddy
题目2: Object.create()
的作用是什么?
答案: Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
。这个方法允许你创建一个对象,并明确指定它的原型对象。
扩展:
javascript
const person = {
isHuman: false,
printIntroduction: function() {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};
const me = Object.create(person);
me.name = "Matthew";
me.isHuman = true;
me.printIntroduction(); // 输出: My name is Matthew. Am I human? true
题目3: ES5 和 ES6 实现继承的区别?
答案: ES5 主要通过构造函数、原型链和 Object.create()
来实现继承。 ES6 引入了 class
和 extends
关键字,使得继承的实现更加直观和简洁。
主要区别:
- 语法:ES6 的 class 语法更加清晰和面向对象。
- 内部实现:ES6 的 class 在内部仍然使用原型继承,但是语法糖使得代码更易读。
- 构造函数:ES6 使用
constructor
方法,更加明确。 - 静态方法:ES6 可以直接在 class 中定义静态方法。
扩展:
javascript
// ES5
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name + ' makes a noise.');
}
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// ES6
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
}
题目4: hasOwnProperty
和 in
操作符的区别?
答案:
hasOwnProperty
方法检查一个对象是否含有特定的自身属性(不包括原型链上的属性)。in
操作符检查一个属性是否在对象或其原型链中。
扩展:
javascript
const obj = {a: 1};
Object.prototype.b = 2;
console.log(obj.hasOwnProperty('a')); // true
console.log(obj.hasOwnProperty('b')); // false
console.log('a' in obj); // true
console.log('b' in obj); // true
题目5: 解释一下 JavaScript 中的原型污染。
答案: 原型污染是一种安全漏洞,攻击者可以通过向 Object.prototype
添加或修改属性来影响应用程序中的对象行为。这可能导致意外的行为、应用程序崩溃或安全漏洞。
防止原型污染的方法:
- 使用
Object.create(null)
创建没有原型的对象。 - 使用
Object.freeze(Object.prototype)
冻结原型。 - 在合并或克隆对象时,仔细检查和净化输入。
扩展:
javascript
const user = {};
const userInput = JSON.parse('{"__proto__": {"admin": true}}');
Object.assign(user, userInput);
console.log(user.admin); // true
console.log({}.admin); // true (所有对象都被污染)
题目6: 什么是 new
关键字?它在创建对象时做了什么?
答案: new
关键字用于创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
当使用 new
时,会执行以下步骤:
- 创建一个新的空对象。
- 将这个空对象的原型指向构造函数的 prototype 属性。
- 将构造函数内部的 this 绑定到新创建的对象。
- 如果构造函数没有返回其他对象,则返回这个新创建的对象。
扩展:
javascript
function Person(name) {
this.name = name;
}
const john = new Person('John');
console.log(john.name); // 'John'
console.log(john instanceof Person); // true
题目7: 如何实现多重继承?
答案: JavaScript 不直接支持多重继承,但可以通过组合、混入(Mixin)或者 Object.assign()
来模拟多重继承。
扩展:
javascript
// 使用混入(Mixin)
const canEat = {
eat: function() {
console.log('Eating...');
}
};
const canWalk = {
walk: function() {
console.log('Walking...');
}
};
function Person() {}
Object.assign(Person.prototype, canEat, canWalk);
const person = new Person();
person.eat(); // 输出: Eating...
person.walk(); // 输出: Walking...
题目8: 解释一下 JavaScript 中的 __proto__
和 prototype
。
答案:
__proto__
是每个对象都有的内部属性,指向该对象的原型。prototype
是函数对象的一个属性,当一个函数被用作构造函数时,它的prototype
属性将被用作新创建对象的原型。
扩展:
javascript
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function() {
console.log('Woof!');
};
const dog = new Dog('Buddy');
console.log(dog.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.constructor === Dog); // true
题目9: 什么是 Object.setPrototypeOf()
和 Object.getPrototypeOf()
?
答案:
Object.setPrototypeOf(obj, prototype)
设置一个指定的对象的原型。Object.getPrototypeOf(obj)
返回指定对象的原型。
这两个方法提供了一种标准的方式来设置和获取对象的原型,而不是使用非标准的 __proto__
属性。
扩展:
javascript
const animal = {
speak: function() {
console.log('The animal makes a noise.');
}
};
const dog = {
bark: function() {
console.log('Woof!');
}
};
Object.setPrototypeOf(dog, animal);
dog.speak(); // 输出: The animal makes a noise.
console.log(Object.getPrototypeOf(dog) === animal); // true
题目10: 解释一下 JavaScript 中的 "原型继承" 和 "类继承" 的区别。
答案: 原型继承:
- 基于原型链
- 更灵活,可以在运行时动态修改原型
- 性能较好,因为方法是共享的
类继承(ES6引入):
- 基于
class
和extends
关键字 - 语法更清晰,更接近传统的面向对象语言
- 实际上是原型继承的语法糖
- 提供了一些额外的功能,如
super
关键字
扩展:
javascript
// 原型继承
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(this.name + ' makes a noise.');
}
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// 类继承
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
bark() {
console.log('Woof!');
}
}
这两种方法在功能上是等价的,但类继承的语法更加直观和易于理解。