Vue原理

0 MVVM 

 aafbb0792b85949d8ed25c0f254a589c.png

1 数据代理

概念:通过vm对象代理data对象中属性的操作,从而更方便的操作data中的数据。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>何为数据代理</title>
	</head>
	<body>
		<!-- 数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)-->
		<script type="text/javascript" >
			let obj = {x:100}
			let obj2 = {y:200}

			Object.defineProperty(obj2,'x',{
				get(){
					return obj.x
				},
				set(value){
					obj.x = value
				}
			})
		</script>
	</body>
</html>

原理:通过Object.defineProperty()把data中的属性添加到vm上。vm上的每个属性都有一个getter/setter,通过getter/setter读写data中对应的属性。(this.xxx===this._data.xxx

e59c61f836b22e14de83a2b98caf5575.png2 数据劫持(响应式)

2.1 v2.x

原理:vue会监视data中所有层次的数据。vm调用setter,更新_data,是数据代理。_data变,调setter,更新页面,是数据绑定。

对象类型:通过```Object.defineProperty()```对属性的读取、修改进行拦截(数据劫持)。

数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。

2.1.1 基本数据监听

问题:

        对象新增属性、删除属性, 界面不会更新。(无法监听新增/删除属性)

        直接通过下标修改数组, 界面不会自动更新。(无法监听数组)

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>模拟一个数据监测</title>
	</head>
	<body>
		<script type="text/javascript" >

			let data = {
				name:'尚硅谷',
				address:'北京',
			}

			//创建一个监视的实例对象,用于监视data中属性的变化
			const obs = new Observer(data)		
			console.log(obs)	

			//准备一个vm实例对象
			let vm = {}
			vm._data = data = obs

			function Observer(obj){
				//汇总对象中所有的属性形成一个数组
				const keys = Object.keys(obj)
				//遍历
				keys.forEach((k)=>{
					Object.defineProperty(this,k,{
						get(){
							return obj[k]
						},
						set(val){
							console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`)
							obj[k] = val
						}
					})
				})
			}
			
		</script>
	</body>
</html>

2.1.2 对象深度监听和数组监听

深度监听需要递归到底,一次性计算量大;

// 触发更新视图
function updateView() {
    console.log('视图更新')
}

// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
    arrProto[methodName] = function () {
        updateView() // 触发视图更新
        oldArrayProperty[methodName].call(this, ...arguments)
        // Array.prototype.push.call(this, ...arguments)
    }
})

// 重新定义属性,监听起来
function defineReactive(target, key, value) {
    // 深度监听
    observer(value)

    // 核心 API
    Object.defineProperty(target, key, {
        get() {
            return value
        },
        set(newValue) {
            if (newValue !== value) {
                // 深度监听
                observer(newValue)

                // 设置新值
                // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
                value = newValue

                // 触发更新视图
                updateView()
            }
        }
    })
}

// 监听对象属性
function observer(target) {
    if (typeof target !== 'object' || target === null) {
        // 不是对象或数组
        return target
    }

    // 污染全局的 Array 原型
    // Array.prototype.push = function () {
    //     updateView()
    //     ...
    // }

    if (Array.isArray(target)) {
        target.__proto__ = arrProto
    }

    // 重新定义各个属性(for in 也可以遍历数组)
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
}

// 准备数据
const data = {
    name: 'zhangsan',
    age: 20,
    info: {
        address: '北京' // 需要深度监听
    },
    nums: [10, 20, 30]
}

// 监听数据
observer(data)

// 测试
// data.name = 'lisi'
// data.age = 21
// // console.log('age', data.age)
// data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
// delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete
// data.info.address = '上海' // 深度监听
data.nums.push(4) // 监听数组

2.1.3 监听新增/删除属性  & 数组

//数组&对象
//Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
Vue.set(target,propertyName/index,value)
vm.$set(target,propertyName/index,value)
vm.$delete(target,propertyName/index)

//数组
//数组响应式API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
//不使用包装方法
this.student.hobby = this.student.hobby.filter((h)=>{
    return h !== '抽烟'
})

2.2 v3.x

vue3通过Proxy实现响应式 。深度监听:vue3按需递归;vue2是全部递归。

Proxy无法兼容所有浏览器,无法polyfill。

2.2.1 监听数组和对象 

getter中只需要处理非原型属性。

setter中重复数据无需处理。

   96be105c57881b6e8f452ec8b75b9970.png

// const data = {
//     name: 'zhangsan',
//     age: 20,
// }
const data = ['a', 'b', 'c']

const proxyData = new Proxy(data, {
    get(target, key, receiver) {
        // 只处理本身(非原型的)属性
        const ownKeys = Reflect.ownKeys(target)
        if (ownKeys.includes(key)) {
            console.log('get', key) // 监听
        }

        const result = Reflect.get(target, key, receiver)//Reflect和Proxy一一对应,代替Object;receiver就是proxyData
        return result // 返回结果
    },
    set(target, key, val, receiver) {
        // 重复的数据,不处理
        if (val === target[key]) {
            return true
        }

        const result = Reflect.set(target, key, val, receiver)
        console.log('set', key, val)
        // console.log('result', result) // true
        return result // 是否设置成功
    },
    deleteProperty(target, key) {
        const result = Reflect.deleteProperty(target, key)
        console.log('delete property', key)
        // console.log('result', result) // true
        return result // 是否删除成功
    }
})

2.2.2 深度监听

// 创建响应式
function reactive(target = {}) {
    if (typeof target !== 'object' || target == null) {
        // 不是对象或数组,则返回
        return target
    }

    // 代理配置
    const proxyConf = {
        get(target, key, receiver) {
            // 只处理本身(非原型的)属性
            const ownKeys = Reflect.ownKeys(target)
            if (ownKeys.includes(key)) {
                console.log('get', key) // 监听
            }
    
            const result = Reflect.get(target, key, receiver)
        
            // 深度监听
            // 性能如何提升的?
            return reactive(result)
        },
        set(target, key, val, receiver) {
            // 重复的数据,不处理
            if (val === target[key]) {
                return true
            }
    
            const ownKeys = Reflect.ownKeys(target)
            if (ownKeys.includes(key)) {
                console.log('已有的 key', key)
            } else {
                console.log('新增的 key', key)
            }

            const result = Reflect.set(target, key, val, receiver)
            console.log('set', key, val)
            // console.log('result', result) // true
            return result // 是否设置成功
        },
        deleteProperty(target, key) {
            const result = Reflect.deleteProperty(target, key)
            console.log('delete property', key)
            // console.log('result', result) // true
            return result // 是否删除成功
        }
    }

    // 生成代理对象
    const observed = new Proxy(target, proxyConf)
    return observed
}

// 测试数据
const data = {
    name: 'zhangsan',
    age: 20,
    info: {
        city: 'beijing',
        a: {
            b: {
                c: {
                    d: {
                        e: 100
                    }
                }
            }
        }
    }
}

const proxyData = reactive(data)

2.2.3 proxy

Vue 3 深入响应式原理 - 聊一聊响应式构建的那些经历_慕课手记

2.3 响应式原理

2.3.1 数据绑定

1). 作用:
    实现数据的
更新显示
2). 基本原理:
   a.通过Object.defineProperterty()给data中所有属性添加setter/getter, 实现数据劫持
   b.为每个data中的属性创建一个对应的dep对象, 一旦属性数据变化, 通知dep对象
   c.getter为模板中的每个表达式创建对应的watcher, 并关联到对应的dep上
   d.一旦dep收到setter数据变化的通知, 会通知所有关联的watcher, watcher收到通知后就更新对应的节点

2.3.2 双向数据绑定 

1). vue实现数据绑定主要是:采用数据劫持 + 消息订阅发布模式的方式,
2). 通过Object.defineProperty()来劫持/监视data中的属性,在数据变动时发布消息给所有订阅者,每个订阅者去更新对应的DOM节点。(数据--->视图)
3). 双向数据绑定是单向数据绑定的基础上, 给元素绑定input监听, 一旦输入改变了, 将最新的值保存到对应的属性上(视图--->数据)

vue模板解析

1). 目的
   实现
初始化显示
2). 整体流程
           1). 将el的所有子节点取出, 添加到一个新建的文档fragment对象中
           2). 对fragment中的所有层次子节点递归进行编译解析处理

                a. 编译/解析包含大括号表达式的文本节点: textNode.textContent = value。根据正则对象匹配出表达式字符串,从data中取出对应的值。
                b. 编译事件指令: elementNode.addEventListener('eventName', callback)。指令中取出事件名,指令表达式从methods得到处理函数。
                c. 编译一般指令: elementNode.xxx = value。指令中取指令名,data中取出指令表达式的值。根据指令值设置节点属性:node.value=xxx;node.className=xxx
           3). 将解析后的fragment添加到el中显示

Dep:

        给data属性初始化进行数据绑定时创建;

        个数就是data中属性的个数

        结构是  id:标识;subs:[ ]//n个相关的watcher容器

Watcher:

        初始化指令/插值时创建的;

        个数是表达式的个数;

        结构如下:

两者关系? 

fb6d8ee47e6f07b27aeb6d91821a66dd.png 4  vue如何渲染和更新

模板编译:vue-template-compiler将模板编译为render函数,执行render函数生成vdom。

const compiler = require('vue-template-compiler')

const template = `<p>{{message}}</p>`
with(this){return createElement('p',[createTextVNode(toString(message))])}

// const template = `
//     <div id="div1" class="container">
//         <img :src="imgUrl"/>
//     </div>
// `
// with(this){return _c('div',
//      {staticClass:"container",attrs:{"id":"div1"}},
//      [
//          _c('img',{attrs:{"src":imgUrl}})])}

// v-model:input事件中更新name变量,:value=name
const template = `<input type="text" v-model="name">`
// with(this){return _c('input',{directives:[{name:"model",rawName:"v-model",value:(name),expression:"name"}],attrs:{"type":"text"},domProps:{"value":(name)},on:{"input":function($event){if($event.target.composing)return;name=$event.target.value}}})}

const res = compiler.compile(template)
console.log(res.render)

初次渲染

        模板编译:将模板编译为render函数(使用webpack中的vue-loader,会在开发环境下编译模板)

        响应式:监听data中的属性,触发getter

        模板渲染:执行render函数,生成vnode,基于vnode执行patch和diff

更新过程

        响应式:修改data,触发setter

        模板渲染:重新执行render函数,生成newVnode,基于newVnode执行patch

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值