TDD测试驱动的javascript开发(2) ---- javascript面向对象 ~~ 深入学习javascript中prototype

1. 原型模式

1.1 我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

简单的解释上面的话的意思就是:首先,我们要知道,面向对象的语言中类的存在,而javascript也是一门面向对象的语言(这句话说的可能有一些毛病,但是不影响),在javascript中定义一个类函数的时候,就默认创建了一个prototype属性,在这个prototype属性里的所有的属性和方法将被这个类的所有实例共享。看代码:

//创建一个Person类
function Person() {
}
//Person类prototype里包含了name、age属性和sayName()方法
Person.prototype.name = "defaultName";
Person.prototype.age = 29;
Person.prototype.sayName = function() {
	return this.name;
};

var person = new Person();
var person3 = new Person();
TestCase("test extends",{
	"test person.sayName() should be equals person3.sayName()" : function() {
		assertEquals(person.sayName(),person3.sayName()); 
	}
});

1.2 在默认的情况下,所有的原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。

Person.prototype.constructor 指向 Person

当为对象实例添加一个属性时,这个属性就会 屏蔽 原型对象中保存的同名属性(它只会阻止我们访问原型中的那个属性,并不会修改那个属性),不过我们可以使用delete操作符删除实例属性,让我们可以继续访问原型中的属性,看代码:

<span style="font-size:14px;">//创建一个Person类
function Person() {
}
//Person类prototype里包含了name、age属性和sayName()方法
Person.prototype.name = "defaultName";
Person.prototype.age = 29;
Person.prototype.sayName = function() {
	alert(this.name);
};

var person = new Person();
person.sayName();         // defaultName

var person2 = new Person();
person2.name = "person2";
person2.sayName();           //person2
//删除实例中的name属性
delete person2.name;
person2.sayName();          //defaultName</span>
1.3 使用hasOwnProperty()方法可以检测一个属性是存在于实例中还是存在于原型中,只有在给定的属性是存在于对象的实例中才会返回true
function Person() {
}
//Person类prototype里包含了name、age属性和sayName()方法
Person.prototype.name = "defaultName";
Person.prototype.age = 29;
Person.prototype.sayName = function() {
	return this.name;
};

var person = new Person();

var person2 = new Person();
person2.name = "person2";

TestCase("test extends",{
	"test person should not be hasOwnProperty name" : function() {
		assertEquals(false,person.hasOwnProperty("name")); 
	},"test person2 should not be hasOwnProperty name" : function() {
		assertEquals(true,person2.hasOwnProperty("name")); 
	},});


1.4 使用 in 操作符确定一个属性是原型中的属

in操作符只要通过对象能够访问到的属性就返回true,(hasOwnProperty()只在属性存在于实例中才返回true),因此,只要in操作符返回true,hasOwnProperty()返回false,就证明该属性是原型中的属性,看代码:

<span style="font-size:14px;"><span style="font-size:12px;">//创建一个Person类
function Person() {
}
//Person类prototype里包含了name、age属性和sayName()方法
Person.prototype.name = "defaultName";
Person.prototype.age = 29;
Person.prototype.sayName = function() {
	alert(this.name);
};

var person = new Person();

var person2 = new Person();
person2.name = "person2";
person2.color = "blue";

TestCase("test extends",{
	"test Person  person.hasOwnProperty(name)" : function() {
		assertEquals(false,person.hasOwnProperty("name")); //name是原型中的属性,实例中不存在
	},"test Person  name in person" : function() {
		assertEquals(true,"name" in person);
	},"test Person  person2.hasOwnProperty(name)" : function() {
		assertEquals(true,person2.hasOwnProperty("name"));//name是实例对象的属性
	},"test Person  Person2 name in person2" : function() {
		assertEquals(true,"name" in person2);
	},"test Person  person2.hasOwnProperty(color)" : function() {
		assertEquals(true,person2.hasOwnProperty("color"));   //color是实例对象的属性
	},"test Person  color property in Person2" : function() {
		assertEquals(true,"color" in person2);		//color是实例对象的属性,in也同样可以访问到
	}
	
});</span>
</span>

1.5枚举出实例的全部属性---- Object.keys()


function Person() {
}
//Person类prototype里包含了name、age属性和sayName()方法
Person.prototype.name = "defaultName";
Person.prototype.age = 29;
Person.prototype.sayName = function() {
	return this.name;
};

var person = new Person();

var person2 = new Person();
person2.name = "person2";

var keys = Object.keys(Person.prototype); 

var keys2 = Object.keys(person2);

TestCase("test extends",{
	"test keys should be an array" : function() {
		assertArray("keys is not an array",keys); 
	},"test keys[0] should be equals name" : function() {
		assertEquals("name",keys[0]);
	},"test keys[1] should be equals age" : function() {
		assertEquals("age",keys[1]);
	},"test keys[2] should be equals age" : function() {
		assertEquals("sayName",keys[2]);
	},"test keys2 should be an array" : function() {
		assertArray("keys is not an array",keys2);
	},"test keys2[0] should be equals age" : function() {
		assertEquals("name",keys2[0]);
	}
});
1.6 更简单的原型语法-----字面量创建对象
function Person() {
	
}
Person.prototype = {
	name : "defaultName",age : 20,sayName : function() {
		return this.name;
	}
		
};

在之前创建函数的时候,我们知道,每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获得constructor。而我们在这里使用的这种语法(字面量),本质上完全重写了默认的prototype对象,因此constructor对象也就变成了新对象的constructor的属性(指向Object构造函数),不再指向Person函数。

function Person() {
	
}
Person.prototype = {
	name : "defaultName",sayName : function() {
		return this.name;
	}
};

var con = new Person();

TestCase("test extends",{
 "test con should be instance of Object" : function() {
  assertInstanceOf("con shoud be instance of Obejct",Object,con);
 },"test con should be instance of Person" : function() {
  assertInstanceOf("con shoud be instance of Person",Person,"test con constructor should be equals Person" : function() {
  assertNotEquals(Person,con.constructor);                    //不再等于Person
 },"test con constructor should not be equals Object" : function() {
  assertEquals(Object,con.constructor); 
 },"test con constructor should be same as Person" : function() {
  assertNotSame(Person,con.constructor);
 },"test con constructor should not be same as Object" : function() {
  assertSame(Object,con.constructor); 
 }
});

1.7如何保留1.6中的constructor,在constructor的值比较重要的时候,需要我们来保存这个constructor的值,这个时候,我们可以在Person.prototype中重写constructor属性,并设置其值为Person。 -------但是注意:原生的constructor属性是不可以枚举的,如果这样设置了之后,constructor将变成可以枚举的。
function Person() {
	
}
Person.prototype = {
	constructor : Person,name : "defaultName",{
	"test con should be instance of Object" : function() {
		assertInstanceOf("con shoud be instance of Obejct",con);
	},"test con should be instance of Person" : function() {
		assertInstanceOf("con shoud be instance of Person","test con constructor should be equals Person" : function() {
		assertEquals(Person,con.constructor);               //完全重写了constructor,所以不再等于Object,但是还是Object的实例哦
	},"test con constructor should not be equals Object" : function() {
		assertNotEquals(Object,con.constructor);  
	},"test con constructor should be same as Person" : function() {
		assertSame(Person,con.constructor);
	},"test con constructor should not be same as Object" : function() {
		assertNotSame(Object,con.constructor);  
	}
});
补充:如果你的浏览器是兼容ECMAScript5,就可以使用defineProperty
//重设构造函数
Object.defineProperty(Person.prototype,"constructor",{
	enumerable:false,value:Persons
});

1.8原型对象的问题

原型中的属性是被所有实例所共享的,当然,这种共享对于函数非常合适,可是对于一些引用类型的属性来说,问题就出现了,因为有些时候我们是不想我们的引用类型的属性被共享:

function Person() {
	
}
Person.prototype = {
	name : "defaultName",friends : ["fengfeng","tongtong"],sayName : function() {
		return this.name;
	}
};
var person = new Person();
person.friends.push("ty");

var person2 = new Person();

TestCase("test property",{
	"test person friends.length should be 3 " : function() {
		assertEquals(person.friends.length,3);  
	},"test person friends[2] should be ty" : function() {
		assertEquals("ty",person.friends[2]);  
	},"test person2 friends.length should be became 3 " : function() {
		assertEquals(person2.friends.length,3);      //person2的长度也变成了3
	},"test person2 friends should be equals person friends " : function() {
		assertEquals(person.friends,person2.friends); //person2的friends属性也发生了变化
	}
});


2.0javascript中的对象

2.1原型模式创建对象: 参见1.1 - 1.8

2.2 构造函数模式创建对象

function Person(name,age) {
	this.name = name;
	this.age = age;
	this.sayName() {
		return this.name;
	};
};
var person1 = new Person("tong",24);
var person2 = new Person("feng",24);

2.3组合使用构造函数模式和原型模式 ------ 解决引用类型的属性不被共享

创建自定义类型的最常见方式,就是组合使用构造函数模式和原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。

function Person(name,age) {
	this.name = name;
	this.age = age;
	this.friends = ['tong','feng'];
}
Person.prototype = {
		constructor : Person,sayName : function() {
			return this.name;
		}
};

var person = new Person("tongtong",25);

var person2 = new Person("fengfeng",26);

person.friends.push("ty");

TestCase("test property","test person2 friends.length should be 2 " : function() {
		assertEquals(person2.friends.length,2);      //person2的长度还是2
	},"test person2 friends should not be equals person friends " : function() {
		assertNotEquals(person.friends,person2.friends); //person2的friends属性没有发生变化
	},"test person2 sayName should be equals person sayName" : function() {
		assertEquals(person.sayName,person2.sayName); //person.sayName===person2.sayName
	}
});

2.4 动态原型模式创建对象
function Person(name,'feng'];
	//method
	if (typeof this.sayName != "function") {
		Person.prototype.sayName = function() {
			return this.name;
		};
	}
}

只有在sayName()方法不存在的时候,才会将它添加到原型中,这段代码只会在初次调用构造函数的时候执行。这里对原型的修改,能够立即在所有实例中得到反映。

使用动态原型模式时,不能使用对象字面量重写原型,如果在已经创建了实例的情况下重写原型,那么就会切断现有实例和新原型之间的联系。

2.5 稳妥构造函数模式创建对象

所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象,稳妥对象最适合使用在一些安全环境中(这些环境会禁止使用this和new),或者在防止数据被其他应用程序改动时使用。

稳妥构造函数的2个特点:

新创建对象的实例方法不引用this, 不使用new操作符调用构造函数

使用稳妥对构造函数模式创建的对象与构造函数之间没什么关系,因此instanceof操作符对这种对象没有意义。

function Person(name,age) {
	var o = new Object();
	o.sayName = function() {
		return name;
	};
	return o;
};

var person = Person("tongtong","25");

TestCase("test property",{
	"test person sayName should be tongtong " : function() {
		assertEquals(person.sayName(),"tongtong");  
	},"test person is not instanceof Person " : function() {
		assertNotInstanceOf(Person,person); 
	}
});

关于安全模式的2个平台

http://www.adsafe.org/

http://code.google.com/p/google-caja/

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结