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