最近看网上各种文章 , 头异常的大 , 于是整理了一下
1. 工厂模式
单纯地创建对象, 使用Object构造函数, 或者字面量表示都 ok, 但是, 会产生大量重复代码, 每次都要写new Object()等等,
工厂模式可以解决这个问题
function createPerson(name, job){
// 创建一个Object
var o = new Object()
o.name = name
o.job = job
// 口吐芬芳
o.sayFword= function(){
alert('Funny')
}
return o
}
// 创建实例
var person1 = createPerson('Tony Stark', 'Iron Man')
var person2 = createPerson('进喜 王', '铁人')
工厂模式解决了创建多个相似对象的问题, 但是没有解决对象识别问题, 所有实例类型均为 object.
为了解决这个问题, 群众想出了构造函数模式
2. 构造函数模式
function Person(name, job){
this.name = name
this.job = job
// 口吐芬芳
this.sayFword= function(){
alert('Funny')
}
}
// 创建实例
var person1 = new Person('Tony Stark', 'Iron Man')
var person2 = new Person('王进喜', '铁人')
这里, Person 取代了 createPerson函数, 而且, 有几个不同:
- 没有显式创建对象
- 直接将属性, 方法赋给了this
- 没有显式return
- 函数名以大写字母开头
这个时候, 就可以识别对象类型了
person1.constructor === Person //true
person2.constructor === Person //true
person1 instanceof Person //true
person2 instanceof Person //true
如果不用 new 会有什么后果 ?
后果是, this指向全局对象, 浏览器中, 传入的属性和方法都挂到window对象了
var person3 = Person('王进喜', '铁人')
window.name //'王进喜'
window.job //'铁人'
我们可以用call或apply, 将this指向实例, 但这样标识不了类型, 构造函数模式便失去了意义
var person4 = new Object()
Person.call(person4, '王进喜', '铁人')
person4.name //'王进喜'
person4 instanceof Person //false
当然, 除非手动将__proto__指向Person.prototype, 这是后话
person4.__proto__ = Person.prototype
person4 instanceof Person // true
构造函数模式有一个问题 —— 每个方法都要在每个实例上创建一遍, 也就是说, 不同函数的同名方法实际上是不相等的
// person1下的sayFword 和 person2下的sayFword 是两个不同的方法
person1.sayFword === person2.sayFword //false
而我们想要让某一些方法成为该类型的所有实例共用的, 而不是每创建一个实例就去创建一个同样的方法/属性
于是, 群众又想出了下一个模式, 原型模式
3. 原型模式
原型模式的思想是, 给构造函数 (实际上只要是函数) 赋予一个prototype属性 , 这个属性的值是一个指针, 指向一个对象, 而这个对象包含其所有实例共享的属性和方法
function Person(){
}
Person.prototype.name = 'Tony Stark'
Person.prototype.job = 'Iron Man'
// 口吐芬芳
Person.prototype.sayFword= function(){
alert('Funny')
}
// 创建实例
var person1 = new Person()
var person2 = new Person()
person1.sayName === person2.sayName // true
默认情况下 prototype 会自动获得 constructor 属性, 该属性指向构造函数本身
Person.prototype.constructor === Person // true
如果实例中定义了跟prototype重名的属性, 会怎么样呢
var person3 = new Person()
person3.name = '灭霸'
console.log(person3.name) //'灭霸'
console.log(Person.prototype.name) // 'Tony Stark'
很明显, 原型上的同名属性并不会受到影响
我们可以用delete操作符删除对实例的属性, 从而重新访问原型中的属性
delete person3.name
console.log(person3.name) // 'Tony Stark'
需要注意的是, 使用 in 操作符, 即使属性是在原型上, 也会返回 true (for in 亦是如此)
"job" in person3 //true
单纯的原型模式, 也是有缺点的, 例如
function Person(){
}
Person.prototype = {
friends: [1,2]
}
// 创建实例
var person1 = new Person()
var person2 = new Person()
person1.friends.push(3)
console.log(person2.friends) // 1,2,3
如果共享的属性为引用类型, 修改实例该属性时, 会影响到原型上的该属性, 从而影响到其他实例
这个问题可以用 组合构造函数和原型模式 来解决
4. 组合构造函数和原型模式
构造函数模式用于定义实例的属性, 而原型模式用于定义方法和共享属性 , 集两家之所长
function Person(name, job, friends){
this.name = name
this.job = job
this.friends = friends
}
// 口吐芬芳
Person.prototype.sayFword = function(){
alert('Funny')
}
// 创建实例
var person1 = new Person('Tony Stark', 'Iron Man', [1, 2])
var person2 = new Person('王进喜', '铁人', [3, 4])
person1.sayFword === person2.sayFword // true
person1.friends.push(3)
console.log(person2.friends) // 3, 4
5. 动态原型模式
这种模式多了一层判断
function Person(name, age) {
this.name = name;
this.age = age;
if (typeof this.sayFword != "function") {
Person.prototype.sayFword = function() {
alert(this.name);
};
}
}
那么, 这样做的优点在哪呢 ?
想想看, 如果没有 if 的话,每 new 一次,都会重新定义一个新的函数,然后挂到Person.prototype.sayFword 属性上, 这就造成没必要的时间和空间浪费 。加上 if 后,只在new第一个实例时才会定义sayFword 方法,之后就不会了 。
并且 ————
if语句检查的可以是初始化之后应该存在的任何属性或方法 —— 不必用一大堆if语句检查每个属性和每个方法,只要检查其中一个即可。
———— 什么意思呢 ?
是这样的:假设除了sayFword 方法外,还定义了其他方法,比如sayHi、sayBye等等。此时只需要把它们都放到对sayFword 判断的if块里面就可以了
if (typeof this.sayFword != "function") {
Person.prototype.sayFword = function() {...};
Person.prototype.sayHi = function() {...};
Person.prototype.sayBye = function() {...};
...
}
这样一来,要么它们全都还没有定义(new第一个实例时),要么已经全都定义了(new其他实例后),即它们的存在性是一致的,用同一个判断就可以了,而不需要分别对它们进行判断。
6. 寄生构造函数模式
function SpecialArray() {
var values = new Array();
// 结构 arguments 拼接数组 ,在es6中不用这样写, 直接 value.push(...arguments)
values.push.apply(values, arguments);
// 此方法 将数组用|分隔拼接成字符串返回
values.toPipedString = function() {
return this.join('|');
};
return values;
}
虽然它写的和工厂模式一样,但是创建时用了new,因此使得实现的过程不一样(但是实现过程不重要)
作用嘛, 比如创建具有额外方法的已有类型(如数组,Date类型等,但是又想不污染原有的类型 ;
例如上面如果直接在Array中定义新的方法,会污染其它的数组对象, 于是创建了一个 SpecialArray 类型, 它包含了Array 的所有方法, 又有自己的方法 toPipedString ;
7. 稳妥构造函数模式
稳妥对象指没有公共属性,而且其他方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者防止数据被其他应用程序改动时使用 .
function Persion(name, age, job) {
// 创建要返回的对象
var o = new Object();
// 添加方法
o.sayName = function() {
alert(name);
}
return o;
}
var p1 = Persion('bill', 23, 'FE');
p1.sayName() // bill;
除了了调用sayName()外,没有别的方式可以访问其他数据成员。