代码改变世界

javascript 原型模式的工作原理 到 对象模式的探寻(上)

2015-04-20 16:44  coder_w  阅读(303)  评论(0编辑  收藏  举报

谈到Javascript面向对象,有几个非常重要的知识点:

  • 创建对象的模式
  • 原型与原型链
  • 继承方式

在Javascript中创建单个对象可以通过很简单的如下两种方式

//Object 实例化
var person = new Object();
person.name = "Nick";
person.sayName = function(){
	alert(this.name);
};

//对象字面量
var persion = {
	name: "Nick",
	sayName: function(){
		alert(this.name);
	}
};

但是这样的缺点就是

  • 创建多个相似对象时,产生大量重复代码
  • 对象识别问题,无法知道一个对象的类型

为了解决这些问题,在javascript的发展过程中,各位前辈大牛使用了以下模式:

  1. 工厂模式
  2. 构造函数模式
  3. 原型模式
  4. 组合模式(构造函数+原型模式)
  5. 其他模式
/**工厂模式**/
//通过函数工厂来创建
//缺点:没有解决对象识别的问题。

function createPerson(name){
	var o = new Object();
	o.name = name;
	o.sayName = function(){
		alert(this.name);
	};
	return o;
}
var personInstance = createPerson("Nick");

/**构造函数模式**/
//ECMAScript 中的构造函数可以用来创建特殊类型的对象。
//缺点:对象中的方法无法共享,每个方法在每个实例中都重新创建了一遍。
funtion Person(name){
	this.name = name;
	this.sayName = function(){
		alert(this.name);
	};
}
var personInstance = new Person("Nick");

/****
在这里js引擎会执行以下步骤:
 1. 创建一个对象 O
 2. 将构造函数的作用域赋给 O ,即this指向了 O
 3. 执行构造函数的代码,即为 O 添加了属性
 4. return 新对象
通过构造函数创建的对象,可以通过 constructor属性 或者 instanceof 操作符来识别对象类型。
****/

原型模式
每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象即原型对象,里面包含了该类型所有instance共享的属性和方法。

function Person(){
}
//在这里我们对prototype完全重写了,所以它的constructor默认不会指向Person函数,需要我们重新加上。
Person.prototype = {
	constructor: Person,
	name : "Nick",
	sayName : function(){
		alert(this.name);
	}
};
var personInstance = new Person();
//这里创建的实例 personInstance 并没有对prototype对象中的任何属性进行copy,而是在自己这边添加了一个指针[prototype]访问prototype对象。

这里写图片描述
需要注意的是 原型的重写一定要在 对象实例化之前,否则会出错。因为实例的指针指向的原型已经被重写了,所有自定义属性都为空。
这里写图片描述
原型模式的问题
原型模式在实现创建多个相似对象和识别对象的基础上,还提供了共享的功能,节省了大量内存。但是正是共享导致了一些问题,前面有说过所有实例共享prototype对象中的属性。那么当实例化的 instance 需要独享的属性呢? 当然,我们可以通过在实例中重写来覆盖prototype中的属性来实现。但是对于引用类型来说就有一定的问题了。

function Person(){
}
Person.prototype = {
	constructor: Person,
	friends: ["s","f"]
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push("gg");
console.log(person1.friends);
console.log(person2.friends);
person1.friends = ["1","2"];
console.log(person1.friends);
console.log(person2.friends);

/**
[s,f,gg]
[s,f,gg]
[1,2]
[s,f,gg]
**/

从上面的代码我们可以得知,如果对引用类型的属性进行操作的时候,也必须进行完全重写才能实现各自有各自的属性。否则将直接修改到prototype上的属性。

最终我们通过组合构造函数模式和原型模式得到的模式,是目前使用最广泛的一种自定义类型的方式。

function Person(name,friends){
	this.name = "Nick";
	this.friends = ["s","f"];
}
Person.prototype = {
	constructor : Person,
	sayName : function(){
		console.log(this.name);
	}
}

其他还有 如 动态原型模式、寄生构造函数模式、稳妥构造函数模式等,这里不做一一介绍。
下篇将总结原型链和Javascript继承等。