【若川视野 x 源码共读】第24期 | vue2工具函数

710 阅读6分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

学习准备

学习目标

  1. Vue2 源码 shared 模块中的几十个实用工具函数
  2. 如何学习源码中优秀代码和思想,投入到自己的项目中
  3. 如何学习 JavaScript 基础知识,会推荐很多学习资料
  4. 博主的一些经验分享
  5. 等等

学习过程

本文是针对【若川视野 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
复制代码
  1. 分析上述精简的代码,使用了Object.create() ,该方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
  2. 回到代码中,其实该方法采用了闭包的柯里化,执行makeMap函数,接收str参数,返回对应str定制化>函数,内部对str参数进行split,创建映射关系的对象;
  3. 后续只需要调用定制化函数,接收键名参数,去映射对象中获取键值; 补充:
  • 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

学习收获或感受

平常上班都挺忙,看文章的时间也是比较零碎的,经常是一种看了就忘记的状态。 今天就先写到这里了,第一次参与源码共读活动,笔记也是对原文做了比较简单的记录,方便日后查阅。 感受有几点:

  1. 将一件复杂的事情简单化之后,会发现其实没有自己想象得那么难;
  2. 在日常业务需求开发中,我们还是倾向于使用第三方库的工具函数来实现功能的;
  3. 写笔记还是挺费时的,特别是新手,从只是看看文章到看了写下来真的是需要一段时间来适应一下;
  4. 大家(特别是自己)要坚持多看书,多学习,加油。