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

目录
相关文章
|
3天前
|
JavaScript 前端开发
js开发:请解释原型继承和类继承的区别。
JavaScript中的原型继承和类继承用于共享对象属性和方法。原型继承通过原型链实现共享,节省内存,但不支持私有属性。
19 0
|
3天前
|
JavaScript 前端开发
谈谈对 JavaScript 中的原型链的理解。
JavaScript中的原型链是实现继承和共享属性的关键机制,它通过对象的`prototype`属性连接原型对象。当访问对象属性时,若对象本身没有该属性,则会查找原型链。此机制减少内存占用,实现代码复用。例如,实例对象可继承原型对象的方法。原型链也用于继承,子类通过原型链获取父类属性和方法。然而,原型属性共享可能导致数据冲突,且查找过程可能影响性能。理解原型链对JavaScript面向对象编程至关重要。如有更多问题,欢迎继续探讨😊
16 3
|
3天前
|
JavaScript 前端开发 Java
深入JS面向对象(原型-继承)(三)
深入JS面向对象(原型-继承)
31 0
|
3天前
|
JavaScript 前端开发 Java
深入JS面向对象(原型-继承)(一)
深入JS面向对象(原型-继承)
32 0
|
3天前
|
JavaScript 前端开发 安全
JavaScript原型链的使用
【4月更文挑战第22天】JavaScript中的原型链是理解继承的关键,它允许对象复用属性和方法,减少代码冗余。示例展示如何通过原型链实现继承、扩展内置对象、构造函数与原型链的关系以及查找机制。应注意避免修改`Object.prototype`,使用安全方式设置原型链,并谨慎处理构造函数和副作用。
|
3天前
|
JavaScript
JS数组增删方法的原理,使用原型定义
JS数组增删方法的原理,使用原型定义
|
22小时前
|
前端开发 JavaScript
前端 js 经典:原型对象和原型链
前端 js 经典:原型对象和原型链
11 1
|
2天前
|
JavaScript 前端开发
JavaScript 原型链继承:掌握面向对象的基础
JavaScript 原型链继承:掌握面向对象的基础
|
3天前
|
JavaScript 前端开发
在JavaScript中,函数原型(Function Prototype)是一个特殊的对象
【5月更文挑战第11天】JavaScript中的函数原型是一个特殊对象,它为所有函数实例提供共享的方法和属性。每个函数在创建时都有一个`prototype`属性,指向原型对象。利用原型,我们可以向所有实例添加方法和属性,实现继承。例如,我们定义一个`Person`函数,向其原型添加`greet`方法,然后创建实例`john`和`jane`,它们都能调用这个方法。尽管可以直接在原型上添加方法,但推荐在构造函数内部定义以封装数据和逻辑。
18 2
|
3天前
|
JavaScript 前端开发
JavaScript原型链:工作原理与深入探究
【4月更文挑战第22天】JavaScript原型链是对象属性查找的关键,它通过对象间的链接形成链式结构。当访问属性时,JS从对象自身开始查找,若未找到则沿原型链向上搜索,直至`null`。原型链用于继承、扩展内置对象和实现多态,但要注意避免修改内置对象原型、控制链长度及使用`Object.create()`创建对象。理解并合理运用原型链能深化JS面向对象编程的理解。