转:http://www.cnblogs.com/tolg/p/4873000.html

没有哪种数据结构比JavaScript的对象更简单灵活了。作为一个弱动态类型语言,JavaScript对对象的属性没有任何约束, 这带来的结果就是,在使用的时候很爽,想加啥属性直接加上去就行了,根本没有类或模板的限制, 想读啥属性直接“点”出来就行了,写出来那是相当简洁;然而这样的代码在运行的时候呢……

JavaScript这种灵活性最大的一个问题也是没有约束。比如一个网店系统有两个部分,一部分产生订单对象, 另一部分拿到订单对象来展示。咱们前端程序员自然是干后面展示那部分事儿的,比如要在页面上展示个订单里面商品的价格, 就会掉order.product.price.sum。然而写产生订单哪部分代码的哥们儿不一定靠谱, 订单里有个赠品就是没价格,price字段干脆没有,前端还是调order.product.price.sum,得,js代码执行到这就算死了, 后面啥也出不来了。更可怕的是现在很多系统引入了node,把前后端分离界限往后移了一截, 这回在node上调用order.product.price.sum,得,整个页面都出不来了。

是不是觉得这个例子挺low的?估计你们公司已经规定好了订单数据里的产品一定会有price字段,哪怕真没价格后端大哥也会给你传个{}。 况且人家后端用的是java,强静态类型语言,各种属性早就定义好了,咋能丢呢? 再说上线前要测试那么长时间,怎么会在线上出这种问题呢?

现在JSON已经是横飞于网络之间的标准数据结构,即便你们系统后端是用java写的,前端调用这个写后端接口时得到的还是JSON。 可是这个JSON是从啥转过来的呢?你指望一定是某个类的对象吗?够呛。后端大哥也许早发现了Java太死板, 为了贴合灵活的JSON,人家早用map了。再说,即便后端老老实实的用class,如果你们的网店系统很大, 有好几个店不同种类的订单,这些订单恐怕不是出自于同一个class吧,字段难免有差别,你能保证你们开发团队文档完整到你能掌握所有类的属性吗?

反正我们没有这么全的文档,即使有时候有点文档,后端大哥也会善意的告诉我:以代码为准!

如果想在上线前把这种缺失字段的bug全都测出来,我觉得太难为QA兄弟了,后端的数据又不是他们说了算, 保不齐上线很长时间后突然真给少点啥。

人总是不靠谱的,还得从代码上想办法。

最直接的办法是小心翼翼步步为营:在每调用一个有风险的属性的时候都判断一下其父节点是否存在。 这判断谨慎到什么程度就要估计一下数据来源的靠谱程度了。对于前面商品总价的例子,一个订单里总不会连商品都没有吧, 所以代码可以是这个样子:

var showPrice
if(order.product.price)
showPrice = order.product.price

实际情况往往比较复杂,比如我现在要取的数据是订单中一款产品的生产厂家的联系方式里面若干电话中的第一个, 而且我不太信任后端接口,连订单中有没有产品都不敢保证,那么……

var temp, temp1, temp2, temp3, phone;
if ((temp = order.product) != null) {
if ((temp1 = temp.seller) != null) {
if ((temp2 = temp1.contact) != null) {
if ((temp3 = temp2.phone) != null) {
phone = temp3[0];
}
}
}
}

如此拙劣!如果JS要这么写我宁愿回头做后端去写Java!

并没有~~ ヾ(¯∇ ̄๑)

我用coffeescript。其实我们现在所做的项目没有构建与coffee之上的,但是我用coffee,怎么用是另一个话题, 反正用了coffee,这段代码就是这样:

phone = order.product?.seller?.contact?.phone?[0]

遗憾的是,我是个多少有些强迫症的人,每当看到那么多问号就会联想到编译后的那一堆if,我都替电脑累。 再说这问号很容易遗漏,悄悄地少个问号很难发现。总之我只会在我自认为有可能缺失的属性前面加个问号。

其实做这些判断要做的无非就是一旦调用链上某个属性缺失就直接返回undefined。js本身不提供这玩意儿,自己封装一个。

Object.prototype.getAttr = function(path){
attrLink = path.split('.');
var ref = this[attrLink[0]];
for(var i=1; i < attrLink.length; i++){
if(ref)
ref = ref[attrLink[i]];
else
return undefined;
}
return ref;
}

于是访问属性就成了这样:

order.getAttr('product.seller.contact.phone.0')

这样就安全了。可是调试的时候我宁愿让解释器用红色的异常告诉我到底是谁没定义或者是空也不想悄咪咪的就得到一个未定义值。 办法是有的,上面的代码在ref为空的时候输出个日志就行了。最后的问题是getAttr方法并非是获取属性的强制方法, 它不像Java,只要把属性私有了就只能用getter。别说保证其它小伙伴一定要用这个方法取属性,就连我自己可能都写着写着就直接点上了。 最终,这个Object.getAttr的办法我在实际开发中从未用过。

感觉上前面那种在cofee里面加问号的办法也就够了,如果我们自己都觉得这个属性必须得有而后端接口就是没传那也太那个啥了。 但真实情况是一切皆有可能,这也不能怪后端大哥,没准这订单数据是从其他系统发送过来的或者是很古老的奇葩数据。

咋闹?我的原则是尽可能把损失降到最低,也就是把异常影响的代码范围缩到最小。这就要靠try..catch。

在哪儿try要看代码的情况,总不可能逮哪try哪。不过还是有些规律的。

那些不靠谱的数据应该不是我们自己写的,而是来源于外部接口,对这个接口数据利用的代码肯定是有一定范围的, 最起码的情况是把这一段给try了。这还太粗糙。在看看数据的特征,大量外部数据往往是可迭代的, 比如会显示一个订单列表,对这个订单列表肯定会有个循环,那么针对循环内部try一下,这样顶多是一个订单无法显示。 还有局部数据有没有统一处理的地方,比如我做的node项目使用handlebars模板,模板会用到很多自定义的helper, 这些helper大多数是用于把数据处理成显示的结果,比如格式化价格什么的。比较巧的是这些helper都有一个统一的代码入口:

helpers =  _.extend(
require('./helper1'),
require('./helper2'),
require('./helper3')
)
// “_”是underscore或者lodash

其实也就是把各个文件中的helpers整合成了一个大对象,然后:

module.exports = _.reduce(helpers, function(memo, f, k){
memo[k] = function(){
try {
return f.apply(this, arguments)
} catch (e) {
console.error('handlebars helper error', e)
return ''
}
}
return memo
}, {})

在export前把所有helper函数都包裹在一个try...catch中。这样在一个helper中有字段缺失的错误也就会引起一小点东西显示不出来。

其实我把所有的helper集中到一个大对象中其初衷并非是为了这个,而是由于把每一个helper文件都注册一遍看起来重复太多,觉得不爽。 不是有一个著名的代码维护原则大概是说“任何重复的代码都应该避免”吗?这就看出来好处了。

好像看起来完美了,也不然。后来一哥们儿又写了个helper,没放到我的这些helpers里面,而是在别的文件中单独注册去了, 结果奇葩的数据真的出现了,悲剧真的防不胜防地上演了。。。

好吧,说到底想尽各种奇技淫巧也抵不过不按规矩出牌。其实要真的前后端所有同志把数据格式规定好了,并且严格执行, 我写的这一大堆全是废话。

JavaScript的对象——灵活与危险的更多相关文章

  1. JavaScript原生对象及扩展

    来源于 https://segmentfault.com/a/1190000002634958 内置对象与原生对象 内置(Build-in)对象与原生(Naitve)对象的区别在于:前者总是在引擎初始 ...

  2. javascript 全局对象--w3school

    JavaScript全局对象 1.  decodeURI()解析某个编码的URI. 2.decodeURInComponent()解析一个编码的URI组件. 3.encodeURI()把字符串编码为U ...

  3. JavaScript Json对象和Json对象字符串的关系 jsonObj<->JsonString

    JavaScript Json对象和Json对象字符串的关系 jsonObj<->JsonString 如下示例: 直接写的a1就是一个Json对象,a2 就是一个Json对象字符串; 通 ...

  4. 从零构建JavaScript的对象系统

    一.正统的类与继承 类是对象的定义,而对象是类的实例(Instance).类不可直接使用,要想使用就必须在内存上生成该类的副本,这个副本就是对象. 以Java为例: public class Grou ...

  5. 关于javascript自定义对象(来自网络)(最近几天不会的)

    javascript定义对象的几种简单方法 1.构造函数方式,全部属性及对象的方法都放在构造方法里面定义 优点:动态的传递参数 缺点:每创建一个对象就会创建相同的方法函数对象,占用大量内存 funct ...

  6. 据说每个大牛、小牛都应该有自己的库——JavaScript原生对象拓展

    在据说每个大牛.小牛都应该有自己的库——框架篇中我扬言要做个小牛,没想到一天没更新,小伙儿伴们就戏谑的问我,油哥是不是要太监了?其实事情是这个样子的,这不是太监的节奏,一是,关于写个自己的库的想法由来 ...

  7. JavaScript RegExp 对象

    JavaScript RegExp 对象 RegExp 对象用于规定在文本中检索的内容. 什么是 RegExp? RegExp 是正则表达式的缩写. 当您检索某个文本时,可以使用一种模式来描述要检索的 ...

  8. JavaScript String 对象

    JavaScript String 对象 String 对象 String 对象用于处理文本(字符串). String 对象创建方法: new String(). 语法 var txt = new S ...

  9. JavaScript Math 对象

    JavaScript Math 对象 Math 对象 Math 对象用于执行数学任务. Math 对象并不像 Date 和 String 那样是对象的类,因此没有构造函数 Math(). 语法 var ...

随机推荐

  1. ORA-14450

    ORA-14450 attempt to access a transactional temp table already in use Cause: An attempt was made to ...

  2. Linq的查询操作符

    Linq有表达式语法和调用方法的语法.两者是可以结合使用,通常情况下也都是结合使用.表达式语法看上去比较清晰而调用方法的语法实现的功能更多,在此文章中介绍的是表达式语法.方法语法可以看System.L ...

  3. java开发webservice

    第一部分:相关下载配置 1.开发环境   eclipse-jee-mars-2-win32-x86_64.zip  http://www.eclipse.org/downloads/index-pac ...

  4. (原)C++中测试代码执行时间

    转载请注明出处(不过这个用法网上到处都是): http://www.cnblogs.com/darkknightzh/p/4987738.html LARGE_INTEGER nFreq, nBegi ...

  5. PDO基础知识

    使用PDO之前首先开启PHP的PDO扩展,方法见百度. PDO连接数据库的方式有三种 1.通过参数的形式连接数据库 (推荐) //通过参数形式连接数据库 try{ $dsn = 'mysql:host ...

  6. llinux之sudo配置

    1.使用visudo来配置,因为visudo在配置完毕后,会检查是否有语法错误. 2.配置格式: 授权账号    授权账号所在hostname=(可切换的账号)    可执行的命令command(如果 ...

  7. vim编辑器参数(不熟参数)

    -1 vim配置文件 全局配置:/etc/vimrc 个人配置:~/.vimrc -2 :set下相关常用参数 ic 忽略大小写 noic  不忽略(默认) ai 自动缩进 noai  不自动缩进(默 ...

  8. theos初探:ios越狱开发教程

    开发环境搭建回顾 现在已经在windows上安装好了theos了.在上一篇中都已经讲了,开发环境主要部件就是: 1.theos,主要包含了使用make时的makefile模板文件.包含了各种库和框架的 ...

  9. Obstack是C标准库里面对内存管理的GNU扩展

    Obstack是C标准库里面对内存管理的GNU扩展 Obstack介绍 Obstack初始化 在Obstack中申请对象 释放对象 申请growing object 获取Obstack状态 数据对齐 ...

  10. 用lsb_release -a 查看linux版本

    1.要通过yum 安装上这个命令的软件包 yum -y install redhat-lsb 2.lsb_release -a 查看linux版本信息