从文档开始,重学vue(下)源码级别

394 阅读4分钟

此篇文章主要是从应用及源码层面讲解vue部分常用api,阅读起来可能略有难度,新手可以看《从文档开始,重学vue(上)》

示例代码均在vue-cli3中完成

Vue.extend()

可以使用 extend 创建一个子类,该方法通常用于构建全局组件,如弹框组件等,下面我们就用它来制作个全局alert组件吧

  1. 首先我们需要一个alert.vue组件,组件很简单就接受一个参数,然后有两个控制显示隐藏的方法
  2. 需要把alert挂载到body 注意extend的使用方式
  3. 使用

使用之前别忘了在main.jsuse一下

import Alert from "./components/Alert/create";
Vue.use(Alert)

用起来也非常方便,如下:

mounted(){
	this.$alert('公众号,码不停息')
}

上面我们使用extend直接给他传了个组件进去,其实我们也可以给extend的配置对象,如下:

Vue.extend({
 template: "<span>{{msg}}</span>",
 data() {
   return {
     msg: "码不停息"
   };
 }
});

下面我们通过源码来看看在vue内部extend都做了哪些事情,关键性代码已经加上注释 主要做的事情就是把通过extend挂载的组件初始化,并完善里面的options最后返回组件

Vue.nextTick()

如果想理解清楚nextTick,需要我们了解vue异步队列javascript(确切的说是浏览器)的事件循环机制

可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替(Vue官网)

可以简单的总结为Vue实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新,他的策略就是同一事件循环中的所有数据变化完成之后,再统一进行视图更新,如果在这个过程中想要操作dom就比较棘手了,而Vue.nextTick就是来解决这样的问题,如下:

<template>
  <div class="about">
    <div id="time">{{ time }}</div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      time: ""
    };
  },
  methods: {
    getDom() {
      return document.getElementById("time").innerHTML;
    }
  },
  mounted() {
    this.time = new Date().toLocaleTimeString();
    console.log("获取time", this.getDom());  //获取不到
    this.$nextTick(() => {
      console.log("获取time", this.getDom());	//可以获取到
    });
  }
};
</script>

可以看到,当我们给time赋值后直接通过原生dom获取值是获取不到的,而用 this.$nextTick(callback)就可以获取到了,那nextTick在内部做了什么呢?我们找到相应的源码 可以看出,当我们在代码中执行$nextTick方法时,内部是调用了nextTick,那我们再来看看nextTick方法里面都有什么? 原来nextTick方法把我们传的函数都push到了一个callback数组里,那这个数组是什么时候执行呢?为了方便看,我把相关代码都复制出来如下: 代码较长,可以放大观看,关键代码已给出注释,主要逻辑如下:

用户调用$nextTick(callback) -> 把用户传入函数push到callback数组 -> 检测当前平台环境决定使用哪种方式处理异步 -> 执行flushCallbacks函数 -> flushCallbacks中循环执行callback

因为微任务会在当前宏任务执行完毕后立即执行,这样就能保证在执行$nextTick()的时候,当前宏任务(包括页面渲染)已经执行完毕

Vue.set()

Vue.set()设置的值是响应式,当我们需要对 复杂数据类型 新增属性和值,同时需要新增的值是 响应式 的时候就需要使用该api 如下所示:

  1. 直接给对象赋一个新的属性和新值
<template>
  <div class="about">
    <div>公众号: {{ userInfo.name }}</div>
    <div>作者:{{ userInfo.author || "暂无数据" }}</div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      userInfo: {
        name: "码不停息"
      }
    };
  },
  methods: {},
  mounted() {
    this.userInfo.author = "刘小灰";  // 注意 userInfo开始没有author属性
  }
};
</script>

渲染结果如下, 数据没有出来,新增的author不是响应式

  1. 我们再用Vue.set()给对象赋一个新的属性和新值
<template>
  <div class="about">
    <div>公众号: {{ userInfo.name }}</div>
    <div>作者:{{ userInfo.author || "暂无数据" }}</div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      userInfo: {
        name: "码不停息"
      }
    };
  },
  methods: {},
  mounted() {
    this.$set(this.userInfo, "author", "刘小灰");  //使用set赋值
  }
};
</script>

在看看结果,新增的author是响应式

除了基本使用,我们来思考下Vue为什么要设置这个api,为什么直接通过.语法添加的属性就不是响应式的呢? 使用this.$set()是有如何做到响应式的呢?

我们来源码中找答案

简单了解下响应式

响应式的具体表现是当我们把data中的属性和页面绑定后,改变data中的数据后,页面会自动更新,那Vue是如何实现响应式的呢?

不难得出在vue2.x中是使用Object.defineProperty对数据进行劫持来实现响应式,当我们初始化的时候,会把每一个数据都进行依赖收集,内部主要是通过遍历及递归来实现,如下(关键代码已给出注释): 下面我们来看看核心方法defineReactive都干了什么事情(关键代码已给出注释)

大体流程

初始化 -> 执行Observer -> 如果是数组特殊处理,否则遍历子 -> 执行walk -> defineReactive进行响应式处理,如果数据中对象嵌套,递归之,否则进行依赖收集,确保全部数据都经过Object.defineProperty的洗礼 -> 响应式处理完毕

这时问题来了, 如果你在代码中给某个对象通过.语法新加个属性,这个时候初始化过程早已结束,新加的属性并没有经过Object.defineProperty的洗礼,自然不会变成响应式数据,这个时候我们就需要使用Vue.set()方法,让数据变成响应式,那set中是如何做的呢?其实就是重新调了下Object.definePropertyset方法进行依赖收集即可

关于Vue是如何对数组进行特殊处理的,可以看Object.defineProperty是如何实现对数组的监听

Vue.use()

安装 Vue 插件使用,use的源码比较短我们直接看源码:如下(关键代码已给出注释) 所以在我们平常使用时,我们有两种使用方式

方式一:
Vue.use({
	install(vue){
    
    }
})

方式二:
Vue.use((vue)=>{

})

无论是哪种方式,Vue都会把vue实例在回调中返回回来供我们使用

最后

最后我想说说为什么我们要学习源码,我觉得源码能不能学透并不重要(当然,如果你可以把源码彻底看懂也再好不过),对我们大部分人来说,学习源码最重要的目的是 查漏补缺 ,看大佬们是怎么写代码,是怎么组织代码,而这种能力不是我们多做几个项目,多发几个ajax请求能够得到的,就拿自己来说,看了源码后我发现自己对函数式编程,对发布订阅模式掌握的还不是不好,然后自己花些时间再加强下这方面的理解,然后把自己理解到的知识再在看源码中得以升华,我感觉这是最酷的!