深入剖析JavaScript的原型及原型链

什么是JavaScript的原型?

原型是函数上的一个属性,它定义了构造函数制造的对象的公共祖先

原型的主要作用在于实现对象之间的属性和方法共享,从而节省内存空间,提高代码的效率

  • 我们通过一段代码来接讲解,通过购买小米su7这个例子讲解

    • 首先我们创建一个function Car来作为车的构造函数
function Car(color, owner) {
this.name = 'su7'
this.lang = 5000
this.height = 1400
this.color = color
this.owner = owner
}

当使用 new Car(...) 来创建新的实例时,每个实例都会具有 namelangheightcolorowner 这些属性。其中 colorowner 的值会根据创建实例时传递的参数来确定,而 namelangheight 的值则是固定的。

接下来我们就去购买小米汽车了

let tian = new Car('black', 'tiantian')

这里我们给tiantian购买了一辆su7,在构造su7的时候,车子的name,lang,height是不变的,无论是谁来购买,这三个数据是不会变的,只有主人和颜色是可以变的,但是每次购买su7的时候,还是会重新创建一份关于name,lang,height的数据,这样就造成了内存的浪费,降低了代码的效率

所以我们的代码可以改造为

Car.prototype.name = 'su7'
Car.prototype.lang = 5000
Car.prototype.height = 1400
function Car(color, owner) {
    // this.name = 'su7'
    // this.lang = 5000
    // this.height = 1400
    this.color = color
    this.owner = owner
}

let tian = new Car('black', 'tiantian')
console.log(tian);
console.log(tian.name); //隐式具有,也就是继承了

代码通过在 Car 的原型对象 Car.prototype 上定义了 namelangheight 属性。

当创建 tian 这个 Car 的实例时,虽然在构造函数内部没有直接为实例设置 namelangheight 属性,实例仍然可以访问到在原型对象上定义的这些属性。

所以当打印 tian 时,会显示出包含从原型继承的属性以及在构造函数中设置的 colorowner 属性的对象。而打印 tian.name 时,能够获取到在原型上定义的 'su7'

image.png

这样就实现了name,lang,height的数据共享,节约了内存

这里我们已经对原型有一个初步的认识了,接下来我们继续讲解什么是prototype属性

Car.prototype.product = "xiaomi"

function Car() {
    this.name = "su7"
}

var car = new Car();

console.log(car);
console.log(car.name);
console.log(car.product);

这段代码的执行结果为

image.png

现在如果我们想修改product的值,可以直接修改吗?

car.product = "iphone"

现在添加这段代码,我们可以看到的运行效果为

image.png

很显然并不能通过这种方式去修改prototype里面的product

这里我们就明白了一个重要的结论:

  • 实例对象可以修改显示继承到的属性
  • 实例对象无法修改隐式继承到的属性(原型上的属性)
  • 实例对象无法给原型新增属性
  • 实例对象无法删除原型上的属性

对象里面到底有什么?

我们将这段代码放在浏览器运行

<script>
  function A() {}

  let a = new A();

  console.log(a);
</script>

你可以看到这样的结果

image.png

可以看到在A{}里面有[[Prototype]][[Prototype]]为对象的原型

注:在谷歌浏览器显示为[[Prototype]],别的浏览器可能显示为__proto__

我们继续修改代码

<script>
  function A() {}

  let a = new A();

  console.log(A.prototype);
  console.log(a.__proto__);
</script>

这段代码的结果为

image.png

结果显而易见,对象的原型 === 它的构造函数的原型

在对象的原型又叫隐式原型,函数的原型又叫显示原型,所以结论可以改为

对象的隐式原型 === 创建它的构造函数的显式原型

这里可以理解为对象用自己来继承函数里的属性,对象用它的原型承接函数的原型的熟悉

对象可以访问到构造函数的显式原型属性,为什么?

回到最初的购买su7

Car.prototype.product = "xiaomi"

function Car() {
    this.name = "su7"
}

var car = new Car();

console.log(car);
console.log(car.name);
console.log(car.product);

现在我们来讨论为什么console.log(car.product);可以打印出结果?

其实V8引擎在执行这段代码时,首先会去car的显示属性里面寻找,然后再去隐式原型里面找,然后隐式原型又相当于其构造函数的显示原型,这便是原型链

原型链

通过以下代码,我们来讲解一下原型链

<script>
  function A() {}

  let a = new A();

  console.log(a);
</script>

我们通过不断的展开对象的__proto__,我们就能够发现一个事情

动画.gif

从这里我们可以看出来两个信息:

  • a.__proto__ === A.prototype
  • A.prototype.__proto__ === Object.prototype

为了方便理解,我们作图讲解

image.png

在 JavaScript 中,Object.prototype.__proto__ 的值为 nullObject.prototype 处于原型链的顶端,没有更高层次的原型

除了 Object 构造函数,几乎所有构造函数的原型对象最终都会通过原型链追溯到 Object.prototype,以继承一些通用的方法和属性,比如 toStringvalueOf

我们通过一个小demo更加直观的感受一下原型链

function GrandFather() {
  this.age = 60;
  this.like = 'drink';
}

Father.prototype = new GrandFather();
function Father() {
  this.age = 40;
  this.fortune = 1000;
}

Son.prototype = new Father();
function Son() {
  this.age = 20;
}

let son = new Son();

console.log(son.age);
console.log(son.fortune);
console.log(son.like);

首先定义了 GrandFather 构造函数,并设置了属性 agelike

然后将 Father 的原型对象设置为 GrandFather 的一个实例,接着定义了 Father 构造函数,设置了属性 agefortune

之后将 Son 的原型对象设置为 Father 的一个实例,再定义了 Son 构造函数,设置了属性 age

我们可以看到结果为:

image.png

console.log(son.age) 输出 20 ,因为实例自身的 age 属性会覆盖原型链上的同名属性。

console.log(son.fortune) 输出 1000 ,这是从原型 Father 继承的

console.log(son.like) 输出 drink ,这也是从原型链上的 GrandFather 继承的

这里我们可以得出一个结论:

js引擎在查找属性时,会沿着原型链查找。即对象的隐式原型向上查找,找不到就沿着隐式原型的隐式原型继续查找,直到找到Object.prototype._proto__,如果没找到就返回undefined。null为止

网易面试题:世界上所有的对象都有隐式原型吗?

答案是否定的

有一种写法是没有隐式原型的

首先我们来看这段代码

let a = {
    name:'tom'
}

let obj = Object.create(a)

console.log(obj)

这段代码的结果为

image.png

这里我们可以看出来这个方法的作用为创建一个新对象,让新对象隐式的继承a的属性

那如果我们使用let obj = Object.create(null)呢?

直接公布结果:

image.png

这样创建出来的对象是没有隐式原型的

总结

本文深度的解析了原型及原型链,从各个角度出发结合代码去分析过程原理以及结果,并结合面试题深入剖析知识点

相信看到这里的你一定会有所收获的!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值