js是弱类型语言。许多标准的操作符和代码库会把输入参数强制转换为期望的类型而不是抛出错误。如果未提供额外的逻辑,使用内置操作符的程序会继承这样的强制转换行为。

functin square(x){
return x*x;
}
square("3");//9

强制转换

强制转换可以带来方便性,但也会带来相关的麻烦,一些错误无法显露出来,导致程序行为的不稳定和难以调试。
当强制转换与重载的函数一起工作的时候,结果会更难理解。上一节讲的位向量类的enable方法。该方法使用其参数的类型来决定其行为。如果enable方法尝试将其参数强制转换为一个期望的类型,那么方法签名可能会变更难理解。将方法的参数强制转换为一个数字完全破坏了重载。

BitVector.prototype.enable=function(x){
x=Number(x);//转化为数字
if(typeof x=== 'number'){//一直是正确的
this.enableBit(x);
}else{//这里永远不会执行
for(var i=0,n=x.length;i < n;i++){
this.enableBit(x[i]);
}
}
};

一般规则,在那些使用参数类型来决定重载函数行为的函数中避免强制转换参数。强制转换使用很难识别出参数的变量。

bits.enable('100');//数字还是位数组值

调用者可以合理地认为参数可以是一个数字或一个位数组值,然而我们的构造函数并不是为字符串设计的,因此无法识别它。

防御性编程

可能是调用者没有用对,但如果设计API时,强制只接收数字和对象,则可以避免出现上面的错误。

BitVector.prototype.enable=function(x){
if(typeof x=== 'number'){
this.enableBit(x);
}else if(typeof x==='object' && object){
for(var i=0,n=x.length;i < n;i++){
this.enableBit(x[i]);
}
}else{
throw new TypeError('请输入一个数或类数组对象');
}
};

enable方法的最终版本是一种风格更加谨慎的示例,被称为防御性编程。防御性编程试图以额外的检查来抵御潜在的错误。抵御所有可能的错误是不可能的。如,我们可能使用检查来确保如果x具有length属性,那么它应该是一个对象,然而这并不是安全的,比如,一个意外使用的String对象。

监视函数

js除了提供实现检查的基本工具外,比如typeof操作符,还可以编写简洁的工具函数来监视函数签名。如,可以使用一个预先检查来监视BitVector的构造函数。

function BitVector(x){
uint32.or(arrayLike).guard(x);
//...
}

借助于共享原型对象来实现guard方法以构建一个监视对象的工具库。

var guard={
guard:function(x){
if(!this.test(x)){
throw new TypeError('expected '+this);
}
}
};

每个监视对象实现自己的test方法和错误消息的字符串描述。

uint32监视对象

var uint32=Object.create(guard);
uint32.test=function(x){
return typeof x === 'number' && x === (x >>> 0);
};
uint32.toString=function(){
return 'uint32';
};

uint32的监视对象使用js位操作符的一个诀窍来实现32位无符号整数的转换。无符号右移位运算符在执行移位运算前会将其第一个参数转换为一个32位的无符号整数。移入零位对整数值没有影响。实际上uint32.test是把一个数字与该数字转换为32位无符号整数的结果做比较。

arrayLike监视对象

下面实现arrayLike的监视对象。

var arrayLike=Object.create(guard);
arrayLike.test=function(x){
return typeof x==='object' && x && uint32.test(x.length);
};
arrayLike.toString=function(){
return 'array-like object';
};

这里又进一步地采取了防御性编程来确保一个类数组对象应该具有一个无符号整数的length属性。

“链”方法

最后,实现一些原型方法的“链”方法,比如or方法。

guard.or=function(other){
var res=Object.create(guard);
var self=this;
res.test=function(x){
return self.test(x)||other.test(x);
};
var description=this+' or '+other;
res.toString=function(){
return description;
};
return res;
}

该方法合并接受者监视对象和另一个监视对象,产生一个新的监视对象。新监视对象的test和toString方法合并了这两个输入对象的方法。这里用局部的self来保存this的引用,以确保能在合成的监视对象的test方法中引用。
当遇到错误时,这些测试能帮助我们更早地捕获错误,使得它们更容易诊断。但,这也可能扰乱代码库并潜在地影响应用程序的性能。是否使用防御性编程是一个成本和收益的问题。

提示

  • 避免强制转换和重载的混用

  • 考虑防御性地监视非预期的输入

[Effective JavaScript 笔记]第59条:避免过度的强制转换的更多相关文章

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

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

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

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

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

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

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

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

  5. [Effective JavaScript 笔记]第54条:将undefined看做“没有值”

    undefined值很特殊,每当js无法提供具体的值时,就会产生undefined. undefined值场景 未赋值的变量的初始值即为undefined. var x; x;//undefined ...

  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. 用Wireshark抓包分析超过70秒的请求

    超过70秒的请求是通过分析IIS日志发现的: 10.159.63.104是SLB的内网IP. 通过Wireshark抓包分析请求是9:22:21收到的(tcp.stream eq 23080): 09 ...

  2. Coding the Matrix (0):映射、复数和域

    1. 非常好的 Python 教程 <深入 Python 3.0> 以及 IBM 开发社区的博客探索 Python. 2. 子集: s 是 S 的子集 >>>S = {2 ...

  3. 第四课:seajs的模块编译_compile过程

    最近比较闲,我就讲下seajs的模块编译_compile过程. 这里紧接着第三课的例子来讲解.首先是a.js的编译 Module.prototype._compile = function() { 1 ...

  4. JQuery ztree 异步加载实践

    本来要做一个文件目录浏览界面,需要遍历所有的文件和目录,很显然一次性读取时很费时费力的一件事情. 因此就需要做异步加载.... 不过网上的几篇帖子还挺坑的!原始参考:JQuery异步加载实例,相对来说 ...

  5. PHP乱码问题,UTF-8(乱码)

    一.HTML页面转UTF-8编码问题 1.在head后,title前加入一行: <meta http-equiv='Content-Type' content='text/html; chars ...

  6. org.hibernate.PropertyValueException: not-null property references a null or transient value:

    org.hibernate.PropertyValueException: not-null property references a null or transient value: com.bj ...

  7. PowerDesigner-如何导出建表sql脚本

    1 按照数据库类型,切换数据库. Database-> Change Current DBMS... 2 生成sql脚本 Database -> Database Generation 的 ...

  8. 缓存插件 EHCache 页面缓存CachingFilter

    Ehcache基本用法 CacheManager cacheManager = CacheManager.create(); // 或者 cacheManager = CacheManager.get ...

  9. Hibernate-入门教程

    首先了解hibernate的目录结构 . +lib antlr.jar cglib-full.jar asm.jar asm-attrs.jars commons-collections.jar co ...

  10. 44.Android之Shape设置虚线、圆角和渐变学习

    Shape在Android中设定各种形状,今天记录下,由于比较简单直接贴代码. Shape子属性简单说明一下:  gradient -- 对应颜色渐变. startcolor.endcolor就不多说 ...