vue双向数据绑定原理学习
vue源码
1、双向数据绑定
- 学习数组的reduce函数
var arr = [2,4,5,67,78,5,1]
// arr.reduce(函数,初始值)
// arr.reduce((上次的初始值,当前循环item项,当前的元素的索引,当前元素所属的数组对象)=>{ return XX },初始值)
const total = arr.reduce((val,item)=>{return val + item
}, 0)
console.log(total) // 162
- reduce链式获取对象属性值操作
const obj ={name: 'zs',info: {address: {location: '北京大东'}}
}
const attrstr = 'info.address.location'
// const location = attrstr.split('.') // [ 'info', 'address', 'location' ]
const location = attrstr.split('.').reduce((newObj, k) => newObj[k], obj)
console.log(location, 'location') // 北京大东 location
2 、vue发布订阅原理和实现
- 简单实现
// 发布订阅模式简单实现// Dep类 有一个收集者,收集订阅者的行为,当某个时间成熟了,就把收到的订阅者行为都发布出来
// (首先有个数组,专门来存放一个向数组中追加订阅信息的方法,其次,还要提供一个向数组追加订阅信息的方法,然后,还要提供一个循环,循坏出发数组中的每个订阅信息// Watcher类 负责订阅一些事件// 收集依赖、收集订阅者
class Dep{constructor() {// 这个subs数组,用来存放所有订阅者的信息this.subs = []}// 向subs数组中,添加订阅者的信息addSub(watcher){this.subs.push(watcher)}// 发布通知的方法notify() {this.subs.forEach((watcher) => watcher.update())}
}// 订阅者的类
class Watcher {constructor(cb) {this.cb = cb}// 触发回调的方法update() {this.cb()}
}// 实例化一个watcher类
const w1 = new Watcher(()=>{console.log('我是一个订阅者')
})// 实例化一个watcher类
const w2 = new Watcher(()=>{console.log('我是二个订阅者')
})const dep = new Dep() // 实例化一个Dep类
dep.addSub(w1) // 在Dep类中添加一个watcher的实例
dep.addSub(w2) // 在Dep类中添加一个watcher的实例dep.notify() // 发布
- vue的发布订阅模式运作
- 只要我们为vue中data数据重新赋值了,这个赋值的动作,会被vue监听到
- 然后vue要把数据的变化,通知到每个订阅者
- 接下来,订阅者(DOM元素)要根据最新的数据,更新自己的内容
- 谁是订阅者
- 为什么要订阅
- 通俗易懂的说 就是当vue中的数据被重新赋值,变化后的一瞬间,就要调用notify通知每个订阅者(DOM元素)更新dom结构,当订阅者(DOM元素)刚被创建的时候就要添加到存放订阅者信息来
3、手动实现vue类 为vue添加getter setter
class Vue{constructor(options){this.$data = options.data// 调用数据劫持的方法Observe(this.$data)// 属性代理,为当前vue的实例添加属性,不用通过.$data获取到,使用户更加的方便,直接通过this.xxx可以获取到,不用通过this.$data获取到console.log(Object.keys(this.$data))Object.keys(this.$data).forEach(key =>{Object.defineProperty(this, key, {enumerable: true, // 当前属性,允许被循环configurable: true, // 当前属性, 运行被配置 deleteget(){return this.$data[key]},set(newValue){this.$data[key] = newValue}})})}}// 定义一个数据劫持(获取)的方法
function Observe(obj) {// 递归条件终止条件
if(!obj || typeof obj !=='object' ) returnObject.keys(obj).forEach(key =>{ let value = obj[key]// 把value子节点进行递归Observe(value) // 深层的也需要重新改写Object.defineProperty(obj, key, {enumerable: true, // 当前属性,允许被循环configurable: true, // 当前属性, 运行被配置 deleteget(){//我们在这里拦截到了数据console.log("get方法被调用");return value},set(newValue){//改变数据的值,拦截下来额console.log("set方法被调用", newValue);value = newValue// 重新赋值后若为对象也需要添加getter setter属性,需要再重新调用一次ObserveObserve(value)}});})
}const vm = new Vue({el: '#app',data:{name: 'zs',age: 20,info: {a: 'a1',c: 'c1'}}
})
// console.log(vm.$data.name)
vm.$data.info = {d:'d1', e:'e1'}
console.log(vm.$data.info)
4、 对HTML结构进行模板编译的方法
- js性能优化,创建文档碎片,提高DOM操作性能
( createDocumentFragment作用是创建一个文档碎片,把要插入的新节点先附加在它上面,然后再一次性添加到document中)
class Vue{constructor(options){this.$data = options.data// 调用数据劫持的方法Observe(this.$data)// 属性代理,为当前vue的实例添加属性,不用通过.$data获取到,使用户更加的方便,直接通过this.xxx可以获取到,不用通过this.$data获取到Object.keys(this.$data).forEach(key =>{......})// 调用模板编译的函数Complile(options.el, this)}}// 定义一个数据劫持(获取)的方法
function Observe(obj) {....
}// 对HTML结构进行模板编译的方法
function Complile(el, vm) {// 获取el对应的DOM元素vm.$el = document.querySelector(el)// 创建文档碎片,提高DOM操作性能const fragment = document.createDocumentFragmentwhile(chileNode = vm.$el.firstChild){fragment.appendChild(chileNode)}// 进行模板编译replace(fragment)vm.$el.appendChild(fragment)// 负责对DOM模板进行编译的方法function replace(node) {// 定义匹配插值表达式const regMustache =/\{\{\s*(\S+)\s*\}\}\}/// 证明当前的node节点是一个文本子节点,需要进行正则的替换if(node.nodeType === 3){// 注意: 文本子节点,也是一个DOM对象,如果获取文本子节点的字符串内容,需要调用textContent属性获取const text = node.textContent// 进行字符串的正则匹配与获取const exectResult = regMustache.exec(text)if(exectResult) {const value = exectResult[1].split('.').reduce((newObj, k) => newObj[k], vm)node.textContent = text.replace(exectResult, value)}// 终止递归的条件return}// 证明不是文本节点,可能是一个DOM元素,需要进行递归处理node.chileNodes.forEach(child => replace(child))}
}
5、 发布订阅实现
class Vue{constructor(options){this.$data = options.data// 调用数据劫持的方法Observe(this.$data)// 属性代理,为当前vue的实例添加属性,不用通过.$data获取到,使用户更加的方便,直接通过this.xxx可以获取到,不用通过this.$data获取到console.log(Object.keys(this.$data))Object.keys(this.$data).forEach(key =>{Object.defineProperty(this, key, {enumerable: true, // 当前属性,允许被循环configurable: true, // 当前属性, 运行被配置 deleteget(){return this.$data[key]},set(newValue){this.$data[key] = newValue}})})// 调用模板编译的函数Complile(options.el, this)}}// 定义一个数据劫持(获取)的方法
function Observe(obj) { const dep = new Dep()// 递归条件终止条件
if(!obj || typeof obj !=='object' ) returnObject.keys(obj).forEach(key =>{ let value = obj[key]// 把value子节点进行递归Observe(value) // 深层的也需要重新改写Object.defineProperty(obj, key, {enumerable: true, // 当前属性,允许被循环configurable: true, // 当前属性, 运行被配置 deleteget(){//我们在这里拦截到了数据console.log("get方法被调用");// 只要执行了下面这一行,那么刚才new的Watcher实例,就被放到了dep.subs这个数组中了Dep.target && dep.addSub(Dep.target)return value},set(newValue){//改变数据的值,拦截下来额console.log("set方法被调用", newValue);value = newValue// 重新赋值后若为对象也需要添加getter setter属性,需要再重新调用一次ObserveObserve(value)// 通知每一个订阅者更新自己的文本dep.notify()}});})
}// 对HTML结构进行模板编译的方法
function Complile(el, vm) {// 获取el对应的DOM元素vm.$el = document.querySelector(el)// 创建文档碎片,提高DOM操作性能const fragment = document.createDocumentFragmentwhile(chileNode = vm.$el.firstChild){fragment.appendChild(chileNode)}// 进行模板编译replace(fragment)vm.$el.appendChild(fragment)// 负责对DOM模板进行编译的方法function replace(node) {// 定义匹配插值表达式const regMustache =/\{\{\s*(\S+)\s*\}\}\}/// 证明当前的node节点是一个文本子节点,需要进行正则的替换if(node.nodeType === 3){// 注意: 文本子节点,也是一个DOM对象,如果获取文本子节点的字符串内容,需要调用textContent属性获取const text = node.textContent// 进行字符串的正则匹配与获取const exectResult = regMustache.exec(text)if(exectResult) {const value = exectResult[1].split('.').reduce((newObj, k) => newObj[k], vm)node.textContent = text.replace(regMustache, value)// 在此时,创建Watcher类实例new Watcher(vm , exectResult[1], (newValue) =>{node.textContent = text.replace(regMustache, newValue)})}// 终止递归的条件return}// 证明不是文本节点,可能是一个DOM元素,需要进行递归处理node.chileNodes.forEach(child => replace(child))}
}// 实现vue发布订阅// 收集依赖、收集订阅者
class Dep{constructor() {// 这个subs数组,用来存放所有订阅者的信息this.subs = []}// 向subs数组中,添加订阅者的信息addSub(watcher){this.subs.push(watcher)}// 发布通知的方法notify() {this.subs.forEach((watcher) => watcher.update())}
}// 订阅者的类
class Watcher {// cb 回调函数中,记录当前Watcher 如何更新自己的文本内容// 但是,只知道如果更新自己还是不行,必须拿到最新的数据// 因此,还需要在new Watcher期间,把vm也传递过来,因为vm中保存者最新的数据// 除此之外,还需要知道,在vm身上众多的数据中,哪个数据才是自己当前所需要的数据// 因此,必须在new Wather期间指定watcher对应数据的名字// cb : 回调函数// vm: 数据// key : 当前更新的keyconstructor(vm,key,cb) {this.vm = vmthis.key = keythis.cb = cb// 下面三行代码,负责把创建的Watcher实例存到Dep实例的subs数组中Dep.target = this // Dep添加一个属性target指向当前watcher的实例key.split('.').reduce((newObj, k) => newObj[k], vm) // 此处vm 中有取值数据,目的不是为了取值,而是为了触发getterDep.target = null}// 触发回调的方法,能让发布者能够通知进行更新update() {const value = this.key.split('.').reduce((newObj, k) => newObj[k], this.vm)this.cb(value)}
}
vue双向数据绑定原理学习相关推荐
- vue双向数据绑定原理分析--Mr.Ember
vue双向数据绑定原理分析 摘要 vue常用,但原理常常不理解,下面我们来具体分析下vue的双向数据绑定原理. (1)创建一个vue对象,实现一个数据监听器observer,对所有数据对象属性进行监听 ...
- vue双向数据绑定原理 1
1. 原理 1.1 vue双向数据绑定原理,又称vue响应式原理,是vue的核心,双向数据绑定是通过数据劫持结合发布订阅模式的方式来实现的,也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化 ...
- Vue 双向数据绑定原理
原理 1.vue双向数据绑定原理,又称vue响应式原理,是vue的核心,双向数据绑定是通过数据劫持结合发布者订阅者模式的方式来实现的,通过Object.defineProperty()来劫持各个属性的 ...
- 分析Vue双向数据绑定原理以及简单实现MVVM
前言 随着Vue的火热发展,越来越多的程序员并不满足于对框架的使用,更多地追求其内在的原理,就像不能沉沦于美丽的外表,更应该追求灵魂的高度. 正文 好了,废话不多说,接下来我们将通过俩方面开展我们对外 ...
- 前端面试题 HTML5 CSS3(盒子模型、盒子水平垂直居中、经典布局) JS(闭包、深浅克隆、数据劫持和拦截) 算法(排序、去重、数组扁平化) Vue(双向数据绑定原理、通信方式)
前端面试题 HTML5 相关面试题 CSS3 相关面试题 盒子模型 盒子水平垂直居中的方案 经典布局方案 圣杯布局 双飞翼布局 flex布局 定位方式布局 css实现三角形 JS 相关面试题 8种数据 ...
- “别具一格”的vue双向数据绑定原理
背景和一点点看法 见网上许多文章讲vue双向数据绑定一开口就大谈 Object.defineProperty 和 proxy.其实不然.这是vue中响应式的"基石". vue 中有 ...
- 理解VUE双向数据绑定原理和实现
一.原理: 1.vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的, 也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变: 2.核心:关于VUE双向数 ...
- vue双向数据绑定原理
什么是双向数据绑定?Vue是一个MVVM框架,数据绑定简单来说,就是当数据发生变化时,相应的视图会进行更新,当视图更新时,数据也会跟着变化. 实现数据绑定的方式大致有以下几种: - 1.发布者-订阅者 ...
- 面试题 请简述vue双向数据绑定原理
MVVM模式 MVVM模式就是Model–View–ViewModel模式.它实现了View的变动,自动反映在 ViewModel,反之亦然.就是在单向绑定的基础上给可输入元素(input.texta ...
最新文章
- c++自底向上算符优先分析_词法分析程序的自动生成器(二)——Thompson算法
- 动静结合学内核:linux idle进程和init进程浅析
- ant引入html页面,antd引入普通html使用,将ant Design本地化
- pack php 详解_函数pack的使用详解
- 【Android】Android Service 服务
- 在谈PHP中的 抽象类(abstract class)和 接口(interface)
- mpu6050 重力加速度_2021年高考物理一轮复习学与练 重力、弹力 、摩擦力 高中物理知识总结大全...
- java锁机制ppt_总结:Java锁机制
- C++反汇编第一讲,认识构造函数,析构函数,以及成员函数
- python 解析命令行
- python3.5安装包_Python 3.5.4详细图文安装教程(附安装包) | 我爱分享网
- IDEA 代码分析工具
- 计算机专业实习日记,计算机专业实习日记精选
- mysqld.exe 无法找到入口
- wiresshark抓包
- Python 飞机航班案例分析
- 10种人脉裂变技能,6个裂变核心,8个吸粉人性本能怎样玩粉丝裂变?
- 自问自答系列——商城相关
- dllhell 听说过吗?_您已经听说过X25519,但是X448有何特别之处?
- windows phone:WP8中的地图和导航