一、JavaScript为什么要设计原型对象
构造函数其实就是普通函数,但是其内部使用了this变量。对构造函数使用new运算符,就能生成实例对象。
但是使用构造函数生成实例对象有一个缺点,就是无法共享属性和方法。每一个实例对象都是构造函数的副本,虽然内容相同但是没有任何关联。进一步说,对一个实例进行更改,其他实例不会发生变化。
function Foo () {
this.name = "cat";
}
var f1 = new Foo();
var f2 = new Foo();
f1.name = "dog";
console.log(f1.name);
console.log(f2.name);
打印结果显示,f2中的name属性并没有被改变。
因此为了能让实例对象相互之间进行通信,方便共享数据,JavaScript中引入了原型这一概念。
函数都有原型对象属性(即prototype):该原型对象保存着所有实例对象的公共属性和方法。
1)实例会继承构造函数的原型对象中的属性
Foo.prototype = {name: "cat"};
function Foo () {};
var f1 = new Foo();
var f2 = new Foo();
console.log(f1.name); //cat
console.log(f2.name); //cat
我们在Foo.prototype中定义了name属性,属性值为“cat”。结果显示,该构造函数构造出的实例都继承了name属性。
2)更改原型对象上的属性,所有实例中的属性都会跟着改变
Foo.prototype = {name: "cat"};
function Foo () {};
var f1 = new Foo();
var f2 = new Foo();
Foo.prototype.name = "dog";
console.log(f1.name); //dog
console.log(f2.name); //dog
3)我们可以通过实例来修改构造函数原型对象中的属性
Foo.prototype = {name: "cat"};
function Foo () {};
var f1 = new Foo();
var f2 = new Foo();
f1.__proto__.name = "dog";
console.log(f1.name);
console.log(f2.name);
使用构造函数创建实例对象之后,就有了两种不同的属性和方法:一种是私有化的属性和方法,书写在构造函数中,对其中一个实例进行更改,不会影响到其他实例,每一个实例中的属性和方法互不干扰;另一种是公有化的属性和方法,存放在原型对象中。所有实例都共享这些属性和方法,如果该原型属性中的方法和属性发生了变化,就会影响到所有的实例对象。
二、探究实例、构造函数、构造函数的原型之间的关系
还是以一下代码为例
Foo.prototype = {name: "cat"};
function Foo () {};
var f1 = new Foo();
var f2 = new Foo();
console.log(f1.name);
console.log(f2.name);
我们再来看一下这几个属性的定义
1)prototype
函数都具有prototype(原型对象)属性,该原型对象保存着所有实例对象的公共属性和方法,也就是共享的属性和方法。
2)__proto__
每个JavaScript对象(除了null,包括构造函数创造出来的实例对象和原型对象)都具有__proto__属性,该__proto__属性指向创造该对象的构造函数的原型对象(prototype)
3)constructor
原型对象都有constructor属性,指向拥有该原型对象的构造函数
1.f1.__proto__指向谁
1、实例f1由构造函数Foo使用new构造出来
2、Foo.prototype是函数的原型对象,因此函数Foo的prototype属性指向Foo.prototype
3、根据定义,Foo.prototype的constructor属性指向拥有该原型对象的构造函数,因为Foo拥有Foo.prototype,所以Foo.prototype指向Foo。
4、f1是一个实例对象,根据定义,对象的__proto__属性指向创建该对象的构造函数的原型,因为f1是由Foo创建的,所以f1.__proto__指向Foo.prototype。
2.Foo.prototype.__proto__指向哪里
Foo.prototype也是一个对象,而对象都是由构造函数Object构造的,因此Foo.prototype.__proto__指向的是构造函数Object的原型对象,也就是Object.prototype
根据这个线索我们同样也可以知道构造函数Object的prototype属性指向Object.prototype,Object.prototype的constructor属性指向构造函数Object本身。
3.Object.prototype.__proto__指向哪里
Object.prototype.__proto__也是一个对象,而对象都是由构造函数Object构造出来的,因此Object.prototype.__proto__指向的是构造函数Object的原型对象,也就是Object.prototype。
咦?难道Object.prototype.__proto__指向自己吗?
验证一下发现,Object.prototype.__proto__指向为null,也就是说Object.prototype.__proto__没有原型。
换句话说,所有的对象可以使用__proto__属性,通过原型链找到Object.prototype,虽然Object.prototype本身也是对象,但这个对象并不是构造函数Object构造的,而是引擎自己构造了Object.prototype。
4.function Object.__proto__指向哪里
我们知道,Function是所有函数的构造函数,所有的函数都是通过Function生成的.而Object()本身也是一个函数,Object()是由function构造出来的,因此Object().__proto__指向的是Function.prototype
因此,Function的prototype属性指向Function.prototype;Function.prototype的constructor属性指向Function
5.Function.__proto__指向哪里
Function()是所有函数的构造函数,那么它自己是由谁生成的呢?我们验证之后发现Function.__proto__指向的是Function.prototype
其实这个结论可以这样理解:所有函数都可以通过__proto__属性找到Function.prototype(就像前面的function Object()一样),而Function自己本质上也是一个函数(虽然它是其它所有构造函数的爸爸),因此按照这个逻辑,函数Function()__proto__指向Function.prototype也是可以解释的通的。
Function.prototype直接创建了一个函数Function(),除此之外的其余所有的函数都是由函数Function()创建的。因此其它函数的通过__proto__属性找到的都是Function.prototype;同样,Function()函数自己的__proto__属性也是指向Function.prototype。
其实Function.prototype也是由引擎自己创建的。首先引擎创建了Object.prototype,然后创建了Function.prototype,并且通过__proto__将二者联系起来。使得Function.prototype.__proto__指向Object.prototype。
6.Foo.__proto__的指向
因为function Foo()是一个函数,而所有函数都是由Function()构造出来的,因此function Foo()指向的是Function.prototype.
总结一下:
1)function Object() 是所有对象的爸爸,因此所有对象都可以通过__proto__找到Object.prototype。
2)Function是所有函数的爸爸,所有函数都可以通过__proto__找到Function.prototype
3)引擎首先创造了Object.prototype,又创造了Function.prototype,同时让Function.prototype的__proto__指向Object.prototype.其中Function.prototype又是一个函数
4)除了上面两个,其他对象都是被Object构造出来的