JavaScript基础系列(7):原型和原型链,我是这么理解的

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

image.png


六月份写过一篇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 === p3.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 === p3.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加入),结合代码和指向图或者你会搞清楚。


image.png

image.png


<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级能否自动推荐到首页了,周五测试了两篇不行,期待已久的功能。

目录
相关文章
|
2月前
|
JavaScript 前端开发 开发者
理解JavaScript中的原型链:基础与实践
【10月更文挑战第8天】理解JavaScript中的原型链:基础与实践
|
1月前
|
JavaScript 前端开发
JavaScript中的原型 保姆级文章一文搞懂
本文详细解析了JavaScript中的原型概念,从构造函数、原型对象、`__proto__`属性、`constructor`属性到原型链,层层递进地解释了JavaScript如何通过原型实现继承机制。适合初学者深入理解JS面向对象编程的核心原理。
26 1
JavaScript中的原型 保姆级文章一文搞懂
|
4月前
|
JavaScript 前端开发
如何在JavaScript中实现基于原型的继承机制
【8月更文挑战第14天】如何在JavaScript中实现基于原型的继承机制
33 0
|
1月前
|
JavaScript 前端开发
JavaScript 原型链的实现原理是什么?
JavaScript 原型链的实现原理是通过构造函数的`prototype`属性、对象的`__proto__`属性以及属性查找机制等相互配合,构建了一个从对象到`Object.prototype`的链式结构,实现了对象之间的继承、属性共享和动态扩展等功能,为 JavaScript 的面向对象编程提供了强大的支持。
|
1月前
|
JavaScript 前端开发
原型链在 JavaScript 中的作用是什么?
原型链是 JavaScript 中实现面向对象编程的重要机制之一,它为代码的组织、复用、扩展和多态性提供了强大的支持,使得 JavaScript 能够以简洁而灵活的方式构建复杂的应用程序。深入理解和熟练运用原型链,对于提升 JavaScript 编程能力和开发高质量的应用具有重要意义。
|
1月前
|
JavaScript 前端开发
如何使用原型链继承实现 JavaScript 继承?
【10月更文挑战第22天】使用原型链继承可以实现JavaScript中的继承关系,但需要注意其共享性、查找效率以及参数传递等问题,根据具体的应用场景合理地选择和使用继承方式,以满足代码的复用性和可维护性要求。
|
2月前
|
JavaScript 前端开发 开发者
探索JavaScript原型链:深入理解与实战应用
【10月更文挑战第21天】探索JavaScript原型链:深入理解与实战应用
37 1
|
2月前
|
JavaScript 前端开发 开发者
深入理解JavaScript原型链:从基础到进阶
【10月更文挑战第13天】深入理解JavaScript原型链:从基础到进阶
35 0
|
2月前
|
JavaScript 前端开发 开发者
原型链深入解析:JavaScript中的核心机制
【10月更文挑战第13天】原型链深入解析:JavaScript中的核心机制
41 0
|
2月前
|
JavaScript 前端开发 安全
深入理解JavaScript原型链:从基础到进阶
【10月更文挑战第13天】深入理解JavaScript原型链:从基础到进阶
30 0