开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
【若川视野 x 源码共读】第26期 | classnames 点击了解本期详情一起参与。
今天阅读的是:Classnames
- 这个库是用来拼接
class的
尝试使用
classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'
// lots of arguments of various types
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'
// other falsy values are just ignored
classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'
- 上述可以知道,这个库可以有条件的拼接
className,通过传入一个对象,或字符串,bool值等,输出最终的拼接字符串
源码分析
- 首先,我们看下入口文件位置在
index.js
- 再看下测试用例,需要实现什么功能
// tests/index.js
it("keeps object keys with truthy values");
it("joins arrays of class names and ignore falsy values");
it("supports heterogenous arguments");
it("should be trimmed");
it("returns an empty string for an empty configuration");
it("supports an array of class names");
it("joins array arguments with string arguments");
it("handles multiple array arguments");
it("handles arrays that include falsy and true values");
it("handles arrays that include arrays");
it("handles arrays that include objects");
it("handles deep array recursion");
it("handles arrays that are empty");
it("handles nested arrays that have empty nested arrays");
it("handles all types of truthy and falsy property values as expected");
it("handles toString() method defined on object");
it("handles toString() method defined inherited in object");
- 保留值为
true的值 - 去除空字符
- 支持传数组
- 为空时返回 ' '
- 支持数组嵌套对象
toString()重写
我们来看下源码是怎么实现的
// 自执行函数
(function () {
"use strict";
var hasOwn = {}.hasOwnProperty;
function classNames() {
var classes = [];
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
// 非空判断
if (!arg) continue;
var argType = typeof arg;
// 类型判断
if (argType === "string" || argType === "number") {
classes.push(arg);
} else if (Array.isArray(arg)) {
// 如果是数组,通过apply 依次传入,执行 classNames()
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
if (arg.length) {
var inner = classNames.apply(null, arg);
if (inner) {
classes.push(inner);
}
}
} else if (argType === "object") {
// 检查参数的toString()是否重写
// Object.prototype.toString
// ƒ toString() { [native code] }
if (
arg.toString !== Object.prototype.toString &&
!arg.toString.toString().includes("[native code]")
) {
classes.push(arg.toString());
continue;
}
for (var key in arg) {
// hasOwn的自身属性 && 当前的值为true
if (hasOwn.call(arg, key) && arg[key]) {
classes.push(key);
}
}
}
}
return classes.join(" ");
}
if (typeof module !== "undefined" && module.exports) {
// export default classNames (common js)
classNames.default = classNames;
module.exports = classNames;
} else if (
// amd 模式
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;
}
})();
总结
这个库的实现流程比较简单,我们可以学到
- 考虑边界情况的处理
- 测试用例尽可能完善
- 使用
.apply()处理数组参数 - 如何判断
toString()重写