与原型、原型链、构造函数相关的面试题
1.什么是构造函数?如何定义它?
构造函数是一个用于创建和初始化一个对象的特殊函数。在 JavaScript 中,几乎每个对象都是由某个构造函数创建的。构造函数的约定是,其名称首字母大写。
如何定义它?
你可以像定义常规函数一样定义构造函数,但是为了创建新的实例,你需要使用 new
关键字。
以下是一个简单的例子:
// 定义一个构造函数
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = function() {
return this.firstName + " " + this.lastName;
};
}
// 使用构造函数创建新的对象
var john = new Person("John", "Doe");
var jane = new Person("Jane", "Smith");
console.log(john.getFullName()); // 输出:John Doe
console.log(jane.getFullName()); // 输出:Jane Smith
在这个例子中:
Person
是一个构造函数,它接受两个参数,firstName
和lastName
。- 使用
this
关键字,我们可以给创建的对象分配属性和方法。 - 通过使用
new
关键字,我们创建了Person
的新实例。
注意:更好的做法是使用原型链来给所有对象共享方法,如:Person.prototype.getFullName = function() {...}
。只在原型链上有一个方法,但是所有对象可用。在构造函数里面创建getFullName会导致所有的对象都有,占用大量内存
2.什么是原型 (prototype)? 每个 JavaScript 对象都有一个原型吗?
在 JavaScript 中,原型 (prototype
) 是一个对象的内部链接,通过这个链接,对象可以继承其他对象的属性和方法。原型是 JavaScript 的核心特性,支撑了它的原型继承机制。
每个 JavaScript 对象都有一个原型吗?
是的,几乎每个 JavaScript 对象都有一个原型。唯一没有原型的对象是通过 Object.create(null)
创建的对象。
当你试图访问一个对象的属性或方法,而该对象本身没有这个属性或方法,JavaScript 会查找该对象的原型(以及原型的原型,以此类推)直到找到该属性或方法或达到原型链的尽头(即 null
)。
3.原型和原型链有什么区别?
原型(prototype)是一个对象,它包含共享属性和方法的对象。
原型链(prototype chain)是由一系列原型对象组成的链式结构。
通过原型链,JavaScript可以实现对象之间的继承和共享属性和方法。
4.解释 proto 和 prototype 的区别。
__proto__
和 prototype
是 JavaScript 中经常被提及,但同时也是经常被混淆的两个属性。它们之间的关系和用途非常核心,对于理解 JavaScript 的原型继承机制至关重要。
prototype
属性:prototype
是 JavaScript 中的函数对象特有的属性
__proto__
属性:
__proto__
是**每个 JavaScript 对象(**除了null
)都具有的一个属性,它是一个访问器属性(一个 getter 函数和一个 setter 函数)。
关系与区别:
-
对于自定义构造函数,其创建的实例的
__proto__
属性指向构造函数的prototype
属性。例如:function MyConstructor() {} let obj = new MyConstructor(); console.log(obj.__proto__ === MyConstructor.prototype); // true
-
只有函数对象才有
prototype
属性,而所有的对象都有__proto__
属性。
5.如何检查一个对象的原型?给定两个对象,如何检查它们是否具有相同的原型?
- 使用
Object.getPrototypeOf(obj)
方法:这个方法返回对象obj
的原型。例如:
var person = {
name: "John",
age: 30,
};
var student = Object.create(person);
console.log(Object.getPrototypeOf(student)); // 输出 person 对象
- 使用
obj.__proto__
属性:__proto__
是一个非标准的属性,但在大多数现代浏览器中支持。它返回对象obj
的原型。例如:
var person = {
name: "John",
age: 30,
};
var student = Object.create(person);
console.log(student.__proto__); // 输出 person 对象
需要注意的是,虽然 __proto__
属性在大多数情况下是有效的,但它不是 JavaScript 标准的一部分,因此不建议在生产环境中广泛使用。
- 使用
instanceof
操作符:instanceof
操作符可用于检查一个对象是否是某个构造函数的实例,其实现原理是通过原型链进行判断。例如:
function Person(name) {
this.name = name;
}
var john = new Person("John");
console.log(john instanceof Person); // 输出 true
console.log(john instanceof Object); // 输出 true(所有对象都是 Object 的实例)
instanceof
操作符可以判断对象是否是某个构造函数的实例,间接地可以判断对象的原型是否是某个对象。
这些方法都可以用来检查一个对象的原型,具体选择哪种方法取决于个人偏好和项目需求。通常情况下,推荐使用 Object.getPrototypeOf(obj)
方法来获取对象的原型,因为它是标准的方法且具有广泛的兼容性。
6.为什么最好不要修改对象的原型?与解决方法
潜在的覆盖问题:如果原型上已经存在了一个同名的方法或属性,你的修改可能会意外地覆盖它,或者在未来的版本中被新的原型方法覆盖。
解决方法:
如果你确实需要修改或扩展原型(虽然这通常不推荐),以下是一些建议:
-
不修改全局对象的原型:特别是不要修改原生对象,如
Array
、Object
、String
等的原型。如果你想为原生对象添加功能,可以考虑使用工具函数或类/对象扩展。 -
使用Object.defineProperty:使用
Object.defineProperty
可以让你更加细致地定义原型上的属性或方法,例如设置为不可枚举,这样它就不会在for...in
循环中出现。Object.defineProperty(Array.prototype, 'myCustomFunction', { value: function() { // ... }, writable: true, configurable: true, enumerable: false });
-
检查方法是否已存在:在添加新的方法或属性前,检查它是否已存在,以避免意外的覆盖。
if (!Array.prototype.myCustomFunction) { Array.prototype.myCustomFunction = function() { // ... }; }
7.解释原型继承是如何工作的。
回答属性查找:当试图访问一个对象的属性时,JavaScript 首先在该对象本身上查找该属性。如果没有找到,它会在对象的原型上查找,然后是原型的原型,依此类推,直到找到属性或达到原型链的末尾。
原型链继承:4种方式,class的extend继承,在原型上new一个父类,function + call,原型链+call
8.hasOwnProperty 方法是从哪里来的?为什么所有的普通对象都可以访问它?
hasOwnProperty
方法用于检查一个对象是否具有指定名称的自身属性(即非继承而来的属性)。它接受一个参数,即属性的名称,如果对象拥有该属性,则返回 true
,否则返回 false
。这个方法对于遍历对象自身的属性非常有用,可以用来确定属性的来源是否是对象本身而不是继承的。
总结来说,hasOwnProperty
方法是所有普通对象通过原型链继承自 Object.prototype
的方法,用于判断一个对象是否具有指定名称的自身属性。
9.什么是原型污染?为什么它可能是危险的?
原型污染(Prototype Pollution)是指通过修改对象的原型链,对对象的原型进行恶意或意外的更改,从而影响到对象及其相关属性和方法的行为。这种污染可以导致安全漏洞和意外的行为。
原型污染之所以可能是危险的,有以下几个原因:
-
污染全局对象:通过污染全局对象的原型,可以影响到所有基于该原型创建的对象。这可能导致全局范围内的意外行为和冲突,影响到整个应用程序。
-
覆盖或篡改原始对象的属性和方法:通过修改对象原型链上的方法或属性,可以改变对象的行为。这可能导致不可预料的结果和潜在的安全风险。例如,攻击者可以修改内置对象的原型,改变其方法的行为,从而实现代码执行、信息泄露或其他攻击。
-
绕过安全检查和访问控制:某些安全机制和访问控制是基于对象的原型链的。通过修改原型链,攻击者可以绕过这些机制,获取未授权的访问权限,从而导致数据泄露、越权访问和其他安全问题。
原型污染可以通过多种方式发生,其中常见的一种是通过用户输入直接或间接地修改对象的原型链。例如,当应用程序使用不可信的用户输入直接作为属性名或方法名时,攻击者可以通过构造特定的输入来修改原型链。
为了防止原型污染,可以采取以下措施:
- 避免使用不可信的用户输入作为属性名或方法名。
- 在使用第三方库时,仔细审查该库是否存在原型污染的安全问题,并及时更新到最新版本。
- 限制或禁用对全局对象的修改,避免对全局对象的原型进行篡改。
- 使用严格的输入验证和过滤,确保接受的用户输入符合预期的格式和范围。
总之,原型污染是一种潜在的安全问题,攻击者可以通过修改对象的原型链来实现不当行为和攻击。为了保护应用程序的安全性,开发人员应了解原型污染的概念,并采取适当的措施来防止和缓解该问题。