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