关于原型

什么是原型?

原型就是原型对象。在 js 中,当我们创建一个函数的时候,这个函数都会默认拥有一个 prototype 属性,这个属性会默认指向与函数同时创建的一个拥有特定属性的对象,这个对象就是函数的原型对象。

如何理解原型对象的原理?原型对象到底是怎么构成的?它的工作机制什么?

要理解原型对象就不得不细说三个重要的概念: 构造函数 原型对象 以及实例对象。原型对象的工作机制就是围绕这三点展开的。

什么是构造函数?

构造函数是一种特殊的函数,是用来创建某一类对象时进行对象初始化的函数。要注意的是,构造函数的特殊在于其要实现的功能特殊,然而在我们进行声明的时候,构造函数和普通函数并没有区别,换句话说,所有的函数都可以作为构造函数来使用。日常使用的时候为了区分构造函数和普通函数,我们会将构造函数的命名首字母大写:

function Dog(name){
	this.name = name;
}

上面的示例中就是一个简单的构造函数,它初始化了一个属性 name。这样当我们通过 new 操作符创建实例对象的时候,这个构造函数就会将参数中的 name 赋值给实例对象的 name 属性。这也就延伸出了第二个概念——实例对象。

什么是实例对象?

首先我们来看什么是对象,ECMA-262中对对象的定义是:“无序属性的集合,其属性可以包含基本值、对象和函数”。“无序”、“集合”、“包含基本值、对象和函数”这个便是对象。换句话说,对象就是储存数据的一个集合,这个集合由一个又一个的属性构成,这些属性可以包含基本值(Null,Undefined,Num,String,boolean),也可以包含对象(Object,Arr,Func,Data,RegExp等),属性与属性之间是无序排列的。

而实例对象,便是储存某个具体的数据,具象化的对象。创建实例对象的方式就是通过 new 操作符:

// 创造一个数组的实例对象
var newArr = new Array();			// []

// 创建 Dog 的实例对象
var newDog = new Dog('chahu');		
什么是原型对象?

原型对象我们之前有介绍过————创建函数时产生的,由函数的 prototype 属性指向的对象。也就是说,我们创建任意一个函数,都会同时产生一个原型对象与它来对应,而访问这个对象的方式便是通过 Func.prototype 的方式来进行访问,我们也可以通过这种方式来对原型对象进行更改,或者重写:

// 重写 Dog 的原型对象
Dog.prototype = {
	constractor : Dog,
	legsNum : 4,
	showName : function(){
		alert(this.name);
	} 
}

原型对象是一个特殊的对象,是伴随函数创建自行创建的、可以由所有该函数创建的实例对象共享其属性的对象。

构造函数 原型对象 实例对象之间的有什么联系吗?

构造函数 原型对象 实例对象这三点在应用的时候可以看做一个单元。而构造函数就是这个单元的发起点。我们来两两的看一下他们之间的关系。

构造函数与原型对象之间的关系

当我们创造构造函数的时候,这个构造函数的原型对象就会自动创建。构造函数的 prototype 属性指向的便是原型对象。而原型对象中同时也会创建一个 constructor 属性,这个属性会指向构造函数。

对于上面示例中的 构造函数 Dog ,访问 Dog 的原型对象的方式就是通过 Dog.prototype ,而 Dog.prototype 的 constructor 属性又指向 Dog 本身:

alert( Dog.prototype.constructor === Dog );		// true	
构造函数与原型对象之间的联系

实例对象是通过 new 操作符对构造函数进行实例化产生的。

// 我们先创建一个构造函数 Dog
function Dog(name){
	this.name = name;
} 

// 当我们需要创造实例的时候 我们就可以利用 new 操作符号来创建实例
var zx = new Dog(zixiao);
var xf = new Dog(xiaofu);

当我们使用 new 操作符调用构造函数的时候。实际上进行了四个步骤:

创建一个新的对象————将函数作用域赋给新对象————执行构造函数中的代码————返回新对象

那么也就是说实例对象是调用构造函数产生的。

那如何通过实例对象找到构造函数呢?实际上这个直接访问 实例对象的 constructor 属性就可以了,就如上面的例子中 zx.constructor 就指向构造函数 Dog。

alert(zx.constructor === Dog);			// true

不过要注意的是,这里的 constructor 属性并不是实例对象的私有属性,而是通过指针访问的原型对象的 constructor 属性。关于这一机制下面将进行详细解释。

原型对象与实例对象的联系

在 ES5 中,当我们实例化一个实例对象的时候,实例对象会产生一个内部的指针 [[prototype]] 来指向构造函数的原型对象。Firefox、Safari、Chrome 中都实现了对这一内部指针的支持,他们通过属性 proto 来指向构造函数的原型对象。而在大多数浏览器中,这个指针是不可见的。

下面重点来了。同一个构造函数的每一个实例对象都是可以访问这个构造函数的原型对象的属性和方法的。准确的来说,当实例对象不包含某个属性(或方法)而原型对象存在的时候,我们可以通过直接访问实例对象来访问这个原型对象的属性(或方法)。

例如上面的例子中,虽然实例对象 zx 并没有设置 legsNum 属性,我们依旧可以访问这一属性:

alert(zx.legsNum);				//  4
// 同理 也可以访问 showName() 方法
zx.showName();					// 'zixiao'

对于实例对象,每当我们读取这个对象的属性的时候,代码都会首先在实例对象中查找该属性,如果这个属性存在,则返回属性的值;如果不存在,那么代码会搜索这个对象内部指针 [[prototype]] 指向的原型对象,在原型对象中查找这一属性,存在则返回属性的值;如果不存在,则搜索原型对象的
[[prototype]] 指针指向的原型对象的构造函数的原型对象,依次进行下去,直到属性存在或者到达最底层的原型对象。这也就可以引申出很多实例对象的特点:

// 1. 当实例对象和原型对象同时存在一个属性时,会优先访问实例对象的属性
alert(zx.legsNum);			// 4
zx.legsNum = 3;
alert(zx.legs);				// 3

// 2. 直接重写实例对象的属性不会影响原型对象的值
zx.legsNum = 3;
alert(xf.legsNum);			// 4

// 3. 更改原型对象会影响所有访问这个原型对象的实例对象 但不会影响到拥有这个私有属性的对象
zx.constructor.prototype.legsNum = 5;
alert(xf.legsNum);			// 5	
alert(zx.legsNum);			// 3

// 4. 要额外注意一些隐性的修改原型对象的情况
Dog.prototype.Num = [0,1,2];
zx.Num.push(3);
alert(xf.Num);		// 0,1,2,3

那么原型对象是用来干什么的?为什么要设计这样一个原型对象呢?

在 js 中,OOP (面向对象编程)的实现是可以通过 new 操作符来实现的,当我们要实现 OOP 的时候,我们就可以用构造函数和 new 操作符来实现类的功能,就像下面示例:

// 我们先创建一个构造函数 Dog
function Dog(name){
	this.name = name;
} 

// 当我们需要创造实例的时候 我们就可以利用 new 操作符号来创建实例
var zx = new Dog(zixiao);
var xf = new Dog(xiaofu);

这也就是我们通常所说的构造函数模式,通过这种方式我们就可以创建一系列在 Dog 中所设置的数据结构的实例对象,也就实现了 OOP。

然后新的问题就产生了,当我们的实例对象中有共同点的时候,或者说他们之间有共同联系的时候这种数据结构要怎么实现呢?

这就可以通过原型来实现,也就是说,原型对象可以储存具有某种特征的所有实例共享的属性和方法。

// 创建构造函数 Dog
function Dog(name){
	this.name = name;
}

// 为 Dog 的原型函数 添加所有实例对象的共有属性
Dog.prototype.legsNum = 4;

// 创建实例对象
var zx = new Dog('zixiao');		
var xf = new Dog('xiaofu');

// 访问公用属性

alert(zx.legsNum);			// 4
alert(xf.legsNum);			// 4

就如上面的示例,通过对构造函数 Dog 的原型对象添加属性,我们可以实现对所有Dog的实例对象的共有属性进行存储。也就实现了 js 的面向对象编程。

posted @ 2017-08-12 13:05  我是一个毛毛虫  阅读(344)  评论(0编辑  收藏  举报