一、工厂模式
工厂模式:使用字面量和 object 构造函数会有很多重复代码,在此基础上改进解决了多个相似对象的问题,但没有解决对象识别的问题(即怎样知道一个对象的类型)
二、构造函数模式
与工厂模式创建对象的不同之处:没有显示创建对象,直接将属性和方法赋给 this 对象,没有 return 语句。默认 return 的是 this 对象。构造函数本身也是函数,只是可以用来创建对象,所以借鉴自其他面向对象语言,构造函数始终应该以一个大写字母开头,非构造函数用一个小写字母开头,用于区别构造函数与其他普通函数。new 操作符创建对象实例,经历 4 个步骤:1、创建一个新对象 2、讲构造函数的作用域赋给新对象(所以此时 this 指向这个新对象)3、执行构造函数中的代码(为这个新对象添加属性)4、返回新对象上面的实例 a 又一个 constructor(构造函数)属性指向 Person。相比较工厂模式的有点:有 constructor 属相,可以标志为一种特性的类型。但实际用来检测对象类型,instanceod 更方便些,所以 instanceof 比较的是 constructor 属性。三种使用构造函数创建对象的方法:
call 和 apply 的作用都是在某个特殊对象的作用域中调用 Person()函数。那么上例中,otherPerson 的作用域中调用 Person 函数,this 指向 otherPerson 对象,执行构造函数中的内容,this.name 指 otherPerson.name=‘shirley’构造函数模式的缺点:每创建一个实例都要执行一遍构造函数的语句,每个实例有不同的属性值很正常,也有许多内容一模一样的方法,这些方法重复创建(实际是 new function 实例,所以 person1.sayname!=otherPerson.sayname。因为 sayName 方法是不同实例,引用地址不同)则显得冗余。很明显,如果实例引用同一个 sayName 实例(同一个方法)就能够解决上述问题。Person 的 sayName 引用的都是同一个 sayName 函数,即全局变量中的 sayName 函数。
三、原型模式
利用每个函数都有的一个 prototype(原型)属性。这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。(解决了构造函数模式的缺点)prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。(就是所创建的对象的原型,例如:var person1=new Person(’linda‘,15)person1 的原型为 Person。otherPerson=new Object()otherPerson 的原型为 Object)例子:判断一个对象的 prototype 是否指向某对象访问一个对象的 prototype 属性 Firef、Safari、Chrome 中支持一个属性__proto__可以访问到 prototype 注意:JavaScript 中任意对象都有一个内置属性 [[Prototype]],在 ES5 之前没有标准的方法访问这个内置属性,但是大多数浏览器都支持通过__proto__来访问。以下统一使用__proto__来访问 [[Prototype]],在实际开发中是不能这样访问的。(摘自:https://segmentfault.com/a/11…)ES5 中增加了一个新方法,Object.getPrototyOf()“原型最初只包含 constructor 属性”即,对象的 constructor 保存在原型属性中。对象实例不能改变原型中的值,所以当我们试图改变原型中的值时,会在实例中新添加一个相同名称的属性。当我们访问这个属性时,就会优先返回实例中的属性,这就是覆盖了原型的属性。那么我们怎么确定一个属性是来自对象实例还是来自对象的原型,使用 Object 对象的 hasOwnProperty()方法可以检测属性是否存在于对象实例中,若存在,则访问的属性必定是来自对象实例,因为当访问一个属性时,先搜索对象属性若不存在才会去搜索对象的原型属性的。delete 可以删除实例的属性,但不能删除对象原型中的属性。对象、构造函数(constructor)和原型(Prototype)的联系:如下图
person1 和 person2 实例中的 prototype 属性中保存的是 Person 的原型,原型里有 constructor 属性又指向 Person。所以对象和 constructor 之间是通过原型联系起来的。
确定属性是原型中的属性:in 操作符只要通过对象能够访问到属性就返回 true,不管是实例的属性还是原型中的属性,所以通过 in 操作符返回 true 儿 hasOwnProperty()返回 false 就可以确定属性是原型中的属性。
用字面量法定义 Person 的 prototype 属性:会造成 constructor 属性不再指向 Person 我们知道,没创建一个函数,就会同时创建它的 prototype 对象,这个对象也会自动获得 constructor 属性。使用字面量定义 prototype 会重写这个对象,因此 constructor 会变成新的对象的 constructor 属性(指向 object 构造函数)。打印 person2 显示:红框内没有 constructor 属性。我们知道 person2.constructor 会寻找 person2 对象中的 constructor 对象中的 constructor 对象,person2 实例对象中没有,寻找原型中(红框)也没有,继续寻找 prototype 的原型对象即 object 对象,所以,person2.constructor===Object true;原型模式创建对象是在函数构造模式的基础上解决共有方法和属性的,那么函数构造模式是为了解决字面量法的无法判断是哪个对象的问题而出现,怎么判断?通过对象的 constructor 属性判断,现在原型的 constructor 都指向对象,就把这个优势抹去了,所以出现下面这样的方法来保存这个特性。这样做与原来的差别是:constructor 属性的 [[Enumberable]] 特性被设置为 true,原来是 false(不可枚举)。所以如果使用兼容 EXMAScript5 的 JavaScript 引擎,可以使用 Object.defineProperty()原型具有动态性。构造函数的原型改变会立即反映到所有实例中,但如果是重新写一个原型对象则不会了
原因:person1 的构造函数会自动指向一个原型对象,而保存的是这个原型对象的指针,新建一个 prototype 对象后,在堆内存中开辟了一个新的对象空间,在 person2 中保存的是这个新对象的引用指针,所以两者不同。原型模式很少会单独使用,因为所有属性都是共享的,但实力一般都是要有属于自己的全部属性的。原型模式无法做到,所以这是原型模式的缺点。
四、组合使用构造函数和原型模式
创建自定义类型的最常见方式,构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。结果每个人实例都有自己的一份实例属性的副本,同时又共享着对方法的引用,最大限度地节省了内存。这种方式还支持向构造函数传递参数。(既有构造函数模式的优点又有原型模式的优点)
五、动态原型模式
在构造函数中判断是否存在 sayName 函数,如果不存在添加到原型中,下图创建了两次对象,调用两次构造函数,但 if 的判断只执行一次,在第一次执行。
六、寄生构造函数模式
与工厂模式在写法上红框圈出不同之外一模一样。叫法上把 Person 函数叫做构造函数,其他无区别适用情境:可以在特殊的情况下用来为对象创建构造函数。假如我们想创建一个具有额外方法的特殊数组。
七、稳妥构造函数模式
稳妥对象:没有公共属性,而且其方法也不引用 this 的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止是使用 this 和 new),或者在防止数据被其他应用程序(如 Mashup 程序)改动时使用。稳妥构造函数与寄生构造函数类似的模式。不同点:1、新创建对象的实例方法不引用 this,2、不适用 new 操作符调用构造函数。
所以,使用稳妥构造函数模式创建的对象与构造函数之间没有关系,无法用 instanceod 判断所属类型。之所以稳妥,是因为除了 sayName 方法外部无法对传入构造函数中的原始数据进行访问。即使可以给这个对象添加方法或数据成员。使用环境:某些安全执行环境下使用(ADsafe 和 Caja)