- 工厂模式
缺点:虽然解决了创建多个相似对象的问题,但是没能识别对象类型
例:
function createWorker(name, age, gender) {
var o = new Object();
o.name = name;
o.age = age;
o.gender = gender;
return o;
}
var worker = createWorker('Mary', '21', 'female');
- 构造函数模式
例:
function Worker(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = function(){
console.log(this.name);
}
}
var worker1 = new Worker('Mary', '21', 'female');
var worker2 = new Worker('Kimi', '20', 'male');
console.log(worker1 instanceof Worker); // true,新建了一个类型为Worker的一个对象
几点tips:
- 构造函数的函数名首字母要大写
- 实例对象时,要用new操作符
- 与工厂模式的对比
- 没有显示的新建对象
- 直接把属性赋值给this
- 没有return语句
- 构造函数的流程:
1.新建一个对象
2.把构造函数的作用域赋值给该对象(this)
3.执行构造函数中的代码
4.返回新对象缺点:每个方法都要重新定义一遍
console.log(worker1.sayName == worker2.sayName);
// false,不同实例的同名函数其实是不相等的
- 原型模式
例:
function Worker(){
}
Worker.prototype.name = 'Mary';
Worker.prototype.age = '21';
Worker.prototype.gender = 'female';
Worker.prototype.sayName = function(){
console.log(this.name);
}
var worker1 = new Worker();
var worker2 = new Worker();
console.log(worker1.sayName == worker2.sayName);
// true 引用的是同一个函数,定义在原型对象中
原型对象:包含某个特定类型的所有属性和方法
构造函数、原型对象和实例之间的关系:
- 初始化一个构造函数的时候,内部包含了一个 prototype 的属性,,该属性是一个指针,它指向了该构造函数对应的原型对象
- 原型对象(对于我们创建的构造函数,该对象中包含可以由所有实例共享的属性和方法)自生成一个 constructor 属性,该属性也是一个指针,指向它的构造函数
- 在调用构造函数创建新的实例时,该实例的内部会自动包含一个[[Prototype]]指针属性,该指针指便指向构造函数的原型对象。
关系图:(我画的有点乱糟糟)
image.png
几点tips
- 如果在实例上定义了一个原型对象同名的属性,则实例上的属性高于原型对象的属性;(当查找一个对象的属性时,会现在实例上查找,如果有,则返回实例上该属性值,查找停止;如果没有,则继续到原型对象里面去查找...)
接上面的例子:
worker1.name = 'Michael';
console.log(worker1.name); // 'Michael' 此时访问的是实例中的name属性
console.log(worker2.name);
// 'Mary' 由于在实例worker2中没有该属性,
因此继续到原型对象中查找,找到返回Mary
判断属性是在原型中还是在实例中的方法:
- hasOwnPrototype:判断某属性是否存在于实例中
接上面的代码:
console.log(worker1.hasOwnPrototype('name'));
// true, 在上面的代码中,给worker1实例定义了一个name属性
console.log(worker2.hasOwnPrototype('name'));
// false,对于worker2实例,name是在原型对象中定义的属性
- in 操作符 :属性如果存在于实例或者原型对象中,都会返回true
console.log('name' in worker1); // true
console.log('name' in worker2); // true
- 组合使用 in 操作符 和 hasOwnPrototype ,就可以很容易得到该属性是否是只存在原型对象
获得原型对象/实例的所有属性
- for-in : 获得对象 (包括原型对象)的所有可枚举的属性
worker1.job = 'teacher';
var props = [];
for(var prop in worker1){
props.push(prop);
}
console.log(props); // [name, age, gender, sayName, job]
- Object.keys() : 获得实例的所有可枚举的属性
console.log(Object.keys(worker1)); // [name,job]
console.log(Object.keys(worker2)); // []
- Object.getOwnPropertyNames(): 获得实例的所有属性(不管是否可枚举)
Object.defineProperty (worker1, 'hobby', { // es5 语法
value : 'music',
enumerable : false, // 将该属性的可枚举性改为false,默认值为true
})
console.log(Object.keys(worker1));
// [name, job] 不包含新增的不可枚举的属性 ‘hobby’
console.log(Object.getOwnPropertyNames(worker1));
// [name, job, hobby] 包含所有可枚举不可枚举的属性
- 原型模式的改写:
function Worker(){
}
var worker3 = new Worker();
Worker.prototype = {
constructor : Worker,
name = 'Mary';
age = '21',
gender = 'female',
sayName = function(){
console.log(this.name);
}
}
var worker4 = new Worker();
console.log(worker3.name);
// undefined, 此时实例worker3 指向最初原型对象,该对象未定义 name 属性
console.log(worker4.name);
// 'Mary' 此时原型对象已经重写,实例worker4指向该原型对象、
- 重写原型对象时,要显示地增加一个指向构造函数 Worker 的 constructor 的属性;(对于支持es5的浏览器,可以用Object.defineProperty())
- 重写原型对象以后,会切断构造函数和最初原型对象之间的连接;
- 在重写原型对象之前新建的实例,是无法获取新原型对象内部定义的属性的(而字面量的写法,则不必在意是初始化原型对象前还是后新建实例的)
原型模式依然存在的问题:所有变量和方法都是共享的,而且初始时具有相同的值;改进=> 让实例拥有自己的特定的属性,把共用的方法和属性定义在原型对象内部
构造函数+原型对象组合(运用最广的一种模式)
function Worker(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.hobby = ['music', 'sing'];
}
Worker.prototype = {
constructor : Worker,
sayName : function(){
console.log(this.name);
},
}
var worker1 = new Worker('Mary', '21', 'female');
var worker2 = new Worker('Kimi', '20', 'male');
worker1.sayName(); // Mary
worker2.sayName(); // Kimi
worker1.hobby.push('swim');
console.log(worker1.hobby); // ['music', 'sing', 'swim'],
console.log(worker2.hobby); // ['music', 'sing']
动态原型模式 (把原型对象的内容也封装在构造函数里)
function Worker(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.hobby = ['music', 'sing'];
if(typeof Worker.prototype.sayName != 'function'){
Worker.prototype.sayName = function(){
console.log(this.name);
}
}
}
几个tips:
- 初次实例化时,就定义原型对象的属性,仅执行一次
- if语句的条件只需要判断原型对象内部的任意一个属性或者方法就可以
- 这里不能使用原型对象的字面量写法,因为如果已经创建了实例再重写原型对象,则会切断实例和最初原型对象的关联
寄生式构造函数
function Worker(name, age, gender){
var o = new Object();
o.name = name;
o.age = age;
o.gender = gender;
o.sayName = function(){
console.log(this.name);
}
}
var worker1 = new Worker('Mary', '21', 'female');
几点tips
- 这种方式构造对象和构造函数以及构造函数的原型对象之间没有任何关系
(通过这个方式构造的对象和在普通对象没什么区别)- 由于上一条,因此不能用 instanceof 区别对象类型
- 除了在新建实例的时候,用了new操作符,其他情况和工厂模式一致
- 在有些情况下可以为对象增加特殊属性
(如给Array对象增加额外属性和方法,就可以在构造函数内部,新建一个array类的对象,然后增加它的属性,返回该对象)- 构造函数在不设置任何返回值的时候,默认返回新实例对象;如果设置了返回值的情况下,可以重写调用构造函数时的返回值。
其他模式可用的情况下,尽量不选择这种模式
稳妥构造函数
function Worker(name,age,gender){
var o = new Object();
o.sayName = function(){
console.log(name);
}
return o;
}
var worker =Worker('Mary', '21', 'female');
worker.sayName(); // Mary
几点tips:
- 和寄生式构造函数一样,该模式创建的对象不具有特殊类型的概念
- 构造函数内部不包含公共属性,且公共方法不能使用this
- 创建对象时,不使用操作符 new 创建构造函数
- 除了在公共方法能访问到name属性的值,再无其他方式可以访问
- 一般使用在安全执行环境中
总结: 以上就是创建对象的几种模型,很大部分的概念是摘自《javascript高级程序设计》。最近又在重新看这个书,想着写点东西,加强下自己的记忆和理解。平时感觉自己创建对象的时候太少了,之前看了某个大佬的直播,一开始他就像我平时写代码的方式一样,想到啥就写啥,但是最后人家重构了一下,把该封装的都封装成对象,可以说非常简洁了。后来,我也重构了项目中的部分代码,感觉很有用。其实也只是用到了非常简单的工厂模式。平时写代码的时候,就顾着完成功能,而不注意代码质量,这一点以后要加强吧。学以致用,这一周重构一下以前项目的部分页面,练练手吧。接下来想着理一理继承方面的知识点。不知道文中有没有哪里没弄对的,如有看到,欢迎指错~
网友评论