阅读 137

JavaScript原型系列1: 原型与原型链基础

一、使用构造函数创建对象

Javascript中创建一个对象最简单的方式就是使用字面量的方式

var obj = {}
obj.a = 1
obj.b = 2
复制代码

但是当对象越来越复杂时,这种方式的弊端开始显现:每次创建对象都要写很多重复的代码。因此,我们可以使用构造函数的方式

function F (foo, bar) {
  this.a = 1
  this.b = 2
  this.foo = foo
  this.bar = bar
}

var ins = new F(11, 22)
复制代码

这种方式虽然解决了字面量带来的问题,但也有缺点。可以看出,在每个实例中,属性a和属性b都是相同的,那么能不能将它们独立出来呢,因此,原型的概念产生了。

二、模板VS原型

  1. prototype

针对前文中提到的所有实例共有的公共属性,JS提供了一个定义在构造函数上的属性prototype,指向一个对象。每次创建实例对象时,以prototype作为模板,因此,上面的代码变为:

function F (foo, bar) {
  this.foo = foo
  this.bar = bar
}
F.prototype.a = 1
F.prototype.b = 2

var ins = new F(11, 22)
复制代码

F.prototype称为ins的原型,F.prototype上的属性称为原型属性,ins中的属性称为实例属性

  1. __proto__

那么问题来了,在计算机中,实例属性与原型属性具体是如何处理的呢?最直观的想法就是将Func.prototype拷贝到每个实例中,或者理解为将Func.prototype拷贝一份,然后在这个拷贝上添加实例属性,这样,每个实例就能够访问到原型的属性和方法了,也符合前文中提到的模板的含义。但是这种做法有两个缺点

  1. 浪费内存空间
  2. 各个原型对象是相互独立的,原型属性的变化不能动态表现在实例上

第一点是毫无疑问的,对于第二点,考虑如下的情况:以Func.prototype为模板新建了实例ins1ins2,然后修改了Func.prototype上的一个属性,之后建立了实例ins3。由于每个实例上都有Func.prototype的独立拷贝,因此Func.prototype上的变化不会表现在旧的实例上。

因此针对这个问题,Javascript采用了第二种方式:将原型对象prototype作为一个独立的对象,在实例中通过一个指针来对它进行访问,也就是__proto__。通过这种引用的方式,节省了内存空间,并且每个实例上的__proto__指针都指向同一个原型对象,实现了原型的动态性,即原型上的变化能够反映在每个实例当中。

prototype,常称为构造函数的原型对象,是构造函数Func的一个属性,指向一个对象,也就是子类实例的模板,在new Func()时,会将Func.prototype作为实例ins的模板

__proto__,可以理解为对象的一个指针,该指针指向自己的原型,也就是构造函数的原型对象Func.prototype,即ins.__proto__===Func.prototype

三、原型链

在明白原型的概念后,原型链也就比较简单了。原型链可以理解为一条链表,只是链表一般通过next指针向后查找,而原型链通过__proto__指针向祖先查找。每个构造函数的prototype指向原型对象(模板),每个实例对象的__proto__指向构造函数的prototype

var ins = new Func()
ins.__proto__ === Func.prototype
复制代码

那么问题来了,除环形链表外,链表都是有结尾的,即next指针指向null,那么JS的原型链呢?没错,原型链也是有尽头的,尽头就是Object.prototype。在JS中,每个对象都是Object的一个实例,Func.prototype,可以理解为Func.prototype = new Object(),因此,Func.prototype.__proto__ === Object.prototype

var ins = new Func()
ins.__proto__ === Func.prototype
Func.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null // 到头了!!!
复制代码

其实原型链是非常复杂的,之后还会有文章讲解继承以及函数的原型链部分

文章分类
前端
文章标签