[Effective JavaScript 笔记]第56条:避免不必要的状态

Posted 脚后跟着猫

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Effective JavaScript 笔记]第56条:避免不必要的状态相关的知识,希望对你有一定的参考价值。

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条:避免不必要的状态的主要内容,如果未能解决你的问题,请参考以下文章

[Effective JavaScript 笔记] 第12条:理解变量声明提升

[Effective JavaScript 笔记]第15条:当心局部块函数声明笨拙的作用域

[Effective JavaScript 笔记]第48条:避免在枚举期间修改对象

[Effective JavaScript 笔记] 第14条:当心命名函数表达式笨拙的作用域

[Effective JavaScript 笔记]第23条:永远不要修改arguments对象

[Effective JavaScript 笔记]第17条:间接调用eval函数优于直接调用