都2023年了,我不允许你还搞不懂原型链!
概念:原型链:就是实例对象和原型对象之间的链接,每一个对象都有原型,原型本身又是对象,原型又有原型,以此类推形成一个链式结构.称为原型链
1.直奔主题,我相信绝大多数小伙伴都看过下面这张图
通过上述的概念+图片,我相信觉大多数小伙伴脑袋是嗡嗡嗡的!其实网上的很多博客,知乎,论坛都把一个简单的知识搞复杂了。再加上面试官常年的追问,把该知识点变成了JavaScript语言里面一座难以逾越的大山!
闲言少叙,首先我们来想一下对象有几种创建方式? 有的小伙伴就开始发问了:你刚说要解释原型链,咋一下又扯到了对象身上? 伙伴们,文章刚开始概念里面第一句是不是就讲了:对象和原型对象之间的链接啊,所以咱就先从创建对象入手呗!
如下:
function Obj(){}
let obj1 = new Obj() //第一种创建对象方式
class Obj{}
let obj2 = new Obj() //第二种创建对象方式
let obj3 = new Object() //第三种创建对象方式
let obj4 = {} //第四种创建对象方式
JavaScript 总共就这4种创建对象方式,对嘛? 如果再有扩展,基本也就是基于上述的方式方法。
我们先讲第一种,通过构造函数创建一个对象。我们都知道不管是以哪种形式创建出来的对象,对象里面都会有一个__proto__, 翻译成中文:杠杆破芋头
咱们这篇文章讲的就是这个__proto__,关联哪个东西?
这里咱不多逼逼,直接告诉你们一句话:对象是谁创建(构建)出来的,它上面的杠杆破芋头(_proto_)。就指向谁的prototype 如下代码
function Obj(){}
let obj = new Obj() //第一种创建对象方式
console.log(obj.__proto__ === Obj.prototype) //true
此时小伙伴们,就要理解 obj 是通过new Obj()创建的对象,那么它的杠杆破芋头(_proto_) 就是指向 Obj.prototype。
同理,那么通过ES6 class关键字new出来的对象,自然也与第一种是一样的!
class Obj{}
let obj = new Obj() //第二种创建对象方式
console.log(obj.__proto__ === Obj.prototype) //true
咱再来看下第三种,new Object() 这个就比较特殊。Object是JavaScript内置的构造函数,前面的构造函数是我们手动创建的 function Obj(){} , 这个Object相当于JavaScript这门语言,内部就帮你创建好了。你直接new就行!
let obj = new Object() //第三种创建对象方式
console.log(obj.__proto__ === Object.prototype) //true
那么最后一种 let obj = {} 直接这么等于一个花括号,是一种字面量的形式,相当于new Object() 语法糖吧!。
现在我们只要搞清楚一个概念:通过new构造函数创建出来的对象,它上面的杠杆破芋头(_proto_)。就指向该构造函数的prototype
其实讲到这里原型链你就已经学会了,好多小伙伴就要问了,不对啊。上面那种图那么复杂,箭头左指右指,从下到上。你就这么两句话讲完了?
我知道伙伴们疑虑在哪?就拿自定义Obj这个构造函数来说,伙伴们是在想知道 Obj.prototype._proto_ 这个杠杆破芋头(_proto_) 又指向哪?
这里咱先看一下浏览器打印
这里可以很清楚的看到这个玩意。首先咱不管它里面的东西。咱就看这个东西是个啥? 很明显,它是不是一个对象啊?
不相信的小伙伴可以跑到浏览器再次打印。确实它是一个对象
console.log(typeof Obj.prototype.__proto__) //object
那么问题就好解决了呀! 它是对象,那它的构造函数是谁呢? 这里很明确的告诉大家,JavaScript 这门语言中一切系统帮你定义或者构造出来的对象,它的构造函数都是Object
讲到这里原型链慢慢的就通了,如下
function A(){} //自定义一个构造函数A
let a = new A()// 创建一个对象
console.log(a.__proto__ == A.prototype) //a是由A创建的,自然这个等式成立:true
console.log(A.prototype.__proto__ == Object.prototype) //true
根据上面的代码,咱们总结一句话:根据文章开头的概念,每一个对象都有原型(_proto_)。然而小a,是由大A创建出来的一个对象,那么它的a._proto_ === A.prototype ,再然而 prototype 也是一个对象,它也有原型(_proto_)。但是它是系统帮你构建出来的, 根据JavaScript一切系统帮你定义或者构造出来的对象,它的构造函数都是Object理念。所以A.prototype._proto_ == Object.prototype
最后 a._proto_ == A.prototype._proto_== Object.prototype 这种查找方式就叫原型链
讲到这里,小伙伴又可能会追问 Object.prototype._proto_ 又指向哪呀?
这里不多赘述,直接看打印结果吧。 它已经到顶了。为null
console.log(Object.prototype.__proto__) //null
文章讲到这里,前面铺垫了那么多。无非就是想讲清楚一个事。
原型链就是根据__proto__ 这个属性,逐层先上查找。直到找不到为止
2.__proto__有啥作用呀?
很多知识点,都是需要透过现象看本质。既然我们已经了解了它的特性。那么下面就讲一下它具体作用是啥!
它其实有两个作用:共享与继承
首先讲一下,啥叫共享。
function Obj(name){ //自定义一个构造函数A
this.name = name
this.fn = function(){
console.log('我是一个方法')
}
}
let a = new Obj('A')// 创建一个A对象
let b = new Obj('B')// 创建一个B对象
let c = new Obj('C')// 创建一个C对象
console.log(a,b,c) //打印出来看效果
咱们可以很清楚的看到,每个对象里面都有一个函数。此时小伙伴就说了,这有什么问题嘛?本来你代码就这么写的呀!
确实,代码是这么写的。但是小伙伴们。 每次这么写代码会不会造成一定的内存开销呀。 因为每次创建一个对象的同时,对象里面又创建了一个函数! 所以为了降低开销,提升性能的同时效果要达到一毛一样。咱把它改造一下!
function Obj(name){ //自定义一个构造函数A
this.name = name
}
Obj.prototype.fn = function(){
console.log(this.name)
}
let a = new Obj('A')// 创建一个A对象
let b = new Obj('B')// 创建一个B对象
let c = new Obj('C')// 创建一个C对象
console.log(a,b,c) //打印出来看效果
此时可以看到,每一个对象里面还有函数嘛?
小伙伴们说确实没有了!但是我要调用咋办?
那要调用就直接调用呗:
function Obj(name){ //自定义一个构造函数A
this.name = name
}
Obj.prototype.fn = function(){
console.log(this.name)
}
let a = new Obj('A')// 创建一个A对象
let b = new Obj('B')// 创建一个B对象
let c = new Obj('C')// 创建一个C对象
console.log(a.fn()) //A
console.log(b.fn()) //B
console.log(c.fn()) //C
小伙伴又疑惑了,对象打印出来看了。 对象上面明明没定义 fn 函数。 但是这么调用也能看到它的结果?
其实这就又引出一个概念,原型链的查找规则
function Obj(name){ //自定义一个构造函数A
this.name = name
this.fn = function(){
console.log(this.name,'1')
}
}
Obj.prototype.fn = function(){
console.log(this.name,'2')
}
let a = new Obj('A')// 创建一个A对象
a.fn()
此时小伙伴们可以看到,打印出来的是:A,1
所以我们原型链的查找规则就是就近原则,对象上面有,就先取对象的,没有则根据__proto__找到它的构造函数,然后在它构造函数的prototype上面取!所以如果上面把对象里面的fn 删掉。 则打印:A,2
function Obj(name){ //自定义一个构造函数A
this.name = name
}
Obj.prototype.fn = function(){
console.log(this.name,'2')
}
let a = new Obj('A')// 创建一个A对象
a.fn() //A,2
所以总结一下:这里讲了两个概念第一要节省内存开销,公用的方法统一定义在原型。第二原型链的查找规则是就近原则
3.巧用继承
上面已经讲了,原型链的查找规则。那么我们可以通过该规则去实现一个继承。直接看代码
function Parent() { //自定义一个构造函数作为继承使用
this.name = '继父'
this.Parentfn = function () {
console.log('我是你的继父')
}
}
function Obj() { //自定义一个构造函数A
this.name = '儿子'
}
Obj.prototype.fn = function () {
console.log('儿子函数')
}
Obj.prototype = {
...Obj.prototype,
...new Parent()
}
let a = new Obj()// 创建一个A对象
a.Parentfn() //我是你的继父
a.fn() //儿子函数
咱最开始的时候就说了Obj构造函数上面的prototype是一个对象,那么我们通过new Parent()与它原有的prototype进行一个合并。从而使Obj创建出来的对象实现继承效果!
好了,文章到这里又接近尾声了。咱主要目的是搞懂什么是原型链,其次通过原型链的作用,回过头再次加深印象!从而在真正实际开发中灵活运用原型链这一特性。
小伙伴们,那你们学会了吗? 有疑问评论区留言哦!
转载自:https://juejin.cn/post/7234803427041247290