一切都从 new Vue(options) 开始
function Vue (options) {
if (!(this instanceof Vue)) {} // 只能通过new
this._init(options)
}
_init
Vue.prototype._init = function (options) {
// 根实例
let vm = this
// 子实例合并options,配置会被保存在VueComponent.options上,
// 另外,vm.$options._renderChildren = children(插槽相关)
if (options && options._isComponent) initInternalComponent(vm, options);
else
// 主要是合并全局组件,全局指令,全局过滤器;
// 实例属性:data数据,template模板(会编译成render函数),生命周期, 组件,computed, watch...
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
// 确定组件的父子关系
initLifecycle(vm);
// 自定义事件 $emit $on
initEvents(vm);
// 定义 $createElement 作为render函数的参数
initRender(vm);
// 第一个生命周期
callHook(vm, 'beforeCreate');
initInjections(vm);
// 数据响应式
initState(vm);
initProvide(vm);
callHook(vm, 'created');
// 挂载
if (vm.$options.el) vm.$mount(vm.$options.el)
}
响应式: initProps initData initComputed initWatch
- initProps
function initProps (vm, propsOptions/* 标签里取到的数据 */) {
// 1. vm.$options.propsData 组件的配置对象里定义的
// 2. vm._props = {} 根据propsData 和 propsOptions得到的props
// 3. defineReactive$$1(props, key, value)
// 4. 代理 proxy(vm, "_props", key)
}
- initData
function initData (vm) {
// 用户定义的 data
var data = vm.$options.data;
// 绑定在实例的 _data 上
data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {};
// 属性校验: key不能和props还有methods重复
// 响应式处理
observe(data, true);
}
function observe (value, asRootData) {
// 省略了一些我认为不是很重要的代码
// 每个对象对应一个ob实例
let ob = new Observer(value)
return ob
}
// 实例化 ob
var Observer = function Observer (value) {
this.value = value // 数据
this.dep = new Dep(); // 给 vue.$set 用
// 如果数据是数组类型 重写原型方法
this.walk(value);
};
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
// 数据的每个key响应式处理
defineReactive$$1(obj, keys[i]);
}
}
function defineReactive$$1 (
obj,
key,
) {
// 数据的每个属性都会对应一个 dep
var dep = new Dep();
// 深度响应式;
var childOb = !shallow && observe(val);
// 利用 Object.defineProperty 属性get的时候执行
function reactiveGetter () {
if (Dep.target) {
dep.depend(); // 将当前的watcher收集起来
if (childOb) {
// 嵌套对象的ob的dep也收集这个watcher
/*
function set(target, key, val) {
// 由于也经历了上面的 observe, 所以target存在__ob__
var ob = (target).__ob__
// 直接将赋予的属性响应式处理
defineReactive$$1(ob.value, key, val)
// 手动触发 notify
ob.dep.notify()
}
*/
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
}
// set的时候执行
function reactiveSetter (newVal) {
// 设置的属性响应式
childOb = !shallow && observe(newVal);
dep.notify(); // 触发 watcher 渲染
}
}
- initComputed
function initComputed (vm, computed) {
var watchers = vm._computedWatchers = Object.create(null);
// 处理每个计算属性
for (var key in computed) {
var userDef = computed[key];
// 我们一般使用都是函数
var getter = typeof userDef === 'function' ? userDef : userDef.get;
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions // { lazy: true }
);
// 计算属性响应式处理
defineComputed(vm, key, userDef);
// get 的时候触发如下:
/*
// 计算属性的 dep 收集渲染watcher, 计算属性依赖的data属性的dep收集计算属性的watcher
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value
}
*/
}
}
- initWatch
// 每个 watch 都会执行 createWatcher(vm, key, handler/* 函数 或者 对象 */)
function $watch(expOrFn/* key */, cb/* 回调 */, options/* 配置 */) {
var watcher = new Watcher(vm, expOrFn, cb, options);
// 存在配置 immediate 初始就会执行回调
if (options.immediate) {
pushTarget();
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info);
popTarget();
}
}
// userWatcher
function Watcher (vm,expOrFn, cb, options) {
// 配置:deep: true
this.cb = cb;
this.deps = [];
this.newDeps = [];
// 去访问属性值,让属性收集这个userWatcher
this.getter = parsePath(expOrFn);
this.value = this.lazy
? undefined
: this.get();
}
function get () {
pushTarget(this)
value = this.getter.call(vm, vm)
// 深度监听
if (this.deep) {
traverse(value)
}
popTarget();
this.cleanupDeps();
return value
}
userWatcher 监听计算属性的情况
// 触发计算属性get -> 执行计算属性get方法
// 计算属性收集userWatcher
// 数据的属性收集计算属性watcher
created 生命周期
mount
Vue.prototype.$mount = function (el) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
}
function mountComponent (
vm,
el
) {
vm.$el = el;
callHook(vm, 'beforeMount');
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
// 开始渲染组件 vm._update(vm._render(), hydrating)
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
return vm
}
vm._render() 创建子vnode
function _createElement (context, tag, data, children, normalizationType) {
// 原生dom
vnode = new VNode(tag, data/* style class event... */, children, undefined, context)
// 组件vnode
vnode = createComponent(
tag /* 组件对象(data template components....) */,
data,
context,
children
)
/*
new Vue({
el: '#app',
render: h => h(App) // 其实就只是传入了tag,也就是抛出的app组件对象
})
一般我们开发写的template,会被vue编译成render函数
_c(tag, data, children)
*/
}
// 继承大Vue
// extendOptions { name, data, template | render, components... }
function extend(extendOptions) {
// 这也就是为啥子组件实例可以用Vue的原型上的方法
// Sub.prototype = Object.create(Super.prototype)
// Sub = function VueComponent (options) { this._init(options) }
// Sub.prototype.constructor = Sub;
// 将后期的 $options 保存在构造器的 options 上
Sub.options = mergeOptions(
Super.options,
extendOptions
)
return Sub
}
// 组件vnode
// 1. 继承Vue得到构造器
// 2. 异步组件逻辑: 返回注释节点,在promise then之后forceRender父组件
// 3. 处理props得到propsData
// 3. 组件钩子
// 4. new vnode
function createComponent (
Ctor,
data,
context,
children,
tag
) {
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor);
}
// 异步组件
Ctor = resolveAsyncComponent(Ctor, baseCtor);
if (Ctor === undefined) {
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
// propsData
// 组件钩子
installComponentHooks(data);
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
}
patch 子vnode
function patch (oldVnode, vnode, hydrating, removeOnly) {
// 保存 子dom节点 子vnode, 用于最后执行 指令的钩子 mounted(先子后父);
var insertedVnodeQueue = []
// 创建组件(实例化子组件 或者 创建dom)
createElm(vnode, insertedVnodeQueue, parentElm)
// vnode.parent.data.pendingInsert = queue
invokeInsertHook(vnode, insertedVnodeQueue)
return vnode.elm
}
function createElm (vnode, insertedVnodeQueue, parentElm) {
// 组件vnode(递归)
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) return
// 原生dom
vnode.elm = createElement(tag, vnode)
// 创建children
createChildren(vnode, children, insertedVnodeQueue);
// 事件,style,class等处理
if (isDef(data)) invokeCreateHooks(vnode, insertedVnodeQueue)
// 插入父节点
insert(parentElm, vnode.elm, refElm)
}
function createComponent (vnode, insertedVnodeQueue, parentElm) {
// 组件 _init
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm);
}
vue的编译
template 解析成ast
// 编译 template 返回root ast节点
var ast = parse(template.trim(), options)
// 通过 while template字符串,每个标签都是一个ast对象,如下:
{
tag, // 标签
attrsMap: { :class: value, @click: 'handle(text)' }, // 标签属性
parent,
children: [], // 子ast
alias: v-for的每一项 // 如果存在 v-for 会丰富ast对象
classBinding // classBinding 的变量
for: // v-for数组
staticClass // 静态class
iterator1: // v-for 索引变量
if: v-if标识 // v-if
ifConditions: [{
block: ast,
exp
}]
// 事件相关属性
events: {
click: {
value: "handle(text)"
}
}
}
codeGen (最终的render函数)
// 编译 render 函数
var code = generate(ast, options)
// 最终返回的代码
_c(tag, data/* 事件 class style等属性 */, children)
事件
- dom原生事件
/*
标签ast { @click.stop: 'value', class: , :class: }
@click.stop修饰符 => modifiers = { native: true, stop: true... }
丰富ast属性:
ast[events | nativeEvents] = {
// 同种类型事件绑定多个就会是数组函数
eventName: [{}] | {value, modifiers}
}
codeGen阶段:
遍历 ast[events | nativeEvents] 生成一种事件类型的代码
根据是否存在modifiers会有不同的规则:
1. 不存在modifiers,常规的事件回调;
根据不同书写形式会生成不同的代码
-------- @click=method on: { click: method }
最终绑定在dom上的回调:function invoker() { method.apply(null, arguments事件对象) }
-------- @click=method($event, param)
on: { click: function($event){return method($event, param)} }
最终绑定在dom上的回调:
function invoker() {
(function($event){return method($event, param)}).apply(null, arguments)
}
-------- @click=() => { console.log() } 这种形式就很有意思了,因为最后生成的代码是:
with(this) { return _c('div', { on: () => { console.log() } }, []) }
会从this(vm)中找 console 由于vue对实例做了一层proxy包装 所以导致直接throw;
所以你可以这样写 ($event)=> { method($event, param) } 这样才能接受到 param 这个参数
($event, param) => { method($event, param) } 因为最后是 apply(null, arguments) 只能接受到事件对象
最终绑定在dom上的回调:
function invoker() {
// 这种写法就只能拿到一个参数了
(($event, param) => { method($event, param) }).apply(null, arguments)
}
2. 存在modifiers(所有代码都会做一层包装)
-------- @click=method(参数只能接受一个 $event)
function($event){
$event.stopPropagation();
return method.apply(null, arguments)
}
-------- @click=method($event, param)(可以接受多个参数)
function ($event){
$event.stopPropagation();
return method($event, param)
}
-------- @click=() => {}
function($event){
$event.stopPropagation();
return (()=>{}).apply(null, arguments)
}
两个时机会执行事件的绑定:
1.createChildren 创建真实dom后
最终绑定的函数是:
function invoker {
fns = invoker.fns // fn[] | fn
// fns.apply(null, arguments)
}
** 为啥将 fns 绑定在invoker上:**
其实最后绑定在dom上的是 invoker 这个函数,这样操作那么事件只需要绑定一次
只需要在更新的时候更改 invoker.fns 那么最终执行的函数就会变化
********************************
** 关于this指向 **
你会发现最终的函数都是apply null,那vue怎么做到methods中方法的this都是指向vm呢
其实就是在 initState 过程中将methods方法都bind了一下
function polyfillBind (fn, ctx) {
function boundFn (a) {
var l = arguments.length;
return l
? l > 1
? fn.apply(ctx, arguments)
: fn.call(ctx, a)
: fn.call(ctx)
}
boundFn._length = fn.length;
return boundFn
}
******************
*/
- 自定义事件
/*
<Comp @select='method'/>
Comp的 ast 属性:
ast.events = {
select: {
value: 'method' // 其他情况: method($event, b) () => {}
}
}
生成的code中的data的部分属性: {on: code}
function ($event) {
method($event,b) // 包装
}
() => {}
method.apply(null, arguments)
Comp的vnode
_c(Comp, { on: { select: fn } })
var vnode = new VNode(
name,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners/* data.on */, tag, children }
)
组件实例化时:
执行 initEvents 会执行 updateListeners(listeners)
function invoker () {
fns = invoker.fns // method () => {}
fns.apply(null, arguments)
}
$on(name, invoker)
$emit(name, arguments /*通过子组件给父传参*/) -> invoker.apply(vm, arguments)
method.apply(null, arguments) // 注意: method也是被bind后的函数,this是指向父实例的
写法 method($event, b) 只能接受一个 $event 参数(第一个子传过来的参数)后一个参数是父组件实例上的
*/
指令
在dom被创建后会执行vue平台的一些钩子: updateClass updateStyles updateDirective
1. 先执行所有指令的bind钩子 bind(vnode.elm/* dom */, dir/* 指令配置:可以定义一些工具方法 */)
2. 将后面执行 inserted 钩子的回调函数放进 domVnode 的hook中
3. invokeCreateHooks
if (isDef(domVnode.data.hook.insert)) { insertedVnodeQueue.push(domVnode) }
4. 组件init后会把自身 vnode 也 push 进 insertedVnodeQueue
5. 所以最终遍历这个queue执行钩子 顺序就是:inserted钩子 组件mounted生命周期
v-model
1. addProps -> value :value=...
2. event -> @input=if($event.target.composing)return; demo=$event.target.value
创建节点后为其绑定 input 事件 注意这是一个自定义事件
model指令 inserted 钩子中去绑定 compositionStart compositionEnd 事件
compositionStart:e.target.composing = true
compositionEnd: 触发标签input事件
插槽
<组件>
<a slot='name' slot-scope='demo'>1</a>
<div slot-scope='demo'></div>
<div></div>
</组件>
1. 标签parse阶段
存在 slot-scope parent.scopedSlots = { name: ast } // 同样name的slot只能存在一个
不存在 slot-scope parent.children = [ast...]
2. 组件生成code阶段
存在 scopedSlots 丰富data属性
{ scopedSlots: _u( [ { key: name, fn/* 接收props 返回vnode */ } ] ) }
function (slotScope) {
return _c(tag, data, children)
}
3. 组件vnode data.scopedSlots = {name: fn} // _u return的结果
4. 组件实例化 $slots $scopedSlots
5. 这个组件执行自己的render会创建子vnode,就会碰上slot组件
6. slot _t(name, children, props)
7. 先找 $scopedSlots 根据name 执行fn(props) 生成vnode
8. 后找 $slots 返回 [vnode, vnode]