原型和原型链

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

后端语言比如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>

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

??? 这是为什么呢

我们把Obj对象打印出来

console.log('Obj', Obj);

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

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

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

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

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

修改一下代码,给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>

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

上段代码中,我们给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)

题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

题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

题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

题5:

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

在这里插入图片描述

六、总结

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

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

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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值