精读 classnames源码,解读重点功能的实现玩转源码
前言
本文主要讲解 classnames 相关的知识点。
对 classnames 源码,按照功能模块进行解读。尤其对于源码中关键代码从实现层面做了解读。
在总结过程中,对 CSS-in-JS 写法有了不同的想法,结合大佬的文章,将想法记录在了文末。
classnames 的原理
源码目录
功能模块
目录结构
classnames
┣ 📂benchmarks
┃ ┣ 📃fixtures.js
┃ ┣ 📃run.js
┃ ┣ 📃runChecks.js
┃ ┣ 📃runInBrowser.js
┃ ┣ 📃runSuite.js
┣ 📂tests
┃ ┣ 📃bind.js
┃ ┣ 📃dedupe.js
┃ ┣ 📃index.js
┣ 📃bind.js
┣ 📃dedupe.js
┗ 📃index.js
解读时刻
底层代码
基础功能
index.js 中的代码并不是很多,做了以下几步操作。
1.定义数组 classes,一个包含所有的最终有效的 class 的数组。
var classes = [];
2.for 循环函数自带 arguments 实例,得到 classNames 函数的所有实参。当某个实参的值不存的时候,跳过这个实参,进入下一个迭代。
for (var i = 0; i < arguments.length; i ) {
var arg = arguments[i];
if (!arg) continue;
}
3.获取每个参数的数据类型,后面会根据这个类型做不同的处理。
var argType = typeof arg;
4.如果参数的数据类型是字符串或者数值,直接放到 classes 数组中。
if (argType === 'string' || argType === 'number') {
classes.push(arg);
}
5.如果参数的数据类型是数组且数组不为空时,进行递归 apply处理。将数组参数扁平化,得到扁平化之后的值存在,则放到 classes 数组中。
if (Array.isArray(arg)) {
if (arg.length) {
var inner = classNames.apply(null, arg);
if (inner) {
classes.push(inner);
}
}
}
6.如果参数的数据类型是对象时,处理相对复杂一些。
首先,对象类型的参数做了更精准的一次判断,使用 Object.prototype.toString 进行原生对象类型判断,为了进一步确保对象类型判断的准确性,还增加了对象 toString 方法的 toString() 的输出值的判断,原生函数的 toString() 会返回 "function fetch() { [native code] }"。
之后,对于不同的条件结果,进行了不同的处理:
- 当两个判断都不成立的时候,表示该参数不是原生对象,将参数调用 toString 的结果放到 classes 数组中。
- 否则,循环对象参数,将每个自有属性放到 classes 数组中。
if (argType === 'object') {
if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) {
classes.push(arg.toString());
continue;
}
for (var key in arg) {
if (hasOwn.call(arg, key) && arg[key]) {
classes.push(key);
}
}
}
7.提供了三种导出 classNames 的方式。
- module.exports 导出 classNames
- 直接返回 classNames
- 将classNames 挂载到 window 上
if (typeof module !== 'undefined' && module.exports) {
classNames.default = classNames;
module.exports = classNames;
} else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
// register as 'classnames', consistent with npm package name
define('classnames', [], function () {
return classNames;
});
} else {
window.classNames = classNames;
}
补充
为什么非继承自 Object.prototype 的对象最终使用其 toString() 返回的值。然后我就想到自定义实例对象。
function MarginTop(val) {
this.val = val;
}
const mt10 = new MarginTop('mt10');
MarginTop.prototype.toString = function mtToString() {
return `${this.val}`;
};
console.log(mt10.toString === Object.prototype.toString); // false
console.log(mt10.toString()); // 'mt10'
bind 功能
为什么有这个版本的功能呢?
这里跟大家分享一个我个人阅读源码的小技巧。
当看到的源码里没有功能的详细注释的时候,可以到 README.md 里面找一找,有没有对该功能的解释。
比如这里 bind 版本的功能,README.md 给的解释是:
当你使用[css-modules]方式,或者抽象类和实际输出到DOM的真实类做映射的方式,可能需要使用 bind 变量。
且结合使用案例,了解到 bind 功能提供了抽象类名映射的功能,所以它的实现代码与基础功能的代码区别在于确定最终的参数的值。
会先判断是否参数是否存在对应的枚举值,如果存在则将枚举值放到 classes 数组中,否则将该参数放到 classes 数组中。
// 基础功能代码
classes.push(arg);
// bind 功能代码
classes.push(this && this[arg] || arg);
dedupe 功能
dedupe 的功能主要是帮助消除类的重复数据,并确保结果集中已经排除了后面参数中指定的错误类。
相较 bind 功能,dedupe 做的处理要更多。
1.使用 Object.create() 方法创建一个新对象。可以帮助后面跳过hasOwnProperty检查。
function StorageObject() {}
StorageObject.prototype = Object.create(null);
// 去重对象
var classSet = new StorageObject();
2.利用对象的 key 值不会重复的特性,进行去重。很机智的实现方式。将所有的对象值设置为 true。
function _parseString(resultSet, str) {
var array = str.split(SPACE);
var length = array.length;
for (var i = 0; i < length; i) {
resultSet[array[i]] = true;
}
}
3.定义数组 list,一个包含所有的最终有效的 class 的数组。
var list = [];
4.将所有 value 值为 true 的 key 值放到 list 数组中。
for (var k in classSet) {
if (classSet[k]) {
list.push(k);
}
}
5.classNames 导出的处理跟基础功能一致。
测试用例
我看川哥提示「多关注测试用例」。然后我发现源码中测试用例有三个文件 bind.js 、dedupe.js 、index.js,分别对应了三种版本的测试用例,为什么会提供三个版本的测试用例呢?容我研究研究。
基础版本
想要看懂测试用例,还需要了解一些单元测试的知识点。以部分代码为例,来看看 classnames 提供的测试用例里面的都写了点什么。
源码片段
describe('classNames', function () {
it('keeps object keys with truthy values', function () {
assert.equal(classNames({
a: true,
b: false,
c: 0,
d: null,
e: undefined,
f: 1
}), 'a f');
});
it('joins arrays of class names and ignore falsy values', function () {
assert.equal(classNames('a', 0, null, undefined, true, 1, 'b'), 'a 1 b');
});
it('supports heterogenous arguments', function () {
assert.equal(classNames({a: true}, 'b', 0), 'a b');
});
......
});
名词解释
名词 |
介绍 |
参数 |
describe |
定义一组测试的函数,创建一个测试集。 |
|
it |
定义一个具体的测试用例的函数。 |
两个入参。 第一个参数是测试描述,字符串类型。 第二个是测试代码,函数类型。 |
assert |
断言函数。 |
|
assert.equal |
类似相等运算符的方法。会将第一个参数和第二个参数进行比较。 |
一般三个参数。 第一个参数是实际值。 第二个参是预测值。 第三个参数存放错误信息。 |
一组测试用例
看完名词解释,豁然开朗,这个页面定义了一组具体的测试用例。我不一个一个地列出来了,根据 it 函数里的测试描述和测试代码不难理解测试内容。
简单分享几个我理解的它要做的测试:
// 过滤错误值的入参并将正确的值连接起来
it('joins arrays of class names and ignore falsy values', function () {
assert.equal(classNames('a', 0, null, undefined, true, 1, 'b'), 'a 1 b');
});
// 支持混合类型的参数
it('supports heterogenous arguments', function () {
assert.equal(classNames({ a: true }, 'b', 0), 'a b');
});
// 空值返回空字符串
it('returns an empty string for an empty configuration', function () {
assert.equal(classNames({}), '');
});
// 支持数组类型的类名
it('supports an array of class names', function () {
assert.equal(classNames(['a', 'b']), 'a b');
});
其他版本
classnames 也提供了bind 版本和dedupe 版本的测试用例。
基准测试
这个概念我第一次听到。于是我百度了一下相关知识点。
首先,百科的介绍是
基准测试是指通过设计科学的测试方法、测试工具和测试系统,实现对一类测试对象的某项性能指标进行定量的和可对比的测试。
其次,为什么需要基准测试?因为基准测试可以帮忙测试代码的性能。
最后,我观察了一下 classnames 中的代码。基于 fixtures.js 文件中提供的不同数据类型的组合和功能文件不同的引入方式,进行了测试。
// 本地文件
suite.add('local#' fixture.description, function () {
local.apply(null, fixture.args);
});
// npm下载的文件
suite.add(' npm#' fixture.description, function () {
npm.apply(null, fixture.args);
});
// dedupe版本
suite.add('local/dedupe#' fixture.description, function () {
dedupe.apply(null, fixture.args);
});
// other handling
suite.on('complete', function () {
log('\n> Fastest is' (' ' this.filter('fastest').map(result => result.name).join(' | ')).replace(/\s /, ' ') '\n');
});
总结
文章总结
文章由浅入深的详细介绍了 classnames 是什么、怎么用。并且对它的源码中三个模块底层代码、测试用例、基准测试做了详细解读。
此外,我由 classnames 的用法引发了对 CSS-in-JS 的一些思考。目前在灵活用法和性能消耗之间有点难抉择的。希望未来能够有明确的答案。
建议
如果有空闲的时间,对于开源的技术,推荐多读读源码,有些想不到的地方、开发思维或者设计模式,可以通过源码阅读悟出来、提炼出来。
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhifkgie
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
excel下划线不显示怎么办
PHP中文网 06-23 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
微信运动停用后别人还能看到步数吗
PHP中文网 07-22 -
excel图片置于文字下方的方法
PHP中文网 06-27