JavaScript基础系列第七篇:原型和原型链,我是这么理解的

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

六月份写过一篇V8是如何运行JavaScript(let a = 1)代码的?,写完之后我就发现,我对平常使用的工具V8引擎,偏底层的知识了解的竟然是如此甚少。同时我真正从事前端的时间还算是比较短的,那么基础也算是非常的薄弱。结合以上,我打算有时间就去从底层的角度去学习了解,便于在使用过程中的理解和解决遇到的问题,理解JavaScript的本质,能够更好的学习JavaScript。如果你跟我有同样的困惑,那我们可以结伴同行,共同学习。

本系列我会从我的视角不断的去总结:

前言

通过本文可以学习到以下知识点:

1、了解两种原型
2、以及两种原型的区别
3、了解原型链的查找规则
4、对JavaScript继承有一定的认识

1、通过demo引出prototype

<script>
function Person(name, age) {
  this.name = name
  this.age = age
  this.country = '中国'
  this.say = function() {
    console.log('开始说话吧')
  }
}

const p1 = new Person('京东', 21)
const p2 = new Person('阿里', 23)
const p3 = new Person('腾讯', 22)
</script>

通过new Person 创建了三个人,也就是三个对象。每个对象都占用了一块空间。

现在假设所有人员都是中国人,也就是国籍的属性始终就是中国,是固定的,不需要进行参数传递。

同时还有一个say函数方法,没有传递任何的数据,直接输出。

那现在我们创建的三个对象上都有相同国籍属性和say函数。通过控制台直接打印

p1.say === p2.say   // false
p1.say === p2.say   // false

从运行结果可以说明三个对象p1、p2、p3上的say方法是不相同的,分别占用了三块内存。

如果我们将数据量放大,同时将类似于国籍这样的属性或者类似于say函数也增多,其实就是这些相同的属性或者相同的方法,在每个对象上都占用了同样的内存,着实有些浪费。

那么有没有一种方式,将这些相同的属性或者方法进行提取封装成这些对象的公共部分呢?

<script>
function Person(name, age, country) {
  this.name = name
  this.age = age
}

Person.prototype.country = '中国'
Person.prototype.say = function (content) {
  console.log(`${content}`)
}
const p1 = new Person('京东', 21)
const p2 = new Person('阿里', 23)
const p3 = new Person('腾讯', 22)

console.log(p1.country)
p1.say('p1说的是英文')
console.log(p2.country)
p2.say('p2说的是中文')
console.log(p3.country)
p3.say('p3说的是日文')

</script>

打印结果如下

中国
p1说的是英文

中国
p2说的是中文

中国
p3说的是日文

这里先看通过prototype设置了一个country属性和一个say方法。say是一个函数对象直接在控制台进行输出

p1.say === p2.say    // true
p1.say === p2.say    // true

从运行结果可以看出,最起码p1.say、p2.say、p3.say三个函数对象引用是一样的,没有造成内存的浪费。

总结:这里通过prototype将对象上公有的属性和方法进行提取封装,然后在其实例上进行调用访问。

2、对prototype的理解

简单理解:每一个函数都天生具备一个内置属性:prototype(原型),prototype的属性值是一个对象。这个对象上包含了所有的共有属性和方法,能够供其实例(通过构造函数产生的实例,即 new xxx())进行调用访问。

箭头函数是一类特殊的函数,不存在prototype内置属性。\

同时注意,我上面说的是每一个函数都有一个内置属性prototype
而普通的对象是不存在prototype的。

let P = {
  a: 1
}
console.log(P.prototype)   //undefined

你可以通过控制台去打印看看是undefined。但是普通对象有另外一个内置属性__proto__

let P = {
  a: 1
}
console.log(P.__proto__ )
console.log(P.__proto__ === Object.prototype)

image.png

通过两个console.log打印我们可以发现,原来普通对象的__proto__竟然跟Object.prototype是相等的。

__proto__又是何方神圣呢?

它们两个竟然还可以比较,那__proto__prototype又存在什么关系吗?

带着这两个疑问我们接着往下看。

3、对__proto__的理解

先说一下对___proto__的简单理解:每一个对象上都有一个__proto__属性,属性值是当前实例所属类的prototype原型。函数也是对象中的一种,所以函数也是存在__proto__属性的。

对__proto__与prototype的总结:

prototype:显示原型,每个函数(非箭头函数)都有的内置属性。\

__proto__:隐士原型,每个对象都有的内置属性。

再来看一个小例子加强一下理解

function P () {

}
const p = new P()
console.log(P.prototype)
console.log(p.__proto__ )
console.log(p.__proto__ === P.prototype)
</script>

运行结果如下

image.png

通过最简单的例子可以发现:小p实例对象的__proto__ 与大P函数对象的prototype是相等的。

换一种说法便是:实例对象的隐式原型 === 函数对象的显式原型。这里前提是针对函数的,因为普通对象是不存在prototype

4、对原型链的理解

原型链,其实上面我们也使用过,就是通过__proto__来查找我们的父级,查找过程有点像作用域链的查找过程。

一图胜千言(为了让指向图更简单一些,暂时没有将构造函数constructor加入),结合代码和指向图或者你会搞清楚。

无标题 (2).jpg

<script>
function Fun () {

}
const fun = new Fun()

//(1--0)
// fun是对象,不存在prototype,所以为undefined
console.log(fun.prototype, '小fun的prototype显示原型')   

//(1--5)
// fun.__proto__  指向 Fun.prototype
console.log(fun.__proto__ , '小fun 的__proto__隐式原型')
console.log(fun.__proto__ === Fun.prototype)

//(5--7)
// Fun.prototype.__proto__(现在是一个对象) 指向Object.prototype
console.log(Fun.prototype.__proto__ , '大Fun、prototype的__proto__隐式原型')
console.log(Fun.prototype.__proto__ === Object.prototype)

// (7--8)
// Object.prototype.__proto__ (终点指向)  指向了 null
console.log(Object.prototype.__proto__, 'Object.prototype.的隐式原型') 


// (2--6)
// (3--6)
//(4--6)
// Fun.__proto__、 Function.__proto__、Object.__proto__ 都指向了  Function.prototype
console.log(Fun.__proto__ , '大Fun的__proto__隐式原型')
console.log(Function.__proto__, 'Function.__proto__隐式原型')
console.log(Object.__proto__, 'Object.__proto__隐式原型')

console.log(Fun.__proto__ === Function.prototype)
console.log(Function.__proto__ === Function.prototype)
console.log(Object.__proto__ === Function.prototype)

//(6--7)
// Function.prototype.__proto__(现在是一个对象) 指向Object.prototype
console.log(Function.prototype.__proto__ , 'Function.prototype.__proto__隐式原型')
console.log(Function.prototype.__proto__ === Object.prototype)

//(7--8)
// 上面写过,参考上面的

</script>

5、最后小结

  • Fun、Function、Object都有构造函数,所以它们的__proto__隐式原型,都为Function.prototype。

  • 原型链的查找过程是通过__proto__。

  • 原型链的查找都会找到Object.prototype,然后Object.prototype.__proto__指向则为null。

  • 原型链的查找终点都是null。

  • JavaScript的继承就是通过原型和原型链来实现的,比起那些面向对象的语言来说,理解起来好像也没那么难。

  • JavaScript万物皆对象,所有的对象都有自己的__proto__,但对象是不存在prototype的。

  • JavaScript所有函数,都有prototype的。

顺便试试4级能否自动推荐到首页了,周五测试了两篇不行,期待已久的功能。

猜你喜欢

转载自juejin.im/post/7126061672032108551