Node.js 中开源库探秘 object-assign | 全栈之路
2015-10-30 09:42
645 查看
这篇内容呢,讲的是另一个技术栈 Node.js 系列,虽然和咱们这里的主题不是特别吻合,不过嘛,汲取多样性的养分是快速成长的好方法,也是现在流行的全栈工程师的必经之路。
由于这篇内容涉及的是 Node.js 社区相关技术,所以要更好的读懂相关代码,还需要有一些 javascript 的基础知识。
咱们开始进入正题, Node.js 是一套服务端体系,也是非常流行和好用的框架。Node.js 社区高人辈出,比如大名鼎鼎的 TJ Holowaychuk,就是 Node 社区的大神级人物。关于他如何被称为大神的问题,大家可以参看知乎上的解答:
http://zhuanlan.zhihu.com/FrontendMagazine/19572823
其中有一句话很有意思:
要问到我是“如何”学习的——没什么特别的地方,也不读书,从不去听课,我就是去阅读别人的代码,并搞清楚那些代码是如何工作的。
确实是这样,读代码是一种最好的学习方式。特别是 Node.js 的社区资源非常丰富,数不尽的开源库。
https://github.com/sindresorhus/object-assign
它的作用也非常的简单:
这个库只对外暴露了一个函数,它的作用就是将参数中的几个对象合并到一起。简单解释一下哈。
这个调用传入了两个对象 -
并且,如果参数中对象的属性有重叠,会用后面对象参数中得属性覆盖前面的,比如:
这个最终得到的结果是
好了,关于这个库的基本使用方式咱们说完了,接下来就看看它的代码吧。
首先,来看这两行:
第一行 'use strict' 是一个代码指示,表示这个文件使用
提到这里顺便说一句,
然后我们跳过中间部分,来看最下面的部分:
赋值操作是通过一个逻辑判断来进行的,首先判断了
这个主要是基于 JS 引擎的版本考虑的,老版本的 ECMAScript 6 以下引擎是不支持 Object.assign 函数的,所以我们就必须自己实现,这个判断的作用就是这样。
接下来,如果
这个实现中,对
确实如此,
好了
接下来,通过一个循环,将后面几个参数的对象中的属性与 to 对象进行合并:
注意这个 -
然后将每个参数取出来后,临时存放到 from 变量中,然后调用
先调用了
接下来,判断了
简单来说,如果我们当前所使用的 javascript 解析引擎是支持 ES 6 的话,那么用
这样我们才能得到所有的键值。这个其实是新的 javascript 标准中的一个特性,就是 javascript 对象,现在除了属性名,每个属性名还对应了不同的
着了对 Symbol 做了简单的介绍,更加详细的描述,大家可以参考 InfoQ 的那篇文章。
最后,调用了这个方法:
现在,这个属性集合准备好了。 我们再次回到最初调用的地方:
这个
然后紧接着,遍历这个
这样,object-assign 的所有代码就都分析完了。
为什么要使用
关于这个开源库的分析就到这里啦,还是那句话,水平有限,只为抛砖引玉给大家提出一个思路,相信各位的聪明才智一定能够发现更多。
由于这篇内容涉及的是 Node.js 社区相关技术,所以要更好的读懂相关代码,还需要有一些 javascript 的基础知识。
咱们开始进入正题, Node.js 是一套服务端体系,也是非常流行和好用的框架。Node.js 社区高人辈出,比如大名鼎鼎的 TJ Holowaychuk,就是 Node 社区的大神级人物。关于他如何被称为大神的问题,大家可以参看知乎上的解答:
http://zhuanlan.zhihu.com/FrontendMagazine/19572823
其中有一句话很有意思:
要问到我是“如何”学习的——没什么特别的地方,也不读书,从不去听课,我就是去阅读别人的代码,并搞清楚那些代码是如何工作的。
确实是这样,读代码是一种最好的学习方式。特别是 Node.js 的社区资源非常丰富,数不尽的开源库。
如何使用
那么咱们就来探索一个叫做 object-assign 的开源库吧。这个库的地址在这里:https://github.com/sindresorhus/object-assign
它的作用也非常的简单:
var objectAssign = require('object-assign'); objectAssign({foo: 0}, {bar: 1}); //=> {foo: 0, bar: 1} // multiple sources objectAssign({foo: 0}, {bar: 1}, {baz: 2}); //=> {foo: 0, bar: 1, baz: 2} // overwrites equal keys objectAssign({foo: 0}, {foo: 1}, {foo: 2}); //=> {foo: 2} // ignores null and undefined sources objectAssign({foo: 0}, null, {bar: 1}, undefined); //=> {foo: 0, bar: 1}
这个库只对外暴露了一个函数,它的作用就是将参数中的几个对象合并到一起。简单解释一下哈。
var objectAssign = require('object-assign');这行代码的作用是将
objc-assign库引入到当前的代码中,并用
objectAssign符号作为引用。引入完成后,接下来就可以调用了。 比如:
objectAssign({foo: 0}, {bar: 1});
这个调用传入了两个对象 -
{foo: 0}和
{bar: 1},函数的作用就是将他们合并成一个对象,然后返回:
{foo: 0, bar: 1}。
并且,如果参数中对象的属性有重叠,会用后面对象参数中得属性覆盖前面的,比如:
objectAssign({foo: 0}, {foo: 1}, {foo: 2});
这个最终得到的结果是
{foo: 2}。 因为这次参数中的三个对象,都包含
foo属性,最后一个将前两个覆盖了。
好了,关于这个库的基本使用方式咱们说完了,接下来就看看它的代码吧。
分析代码
object-assign只有一个源文件,而且,所有的代码都在这里了:
'use strict'; var propIsEnumerable = Object.prototype.propertyIsEnumerable; function ToObject(val) { if (val == null) { throw new TypeError('Object.assign cannot be called with null or undefined'); } return Object(val); } function ownEnumerableKeys(obj) { var keys = Object.getOwnPropertyNames(obj); if (Object.getOwnPropertySymbols) { keys = keys.concat(Object.getOwnPropertySymbols(obj)); } return keys.filter(function (key) { return propIsEnumerable.call(obj, key); }); } module.exports = Object.assign || function (target, source) { var from; var keys; var to = ToObject(target); for (var s = 1; s < arguments.length; s++) { from = arguments[s]; keys = ownEnumerableKeys(Object(from)); for (var i = 0; i < keys.length; i++) { to[keys[i]] = from[keys[i]]; } } return to; };
首先,来看这两行:
'use strict'; var propIsEnumerable = Object.prototype.propertyIsEnumerable;
第一行 'use strict' 是一个代码指示,表示这个文件使用
javascript严格语法标准。紧接着的这行,是将
Object.prototype.propertyIsEnumerable方法做一个引用,以备后面使用。
提到这里顺便说一句,
javascript中的函数和变量是都可以赋值给另一个变量的,并且如果变量指向的是一个函数,也可以通过这个变量来调用它指向的函数。这点 Swift 与它比较相似。
然后我们跳过中间部分,来看最下面的部分:
module.exports = Object.assign || function (target, source) { var from; var keys; var to = ToObject(target); for (var s = 1; s < arguments.length; s++) { from = arguments[s]; keys = ownEnumerableKeys(Object(from)); for (var i = 0; i < keys.length; i++) { to[keys[i]] = from[keys[i]]; } } return to; };
module.exports是 Node.js 的一个通用变量,它表示当前模块对外导出的函数,也就是在外面调用这个模块时,是
module.exports中的内容。
赋值操作是通过一个逻辑判断来进行的,首先判断了
Object.assign方法是否存在,如果已经存在就直接用这个方法了。
这个主要是基于 JS 引擎的版本考虑的,老版本的 ECMAScript 6 以下引擎是不支持 Object.assign 函数的,所以我们就必须自己实现,这个判断的作用就是这样。
接下来,如果
Object.assign判断失败了,我们就要使用我们自己对
assigin操作的实现了:
function (target, source) { var from; var keys; var to = ToObject(target); for (var s = 1; s < arguments.length; s++) { from = arguments[s]; keys = ownEnumerableKeys(Object(from)); for (var i = 0; i < keys.length; i++) { to[keys[i]] = from[keys[i]]; } } return to; };
这个实现中,对
target变量调用了
ToObject(target)方法,实际上是对 target 做了一次非空判断,我们来看看 ToObject 的实现细节:
function ToObject(val) { if (val == null) { throw new TypeError('Object.assign cannot be called with null or undefined'); } return Object(val); }
确实如此,
ToObject对
val进行了一个判断,如果它的值为 null 就抛出异常,如果正常,就返回这个对象。
好了
ToObject分析完了,我们再回头看
assign函数。调用完成后,我们将结果存放到了
to变量中:
var to = ToObject(target);
接下来,通过一个循环,将后面几个参数的对象中的属性与 to 对象进行合并:
for (var s = 1; s < arguments.length; s++) { from = arguments[s]; keys = ownEnumerableKeys(Object(from)); for (var i = 0; i < keys.length; i++) { to[keys[i]] = from[keys[i]]; } }
注意这个 -
var s = 1;我们之所以将 s 其实值设置为 1,是因为我们要跳过第一个参数,因为第一个参数就是我们的目标, to 变量的值。
然后将每个参数取出来后,临时存放到 from 变量中,然后调用
ownEnumerableKeys函数获取 from 变量中可遍历的属性,我们再来看看 ownEnumerableKeys 函数的定义:
function ownEnumerableKeys(obj) { var keys = Object.getOwnPropertyNames(obj); if (Object.getOwnPropertySymbols) { keys = keys.concat(Object.getOwnPropertySymbols(obj)); } return keys.filter(function (key) { return propIsEnumerable.call(obj, key); }); }
先调用了
getOwnPropertyNames获取了这个对象所有的属性名。
接下来,判断了
Object.getOwnPropertySymbols方法是否存在。这个方法是干什么的呢,这时
ES 6标准新引进的一个特性,为了防止命名冲突。InfoQ 的这篇文章中有非常详细的介绍 http://www.infoq.com/cn/articles/es6-in-depth-symbols?utm_campaign=infoq_content&utm_source=infoq&utm_medium=feed&utm_term=global
简单来说,如果我们当前所使用的 javascript 解析引擎是支持 ES 6 的话,那么用
var keys = Object.getOwnPropertyNames(obj);方法并不能得到所有的属性键值,还需要进行一下这个操作:
if (Object.getOwnPropertySymbols) { keys = keys.concat(Object.getOwnPropertySymbols(obj)); }
这样我们才能得到所有的键值。这个其实是新的 javascript 标准中的一个特性,就是 javascript 对象,现在除了属性名,每个属性名还对应了不同的
Symbol之后获取了这个
Symbol后才能得到真正的属性名称。
着了对 Symbol 做了简单的介绍,更加详细的描述,大家可以参考 InfoQ 的那篇文章。
最后,调用了这个方法:
return keys.filter(function (key) { return propIsEnumerable.call(obj, key); });
filter方法会根据条件筛选数组中的元素,生成一个新的元素。筛选条件就是调用的我们最初看到的 propIsEnumerable 变量中的方法。再次判断了一下属性的有效性。
现在,这个属性集合准备好了。 我们再次回到最初调用的地方:
for (var s = 1; s < arguments.length; s++) { from = arguments[s]; keys = ownEnumerableKeys(Object(from)); for (var i = 0; i < keys.length; i++) { to[keys[i]] = from[keys[i]]; } }
这个
for循环用 ownEnumerableKeys 方法的到要遍历的后面几个参数中的有效属性的名称,存入
keys变量中。
然后紧接着,遍历这个
keys集合,将
from对象中得属性赋值给
to对象相应的属性,如果有同名的属性,就会用 from 中得值覆盖 to 的原始值。
这样,object-assign 的所有代码就都分析完了。
结论回顾
object-assign 的代码量非常少,但是经过咱们这样分析一下,是不是感觉麻雀虽小,五脏俱全呢。这里面包含了很多 javascript 的特性,以及大部分教程类书籍都不常提及的细节处理。比如:为什么要使用
module.exports = Object.assign ||function(){ ... }这样的写法。它用来判断 JS 引擎的兼容性。像是这种代码,恐怕在大多教科书类的内容中会很少强调,但在实际应用中,为了加强代码的健壮性,却是非常重要。这也是读代码学习编程的最大好处,让我们可以从实际生产环境的角度去思考问题。
关于这个开源库的分析就到这里啦,还是那句话,水平有限,只为抛砖引玉给大家提出一个思路,相信各位的聪明才智一定能够发现更多。
相关文章推荐
- 提升Object-C代码质量
- iOS纯代码实现界面建立、跳转、导航栏(无storyboard、无nib)(Objective-C)
- Objective-C的属性和成员变量用法及关系浅析
- 详谈OC(object-c)深浅复制/拷贝-什么情况下用retain和copy
- [Object C]_[初级]_[两个数组共有元素的提取,删除,数组的合并]
- iOS SDK开发时,有关OC和C++混编造成其它类错误的问题(Compile Sources As Objective-C++)
- [Objective-C]关联(objc_setAssociatedObject、objc_getAssociatedObject、objc_removeAssociatedObjects)
- iPhone How-to:解析URL中的键值对
- 《从C++到Objective-C》看Objective-C
- Objective-C学习笔记类目、协议
- Python Exception Objects
- 【Objective-C Runtime】之理解
- 一些常用的工具类方法
- objective学习笔记1
- Ljava.lang.Object;@ba8a1dc 问题
- Objective-c学习笔记之集合
- NSObject 排序
- 使用Objective-C的文档生成工具:appledoc
- django LazyObject类研究
- JsonObject&JSONArray