本文首发于个人博客:www.wyb.plus
1. 组合使用构造函数和原型
首先再来回顾一下这三种模式 : 构造函数模式 , 原型模式 以及构造函数和原型的组合模式
构造函数模式 >>>
function Person(name, age) { this.name = name; this.age = age; this.sayName = function() { console.log(this.name); } }
缺点 : 浪费内存
原型模式 >>>
function Person() {} Person.prototype = { constructor: Person, name: 'wangyubo', age: 18, girlfriends: { first: "斋藤飞鸟", second: "迪丽热巴" }, sayName: function() { console.log(this.sayName); } }
缺点 : 原型的覆盖问题和原型的重写问题 , 不能传递参数
组合模式 >>>
function Person(name, age) { this.name = name; this.age = age; this.girlfriends = { first: "斋藤飞鸟", second: "迪丽热巴" } } Person.prototype = { constructor: Person, sayName: function() { console.log(this.sayName); } }
- 创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。
- 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。
- 每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存
- 另外,这种混成模式还支持向构造函数传递参数;可谓是集两种模式之长
组合使用构造函数和原型时有这么几个注意点:
- 对于对象内后期需要修改的引用类型的继承的值,这个值以
构造函数的this.属性名称的方式
来实现继承,这样可以避免某个示例操作继承来的对象而引起其他实例的变化- 这种构造函数与原型混成的模式,是目前在ECMAScript 中使用最广泛、认同度最高的一种创建自定义类型的方法。可以说,这是用来定义引用类型的一种默认模式
2. 动态原型模式
动态原型模式把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型
function Person(name, age) { this.name = name; this.age = age; this.girlfriends = { first: "斋藤飞鸟", second: "迪丽热巴" } if (typeof this.sayName !== "function") { Person.prototype.sayName = function() { console.log(this.name); } } } let wyb = new Person("xxx", 18) let wangyubo = new Person("yyy", 28)
此时条件满足 , 原型被修改 , sayName方法被添加到原型上
使用动态原型模式时,不能使用对象字面量重写原型。
前面已经解释过了,如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。
3. 寄生构造函数模式
这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;但从表面上看,这个函数又很像是典型的构造函数
function Person(name, age) { let obj = new Object(); obj.name = name; obj.age = age; obj.sayname = function() { console.log(this.name); } return obj } let wyb = Person("xxx", 18)//这样就是工厂模式 let wangyubo = new Person("yyy", 28)//这样就是寄生构造函数模式 //没错 他们的区别就在于是否是使用new操作符
看起来这两种模式生成的对象没有任何区别 , 都是由Object对象生成的
但是如果我给构造函数不添加返回值 , 那么结果就完全就不同了
//return obj注释掉 //turn obj
此时工厂模式创造的实例对象wyb为undefined , 寄生构造函数模式创造的实例对象wangyubo为Person构造的对象
这个问题的原因和上面他们没区别的原因都是一样的----return obj 手动截胡
- Person函数在没有注释掉return之前 , 最终返回值为obj , obj是由 Object()函数构造出来的 , 所以无论使用工厂模式还是寄生模式 , 都受到这个返回值的影响
- 而在return注释掉以后 , 工厂模式由于没有返回值 , 所以生成的实例对象wyb为undefined
- 寄生模式由于使用了new操作符 , 所以他会自动 return 新生成的对象
//伪代码 模拟寄生模式 function Person(name, age) { let o1 = new Object(); let obj = new Object(); obj.name = name; obj.age = age; obj.sayname = function() { console.log(this.name); } // return obj return o1 } // let wyb = Person("xxx", 18) let wangyubo = new Person("yyy", 28)
- 在寄生模式下 , Person里面类似于会有两个return , 如果自己定义了return的结果 , name就返回自己定义的结果 , 如果自己没有定义 , 那么也会按照new的默认步骤进行 , 最终拥有一个返回结果
标准一点的说法是 :
- 构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个return 语句,可以重写调用构造函数时返回的值。
- 如果不返回一个对象的话, 那么就如同是对一个普通的函数调用的new操作符, 此时Person的每一行代码照样会执行,只是丝毫不会影响新的对象
function Person(name, age) { // let o1 = new Object(); let obj = new Object(); obj.name = name; obj.age = age; obj.sayname = function() { console.log(this.name); } // return obj // return o1 } // let wyb = Person("xxx", 18) let wangyubo = new Person("yyy", 28)
function Person(name, age) { //构造函数模式 this.name = name; this.age = age; this.sayName = function() { console.log(this.name); }; //工厂模式 let obj = new Object(); obj.name = name; obj.age = age; obj.sayAge = function() { console.log(this.age); } return obj } let wangyubo = new Person("yyy", 28)
当工厂模式有返回值的时候 , wangyubo就是由Object构造的
//return obj
此时执行的是sayName方法 , sayAge为undefined , 且
wangyubo instanceof Person
为true , 不过wangyubo instanceof Object
也为true你觉得奇怪吗?一点都不奇怪 , 此时的Object是Person的原型对象 , 而不是Person里面的那个Object了 , 不要想歪了
3.1 寄生构造函数的用法
这个模式可以在特殊的情况下用来为对象创建构造函数(可以将现在已经有的构造函数拿来用)。
假设我们想创建一个具有额外方法的特殊数组。由于直接修改Array 构造函数造成的后果和影响太大,我们就可以用这个方式来实现
function Person(name, age) { let a = new Array(); a.name = name; a.age = age; a.sayname = function() { console.log(this.name); } return a } let wangyubo = new Person("yyy", 28)
此模式的问题:
- 返回的对象与构造函数(Person)或者与构造函数的原型属性之间没有关系
- 也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同
- 为此,不能依赖instanceof 操作符来确定对象类型。
- 由于存在上述问题,我们建议在可以使用其他模式的情况下,不要使用这种模式。
4. 稳妥构造函数模式
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this 的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this 和new),或者在防止数据被其他应用程序改动时使用
function Person(name, age) { let a = new Array(); a.name = name; a.age = age; a.sayname = function() { console.log(name); } return a } let wangyubo = Person("yyy", 28)
稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:
- 一是新创建对象的实例方法不引用this
- 二是不使用new 操作符调用构造函数
在以这种模式创建的对象中,除了使用sayname()方法之外,没有其他办法访问name 的值 , 和闭包的理念类似
用稳妥构造函数模式创建的对象与构造函数之间也没有什么关系,因此instanceof 操作符对这种对象也没有意义。