一、前言

笔者在读Vue源码时, 手记一些源码中优美的代码片段,一起来学习吧

二、代码片段

1. makeMap

检测某值是否在字符串(逗号分隔的字符串)中存在, 运用了柯里化函数和缓存函数

  1. 源码鉴赏
/**
 * Make a map and return a function for checking if a key
 * is in that map.
 * map 对象中的[name1,name2,name3,name4]  变成这样的map{name1:true,name2:true,name3:true,name4:true}
 * 并且传进一个key值取值,这里用到策略者模式
 * expectsLowerCase 是否开启小写转换
 */
function makeMap(str, expectsLowerCase) {
	var map = Object.create(null); //创建一个新的对象
	var list = str.split(","); //按字符串,分割
	for (var i = 0; i < list.length; i++) {
		map[list[i]] = true; //map 对象中的[name1,name2,name3,name4]  变成这样的map{name1:true,name2:true,name3:true,name4:true}
	}
	return expectsLowerCase
		? function (val) {
				return map[val.toLowerCase()];
		  } //返回一个柯里化函数 toLowerCase转换成小写
		: function (val) {
				return map[val];
		  }; //返回一个柯里化函数 并且把map中添加一个 属性建
}

  1. 不开启小写转换使用
var isBuiltInTag = makeMap("slot,component", true);

console.log(isBuiltInTag("SlOt")); // true 找不到的返回undefined
  1. 开启小写转换使用
var isBuiltInTag = makeMap("slot,component", false);

console.log(isBuiltInTag("SlOt")); // undefined 
2. remove

删除数组中某一项, 该函数运用了indexOf,不可使用下标删除

  1. 源码鉴赏
/**
  * Remove an item from an array
  *    //删除数组
  */
function remove(arr, item) {
    if (arr.length) {
      var index = arr.indexOf(item);
      if (index > -1) {
        return arr.splice(index, 1);
      }
    }
}
  1. 使用
let arr = [{name:"张三"},{name:"李四"}]; 
let item = arr[1]; 
remove(arr, item) // 删除数组 arr 的第二项数据 | 返回值是删除的数据
3. hasOwn

检查是否为自身属性,会忽略原型链继承的属性

  1. 源码鉴赏
/**
  * Check whether the object has the property.
  * 检查对象属性是否是实例化还是原型上面的 
  * 该方法会忽略掉那些从原型链上继承到的属性,只有obj对象自身的属性才会返回true
  */
var hasOwnProperty = Object.prototype.hasOwnProperty;

function hasOwn(obj, key) {
    return hasOwnProperty.call(obj, key);
}
  1. 使用
let obj = { 
    name: "张三"
}
hasOwn(obj,'name') // true
hasOwn(obj,'__proto__') // false
4. cached

缓存函数,利用对象对字符串进行缓存

  1. 源码鉴赏
/**
 * 缓存函数,利用对象对字符串进行缓存
 * @param {*} fn 
 * @returns 
 */
function cached(fn) {
	var cache = Object.create(null);
	return function cachedFn(str) {
		var hit = cache[str];
		return hit || (cache[str] = fn(str));
	};
}
  1. 字符串转换成驼峰使用
/**
* 将连字符(-)分隔的字符串转换成驼峰写法
* 如: v-model 变成 vModel
*/
var camelizeRE = /-(\w)/g;
let camelize = cached(function (str) {
  console.log(11);
	return str.replace(camelizeRE, function (_, c) {
		return c ? c.toUpperCase() : "";
	});
});

console.log(camelize('user-name'));
console.log(camelize('user-name'));
  1. 输出
11 // 只会调用一次
userName
userName
  1. 首字母大写使用
/**
 * 将首字母大写
 */
var capitalize = cached(function (str) {
	return str.charAt(0).toUpperCase() + str.slice(1);
});

console.log(capitalize('user')); // User
  1. 驼峰转连字符使用
// \B的用法: \B是非单词分界符,即可以查出是否包含某个字,如“ABCD”中是否包含“BCD”这个字。
var hyphenateRE = /\B([A-Z])/g;
var hyphenate = cached(function (str) {
  //大写字母,加完减号又转成小写了 比如把驼峰 aBc 变成了 a-bc
  //匹配大写字母并且两面不是空白的 替换成 '-' + '字母' 在全部转换成小写
  return str.replace(hyphenateRE, "-$1").toLowerCase();
});

console.log(hyphenate('aBchFa')); // a-bch-fa
5. 改变this指向
  1. 源码鉴赏
/* istanbul ignore next */
//绑定事件 并且改变上下文指向
function polyfillBind(fn, ctx) {
    function boundFn(a) {
      var l = arguments.length;
      // 1. 无参数直接调用 .call
      // 2. 一个参数传参调用 .call
      // 3. 其余参数 fn.apply(ctx, arguments)
      return l? l > 1? fn.apply(ctx, arguments): fn.call(ctx, a): fn.call(ctx);
    }
    boundFn._length = fn.length;
    return boundFn;
}

//执行方式
function nativeBind(fn, ctx) {
    return fn.bind(ctx);
}

//bing 改变this上下文
var bind = Function.prototype.bind ? nativeBind : polyfillBind;
  1. 使用
let o = {
    a: 10,
    fn: function() {
      console.log(a);
    }
}

a = 20;
console.log(bind(o.fn, o)()); // 不传参 a: 10 

console.log(bind(o.fn, this)()); // 不传参 a: 20
  1. 使用
let o = {
	a: 10,
	fn: function (a) {
		console.log(a, this.a);
	},
};
console.log(bind(o.fn, this)(30)); // 传参 a: 30 undefined(Node环境) | 20(Window)

console.log(bind(o.fn, this)(30)); // 传参 a: 30 10
6. toArray

从数组第几位开始截取,返回新数组

  1. 源码鉴赏
/**
 * 将假的数组转换成真的数组
 * 主要用于参数截取
 */
function toArray(list, start) {
	start = start || 0;
	var i = list.length - start;
	var ret = new Array(i);
	while (i--) {
		ret[i] = list[i + start];
	}
  return ret;
}

  1. 使用
function fn(a,b,c) {
  let args = toArray(arguments, 1);
  console.log(args); // [ 2, 3 ]
}

fn(1,2,3)
7. extend

对象合并,将from合并到to对象中

  1. 源码鉴赏
/**
 * Mix properties into target object.
 * * 浅拷贝
 * 将属性混合到目标对象中。
 * 类似 Object.assgin()
 */

//对象浅拷贝,参数(to, _from)循环_from的值,会覆盖掉to的值
function extend(to, _from) {
	for (var key in _from) {
		to[key] = _from[key];
	}
	return to;
}

  1. extend使用
let o = { name: "前端小溪", obj:{ name: "张三" } };

let b = extend({}, o);

b.name ="掘金打咯"
b.obj.name = "加油"
console.log(o); // { name: '前端小溪', obj: { name: '加油' } }
console.log(b); // { name: '掘金打咯', obj: { name: '加油' } }
  1. toObject
// 将对象数组合并成一个对象
function toObject(arr) {
  var res = {};
  for (var i = 0; i < arr.length; i++) {
    if(arr[i]) {
      extend(res, arr[i]);
    }
  }
  return res;
}

let arr = [
  { name: "前端小溪", obj:{ name: "张三" }, age: 19 },
  { name: "手打", obj:{ name: "李四" } }
]

console.log(toObject(arr)); // { name: '手打', obj: { name: '李四' }, age: 19 }
8. genStaticKeys

合并对象指定字符

  1. 源码鉴赏
/**
 * Generate a static keys string from compiler modules.
 *
 *    [{ staticKeys:1},{staticKeys:2},{staticKeys:3}]
 * 连接数组对象中的 staticKeys key值,连接成一个字符串 str=‘1,2,3’
 */
function genStaticKeys(modules) {
	return modules
		.reduce(function (keys, m) {
			//累加staticKeys的值变成数组
			return keys.concat(m.staticKeys || []);
		}, [])
		.join(","); //转换成字符串
}

  1. 使用
let staticKeys = [{ staticKeys: 1 }, { staticKeys: 2 }, { staticKeys: 3 }];

console.log(genStaticKeys(staticKeys)); // 1,2,3
9. looseEqual

宽松相等

  1. 源码鉴赏
/**
 * Check if two values are loosely equal - that is,
 * if they are plain objects, do they have the same shape?
 * 检测a和b的数据类型,是否是不是数组或者对象,对象的key长度一样即可,数组长度一样即可
 */

// 松散相等

// 1. 首先判断两个值是否完全相等
// 2. 判断两个值是否为数组或对象
// 3. 如是数组或对象
//    - 为数组判断数组长度和两个值子项递归对比
//    - 为对象,将其变为keys数组,判断长度和两个值子项递归对比
// 4. 如是其他数据类型
//    - 将两值转化为字符串,判断是否相同
function isObject(obj) {
	//判断是否是对象
	return obj !== null && typeof obj === "object";
}
function looseEqual(a, b) {
	if (a === b) {
		return true;
	} //如果a和b是完全相等 则true
	var isObjectA = isObject(a);
	var isObjectB = isObject(b);
	if (isObjectA && isObjectB) {
		//如果a和都是对象则让下走
		try {
			var isArrayA = Array.isArray(a);
			var isArrayB = Array.isArray(b);
			if (isArrayA && isArrayB) {
				//如果a和b都是数组
				// every  条件判断
				return (
					a.length === b.length &&
					a.every(function (e, i) {
						//如果a长度和b长度一样的时候
						return looseEqual(e, b[i]); //递归
					})
				);
			} else if (!isArrayA && !isArrayB) {
				//或者a和b都不是数组
				var keysA = Object.keys(a); // 获取到a的key值 变成一个数组
				var keysB = Object.keys(b); // 获取到b的key值 变成一个数组
				//他们的对象key值长度是一样的时候  则加载every 条件函数
				return (
					keysA.length === keysB.length &&
					keysA.every(function (key) {
						//递归 a和b的值
						return looseEqual(a[key], b[key]);
					})
				);
			} else {
				//如果不是对象跳出循环
				/* istanbul ignore next */
				return false;
			}
		} catch (e) {
			//如果不是对象跳出循环
			/* istanbul ignore next */
			return false;
		}
	} else if (!isObjectA && !isObjectB) {
		//b和a 都不是对象的时候
		//把a和b变成字符串,判断他们是否相同
		return String(a) === String(b);
	} else {
		return false;
	}
}
  1. 验证对象相等使用
console.log(looseEqual({name: "zd"},{name: "ds"})); // false
console.log(looseEqual({},{})); // true
console.log(looseEqual([{}],[{}])); // true
  1. 验证数组某值
/**
 * 判断数组中的数组, 是否和val 相等
 */
function looseIndexOf(arr, val) {
	for (var i = 0; i < arr.length; i++) {
		if (looseEqual(arr[i], val)) {
			return i;
		}
	}
	return -1;
}

console.log(looseIndexOf([1,5,6,8],'8')); // 3 数组有返回下标,没有返回-1
10. once

函数只会执行一次

  1. 源码鉴赏
/**
 * Ensure a function is called only once.
 *  确保该函数只调用一次 闭包函数
 */
function once(fn) {
	var called = false;
	return function () {
		if (!called) {
			called = true;
			return fn.apply(this, arguments);
		}
	};
}
  1. 使用
add = (a, b) => {
	console.log("执行");
	return a + b;
};

let addOnce = once(add);
console.log(addOnce(1, 2));
console.log(addOnce(2,3));

  1. 输出
执行 // add函数只会执行一次
3
undefined

三、参考资料

  1. Vue.js v2.5.16版本

四、总结

本文从代码片段角度分析了十个源码函数。

我是前端小溪,欢迎感兴趣的同学关注下前端小溪公众号,也欢迎加我微信wxl-15153496335。

Logo

前往低代码交流专区

更多推荐