Vue进阶及原理

本文详细介绍了Vue的数据绑定原理,包括Vue2中使用Object.defineProperty()实现响应式,以及Vue3中利用Proxy()改进的响应式机制。还深入探讨了响应式实现的整体分析,包括发布订阅者模式和响应式处理过程,涉及Observer、Dep和Watcher的角色和交互。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、数据绑定原理

Object.defineProperty()方法

  1. 参数1:操作的对象
  2. 参数2:操作对象的名称
  3. 参数3:对属性描述的对象
var obj = {name: 'zs', age: '18'}
Object.defineProperty(obj, gender, {
  value: '男'writable: false //默认false 不可写
  enumerable: false //默认false不可枚举(遍历)
  configurable: false //默认false 以后不可重新定义配置项
})
console.log(obj) //{name: 'zs', age: '18',gender:'男'}
obj.name='a', obj.gender='b'  //{name: 'a', age: '18',gender:'男'} 无法修改defineProperty增加的属性
  for(var i in obj) {
  console.log(k,obj[k])} // {name: 'zs', age: '18'} 无法遍历到defineProperty增加的属性

可以设置get和set方法,分别在获取和赋值时触发。设置set()方法时需要将值存到外部变量中赋值,否则无限递归,set和get方法不可以和value、writable属性同时使用

var obj = {name: 'zs', age: '18'}
var genderValue='男'
Object.defineProperty(obj, 'gender', {
  get(){
  return genderValue //获取值时触发,可添加其他操作
},
  set(newValue){
  genderValue = newValue //设置值时触发,不可写成 gender = newValue导致递归死循环,将新值赋给第三方调用时调用第三方数据
}
})
Obj.genderValue = 'a' // 触发set方法
obj.gender            //触发get 方法

Object.defineProperty只能监听对象单个属性的获取与设置(数据劫持)

vue2 响应式原理

通过Object.defineProperty()实现,设置data后遍历所有的属性,转换为getter和setter,从而在数据变化时进行视图的更新等操作。通过数据劫持的方式将data属性设置为getter、setter。绑定视图通过虚拟DOM实现

<!--模拟响应式原理->
<div id="app">原始内容</div>
<script>
let data ={msg1:'hellow', msg2:'world'}
var vm={}
//遍历劫持对象的所有属性
Object.key(data).forEach(key => {
  Object.defineProperty(vm, key, {
	enumerable: true, 		//可遍历
	configurable: true,		//可重新定义配置项
	get() {
	  return data[key] 
	}
	set(newValue){
	   data[key] = newValue  //更改数据
	   // 更改视图
	   document.querySelector('#app').textContent = data[key]
	}
  })
})
</script>

通过索引下标修改数组无法更新到视图中。而通过数组方法可以更新到视图

  let vm={}
  // 封装为函数用于对数组响应式处理
  const createReactive = (function(){
  const arrMethodName= ['push','pop', 'shift', 'unshist', 'splice', 'sort', 'revers']
  //用于存储处理结果的对象,准备替换掉数组示例的原型指针__proto__
  const customProto = {}
  //避免数组实例无法再使用其他的数组方法
  customProto.__proto__= Array.prototype
  arrMethodName.forEach(method => {
    customProto[method] = function(){
       //确保原始功能可以使用
       const result = Array.prototype[method].apply(this, argument)
       document.querySelector('#app').textContent = this
          return result
     }
  })
    // 需要数据劫持的主体功能,也是递归时需要的功能 
  return function(data, vm){
    Object.keys(data).forEach(key => {
        if(Array.isArray(data[key])){
        //  将当前数组筛查的__proto__更换为customProto
         data[key].__proto__=customoProto
        } else if (typeof data[key] ==='object' && data[key] !== null ) {
          //检测是否为对象,如果为对象进行递归操作
          vm[key] = {}
          createReactive(data[key],vm[key])
          return 
        }
      },
    Object.defineProperty(vm, key, {
        enumerable: true, 		//可遍历
        configurable: true,		//可重新定义配置项
        get() {
          return data[key] 
        },
        set(newValue){
           data[key] = newValue  //更改数据
           // 更改视图
           document.querySelector('#app').textContent = data[key]
        }
      })
  }()
})()
  createReactive(data,vm)

Proxy()

需要new一个实例进行操作
参数1:要操作的对象
参数2:配置对象,对原始对象 的操作

const data = {
	msg1: '内容',
	arr: [1,2,3],
	obj: {name: 'zs', age: 18}
}
const p = new Proxy(data,{
	get(target, property, receiver){
	  console.log(target, property, receiver) //target 指data,property是访问/修改的参数,receiver表示p
	  return target[property]
	},
	set(target,property, value, receiver){
	  target[property] = value
	}
})

可以通过数组下标和方法实现对数据视图的修改

vue3 响应式原理

使用Proxy监听整个对象,不需要遍历监听每个属性。ES6 中新增,IE不支持

<!--模拟响应式原理->
<div id="app">原始内容</div>
<script>
let data ={msg1:'hellow', msg2:'world'}
let vm=new Proxy(data,{
	//执行代理行为的函数
	//当访问vm的成员会执行
	get(target, key){
		console.log('get:key',key,target[key])
		return target[key]
	},
	set(target, key ,newValue){
		console.log('set:key',key,newValue)
		if(target[key] === newValue){
			return 
		}
		target[key] = newValue
		document.querySelector('#app').textContent = target[key]
	}
})
</script>

响应式实现整体分析

Vue类:将data数据注入到Vue实例,便于方法内操作

  • 配置选项
  • 将data属性转化为Getter、Setter并注入Vue实例
  • 监听data所有属性的变化,设置成响应式数据
  • 调用解析功能(解析模板内的插值表达式、指令)
    Observer(发布者):数据劫持,监听数据变化,并在变化是通知Dep
  • 通过数据劫持的方式监视data中的属性变化,变化时通知消息中心Dep
  • 需要考虑data的属性叶铿是对象,也要转换成响应式数据
    Dep(消息中心):存储订阅者以及管理消息的发送
    Watcher(订阅者):订阅数据变化,进行视图更新
  • 实例化Watch时,往Dep对象中添加自己
  • 当数据变化触发dep,dep通知所有对应的 Watcher实例更新视图
    Compiler:解析模板中的指令和插值表达式,并替换相应的数据

发布订阅者模式

模拟实现$on$emit

//事件触发器
class EventEmitter {
  constructor() {
    this.subs = Object.create(null)	//存储事件数组 {'click': [fn1,fn2],'change':[]}
  }
  //注册事件
  $on(eventType, handler){
    this.subs[eventType] = this.subs[eventType] || []
	this.subs[eventType].push(handler)
  }
  //触发事件
  $emit(eventType){
  	if(this.subs[eventType]){
  		this.subs[eventType].forEach(handler =>{handler()})
  	}
  }
}

响应式处理过程

initState() => initData() => observe()

  1. observe(value):src/core/observer/index.js
    作用:判断value是否是对象,不是直接返回;判断value对象是否有__ob__ ,如果没有创建observer对象,有则返回。返回observer对象
  2. Observer类:src/core/observer/index.js
    作用:给value对象定义不可枚举的__ob__属性,记录当前observer对象;数组的响应式处理;对象的响应式处理,调用walk方法(遍历对象的每个属性,为每个属性调用defineReactive)
  3. defineReactive:src/core/observer/index.js
    作用:为每个属性创建dep对象;如果当前属性的值是对象调用observe;定义getter收集依赖,返回属性的值;定义setter,保存新值,新值是对象则调用observe,派发通知
  4. 收集依赖:在watcher对象的get方法中调用pushTarger记录到Dep.target属性;访问data中的成员时收集依赖,把属性对应的watcher对象添加到dep的subs数组中;属性的值也是对象则给childOb收集依赖,子对象添加和删除成员时发送通知。
  5. watcher:数据变化时调用dep.notify()发送通知,会调用watcher对象的update()方法。update()方法中调用queueWatcher()判断watcher是否被处理,如果没被处理会被添加到queue队列中并调用flushSchedulerQueue()。在该函数中触发beforeUpdate钩子函数,调用watcher.run()。清空上一次的依赖,触发actived钩子函数,触发updated钩子函数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值