通过Object.defineProperty()把data中的数据代理到Vue的实例上。这样就可以通过vm.xxx来获取data中的数据。
注意:数据代理并不是把data中的数据copy一份到vm上,而是在getter()中对数据进行了劫持,获取vm.xxx其实还是获取data中的数据。
class Vue {
constructor(options){
this.$options=options;
this.$data=options.data;
this.initData();
}
initData(){
let data=this.$data;
const keys=Object.keys(data);
for(let i=0;i<keys.length;i++){
Object.defineProperty(this,keys[i],{
configurable:true,
enumerable:true,
get:function proxyGetter(){
return data[keys[i]]; //对数据进行了劫持
},
set:function proxySetter(val){
data[keys[i]]=val;
}
})
}
}
}
1.利用Object.defineProperty()把data中的数据变成响应式。
2.后面可能经常会用到这个方法,所以把它封装成一个函数。
3.考虑到一个对象里边可能会再嵌套一个对象,所以要递归遍历,把每一个属性都变成响应式。
class Vue {
constructor(options){
this.$options=options;
this.$data=options.data;
this.initData();
}
initData(){
let data=this.$data;
const keys=Object.keys(data);
for(let i=0;i<keys.length;i++){
Object.defineProperty(this,keys[i],{
configurable:true,
enumerable:true,
get:function proxyGetter(){
return data[keys[i]];
},
set:function proxySetter(val){
data[keys[i]]=val;
}
})
}
observe(data); //observe()作用:对data进行观测
}
}
function defineReactive(obj,key,value){
observe(value); //如果被观测的值是对象,则递归
Object.defineProperty(obj,key,{
configurable:true,
enumerable:true,
get:function reactiveGetter(){
console.log(`获取data中${
key}的值`);
return value; //不能在getter()中再次获取值
},
set:function reactiveSetter(val){
if(val!==value){
console.log(`设置data中${
key}的值`);
value=val; //不能在setter()中再次设置值
}
}
})
}
function observe(data){
var type=Object.prototype.toString.call(data);
if(type!=='[object Object]'&&type!=='[object Array]'){
//不是复杂类型,返回为空
return;
}
new Observer(data); //Observer类作用:把data中的数据变成响应式
}
class Observer {
constructor(data){
this.walk(data);
}
walk(data){
let keys=Object.keys(data);
for(let i=0;i<keys.length;i++){
defineReactive(data,keys[i],data[keys[i]]);
}
}
}
先用watch和$watch来测试对属性的监听。
1.当属性发生变化的时候,对应的回调函数肯定不能硬编码在对应的setter()中
解决方法:我们每个属性需要有一个自己的"筐",不管是使用watch初始化还是$watch来监听某个属性时,我们需要把这些回调函数添加到属性对应的"筐"中,等到属性setter触发时,从"筐"中把回调拿出来通知(notify)他们执行。
2.有可能同一个回调依赖多个属性,例如 模板或者computed
解决方法:我们可以对属性进行求值,来触发相应的getter(),在getter中让属性的"筐"去收集当前的回调。
3."筐"去哪里找当前的回调
解决方法:我们可以把当前的回调在触发getter()之前放在一个公共的地方,触发后从公共的地方移除掉。(就像一个人上舞台和下舞台的过程)
把"筐"抽象成一个Dep类的实例,把回调函数抽象成一个Watcher类的实例。
class Vue {
constructor(options){
this.$options=options;
this.$data=options.data;
this.initData();
this.initWatch();
}
initData(){
let data=this.$data;
const keys=Object.keys(data);
for(let i=0;i<keys.length;i++){
Object.defineProperty(this,keys[i],{
configurable:true,
enumerable:true,
get:function proxyGetter(){
return data[keys[i]];
},
set:function proxySetter(val){
data[keys[i]]=val;
}
})
}
observe(data); //observe()作用:对data进行观测
}
initWatch(){
const watch=this.$options.watch;
if(watch){
const watches=Object.keys(watch);
for(let i=0;i<watches.length;i++){
new Watcher(this,watches[i],watch[watches[i]]);
}
}
}
$watch(exp,cb){
new Watcher(this,exp,cb);
}
}
function defineReactive(obj,key,value){
observe(value); //如果被观测的值是对象,则递归
const dep=new Dep(); //每个属性都有自己对应的筐
Object.defineProperty(obj,key,{
configurable:true,
enumerable:true,
get:function reactiveGetter(){
// console.log(`获取data中${key}的值`);
if(Dep.target){
dep.depend();
}
return value; //不能在getter()中再次获取值
},
set:function reactiveSetter(val){
if(val!==value){
dep.notify();
// console.log(`设置data中${key}的值`);
value=val; //不能在setter()中再次设置值
}
}
})
}
function observe(data){
var type=Object.prototype.toString.call(data);
if(type!=='[object Object]'&&type!=='[object Array]'){
//不是复杂类型,返回为空
return;
}
new Observer(data); //Observer类作用:把data中的数据变成响应式
}
class Observer {
constructor(data){
this.walk(data);
}
walk(data){
let keys=Object.keys(data);
for(let i=0;i<keys.length;i++){
defineReactive(data,keys[i],data[keys[i]]);
}
}
}
class Dep {
constructor(){
this.sub=[];
}
depend(){
//收集
if(Dep.target){
this.sub.push(Dep.target);
}
}
notify(){
//通知
this.sub.forEach(watcher=>{
watcher.run();
})
}
}
class Watcher {
constructor(vm,exp,cb){
this.vm=vm;
this.exp=exp;
this.cb=cb;
this.get();
}
get(){
Dep.target=this;
this.vm[this.exp];
Dep.target=null;
}
run(){
this.cb.call(this.vm);
}
}
注意:在实际的vue中,watcher实例的求值和调用回调函数是异步调用的,并且在上一个事件循环当中无论改变几次属性,对应的回调函数只会异步调用一次,所以继续对Watcher类及run方法进行改造
let watcherQueue=[]; //所有的watcher队列
let watcherId=0;
class Watcher {
constructor(vm,exp,cb){
this.vm=vm;
this.exp=exp;
this.cb=cb;
this.get();
this.id=++watcherId;//对每个watcher进行标记,没个watcher都是独一无二的
}
get(){
Dep.target=this;
this.vm[this.exp];
Dep.target=null;
}
run(){
if(watcherQueue.indexOf(this.id)!==-1){
return
}
watcherQueue.push(this.id);
let index=watcherQueue.length-1; //保存新添加watcher的索引
Promise.resolve().then(()=>{
//函数的调用变成异步
this.cb.call(this.vm);
watcherQueue.splice(index,1);
})
}
}
至此,实现了一个基于发布订阅的Dep和Watcher,但还存在以下问题:
1.对象新增的属性无法触发回调
2.数组没有做处理,如果用Object.defineProperty对数组进行劫持,会有以下问题
a.改变数组的顺序,删除数组某一项,数组的下标就会全乱了,之前的初始化数据也全乱了
b.数组原生方法进行增删改查无法触发回调
在实际vue中,每个对象上都有一个__ob__属性,他就是Observer实例,并且该属性上还有一个Dep实例。
1.在创建Observer实例时,也创建一个筐,挂载Observer实例上,再把Observer实例挂载到对象的__ob__属性上。
2.触发getter()时,不光把watcher收集一份到之前的筐里,也收集一份到这个筐里。
3.在用户调用$set时,手动的触发__ob__.dep.notify()
4.在调用notify()之前,把新的属性也定义成响应式。
class Vue {
$set(obj,key,value){
let ob=obj.__ob__;
defineReactive(ovj,key,value);
ob.dep.notify();
}
}
function defineReactive(obj,key,value){
let childOb=observe(value); //如果被观测的值是对象,则递归
const dep=new Dep(); //每个属性都有自己对应的筐
Object.defineProperty(obj,key,{
configurable:true,
enumerable:true,
get:function reactiveGetter(){
// console.log(`获取data中${key}的值`);
if(Dep.target){
dep.depend();
if(childOb){
//如果是一个复杂类型,再收集一份
childOb.dep.depend();
}
}
return value; //不能在getter()中再次获取值
},
})
}
function observe(data){
var type=Object.prototype.toString.call(data);
if(type!=='[object Object]'&&type!=='[object Array]'){
//不是复杂类型,返回为空
return;
}
if(data.__ob__){
return data.__ob__;
}
return new Observer(data); //Observer类作用:把data中的数据变成响应式
}
class Observer {
constructor(data){
this.walk(data);
this.dep=new Dep();
Object.defineProperty(data,'__ob__',{
value:this,
writable:true,
enumerable:false,
configurable:false
})
}
}
1.数组的回调也是通过__ob__.dep来收集的,在数组调用push,pop等方法时,手动触发__ob__.dep.notify
2.在数组的原型链上插入一个自定义对象,拦截原来的push等方法,在自定义对象的同名方法中先执行原本的方法,再人为的调用__ob__.dep.notify()去执行之前收集到的回调。
3.数组里边的元素如果是对象的话,需要变成响应式
4.数组中push的元素也要变成响应式
class Observer {
constructor(data) {
this.dep = new Dep();
if (Array.isArray(data)) {
//如果是数组,用改变数组原型链的这种方式
data.__proto__ = arrayMethods;//自定义的对象
this.observeArray(data);//数组中若有对象,把对象中的元素变成响应式
} else {
this.walk(data);
}
Object.defineProperty(data, '__ob__', {
value: this,
writable: true,
enumerable: false,
configurable: false
})
}
observeArray(arr) {
for (let i = 0; i < arr.length; i++) {
observe(arr[i]);
}
}
walk(data) {
let keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {
defineReactive(data, keys[i], data[keys[i]]);
}
}
}
//处理数组
const methods = ['push', 'pop', 'shift'];
//自定义的对象
const arrayMethods = Object.create(Array.prototype);//创建一个对象,这个对象.proto的值为Array.prototype
const arrayProto = Array.prototype;
methods.forEach(method => {
arrayMethods[method] = function (...arg) {
if (method === 'push') {
this.__ob__.observeArray(args);//数组中添加的元素也变成响应式
}
arrayMethods[method] = function (...arg) {
const result = arrayProto[method].apply(this, args);//apply接收一个数组作为参数
this.__ob__.dep.notify();
return result;
}
}
})
1.是一个函数运行的结果
2.函数里用到的所有属性都会引起计算属性的变化。本质还是一个watcher.
watcher第二个参数exp也可以传一个函数,然后运行这个函数并获取返回值,运行的过程中,函数中的this.xxx都会触发getter(),这样就可以让多个筐收集到这个watcher
3.计算属性不存在data中,要单独进行初始化
4.计算属性只能取,不能存。也就是说,设置setter无效
5.计算属性本身不再需要筐去收集,对一个计算属性进行监听,回调触发的本质是计算属性依赖的其他属性发生了变化
注意:
1.计算属性是惰性的:计算属性依赖的其他属性发生变化时,计算属性不会立即重新计算,而是求值的时候,才会重新计算
2.计算属性是缓存的:如果计算属性依赖的值没有发生变化,即使重新对计算属性求值,也不会重新计算计算属性
还存在问题:
let vm = new Vue({
data:{
person:{
name:'zs'
}
},
watch:{
x() { //2号watcher
console.log('x监听');
}
},
computed:{
x() { //1号watcher
console.log('x计算');
return JSON.stringify(this.person)
}
}
})
person.ob.dep.subs中也就是person对应的筐中,应该收集到两个watcher,一个是1号watcher,一个是2号watcher
解决思路:
1.维护一个栈,有新的watcher上台时入栈,下台时出栈,台上永远是栈顶的watcher
2.watcher被dep收集时,也收集dep,相互收集。计算属性getter完成之后,检查舞台上还有没有watcher,有的话就把自己的watcher收集的dep拿出来通知,收集舞台上的watcher
class Vue {
constructor(options) {
this.$options = options;
this.$data = options.data;
this.initData();
this.initComputed();
this.initWatch();
}
initData() {
let data = this.$data;
const keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {
Object.defineProperty(this, keys[i], {
configurable: true,
enumerable: true,
get: function proxyGetter() {
return data[keys[i]];
},
set: function proxySetter(val) {
data[keys[i]] = val;
}
})
}
observe(data); //observe()作用:对data进行观测
}
initWatch() {
const watch = this.$options.watch;
if (watch) {
const watches = Object.keys(watch);
for (let i = 0; i < watches.length; i++) {
new Watcher(this, watches[i], watch[watches[i]]);
}
}
}
$watch(exp, cb) {
new Watcher(this, exp, cb);
}
$set(obj, key, value) {
let ob = obj.__ob__;
defineReactive(ovj, key, value);
ob.dep.notify();
}
initComputed(){
const computed=this.$options.computed;
if(computed){
const keys=Object.keys(computed);
for(let i=0;i<keys.length;i++){
const watcher=new Watcher(this,computed[keys[i]],function(){
},{
lazy:true})
Object.defineProperty(this,keys[i],{
enumerable:true,
configurable:true,
get:function computedGetter(){
if(watcher.dirty){
//依赖的属性发生变化时,计算属性才会执行
watcher.get();
watcher.dirty=false;
}
if(Dep.target) {
//如果舞台上还有watcher
watcher.deps.forEach(dep=>{
dep.depend()
})
}
return watcher.value;
},
set:function computedSetter(){
console.warn('请不要对computed进行赋值');
}
})
}
}
}
}
function defineReactive(obj, key, value) {
let childOb = observe(value); //如果被观测的值是对象,则递归
const dep = new Dep(); //每个属性都有自己对应的筐
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get: function reactiveGetter() {
// console.log(`获取data中${key}的值`);
if (Dep.target) {
dep.depend();
if (childOb) {
//如果是一个复杂类型,再收集一份
childOb.dep.depend();
}
}
return value; //不能在getter()中再次获取值
},
set: function reactiveSetter(val) {
if (val !== value) {
dep.notify();
// console.log(`设置data中${key}的值`);
value = val; //不能在setter()中再次设置值
}
}
})
}
function observe(data) {
var type = Object.prototype.toString.call(data);
if (type !== '[object Object]' && type !== '[object Array]') {
//不是复杂类型,返回为空
return;
}
if (data.__ob__) {
return data.__ob__;
}
return new Observer(data); //Observer类作用:把data中的数据变成响应式
}
class Observer {
constructor(data) {
this.dep = new Dep();
if (Array.isArray(data)) {
//如果是数组,用改变数组原型链的这种方式
data.__proto__ = arrayMethods;//自定义的对象
this.observeArray(data);//数组中若有对象,把对象中的元素变成响应式
} else {
this.walk(data);
}
Object.defineProperty(data, '__ob__', {
value: this,
writable: true,
enumerable: false,
configurable: false
})
}
observeArray(arr) {
for (let i = 0; i < arr.length; i++) {
observe(arr[i]);
}
}
walk(data) {
let keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {
defineReactive(data, keys[i], data[keys[i]]);
}
}
}
class Dep {
constructor() {
this.sub = [];
}
addSub(watcher){
this.sub.push(watcher);
}
depend() {
//收集
if (Dep.target) {
// this.sub.push(Dep.target);
Dep.target.addDep(this);//watcher收集dep
}
}
notify() {
//通知
this.sub.forEach(watcher => {
watcher.update();
})
}
}
let targetStack=[];//用栈来维护舞台上的watcher
let watcherQueue = []; //所有的watcher队列
let watcherId = 0;
class Watcher {
constructor(vm, exp, cb,options={
}) {
this.dirty=this.lazy=!!options.lazy;
this.vm = vm;
this.exp = exp;
this.cb = cb;
this.deps=[];
this.lazy || this.get();
this.id = ++watcherId;//对每个watcher进行标记,没个watcher都是独一无二的
}
addDep(dep){
if(this.deps.indexOf(dep)!==-1){
return
}
this.deps.push(dep);
dep.addSub(this);
}
get() {
Dep.target = this;
targetStack.push(this);
if(typeof this.exp === 'function'){
this.value=this.exp.call(this.vm);
}else {
this.value=this.vm[this.exp];
}
targetStack.pop();
Dep.target=targetStack.length? targetStack[targetStack.length-1]:null;
// Dep.target = null;
}
update(){
if(this.lazy){
this.dirty=true;
}else {
this.run();
}
}
run() {
if (watcherQueue.indexOf(this.id) !== -1) {
return
}
watcherQueue.push(this.id);
let index = watcherQueue.length - 1; //保存新添加watcher的索引
Promise.resolve().then(() => {
//函数的调用变成异步
this.get();//重新求值
this.cb.call(this.vm);
watcherQueue.splice(index, 1);
})
}
}
const methods = ['push', 'pop', 'shift'];
//自定义的对象
const arrayMethods = Object.create(Array.prototype);//创建一个对象,这个对象.proto的值为Array.prototype
const arrayProto = Array.prototype;
methods.forEach(method => {
arrayMethods[method] = function (...arg) {
if (method === 'push') {
this.__ob__.observeArray(args);//数组中添加的元素也变成响应式
}
arrayMethods[method] = function (...arg) {
const result = arrayProto[method].apply(this, args);//apply接收一个数组作为参数
this.__ob__.dep.notify();
return result;
}
}
})
对html进行响应,最简单的方法就是创建一个watcher:
new Watcher(this,()=>{
document.querySelector('#app').innerHTML=`${
this.name}`
},()=>{
})
这个watcher被称为render watcher
还存在一些问题:
1.用户是可以使用模板语法的,需要把模板进行一些处理,最终转化成一个执行dom更新的函数
2.直接替换所有的dom开销很大,最好按需更新dom
Vdom:js对象,描述当前dom长什么样。
作用:
a.如果视图通过vdom来描述,当数据发生改变时,将新vdom和旧vdom进行对比,找到哪里发生了变化,再去对应的dom上改变响应的元素。不用暴力刷新整个dom。操作js对象是很快的,但是操作dom元素很慢。
b.上述步骤只有最后一步更新需要依赖dom api,意味着只要能跑js的地方就可以用vdom去描述当前视图,更新时,只要调用相应平台的api就行了,实现了跨平台。
渲染函数:运行渲染函数,就会生成vdom。
Vue实例如果传入了dom或者template,首先就是要把模板字符串转化为渲染函数,这个过程叫 编译。
关于编译原理:
1.将模板字符串转化为AST(parser:解析器)
2.对AST进行静态节点标记,主要用来做虚拟dom的渲染优化(优化器)
3.使用AST生成render()函数 (codegenerate)
AST树:
{
children: [{…}],
parent: {},
tag: “div”,
type: 1, //1-元素节点 2-带变量的文本节点 3-纯文本节点,
expression:’_s(name)’, //type如果是2,则返回_s(变量)
text:’{ {name}}’ //文本节点编译前的字符串
}
/**
*
以<为标识符,代表一个开始标签或者是结束标签,如果是开始标签,代表树的层级加了一层,如果是结束标签代表层级回退了一层。同时每一层要记录它的父级元素是谁。
所以可以使用一个栈去维护当前元素到了哪一层。有开始标签则入栈,结束标签则出栈。另外,标签之间是文本节点,文本节点不对栈进行操作
实现对HTML进行parse
*/
function parser(html) {
let stack = [];
let root;
let currentParent;
while (html) {
let ltIndex = html.indexOf('<')
if (ltIndex > 0) {
//前面有文本
//type 1-元素节点 2-带变量的文本节点 3-纯文本节点
let text = html.slice(0, ltIndex)
// const element = {
// type: 3,
// text,
// parent:currentParent
// }
const element = parseText(text)
element.parent = currentParent
currentParent.children.push(element)
html = html.slice(ltIndex)
} else if (html[ltIndex + 1] !== '/') {
//前面没有文本,且是开始标签
let gtIndex = html.indexOf('>')
const element = {
type: 1,
tag: html.slice(ltIndex + 1, gtIndex), //不考虑dom的任何属性
parent: currentParent,
children: [],
}
if (!root) {
root = element
} else {
currentParent.children.push(element)
}
stack.push(element)
currentParent = element
html = html.slice(gtIndex + 1)
} else {
//结束标签
let gtIndex = html.indexOf('>')
stack.pop()
currentParent = stack[stack.length - 1]
html = html.slice(gtIndex + 1)
}
}
return root
}
/**
实现对文本节点的parse
以{
{和}}为标识符,对把插值变量名转换成_s(name)的形式。
*/
function parseText(text) {
let originText = text
let tokens = []
let type = 3
while (text) {
let start = text.indexOf('{
{')
let end = text.indexOf('}}')
if (start !== -1 && end !== -1) {
type = 2
if (start > 0) {
tokens.push(JSON.stringify(text.slice(0, start)))
}
let exp = text.slice(start + 2, end)
tokens.push(`_s(${
exp})`)
text = text.slice(end + 2)
} else {
tokens.push(JSON.stringify(text))
text = ''
}
}
let element = {
type,
text: originText,
}
type === 2 ? element.expression = tokens.join('+') : ''
return element
}
/**
* 生成AST后需要把AST再转换成渲染函数
1. 递归AST,遇到元素节点则生成如下格式的字符串_c(标签名, 属性对象, 后代数组)
2. 遇到文本节点,如果是纯文本节点,则生成如下格式的字符串_v(文本字符串)
3. 如果是带变量的文本节点,则生成如下格式的字符串_v(_s(变量名))
4. 为了让变量能正常取到,生成最后将字符串包一层with(this)
5. 最后把字符串作为函数体生成一个函数,挂载到vm.$options上
*
*/
function generate(ast) {
const code = genElement(ast)
return {
render: `with(this){return ${
code}}`,
}
}
function genElement(el) {
const children = genChildren(el)
let code = `_c('${
el.tag}',{},${
children})`
return code
}
function genChildren(el) {
if (el.children.length) {
return '[' + el.children.map(child => genNode(child)).join(',') + ']'
}
}
function genNode(node) {
if (node.type === 1) {
return genElement(node)
} else {
return genText(node)
}
}
function genText(text) {
return `_v(${
text.type === 2 ? text.expression : JSON.stringify(text.text)})`
}
由渲染函数生成vdom。
定义一个简单的类VNode,描述一个DOM节点的相关信息,实现上一节渲染函数中未实现的_c,_v,_s函数。
_c(tag, attrs, children) {
return new VNode(tag, attrs, children)
}
_v(text) {
return new VNode(null, null, null, text)
}
_s(val) {
if (val === null || val === undefined) {
return ''
} else if (typeof val === 'object') {
return JSON.stringify(val)
} else {
return String(val)
}
}
class VNode {
constructor(tag, attrs, children, text) {
this.tag = tag
this.attrs = attrs
this.children = children
this.text = text
}
}
运行vm.$options.render.call(vm)即可得到当前vdom。描述了渲染后的HTML应该长什么样
首先实现一个createElm函数将Vdom转化为真正的dom,稍后更新dom会用到此函数
//首先实现一个createElm函数将Vdom转化为真正的dom,稍后更新dom会用到此函数
function createElm(vnode) {
if (!vnode.tag) {
const el = document.createTextNode(vnode.text)
vnode.elm = el
return el
}
const el = document.createElement(vnode.tag)
vnode.elm = el
vnode.children.map(createElm).forEach(childDom => {
el.appendChild(childDom)
})
return el
}
vdom中最核心的就是patch
//改造后
constructor(options){
if (options.el) {
let html = document.querySelector(options.el).outerHTML;
let ast = parser(html);
let code = generate(ast).render;
this.$options.render = new Function(code);//生成了渲染函数
this.$mount(options.el); //把vue实例和dom元素联系起来
}
}
$mount(el) {
this.$el = document.querySelector(el);//把dom元素挂载到vue实例上
this._watcher = new Watcher(this, () => {
//生成一个 render watcher
this._update(this.$options.render.call(this));
}, () => {
})
}
_update(vnode) {
if (this._vnode) {
//this._vnode:旧的vdom
patch(this._vnode, vnode);//旧的vnode 新的vnode
} else {
patch(this.$el, vnode);
}
this._vnode = vnode; //把旧的vdom存起来
}
//将原先粗暴式的代码进行改造
//1.实现一个$mount函数,将原先的初始化render watcher的逻辑搬到$mount里
//2.实现一个_update函数,该函数接受一个新的vdom,然后对比新旧vdom并更新真实dom,render watcher中不再暴力更新dom,而是调用_update函数
//接下来,实现vdom机制中最核心的patch.vue中vdom进行patch的逻辑是基于snabbdom
/**
* 1.patch函数接受两个参数:旧的vdom和新的vdom
* 2.当第一次挂载时旧的vdom是一个真实dom,单独处理
* 3.后续更新时,分为如下三种情况
* a.新节点不存在,则删除对应的dom
* b.新旧节点标签不一样或文本不一样,则调用createElm生成新dom,并替换旧dom
* c.旧节点不存在,新节点存在,则调用createElm生成新dom,并原dom后添加新dom
* d.递归以上逻辑
*/
function patch(oldNode, newNode,) {
const isRealElement = oldNode.nodeType //只有真实的dom元素才会有nodeType
//如果是对真实dom进行patch
if (isRealElement) {
let parent = oldNode.parentNode
parent.replaceChild(createElm(newNode), oldNode) //数据和dom元素进行拼接
return
}
//当前vdom对应的真实dom
let el = oldNode.elm //对el进行删改
//当前vdom对应的真实父级dom
let parent = el.parentNode //在父级的基础上
if (newNode) {
newNode.elm = el
}
//开始进行对比
if (!newNode) {
//新节点不存在,删除
parent.removeChild(el)
} else if (changed(newNode, oldNode)) {
//发生了变化,替换
parent.replaceChild(createElm(newNode), el)
} else if (newNode.children) {
const newLength = newNode.children.length
const oldLength = oldNode.children.length
for (let i = 0; i < newLength || i < oldLength; i++) {
if (i >= oldLength) {
//旧节点不存在,有多余的新节点,增加
el.appendChild(createElm(newNode.children[i]))
} else {
//递归
patch(oldNode.children[i], newNode.children[i])
}
}
}
}
//判断是否是相同节点
function changed(newNode, oldNode) {
return (newNode.tag !== oldNode.tag || newNode.text !== oldNode.text)
}
以上就是vue的基本原理!