工厂模式

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

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. JS性能优化

    1.不要在同一行声明多个变量. 2.请使用 ===/!==来比较true/false或者数值 3.使用对象字面量替代new Array这种形式 4.不要使用全局函数. 5.Switch语句必须带有de ...

  2. Xamarin for Visual Studio V3.11.431 于 2015.4.3-2015.4.17 最新发布(Win & Mac)

    Beta Release: April 3 edited April 17 in Visual Studio Released versions: Windows Xamarin.VisualStud ...

  3. 翻译:使用 ASP.NET MVC 4, EF, Knockoutjs and Bootstrap 设计和开发站点 - 1

    原文地址:http://ddmvc4.codeplex.com/ 原文名称:Design and Develop a website using ASP.NET MVC 4, EF, Knockout ...

  4. WangSql 1.0源码共享

    一,项目背景 现在基本上大大小小的项目都需要和数据库打交道,自然而然数据库操作会有很多地方,而使用传统ADO.NET整个流程有点麻烦,出参都需要手动转换为对象.基于以上,我们需要一个SQL执行工具,能 ...

  5. c语言实现输入一组数自动从大到小排列

    #include <stdio.h>main(){    int x;    printf("请输入要排序数字个数:");    scanf("%d" ...

  6. C标准头文件<ctype.h>

    主要包括了一些字符识别和转换函数 字符判断 isalnum() //函数原型 #include<ctype.h> int isalum(int c); 功能:如果输入的字符是字母(alph ...

  7. HashMap 源码解析

    HashMap简介: HashMap在日常的开发中应用的非常之广泛,它是基于Hash表,实现了Map接口,以键值对(key-value)形式进行数据存储,HashMap在数据结构上使用的是数组+链表. ...

  8. 详细介绍Mysql各种存储引擎的特性以及如何选择存储引擎

    最近业务上有要求,要实现类似oracle 的dblink   linux版本 Server version: 5.6.28-0ubuntu0.14.04.1 (Ubuntu) 修改配置文件 /etc/ ...

  9. 使用PowerShell修改操作系统“环境变量”

      有时候我们需要命令行工具,但在使用前往往需要先导航至命令工具所在的目录,比如:stsadm 我们首先需要导航至(以SharePoint2013为例):C:\Program Files\Common ...

  10. android AsynTask处理返回数据和AsynTask使用get,post请求

    Android是一个单线程模型,Android界面(UI)的绘制都只能在主线程中进行,如果在主线程中进行耗时的操作,就会影响UI的绘制和事件的响应.所以在android规定,不可在主线中进行耗时操作, ...