工厂模式

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

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. CSS知识总结(六)

    CSS常用样式 4.段落样式 1)行高 控制段落内每行高度 line-height : normal | length 例子 源代码: /* CSS代码 */ .normal{ line-height ...

  2. 利用Python进行数据分析(3) 使用IPython提高开发效率

      一.IPython 简介 IPython 是一个交互式的 Python 解释器,而且它更加高效. 它和大多传统工作模式(编辑 -> 编译 -> 运行)不同的是, 它采用的工作模式是:执 ...

  3. Node基础篇(文件操作)

    文件操作 相关模块 Node内核提供了很多与文件操作相关的模块,每个模块都提供了一些最基本的操作API,在NPM中也有社区提供的功能包 fs: 基础的文件操作 API path: 提供和路径相关的操作 ...

  4. Redis命令拾遗二(散列类型)

    本文版权归博客园和作者吴双共同所有,欢迎转载,转载和爬虫请注明原文地址 :博客园蜗牛NoSql系列地址  http://www.cnblogs.com/tdws/tag/NoSql/ Redis命令拾 ...

  5. Cocoapods无法使用/安装失败/失效解决方法

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px "Helvetica Neue"; color: #666666 } sp ...

  6. 一个java文件中可包含多个main方法

    java中的main方法是java应用程序的入口,java程序在运行时,首先调用执行main方法.但并不是说java中只能有一个main方法,不同类中都可以包含main方法.当JVM进行编译时,会提示 ...

  7. Quartz —— Spring 环境下的使用

    一.在 Spring 环境下 Quartz 的使用超级简单. 二.具体使用 1.添加对应的 spring-quartz 的配置文件. 2.新建要执行定时任务的目标类和目标方法,不需要继承 Job 接口 ...

  8. 【初探IONIC】不会Native可不可以开发APP?

    前言 Hybrid技术流行已经有一段日子了,楼主的关注点也一直围绕着移动端围绕着Hybrid相关展开,Hybrid已经是大大提升开发效率的开发方式了,但是仍然需要至少一个IOS与Andriod,那么可 ...

  9. MapFile生成WMS

    MAP  NAME "HBWMS"  STATUS ON  SIZE 800 600  EXTENT 107.795 28.559 116.977 33.627  UNITS ME ...

  10. Android中使用ExpandableListView实现好友分组

    一个视图显示垂直滚动两级列表中的条目.这不同于列表视图,允许两个层次,类似于QQ的好友分组.要实现这个效果的整体思路为: 1.要给ExpandableListView 设置适配器,那么必须先设置数据源 ...