[源码阅读]vue2源码系列(中)

162 阅读6分钟

前言

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

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

这是源码共读的第24期,链接:第24期 【vue2 工具函数】初学者也能看懂的 Vue2 源码中那些实用的基础工具函数

其实对于Vue2源码系列以及有了一篇[源码阅读]Vue2源码系列(上) - 掘金 (juejin.cn),因为Vue2的源码比较多,所以花了两天时间给自己理解一下

源码分析

makeMap()

定义:传入一个用,划分的字符串和是否大小写,生成一个对象,判断key是不是在这个对象(map)中,如果在这个对象中,那么就会返回对应的value,没有直接undefined

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]; }
}
 console.log(makeMap("1,2,3,4,5,a,s,d,f",true)("6"));//undefined
 console.log(makeMap("1,2,3,4,5,a,s,d,f",true)("a"));//true

object.create()

创建一个新对象,可以使用现有的对象来作为新对象的原型

const person = {
    name:'summer',
    age:13,
    do:function(){console.log("你好")}
}
const summer = Object.create(person)//这时,这个summer的原型就是person
console.log(summer)
{}
[[Prototype]]: Object
age: 13
do: ƒ ()
name: "summer"

意思就是summer其实是可以调用person身上的一些方法和属性

summer.name = "summer瓜瓜"
console.log(summer)//{name: 'summer瓜瓜'}
console.log(perosn)//{name: 'summer', age: 13, do: ƒ}
person.name = "Alex"
console.log(summer)//{name: 'summer瓜瓜'}
console.log(perosn)//{name: 'alex', age: 13, do: ƒ}

发现:create以后的对象不会和之前的对象就是原型上有关系,他们不会使用一样的地址,summer如果定义了和person一样的属性,那么属性进行覆盖

注意:

Object.create(null)

其实我感觉这个需要慎用,null在js中其实也是一个对象,但是它不具有原型,这也就意味者,Object的一些需要使用原型的api,我现在创建的这个是用不了的

我们一般将以null为原型的对象理解为map的替代

参考链接:Object.create() - JavaScript | MDN (mozilla.org)

疑问:但是我return的其实是一个函数,但是我的参数些什么呢?或者说,这个使用场景是什么?

remove()

定义:从数组里删除一个item

function remove (arr, item) {
   if (arr.length) {
    var index = arr.indexOf(item);
    if (index > -1) {
      return arr.splice(index, 1)
   }
  }
}

逻辑:

  • 首先先找到item所在的位置
  • 使用api进行删除(如果这个值存在的话)

array.splice()

是array比较常用的一种api,可以用于增删改功能

array.splice(start,num,value):意思就是从start位置删除num个元素,并在后面添加value,就是在删的后面添加

hasOwn(obj,key)

定义:它其实就是在判断obj上是不是又这个key

  var hasOwnProperty = Object.prototype.hasOwnProperty;
  function hasOwn (obj, key) {
    return hasOwnProperty.call(obj, key)
  }

注意:

明明一般的我们本来就可以使用object1.hasOwnProperty(key)来判断他是不是有这个属性,那么我们什么还需要多加这个呢??

参考:Object.prototype.hasOwnProperty() - JavaScript | MDN (mozilla.org)

因为我们为了防止一个对象他的身上本身就有hasOwnPrototpye这个属性,其实这时候我们可以联想到Object.create()

var foo = {
  bar: 'Here be dragons'
};
foo.hasOwnProperty('bar'); //true

因为这个foo身上没有hasOwnPrototype属性,所以我们可以使用他原型上的这个属性

但是我们改为

var foo = {
  hasOwnProperty: function() {
    return false;
  },
  bar: 'Here be dragons'
};
foo.hasOwnProperty('bar'); // 始终返回 false

因为我们对这个属性进行了重新赋值(就这么理解,具体我也不知道),所以他不具有原型上的属性的原来属性值了

这时候我们就可以使用Object.hasOwnPrototype.call(foo,"bar")

cached(fn)

定义:创建一个纯函数

其实我还是不太理解为什么需要这个函数?🤦‍♀️,因为我觉得,后面的例子明明不使用这个函数也同样可以实现啊!!

  function cached (fn) {
    var cache = Object.create(null);//创建了一个对象
    return (function cachedFn (str) {
      var hit = cache[str];
      return hit || (cache[str] = fn(str))
    })
  }

补充:

纯函数

  • 每次调用参数相同,输出也是相同的
  • 不会改变传入参数的值,也不会改变外部变量的值
  • 没有副作用:不会进行网络请求,Math.random之类

参考链接:程序员 - 纯函数是什么?_个人文章 - SegmentFault 思否

高阶函数

至少满足下列一个条件

  • 接受函数作为参数
  • 返回一个函数

柯里化

大概意思就是:他也是一个函数,也接收参数,但是接收参数不会立即计算,而是返回另一个函数,原来传入的参数在闭包中存起来。

在vue源码中,他是这么用的

camlize()

定义:将字符转为小驼峰

var camelizeRE = /-(\w)/g;//匹配所有-开头的东西
var camelize = cached(function (str) {
  return str.replace(camelizeRE, function (_, c) {
      return c ? c.toUpperCase() : ''; })
});

补充

string.replace(regexp,function)

在这里我们只聊聊这一种

针对mdn的理解:

  1. 当我们去匹配正则,并且匹配成功后,我们的函数就会立即执行
  2. 并且函数的返回值会替换你刚刚匹配的那些值

参考链接:String.prototype.replace() - JavaScript | MDN (mozilla.org)

其实我们可以将上述代码改一下

var camelizeRE = /-(\w)/g;
var change = (match, p1, offset, string) => {
  //console.log("是些",match, p1, offset, string);
  return p1 ? p1.toUpperCase() : '';
}
var camelize = cached(function (str) {
  return str.replace(camelizeRE, change)
});
console.log(camelize('-WSE-123'));//WES123
console.log(camelize('on-click'));//onclick
是些 -W W 0 -WSE-123
是些 -1 1 4 -WSE-123
WSE123

可以看到函数执行了两次,满足几次就会执行几次这个函数

参数

  • match:其实我们需要匹配的正则或者字串
  • p{n}:正则可能有n个,n个匹配的字符串
  • offset:就是你匹配的字符在原数组的index是多少
  • string:需要被匹配的字符串

这么一理解的话,就知道function (_, c)里面的占位符,他占的是match

capitalize()

定义:首字母大写

var capitalize = cached(function (str) {
  return str.charAt(0).toUpperCase() + str.slice(1)//字符串进行拼接
});
console.log(capitalize("summer"));//Summer

hyphenate()

定义:就是将onClick => on-click

  var hyphenateRE = /\B([A-Z])/g;
  var hyphenate = cached(function (str) {
    return str.replace(hyphenateRE, '-$1').toLowerCase()
  });

具体关于replace的使用参考:String.prototype.replace() - JavaScript | MDN (mozilla.org)

这边$1:就是匹配第一个正则匹配的项,在前面加上-

感悟

通过这次源码阅读,我真的收获很多

  • Object.create()的使用,对原型的一些理解

    • A.prototype = B:B就是A的原型
    • 那么B上面的属性A都是有的,但是一旦A设置了一个和B属性一样的属性,那么我觉得就是类似于一种重写
  • 判断一个对象身上有没有这个属性obj.hasOwnProperty(key)

  • 了解到了柯里化,高阶函数和纯函数

  • 了解了replace,这个一直很常用,但是我一直都不太熟悉

  • 关于我对cached()还是很迷惑,主要是不太理解为什么非得用它