API有时被归为两类:有状态的和无状态的。无状态的API提供的函数或方法的行为只取决于输入,而与程序的状态改变无关。字符串的方法是无状态的。字符串的内容不能被修改,方法只取决于字符串的内容及传递给方法的参数。不管程序其他部分的情况,表达式"foo".toUpperCase()总是产生"FOO"。相反,Date对象的方法却是有状态的。对于相同的Date对象调用toString方法会产生不同的结果,这取决于Date的各种set方法是否已经将Date的属性修改。

有状态的和无状态

无状态的API更容易学习和使用,更自我描述,且不易出错。但状态有时是必需的,Web的API Canvas库。就是一个有状态库,它提供了绘制形状和图片到其平面的用户界面元素方法。一个程序可以使用fillText方法绘制文本到一个画布。

c.fillText('hello,world~!',75,25);

该方法提供了一个用于绘制的字符串和画面中的位置。但其并未制定被绘制文本的其他属性,例如颜色、透明度或文本样式。所有的属性通过改变画布的内部状态来单独指定。

c.fillStyle='blue';
c.font='24pt serif';
c.textAlign='center';
c.fillText('hello,world~',75,25);

该API的无状态版本可能如下:

c.fillText('hello,world~',75,25,{
fillStyle:'blue',
font:'24pt serif',
textAlign:'center'
})

为什么后面这种无状态的版本更可取呢?
首先,它不脆弱。为了实现定制化,有状态的API需要修改画布的内部状态,这将导致绘制操作之间互相影响,即使没有关联的操作。

默认值问题

例如,默认填充样式是黑色。如果想获得默认值,必须别人没有改变默认值。如果想在默认值被修改之后使用默认值进行绘制操作,必须显示指定默认值。

c.fillText('text 1',0,0);
c.fillStyle='blue';
c.fillText('text 2',0,30);
c.fillStyle='black';
c.fillText('text 3',0,60);

相比有状态的API,无状态的API会自动重用默认值。

c.fillText('text 1',0,0);
c.fillText('text 2',0,30,{fillStyle:'blue'});
c.fillText('text 3',0,60)

注意,每个语句如何变得更可读。任何单独的调用fillText方法,不必了解它前面的所有修改。事实上,画布可能已经在程序的一些完全独立的部分被修改。其他地方的一块代码修改了画布的状态,这很容易出错。

c.fillStyle='blue';
drawMyImage(c);
c.fillText('hello,world~',75,25);

模块化问题

无状态的API会使代码更好模块化,从而避免代码不同部分相互影响,同时也使代码更易于阅读。
有状态的API,更难学习。非专业人士很难知道是否正确的初始化了所有必要的状态。当需要一个有状态的API时,你需要小心地记录这些状态依赖项。但无状态的API完全消除了隐式依赖,所以并不需要在最开始提供额外的文档。

复杂度

无状态的API的另一个好处是简洁。有状态的API往往会导致额外的声明,仅仅在调用方法前设置对象的内部状态。考虑一个流行的“INI”配置文件格式的解析器。例如,一个简单的INI文件的是这样的。

[Host]
address=172.0.0.1
name=localhost
[Connection]
timeout=10000

操作这种数据的API的一种方式是提供一个setSection方法在使用get方法查找配置参数之前选择一个区域

var ini=INI.parse(src);
ini.setSection('Host');
var addr=ini.get('address');
var hostname=ini.get('name'); ini.setSection('Connection');
var timeout=ini.get('timeout');
var server=new Server(addr,hostname,timeout);

对于无状态的API,没必要创建额外的变量在更新区域前保存提取的数据。

var ini=INI.parse(src);
var server=new Server(ini.Host.address,ini.Host.name,ini.Connection.timeout);

注意:一旦显式地指定区域,可以简单地将ini对象表示为一个字典,每个区域作为一个字典使用API更简单。

提示

  • 尽可能地使用无状态的API

  • 如果API是有状态的,标示出每个操作与哪些状态有关联

相关阅读

[Effective JavaScript 笔记]第56条:避免不必要的状态的更多相关文章

  1. [Effective JavaScript 笔记]第36条:只将实例状态存储在实例对象中

    理解原型对象与其实例之间是一对多的关系,对于实现正确的对象行为很重要.常见的错误是不小心将每个实例的数据存储到了其原型中. 示例 一个实现了树型数据结构的类可能将子节点存储在数组中. 实例状态在原型中 ...

  2. [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

    js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...

  3. [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符

    “1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...

  4. [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

    函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...

  5. [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

    js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...

  6. [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑

    构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...

  7. [Effective JavaScript 笔记]第46条:使用数组而不要使用字典来存储有序集合

    对象属性无序性 js对象是一个无序属性集合. var obj={}; obj.a=10; obj.b=30; 属性a和属性b并没有谁前谁后之说.for...in循环,先输出哪个属性都有可能.获取和设置 ...

  8. [Effective JavaScript 笔记]第45条:使用hasOwnProperty方法以避免原型污染

    之前的43条,44条讨论了属性的枚举,但都没有彻底地解决属性查找中原型污染的问题.看下面关于字典的一些操作 'zhangsan' in dict; dict.zhangsan; dict.zhangs ...

  9. [Effective JavaScript 笔记]第67条:绝不要同步地调用异步的回调函数

    设想有downloadAsync函数的一种变种,它持有一个缓存(实现为一个Dict)来避免多次下载同一个文件.在文件已经被缓存的情况下,立即调用回调函数是最优选择. var cache=new Dic ...

随机推荐

  1. 一起用HTML5 canvas做一个简单又骚气的粒子引擎

    前言 好吧,说是"粒子引擎"还是大言不惭而标题党了,离真正的粒子引擎还有点远.废话少说,先看demo 本文将教会你做一个简单的canvas粒子制造器(下称引擎). 世界观 这个简单 ...

  2. 定一个小目标:明年1024能成功转行web前端,光荣地成为一个程序员!

    第一次在博客园写博,我为什么要选择这里吗? 据说博客园这里的IT大牛如云,作为一个求知若渴的小白,我屁颠屁颠的跟着过来了. 于是今天早上兴高采烈的注册了账号,迫不及待的打开我的博客,呃!注册账号成功了 ...

  3. new-nav-js

    'use strict';define([ 'jquery'], function($) { var nav = { init : function() { $("#burger-menu& ...

  4. 使用Ps制作透明ico

    准备好图片 打开Ps新建透明图片->抠取图片->复制粘贴 保存为gif格式->使用ico在线转换即可

  5. 详解在visual studio中使用git版本系统(图文)

    很多人已经在使用git(或正在转移到git上),在github.com上,也看到园子里不少同学的开源项目,非常不错.但相关教程似乎不多,所以趁着我自己的开源项目源码托管(https://github. ...

  6. c# 多线程 调用带参数函数

    线程操作主要用到Thread类,他是定义在System.Threading.dll下.使用时需要添加这一个引用.该类提供给我们四个重载的构造函数(以下引自msdn).       Thread (Pa ...

  7. windows Server2008R2 每隔一段时间自动关机解决办法

    情况描述: “我的电脑-->右键属性”中显示“已激活”,而“管理工具”中显示未激活.系统中有进程wlms.exe. 网上找了下解决方式: 1.提权工具:PSTOOLS(下载地址:http://m ...

  8. Spring 配置文件applicationContext.xml

    Spring配置文件是用于指导Spring工厂进行Bean生产.依赖关系注入(装配)及Bean实例分发的"图纸". Spring配置文件是一个或多个标准的XML文档,applica ...

  9. str和repr的

    尽管str(),repr()和``运算在特性和功能方面都非常相似,事实上repr()和``做的是完全一样的事情,它们返回的是一个对象的“官方”字符串表示,也就是说绝大多数情况下可以通过求值运算(使用内 ...

  10. Hibernate中一级缓存和二级缓存使用详解

    一.一级缓存二级缓存的概念解释 (1)一级缓存就是Session级别的缓存,一个Session做了一个查询操作,它会把这个操作的结果放在一级缓存中,如果短时间内这个 session(一定要同一个ses ...