六月份写过一篇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)
通过两个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>
运行结果如下
通过最简单的例子可以发现:小p实例对象的__proto__ 与大P函数对象的prototype是相等的。
换一种说法便是:实例对象的隐式原型 === 函数对象的显式原型。这里前提是针对函数的,因为普通对象是不存在prototype
。
4、对原型链的理解
原型链,其实上面我们也使用过,就是通过__proto__
来查找我们的父级,查找过程有点像作用域链的查找过程。
一图胜千言(为了让指向图更简单一些,暂时没有将构造函数constructor
加入),结合代码和指向图或者你会搞清楚。
<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级能否自动推荐到首页了,周五测试了两篇不行,期待已久的功能。