工厂模式
// 工厂模式
function createPerson(name, age, job){
var obj = new Object()
obj.name = name
obj.age = age
obj.job = job
obj.sayName = function(){
console.log(this.name);
}
return obj
}
var person1 = createPerson("Lin", 22, "Student");//注意:不需要new
person1.sayName();
var person2 = createPerson("Ququ", 22, "Student");
person2.sayName();
优点:接受参数,可以无数次的调用这个函数,创建Person对象,而每次他都可以返回一个包含三个属性一个方法的对象。
缺点:虽然解决了创建多个相似对象的问题,但是没有解决对象识别的问题(即怎么知道一个对象的类型)。
构造函数模式
// 构造函数模式
function Person(name, age, job){ //构造函数名称大写
this.name = name
this.age = age
this.job = job
this.sayName = function(){
console.log(this.name);
}
//没有return语句
}
var person1 = new Person("Lin", 22, "Student"); //创建对象需要new
person1.sayName();
var person2 = new Person("Ququ", 22, "Student");
person2.sayName();
构造函数模式Person()
中的代码 和 工厂模式createPerson()
的不同之处:
- 没有显式地创建对象
- 直接将属性和方法赋给了
this
对象 - 构造函数如果没有使用显式
return
语句,则返回this
要创建Person对象的新实例,必须使用new
操作符。以这种方式调用构造函数世纪上会经历一下四个步骤(联系:构造函数----new创建对象的过程 TODO):
- 创建空对象{}
- 将空对象分配给this值
- 将空对象的
__proto__
指向构造函数的prototype
- 通过call执行构造函数函数
- 如果没有使用显式
return
语句,则返回this
优点:创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型。
缺点:每个方法都要在每个实例上重新创新一遍。person1,person2
都有一个名为sayName()
的方法,但这两个方法不是同一个Function
的实例。因为ECMAScript中的函数是对象,所以每定义一个函数都是实例化了一个对象。
原型模式
// 原型模式
function Person(){
}
Person.prototype.name = "Yvette";
Person.prototype.age = 26;
Person.prototype.job = "engineer";
Person.prototype.sayName = function(){
alert(this.name);
}
var person1 = new Person();
var person2 = new Person();
alert(person1.sayName() === person2.sayName());
理解原型对象
1、创建的每个函数都有一个prototype
(原型)属性,这个属性是一个指针,指向函数的原型对象。
2、默认情况下,所有原型对象都自动获得一个constructor
属性,这个属性是一个指向prototype
属性所在函数的指针。
3、当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性)[[Prototype]]
,指向构造函数的原型对象。
读取对象属性流程
当代码读取某个对象的属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始,如果在实例中找到了具有给定名字的属性,则返回该属性的值,如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。
优点:减少了代码的重复,也可用标识来创建对象。
缺点:
- 它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。
- 原型中所有属性是被很多实例共享的,这种共享对函数来说非常适合,对于那些包含基本值的属性也还说得过去,但是对于包含引用类型值的属性来说,就是一个问题了,因为实例一般都有属于自己的全部属性。
混合模式(构造函数模式+原型模式)[广泛使用]
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor: Person,
sayName: function(){
alert(this.name);
}
}
var person1 = new Person("Lin", 22, "Student");
var person2 = new Person("Ququ", 22, "Student");
person1.friends.push("Liu");
console.log(JSON.stringify(person1.friends)); //["Shelby","Court","Liu"]
console.log(JSON.stringify(person2.friends)); //["Shelby","Court"]
console.log(person1.friends === person2.friends); //false
console.log(person1.sayName === person2.sayName); //true
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。这样,每个实例都有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度的节省了内存。而且还支持向构造函数传递参数。这种方式也是ECMAScript种使用最广泛,认同度最高的一种创建自定义类型的方法。
动态原型模式
动态原型模式把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
if( typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
}
}
}
var person1 = new Person("Lin", 22, "Student");
var person2 = new Person("李四",22,"coder");
person1.sayName();
当第一次创建person1
实例对象的时候,原型就开始初始化。初始化会创建一个prototype
属性,该属性是是一个指针,指向Person
构造函数的原型对象(构造函数.prototype === 原型
)。创建原型对象时会创建一个constructor
属性,这个属性也是个指针,指向person1
实例的构造函数(原型.constructor === 构造函数
)。因为Person
构造函数够拥有Person.prototype
的指向的原型对象的所有属性和方法,所以可以用this判断原型中是否存在sayName
方法。
注意:使用动态原型模式时,不能使用对象字面量重写原型。如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。
寄生构造函数模式
这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码。
function Person(name, age, job){
var o= new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
}
return o;
}
var person = new Person("Lin", 22, "Student");
person.sayName();
在上面寄生模式的例子中,Person函数创建了一个新对象,并以相应的属性和方法初始化该对象,然后又返回这个对象。
然后分析其与工厂模式的区别:
- 寄生模式创建对象时使用了New关键字
- 寄生模式的外部包装函数Person是一个构造函数
除了上面这2个区别寄生模式和工厂模式几乎一样,构造函数在不返回值的情况下,默认返回对象的新实例。而通过在构造函数的末尾添加一个return 语句,可以重写调用构造函数返回的值。
优点:寄生模式可以在特殊的情况下为对象来创建构造函数,原因在于我们可以通过构造函数重写对象的值,并通过return返回 重写调用构造函数(创建的对象的实例)之后的对象实例的新的值。
缺点:与工厂模式相同的是,寄生构造函数模式的实例也不能正确检测其类型。
假设我们想创建一个具有额外方法的特殊数组。由于不能直接修改Array构造函数,所以我们可以使用寄生模式。代码如下:
function SpecialArray() {
//创建数组
var array=new Array();
//添加值 arguments获取的是实参,不是形参,所以SpecialArray()并没有形参接收传递过来的参数
array.push.apply(array,arguments);
array.toPipedString=function(){
return this.join("|");
}
return array;
}
var colors=new SpecialArray("red","blue","black");
alert(colors.toPipedString()); //输出:red|blue|black
alert(colors instanceof SpecialArray); //输出:false
alert(colors instanceof Array); //输出:true
返回的对象是Array类型,而且colors接收了这个返回的对象,所以colors并不是SpecialArray构造函数的实例,而是Array的实例。
稳妥构造函数模式
道格拉斯·克罗克福德 发明了JavaScript中的稳妥对象这个概念。所谓稳妥对象指的是没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合用在一些安全的环境中(这些环境会禁止使用new和this),或者防止数据被其他的应用改动。
稳妥构造函数与寄生构造函数模式类似,但是也有两点区别:
- 稳妥模式不使用new操作符调用构造函数
- 新创建对象的实例方法不引用this
其代码如下:
function Person(name,age) {
//创建要返回的对象
var o=new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName=function(){
alert(name);
}
//返回对象
return o;
}
var person=Person("张三",22);
person.sayName(); //张三, 使用稳妥构造函数模式只能通过其构造函数内部的方法来获取里面的属性值
上面的代码定义了一个person变量,里面保存的是一个稳妥对象,而除了调用他的sayName()方法外,没有别的方法可以访问其数据成员。即使有其他的代码会给这个对象添加方法和数据成员,但也不可能有别的方法访问到传入到构造函数中的原始数据。稳妥构造函数模式提供的这种安全性。是的它非常适合在某些安全执行环境中。
【参考文档】