此篇文章主要是从应用及源码层面讲解vue
部分常用api,阅读起来可能略有难度,新手可以看《从文档开始,重学vue(上)》
示例代码均在vue-cli3中完成
Vue.extend()
可以使用 extend
创建一个子类
,该方法通常用于构建全局组件,如弹框组件等,下面我们就用它来制作个全局alert
组件吧
- 首先我们需要一个
alert.vue
组件,组件很简单就接受一个参数,然后有两个控制显示隐藏的方法 - 需要把
alert
挂载到body
注意extend
的使用方式 - 使用
使用之前别忘了在main.js
中use
一下
import Alert from "./components/Alert/create";Vue.use(Alert)
用起来也非常方便,如下:
mounted(){ this.$alert('公众号,码不停息')}
上面我们使用extend
直接给他传了个组件进去,其实我们也可以给extend
的配置对象,如下:
Vue.extend({ template: "{{msg}}", data() { return { msg: "码不停息" }; }});
下面我们通过源码来看看在vue
内部extend
都做了哪些事情,关键性代码已经加上注释主要做的事情就是把通过extend
挂载的组件初始化,并完善里面的options
最后返回组件
Vue.nextTick()
如果想理解清楚nextTick
,需要我们了解vue
的异步队列
及 javascript
(确切的说是浏览器)的事件循环机制
- vue异步更新队列
- 事件循环机制
可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替(Vue官网)
可以简单的总结为Vue实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新,他的策略就是同一事件循环中的所有数据变化完成之后,再统一进行视图更新,如果在这个过程中想要操作dom就比较棘手了,而Vue.nextTick
就是来解决这样的问题,如下:
{{ time }}
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()); //可以获取到 }); }};
可以看到,当我们给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 如下所示:
- 直接给对象赋一个新的属性和新值
公众号: {{ userInfo.name }}
作者:{{ userInfo.author || "暂无数据" }}
export default { data() { return { userInfo: { name: "码不停息" } }; }, methods: {}, mounted() { this.userInfo.author = "刘小灰"; // 注意 userInfo开始没有author属性 }};
渲染结果如下, 数据没有出来,新增的author不是响应式
- 我们再用
Vue.set()
给对象赋一个新的属性和新值
公众号: {{ userInfo.name }}
作者:{{ userInfo.author || "暂无数据" }}
export default { data() { return { userInfo: { name: "码不停息" } }; }, methods: {}, mounted() { this.$set(this.userInfo, "author", "刘小灰"); //使用set赋值 }};
再看看结果,新增的author是响应式
除了基本使用,我们来思考下Vue
为什么要设置这个api,为什么直接通过.
语法添加的属性就不是响应式的呢? 使用this.$set()
是有如何做到响应式的呢?
我们来源码中找答案
简单了解下响应式
响应式的具体表现是当我们把data中的属性和页面绑定后,改变data中的数据后,页面会自动更新,那Vue
是如何实现响应式的呢?
不难得出在vue2.x
中是使用Object.defineProperty
对数据进行劫持来实现响应式,当我们初始化的时候,会把每一个数据都进行依赖收集,内部主要是通过遍历及递归来实现,如下(关键代码已给出注释):下面我们来看看核心方法defineReactive
都干了什么事情(关键代码已给出注释)
大体流程
初始化 -> 执行
Observer
-> 如果是数组特殊处理,否则遍历子 -> 执行walk
->defineReactive
进行响应式处理,如果数据中对象嵌套,递归之,否则进行依赖收集,确保全部数据都经过Object.defineProperty
的洗礼 -> 响应式处理完毕
这时问题来了, 如果你在代码中给某个对象通过.
语法新加个属性,这个时候初始化过程早已结束,新加的属性并没有经过Object.defineProperty
的洗礼,自然不会变成响应式数据,这个时候我们就需要使用Vue.set()
方法,让数据变成响应式,那set
中是如何做的呢?其实就是重新调了下Object.defineProperty
的set
方法进行依赖收集即可
关于Vue
是如何对数组进行特殊处理的,可以看Object.defineProperty是如何实现对数组的监听
Vue.use()
安装 Vue
插件使用,use的源码比较短我们直接看源码:如下(关键代码已给出注释)所以在我们平常使用时,我们有两种使用方式
方式一:Vue.use({ install(vue){ }})方式二:Vue.use((vue)=>{})
无论是哪种方式,Vue
都会把vue实例
在回调中返回回来供我们使用
最后
最后我想说说为什么我们要学习源码,我觉得源码能不能学透并不重要(当然,如果你可以把源码彻底看懂也再好不过),对我们大部分人来说,学习源码最重要的目的是 查漏补缺 ,看大佬们是怎么写代码,是怎么组织代码,而这种能力不是我们多做几个项目,多发几个ajax请求能够得到的,就拿自己来说,看了源码后我发现自己对函数式编程,对发布订阅模式掌握的还不是不好,然后自己花些时间再加强下这方面的理解,然后把自己理解到的知识再在看源码中得以升华,我感觉这是最酷的!
最后的最后
交个朋友吧,关注微信公众号,拉你进群,和一群志同道合的人学习源码