• 原型和原型链


    一、为什么必须要学会原型和原型链

    后端语言比如Java等,都是面向对象的开发方式,面向对象有许多特点,其中继承就是其中一个,在Java中通常通过类class的方式实现继承。

    而JS是一门基于对象的语言,它不是一门真正的面向对象编程的语言,虽然ES6提出了class编程的方式,但是它始终是一个语法糖,与Java中的类class不同,JS中的class编译之后其实就是一个函数。那么JS如何实现继承呢?

    原型和原型链就非常巧妙的解决了JS中继承的问题。

    二、原型和原型对象

    • 每个函数都有prototype属性,这个属性称为原型,也被称为“显式原型”

    • prototype原型指向一个对象,这个对象被称为原型对象

    三、prototype和__proto__

    很多友友会把protptype和__proto__混为一谈,但是其实这两个是两个维度的东西。

    • prototype的维度是函数
    • __proto__的维度是对象,__proto__是每个对象都有的属性,我们通常把它称为隐式原型

    因为函数也是一个对象,所以函数既有prototype属性还有__proto__属性。

    而对象只有__proto__属性。

    注:[[prototype]]就是__proto__

    四、原型链

    上边我们介绍了prototype显式原型和__proto__隐式原型,那他们之间有什么关系呢?

    话不多说,看代码:

     <script>
            function Student() {
            }
    
            // 给Student函数的显式原型上添加属性和方法
            Student.prototype.name = '张三';
            Student.prototype.say = function(){
                console.log('hello')
            }
    //使用new关键词创建一个Student函数的实例对象
            const Obj = new Student();
      
            console.log(Obj.name)
            Obj.say();
        </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    从代码上看,Obj对象上并没有name属性和say方法,先不着急,我们按照上述代码运行一下。
    在这里插入图片描述
    我们发现输出的结果是Student原型上的内容

    ??? 这是为什么呢

    我们把Obj对象打印出来

    console.log('Obj', Obj);
    
    • 1

    在这里插入图片描述
    我们发现其实Obj对象上其实没有name属性和say方法,但是在它的隐式原型[[prototype]]上有name和say,而且我们发现Obj的[[prototype]]中的constructor指向它的构造函数Student。

    做个猜想:Obj的隐式原型__proto__和构造函数Student的显式原型prototype是相等的

    为了验证猜想,我们试着打印一下:

     console.log(Obj.__proto__ === Student.prototype);
    
    • 1

    在这里插入图片描述
    果然相等

    修改一下代码,给obj对象上添加一个name属性,看能输出什么:

    <script>
            function Student() {
                this.name = "李四"
            }
    
            // 给Student的显式原型上添加属性和方法
            Student.prototype.name = '张三';
            Student.prototype.say = function(){
                console.log('hello')
            }
    
            const Obj = new Student();
            console.log('Obj', Obj);
            console.log(Obj.name)
            Obj.say();
        </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    结果如图:
    在这里插入图片描述
    我们看到打印的是李四

    上段代码中,我们给Obj对象添加了自己的name属性,这个时候输出的就是Obj自带的name属性。

    由此我们可以想到,Obj对象想要获取name或者say(),首先判断自己的属性当中有没有,如果没有,那么就在__proto__隐式原型上找,而这个时候__proto__和Student的显式原型prototype是相等的,也就是__proto__指向Student,那么就可以找到name和say()。

    对上段再进行扩展:既然函数的prototype是一个对象,那么它必然有__proto__属性,当我们在函数的原型上没有找到的时候,就会继续查找prototype的__proto__,以此下去,直到找到或者__proto__没有指向某个构造函数为止。

    这样一层一层向上查找就会形成一个链式结构,这个链式结构就是我们所说的原型链。

    我们可以把原型链拆开来理解,原型和链:

    • 原型就是我们的prototype
    • 链就是__proto__,将整个链路连接起来

    附上经典原型和原型链图:

    在这里插入图片描述

    五、原型和原型链的经典练习题

    题1:

     function A(){}
        A.prototype.n = 1
        var b = new A()
        
         //这一步修改了A.prototype的地址值,改变了A.prototype的指向 
        A.prototype = {
            n:2,
            m:3
        }
         //实例对象c的指向和此时改变后的A.prototype的指向一致
        var c = new A()
         //实例对象b的指向并没有被改变
        console.log(b.n,b.m,c.n,c.m) //1 undefined 2 3
    
    //解释:
    1. b的原型链:b.__proto__ => A.prototype(n属性),此时A.prototype的地址并没有改变,所以A.prototype的属性只有n,打印结果为1undefined2.  c的原型链:c.__proto__ => A.prototype(新的A.prototype,含有属性n,m)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    题2:

     var F = function(){}
        Object.prototype.a = function(){
            console.log('a')
        }
        Function.prototype.b = function(){
            console.log('b')
        }
        var f = new F();
     //解释:
     1. f的原型链:f是F的实例对象,f.__proto__ => F.prototype(是一个空的Object对象).__proto__ => Object.prototype(a属性).__proto__ => null,没有找到b属性,所以打印TypeError:f.b is not a function。 
        f.a();//a
        f.b();//TypeError:f.b is not a function
    //解释:
    2.F的原型链:F是一个构造函数,所有的函数都是由Function new出来的,所以F.__proto__ => Function.prototype(b属性).__proto__ => Object.prototype(a属性).__proto__ => null
        F.a();//a
        F.b();//b
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    题3:

    var foo = {},
        F = function(){};
    Object.prototype.a = 'value a';
    Function.prototype.b = 'value b';
    //解释:
    foo的原型链:foo是一个对象,foo._proto_ => Object.prototype(a属性)._proto_ => null,没有b属性
    console.log(foo.a);//value a
    console.log(foo.b);//undfined
    //解释:
    F的原型链:F是一个函数,所有的函数都是由Function new出来的,所以F._proto_ => Function.prototype._proto_(b属性) => Object.prototype._proto_(a属性) => null
    console.log(F.a);//value a
    console.log(F.b);//value b
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    题4:

    function A() {}
    function B(a) {
    //this指向实例对象,相当于给实例对象自身添加属性
        this.a = a;
    }
    function C(a) {
        if (a) {
        //this指向实例对象,相当于给实例对象自身添加属性
            this.a = a;
        }
    }
    A.prototype.a = 1;
    B.prototype.a = 1;
    C.prototype.a = 1;
    //解释:首先,我们知道,如果在实例对象自身可以找到某个属性,那么就不会继续查找该实例对象的隐式原型
    1. new A()是一个实例对象,原型链为:new A()._proto_ => A.prototype._proto_(找到a属性) => Object.prototype._proto_ => null
    2.  new B()是一个实例对象,原型链为:new B()(找到a属性)._proto_ => B.prototype._proto_ => Object.prototype._proto_ => null,该实例对象自身有a属性,但是我们调用的时候并没有传a的值,所以为undefined
    3. new C()是一个实例对象,原型链为:new C()(找到a属性)._proto_ => C.prototype._proto_ => Object.prototype._proto_ => null,该实例对象自身有a属性,且调用的时候传了a的值,为2,所以打印结果是2
    
    console.log(new A().a); //1
    console.log(new B().a); //undefined
    console.log(new C(2).a); //2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    题5:

    //解释:
    123后边调用toString方法会使javaScript产生一个原始包装对象,将123包装成Number对象类型的值,而Number.prototype.toString.length === 1,所以最后的打印结果使124
    console.log(123['toString'].length + 123) // 124
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    六、总结

    以上就是原型和原型链的知识啦!

    利用原型链这种链式查找的方法,我们就可以巧妙地实现继承!

    下一篇,我们来讲讲继承!

  • 相关阅读:
    《诊断服务》禁止功能寻址
    volatile类型变量提供什么保证?
    C++标准模板(STL)- 类型支持 (类型特性,is_void,is_null_pointer,is_integral)
    redis的安装、基础命令及常用数据结构
    微服务实战微服务网关Zuul入门与实战
    关于Vue中npm install出现报错及解决方法
    (科目三)数据库基础知识
    搜索二叉树实现(非递归版本)
    vue3 异步组件
    数据分析基础入门_环境安装
  • 原文地址:https://blog.csdn.net/weixin_52148548/article/details/125287450