1.渲染原理
- 1.将更新的功能封装为一个Watcher的类;
- 2.渲染页面前,会将当前watcher放到Dep类上;
- 3.在Vue中页面渲染时使用的属性,需要进行依赖收集,收集对象的渲染watcher
- 4.取值时,给每个属性都添加了个dep属性,用于存储这个渲染watcher「同一个watcher会对应多个dep」
- 5.每个属性可能对应多个视图「多个视图肯定时多个watcher」,一个属性要对应多个watcher
- 6.dep.depend()->通知dep存放watcher->Dep.target.addDep->通知watcher存放dep
1.1.测试实例
在2s后用户改变
name
的值,使试图自动刷新功能实现
<body>
<div id="app" style="color:red;background:green">hello {{name}} world</div>
<script src="./dist/vue.js"></script>
<script>
const vm=new Vue({
el:'#app',
data:{
name:'zhangsan'
}
})
setTimeout(() => {
vm.name='lisi';
}, 2000);
</script>
</body>
复制代码
1.2.lifecycle.js
import Watcher from "./observer/watcher";
import {
patch
} from "./vdom/patch";
export function lifecycleMixin(Vue) {
Vue.prototype._update = function (vnode) {
const vm = this;
//需要给vm.$el赋值为新的虚拟DOM
vm.$el = patch(vm.$el, vnode);
}
}
/**
* 组件挂载
* @param {*} vm
* @param {*} el <div id='app'></div>
*/
export function mountComponent(vm, el) {
/**
* TODO:更新函数
* 1.调用_render生成vdom
* 2.调用_update进行更新操作
*/
const updateComponent = () => {
vm._update(vm._render());
}
//true代表渲染watcher
const watcher=new Watcher(vm, updateComponent, () => {
console.log('更新试图')
}, true);
}
复制代码
1.3.observer/watcher.js
每个组件拥有一个渲染watcher
import {
popTarget,
pushTarget
} from "./dep";
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++;
this.getter = exprOrFn;
this.deps = [];
this.depsId = new Set();
this.get(); //默认初始化要执行一次
}
get() {
//Dep.target=watcher
pushTarget(this);
this.getter(); //TODO:this.getter->render()执行,vm取值,会在get
popTarget();
}
update() {
this.get();
}
addDep(dep) {
//防止同一个watcher里存多个相同的dep
if (!this.depsId.has(dep.id)) {
this.depsId.add(dep.id);
this.deps.push(dep);
dep.addSub(this); //dep关联watcher
}
}
}
export default Watcher;
复制代码
1.4.observer/dep.js
- 1.每个属性都拥有一个dep
- 2.dep中存放多个watcher
let id = 0;
class Dep {
constructor() {
this.id = id++;
this.subs = []; //存放watcher的
}
depend() {
if (Dep.target) {
//让watcher标记dep
Dep.target.addDep(this);
}
}
addSub(watcher) {
this.subs.push(watcher);
}
notify(){
this.subs.forEach(watcher=>watcher.update());
}
}
Dep.target = null;
export function pushTarget(watcher) {
Dep.target = watcher;
}
export function popTarget() {
Dep.target = null;
}
export default Dep;
复制代码
1.5.observer/index.js
- 每个属性拥有一个自己的dep
- get时,将属性dep与watcher建立关系
- set使,通知dep中存放的watcher更新
function defineReactive(data, key, value) {
observe(value); //TODO:如果value是一个对象,需要对value进行深层次的劫持操作
//TODO:每个属性都拥有一个dep属性
const dep = new Dep();
Object.defineProperty(data, key, {
get() {
if (Dep.target) {//render时,回来get中取值,在此处将dep与watcher建立双向关联关系
dep.depend(); //让dep记住watcher
}
return value;
},
set(newVal) {
if (newVal === value) return;
observe(newVal); //TODO:重新设置的值可能是一个对象,这个时候需要重新对其进行劫持处理
value = newVal;
dep.notify(); //修改值时,通知当前的属性存放的watcher执行
}
})
}
复制代码
2.异步更新
<body>
<div id="app" style="color:red;background:green">hello {{name}} world</div>
<script src="./dist/vue.js"></script>
<script>
const vm=new Vue({
el:'#app',
data:{
name:'zhangsan'
}
})
setTimeout(() => {
vm.name='lisi';
vm.name='111';
vm.name='2222';
vm.name='333';
console.log(vm.$el)
vm.$nextTick(()=>{
console.log(vm.$el);
})
}, 2000);
</script>
</body>
复制代码
2.1.observer/watcher.js
update() {
//多次调用update,希望先将watcher缓存,一起更新
queueWatcher(this);
}
run() {
console.log('run')
this.get();
}
复制代码
2.2.observer/scheduler.js
import {
nextTick
} from "../utils";
let queues = [];
let has = {};
let pending = false;
function flushSchedulerQueue() {
for (let i = 0; i < queues.length; i++) {
queues[i].run(); //更新视图
}
queues = [];
has = {};
pending = false;
}
export function queueWatcher(watcher) {
const id = watcher.id;
if (has[id] == null) { //去重
has[id] = true;
queues.push(watcher);
if (!pending) { //防抖
nextTick(flushSchedulerQueue);
pending = true;
}
}
}
复制代码
2.3.lifecycle.js
export function lifecycleMixin(Vue) {
Vue.prototype._update = function (vnode) {
const vm = this;
//需要给vm.$el赋值为新的虚拟DOM
vm.$el = patch(vm.$el, vnode);
}
//用户自己调用nextTick时
Vue.prototype.$nextTick = function (cb) {
nextTick(cb);
}
}
复制代码
2.4.utils.js
const callbacks = [];
let waiting = false;
function flushCallbacks() {
callbacks.forEach(cb => cb());
waiting = false
}
function timer(flushCallbacks) {
let timerFn = () => {};
if (Promise) { //是否支持Promise
timerFn = () => {
Promise.resolve().then(flushCallbacks);
}
} else if (MutationObserver) { //是否支持文本变化,微任务
let textNode = document.createTextNode(0);
//监听文本变化
const observe = new MutationObserver(flushCallbacks);
observe.observe(textNode, {
characterData: true
})
timerFn = () => {
textNode.textContent = 1;
}
} else if (setImmediate) { //只有IE支持
timerFn = () => {
setImmediate(flushCallbacks);
}
} else {
timerFn = () => {
setTimeout(flushCallbacks);
}
}
timerFn();
}
export function nextTick(cb) {
/**
* TODO:
* 1.属性赋值的nextTick
* 2.用户调用vm.$nextTick
*/
callbacks.push(cb);
if (!waiting) {
//vue2中考虑了兼容问题,vu3中直接Promise.resolve().then()
timer(flushCallbacks);
waiting = true;
}
}
复制代码
3.数组更新原理
- 1.Vue中嵌套层次不能太深,否则会有大量递归
- 2.Vue中对象通过的是defineProperty实现的响应式,拦截了get和set。如果不存在的属性不会拦截,也不会相应。可以使用$set让对象自己notify,或者赋予一个新对象
- 3.Vue中的数组改索引和长度是不会影响更新的,通过变异7种方法可以更新视图,数组中如果是对象类型,修改对象也可以更新视图
3.1.一维数组
3.1.1.测试
<body>
<div id="app" style="color:red;background:green">{{arr}}</div>
<script src="./dist/vue.js"></script>
<script>
const vm=new Vue({
el:'#app',
data:{
arr:[1,2,3]
}
})
setTimeout(() => {
vm.arr.push(4);
}, 2000);
</script>
</body>
复制代码
3.1.2.observer/index.js
class Observer {
constructor(data) {
//TODO:给外层数据添加dep属性
this.dep = new Dep();
}
}
function defineReactive(data, key, value) {
const childOb = observe(value); //TODO:如果value是一个对象,需要对value进行深层次的劫持操作
Object.defineProperty(data, key, {
get() {
if (Dep.target) {
dep.depend(); //让dep记住watcher
/**
* TODO:childOb
* 1.数组情况,假如:value是arr时,childOb=new Observe()
* 2.对象情况,对象是无法对新增属性进行收集的,后续可以通过$set实现
*/
if (childOb) {
childOb.dep.depend(); //数组/对象记录watcher
}
}
return value;
}
})
}
export function observe(data) {
//TODO:data必须是一个对象,默认最外层必须是一个对象
if (!isObject(data)) return;
//如果观察的数据已经有了__ob__属性,说明这个数据已经被劫持过了,不用再劫持
if (data.__ob__) return data.__ob__;
return new Observer(data);
}
复制代码
3.1.3.observer/array.js
methods.forEach(method => {
//用户调用的如果是上面的7种方法,会先走自己重新的方法
arrayMethods[method] = function (...args) {
//新增的数据需要对其进行劫持 「this.__ob__是Observer实例」
if (inserted) this.__ob__.observeArray(inserted);
//TODO:数组在调用7种方法时,通过外层的dep通知视图更新
this.__ob__.dep.notify();
}
})
复制代码
3.2.多维数组
3.2.1.测试
<body>
<div id="app" style="color:red;background:green">{{arr}}</div>
<script src="./dist/vue.js"></script>
<script>
const vm=new Vue({
el:'#app',
data:{
arr:[[1,2,3]]
}
})
setTimeout(() => {
vm.arr[0].push(4);
}, 2000);
</script>
</body>
复制代码
3.2.2.observer/index.js
function dependArray(value) {
for (let i = 0; i < value.length; i++) {
const current = value[i];
current.__ob__ && current.__ob__.dep.depend();
if (Array.isArray(current)) {
dependArray(current);
}
}
}
/**
* TODO:Vue2为什么性能不好,主要原因就是数据的劫持的全量劫持
* @param {*} data 原数据
* @param {*} key key
* @param {*} value 值
*/
function defineReactive(data, key, value) {
const childOb = observe(value); //TODO:如果value是一个对象,需要对value进行深层次的劫持操作
//TODO:每个属性都拥有一个dep属性
const dep = new Dep();
Object.defineProperty(data, key, {
get() {
if (Dep.target) {
dep.depend(); //让dep记住watcher
/**
* TODO:childOb
* 1.数组情况,假如:value是arr时,childOb=new Observe()
* 2.对象情况,对象是无法对新增属性进行收集的,后续可以通过$set实现
*/
if (childOb) {
childOb.dep.depend(); //数组/对象记录watcher
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value;
}
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END