js中创建对象的几种方式


Posted in Javascript onFebruary 05, 2017

前言

不管是哪门语言,千变万化不离其宗,深入理解其本质,方能应用自如。对应到js,闭包,原型,函数,对象等是需要花费大功夫思考、理解的。本文穿插了js原型和函数的相关知识,讨论了批量创建对象的几种方式以及它们的优缺点。

正文

说起创建对象,最容易想到的便是通过对象字面量方式直接定义一个对象吧,但这种方式只能创建少量,单独且相互间无联系的对象。若要批量创建对象,该如何?

工厂模式

工厂模式非常直观,将创建对象的过程抽象为一个函数,用函数封装以特定接口创建对象的细节。如下所示:

function createStudent(name,sex,grade){       
 var o = new Object();
 o.name = name;
 o.sex = sex;
 o.grade = grade;

 o.sayName = function(){
 console.log(this.name);
 }
 return o;
}
var s1 = createStudent('Claiyre','famale',1);

通俗地讲,工厂模式就是将创建对象的语句放在一个函数里,通过传入参数来创建特定对象,最后返回创建的对象。

工厂模式虽然可以创建多个相似的对象,但却不能解决对象标识的问题,即怎样知道一个对象的类型。构造函数模式应运而生。

构造函数模式

构造函数模式是java语言创建对象的通用方式。两种语言用构造函数创建对象的方式略有不同,注意区别。

在JavaScript中没有类的概念,函数即为一等公民,因此,不必显式声明某个类,直接创建构造函数即可,类的方法和属性在构造函数中(或原型对象上)处理。构造函数模式的示例代码如下:

function Student(name,sex,grade){       
 this.name = name;
 this.sex = sex;
 this.grade = grade;
 this.sayName = function(){
 console.log(this.name);
 }
}
var s2 = new Student('孙悟空','male',2);

细心的朋友一定发现了构造函数的函数名首字母是大写的,而普通函数首字母则是小写,这是众多OO语言约定俗成的规定,虽然大多数情况下不大写也不会报错,但是为了代码的规范性和可读性,还是应该将构造函数的首字母大写,与普通函数区别开。

与工厂模式相比,用构造模式创建对象有以下几点不同:

  • 没有显示地创建对象
  • 直接将属性和方法赋给this对象
  • 没有return语句

此外,还应注意到要创建Student的实例,必须要使用new操作符,创建的实例对象将有一个constructor(构造器)属性,指向Person构造函数。调用构造函数创建对象经过了以下几个过程:

  • 创建一个新对象
  • 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
  • 执行构造函数中的代码
  • 返回新对象(不需要显式返回)

构造函数虽好用,但也不是没有缺点。使用构造函数的主要问题是:每个方法都要在每个实例上创建一遍。在ECMAScript中,函数即对象,因此每定义一个函数,也就是实例化了一个对象。下面的例子证明了这个缺点。

var s3 = new Student('唐僧','male',3);
var s4 = new Student('白骨精','female',4);
s3.sayName();
s4.sayName();
console.log(s3.sayName == s4.sayName);

运行结果:

js中创建对象的几种方式

也就是说通过构造函数实例化的多个对象的方法,是多个不同的方法,但它们内部的代码以及实现的功能是相同的,这就造成了一定的资源浪费。

幸运的是,这个问题可以用原型模式来解决。

原型模式

js中,每个函数都有一个prototype属性,它是一个指针,指向一个对象,叫做原型对象,原型对象包含了可以由特定类型的所有实例对象共享的属性和方法。此外,这个对象有一个与生自来的属性constructor,指向创建对象的构造方法。

使用原型模式可以让所有的实例共享原型对象中的属性和方法,也就是说,不必再构造函数中定义对象实例的信息。用代码表示如下:

function Student_1(){
}
Student_1.prototype.name = 'Claiyre';
Student_1.prototype.sex = 'female';
Student_1.prototype.class = 5;
Student_1.prototype.sayName = function (){
 console.log(this.name);
}
var s5 = new Student_1();       s5.sayName(); //Claiyre
var s6 = new Student_1();
s6.sayName(); //Claiyre

一张图胜过千言万语,下图清楚地阐释了各个对象和原型对象间的关系:

js中创建对象的几种方式

了解过原型后,可以继续在实例对象上增添属性或方法:

s6.name = 'John';      
s6.sayName(); //John

当要读取某个对象的属性时,都会执行一次搜索,搜索首先从对象实例本身开始,如果在实例中找到了这个属性,则搜索结束,返回实例属性的值;若实例上没有找到,则继续向对象的原型对象延伸,搜索对象的原型对象,若在原型对象上找到了,则返回原型上相应属性的值,若没有找到,则返回undefined。因此,实例对象属性会覆盖原型对象上的同名属性,所以上面第二行代码输出的是John。

  • Object.getPrototypeOf(object)方法返回参数对象的原型对象。
  • Object.keys(object)方法返回对象上课枚举的实例属性。

原型中的所有属性都是被所有实例所共享的,这种共享对于函数来说非常合适,对于包含基本值的属性也说的过去(实例属性会覆盖原型同名属性),但对于那些包含引用类型的属性,可有大麻烦了

Student_1.prototype.friends = ['aa','bb'];
console.log('s6的朋友' + s6.friends);
s5.friends.push('cc');
console.log('s5的朋友' + s5.friends);
console.log('s6的朋友' + s6.friends);

运行结果:

js中创建对象的几种方式

问题来了,我们只想改变s5的朋友列表,但由于原型模式的共享本质,s6的朋友列表也随之改变了。

因此,很少单独使用原型模式。

组合使用构造函数和原型模式

构造函数模式用于定义实例属性,原型模式则用于定义方法和共享的属性。这种混合模式不仅支持向构造函数传入参数,还最大限度地节约了内存,可谓是集两模式之长。示例代码如下:

function Student(name,sex,grade){       
 this.name = name;
 this.sex = sex;
 this.grade = grade;
}
Student.prototype.sayName = function(){
 console.log(this.name);
}
Student.prototype.school = 'Joooh school';

其他模式

除了以上几种常见的模式外,批量创建对象的方式还有

  • 动态原型模式:仅在第一次调用构造函数时,将方法赋给原型对象的相应属性,其他示例的处理方式同构造函数模式
  • 寄生构造函数模式:仅仅封装创建对象的代码,然后再返回新创建的对象,仍使用new操作符调用
  • 稳妥构造函数模式:没有公共属性,只有私有变量和方法,以及一些get/set方法,用以处理私有变量。

结语

每种模式都有各自的优缺点,具体要使用哪种,还需结合实际场景,深入理解,灵活运用。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
js 实现复制到粘贴板的功能代码
May 13 Javascript
Jquery ajaxsubmit上传图片实现代码
Nov 04 Javascript
jQuery实现拖动调整表格单元格大小的代码实例
Jan 13 Javascript
JavaScript 对象深入学习总结(经典)
Sep 29 Javascript
bootstrap-table组合表头的实现方法
Sep 07 Javascript
AngularJS模糊查询功能实现代码(过滤内容下拉菜单排序过滤敏感字符验证判断后添加表格信息)
Oct 24 Javascript
Vue 组件(component)教程之实现精美的日历方法示例
Jan 08 Javascript
jQuery实现获取form表单内容及绑定数据到form表单操作分析
Jul 03 jQuery
create-react-app安装出错问题解决方法
Sep 04 Javascript
Vue 动态路由的实现及 Springsecurity 按钮级别的权限控制
Sep 05 Javascript
vue element-ui el-date-picker限制选择时间为当天之前的代码
Nov 07 Javascript
Vue接口封装的完整步骤记录
May 14 Vue.js
js实现增加数字显示的环形进度条效果
Feb 05 #Javascript
JS实现复制内容到剪贴板功能
Feb 05 #Javascript
js实现适合新闻类图片的轮播效果
Feb 05 #Javascript
BootStrap table删除指定行的注意事项(笔记整理)
Feb 05 #Javascript
JS前端开发判断是否是手机端并跳转操作(小结)
Feb 05 #Javascript
thinkjs之页面跳转同步异步操作
Feb 05 #Javascript
js实现5秒倒计时重新发送短信功能
Feb 05 #Javascript
You might like
php 文件上传系统手记
2009/10/26 PHP
对淘宝URL中ID提取的PHP代码
2013/09/01 PHP
深入理解 PHP7 中全新的 zval 容器和引用计数机制
2018/10/15 PHP
Nigma vs Liquid BO3 第二场2.14
2021/03/10 DOTA
XHTML-Strict 内允许出现的标签
2006/12/11 Javascript
JavaScript基本对象
2007/01/11 Javascript
几个高效,简洁的字符处理函数
2007/04/12 Javascript
jquery 打开窗口返回值实现代码
2010/03/04 Javascript
Ajax执行顺序流程及回调问题分析
2012/12/10 Javascript
上传的js验证(图片/文件的扩展名)
2013/04/25 Javascript
JS 如何获取radio选中后的值及不选择取radio的值
2013/10/28 Javascript
node.js中的buffer.Buffer.isEncoding方法使用说明
2014/12/14 Javascript
js实现文件上传表单域美化特效
2015/11/02 Javascript
HTML5 Shiv完美解决IE(IE6/IE7/IE8)不兼容HTML5标签的方法
2015/11/25 Javascript
Node.js静态文件服务器改进版
2016/01/10 Javascript
javascript实现label标签跳出循环操作
2016/03/06 Javascript
thinkphp标签实现bootsrtap轮播carousel实例代码
2017/02/19 Javascript
AngularJS使用ocLazyLoad实现js延迟加载
2017/07/05 Javascript
jQuery实现获取及设置CSS样式操作详解
2018/09/05 jQuery
JavaScript遍历数组的三种方法map、forEach与filter实例详解
2019/02/27 Javascript
详解JS判断页面是在手机端还是在PC端打开的方法
2019/04/26 Javascript
你不可不知的Vue.js列表渲染详解
2019/10/01 Javascript
[09:37]DOTA2卡尔工作室 英雄介绍圣堂刺客篇
2013/06/13 DOTA
python使用cStringIO实现临时内存文件访问的方法
2015/03/26 Python
Python的Urllib库的基本使用教程
2015/04/30 Python
Python基于辗转相除法求解最大公约数的方法示例
2018/04/04 Python
Python嵌套列表转一维的方法(压平嵌套列表)
2018/07/03 Python
Python批量查询关键词微信指数实例方法
2019/06/27 Python
python3实现的zip格式压缩文件夹操作示例
2019/08/17 Python
django model object序列化实例
2020/03/13 Python
Python3爬虫关于识别检验滑动验证码的实例
2020/07/30 Python
Python 数据的累加与统计的示例代码
2020/08/03 Python
红色康乃馨酒店:Red Carnation Hotels
2017/06/22 全球购物
美国廉价机票预订网站:Cheapfaremart
2018/04/28 全球购物
疾病证明书
2015/06/19 职场文书
《颐和园》教学反思
2016/02/19 职场文书