携手创作,共同成长!这是我参与「掘金日新计划 · 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)
通过两个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级能否自动推荐到首页了,周五测试了两篇不行,期待已久的功能。