七种创建模式表格对比:循序渐进解决问题
- 当然不能忘记es6的class
创建模式 | 解决的问题 | 未解决的问题 | 是否能使用instanceof检测类型 |
---|---|---|---|
工厂模式 | 解决了重复调用函数的问题 | 不能具体识别对象的类型(都是从Object来的) | 可以 |
构造函数模式 | 解决了识别对象的问题 | 每个方法都要在实例上重新创建一遍,即每个Person实例都包含一个不同的Function实例,浪费空间 | 可以 |
原型模式 | 解决了不能共享方法,浪费内现存的问题 | 1. 不能为构造函数初始化参数 2. prototype里包含引用类型值的属性,修改时会影响到原型 | 可以 |
组合使用原型模式和构造模式 | 解决了共享内存的问题,传参的问题,比较好的 | ----- | 可以 |
动态原型模式 | 不把原型和构造函数分开写,把所有信息都封装在构造函数中 | 不能使用对象字面量重写原型 | 可以 |
寄生构造函数模式 | 同工厂函数一样 | 不可以 | |
稳妥构造函数模式 | 适合安全执行环境,比如不能用this和new的 | 不可以 |
一、定义方法
// 1. new的方式创建
var obj = new Object();
// 2. 对象字面量创建
var obj = {};
// 3. Object.create方法创建
var obj = Object.create(o);
二、追求更好的创建方法
0、class
class Person {
constructor(name, age){
this.name = name;
this.age = age;
this.friends = ['a', 'b'];
}
getName() {
console.log(this.name);
}
}
var p = new Person ('tom', 24);
p.getName();
1、工厂模式-----自己理解就是专门抽取一个函数(模子)来创建同类的对象
var createPerson = function (name, age) {
// 声明一个中间对象,该对象就是工厂对象的模子
var o = new Object();
// 添加需要的属性和方法
o.name = name;
o.age = age;
o.getName = function () {
return this.name;
}
return o;
}
var personTom = createPerson('tom', 27);
var personJack = createPerson('jack', 18);
console.log(personTom instanceof Object); // true
console.log(personJack);
2、构造函数模式-----构造函数可以用来创建特定类型的对象。
var Person = function (name, age) {
this.name = name;
this.age = age;
this.getName = function () {
return this.name;
}
}
var pa1 = new Person('tom', 27);
console.log(pa1); // Person{.....}
console.log(pa1 instanceof Person); // true
console.log(pa1 instanceof Object); // true
// 发现的相等关系
console.log(p1.constructor === Person);
console.log(p1.__proto__.constructor === Person);
console.log(p1.__proto__.constructor === p1.constructor);
// 不能共享实例的方法的问题
console.log(pa1.getName === pa2.getName); // false
- 通过new,改变this的指向。
function demo() {
console.log(this);
}
demo(); // Window
new demo(); // demo{}
new的实现:
//
// 将该中间对象的原型指向构造函数的原型;
// 将构造函数的this,指向该中间对象
// 返回该中间对象,即返回实例对象
function New(func) {
// step1:声明一个新的中间对象,该对象为最终返回的实例
var res = {};
if (func.prototype !== null) {
// step2:将该中间对象的__proto__指向构造函数的原型对象,获取构造函数上的属性和方法
res.__proto__ = func.prototype;
}
// step3:ret为执行构造函数的结果,通过apply,将构造函数的作用域赋给新对象,即将this指向了这个新对象
var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
// step4: 返回新的对象--若我们在构造函数中,明确指定了返回对象,则返回ret
if ((typeof ret === 'object' || typeof ret === 'function') && ret !== null) {
return ret;
}
// step4: 返回新的对象--若我们在构造函数中没有明确指定返回对象时,那么默认返回res。因为ret为undefined
return res;
}
var p1 = New(Person, 'tom', 21);
console.log(p1.getName());
console.log(p1 instanceof Person);
console.log(p1 instanceof Object);
3、原型模式-----根据需求,选择性的将一些属性和方法通过prototype,挂载在原型对象上。
- 几乎每个函数都有一个prototype,几乎每个对象都有__proto__。
- 当访问实例对象中的属性或者方法时,会优先访问实例自身的。
- 可以通过 in 来判断,一个对象上是否拥有某个属性或方法。(不论实例自身存在还是原型上存在)
console.log('name' in p1);
in最常用的场景之一就是判断页面是否在移动端打开
isMobile = 'ontouchstart' in document;
还有其他相关判断方法:
// 1、hasOwnProperty: 只有属性存在于实例上才返回true
function Person () {}
Person.prototype.name = '12';
Person.prototype.sayName = function () {
console.log(this.name);
};
var p1=new Person();
var p2=new Person();
console.log(p1.hasOwnProperty('name')); // false
console.log('name' in p1); // true
console.log(p1.name); // '12'
p1.name = 'rrrr'; // 直接给实例添加name属性,实例上的属性会覆盖(屏蔽)原型上的属性
console.log(p1.hasOwnProperty('name')); // true
console.log('name' in p1); // true
console.log(p1.name); // 'rrrr'
console.log(p2.hasOwnProperty('name')); // false 上面给p1添加的name不影响p2
console.log('name' in p2); // true
console.log(p2.name); // '12'
delete p1.name; // 删除p1上添加的name
console.log(p1.hasOwnProperty('name')); // false
console.log('name' in p1); // true
console.log(p1.name); // '12' 原型上的
// 2、hasOwnProperty和in结合,可判断属性是否在原型上的
function hasPrototypeProperty(object, name) {
return !object.hasOwnProperty(name) && (name in object);
}
// 3、for-in循环,返回实例和原型上的可枚举属性。(新添加的覆盖了原型上本不可枚举的属性也会被循环出来)
for(let item in p1){
console.log(item, p1[item]);
}
// 4、Object.keys获取所有实例上的可枚举的属性,返回key的数组
// p1.propertyIsEnumerable('name'); // 返回boolean,检测只在实例上的属性是否可枚举
// 5、Object.getOwnPropertyNames获取所有实例上的属性(包括可枚举和不可枚举),返回key的数组
// 6、4,5结合实现获取实例上的不可枚举属性
var target = p1;
var enum_and_nonenum = Object.getOwnPropertyNames(target);
var enum_only = Object.keys(target);
var nonenum_only = enum_and_nonenum.filter((item) => {
return !enum_only.includes(item);
});
console.log(nonenum_only);
- 原型的动态性
(1)对原型对象所做的修改能够立即反映在实例上。跟实例的创建和原型的修改先后无关。
(2)实例中的指针指向原型,不是指向构造函数。重写原型对象会切断现有原型和之前一期经存在的实例之间的联系。
function Person () {
}
Person.prototype.age = 17;
// 也可以重写原型对象,所以要把constructor: Person加上。(通过这种方法重设constructor会使其[[Enumerable]]变成true。默认情况下 constructor的[[Enumerable]]是false)
Person.prototype = {
constructor: Person, // constructor是创建函数的时候,自动获取到的属性
name: 'tom',
friends: ['a', 'b'],
sayName() {
console.log(this.name);
}
};
let p1 = new Person();
console.log(p1.friends); // ['a', 'b']
let p2 = new Person();
console.log(p2.friends); // ['a', 'b']
p1.friends.push('c');
console.log(p1.friends); // ['a', 'b', 'c']
console.log(p2.friends); // ['a', 'b', 'c']
- 原生对象也可以在构造函数上定义方法,比如
Array.prototype.startWith = function () {}
但是不推荐,防止不同地方的命名冲突。
4、组合使用构造函数模式和原型模式:使用最广泛,认同度最高,是定义引用类型的默认模式。
- 通过原型模式定义方法和共享属性,既能保留实例的属性副本,又能节省内存。
- 通过构造函数模式传递初始化参数。
function Person(name, age) {
this.name = name;
this.age = age;
this.friends = ['a', 'b'];
}
Person.prototype.getName = function () {
console.log(this.name);
}
var p1 = new Person('tom', 27);
var p2 = new Person('jack', 18);
p1.friends.push('c');
console.log(p1.friends); // ["a", "b", "c"]
console.log(p2.friends); // ["a", "b"]
console.log(p2.getName === p2.getName); // true
5、动态原型模式:在构造函数中初始化原型(仅在必要的情况下)
- 使用动态原型模式不能使用对象字面量重写原型
function Person(name, age) {
this.name = name;
this.age = age;
this.friends = ['a', 'b'];
// 只会在初次调用构造函数时执行
if (typeof this.getName !== 'function') {
Person.prototype.getName = function () {
console.log(this.name);
}
}
}
let p1 = new Person('tom', 27);
p1.getName();
6、寄生构造函数模式:和工厂模式一样,但是使用new调用。(请移步采用这种模式)
- 返回的对象和构造函数或者构造函数的原型属性之间没有关系。所以不能用instanceof来确定类型。
function SpecialArray () {
let values = new Array();
values.push.apply(values, arguments);
values.toPipedString = function () {
return this.join('|');
}
return values;
}
var colors = new SpecialArray('red', 'green', 'blue');
console.log(colors.toPipedString()); // red|green|blue
7、稳妥构造函数模式
- 同寄生构造函数相比,新创建对象的实例方法不引用this;不适用new操作符调用构造函数。
function Person(name, age) {
var o = new Object();
o.name= name;
o.age = age;
o.getName = function () {
console.log(name);
}
return o;
}
var p = Person ('tom', 24);
p.getName();
p保存的是一个稳妥对象。除了调用getName方法,没有别的办法可以访问数据成员。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。