js:从0到1解析原型,小白也能看懂网易面试题

203 阅读6分钟

前言

网易面试题:是否所有的对象最终都会继承自Object.prototype?
Object.prototype,意思是Object对象的原型, 我们先一步一步了解原型再来回答这个问题。在探讨原型前我们先来深入了解一下对象。

对象基本概念

javascript中数据类型可以简单分为简单数据类型和引用数据类型。 简单数据类型占据空间不大,是直接存放在调用栈中,但是引用数据类型可能需要非常大的空间因此存放在内存中

变量存储区别:

  • 基本数据类型变量直接存储的是变量值
    引用数据类型变量当中存储的是该变量在堆内存中所属空间的地址值

  • 对象也是一种引用数据类型,它可以存储和组织多个值作为属性。对象可以包含任意数量的属性,每个属性都有一个名称和对应的值。

image.png
let obj = {
name: 'John', 
age: 30, 
gender: 'male' };
//obj中实际存储了地址值

如何创建一个对象

1.通过对象对象字面量

var obj1={}//创建一个空对象
  1. 通过构造函数
let obj2=new Object()
  1. 通过自定义的构造函数创建
 function Car(color){
    this.name='BMW'
    this.height=1400
    this.lan=4900
    this.weight=1000
    this.color=color
 }
 var car=new Car(black)
  1. 通过obj.create()函数创建新对象,括号内参数为指定对象为该对象的原型(记住这个函数后面会用到)
// 定义一个原型对象
const person = {
  firstName: "",
  lastName: "",
  fullName: function() {
    return this.firstName + " " + this.lastName;
  }
};

// 使用 Object.create() 创建一个新对象,并指定原型为 person
const johnDoe = Object.create(person);

原型(显式原型)

1.定义

原型prototype是函数天生就具有的属性,它定义了构造函数制造出对象的公共祖先。通过该构造函数产生的对象,可以隐式地继承到原型上的属性和方法。通俗的讲,构造函数自带一个叫原型的属性,这个属性是一个对象,这个对象的值可以被这个构造函数创建的所有对象读取。

  • 只要构造函数被定义了就会有prototype这个属性,这个属性便是原型
  • 原型也是一个对象里面的属性是能够被所有子类所访问(有点像静态变量,但是子类不能去更改该公共属性的值)

2.意义

可以提取共有属性,简化代码执行

3.原型的增删改查

  • 实例对象只能查看原型的属性和方法
  • 无法通过实例对象删除原型的属性和函数。想要添加删除更改的话得通过原型
  • constructor 属性记录对象是由谁创建的
//直接对构造函数Car的prototype创建属性并赋值,说明prototype是函数自带的
Car.prototype.height=1400
Car.prototype.lan=4900
Car.prototype.name='BMW'
function Car(owner,color){
    
    this.owner=owner
    this.color=color
}
var car1 = new Car('user1','red')
var car2 = new Car('user2','black')
car1.color='pink'
console.log("car1"+car1)
console.log("car2"+car2)
//打印隐式继承公共属性,说明实例对象可以访问函数原型的属性
console.log(car1.height)
console.log(car2.height)
//更改原型属性
car1.height=0
console.log(car1.height)//height的值不发生变化说明实例对象无法去修改函数原型的属性
//通过函数原型修改属性
Car.prototype.height=1500
console.log(car1.height)//修改成功打印1500
console.log(car2.height)
//同样删除原型属性也只能通过原型来删除
delete car1.height
console.log(car1.height)//失败,仍然打印1500
delete Car.prototype.height
console.log(car1.height)//成功

运行结果:

image.png
构造函数原型的创建者是该构造函数本身

function Car(){

}
var car=new Car();
console.log(car.prototype.constructor)//返回构造函数

运行结果:

image.png

隐式原型

为什么构造函数创建的对象能够访问构造函数原型中的变量和属性呢

每个对象都有一个隐藏的内部属性[[Prototype]]__proto__,也称为隐式原型(implicit prototype),它指向该对象的原型。

Car.prototype={
lan:4900,
name:'BMW'

}

function Car(owner,color){
    this.height=1400
    this.owner=owner
    this.color=color
}
var Car = new Car('user1','red')

new关键词的作用:

  1. 创建this对象
  2. 执行函数里面的逻辑为该对象增加属性
  3. 将隐式对象值赋予为构造函数的显式对象
  4. 返回这个this对象
//new关键词生成对象的作用过程
this={
    height:1400
    towner:user1
    color:red
    [[prototype]]:Car.prototype
}
return this

当查找对象中属性时,会先显式属性中查找,再到隐式原型中的属性查找。而该对象的隐式原型就是构造函数的原型,所以说构造函数创建的对象能够隐式继承原型中的属性变量。

原型链

我们继续思考一下,原型其实也是一个对象,那么也有隐式原型,也就会有原型,那么这样会一直循环下去吗?

原型链概念:

当访问对象中不存在的属性,会去找其隐式对象中的属性,再找不到,回去找构造函数原型的隐式原型中的属性,这样循环找下去,直到找到目标或者直到null,也就是找到obj的构造函数原型的隐式原型,这种查找关系叫做原型链。现在我们也可以回答上面的问题,通过原型链一直寻找直到找到目标值或者到obj的构造函数原型的隐式原型为null。结合下面这张图片,加深理解。

  • 通过Foo创建的对象f1的隐式原型__proto__就是构造函数Foo()的原型Foo.prototype
  • 函数Foo的显式原型Foo.prototype也是一个对象,那么其隐式原型__proto__是其构造函数的原型也就是Object.prototype。而Object.prototype并没有隐式原型,也就说明了,Object类是所有对象的基类,也就是所有对象原型链的终点。
  • 函数Foo的原型是由Foo函数本身生成的
  • 函数也是一种特殊的对象所有的函数都是由构造函数Function生成的
  • function的隐式原型__proto__也就是构造函数Function的原型Foo.prototype,该原型是由函数Function本身生成
  • 创建对象的Object()函数也是由构造函数Function生成,故Object()的隐式原型也就是Function的显式原型
  • Function.prototype对象的隐式原型是Object.prototype,说明Function这个对象使用函数Object()函数创建的 原型链.jpg

结论

  • 那么原型链的终点是Object.prototype
  • 那么也就是说所有的对象最终都会继承到Object.prototype

网易面试题

网易面试题:是否所有的对象最终都会继承自Object.prototype?

通过前面的学习我们知道所有的对象最终都会继承Object.prototype,但是实际上这里存在着一个BUG会导致存在生成的对象的原型不继承任何对象

var myobj=Obj.create(null)//生成一个原型为空的对象

null一个非常久远的BUG

对象数据类型在转换为二进制时前面三位数都是0,而null在转换为二进制时所有位数都是0,因此函数Obj.create()方法会误将null当成对象处理。因此该函数便会将null作为新对象的原型,故存在原型不继承Object.prototype的对象。

答案

所以这道题答案应该是绝大多数的对象都继承,但是通过Obj.create(null)方法创建的对象不会继承。