Vue2.x响应式原理
一、回顾Vue响应式用法
vue响应式,我们都很熟悉了。当我们修改vue中data对象中的属性时,页面中引用该属性的地方就会发生相应的改变。避免了我们再去操作dom,进行数据绑定。
二、Vue响应式实现分析
对于vue的响应式原理,官网上给了出文字描述 https://cn.vuejs.org/v2/guide/reactivity.html 。
vue内部主要是通过数据劫持和观察者模式实现的
数据劫持:
vue2.x内部使用Object.defineProperty https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
vue3.x内部使用的Proxy https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
观察者模式:https://juejin.cn/post/6995865134132363295
内部成员示意图
各个成员的功能
Vue:
- 把data中的成员注入到Vue实例中,并把data中的成员转换为getter和setter
Observer:
- 对data对象中的简单类型数据及对象进行监听,当数据发生变化时通知Dep
Compiler:
- 解析每个元素中的指令/差值表达式,并替换成相应的数据
Dep:
- 观察者模式中的通知者,添加观察者,当数据变化时通知观察者
Watcher:
- 每个引用data中的属性的地方都有一个watcher对象,负责更新视图
附:data对象中的属性充当被观察者,引用data对象中属性的地方充当观察者
三、Vue响应式源码实现
Vue对象实现
功能
- 负责接受初始化的参数
- 把data中的属性注入到data实例,转换成getter和setter
- 调用Observer监听data中所有属性的变化
- 调用compiler解析指令、差值表达式.
class Vue{
constructor(options){
// 1、通过属性保存穿进来的属性
this.$options= options||{};
this.$data= options.data||{};
this.$el = typeof options.el ==='string' ? document.querySelector(options.el) : options.el;
// 2、把data参数中的数据转换为getter和setter 挂载到Vue实例上
this._proxyData(this.$data)
// 3、调用observe对象监视data数据的变化
new Observer(this.$data)
// 4、调用compiler对象渲染页面
new Compiler(this)
}
_proxyData(data){
if (data&&Object.keys(data).length>0){
for (const key in data) {
Object.defineProperty(this,key,{
configurable:true,
enumerable:true,
get(){
return data[key]
},
set(value){
if (data[key]===value) {
return;
}
data[key]=value;
}
})
}
}
}
}
Observer对象实现
功能
- 把data选项中的属性进行数据劫持
- data中的某个属性也是对象的话,进行递归转换成响应式对象
- 数据变化发送通知
//数据劫持
class Observer {
constructor(data) {
this.walk(data)
}
walk(data) {
//1、判断data是否是对象
if (!data || typeof data !== 'object') {
return
}
//2、循环调用defineReactive进行数据劫持
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
defineReactive(obj, key, val) {
//创建通知者
const dep = new Dep()
//使用walk把引用对象中的属性变成响应式的
this.walk(val)
const that=this;
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get() {
//通知者收集观察者
Dep.target && dep.addSub(Dep.target)
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal;
that.walk(newVal)
//被观察者发生变化的时候,通知者对象给每个观察者发送通知
dep.notify()
}
})
}
}
Compile对象实现
功能
- 负责编译模板,解析指令、差值表达式
- 负责页面首次渲染
- 当数据发生改变后,负责重新渲染视图
//编译器
class Compiler {
constructor(vm) {
this.el = vm.$el;
this.vm = vm;
this.compile(this.el)
}
//编译模板 判断节点是文本节点还是元素节点
compile(el) {
let childNodes = el.childNodes;
//处理第一层子节点
Array.from(childNodes).forEach(node => {
if (this.isTextNode(node)) {
this.compileText(node)
} else if (this.isElementNode(node)) {
this.compileElement(node)
}
//如果当前节点还有子节点 递归调用编译指令
if (node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
//编译元素节点,处理指令
compileElement(node) {
//遍历所有的指令
Array.from(node.attributes).forEach(attr => {
//判断是不是指令节点
if (this.isDirective(attr.name)) {
const nodeName = attr.name;
const key = attr.nodeValue;
const directive = nodeName.substr(2)
this.updater(directive,node,key)
}
})
}
updater(directive,node,key){
const updaterFn = this[directive+"Updater"]
updaterFn && updaterFn.call(this,node,this.vm[key],key)
}
//v-text
textUpdater(node,value,key){
node.textContent=value
//使用v-text表达式的地方就是一个观察者
new Watcher(this.vm,key,newValue => {
node.textContent = newValue
})
}
//v-model
modelUpdater(node,value,key){
node.value =value
//使用v-model表达式的地方就是一个观察者
new Watcher(this.vm,key,newValue => {
node.value = newValue
})
node.addEventListener('input',()=>{
this.vm[key] = node.value
})
}
//v-html
htmlUpdater(node,value,key){
node.innerHTML = value
//使用v-html表达式的地方就是一个观察者
new Watcher(this.vm,key,newValue => {
node.innerHTML = newValue
})
}
//处理差值表达式
compileText(node) {
//匹配差值表达式的正则
let reg = /\{\{(.+?)\}\}/
//用正则匹配node的textContent,如果匹配到了 就替换
if (reg.test(node.textContent)) {
//获取插值表达式的key
let key = RegExp.$1;
let value = node.textContent;
node.textContent = value.replace(reg, this.vm[key])
//使用差值表达式的地方就是一个观察者
new Watcher(this.vm,key,newValue => {
node.textContent = newValue
})
}
}
//是否是指令
isDirective(attrName) {
return attrName.startsWith('v-')
}
//是否是文本节点
isTextNode(node) {
return node.nodeType === 3
}
//是否是元素
isElementNode(node) {
return node.nodeType === 1
}
}
Dep对象实现
功能
- 收集依赖,添加观察者
- 通知所有观察者
//通知者类
class Dep {
constructor() {
//存储观察者
this.subs = []
}
/**
* 收集观察者
*/
addSub(sub) {
if (sub && sub.update) {
this.subs.push(sub)
}
}
/**
* 通知观察者改变状态
*/
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
Watcher对象实现
功能
- 当数据变化时,Dep通知所有Watcher实例更新视图
- 自身实例化的时候往Dep对象中添加自己
//观察者类
class Watcher {
constructor (vm,key,cb) {
//Vue实例
this.vm =vm;
// data中的key对象
this.key =key;
// 更新视图的回调函数
this.cb = cb
//把当前观察者实例存放在Dep的target静态属性中
Dep.target =this
//触发Observe的getter方法,把当前实例存放在Dep.subs中
//data中key对应的旧值
this.oldValue = this.vm[this.key]
Dep.target = null
}
//每个观察者都有一个update方法来改变状态
update(){
const newValue = this.vm[this.key]
if ( this.newValue === this.oldValue ) {
return
}
this.cb(newValue)
}
}
测试
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>index</title>
<script src="./js/dep.js"></script>
<script src="./js/watcher.js"></script>
<script src="./js/compiler.js"></script>
<script src="./js/observer.js"></script>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="app">
<h1>差值表达式</h1>
<h3>{{msg}}</h3>
<h3>{{count}}</h3>
<h1>v-text</h1>
<div v-text='msg'></div>
<h1>v-model</h1>
<input type="text" v-model="msg" attr="msg">
<input type="text" v-model="count">
<h1>v-html</h1>
<div v-html="htmlText"></div>
</div>
<script>
let vm = new Vue({
el:"#app",
data:{
msg:'信息',
count:'数量',
person:{name:'张三'},
htmlText:"<div style='color:red'>你好</div>"
}
})
</script>
</body>
Vue2.x响应式原理的更多相关文章
- Vue2.0响应式原理以及重写数组方法
// 重写数组方法 let oldArrayPrototype = Array.prototype; let proto = Object.create(oldArrayPrototype); ['p ...
- vue2.0与3.0响应式原理机制
vue2.0响应式原理 - defineProperty 这个原理老生常谈了,就是拦截对象,给对象的属性增加set 和 get方法,因为核心是defineProperty所以还需要对数组的方法进行拦截 ...
- 【Vue源码学习】响应式原理探秘
最近准备开启Vue的源码学习,并且每一个Vue的重要知识点都会记录下来.我们知道Vue的核心理念是数据驱动视图,所有操作都只需要在数据层做处理,不必关心视图层的操作.这里先来学习Vue的响应式原理,V ...
- [切图仔救赎]炒冷饭--在线手撸vue2响应式原理
--图片来源vue2.6正式版本(代号:超时空要塞)发布时,尤雨溪推送配图. 前言 其实这个冷饭我并不想炒,毕竟vue3马上都要出来.我还在这里炒冷饭,那明显就是搞事情. 起因: 作为切图仔搬砖汪,长 ...
- 由浅入深,带你用JavaScript实现响应式原理(Vue2、Vue3响应式原理)
由浅入深,带你用JavaScript实现响应式原理 前言 为什么前端框架Vue能够做到响应式?当依赖数据发生变化时,会对页面进行自动更新,其原理还是在于对响应式数据的获取和设置进行了监听,一旦监听到数 ...
- Vue2响应式原理
vue2响应式原理 vue的特性:数据驱动视图和双向数据绑定.vue官方文档也提供了响应式原理的解释: 深入响应式原理 Object.defineProperty() Object.definePro ...
- Vue2 响应式原理
我们经常用vue的双向绑定,改变data的某个属性值,vue就马上帮我们自动更新视图,下面我们看看原理. Object的响应式原理: 可以看到,其实核心就是把object的所有属性都加上getter. ...
- Vue源码--解读vue响应式原理
原文链接:https://geniuspeng.github.io/2018/01/05/vue-reactivity/ Vue的官方说明里有深入响应式原理这一节.在此官方也提到过: 当你把一个普通的 ...
- Vue响应式原理的实现-面试必问
Vue2的数据响应式原理 1.什么是defineProperty? defineProperty是设置对象属性,利用属性里的set和get实现了响应式双向绑定: 语法:Object.definePro ...
随机推荐
- 13、linux中用户和用户组
linux是多用户多进程的系统: 每个文件和进程都需要应对一个用户和用户组: linux系统通过uid和gid来识别用户和组的: 一个用户必须要有唯一的uid和一个主组来识别身份,不同的用户可以使用同 ...
- 个人使用uploadify插件遇到的一些问题
当uploadify上传插件遇到的好几个问题 现在开始自我反省,留下脚印希望能够帮助其他遇到同样问题的朋友. 我遇到的第一个是, 在firefox不能执行uploadify事件onUploadSucc ...
- 《手把手教你》系列技巧篇(七)-java+ selenium自动化测试-宏哥带你全方位吊打Chrome启动过程(详细教程)
1.简介 经过前边几篇文章和宏哥一起的学习,想必你已经知道了如何去查看Selenium相关接口或者方法.一般来说我们绝大多数看到的是已经封装好的接口,在查看接口源码的时候,你可以看到这个接口上边的注释 ...
- 【面试】详解同步/异步/阻塞/非阻塞/IO含义与案例
本文详解同步.异步.阻塞.非阻塞,以及IO与这四者的关联,毕竟我当初刚认识这几个名词的时候也是一脸懵. 目录 1.同步阻塞.同步非阻塞.异步阻塞.异步非阻塞 1.同步 2.异步 3.阻塞 4.非阻塞 ...
- Kafka:docker安装Kafka消息队列
安装之前先看下图 Kafka基础架构及术语 Kafka基本组成 Kafka cluster: Kafka消息队列(存储消息的队列组件) Zookeeper: 注册中心(kafka集群依赖zookee ...
- java_线程创建的三种方式及区别
java中关于线程的创建有三种: (1)通过继承Thread类创建线程. (2)通过实现Runnable接口创建线程. (3)通过Callable 和 Future 接口创建线程. * * * * * ...
- Ubuntu创建图标
起因 安装一些软件时,总是没有图标,导致无法固定到docky栏,所以极为不方便,所以需要自己创建图标. 操作 以创建微信图标为例 [Desktop Entry] Name=Wecaht #名字 Com ...
- WPF技巧:通过代码片段管理器编写自己常用的代码模板提示效率
在写自定义控件的时候,有一部分功能是当内部的值发生变化时,需要通知控件的使用者,而当我在写依赖项属性的时候,我可以通过popdp对应的代码模板来完成对应的代码,但是当我来写属性更改回调的时候,却发现没 ...
- Vue3 + Cesium + Typescript 集成搭建的快速启动模板(包含示例数据)
开门见山 项目地址:https://github.com/tanghaojie/vue3-cesium-typescript-start-up-template 好用的话给个star呗,有更新可以第一 ...
- android实现计时器(转)
新建布局文件activity_main.xml <?xml version="1.0" encoding="utf-8"?> <Linea ...