1.1.1 基础用法
引入 vue.js
并且 new
一个 Vue
实例,并且挂在到 #app
上。
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.8/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
message: 'Test'
},
})
</script>
1.1.2 Vue构造器
(function (global, factory) {
// 遵循UMD规范
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.Vue = factory());
}(this, function () { 'use strict';
···
// Vue 构造函数
function Vue (options) {
// 保证了无法直接通过Vue()去调用,只能通过new的方式去创建实例
if (!(this instanceof Vue)
// 实例this在不在Vue构造函数中
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
return Vue
})
1.1.3 定义原型属性方法
vue
之所以能适应基础的场景开发,除了经常提到的支持组件开发,除了它有完善的响应式系统之外,还提供了一些丰富的 api
方法,不管是静态还是原型方法,它们都丰富到足以满足我们日常基础的开发需求。
首先是原型上的属性方法,在构造函数的定义之后,有这样五个函数,他们分别针对不同场景定义了 Vue
原型上的属性和方法。
// 定义Vue原型上的init方法(内部方法)
initMixin(Vue);
// 定义原型上跟数据相关的属性方法
stateMixin(Vue);
//定义原型上跟事件相关的属性方法
eventsMixin(Vue);
// 定义原型上跟生命周期相关的方法
lifecycleMixin(Vue);
// 定义渲染相关的函数
renderMixin(Vue);
initMixin
initMixin
定义了内部在实例化Vue
时会执行的初始化代码,他是一个内部使用方法。混入了Vue.prototype._init方法,用于初始化Vue实例(即构造函数中调用的_init方法)
function initMixin(Vue){
Vue.prototype._init = function (options) {
const this = vm; //将当前实例保存为vm(view-model的缩写)
vm._uid = uid++; //为当前实例打上唯一标记
if(options && options._isComponent){
// 如果是组件,则采用组件专用的初始化方法,效率更高
initInternalComponent(vm, options);
} else {
// 将传入的options与vm构造函数的默认options合并,得到完整的options
vm.$options = mergeOptions(
resolveContructorOptions(vm.constructor),
options || {}, vm
)
}
...
// 如果options存在el属性,就执行挂载
//(否则需要使用vm.$mount手动挂载)
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
stateMixin
stateMixin
方法会定义跟数据相关的属性方法,例如代理数据的访问,我们可以在实例上通过this.$data
和this.$props
访问到data,props
的值,并且也定义了使用频率较高的this.$set,this.$delte
等方法。
function stateMixin (Vue) {
var dataDef = {};
dataDef.get = function () { return this._data };
var propsDef = {};
propsDef.get = function () { return this._props };
{
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
);
};
propsDef.set = function () {
warn("$props is readonly.", this);
};
}
// 代理了_data,_props的访问
Object.defineProperty(Vue.prototype, '$data', dataDef);
Object.defineProperty(Vue.prototype, '$props', propsDef);
// $set, $del
Vue.prototype.$set = set;
Vue.prototype.$delete = del;
// $watch
Vue.prototype.$watch = function (expOrFn, cb, options) {
const vm = this
// 判断接收的参数cb是否为纯对象,就调用createWatcher,
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
// 触发data的get函数收集到这个 watcher 实例。数据变化的时候就能触发 set,调用 watcher 实例的 update 方法,最终触发这里传入的 cb。
if (options.immediate) {
cb.call(vm, watcher.value)
}
return function unwatchFn () {
watcher.teardown() // 取消监听
}
//如果 options.immediate 为真,则立即调用 cb,但是参数只有 newVal,没有 oldVal。
};
}
// 实现内层obj响应式
function observer(obj){
let value;
for(const key in obj){
if(typeof obj[key] === 'object'){
arguments.callee(obj[key]) // 调用自己,实现递归函数
} else {
value = obj[key]
Object.defineProperty(obj, key, {
get:function(){
return value
},
set:function(newValue){
value = newValue
}
})
}
}
}
// this.$set()源码
function set(target: Array(any) | Object, key:any, val:any ):any {
if(process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)))
{
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 数组
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 设置数组的 length 属性,设置的属性值是 "数组原长度" 和 "key" 中的最大值
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
// 对象
// 这里用于处理 key 已经存在于 target 中的情况
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
// 如果 target 没有 __ob__ 属性的话,说明 target 并不是一个响应式的对象
// 所以在这里也不需要做什么额外的处理,将 val 设到 target 上,并且返回这个 val 即可
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
if (!ob) {
target[key] = val
return val
}
// 在这里使用 defineReactive 将 val 变成 getter/setter 的形式
defineReactive(ob.value, key, val)
// 因为新增了一个属性,所以 ob.value 变化了,所以在这里需要出发依赖的更新
ob.dep.notify()
return val
}
// this.$delete()
function del(target: Array(any) | Object, key:any, val:any):any{
if(Array.isArray(target) && isValidArrayIndex(key)){
// 执行数组原型上的 splice 方法,该方法会执行删除的操作,并且会出发依赖的更新
target.splice(key, 1)
return
}
const ob = (target: any).__ob__
if (!hasOwn(target, key)) {
return
}
delete target[key]
// 在这里判断 target 是不是响应式的,如果不是的话,就不用出发依赖的更新操作了。在这里,直接 return
if (!ob) {
return
}
ob.dep.notify()
}
// this.$watch()
class watch{
constructor(vm, expOrFn, cb){
this.cb = cb //调用$watch时候传进来的回调
this.vm = vm
this.expOrFn = expOrFn // 要监听的属性和方法,也就是$watch方法的第一个参数
this.value = this.get() // 调用自己的get方法
}
get(){
Dep.target = this //将Dep身上的target 赋值为Watcher对象
const value = this.vm._data[this.expOrFn]
// 声明value,使用this.vm._data进行赋值,并且触发_data[a]的get事件
Dep.target = null
return value
}
}
isUndef
和 isPrimitive
方法,isUndef
是判断 target
是不是等于 undefined
或者 null
。isPrimitive
是判断 target
的数据类型是不是 string、number、symbol、boolean
中的一种。