对于JavaScript中的原型链,一直很疑惑,看不懂,最近读到一篇文章,觉得豁然开朗。记录一下。关于javaScript为什么会有原型链,可以点击 Javascript继承机制的设计思想讲得非常清楚
普通对象和函数对象
在 JavaScript中除了基本数据类型之外(引用数据类型),都是对象,但是对象也分为普调对象 和 函数对象。Object和Function 是自带的函数对象。
typeof能判断引用数据类型是什么类型的对象,所以我们使用typeof 来判断一下:
1 | const obj1= {}; |
上面中 obj1,obj2,obj3都是普通对象,内置的 Function,Object都是函数对象,f1,f2,f3都是函数对象。
它们之前的区分是什么?
凡是通过
new Function()创建的对象都是函数对象,其他的都是普通对象。
F3 通过 new Function 创建的,所以是函数对象,Object和Function 是自带的函数对象。f1, f2归根到底通过new Function 创建的。可能会觉得f1, f2会觉得有点疑惑。 我们验证一下:
1 | function f1() {} |
构造函数
1 |
|
这个例子中 MyPerson 我们叫构造函数, person1 和 person2 是构造函数 MyPerson 的实例person1 和 person2 这两个实例都有一个 constructor 属性,这个属性是一个指针,指向构造函数 MyPerson
原型对象
在JavaScript中, 每当定义一个对象(函数也是对象)的时候, 对象中都会有一些预定义的属性。其中每个函数对象都会有prototype属性。这个属性指向个对象的原型对象
1 |
|
上面代码中, 我们定义了一个对象(MyPerson),因为是定义的函数对象,所以有prototype属性。这个属性指向对象原型(MyPerson.prototype的就是原型对象 )。
很容易看出原型对象就是一个普通的对象:
1 | function MyPerson() {} |
而普通对象是由构造器 Object构造的, 下面会讲到构造器。
constructor
默认情况下,所有的原型对象都会自动获得一个
constructor属性(构造函数属性),这个属性是一个指针,指向prototype属性所在的函数(MyPerson)
1 | // 定义了一个对象 MyPerson |
在上面的小节中,我们实例的constructor(构造函数属性)指向构造函数person1.constructor === MyPerson。 这两个指向是一样的。
1 |
|
person1为什么会有 constructor 属性? 那是因为person1是MyPerson 的实例。
但是MyPerson的原型对象(MyPerson.prototype)为什么有 constructor 属性? 同理,MyPerson.prototype 应该就是 MyPerson 的一个实例。
也就是说明在定义一个函数对象的时候, 创建了一个它自己的实例对象,并且赋值给它的原型对象。类似如下的过程:
1 | var A = new Person(); |
那么我们可以得到这样结论:
原型对象是构造函数的一个实例
注意:如果我们改写了 对象原型,那么原型的constructor就不是MyPerson了, 已经被改写了
Function.prototype
以上我们可以知道,原型对象是一个普通的对象,但是 Function.prototype是一个例外,它不是普通对象而是函数对象? 并且它没有prototype属性(前面讲过:函数对象都有prototype属性)为什么?
根据上面constructor这个小节可知:原型对象是构造函数的一个实例。那么Function.prototype就是 Function的一个实例。类似下面这样的过程:
1 | var A = new Function (); |
而上面我们提到凡是通过 new Function() 产生的对象都是函数对象。上面的A是函数对象,所以Function.prototype是一个函数对象。
原型的作用
那原型对象是用来最什么的呢? 它的主要做作用是是继承。
1 | function MyPerson(name, age) { |
上面代码中, 我们改了 MyPerson的原型,使其有一个 getName方法。person1是MyPerson的一个实例。这个实例继承了对象原型的方法。
关于是如何继承, 我们就需要讲到下面的原型链了。
_proto_
JavaScript在创建对象的时候(不论是函数对象还是普通对象),都有一个叫做__proto__属性。这属性指向创建它的构造函数的原型对象。
那根据以上全部我们可以得到 一个实例与它构造函数,构造函数的原型对象之间的关系如下:
1 | // 定义了一个对象 MyPerson |
可以得出很重要的一点:实例与构造原型对象之间是通过__proto__来进行链接
构造器
我们创建一个对象的时候:
1 | var obj = {}; |
它等同于
1 | var obj = new Object(); |
obj 是构造函数 Object 的一个实例; 所以:
1 | obj.constructor === Object |
新对象obj是使用new操作符后跟一个构造函数来创建的。构造函数Object 本身就是一个函数对象。它和我们之前的构造函数MyPerson是差不多的。只不多这个函数是出自于创建新对象的目的而定义的。
同理,可以创建对象的构造器不仅仅有Object还有其他的构造器如:Array,Date,Function等。
用不同的构造器来创建不同的对象:
1 |
|
而这些构造器都是函数对象:
所以我们自己写构造函数( MyPerson)和构造器都会有Function构造的。
原型链
总结一下全部的知识点
每个函数对象都有
prototype属性,指向自己的原型、每个对象都有
__proto__属性, 指向自构造自己的构造函数的的原型。构造函数和构造器一样都是函数对象,是由
Function构造的。所以构造函数的原型的__proto__属性指向Function.prototype。而构造函数的原型也是对象, 也有
__proto__属性,构造函数的原型都是普通对象。所以指向Object.prototype而
Function.prototype也是一个对象,也有__proto__属性。在JavaScript中万物皆对象。所以Function.prototype是由Object构造的。所以Function.prototype的__proto__属性指向Object的原型。prototype`。1
Function.prototype.__proto__ === Object.prototype; // true
Object.prototype也是一个对象,也有__proto__属性。但是它指向null, 指原型链的终点。
而上面的对象也原型之前是通过__proto__来进行链接。这样就构成了原型链。
根据以上内容,我们来做做下面的测试:
1 | function MyPeople() {}; |
person1.__proto__ === ?MyPeople.__proto__ === ?Person.prototype.__proto__ === ?Object.__proto__ === ?Function.prototype === ?Object.prototype.__proto__ === ?
person1.__proto__ === ?:每个对象都有
__proto__属性,指向当前对象的构造函数的原型,person1的构造函数是MyPeople。所以:person1.__proto__ === MyPeople.prototypeMyPeople.__proto__ === ?:Person也是一个对象, 所以它也有__proto__属性, 指向当前对象的构造函数的原型。我们自己写的构造函数和自带的构造器是一样的都是函数对象,所以MyPeople的构造函数是Function。所以:MyPeople.__proto__ === Function.prototype。Person.prototype.__proto__ === ?:构造函数的原型也是一个对象,也有
__proto__属性。构造函数的原型都是普调对象。普通对象的话那就是由构造器Object构造的。 所以:Person.prototype.__proto__=== Object.prototypeObject.__proto__ === ?构造器
Object也是一个对象,也有__proto__属性。 构造器跟我们自己写的构造函数一样, 都是函数对象,所以Object是由Function构造的。 所以:Object.__proto__ === Function.prototype。Function.prototype === ?Function.prototype也是一个对象。但是它比较特,他是函数对象(前面讲过构造函数的原型就是一个普通函数)。并且它没有prototype属性。在JavaScript中万物皆对象。所以Function.prototype是由Object构造的。所以Function.prototype__proto__ === Object.prototype。Object.prototype.__proto__ === ?Object.prototype也是一个对象,也有__proto__属性。但是它比较特殊,它为null,null为原型链的顶端。
测试结果:
1 | function MyPeople() {}; |
所以现在来看这个原型链图应该明白很多:
也可以对比网上的图
参考文章:
