【若川视野 x 源码共读】第26期 | classnames

83 阅读2分钟

用法

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'

var arr = ['b', { c: true, d: false }];
classNames('a', arr); // => 'a b c'

let buttonType = 'primary';
classNames({ [`btn-${buttonType}`]: true });

源代码

//自执行函数
(function () {
	'use strict';

	var hasOwn = {}.hasOwnProperty;

	function classNames() {
		var classes = [];

  	//遍历传参
		for (var i = 0; i < arguments.length; i++) {
			var arg = arguments[i];
      //过滤null、undefined、false、0等非法字符
			if (!arg) continue;

			var argType = typeof arg;
    	//是string或者number类型
			if (argType === 'string' || argType === 'number') {
				classes.push(arg);
			} else if (Array.isArray(arg)) {   //是数组
        //空数组
				if (arg.length) {
          //递归遍历数组的内容
					var inner = classNames.apply(null, arg);
					if (inner) {
						classes.push(inner);
					}
				}
			} else if (argType === 'object') {  //对象 
        //({ha:true}).toString() '[object Object]'
				//({}).toString() "[object Object]"   
        //Object.prototype.toString [native code]
        //arg.toString.toString() 'function toString() { [native code] }'
        //不是空对象的话                                     
      	//不是原生对象,例如Object.toString();//"function Object() { [native code] }"
        //String.toString();//"function String() { [native code] }"
     		//自定义var person1 = new Person();
				//person1.toString();//"[object Object]"
      	//重写了则结果集合中加入调用toString
				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);
					}
				}
			}
		}
		//结果数组使用空格分隔
		return classes.join(' ');
	}

  //导出classnames
	if (typeof module !== 'undefined' && module.exports) {
		classNames.default = classNames;
		module.exports = classNames;   //require.js  
	} else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
		// register as 'classnames', consistent with npm package name
		define('classnames', [], function () {   //CMD
			return classNames;   
		});
	} else {
		window.classNames = classNames;  //挂载到window
	}
}());

对象则检查是否重写了toString方法。如果重写了则结果集合中加入调用toString方法后的值,如果没有重写则依次检查对象的每一个自有属性,为真值则加入到结果集合中。

为什么重写toString()?

对象的原始的toString()函数是位于该对象的原型的原型对象中的,我们只要在该对象的原型中新建一个toString()函数就可覆盖对象的原始的toString()函数。

 function Person(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
}
Person.prototype.toString = function(){
    return "Person[name="+this.name+",age="+this.age+",gender="+this.gender+"]";
}

//创建一个Person实例
var p1 = new Person("曹阿瞒",77,"男");


//当我们直接在页面中打印一个对象时,实际上是输出的对象toString()方法的返回值
var result = p1.toString();