《JavaScript应用程序设计》一一3.8 工厂函数

3.8 工厂函数

使用对象字面量带来的便捷是显而易见的,不过它们无法封装私有数据。封装的概念之所以在编程中具有价值,是因为它将程序内部的实现细节对使用者做了隐藏。回忆一下“四人帮”在面向对象设计模式一书中首章的描述,“面向接口编程,而不是面向实现编程”,封装将这一编码原则在代码中贯彻,即对使用者隐藏实现细节。
不过,经过前面几节的介绍,你已经对构造函数的弊病有所了解,并知晓如何去规避。下面介绍一种构造函数的替代方案:工厂函数。
工厂函数被用来构建并实例化对象,使用它的目的在于将对象构建的细节从对象使用的过程中抽象出来,在面向对象的程序设计中,工厂函数的使用范围非常广。
回到之前单例模式的例子,将单例对象通过方法调用封装起来是非常实用的,你可以将单例对象存放在一个私有变量中,随后通过闭包来获取它的引用。

function factory() {
  var highlander = {
      name: 'MacLeod'
    };

  return {
    get: function get() {
      return highlander;
    }
  };
}

test('Singleton', function () {
  var singleton = factory();
    hero = singleton.get(),
    hero2 = singleton.get();

  hero.sword = 'Katana';

  // Since hero2.sword exists, you know it's the same
  // object.
  ok(hero2.sword, 'There can be only one.');
});

使用相同的方法为car类添加停车与刹车功能:

var car = function car(color, direction, mph) {
  var isParkingBrakeOn = false;

  return {
    color: color || 'pink',
    direction: direction || 0,
    mph: mph || 0,
    gas: function gas(amount) {
      amount = amount || 10;
      this.mph += amount;
      return this;
    },

    brake: function brake(amount) {
      amount = amount || 10;
      this.mph = ((this.mph - amount) < 0) ? 0
        : this.mph - amount;
      return this;
    },

    toggleParkingBrake: function toggleParkingBrake() {
      isParkingBrakeOn = !isParkingBrakeOn;
      return this;
    },

    isParked: function isParked() {
      return isParkingBrakeOn;
    }
  };
},
myCar = car('orange', 0, 5);

test('Factory with private variable.', function () {
  ok(myCar.color, 'Has a color');

  equal(myCar.gas().mph, 15,
    '.accelerate() should add 10mph.'
  );

  equal(myCar.brake(5).mph, 10,
    '.brake(5) should subtract 5mph.'
  );

  ok(myCar.toggleParkingBrake().isParked(),
    '.toggleParkingBrake() toggles on.');

  ok(!myCar.toggleParkingBrake().isParked(),
    '.toggleParkingBrake() toggles off.');
});

与构造函数的效果一样,你将私有数据封装在了闭包中,现在唯有使用特权方法.toggleParkingBrake()才可以控制刹车杆状态。
与构造函数不同的是,你无需在工厂函数前追加new关键字(或无需担心忘记new关键字时,属性与方法的赋值会污染至全局对象)。
当然,在这里你完全可以使用原型来提升代码执行效率。

var carPrototype = {
    gas: function gas(amount) {
      amount = amount || 10;
      this.mph += amount;
      return this;
    },
    brake: function brake(amount) {
      amount = amount || 10;
      this.mph = ((this.mph - amount) < 0)? 0
        : this.mph - amount;
      return this;
    },
    color: 'pink',
    direction: 0,
    mph: 0
  },

  car = function car(options) {
    return extend(Object.create(carPrototype), options);
  },

  myCar = car({
    color: 'red'
  });

test('Flyweight factory with cloning', function () {
  ok(Object.getPrototypeOf(myCar).gas,
    'Prototype methods are shared.'
  );
});

现在工厂函数本身的代码已被精简至一行,并使用对象字典options作为其参数列表,这样一来你便可以配置那些你想要覆盖的属性。
利用原型本身所具有的特性,你完全可以在程序运行期间,对原型进行任意的属性替换操作,这里,我们使用之前所定义的carPrototype原型对象:

test('Flyweight factory with cloning', function () {
  // Swap out some prototype defaults:
  extend(carPrototype, {
    name: 'Porsche',
    color: 'black',
    mph: 220
  });

  equal(myCar.name, 'Porsche',
    'Instance inherits the new name.'
  );

  equal(myCar.color, 'red',
    'No instance properties will be impacted.'
  );
});

注意: 最好不要将对象或者数组类型的属性放置在原型上托管,万一它们在实例层面上使用,你麻烦就大了。针对这种引用类型的属性,建议在工厂函数中为每个实例单独创建一份拷贝。

时间: 2024-09-20 14:49:57

《JavaScript应用程序设计》一一3.8 工厂函数的相关文章

JavaScript高级程序设计(第3版)学习笔记9 js函数(下)_基础知识

再接着看函数--具有魔幻色彩的对象. 9.作为值的函数 在一般的编程语言中,如果要将函数作为值来使用,需要使用类似函数指针或者代理的方式来实现,但是在ECMAScript中,函数是一种对象,拥有一般对象具有的所有特征,除了函数可以有自己的属性和方法外,还可以做为一个引用类型的值去使用,实际上我们前面的例子中已经有过将函数作为一个对象属性的值,又比如函数也可以作为另一个函数的参数或者返回值,异步处理中的回调函数就是一个典型的用法. 复制代码 代码如下: var name = 'linjisong'

JavaScript高级程序设计(第3版)学习笔记8 js函数(中)_基础知识

6.执行环境和作用域 (1)执行环境(execution context):所有的JavaScript代码都运行在一个执行环境中,当控制权转移至JavaScript的可执行代码时,就进入了一个执行环境.活动的执行环境从逻辑上形成了一个栈,全局执行环境永远是这个栈的栈底元素,栈顶元素就是当前正在运行的执行环境.每一个函数都有自己的执行环境,当执行流进入一个函数时,会将这个函数的执行环境压入栈顶,函数执行完之后再将这个执行环境弹出,控制权返回给之前的执行环境. (2)变量对象(variable ob

JavaScript高级程序设计(第3版)学习笔记7 js函数(上)_基础知识

变量类型 在说函数之前,先来说说变量类型. 1.变量:变量在本质上就是命名的内存空间. 2.变量的数据类型:就是指变量可以存储的值的数据类型,比如Number类型.Boolean类型.Object类型等,在ECMAScript中,变量的数据类型是动态的,可以在运行时改变变量的数据类型. 3.变量类型:是指变量本身的类型,在ECMAScript中,变量类型就只有两种:值类型和引用类型.当变量的数据类型是简单数据类型时,变量类型就是值类型,当变量的数据类型是对象类型时,变量类型就是引用类型.在不引起

[译] ES6+ 中的 JavaScript 工厂函数(第八部分)

本文讲的是[译] ES6+ 中的 JavaScript 工厂函数(第八部分), 原文地址:JavaScript Factory Functions with ES6+ 原文作者:Eric Elliott 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m- 译者:lampui 校对者:IridescentMia.sunui Smoke Art Cubes to Smoke - MattysFlicks - (CC BY 2.0) 注意:这是"软件编写"系

javascript设计模式之对象工厂函数与构造函数详解_基础知识

下面通过文字详解加代码分析的方式给大家分享下javascript设计模式之对象工厂函数与构造函数的相关知识. 概述使用对象字面量,或者向空对象中动态地添加新成员,是最简单易用的对象创建方法.然而,除了这两种常用的对象创建方式,JavaScript还提供了其他方法创建对象.1).使用工厂函数创建对象我们可以编写一个函数,此函数的功能就是创建对象,可将其. 概述 使用对象字面量,或者向空对象中动态地添加新成员,是最简单易用的对象创建方法. 然而,除了这两种常用的对象创建方式,JavaScript还提

《JavaScript应用程序设计》一一3.3 原型

3.3 原型 原型让你能够对现有对象克隆,从而构建出一个拥有其范性方法的代理对象.工厂函数在JavaScript中,有许多灵活且简单的构造函数,所有JavaScript中的函数均可以返回对象,所以你无需再使用构造函数去构建对象.相较于构造函数,工厂函数不仅为调用者封装了对象实例化的细节,而且省略了多余的new关键字.在工厂函数中,可以组合使用JavaScript的所有语言特性,甚至能够在程序运行期间,动态修改对象的构建逻辑(而且丝毫不影响正在实例化的对象).流式API(不要与流式JavaScri

《JavaScript应用程序设计》一一3.9使用Stamps进行原型继承

3.9使用Stamps进行原型继承 对象在JavaScript中,有各式各样灵活的特性,相比之下通过Object.create()方法所构建出的对象,感觉就像被"阉割"了一样.开发者总是需要自己去编写额外的代码来实现诸如让享元对象支持数据隐蔽特性这样的特性,所以当你需要将多个对象上的功能做组合时,一般情况下你会感觉非常无力.现今多数JavaScript类库中提供了一套与类继承概念相似的对象复用机制,基于原型继承的JavaScript类库占比还是太少,所以就更别提什么业界标准了.既然少量

《JavaScript应用程序设计》一一2.19 小结

2.19 小结 我希望现在你能以一个全新的视角来看待函数,我可以将本书中的所有例子用函数式编程来改写,但前提是你要熟练掌握lambda与闭包机制.如果你想对JavaScript函数式编程继续深入了解,可以拜读Reginald Braithwaite的<JavaScript Allongé: A Strong Cup of Functions, Objects, Combinators, and Decorators>一书(https://leanpub.com/javascript-allon

《JavaScript应用程序设计》一一第3章 对象

第3章 对象JavaScript拥有原型继承.动态对象扩展.闭包等特性,在现今市面上流行的所有编程语言中,基于对象编程的JavaScript最具灵活性与表现力.在JavaScript中,你可以将诸如函数.数组.键/值对以及一些基础数据结构都视为对象,甚至一些原始数据类型在用点语法做属性操作时,也会被JavaScript隐式当作对象处理.为了能够调用原型链上的方法,原始数据类型在使用时会被临时包裹为对象,例如:'tonya@example.com'.split('@')[1]; // => exa