JavaScript重难点突破:原型对象和原型链

什么是原型(原型对象)

先来看这样一个例子。

    function Student (name, sex, age) {
      this.name = name;
      this.sex = sex;
      this.age = age;
    }

    const xiaoming = new Student('小明', '男', '成年');
    const xiaowang = new Student('小王', '男', '成年');

    console.log(xiaoming);
    console.log(xiaowang);

很明显,我们通过构造函数创建了两个对象,xiaowang和xiaoming。他们有各自的姓名、性别、年龄属性。

在js中,通过new关键字创建的对象都独立占用一份内存空间。

这个时候我们回头看xiaowang和xiaoming两个对象,他们除了名字不同,年龄和性别都是完全相同的,并且随着成年男性的同学越来越多,内存中会多存储很多份相同的{sex: '男',age: '成年'}这样的数据。

于是我们思考,是否可以只创建一个{sex: '男',age: '成年'}的根对象放在内存中,然后再让不同name的对象去继承这个根对象,这样就可以大大节省重复的内存。

请添加图片描述

如图所示,我们已经做完了上述的优化,接下来我们想通过某种方式来得到这个根对象{sex: '男',age: '成年'}

那么首先我们想到,所有Student对象都是通过Student这个函数new出来的,于是我们在Student这个函数中创建一个属性prototype,这样就能轻松找到我们的根对象了,这个根对象也就是所谓的原型

由于js中对于普通函数和构造函数没有严格区分,只要使用new关键字,js就试图创建一个对象,因此所有的函数都会拥有prototype属性。

我们再考虑到,所有通过new关键字创造出来的对象(下文将这种对象称为实例对象)都指向这个原型,因此我们对于所有的对象都提供一个[[Prototype]]属性,用于指向这个原型

在最初的设计中,实例对象的原型应该是一个隐藏起来的指针,但各个浏览器为了使用这个[[Prototype]]属性,给开发者提供了__proto__属性,通过这个属性可以访问对象的原型。而在ES 6中,官方也对这种情况做出了妥协,定义了Object.getPrototypeof()Object.setPrototypeof()两个方法来获取这个属性。

知识点:

  • prototype:所有的函数都拥有prototype属性,这个属性是一个指针(内存地址),指向这个函数的原型

  • 原型:原型也是一个对象,因此也被叫做原型对象。所有对象都可以从原型对象中继承(也就是获取)属性和函数。

  • __proto__或者[[Prototype]]:所有的对象都拥有[[Prototype]]属性,这个属性是一个指针(内存地址),指向这个对象的原型。在大多数浏览器中,可以使用__proto__属性来获取[[Prototype]]属性。

    • 需要注意的是__proto__并不是一个官方的属性,而是浏览器自动添加的。

什么是原型链

刚刚我们讲到了原型对象、构造函数、实例对象的三角关系,我们可以通过任何一个来获取另外两个。

    // 构造函数
    function Student (name, sex, age) {
      this.name = name;
      this.sex = sex;
      this.age = age;
    }

    // 构造函数 -> 实例对象
    const xiaoming = new Student('小明', '男', '成年');

    // 构造函数 -> 原型对象
    const constr = Student.prototype

    // 原型对象 -> 构造函数
    console.log(constr.constructor === Student)

    // 实例对象 -> 构造函数
    console.log(xiaoming.constructor === Student)

    // 实例对象 -> 原型对象
    console.log(xiaoming.__proto__ === constr)

    // 原型对象 -> 实例对象 x 不存在

如下图

请添加图片描述

可以看到,原型对象也被叫做对象,所以当然也有[[prototype]]属性,如果构造函数没有显式继承某一个构造函数(extends),那么原型的[[prototype]]属性指向的就是Object.prototype。

如果足够敏锐可以发现,Object.prototype是Object类的原型对象,那么他自然也有[[prototype]]属性,Object.prototype.__proto__ === null

知识点

  • 如果一个子构造函数通过extends关键字,继承(extends)了另一个父构造函数,那么这个子构造函数的原型的[[prototype]]属性(原型),就是父构造函数的原型。(prototype)。

  • 这种通过原型不断继承下来的单向链表,就是所谓的原型链。

举一个例子。请尝试分析以下的三行打印结果分别是什么,尝试运行并得到答案。

    class Animal { }
    class Dog extends Animal { } // 显式继承

    // Dog.prototype 的 [[Prototype]] 指向 Animal.prototype
    console.log(Dog.prototype.__proto__ === Animal.prototype); 

    // Animal.prototype 的 [[Prototype]] 指向 Object.prototype
    console.log(Animal.prototype.__proto__ === Object.prototype); 

    // Object.prototype → null 
    console.log(Object.prototype.__proto__ === null); 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值