本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
学习准备
- 看文章:初学者也能看懂的 Vue2 源码中那些实用的基础工具函数
- 在线vscode 查看 github1s.com/vuejs/vue/b…
- 打包后的工具函数 github.com/vuejs/vue/b…
学习目标
- Vue2 源码 shared 模块中的几十个实用工具函数
- 如何学习源码中优秀代码和思想,投入到自己的项目中
- 如何学习 JavaScript 基础知识,会推荐很多学习资料
- 博主的一些经验分享
- 等等
学习过程
本文是针对【若川视野 x 源码共读】第24期 | vue2工具函数的阅读总结笔记,本文只挑选部分工具函数进行记录。
Flow的概念
因为JS是弱类型的脚本语言,类型异常需要等到运行阶段才能发现 类型不确定可能导致函数功能发生改变。 Flow是类型检查器,引入Flow,使得错误更早暴露、代码更智能、编码更准确。
一些工具函数
1. Object.freeze() 冻结对象
Object.freeze()方法对一个对象进行冻结,冻结之后不能对属性进行添加、修改、删除,然而,它仅仅对对象进行浅冻结,意味着只有对象中的直接属性被冻结。像例子中的 address 属性没有被冻结,仍然可以被修改。 使用 Object.isFrozen()来判断对象是否冻结。
const person = {
name: 'coco',
address: {
street: '100 Main St'
}
};
Object.freeze(person);
person.address.street = '888'; // 此项可以修改 person.address.street 的值
// 判断对象是否冻结
Object.isFrozen(person); // true
2. isObject 判断是对象(没有严格区分数组和对象)
function isObject (obj) {
return obj !== null && typeof obj === 'object'
}
// 例子:
isObject([]) // true
isObject({}) // true
2.1 补充一下typeof的知识点
typeof 返回一个表示数据类型的字符串,
表达式: typeof xxx
返回结果包括:number、boolean、string、object、undefined、function 6种数据类型。
null表示一个空对象指针,因此 typeof null; // object;
如果定义的变量准备在将来用于保存对象,那么最好将该对象初始化为null。
// 原始类型,除了null都可以正确显示
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof 'str'); // string
console.log(typeof undefined); // undefined
console.log(typeof Symbol()); // 'symbol'
console.log(typeof null); // object
// 引用类型,除了函数之外,都显示object
console.log(typeof function(){}); // function
console.log(typeof []); // object
console.log(typeof {}); // object
其中数组、对象、null都会被判断为object,其他判断都正确。
3. isPlainObject 是否是纯对象
上文isObject判断对象没有对数组和纯对象进行区分,可以使用isPlainObject方法来判断对象是否是纯对象。
var _toString = Object.prototype.toString;
function isPlainObject (obj) {
return _toString.call(obj) === '[object Object]'
}
// 上文 isObject([]) 也是 true
// 这个就是判断对象是纯对象的方法。
// 例子:
isPlainObject([]) // false
isPlainObject({}) // true
4. toString()获取数据原始类型
var _toString = Object.prototype.toString;
function toRawType (value) {
console.log(_toString.call(value)); // [object String]
return _toString.call(value).slice(8, -1)
}
toRawType('11'); // 'String'
toRawType(); // 'Undefined'
toRawType(undefined); // 'Undefined'
toRawType(null); // 'Null'
toRawType(true); // 'Boolean'
toRawType(100); // 'Number'
toRawType(Symbol()); // 'Symbol'
toRawType([]); // 'Array'
toRawType({}); // 'Object'
toRawType(function test() {console.log("111")}) ; // 'Function'
toRawType(/test/); // 'RegExp'
引用这位同学的过程分析:
- 获取
Object.prototype.toString方法,赋值给_toString;- 通过call方法调用_toSring()方法,并指定this为需要获取原始类型的数据。toString内部是根据隐式调用获取this,去执行内部的逻辑,因此
Object.prototype.toString()得到的值为[object Object];注意String的toString对原型上的toString进行了重写,此处不作再述;- 此处通过call把this显示的传递到toString中;
- 通过
_toString.call(value)获取的结果形如[object 原始类型],继续通过字符串的slice方法进行截取,从索引为8字符(包含)或者第8个字符(不包含)开始截取,截到倒数第1个(不包含),得到最终的原始类型; 作者:ali-go
链接:juejin.cn/post/709046…
5. isPromise 判断是否是 promise
function isDef (v) {
return v !== undefined && v !== null
}
function isPromise (val) {
/**
* Promise {<pending>}
* 1. [[Prototype]]: Promise
* 1. [[PromiseState]]: "fulfilled"
* 1. [[PromiseResult]]: "foo"
*/
console.log(val);
return (
isDef(val) &&
typeof val.then === 'function' &&
typeof val.catch === 'function'
)
}
// 例子:
const promise = Promise.resolve('foo')
.then(console.log());
isPromise(promise); // true
此处的isDef改成使用 isObejct会更严谨哦。
function isObject (obj) {
return obj !== null && typeof obj === 'object'
}
6. toString()转字符串
var _toString = Object.prototype.toString;
function isPlainObject (obj) {
return _toString.call(obj) === '[object Object]'
}
function toString (val) {
return val == null
? ''
: Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
? JSON.stringify(val, null, 2)
: String(val)
}
toString(11); // '11'
toString(function test(){console.log("11")}); // 'function test(){console.log("11")}'
// JSON.stringify()的第三个参数表示文本缩进或换行等
toString({name: 'coco',age: 12}); // '{\n "name": "coco",\n "age": 12\n}'
7. makeMap 生成一个 map (对象)
传入一个以逗号分隔的字符串,生成一个 map(键值对),并且返回一个函数检测 key 值在不在这个 map 中。第二个参数是小写选项。
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;
}
return expectsLowerCase
? function (val) { return map[val.toLowerCase()]; }
: function (val) { return map[val]; }
}
// Object.create(null) 没有原型链的空对象
makeMap('name, coco, jojo, coco,haha', 'name');// ƒ (val) { return map[val.toLowerCase()]; }
调用makeMap方法返回的是一个函数,看着似乎有点晕乎乎了吧。再来看这位同学写的例子就明白了,如下所示:
1.让我们对代码进行一些改造,我们先去掉
expectsLowerCase扩展功能,精简代码;function makeMap ( str ) { var map = Object.create(null); var list = str.split(','); for (var i = 0; i < list.length; i++) { map[list[i]] = true; } return function (val) { return map[val]; } } //获取定制化函数 const strFn = makeMap("a,b,c,d,e"); //此时内部的map对象为:{ a: true, b: true, c: true, d: true, e: true } const hasA = strFn("a"); //true const hasNull = strFn(null); //undefined const hasF = strFn("F"); //undefined 复制代码
- 分析上述精简的代码,使用了
Object.create(),该方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。- 回到代码中,其实该方法采用了闭包的柯里化,执行
makeMap函数,接收str参数,返回对应str的定制化>函数,内部对str参数进行split,创建映射关系的对象;- 后续只需要调用
定制化函数,接收键名参数,去映射对象中获取键值; 补充:
- Object.create的应用场景:由于Object.create(null)创建出来的对象是一个
干净对象,除自身属性之外,>没有附加其他属性,我们知道for in或者对象in时会遍历原型对象prototype上的可枚举属性,如果我们>不需要这部分数据,势必就会造成部分性能的损耗,使用Object.create(null)则不必再对其进行遍历了。 作者:ali-go\ 链接:juejin.cn/post/709046…
8. extend对象合并
function extend (to, _from) {
for (var key in _from) {
to[key] = _from[key];
}
return to;
}
// 例子:
const data = { name: '若川' };
const data2 = extend(data, { mp: '若川视野', name: '是若川啊' });
console.log(data); // { name: "是若川啊", mp: "若川视野" }
console.log(data2); // { name: "是若川啊", mp: "若川视野" }
console.log(data === data2); // true
ES6的扩展运算符也可以用于合并对象,代码如下:
const obj1 = { name: 'coco' };
const obj2 = { age: 11, name: 'jojo' };
const data = {
...obj1,
...obj2,
};
console.log(data); // {name: 'jojo', age: 11}
console.log(data === obj1); // false
console.log(data === obj2); // false
// ...的效果等于Object.assign
const data = Object.assign({}, obj1, obj1);
console.log(data); // {name: 'jojo', age: 11}
console.log(data === obj1); // false
console.log(data === obj2); // false
学习收获或感受
平常上班都挺忙,看文章的时间也是比较零碎的,经常是一种看了就忘记的状态。 今天就先写到这里了,第一次参与源码共读活动,笔记也是对原文做了比较简单的记录,方便日后查阅。 感受有几点:
- 将一件复杂的事情简单化之后,会发现其实没有自己想象得那么难;
- 在日常业务需求开发中,我们还是倾向于使用第三方库的工具函数来实现功能的;
- 写笔记还是挺费时的,特别是新手,从只是看看文章到看了写下来真的是需要一段时间来适应一下;
- 大家(特别是自己)要坚持多看书,多学习,加油。