vue源码中优秀代码片段(一)
笔者在读Vue源码时, 手记一些源码中优美的代码片段,一起来学习吧。
·
一、前言
笔者在读Vue源码时, 手记一些源码中优美的代码片段,一起来学习吧
二、代码片段
1. makeMap
检测某值是否在字符串(逗号分隔的字符串)中存在, 运用了柯里化函数和缓存函数
- 源码鉴赏
/**
* 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中添加一个 属性建
}
- 不开启小写转换使用
var isBuiltInTag = makeMap("slot,component", true);
console.log(isBuiltInTag("SlOt")); // true 找不到的返回undefined
- 开启小写转换使用
var isBuiltInTag = makeMap("slot,component", false);
console.log(isBuiltInTag("SlOt")); // undefined
2. remove
删除数组中某一项, 该函数运用了indexOf,不可使用下标删除
- 源码鉴赏
/**
* 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);
}
}
}
- 使用
let arr = [{name:"张三"},{name:"李四"}];
let item = arr[1];
remove(arr, item) // 删除数组 arr 的第二项数据 | 返回值是删除的数据
3. hasOwn
检查是否为自身属性,会忽略原型链继承的属性
- 源码鉴赏
/**
* Check whether the object has the property.
* 检查对象属性是否是实例化还是原型上面的
* 该方法会忽略掉那些从原型链上继承到的属性,只有obj对象自身的属性才会返回true
*/
var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
return hasOwnProperty.call(obj, key);
}
- 使用
let obj = {
name: "张三"
}
hasOwn(obj,'name') // true
hasOwn(obj,'__proto__') // false
4. cached
缓存函数,利用对象对字符串进行缓存
- 源码鉴赏
/**
* 缓存函数,利用对象对字符串进行缓存
* @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));
};
}
- 字符串转换成驼峰使用
/**
* 将连字符(-)分隔的字符串转换成驼峰写法
* 如: 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'));
- 输出
11 // 只会调用一次
userName
userName
- 首字母大写使用
/**
* 将首字母大写
*/
var capitalize = cached(function (str) {
return str.charAt(0).toUpperCase() + str.slice(1);
});
console.log(capitalize('user')); // User
- 驼峰转连字符使用
// \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指向
- 源码鉴赏
/* 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;
- 使用
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
- 使用
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
从数组第几位开始截取,返回新数组
- 源码鉴赏
/**
* 将假的数组转换成真的数组
* 主要用于参数截取
*/
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;
}
- 使用
function fn(a,b,c) {
let args = toArray(arguments, 1);
console.log(args); // [ 2, 3 ]
}
fn(1,2,3)
7. extend
对象合并,将from合并到to对象中
- 源码鉴赏
/**
* 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;
}
- 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: '加油' } }
- 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
合并对象指定字符
- 源码鉴赏
/**
* 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(","); //转换成字符串
}
- 使用
let staticKeys = [{ staticKeys: 1 }, { staticKeys: 2 }, { staticKeys: 3 }];
console.log(genStaticKeys(staticKeys)); // 1,2,3
9. looseEqual
宽松相等
- 源码鉴赏
/**
* 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;
}
}
- 验证对象相等使用
console.log(looseEqual({name: "zd"},{name: "ds"})); // false
console.log(looseEqual({},{})); // true
console.log(looseEqual([{}],[{}])); // true
- 验证数组某值
/**
* 判断数组中的数组, 是否和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
函数只会执行一次
- 源码鉴赏
/**
* Ensure a function is called only once.
* 确保该函数只调用一次 闭包函数
*/
function once(fn) {
var called = false;
return function () {
if (!called) {
called = true;
return fn.apply(this, arguments);
}
};
}
- 使用
add = (a, b) => {
console.log("执行");
return a + b;
};
let addOnce = once(add);
console.log(addOnce(1, 2));
console.log(addOnce(2,3));
- 输出
执行 // add函数只会执行一次
3
undefined
三、参考资料
- Vue.js v2.5.16版本
四、总结
本文从代码片段角度分析了十个源码函数。
我是前端小溪,欢迎感兴趣的同学关注下前端小溪公众号,也欢迎加我微信wxl-15153496335。
更多推荐
已为社区贡献2条内容
所有评论(0)