这篇内容呢,讲的是另一个技术栈 Node.js 系列,虽然和咱们这里的主题不是特别吻合,不过嘛,汲取多样性的养分是快速成长的好方法,也是现在流行的全栈工程师的必经之路。

由于这篇内容涉及的是 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);
}

确实如此,ToObjectval 进行了一个判断,如果它的值为 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 引擎的兼容性。像是这种代码,恐怕在大多教科书类的内容中会很少强调,但在实际应用中,为了加强代码的健壮性,却是非常重要。这也是读代码学习编程的最大好处,让我们可以从实际生产环境的角度去思考问题。

关于这个开源库的分析就到这里啦,还是那句话,水平有限,只为抛砖引玉给大家提出一个思路,相信各位的聪明才智一定能够发现更多。

Node.js 中开源库探秘 object-assign | 全栈之路的更多相关文章

  1. C蛮的全栈之路-node篇(一) 环境布置

    目录 C蛮的全栈之路-序章 技术栈选择与全栈工程师C蛮的全栈之路-node篇(一) 环境布置C蛮的全栈之路-node篇(二) 实战一:自动发博客 ---------------- 我是分割线 ---- ...

  2. C蛮的全栈之路-node篇(二) 实战一:自动发博客

    目录 C蛮的全栈之路-序章 技术栈选择与全栈工程师C蛮的全栈之路-node篇(一) 环境布置C蛮的全栈之路-node篇(二) 实战一:自动发博客 ---------------- 我是分割线 ---- ...

  3. 在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查

    Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用JavaScript开发并且能够用在Node.JS环境中,易于使用,支持多SQL方言(dialect),.它当前支持M ...

  4. [转]在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查

    本文转自:https://www.cnblogs.com/kongxianghai/p/5582661.html Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用Ja ...

  5. 如何在Node.js中合并两个复杂对象

    通常情况下,在Node.js中我们可以通过underscore的extend或者lodash的merge来合并两个对象,但是对于像下面这种复杂的对象,要如何来应对呢? 例如我有以下两个object: ...

  6. NodeBB – 基于 Node.js 的开源论坛系统

    NodeBB 是一个更好的论坛平台,专门为现代网络打造.它是免费的,易于使用. NodeBB 论坛软件是基于 Node.js 开发,支持 Redis 或 MongoDB 的数据库.它利用 Web So ...

  7. 初步揭秘node.js中的事件

    当你学习node.js的时候,Events是一个非常重要的需要理解的事情.非常多的Node对象触发事件,你能在文档API中找到很多例子.但是关于如何写自己的事件和监听,你可能还不太清楚.如果你不了解, ...

  8. node.js中对 redis 的安装和基本操作

    一.win下安装redis https://github.com/MicrosoftArchive/redis/releases 下载Redis-x64-3.2.100.zip,然后解压,放到自定义目 ...

  9. 浏览器中的 JS 和 Node.js 中的 JS

    一个是前端技术,一个是后端技术 浏览器中的 JavaScript ECMAScript  语言基础,如语法.数据类型结构.一些内置对象 BOM(Browser Object Model)  一些操作页 ...

随机推荐

  1. JVM调优2

    原文地址:https://blog.csdn.net/sun1021873926/article/details/78002118 一.什么是JVM  JVM是Java Virtual Machine ...

  2. C++设计模式实现--备忘录(Memento)模式

    一. 备忘录模式 定义:在不破坏封装性的前提下,捕获一个对象的内部状态.并在该对象之外保存这个状态. 这样以后就可将该对象恢复到原先保存的状态. 结构图: 使用范围: Memento 模式比較适用于功 ...

  3. oracle 之 COMMENT 分类: H2_ORACLE 2014-04-24 15:30 386人阅读 评论(0) 收藏

    http://blog.csdn.net/liguihan88/article/details/3002403 无疑注释现在都被大家接受和认可,在大家编程用的IDE中都提供或有第三方插件来支持提取注释 ...

  4. php javascript的ajax

    先说基础一点的get类型的ajax function loadXMLDoc() { var xmlhttp;//首先判断浏览器是否支持xmlhttprequest,因为ie56不是这个对象,是acti ...

  5. [React] Update State in React with Ramda's Evolve

    In this lesson we'll take a stateful React component and look at how we can refactor our setState ca ...

  6. jquery-6 jquery属性选择器

    jquery-6 jquery属性选择器 一.总结 一句话总结:jquery操作就是选择器加jquery对象的各种方法. 1.大量操作样式用什么方式? 大批量样式通过加类和减类完成 2.jquery中 ...

  7. [Ramda] Declaratively Map Data Transformations to Object Properties Using Ramda evolve

    We don't always control the data we need in our applications, and that means we often find ourselves ...

  8. html5中的dom中的各种节点的层次关系是怎样的

    html5中的dom中的各种节点的层次关系是怎样的 一.总结 一句话总结:Node节点是所有节点的基类,所以都继承它的方法 1.dom提供在js中动态修改html标签的作用 比如增加修改标签等,并且是 ...

  9. Linux硬件信息查询命令

    系统 uname -a              # 查看内核/操作系统/CPU信息 Linux hostname 2.6.18-128.el5 #1 SMP Wed Dec 17 11:41:38 ...

  10. sublime 内容高级搜索

    在发展中经常需要搜索内表面的文件.更好地想找到$video->getTitle() 在该文件中使用的表面.好了,这个时候就需要使用高级搜索功能,的操作,如以下: ctrl+shif+f纽带 例如 ...