undefined值很特殊,每当js无法提供具体的值时,就会产生undefined。

undefined值场景

未赋值的变量的初始值即为undefined。
var x;
x;//undefined
访问对象不存在的属性也会产生undefined。
var obj={};
obj.x;//undefined
一个函数体结尾使用未带参数的return语句,或未使用return语句都会返回值undefined。
function f(){
return;
}
function g(){}
f();//undefined
g();//undefined
未给函数参数提供实参则该参数的值为undefined。
function f(x){
return x;
}
f();//undefined

这几种情况下undefined值表明操作结果并不是一个特定的值。或说叫做“没有值”,但undefined是一个值,很自相矛盾。因为js里的每个操作都要产生点什么,所以就用undefined来填补这些空白了。

undefined公约

将undefined看做缺少某个特定值是js里的公约。将它用于其他的目的,可能会产生严重的问题。例如,一个用户界面元素库可能支持一个highlight方法用于改变一个元素的背景颜色。

element.highlight();//使用默认的颜色
element.highlight('yellow');//使用黄色

如果想提供一种方法来设置一个随机颜色,可能会使用undefined作为特殊的值来实现。

element.highlight(undefined);//使用随机颜色

但上面的代码会产生一个歧义,因为undefined在不传入实参的时候,形参的值也是undefined。所以程序无法在默认颜色,还是随机颜色之间做决定。程序产生了二义性无法正确运行。
再比如,程序可能用一个可选的颜色偏好的配置对象。

var config=JSON.parse(preferences);
//...
element.highlight(config.highlightColor);

如果config.highlightColor并没有设定,那么它的值也会是undefined。这个时候程序员可能希望得到一个默认的颜色。但由于上面的undefined被做为一个特殊的值,这样像上面说的产生了二义性,产生了一个随机的颜色值。更好的方式是对可能会使用一种特殊的颜色名来实现随机颜色。

element.highlight('random');

有时一个API不能够从通常函数可接受的字符串集合中区分出一个特殊的字符串值。在这种情况下,可以使用除undefined以外的其他特殊值,如null或true。这往往会导致可读性下降。

element.highlight(null);

如果阅读代码的人没有记住这个函数的具体使用方法,那么这样的代码很难理解。可能会给人以取消元素高亮的误解。一个更具明确、更具描述性的可选做法是将随机情况表示为一个具有random属性的对象

element.highlight({random:true});

可选参数的实现

undefined有可能出问题的地方是可选参数的实现。理论上arguments对象可检测是否传递了一个参数,但实际上,测试是否为undefined会使API更健壮。例如,一个Web服务器可以接收一个可选的主机名称。

var s1=new Server(80,'example.com');
var s2=new Server(80);//默认为localhost

参数长度

可以通过判断arguments.length来实现Server构造函数。

function Server(port,hostname){
if(arguments.length<2){
hostname='localhost';
}
hostname=''+hostname;
}

上面的这个实现也是会产生问题,当我第二个参数显式传递从其他源请求的一个值。那么可能变成传递的是一个undefined值,但这个时候arguments.length的值是2,代码被破坏。

var s3=new Server(80,config.hostname);

上面代码如果config.hostname没有值,hostname的正常行为应该是设置为'localhost'。上面的代码会产生一个undefined的主机名。

检测参数是否为undefined

最好是测试undefined,不传递参数和传递一个表达式结果是undefined的情况都可以覆盖。

function Server(port,hostname){
if(hostname === undefined){
hostname='localhost';
}
hostname=''+hostname;
//...
}

测试参数是否为真

另一种替代方案是测试hostname是否为真。使用逻辑运算符很好实现

function Server(port,hostname){
hostname=''+(hostname||'localhost');
//...
}

|| 逻辑运算符或。当第一个参数为真值则会返回第一个参数,否则返回第二个参数。所以hostname值为undefined或空字符串,该表达式(hostname||'localhost')的结果是'localhost'。这里把hostname的值测试了很多种情况,包括其他能转化为false的值。这里对于Server函数是可以的。真值测试是实现参数默认值的一种简明的方式。

真值测试

真值测试并不是总是安全的。如果一个函数应该接收空字符串为合法值,真值测试将覆盖空字符串并返回默认值。还有其它的可以转化为false的值,则不应该使用真值测试。例如,一个用于创建用户界面元素的函数可能允许一个元素的宽度或高度为0,但提供默认值却不一样。

var c1=new Element(0,0);//width:0 height:0
var c2=new Element();//width:320 height:240

使用真值测试的实现会出现问题

function Element(w,h){
this.width=w||320;
this.height=h||240;
}
var c1=new Element(0,0);
c1.width;//320
c1.height;//240

这里就不能使用真值而应该使用undefined检测

function Element(w,h){
this.width=w===undefined?320:w;
this.height=h===undefined?240:h;
}
var c1=new Element(0,0);
c1.width;//320
c1.height;//240
var c2=new Element();
c2.width;//320
c2.height;//240

提示

  • 避免使用undefined表示任何非特定值

  • 使用描述性的字符串值或命名布尔属性的对象,而不要使用undefined或null来代表特定应用标志

  • 提供参数默认值应当采用测试undefined的方式,而不是检查arguments.length

  • 在允许0、NaN或空字符串为有效参数的地方,绝不要通过真值测试来实现参数默认值

相关阅读

[Effective JavaScript 笔记]第54条:将undefined看做“没有值”的更多相关文章

  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 笔记]第28条:不要信赖函数对象的toString方法

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

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

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

  5. [Effective JavaScript 笔记]第55条:接收关键字参数的选项对象

    53节建议保持参数顺序的一致约定对于帮助程序员记住每个参数在函数调用中的意义很重要.参数较少这个主意不错,但如果参数过多后,就出现麻烦了,记忆和理解起来都不太容易. 参数蔓延 如下面这些代码: var ...

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

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

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

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

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

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

  9. [Effective JavaScript 笔记] 第6条:了解分号插入的局限

    分号可以省略 js可以在语句结束不强制加分号.(建议还是添加,不添加分号往往会出现不易发现的BUG) function Point(x,y){ this.x=x||0; this.y=y||0; } ...

随机推荐

  1. 微信小程序开发:http请求

    在微信小程序进行网络通信,只能和指定的域名进行通信,微信小程序包括四种类型的网络请求. 普通HTTPS请求(wx.request) 上传文件(wx.uploadFile) 下载文件(wx.downlo ...

  2. 4G时代的抢钱之道

    最近最大的事件恐怕就是移动的4G上线了,50元600M起计价,上网速度最高理论到达100Mbps,相当于一秒下载10MB的数据,或者是一分钟下载一张VCD(600M)电影. 当然,对于这样的流量套餐, ...

  3. [设计模式] javascript 之 工厂方法模式

    1. 简单工厂模式 说明:就是创建一个工厂类,里面实现了所对同一个接口的实现类的创建. 但是好像JavaScript 好像没有 接口 这号东西,所以我们去掉接口这个层; 当然,我们这里的 实现类 下的 ...

  4. CSS3——动画效果

    CSS3动画在Style里面就实现了以往我们用JQ写的动画效果,着实简便了不少~ 简单Demo: html代码: <div id="dv1"></div> ...

  5. [转]DBA,SYSDBA,SYSOPER三者的区别

    原文地址:http://www.oracleonlinux.cn/2010/02/dba_sysdba_sysoper/ 什么是DBA?什么是SYSDBA,什么又是SYSOPER?三者究竟有何联系呢? ...

  6. zabbix_监控_进程

        一.根据进程名称监控 1.创建Item(只能通过进程名.用户过滤进程)  http://www.2cto.com/os/201405/302249.html http://www.ithao1 ...

  7. Mysql-字段类型

    首先统计所有,以表格查看 数字类型 列类型 需要的存储量 TINYINT 1 字节 SMALLINT 2 个字节 MEDIUMINT 3 个字节 INT 4 个字节 INTEGER 4 个字节 BIG ...

  8. Java-ArrayList和Vector的区别

    这两个类都实现了List接口(List接口继承了Collection接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引号取出某个元素, ...

  9. awk多文件操作

    awk 多文件操作2种实现方法 我们经常会将2个有关联文本文件进行合并处理.分别从不同文件获取需要的列,然后, 整体输出到一起.awk进行多文件处理时候,常常会遇到2个方面问题,第一个是怎么样合并多个 ...

  10. sprintf、strcpy和memcpy的区别

    做某题用到了sprintf把一个字符数组(字符串)写到二维字符数组里,然后耗时挺长的,想了想strcpy好像也可以,事实证明strcpy效率果然更高,然后想了想觉得memcpy好像也可以.实践了一下的 ...