js面试里的坑:所有对象都有原型?
前言
JavaScript 中的原型(Prototype)和原型链(Prototype Chain)是理解该语言核心的重要概念。它们构成了 JavaScript 中的面向对象编程模型的基础,同时也解释了许多 JavaScript 中的特性和行为。我们将探索原型和原型链的含义、作用以及它们如何影响 JavaScript 中的对象和继承机制。
正文
面试题
所有的对象都有原型?
你的答案会是什么呢?我们先了解一下原型和原型链。
什么是原型?
原型(Prototype)在 JavaScript 中是一种机制,它允许对象通过原型链(Prototype Chain)来共享属性和方法。每个 JavaScript 对象都有一个原型,它是一个指向另一个对象的引用。
原型分为隐式原型和显示原型两种。
原型的作用
当我们访问对象的属性或方法时,如果对象本身没有定义这个属性或方法,JavaScript 引擎会自动去对象的原型链中查找。这样,原型为对象提供了一种基于原型继承的特性,使得对象之间可以共享和复用代码,提高了代码的效率和可维护性。
在聊原型之前我们先了解一下构造函数。
构造函数
构造函数在本质上与普通函数并没有太大的不同。但是构造函数具有一种特殊的用途,它们用于创建新的对象。通过使用new关键字调用构造函数,我们可以创建一个新的对象,并且该对象会自动具有构造函数中定义的属性和方法。
function Person(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
}
let xm = new Person('小明', 18, 'boy')
xm是通过new关键字调用Person构造函数创建的实例对象。
new一个新对象的过程中做了什么?
function Person(name, age, sex) {
// var objct = {}
//Person.call(object)
//object.__proto__ === Person.prototype
this.name = name
this.age = age
this.sex = sex
// var objct = {
// name: name,
// age: age,
// sex: sex
// }
// return objct
}
let xm = new Person('小明', 18, 'boy')//实例对象
- new会在构造函数中创建一个objct对象。
- 通过call方法将this指向objct对象。
- 将这个objct对象的原型指向构造函数的原型。
- 执行函数中的逻辑代码(相当于往objct对象上添加属性)。
- 返回这个objct对象。
显式原型
每个函数对象(包括构造函数)都有一个 prototype属性,这个属性指向了一个对象,我们称之为显式原型。显式原型定义了构造函数创建的所有实例对象所共享的属性和方法。eg:
Person.prototype.lastName = '猪'
Person.prototype.say = function () {
console.log('hello')
}
function Person() {
this.name = '猪八戒'
}
let zbj = new Person()
console.log(zbj.name)//猪八戒
console.log(zbj.lastName)//猪
zbj.say()//hello
constructor是 JavaScript 中的一个特殊属性,它指向创建当前对象的构造函数。换句话说,当我们使用构造函数创建一个对象时,该对象的 constructor 属性将会指向用于创建它的构造函数。
zbj.constructor
的值为Person构造函数。
constructor属性可以被修改。(一般不特意修改)eg:
function Bus() {}
Car.prototype = {
constructor: Bus
}
function Car() {}
let car = new Car()
console.log(car.constructor);//输出:Bus构造函数
实例对象car的constructor 属性的指向被改变了。
隐式原型
每个 JavaScript 对象在创建时都会关联一个隐式原型,它是一个指向其构造函数的 prototype 属性的引用。换句话说,对象的隐式原型指向了创建该对象的构造函数的显式原型。通过原型链,对象可以访问其构造函数的原型上的属性和方法。通过 __ proto __ 属性可以访问对象的隐式原型。eg:
Person.prototype.lastName = '猪'
Person.prototype.say = function () {
console.log('hello')
}
function Person() {
this.name = '猪八戒'
}
let zbj = new Person()
console.log(zbj.name)//猪八戒
console.log(zbj.lastName)//猪
zbj.say()//hello
隐式原型和显式原型的关系
zbj是由Person构造函数创造的实例化对象。zbj的隐式原始与Person构造函数的显示原型是一样的。这就说明了对象的隐式原型指向了创建该对象的构造函数的显式原型,也就是说明对象的隐式原型 === 创建它的构造函数的显示原型(zbj.__ proto __ === Person.prototype)
实例对象的特点
- 实例对象可以修改显示继承到的属性,无法修改隐式继承到的属性(原型上的属性)
- 实例对象无法给原型新增属性
- 实例对象无法删除原型上的属性
原型链
在之前的例子中,zbj对象只定义了name='猪八戒'
,那么为什么执行console.log(zbj.lastName)
和zbj.say()
会输出猪
和hello
?
JavaScript引擎在查找属性时,会先查找对象显式具有的属性,找不到再查找对象的隐式原型(__ proto __)。
JavaScript引擎在查找zbj显式具有的属性,发现只有name属性,没有say和lastName属性;那么找不到就查找zbj的隐式原型(zbj.__ proto __)也就是Person构造函数的显示原型(Person.prototype)上的属性,最后找到了say和lastName属性。zbj对象可以访问其构造函数的原型上的属性和方法,所以可以输出猪
和hello
。
原型链是什么?
原型链是 JavaScript 中实现继承的基础机制之一。js引擎在查找属性时,会顺着查找对象的隐式原型向上查找,找不到,则查找隐式原型的隐式原型,一直向上,直到找到null为止,这种查找关系称之为原型链。
总之,原型链是由对象之间的原型关系构成的链式结构,用于实现对象之间的继承和共享属性和方法。
图解原型链
GrandFather.prototype.say = function () {
console.log('haha');
}
function GrandFather() {
this.age = 60
this.like = 'drink'
}
Father.prototype = new GrandFather()
function Father() {
this.age = 40
this.fortune = {
card: 'visa'
}
}
Son.prototype = new Father()
function Son() {
this.age = 18
}
let son = new Son()
console.log(son.age);
console.log(son.fortune);
console.log(son.like);
console.log(son.say());
图解原型链:

原型链就是通过__ proto __连接的链式结构。
执行结果为:
面试题解答
eg:
let a = {
name: 'Tom'
}
let obj = Object.create(a)
let obj1 = Object.create(null)
console.log(obj.__proto__ === a)
console.log(obj1.__proto__)
Object.create()
方法用于创建一个新的对象,并将其原型设置为指定对象。
面试题答案:
在该例子中,obj1对象没有原型。所以不是所有对象都有原型。
小结
JavaScript 中的原型和原型链是理解该语言核心的重要概念。原型允许对象通过原型链共享属性和方法,构造函数用于创建新的对象实例。原型分为显式原型和隐式原型,构造函数的 prototype 属性定义了显式原型。通过 __ proto __ 属性可以访问对象的隐式原型。原型链是由对象之间的原型关系构成的链式结构,实现了继承和共享属性和方法的机制。掌握原型和原型链的概念对于理解 JavaScript 的对象和继承机制至关重要。
转载自:https://juejin.cn/post/7366445016779833354