javascript面向对象系列第二篇——创建对象的5种模式
前面的话
如何创建对象,或者说如何更优雅的创建对象,一直是一个津津乐道的话题。本文将从最简单的创建对象的方式入手,逐步介绍5种创建对象的模式
对象字面量
一般地,我们创建一个对象会使用对象字面量的形式
[注意]有三种方式来创建对象,包括new构造函数、对象直接量和Object.create()函数,详细情况移步至此
var person1 = {
name: "bai",
age : 29,
job: "Software Engineer",
sayName: function(){
alert(this.name);
}
};
如果我们要创建大量的对象,则如下所示:
var person1 = {
name: "bai",
age : 29,
job: "Software Engineer",
sayName: function(){
alert(this.name);
}
};
var person2 = {
name: "hu",
age : 25,
job: "Software Engineer",
sayName: function(){
alert(this.name);
}
};
/*
var person3 ...
*/
虽然对象字面量可以用来创建单个对象,但如果要创建多个对象,会产生大量的重复代码
工厂模式
为了解决上述问题,人们开始使用工厂模式。该模式抽象了创建具体对象的过程,用函数来封装以特定接口创建对象的细节
function createPerson(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayname = function(){
alert(this.name);
}
return o;
}
var person1 = createPerson('bai',29,'software Engineer');
var person2 = createPerson('hu',25,'software Engineer');
工厂模式虽然解决了创建多个相似对象的问题,但没有解决对象识别的问题,因为使用该模式并没有给出对象的类型
构造函数模式
可以通过创建自定义的构造函数,来定义自定义对象类型的属性和方法。创建自定义的构造函数意味着可以将它的实例标识为一种特定的类型,而这正是构造函数模式胜过工厂模式的地方。该模式没有显式地创建对象,直接将属性和方法赋给了this对象,且没有return语句
function Person(name,age,job){
this.name = name;
this.age = age;
this.jog = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("bai",29,"software Engineer");
var person2 = new Person("hu",25,"software Engineer");
使用构造函数的主要问题是每个方法都要在每个实例上重新创建一遍,创建多个完成相同任务的方法完全没有必要,浪费内存空间
function Person(name,age,job){
this.name = name;
this.age = age;
this.jog = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("bai",29,"software Engineer");
var person2 = new Person("hu",25,"software Engineer");
//具有相同作用的sayName()方法在person1和person2这两个实例中却占用了不同的内存空间
console.log(person1.sayName === person2.sayName);//false
构造函数拓展模式
在构造函数模式的基础上,把方法定义转移到构造函数外部,可以解决方法被重复创建的问题
function Person(name,age,job){
this.name = name;
this.age = age;
this.jog = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
};
var person1 = new Person("bai",29,"software Engineer");
var person2 = new Person("hu",25,"software Engineer");
console.log(person1.sayName === person2.sayName);//true
现在,新问题又来了。在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。而且,如果对象需要定义很多方法,就要定义很多全局函数,严重污染全局空间,这个自定义的引用类型没有封装性可言了
寄生构造函数模式
该模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。该模式是工厂模式和构造函数模式的结合
寄生构造函数模式与构造函数模式有相同的问题,每个方法都要在每个实例上重新创建一遍,创建多个完成相同任务的方法完全没有必要,浪费内存空间
function Person(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
};
return o;
}
var person1 = new Person("bai",29,"software Engineer");
var person2 = new Person("hu",25,"software Engineer");
//具有相同作用的sayName()方法在person1和person2这两个实例中却占用了不同的内存空间
console.log(person1.sayName === person2.sayName);//false
还有一个问题是,使用该模式返回的对象与构造函数之间没有关系。因此,使用instanceof运算符和prototype属性都没有意义。所以,该模式要尽量避免使用
function Person(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
};
return o;
}
var person1 = new Person("bai",29,"software Engineer");
console.log(person1 instanceof Person);//false
console.log(person1.__proto__ === Person.prototype);//false
稳妥构造函数模式
所谓稳妥对象指没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全环境中(这些环境会禁止使用this和new)或者在防止数据被其他应用程序改动时使用
稳妥构造函数与寄生构造函数模式相似,但有两点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数
function Person(name,age,job){
//创建要返回的对象
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function(){
console.log(name);
};
//返回对象
return o;
}
//在稳妥模式创建的对象中,除了使用sayName()方法之外,没有其他方法访问name的值
var friend = Person("bai",29,"Software Engineer");
friend.sayName();//"bai"
与寄生构造函数模式相似,使用稳妥构造函数模式创建的对象与构造函数之间也没有什么关系,因此instanceof操作符对这种对象也没有什么意义
原型模式
使用原型对象,可以让所有实例共享它的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中
function Person(){
Person.prototype.name = "bai";
Person.prototype.age = 29;
Person.prototype.job = "software Engineer";
Person.prototype.sayName = function(){
console.log(this.name);
}
}
var person1 = new Person();
person1.sayName();//"bai"
var person2 = new Person();
person2.sayName();//"bai"
alert(person1.sayName == person2.sayName);//true
更简单的原型模式
为了减少不必要的输入,也为了从视觉上更好地封装原型的功能,用一个包含所有属性和方法的对象字面量来重写整个原型对象
但是,经过对象字面量的改写后,constructor不再指向Person了。因为此方法完全重写了默认的prototype对象,使得Person.prototype的自有属性constructor属性不存在,只有从原型链中找到Object.prototype中的constructor属性
function Person(){};
Person.prototype = {
name: "bai",
age: 29,
job: "software Engineer",
sayName : function(){
console.log(this.name);
}
};
var person1 = new Person();
person1.sayName();//"bai"
console.log(person1.constructor === Person);//false
console.log(person1.constructor === Object);//true
可以显式地设置原型对象的constructor属性
function Person(){};
Person.prototype = {
constructor:Person,
name: "bai",
age: 29,
job: "software Engineer",
sayName : function(){
console.log(this.name);
}
};
var person1 = new Person();
person1.sayName();//"bai"
console.log(person1.constructor === Person);//true
console.log(person1.constructor === Object);//false
由于默认情况下,原生的constructor属性是不可枚举的,更妥善的解决方法是使用Object.defineProperty()方法,改变其属性描述符中的枚举性enumerable
function Person(){};
Person.prototype = {
name: "bai",
age: 29,
job: "software Engineer",
sayName : function(){
console.log(this.name);
}
};
Object.defineProperty(Person.prototype,'constructor',{
enumerable: false,
value: Person
});
var person1 = new Person();
person1.sayName();//"bai"
console.log(person1.constructor === Person);//true
console.log(person1.constructor === Object);//false
原型模式问题在于引用类型值属性会被所有的实例对象共享并修改,这也是很少有人单独使用原型模式的原因
function Person(){}
Person.prototype = {
constructor: Person,
name: "bai",
age: 29,
job: "Software Engineer",
friend : ["shelby","Court"],
sayName: function(){
console.log(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends);//["shelby","Court","Van"];
alert(person2.friends);//["shelby","Court","Van"];
alert(person1.friends === person2.friends);//true
组合模式
组合使用构造函数模式和原型模式是创建自定义类型的最常见方式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,这种组合模式还支持向构造函数传递参数。实例对象都有自己的一份实例属性的副本,同时又共享对方法的引用,最大限度地节省了内存。该模式是目前使用最广泛、认同度最高的一种创建自定义对象的模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["shelby","Court"];
}
Person.prototype = {
constructor: Person,
sayName : function(){
console.log(this.name);
}
}
var person1 = new Person("bai",29,"Software Engineer");
var person2 = new Person("hu",25,"Software Engineer");
person1.friends.push("Van");
alert(person1.friends);// ["shelby","Court","Van"];
alert(person2.friends);// ["shelby","Court"];
alert(person1.friends === person2.friends);//false
alert(person1.sayName === person2.sayName);//true
动态原型模式
动态原型模式将组合模式中分开使用的构造函数和原型对象都封装到了构造函数中,然后通过检查方法是否被创建,来决定是否初始化原型对象
使用这种方法将分开的构造函数和原型对象合并到了一起,使得代码更加整齐,也减少了全局空间的污染
[注意]如果原型对象中包含多个语句,只需要检测其中一个语句即可
function Person(name,age,job){
//属性
this.name = name;
this.age = age;
this.job = job;
//方法
if(typeof this.sayName != "function"){
Person.prototype.sayName = function(){
console.log(this.name);
};
}
}
var friend = new Person("bai",29,"Software Engineer");
friend.sayName();//'bai'
最后
本文从使用对象字面量形式创建一个对象开始说起,创建多个对象会造成代码冗余;使用工厂模式可以解决该问题,但存在对象识别的问题;接着介绍了构造函数模式,该模式解决了对象识别的问题,但存在关于方法的重复创建问题;接着介绍了原型模式,该模式的特点就在于共享,但引出了引用类型值属性会被所有的实例对象共享并修改的问题;最后,提出了构造函数和原型组合模式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,这种组合模式还支持向构造函数传递参数,该模式是目前使用最广泛的一种模式
此外,一些模式下面还有一些解决特殊需求的拓展模式
以上
javascript面向对象系列第二篇——创建对象的5种模式的更多相关文章
- 深入理解javascript函数系列第二篇——函数参数
× 目录 [1]arguments [2]内部属性 [3]函数重载[4]参数传递 前面的话 javascript函数的参数与大多数其他语言的函数的参数有所不同.函数不介意传递进来多少个参数,也不在乎传 ...
- 深入理解javascript作用域系列第二篇——词法作用域和动态作用域
× 目录 [1]词法 [2]动态 前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极 ...
- 深入理解javascript作用域系列第二篇
前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极易出错.这实际上是由两种作用域工作 ...
- 深入理解javascript对象系列第二篇——属性操作
× 目录 [1]查询 [2]设置 [3]删除[4]继承 前面的话 对于对象来说,属性操作是绕不开的话题.类似于“增删改查”的基本操作,属性操作分为属性查询.属性设置.属性删除,还包括属性继承.本文是对 ...
- javascript面向对象系列第一篇——构造函数和原型对象
× 目录 [1]构造函数 [2]原型对象 [3]总结 前面的话 一般地,javascript使用构造函数和原型对象来进行面向对象编程,它们的表现与其他面向对象编程语言中的类相似又不同.本文将详细介绍如 ...
- 前端学PHP之面向对象系列第二篇——魔术方法
× 目录 [1]构造方法 [2]析构方法 [3]不可访问属性[4]对象复制[5]字符串[6]对象不存在[7]自动加载类[8]串行化[9]函数调用 前面的话 php在面向对象部分有很多相关的魔术方法,这 ...
- javascript动画系列第二篇——磁性吸附
× 目录 [1]范围限定 [2]拖拽范围 [3]磁性吸附 前面的话 上一篇,我们介绍了元素拖拽的实现.但在实际应用中,常常需要为拖拽的元素限定范围.而通过限定范围,再增加一些辅助的措施,就可以实现磁性 ...
- javascript运动系列第二篇——变速运动
× 目录 [1]准备工作 [2]加速运动 [3]重力运动[4]减速运动[5]缓冲运动[6]加减速运动[7]往复运动[8]变速函数 前面的话 前面介绍过匀速运动的实现及注意事项,本文在匀速运动的基础上, ...
- javascript面向对象系列第三篇——实现继承的3种形式
× 目录 [1]原型继承 [2]伪类继承 [3]组合继承 前面的话 学习如何创建对象是理解面向对象编程的第一步,第二步是理解继承.本文是javascript面向对象系列第三篇——实现继承的3种形式 [ ...
随机推荐
- 【UISegmentedControl】- 分段控件
一.初始化 二.常见的属性 1.segmentedControlStyle属性:设置基本的样式 2.momentary属性:设置在点击后是否恢复原样 . 3.numberOfSegments属性:只读 ...
- MySQL多表查询
第一种: select a.a1,a.a2,a.a3,b.b2,c.c2,d.d2 from a,b,c,d where a.a1=b.b1 and b.b1=c.c1 and c.c1=d.d1 第 ...
- PLSQL配置新的oracle实例
在Win环境下的Oracle中,D:\Oracle\product\10.1.0\db_1\NETWORK\ADMIN\tnsnames.ora文件很重要,它作用是:本地命名的配置.本地名可以用简单的 ...
- 本周PSP流程进度
一计划 (1)估计这个任务需要多少时间:8天 二开发 (1)需求分析:作为一名观众,我希望了解每一场的比赛成绩,以便加深对自己喜爱球队的了解,以及赛况. (2)生成设计文档: (3)设计复审(和同学交 ...
- 【Telerik】弹出对话框RadWindow,确认删除信息
要做一个删除功能,但是删除前正常都要弹出对话框确认一下是否删除信息,防止误删信息.
- C代码实现栈
# include <stdio.h> # include <malloc.h> # include <stdlib.h> //C语言实现栈 //结点 typede ...
- css 清除浮动 clear
.clearfix{ zoom:1;/*对于老版本的IE进行兼容的设置*/ } .clearfix:after{ content:""; display:block; visibi ...
- Web 入门之 XML
160916 1. 什么是XML? XML 是 EXtensible Markup Language 的缩写,称为可扩展标记语言,所谓可扩展指用户可根据XML规则自定义标记.例子1-1 = ...
- ubuntu下 编译安装swftools
1.下载:http://www.swftools.org/download.html2.安装: tar -zvxf swftools-0.x.x.tar cd swftools-0.x.x ./con ...
- Windows Azure 服务总线和物联网
机器到机器 (M2M) 计算正迅速成为一种技术,所有开发人员和架构师需要拥抱. 许多研究表明一个未来世界的数百亿美元的设备 (在地球上的每一个人的出现).MSDN杂志有2篇文章讨论Azure服务总线和 ...