前言
- 当数据发生变化 视图也在发生变化
- 每个组件都有一个渲染watcher
new Watch()
(观察者模式)
- 在
new Watche()
组件开始渲染组件,在render函数生成vnode时,调用了劫持数据getter
- 这个时候每个属性可以订阅一个渲染watcher, 在数据发生变化时就会调用劫持数据的
setter
- 在
setter
里去通过一个notify
去调用渲染watcher中的updateComponent
方法去更新视图
html 和 javascript模板
<div id="app">{{ message }} - {{ arr }}</div>
<script>
var vm = new Vue({
data: {
message: 'Hello Vue!',
arr: [[0]]
}
})
vm.$mount('#app')
setTimeout(() => {
vm.message = 'vue updater'
vm.arr.push(100)
}, 2000)
</script>
render函数生成vnode 方法
/**
* @description 创建标签vnode
*/
export function createElement(vm, tag, data = {}, ...children) {
return vnode(vm, tag, data, data.key, children, undefined);
}
/**
* @description 创建文本的vnode
*/
export function createTextElement(vm, text) {
return vnode(vm, undefined, undefined, undefined, undefined, text);
}
/** 核心方法 */
/**
* @description 套装vnode
*/
function vnode(vm, tag, data, key, children, text) {
return {
vm,
tag,
data,
key,
children,
text,
// .....
}
}
export function renderMixin(Vue){
Vue.prototype._c = function() {
return createElement(this,...arguments)
}
Vue.prototype._v = function(text) {
return createTextElement(this,text)
}
Vue.prototype._s = function(val) {
if(typeof val == 'object') return JSON.stringify(val)
return val;
}
Vue.prototype._render = function(){
const vm = this
let render = vm.$options.render
let vnode = render.call(vm)
return vnode
}
}
初始化
function Vue (options) {
/** 初始化 */
this._init(options)
}
/**
* @description 扩展原型
*/
initMixin(Vue)
renderMixin(Vue)
export function initMixin (Vue) {
Vue.prototype._init = function (options) {
vm.$options = options
/** 数据初始化 */
initState(vm)
/** compile */
if(vm.$options.el){
vm.$mount(vm.$options.el);
}
}
Vue.prototype.$mount = function (el) {
const vm = this;
const options = vm.$options
el = document.querySelector(el);
vm.$el = el;
if(!options.render) {
let template = options.template;
if(!template && el) {
template = el.outerHTML;
}
// 生成的render函数
let render = compileToFunction(template);
options.render = render;
}
/** 组件挂载 */
mountComponent(vm,el)
}
}
组件挂载,初始化渲染watcher
mountComponent文件
import { patch } from './vdom/patch'
import Watcher from './observer/watcher'
/** 生命周期 */
export function lifecycleMixin(Vue) {
Vue.prototype._update = function(vnode) {
const vm = this
// patch是将老的vnode和新的vnode做比对 然后生成真实的dom
vm.$el = patch(vm.$el, vnode)
}
}
export function mountComponent(vm, el) {
// 更新函数 数据变化后 会再次调用此函数
let updateComponent = () => {
vm._update(vm._render())
}
new Watcher(vm, updateComponent, () => {
console.log('视图更新了')
}, true)
}
patch文件 将vnode生成真实dom
/** patch 文件 这里只是简单处理 直接生成真实的dom */
export function patch(oldVnode, vnode) {
if (oldVnode.nodeType == 1) {
const parentElm = oldVnode.parentNode
let elm = createElm(vnode)
// 在第一次渲染后 删除掉节点
parentElm.insertBefore(elm, oldVnode.nextSibling)
parentElm.removeChild(oldVnode)
return elm
}
}
function createElm(vnode) {
let { tag, data, children, text, vm } = vnode
if (typeof tag === 'string') {
vnode.el = document.createElement(tag)
children.forEach(child => {
vnode.el.appendChild(createElm(child))
})
} else {
vnode.el = document.createTextNode(text)
}
return vnode.el
}
Watcher 和 Dep (重点)
Watcher 类
import { popTarget, pushTarget } from './dep'
import { queueWatcher } from './scheduler'
let id = 0
class Watcher {
constructor(vm,exprOrFn,cb,options){
this.vm = vm
this.exprOrFn = exprOrFn
this.cb = cb
this.options = options
this.id = id++
// 视图更新 就是上面的updateComponent方法
this.getter = exprOrFn
this.deps = []
this.depsId = new Set()
this.get()
}
get(){
// 将渲染wather指向给dep 在数据getter时 依赖起来 将dep与watcher关联起来(神来之笔)
pushTarget(this)
this.getter()
// 页面渲染后将 Dep.target = null
popTarget()
}
update(){
// 异步更新操作 就是将更新缓存起来(做一些去重, 防抖)然后一起调用,最后还是调用下方run方法
queueWatcher(this)
}
run(){
this.get()
}
/**
* @description 将watcher 存储dep dep也存储watcher实现双向双向存储, 并做去重处理
* @description 给每个属性都加了个dep属性,用于存储这个渲染watcher (同一个watcher会对应多个dep)
* @description 每个属性可能对应多个视图(多个视图肯定是多个watcher) 一个属性也要对应多个watcher
*/
addDep(dep){
let id = dep.id;
if(!this.depsId.has(id)){
this.depsId.add(id);
this.deps.push(dep);
dep.addSub(this)
}
}
}
export default Watcher
Dep 类
/** 每个劫持的属性 加上唯一的标识 */
let id = 0
/**
* @description 每个劫持的属性 new Dep
*/
class Dep {
constructor() {
this.id = id++
this.subs = []
}
/** dep传给watcher */
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}
addSub(watcher) {
this.subs.push(watcher)
}
notify() {
this.subs.forEach(watcher => watcher.update())
}
}
Dep.target = null
let stack = []
export function pushTarget(watcher) {
Dep.target = watcher
stack.push(watcher)
}
export function popTarget() {
stack.pop()
Dep.target = stack[stack.length - 1]
}
export default Dep
数据劫持
Observer 类(主文件)
import { isObject } from '../utils'
import { arrayMethods } from './array'
import Dep from './dep'
class Observer {
constructor(data) {
// 看这里 数据 加了个Dep
this.dep = new Dep()
Object.defineProperty(data, '__ob__', {
value: this,
enumerable: false
})
/** 数据是数组 */
if (Array.isArray(data)) {
// 针对数组中使用的方法 如push splice... 修改原数组增加的元素(是对象)进行劫持
data.__proto__ = arrayMethods
// 初始化 劫持数组中的每个元素 如果是对象进行劫持
this.observeArray(data)
return
}
/** 数据是对象 */
this.walk(data)
}
walk(data) {
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
observeArray(data) {
data.forEach(item => observe(item))
}
}
/**
* @description 看这里 劫持数据只劫持对象 不劫持数组 通过current.__ob__.dep依赖watcehr
* @description 多层数组 依赖收集 watcher
*/
function dependArray(value) {
for (let i = 0; i < value.length; i++) {
let current = value[i]
current.__ob__ && current.__ob__.dep.depend()
if (Array.isArray(current)) {
dependArray(current)
}
}
}
/** 核心方法 */
/**
* @description 劫持对象数据
*/
function defineReactive(data, key, value) {
let childOb = observe(value)
let dep = new Dep()
Object.defineProperty(data, key, {
get() {
if (Dep.target) {
dep.depend()
// 看这里 数组进行依赖收集watcher
if (childOb) {
childOb.dep.depend()
// 看这里 多层数组[[[]]]
if (Array.isArray(value)) { dependArray(value) }
}
}
return value
},
set(newValue) {
if (newValue !== value) {
observe(newValue)
value = newValue
dep.notify()
}
}
})
}
export function observe(data) {
if (!isObject(data)) return
if (data.__ob__) return data.__ob__
return new Observer(data)
}
arrayMethods调用数组的七个方法时 直接notify()
const oldArrayPrototype = Array.prototype
export let arrayMethods = Object.create(oldArrayPrototype)
/**
* @description 改变原数组的方法
*/
const methods = [
'push',
'pop',
'unshift',
'shift',
'reverse',
'sort',
'splice'
]
methods.forEach(method => {
arrayMethods[method] = function (...args) {
oldArrayPrototype[method].call(this, ...args)
let ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break;
case 'splice':
inserted = args.slice(2)
break;
default:
break;
}
if (inserted) ob.observeArray(inserted)
// 看这里
ob.dep.notify()
}
})
完
发表评论
还没有评论,快来抢沙发吧!