likes
comments
collection
share

都2023年了,我不允许你还搞不懂原型链!

作者站长头像
站长
· 阅读数 40

概念原型链:就是实例对象和原型对象之间的链接,每一个对象都有原型,原型本身又是对象,原型又有原型,以此类推形成一个链式结构.称为原型链

1.直奔主题,我相信绝大多数小伙伴都看过下面这张图

都2023年了,我不允许你还搞不懂原型链!

通过上述的概念+图片,我相信觉大多数小伙伴脑袋是嗡嗡嗡的!其实网上的很多博客,知乎,论坛都把一个简单的知识搞复杂了。再加上面试官常年的追问,把该知识点变成了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_) 又指向哪?

这里咱先看一下浏览器打印

都2023年了,我不允许你还搞不懂原型链!

这里可以很清楚的看到这个玩意。首先咱不管它里面的东西。咱就看这个东西是个啥? 很明显,它是不是一个对象啊?

不相信的小伙伴可以跑到浏览器再次打印。确实它是一个对象

console.log(typeof Obj.prototype.__proto__) //object

都2023年了,我不允许你还搞不懂原型链!

那么问题就好解决了呀! 它是对象,那它的构造函数是谁呢? 这里很明确的告诉大家,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)  //打印出来看效果

都2023年了,我不允许你还搞不懂原型链!

咱们可以很清楚的看到,每个对象里面都有一个函数。此时小伙伴就说了,这有什么问题嘛?本来你代码就这么写的呀!

确实,代码是这么写的。但是小伙伴们。 每次这么写代码会不会造成一定的内存开销呀。 因为每次创建一个对象的同时,对象里面又创建了一个函数! 所以为了降低开销,提升性能的同时效果要达到一毛一样。咱把它改造一下!


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)  //打印出来看效果

都2023年了,我不允许你还搞不懂原型链!

此时可以看到,每一个对象里面还有函数嘛?

小伙伴们说确实没有了!但是我要调用咋办?

那要调用就直接调用呗:


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
评论
请登录