这篇内容呢,讲的是另一个技术栈 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. java连接MongoDB查询导出为excel表格

    背景 因为项目需求.每一个星期须要统计每一个公众号7天的訪问数,月底就须要统计一个月的訪问数,40多个公众号.每次手动统计都须要花费1个小时,总之是一项无技术耗时耗神的手工活. 于是.想写个程序来统计 ...

  2. Sphinx+MySQL5.1x+SphinxSE+mmseg中文分词

    什么是Sphinx Sphinx 是一个全文检索引擎,一般而言,Sphinx是一个独立的搜索引擎,意图为其它应用提供快速.低空间占用.高结果相关度的全文搜索功能.Sphinx能够很easy的与SQL数 ...

  3. .NET Framework基础知识(三)(转载)

    .正则表达式:用一串字符验证是否符合一种规范,这个规范就是正则表达式. .正则表达式中常用的元字符: . 匹配除换行符以外的任意字符 \w 匹配字母或数字或下划线或汉字 \s 匹配空白符 \d 匹配数 ...

  4. 【读书笔记与思考】Andrew 机器学习课程笔记

    Andrew 机器学习课程笔记 完成 Andrew 的课程结束至今已有一段时间,课程介绍深入浅出,很好的解释了模型的基本原理以及应用.在我看来这是个很好的入门视频,他老人家现在又出了一门 deep l ...

  5. javascript数组全排列,数组元素所有组合

    function permute(input) { var permArr = [], usedChars = []; function main(input){ var i, ch; for (i ...

  6. .NET-架构优化实战-底层服务优化

    原文:.NET-架构优化实战-底层服务优化 前言 问题分析 在本系列第一篇文章我们提到,底层问题主要存在以下两点: 代码冗余 时效低 代码冗余 例如: 领奖方法不统一,一次性的写一套,可循环的又写一套 ...

  7. swf loading 自身

    stop(); import flash.net.URLRequest; import caurina.transitions.Tweener; loaderInfo.addEventListener ...

  8. 忙里偷闲( ˇˍˇ )闲里偷学【C语言篇】——(7)结构体

    一.为什么需要结构体? 为了表示一些复杂的事物,而普通类型无法满足实际需求 二.什么叫结构体? 把一些基本类型组合在一起形成的一个新的复合数据类型叫做结构体. 三.如何定义一个结构体? 第一种方式: ...

  9. 历届图灵奖 (Turing award)得奖名单

    历届图灵奖 (Turing award)得奖名单 一.总结 一句话总结:各个方面都有. 二.历届图灵奖 (Turing award)得奖名单 Turing奖最早设立于1966年,是美国计算机协会在计算 ...

  10. svn删除文件或文件夹后提交失败及解决

    svn删除文件夹后提交显示Item 'XXXX' is out of date 有这么几种可能, 1.别人已经提交代码.恰好这个文件或文件夹有改动,这样的情况须要先回复再更新再删除再提交. 2.没有人 ...