工厂模式

工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程。工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题。

function createPerson(name, age, job) {
  var o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.sayName = function() {
    alert(this.age);
  };
  return o;
}

var person = createPerson('hanzichi', 30, 'JavaScript Engineer');

构造函数模式

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayName = function() {
    alert(this.name);
  };
}

var person = new Person('hanzichi', 30, 'JavaScript Engineer');

按照惯例,构造函数始终都应该以一个大写字母开头,这个做法借鉴自其他 OO 语言。

要创建 Person 的新实例,必须使用 new 操作符。以这种方式调用构造函数实际上会经历以下 4 个步骤(也可以参考 一道有意思的笔试题引发的对于new操作符的思考):

  1. 创建一个对象
  2. 将构造函数的作用域赋给新的对象(因此 this 就指向了这个新对象)
  3. 执行构造函数中的代码(为这个新对象添加属性)
  4. 返回新对象

原型模式

function Person() {}

Person.prototype.name = 'hanzichi';
Person.prototype.age = 30;
Person.prototype.job = 'JavaScript Engineer';
Person.prototype.sayName = function() {
  alert(this.name);
};

var person = new Person();
person.sayName();

在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。

我们也可以用一个包含所有属性和方法的对象字面量来重写整个原型对象:

function Person() {}

Person.prototype = {
  name: 'hanzichi',
  age: 30,
  job: 'JavaScript Engineer',
  sayName: function() {
    alert(this.name);
  }
};

var person = new Person();

在上面的代码中,我们将 Person.prototype 设置为等于一个以对象字面量形式创建的新对象。最终结果相同,但有一个例外:constructor 属性不再指向 Person 了。每创建一个函数,就会同时创建它的 prototype 对象,这个对象也会自动获得 constructor 属性。而我们在这里使用的语法,本质上完全重写了默认的 prototype 对象,因此 constructor 属性也就变成了新对象的 constructor 属性(指向 Object 构造函数),不再指向 Person 函数。此时,尽管 instanceof 操作符还能返回正确的结果,但通过 constructor 已经无法确定对象的类型了。

function Person() {}

Person.prototype = {
  name: 'hanzichi',
  age: 30,
  job: 'JavaScript Engineer',
  sayName: function() {
    alert(this.name);
  }
};

var person = new Person();
console.log(person instanceof Object); // true
console.log(person instanceof Person); // true
console.log(person.constructor === Person); // false
console.log(person.constructor === Object); // true

如果 constructor 的值真的很重要,可以像下面这样特意将它设置回适当的值:

function Person() {}

Person.prototype = {
  constructor: Person,
  name: 'hanzichi',
  age: 30,
  job: 'JavaScript Engineer',
  sayName: function() {
    alert(this.name);
  }
};

var person = new Person();
console.log(person.constructor === Person); // true

注意,以这种方式重设 constructor 属性会导致它的 [[Enumerable]] 特性被设置为 true。默认情况下,原生的 constructor 属性是不可枚举的,可以用 Object.definePropety(ES5):

function Person() {}

Person.prototype = {
  name: 'hanzichi',
  age: 30,
  job: 'JavaScript Engineer',
  sayName: function() {
    alert(this.name);
  }
};

// 重设构造函数,适用 ES5+ 浏览器
Object.defineProperty(Person.prototype, 'constructor', {
  value: Person,
  enumerable: false
});

var person = new Person();
console.log(person.constructor === Person); // true

构造函数模式 + 原型模式

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}

Person.prototype = {
  constructor: Person,
  sayName: function() {
    alert(this.name);
  }
};

var person = new Person('hanzichi', 30, 'JavaScript Engineer');

这种构造函数与原型混合的模式,是目前 ECMAScript 中使用最广泛、认同度最高的一种创建自定义类型的方法

动态原型模式

有其他 OO 语言经验的开发人员在看到独立的构造函数和原型时,很可能会感到非常困惑。动态原型模式正是致力于解决这个问题的一个方案,它把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;

  if (typeof this.sayName !== 'function') {
    Person.prototype.sayName = function() {
      alert(this.name);
    };
  }
}

var person = new Person('hanzichi', 30, 'JavaScript Engineer');

有点「延迟加载」的意思(参 高性能JavaScript 编程实践 「不要重复工作」一节)。

寄生构造函数模式

这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。但从表面上看,这个函数又很像是典型的构造函数。

function Person(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 person = new Person('hanzichi', 30, 'JavaScript Engineer');

这个模式可以在特殊的情况下用来为对象创建构造函数。假设我们想创建一个具有额外方法的特殊的数组,由于不能直接修改 Array 构造函数,因此可以使用这个模式。

function SpecialArray() {
  var values = new Array();

  values.push.apply(values, arguments);

  // 添加方法
  values.toPipedString = function() {
    return this.join('|');
  };

  return values;
}

var colors = new SpecialArray('red', 'blue', 'green');
console.log(colors.toPipedString()); // red|blue|green

关于寄生构造函数模式,有一点需要说明:首先,返回的对象和构造函数或者与构造函数的原型属性之间并没有关系,也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。为此,不能依赖 instanceof 操作符来确定对象类型。由于存在上述问题,我们建议在可以使用其他模式的情况下,不要使用这种模式。

稳妥构造函数模式

所谓稳妥对象,指的是没有公共属性,而且其方法也不引用 this 的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用 this 和 new),或者在防止数据被其他应用程序改动时使用。

function Person(name, age, job) {
  // 创建要返回的对象
  var o = new Object();

  // 可以在这里定义私有变量和函数
  var name = name;
  var age = age;
  var job = job;

  // 添加方法
  o.sayName = function() {
    alert(name);
  };

  // 返回对象
  return o;
}

var person = Person('hanzichi', 30, 'JavaScript Engineer');
person.sayName();

注意,在以这种模式创建的对象中,除了使用 sayName() 方法之外,没有其他方法访问 name 的值。

变量 person 保存的是一个稳妥对象,而除了调用 sayName() 方法外,没有别的方式可以访问其数据成员。

和寄生构造函数模式类似,使用稳妥构造函数模式创建的对象与构造函数之间也没有什么关系,因此 instanceof 操作符对这种对象也没有意义。

JavaScript OOP 之「创建对象」的更多相关文章

  1. 「2014-3-13」Javascript Engine, Java VM, Python interpreter, PyPy – a glance

    提要: url anchor (ajax) => javascript engine (1~4 articles) => java VM vs. python interpreter =& ...

  2. 「译」JavaScript 的怪癖 1:隐式类型转换

    原文:JavaScript quirk 1: implicit conversion of values 译文:「译」JavaScript 的怪癖 1:隐式类型转换 译者:justjavac 零:提要 ...

  3. JavaScript 引擎「V8」发布 8.0 版本,内存占用量大幅下降

    上周,JavaScript 引擎「V8」的开发团队在该项目官方网站上正式宣布推出最新的 8.0 版本.这次更新的重点主要集中在错误修复及性能改善上,正式的版本将在数周后随着谷歌 Chrome 80 稳 ...

  4. 「MoreThanJava」Day 4:面向对象基础

    「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...

  5. jvm系列(十):如何优化Java GC「译」

    本文由CrowHawk翻译,是Java GC调优的经典佳作. 本文翻译自Sangmin Lee发表在Cubrid上的"Become a Java GC Expert"系列文章的第三 ...

  6. 一个「学渣」从零开始的Web前端自学之路

    从 13 年专科毕业开始,一路跌跌撞撞走了很多弯路,做过餐厅服务员,进过工厂干过流水线,做过客服,干过电话销售可以说经历相当的“丰富”. 最后的机缘巧合下,走上了前端开发之路,作为一个非计算机专业且低 ...

  7. 使用JavaScript OOP特性搭建Web应用

    最近,我面试了一个有五年 Web 应用程序开发经验的软件开发人员.四年半来她一直在从事 JavaScript 相关的工作,她自认为 JavaScript 技能非常好,但在不久之后我就发现实际上她对 J ...

  8. 「模板」 树链剖分 HLD

    「模板」 树链剖分 HLD 不懂OOP的OIer乱用OOP出人命了. 谨此纪念人生第一次类套类. 以及第一次OI相关代码打过200行. #include <algorithm> #incl ...

  9. 【微信小程序】开发实战 之 「配置项」与「逻辑层」

    微信小程序作为微信生态重要的一环,在实际生活.工作.商业中的应用越来越广泛.想学习微信小程序开发的朋友也越来越多,本文将在小程序框架的基础上就微信小程序项目开发所必需的基础知识及语法特点进行了详细总结 ...

随机推荐

  1. Angular2 小贴士 NgModule 模块

    angular2 具有了模块的概念,响应了后台程序的号召,高内聚 低耦合.模块就是用来进行封装,进行高内聚  低耦合的功能. 其实各人认为ng2 的模块和.net的工程类似,如果要使用模块中定义的功能 ...

  2. jQuery中怎样阻止后绑定事件

    你的代码在页面载入过程中已经完成事件绑定了,没有阻止后绑定的事件的办法了,不过可以删除当前指定节点的事件绑定.方法如下:$("#btn").click(function(){if( ...

  3. 高分辨率下IE浏览器缩放导致出现右侧滚动条问题的解决

    0 问题描述 由于需要演示触控操作,采购了SurfacePro,SurfacePro的推荐分辨率为2736×1824,且默认缩放比例为200%,IE浏览器的默认缩放比例也是200%,这样就导致右侧出现 ...

  4. 『.NET Core CLI工具文档』(八)dotnet-restore

    说明:本文是个人翻译文章,由于个人水平有限,有不对的地方请大家帮忙更正. 原文:dotnet-restore 翻译:dotnet-restore 名称 dotnet-restore - 还原一个项目的 ...

  5. StackExchange.Redis帮助类解决方案RedisRepository封装(基础配置)

    本文版权归博客园和作者吴双本人共同所有,转载和爬虫,请注明原文地址.http://www.cnblogs.com/tdws/p/5815735.html 写在前面 这不是教程,分享而已,也欢迎园友们多 ...

  6. Servlet 服务器性能提高--->数据库请求频率控制(原创)

    首先我要说下我实现这个功能接口涉及到的业务和实现的详细流程,然后会说此接口涉及到的相关技术,最后会贴出注释后的详细代码, 这个接口涉及到的是 app上咻一咻功能,咻一咻中奖的奖品一共有七类,其中四类是 ...

  7. spring框架之javaconfig

    简介:随着java5的推出,加上当年基于纯java annotation的依赖注入框架Guice的出现,spring推出并持续完善了基于java代码和annotation元信息的依赖关系绑定描述方法, ...

  8. 课堂Java小程序(加减乘除与验证码)

    一.编写一个程序,用户输入两个数,求出其加减乘除,并用消息框 显示计算结果. 1.设计思想:从键盘输入两个数字和运算符,然后计算.将输入的数字及运算符由字符型转换为整型,再用if判断输入的运算符,根据 ...

  9. 修改nginx配置文件解决dx2.5下载附件停止不动的问题

    在下载论坛附件的时候,总是停止在某个字数数不动 如下图 后来查看log发现 如下图 权限拒绝 发现后nginx的配置文件的启动者有关系 改了下 user 为 root 居然好了

  10. JavaScript中数据类型转换总结

    JavaScript中数据类型转换总结 在js中,数据类型转换分为显式数据类型转换和隐式数据类型转换. 1, 显式数据类型转换 a:转数字: 1)Number转换: 代码: var a = " ...