浅析在javascript中创建对象的各种模式


Posted in Javascript onMay 06, 2016

最近在看《javascript高级程序设计》(第二版)

javascript中对象的创建

•工厂模式

•构造函数模式

•原型模式

•结合构造函数和原型模式

•原型动态模式

面向对象的语言大都有一个类的概念,通过类可以创建多个具有相同方法和属性的对象。虽然从技术上讲,javascript是一门面向对象的语言,但是javascript没有类的概念,一切都是对象。任意一个对象都是某种引用类型的实例,都是通过已有的引用类型创建;引用类型可以是原生的,也可以是自定义的。原生的引用类型有:Object、Array、Data、RegExp、Function。 !引用类型就是一种数据结构,将数据和功能组织在一起,通常被称为类。 缺乏类概念的javascript中,需要解决的问题就是如何高效的创建对象。

1.1.0.创建对象的一般方法

var person = {}; //对象字面量表示,等同于var person = new Objcect();

person.name = 'evansdiy';
person.age = '22';
person.friends = ['ajiao','tiantian','pangzi'];
person.logName = function() {
  console.log(this.name);
}

基于Object引用类型,创建了一个对象,该对象包含四个属性,其中一个为方法。如果需要很多类似person的实例,那就会有许多重复的代码。

1.1.1.工厂模式 [top]

通过一个可以包含了对象细节的函数来创建对象,然后返回这个对象。

function person(name,age,friends) {

  var o = {
    name: name,
    age: age,
    friends: friends,
    logName: function() {
      console.log(this.name);
    }
  };

  return o;

}

var person1 = person('Evansdiy','22',['ajiao','tiantian','pangzi']);

每次调用person函数,都会通过该函数内部的对象o创建新的对象,然后返回,除此之外,这个为了创建新对象而存在的内部对象o没有其他的用途。另外,无法判断工厂模式创建的对象的类型。

1.1.2.构造函数模式 [top]

function Person(name,age,job) {

  this.name = name;
  this.age = age;
  this.job = job;
  this.logName = function() {
    console.log(this.name);
  }

}

//通过new操作符创建Person的实例
var person1 = new Person('boy-a','22','worker');

var person2 = new Person('girl-b','23','teacher');

person1.logName(); //boy-a

person2.logName(); //girl-a

对比工厂模式,可以发现,这里并不需要创建中间对象,没有return。另外,可以将构造函数的实例标识为一种特定的类型,这就解决了对象识别的问题(通过检查实例的constructor属性,或利用instanceof操作符检查该实例是否通过某个构造函数创建)。

console.log(person1.constructor == Person);//constructor位于构造函数原型中,并指向构造函数,结果为true

console.log(person1 instanceof Person);//通过instanceof操作符,判断person1是否为构造函数Person的实例但构造函数模式也有自己的问题,实际上,logName方法在每个实例上都会被重新创建一次,需要注意的是,通过实例化创建的方法且并不相等,以下代码将会得到false:

console.log(person1.logName == person2.logName);//false我们可以将方法移到构造器外部(变为全局函数)来解决这个问题:

function logName() {
  console.log(this.name);
}

function logAge() {
  console.log(this.age);
}

但是,在全局下创建的全局函数实际上只能被经由Person创建的实例调用,这就有点名不副实了;如果方法很多,还需要逐一定义,缺少封装性。

1.1.3.原型模式 [top]

javascript中的每一个函数都包含一个指向prototype属性的指针(大部分浏览器可以通过内部属性__proto__访问),prototype属性是一个对象,其中包含了由某种引用类型创建的所有实例共享的属性和方法。 

function Person() {}

Person.name = 'evansdiy';

Person.prototype.friends = ['ajiao','jianjian','pangzi'];

Person.prototype.logName = function() {
  console.log(this.name);
}

var person1 = new Person();

person1.logName();//'evansdiy'

以上代码做了这几件事情:

1.定义了一个构造函数Person,Person函数自动获得一个prototype属性,该属性默认只包含一个指向Person的constructor属性;

2.通过Person.prototype添加三个属性,其中一个作为方法;

3.创建一个Person的实例,随后在实例上调用了logName()方法。

这里需要注意的是logName()方法的调用过程:

1.在person1实例上查找logName()方法,发现没有这个方法,于是追溯到person1的原型

2.在person1的原型上查找logame()方法,有这个方法,于是调用该方法 基于这样一个查找过程,我们可以通过在实例上定义原型中的同名属性,来阻止该实例访问原型上的同名属性,需要注意的是,这样做并不会删除原型上的同名属性,仅仅是阻止实例访问。

var person2 = new Person();

person2.name = 'laocai';如果我们不再需要实例上的属性时,可以通过delete操作符删除。

delete person2.name;利用for-in循环枚举出实例可以访问到的所有属性(不论该属性存在于实例或是原型中):

for(i in person1) {
  console.log(i);
}

同时,也可以利用hasOwnProperty()方法判断某个属性到底存在于实例上,还是存在于原型中,只有当属性存在于实例中,才会返回true:

console.log(person1.hasOwnProperty('name'));//true!hasOwnProperty来自Object的原型,是javascript中唯一一个在处理属性时不查找原型链的方法。[via javascript秘密花园] 另外,也可以通过同时使用in操作符和hasOwnProperty()方法来判断某个属性存在于实例中还是存在于原型中:

console.log(('friends' in person1) && !person1.hasOwnProperty('friends'));先判断person1是否可以访问到friends属性,如果可以,再判断这个属性是否存在于实例当中(注意前面的!),如果不存在于实例中,就说明这个属性存在于原型中。 前面提到,原型也是对象,所以我们可以用对象字面量表示法书写原型,之前为原型添加代码的写法可以修改为: 

Person.prototype = {

  name: 'evansdiy',
  friends: ['ajiao','jianjian','pangzi'],
  logName: function() {
    console.log(this.name);
  }

}

由于对象字面量语法重写了整个prototype原型,原先创建构造函数时默认取得的constructor属性会指向Object构造函数:

//对象字面量重写原型之后

console.log(person1.constructor);//Object不过,instanceof操作符仍会返回希望的结果:

//对象字面量重写原型之后

console.log(person1 instanceof Person);//true当然,可以在原型中手动设置constructor的值来解决这个问题。

Person.prototype = {

  constructor: Person,
  ......

}

如果在创建对象实例之后修改原型对象,那么对原型的修改会立即在所有对象实例中反映出来:

function Person() {};

var person1 = new Person();

Person.prototype.name = 'evansdiy';

console.log(person1.name);//'evansdiy'

实例和原型之间的连接仅仅是一个指针,而不是一个原型的拷贝,在原型实际上是一次搜索过程,对原型对象的所做的任何修改都会在所有对象实例中反映出来,就算在创建实例之后修改原型,也是如此。 如果在创建对象实例之后重写原型对象,情况又会如何?

function Person() {};

var person1 = new Person1();//创建的实例引用的是最初的原型

//重写了原型
Person.prototype = {
  friends: ['ajiao','jianjian','pangzi']
}

var person2 = new Person();//这个实例引用新的原型

console.log(person2.friends);

console.log(person1.friends);

以上代码在执行到最后一行时会出现未定义错误,如果我们用for-in循环枚举person1中的可访问属性时,会发现,里头空无一物,但是person2却可以访问到原型上的friends属性。 !重写原型切断了现有原型与之前创建的所有对象实例的联系,之前创建的对象实例的原型还在,只不过是旧的。

//创建person1时,原型对象还未被重写,因此,原型对象中的constructor还是默认的Person()

console.log(person1.constructor);//Person()

//但是person2的constructor指向Object()

console.log(person2.constructor);//Object()需要注意的是,原型模式忽略了为构造函数传递参数的过程,所有的实例都取得相同的属性值。同时,原型模式还存在着一个很大的问题,就是原型对象中的引用类型值会被所有实例共享,对引用类型值的修改,也会反映到所有对象实例当中。

function Person() {};

Person.prototype = {
  friends: ['ajiao','tiantian','pangzi']
}

var person1 = new Person();

var person2 = new Person();

person1.friends.push('laocai');

console.log(person2.friends);//['ajiao','tiantian','pangzi','laocai']

修改person1的引用类型值friends,意味着person2中的friends也会发生变化,实际上,原型中保存的friends实际上只是一个指向堆中friends值的指针(这个指针的长度是固定的,保存在栈中),实例通过原型访问引用类型值时,也是按指针访问,而不是访问各自实例上的副本(这样的副本并不存在)。

1.1.4.结合构造函数和原型模式创建对象 [top]

结合构造函数和原型模式的优点,弥补各自的不足,利用构造函数传递初始化参数,在其中定义实例属性,利用原型定义公用方法和公共属性,该模式应用最为广泛。

function Person(name,age) {

  this.name = name;
  this.age = age;
  this.friends = ['ajiao','jianjian','pangzi'];

}

Person.prototype = {

  constructor: Person,
  logName: function() {
    console.log(this.name);
  }

}

var person1 = new Person('evansdiy','22');

var person2 = new Person('amy','21');

person1.logName();//'evansdiy'

person1.friends.push('haixao');

console.log(person2.friends.length);//3

1.1.5.原型动态模式 [top]

原型动态模式将需要的所有信息都封装到构造函数中,通过if语句判断原型中的某个属性是否存在,若不存在(在第一次调用这个构造函数的时候),执行if语句内部的原型初始化代码。

function Person(name,age) {

  this.name = name;
  this.age = age;

  if(typeof this.logName != 'function') {
    Person.prototype.logName = function() {
      console.log(this.name);
    };
    Person.prototype.logAge = function() {
      console.log(this.age);
    };
  };

}

var person1 = new Person('evansdiy','22');//初次调用构造函数,此时修改了原型

var person2 = new Person('amy','21');//此时logName()方法已经存在,不会再修改原型

需要注意的是,该模式不能使用对象字面量语法书写原型对象(这样会重写原型对象)。若重写原型,那么通过构造函数创建的第一实例可以访问的原型对象不会包含if语句中的原型对象属性。

function Person(name,age) {

  this.name = name;
  this.age = age;

  if(typeof this.logName != 'function') {
    Person.prototype = {
      logName: function() {
        console.log(this.name);
      },
      logAge: function() {
        console.log(this.Age);
      }
    }
  };

}

var person1 = new Person('evansdiy','22');

var person2 = new Person('amy','21');

person2.logName();//'amy'

person1.logName();//logName()方法不存在

需要说明的是,各模式都有自己的应用场景,无所谓优劣。

以上这篇浅析在javascript中创建对象的各种模式就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
利用404错误页面实现UrlRewrite的实现代码
Aug 20 Javascript
js中settimeout方法加参数的使用实例
Feb 27 Javascript
Jquery时间轴特效(三种不同类型)
Nov 02 Javascript
bootstrap-treeview自定义双击事件实现方法
Jan 09 Javascript
AngularJS仿苹果滑屏删除控件
Jan 18 Javascript
Bootstarp风格的toggle效果分享
Feb 23 Javascript
JQuery Mobile 弹出式登录框的实现方法
May 28 Javascript
JS实现按钮颜色切换效果
Sep 05 Javascript
vue加载完成后的回调函数方法
Sep 07 Javascript
vue3.0 CLI - 3.2 路由的初级使用教程
Sep 20 Javascript
基于js实现抽红包并分配代码实例
Sep 19 Javascript
jquery实现简易验证插件封装
Sep 13 jQuery
jquery自定义插件——window的实现【示例代码】
May 06 #Javascript
javascript创建对象的几种模式介绍
May 06 #Javascript
前端性能优化及技巧
May 06 #Javascript
jquery自定义插件开发之window的实现过程
May 06 #Javascript
快速使用Bootstrap搭建传送带
May 06 #Javascript
BootStrap响应式导航条实例介绍
May 06 #Javascript
jQuery侧边栏实现代码
May 06 #Javascript
You might like
php判断表是否存在的方法
2015/06/18 PHP
php使用COPY函数更新配置文件的方法
2015/06/18 PHP
PHP模板引擎Smarty中的保留变量用法分析
2016/04/11 PHP
JS简单实现文件上传实例代码(无需插件)
2013/11/15 Javascript
javascript动态创建及删除元素的方法
2014/12/22 Javascript
jQuery trigger()方法用法介绍
2015/01/13 Javascript
学习JavaScript正则表达式
2015/11/13 Javascript
关于验证码在IE中不刷新的快速解决方法
2016/09/23 Javascript
jQuery双向列表选择器DIV模拟版
2016/11/01 Javascript
jquery.Callbacks的实现详解
2016/11/30 Javascript
详解Javascript数据类型的转换规则
2016/12/12 Javascript
node学习记录之搭建web服务器教程
2017/02/16 Javascript
[58:12]Ti4第二日主赛事败者组 LGD vs iG 3
2014/07/21 DOTA
[43:18]NB vs Infamous 2019国际邀请赛淘汰赛 败者组 BO3 第一场 8.22
2019/09/05 DOTA
基于python OpenCV实现动态人脸检测
2018/05/25 Python
Python实现base64编码的图片保存到本地功能示例
2018/06/22 Python
python爬虫之线程池和进程池功能与用法详解
2018/08/02 Python
Python两台电脑实现TCP通信的方法示例
2019/05/06 Python
python实现各种插值法(数值分析)
2019/07/30 Python
python Django里CSRF 对应策略详解
2019/08/05 Python
python创建与遍历List二维列表的方法
2019/08/16 Python
Python使用gluon/mxnet模块实现的mnist手写数字识别功能完整示例
2019/12/18 Python
如何基于python3和Vue实现AES数据加密
2020/03/27 Python
爱他美官方海外旗舰店:Aptamil奶粉
2017/12/22 全球购物
美国女士内衣在线折扣商店:One Hanes Place
2019/03/24 全球购物
业务代表的岗位职责
2013/11/16 职场文书
应届毕业生求职信范文分享
2013/12/26 职场文书
应届实习生的自我评价范文
2014/01/05 职场文书
向领导表决心的话
2014/03/11 职场文书
《中国梦我的梦》小学生演讲稿
2014/08/20 职场文书
2014年档案管理员工作总结
2014/12/01 职场文书
2014年领导班子工作总结
2014/12/11 职场文书
智慧人生:永远不需要向任何人解释你自己
2019/08/20 职场文书
nginx 多个location转发任意请求或访问静态资源文件的实现
2021/03/31 Servers
详解MySQL 用户权限管理
2021/04/20 MySQL
GO语言字符串处理函数之处理Strings包
2022/04/14 Golang