1,数据响应式

当数据发生改变的时候,我们立即知道数据发生改变,并做出相关的操作:发送请求,打印文字,操作DOM等。

1.1,vue实现数据响应的原理

vue中使用了两种模式来实现数据响应式,分别是vue2(Object.defineProperty ()),vue3(Proxy)。

1.2,vue2实现数据响应:对象属性拦截Object.defineProperty ()  

首先我们先要知道如何多一个对象进行赋值:两种方式,字面量赋值和Object.defineProperty ()

A:字面量赋值

我们定义一个数据的时候,通过字面值的数据进行定义:

<script>
let obj={
name:"小智"
}
obj.name="小红"
let app=document.getElementById('app')
console.log(obj);
app.innerText=obj.name
</script>

这时我们在控制台上看到好像是可以知道obj属性的变化的,我们再在控制台上打印:

obj的name属性是小红,我们再修改name属性值。

B:Object.defineProperty ()  方法拦截对象属性

JavaScript提供了Object.defineProperty ()  方法拦截对象属性。

let obj={}
let _name="小红"
Object.defineProperty(obj,'name',{
// 当访问对象的属性的时候,get方法被调用
get(value){
return _name
},
// 当修改对象属性的时候被调用
set(newValue){
_name=newValue
}
})

_name属性是一个中间变量,用于解决get/set联动问题。

 c:强化属性劫持方案

一般情况下,我们需要将一个个变量都放到一个对象中,这时候实现Object.defineProperty ()赋值的时候,中间变量会变得有点多,这时候我们需要对其进行优化处理:

  let data={
name:"小智",
age:12,
sex:"男" }
Object.keys(data).forEach(key=>{
Observer(data,key,data[key])
console.log(key+data[key]);
})
function Observer(obj,key,value){
Object.defineProperty(obj,key,{
get(){
return value
},
set(Newvalue){
value=Newvalue
}
})
}

总结:响应式是指拦截数据的访问和设置,并对其做一些操作。

2,数据响应式反应到视图

我们已经可以实现数据响应式,拦截在数据设置和访问的状态了,例如:

let data={
name:"小吴",
age:16
}
Object.keys(data).forEach(key=>{
webserver(data,key,data[key])
})
function webserver(obj,key,value){
Object.defineProperty(obj,key,{
get(){
return value+'vip'
},
set(newValue){
value=newValue+'vip'
}
})
}

我们对对象属性设置和访问都进行拦截,并假设“vip”字符串,在控制台打印一下会发现:

我们只进行初始化没有进行任何后续的赋值都会在name和age后字段加‘vip’字符串,这个就是属性响应式的原理。

问题:这里发现一个有趣的问题,我本来就喜欢将功能写在一起,但是这里就导致了一个问题,问题代码如下:

let data={
name:"小吴",
age:16
}
Object.keys(data).forEach(key=>{
Object.defineProperty(data,key,{
get(){
return data[key]
},
set(newValue){
data[key]=newValue
}
})
})

在控制台打印。属性发生以下错误:

开始不知道为什么,后来才知道,这是作用域的问题,响应式是一个异步的过程,而forEach是一个同步的过程,在循环结束之后,才执行Object.defineProperty内的代码,解决方法有两个,一个是定义外部状态,形成闭包,代码如下:

  let data={
name:"小吴",
age:16
}
Object.keys(data).forEach(key=>{
// webserver(data,key,data[key])
let obj=data
let item=key
let value=data[key]
Object.defineProperty(obj,item,{
get(){
return value
},
set(newValue){
value=newValue
}
})
})

另一种就是封装到一个方法里面,我们知道,方法的形参是可以提升的,以上封装方法的方式提示相当一下代码:

let data={
name:"小吴",
age:16
}
Object.keys(data).forEach(key=>{
webserver(data,key,data[key])
})
function webserver(obj,key,value){
let obj
let key
let value
Object.defineProperty(obj,key,{
get(){
return value+'vip'
},
set(newValue){
value=newValue+'vip'
}
})
}

这样在Object.defineProperty内部形成一个闭包。

2.1.数据反映到视图层---编程式响应

之前我们已经实现类数据响应式,那如何将数据响应到视图呢?说过一句话,响应式是用来拦截数据的设置和读取的,也就是说,在设置之前(set方法里面),我们是可以拿到数据的,只要将拿到的数据添加到DOM节点上就可以了:

let data={
name:"小吴",
age:16
}
let app=document.getElementById('app')
app.innerText=data.name
Object.keys(data).forEach(key=>{
webserver(data,key,data[key])
})
function webserver(obj,key,value){
Object.defineProperty(obj,key,{
get(){
return value
},
set(newValue){
if(newValue===value){//如果设置的值和原来的值相等,不执行赋值代码
return
}
app.innerText=newValue
value=newValue
}
})
}

这个是单数据响应,多数据响应处理起来还是比较麻烦,处理思路为:(等有空再说,会补坑的)

A:双向数据绑定

思路:给input绑定一个内容发生变化就会触发的一个函数,通过该函数改变变量实现双向数据绑定。

<body>
<div id="app"></div>
<!-- <div id="app1"></div> -->
<input type="text" oninput="change()" id="text">
<script>
let text=document.getElementById('text')
let app=document.getElementById('app')
let data="小美"
app.innerText=data
text.value=data
function change(data){
data=text.value
app.innerText=data
} </script>
</body>

2.2.数据反映到视图层(数据双向绑定)

将数据响应到视图层的方式,在vue2使用的是Object.defineProperty()来实现,需要劫持到数据的变化,在数据变化的时候将其渲染到视图上:

第一步:数据层到视图层的响应

A,首先需要遍历data的数据,获取到每个data的属性

B,其次,获取到data的每个属性之后,对属性进行拦截。

C,在拦截时,需要将其值反映到对应的视图标签上。

第C步中又可以分为:

        a,获取到视图中每个节点的DOM对象, 

        b,遍历DOM对象,获取到每个DOM对象对应的node对象(节点对象)。

        c,遍历node对象,获取到node对象中含有v-model属性的节点

        d,将对应的data的属性值赋值给node节点的value值上

代码实现为:

<body>
<div id="app">
名字:<input type="text" v-model="name" value="12"><br>
年龄:<input type="text" v-model="age" value="2323">
<div class="sjkx">
<h1 class="name">模拟v-model</h1>
</div>
</div>
<script>
let data = {
name: '',
age: '',
}
let view = document.getElementById('app')
// 数据反映到视图方法
function getVmodelNode(view, data) {
// a,获取到视图中每个节点的DOM对象,
let allDom = view.getElementsByTagName("*")
let allDomArray = Array.from(allDom)//将伪数组转化成数组
let attributeArray = []
// b,遍历DOM对象,获取到每个DOM对象对应的node对象。
allDomArray.forEach(nodeItem => {
//c, 遍历node对象,过滤获取到node对象中含有v-model的节点
Array.from(nodeItem.attributes).forEach(item => {
if (item.nodeName === 'v-model') {
//d,将对应的data的属性值赋值给node节点的value值上
nodeItem.value = data[item.nodeValue]
}
})
})
}
// A,首先需要遍历data的数据,获取到每个data的属性
Object.keys(data).forEach(key => {
// B,其次,获取到data的每个属性之后,对属性进行拦截。
debugger
WebServer(data,key,data[key])
})
function WebServer(data, key, value) {
Object.defineProperty(data, key, {
get() {
return value
},
set(newValue) {
value= newValue
// C,在拦截时,需要将其值反映到对应的视图标签上。
getVmodelNode(view,data)
}
})
}
</script>
</body>

第二步:视图层到数据层的响应。

视图层响应到数据层的思路:当输入框的数据发生变化的时候,调用函数,将输入框的值赋值给data对应的属性上。

实际上就是在第一步的第C步多加了一步:为对应的node对象绑定一个input事件,并将node的value赋值给data对应的属性上。

<body>
<div id="app">
名字:<input type="text" v-model="name" value="12"><br>
年龄:<input type="text" v-model="age" value="2323">
<div class="sjkx">
<h1 class="name">模拟v-model</h1>
</div>
</div>
<script>
let data = {
name: '',
age: '',
}
let view = document.getElementById('app')
// 数据反映到视图方法
function getVmodelNode(view, data) {
// a,获取到视图中每个节点的DOM对象,
let allDom = view.getElementsByTagName("*")
let allDomArray = Array.from(allDom)//将伪数组转化成数组
let attributeArray = []
// b,遍历DOM对象,获取到每个DOM对象对应的node对象。
allDomArray.forEach(nodeItem => {
//c, 遍历node对象,过滤获取到node对象中含有v-model的节点
Array.from(nodeItem.attributes).forEach(item => {
if (item.nodeName === 'v-model') {
//d,将对应的data的属性值赋值给node节点的value值上
nodeItem.value = data[item.nodeValue]
//视图层反映到数据层上,
nodeItem.addEventListener('input',(e)=>{
data[item.nodeValue]=e.target.value
})
} }) })
}
// A,首先需要遍历data的数据,获取到每个data的属性
Object.keys(data).forEach(key => {
// B,其次,获取到data的每个属性之后,对属性进行拦截。 WebServer(data,key,data[key])
})
function WebServer(data, key, value) {
Object.defineProperty(data, key, {
get() {
return value
},
set(newValue) {
value= newValue
// C,在拦截时,需要将其值反映到对应的视图标签上。
getVmodelNode(view,data)
}
})
}
//视图响应到数据层
getVmodelNode(view, data)
</script>
</body>

这就是完整的模拟v-model的代码了。我们还可以通过这个代码实现v-text,只需要加个判断就可以了

<body>
<div id="app">
名字:<input type="text" v-model="name" value="12"><br>
年龄:<input type="text" v-model="age" value="2323">
<div class="sjkx">
<h1 class="name" >模拟v-model</h1>
<p v-text="text"> </p>
</div>
</div>
<script>
let data = {
name: '',
age: '',
text:'hHHHHHHH'
}
let view = document.getElementById('app')
// 数据反映到视图方法
function getVmodelNode(view, data) {
// a,获取到视图中每个节点的DOM对象,
let allDom = view.getElementsByTagName("*")
let allDomArray = Array.from(allDom)//将伪数组转化成数组
let attributeArray = []
// b,遍历DOM对象,获取到每个DOM对象对应的node对象。
allDomArray.forEach(nodeItem => {
//c, 遍历node对象,过滤获取到node对象中含有v-model的节点
Array.from(nodeItem.attributes).forEach(item => {
// 实现v-text核心代码
if (item.nodeName === 'v-text') {
console.log("haha",data[item.nodeValue]);
//d,将对应的data的属性值赋值给node节点的value值上
nodeItem.innerText = data[item.nodeValue]
}
//实现v-model核心代码
console.log(item.nodeName);
if (item.nodeName === 'v-model') {
//d,将对应的data的属性值赋值给node节点的value值上
nodeItem.value = data[item.nodeValue]
nodeItem.addEventListener('input',(e)=>{
let nodeValue=e.target.value
data[item.nodeValue]=nodeValue
})
} }) })
}
// A,首先需要遍历data的数据,获取到每个data的属性
Object.keys(data).forEach(key => {
// B,其次,获取到data的每个属性之后,对属性进行拦截。
WebServer(data,key,data[key])
})
function WebServer(data, key, value) {
Object.defineProperty(data, key, {
get() {
return value
},
set(newValue) {
console.log("被调用了");
value= newValue
// C,在拦截时,需要将其值反映到对应的视图标签上。
getVmodelNode(view,data)
}
})
}
//视图响应到数据层
getVmodelNode(view, data)
</script>
</body>

现存问题:这里现存一个问题,就是在执行v-model核心代码的时候,我们只是输入一个值,但是v-model的核心代码块执行了两次,这不是我们想要的,而是应该我只输入一个值得时候,另一个值得v-model代码块不应该被执行

这时候需要引入一个优化点——精确更新

因为我们在执行的时候,执行到

 if (item.nodeName === 'v-model') {
//d,将对应的data的属性值赋值给node节点的value值上
nodeItem.value = data[item.nodeValue]
nodeItem.addEventListener('input',(e)=>{
let nodeValue=e.target.value
data[item.nodeValue]=nodeValue
})
}

这个代码块的时候,我们都需要操作nodeItemitem.nodeValue  nodeItem  是我们需要操作的DOM节点,item.nodeValue  是我们需要赋值的属性值,每次都操作同一个的时候,每次执行都会被创建,这会导致资源过多被使用。

因此,优化的思路为:  将nodeItemitem.nodeValue       存储到浏览器内存内,每次调用就从浏览器内存取出而不是创建。

存贮到浏览器内存中的方式:闭包 ,代码如下

()=>{nodeItem.value = data[item.nodeValue]}

上述还有一个问题,由于我们响应式数据绑定到的dom节点可能有多个,所以node 节点可能存在多个一旦响应式属性(name) 发生变化与name属性相关的所有的dom节点都需要进行一轮更新, 所以属性和更新函数之间是一个对多的关系

{
nodeValue:[
()=>{nodeItem1.value = data[item.nodeValue]},//name标签的
()=>{nodeItem1.value = data[item.nodeValue]},//age标签的
......
]
}

而要实现精确更新的这个过程,也被称之为订阅发布者模式

3,订阅发布者模式

我们先要知道定义发布者模式,在浏览器中,我们不能为同一个标签绑定相同的事件名称,此时我们可以使用addEventListener为多个DOM节点绑定相同的事件名,这种模式就是订阅发布者模式,

我们可以自定义一个定义发布者模式事件:

第一步:定义一个map,key为事件名,value为事件回调函数集合

let map = {}

第二步:收集事件名和回调函数存贮到map中

function collect(eventName, fn) {
if (!map[eventName]) {
map[eventName] = []
}
map[eventName].push(fn)
}

第三步,执行回调函数

function trigger(eventName) {
map[eventName].forEach(fn => fn())
}

为了代码美观,我们把全部代码放到一个对象内。

 let addDeep = {
map: {},
collect: function (eventName, fn) {
if (!this.map[eventName]) {
this.map[eventName] = []
}
this.map[eventName].push(fn)
},
trigger:function (eventName) {
this.map[eventName].forEach(fn => fn())
}
}

测试代码:

        addDeep.collect('lick', () => {
console.log("加一");
})
addDeep.collect('lick', () => {
console.log("加二");
})
addDeep.trigger('lick')

此时一个简单的订阅发布者模式就做好了。

3.1.使用订阅发布者模式实现精确更新

思路:我们在实现v-model和v-text的时候,更新的代码分别为 data[item.nodeValue]=nodeValue和nodeItem.innerText = data[item.nodeValue],我们需要更新的是data的属性值,因此我们需要收集到相关属性,之前我们说需要把nodeItemitem.nodeValue   形成一个闭包,保存到内存中。因此回调函数的代码就是

nodeItem.value = data[item.nodeValue]

也就是将更新操作放到回调函数中。

在核心代码块处:收集相关数据

 if (item.nodeName === 'v-model') {
// 收集数据
addDeep.collect(item.nodeValue,()=>{
nodeItem.value = data[item.nodeValue]
})
nodeItem.addEventListener('input',e=>{
let dataValue=e.target.value
data[item.nodeValue]=dataValue
console.log("11",data);
})
}

实现精确更新

 set(newValue) {
value= newValue
// 精确更新
addDeep.trigger(key)
}

完整代码(v-model)

<body>
<div id="app">
名字:<input type="text" v-model="name" value="12"><br>
年龄:<input type="text" v-model="age" value="2323">
<div class="sjkx">
<h1 class="name">模拟v-model</h1>
</div>
</div>
<script>
let data = {
name: '',
age: '',
}
let addDeep = {
map: {},
collect: function (eventName, fn) {
if (!this.map[eventName]) {
this.map[eventName] = []
}
this.map[eventName].push(fn)
},
trigger:function (eventName) {
this.map[eventName].forEach(fn => fn())
}
}
let view = document.getElementById('app')
// 数据反映到视图方法
function getVmodelNode(view, data) {
// a,获取到视图中每个节点的DOM对象,
let allDom = view.getElementsByTagName("*")
let allDomArray = Array.from(allDom)//将伪数组转化成数组
let attributeArray = []
// b,遍历DOM对象,获取到每个DOM对象对应的node对象。
allDomArray.forEach(nodeItem => {
//c, 遍历node对象,过滤获取到node对象中含有v-model的节点
Array.from(nodeItem.attributes).forEach(item => {
if (item.nodeName === 'v-model') {
//d,将对应的data的属性值赋值给node节点的value值上
// nodeItem.value = data[item.nodeValue]
console.log("22",data);
addDeep.collect(item.nodeValue,()=>{
nodeItem.value = data[item.nodeValue]
})
//视图层反映到数据层上,
nodeItem.addEventListener('input',e=>{
let dataValue=e.target.value
data[item.nodeValue]=dataValue
console.log("11",data);
})
} }) })
}
// console.log(addDeep.map);
// A,首先需要遍历data的数据,获取到每个data的属性
Object.keys(data).forEach(key => {
// B,其次,获取到data的每个属性之后,对属性进行拦截。
WebServer(data,key,data[key])
})
function WebServer(data, key, value) {
Object.defineProperty(data, key, {
get() {
return value
},
set(newValue) {
value= newValue
// C,在拦截时,需要将其值反映到对应的视图标签上。
// getVmodelNode(view,data)
addDeep.trigger(key)
}
})
}
getVmodelNode(view, data)
// console.log(data);
</script>
</body>

在v-model代码核心处打印:

                     if (item.nodeName === 'v-model') {
//d,将对应的data的属性值赋值给node节点的value值上
// nodeItem.value = data[item.nodeValue]
console.log("22",data);
addDeep.collect(item.nodeValue,()=>{
nodeItem.value = data[item.nodeValue]
})
//视图层反映到数据层上,
nodeItem.addEventListener('input',e=>{
let dataValue=e.target.value
data[item.nodeValue]=dataValue
console.log("11",data);
})
}

初始化时:调用两次

输入新值:

值调用一次更新函数。这就实现了优化比较好的v-model源码了。

结束了,看都看到这了,点赞收藏吧~~~~~算我球球你啦!!!

vue进阶一~数据响应式,数据响应到视图层,手写v-model,订阅发布者模式,的更多相关文章

  1. angular,vue,react的基本语法—插值表达式,渲染数据,响应式数据

    基本语法: 1.插值表达式: vue:{{}} react:{} angular:{{}} 2.渲染数据 vue js: export default{ data(){ return{ msg:&qu ...

  2. Vue实现双向绑定的原理以及响应式数据

    一.vue中的响应式属性 Vue中的数据实现响应式绑定 1.对象实现响应式: 是在初始化的时候利用definePrototype的定义set和get过滤器,在进行组件模板编译时实现water的监听搜集 ...

  3. vue源码之响应式数据

    分析vue是如何实现数据响应的. 前记 现在回顾一下看数据响应的原因. 之前看了vuex和vue-i18n的源码, 他们都有自己内部的vm, 也就是vue实例. 使用的都是vue的响应式数据特性及$w ...

  4. vue响应式数据变化

    vue响应式数据变化 话不多说,先上代码: //拷贝一份数组原型,防止修改所有数组类型变量的原型方法 let arrayProto = Array.prototype;// 数组原型上的方法 let ...

  5. vue基础响应式数据

    1.vue 采用 v……vm……m,模式,v---->el,vm---->new Vue(实例),m---->data 数据,让前端从操作大量的dom元素中解放出来. 2.vue响应 ...

  6. 仿VUE创建响应式数据

    VUE对于前端开发人员都非常熟悉了,其工作原理估计也都能说的清个大概,具体代码的实现估计看的人不会太多,这里对vue响应式数据做个简单的实现. 先简单介绍一下VUE数据响应原理,VUE响应数据分为对象 ...

  7. Vue2手写源码---响应式数据的变化

    响应式数据变化 数据发生变化后,我们可以监听到这个数据的变化 (每一步后面的括号是表示在那个模块进行的操作) 手写简单的响应式数据的实现(对象属性劫持.深度属性劫持.数组函数劫持).模板转成 ast ...

  8. Vue 进阶系列(一)之响应式原理及实现

    Vue 进阶系列(一)之响应式原理及实现:https://juejin.im/post/5bce6a26e51d4579e9711f1d Vue 进阶系列(二)之插件原理及实现:https://jue ...

  9. 【Vue原理模拟】模拟Vue实现响应式数据

    1. 预期效果 当数据变动时,触发自定义的回调函数. 2. 思路 对对象 object 的 setter 进行设置,使 setter 在赋值之后执行回调函数 callback(). 3.细节 3.1 ...

  10. Vue 响应式数据说明

    值得注意的是只有当实例被创建时 data 中存在的属性才是响应式的.也就是说如果你添加一个新的属性,比如: vm.b = 'hi' 那么对 b 的改动将不会触发任何视图的更新. 这里唯一的例外是使用  ...

随机推荐

  1. mysql,左连接 ,查询右表为null的写法,删除,带join条件的写法

    select * from sale_guest sg left join sale_bill sbon sg.bill_id=sb.id where sg.gmt_create>'2023-1 ...

  2. golang nsq 同一个topic有多个channel,同时项目又互相引用时出现的问题

    p.p1 { margin: 0; font: 12px ".PingFang SC" } span.s1 { font: 12px "Helvetica Neue&qu ...

  3. .NET Core 项目Linux环境下生成二维码

    问题: 公司系统开发中,需要对企微授权链接进行二维码生成,然后向客户提供:当然,首当其冲想到的是使用ZXing.NET库进行实现,毕竟生成简单二维码也就那几句代码:然而,在本地环境中,一切都很正常,但 ...

  4. Jenkins自动化集成

    gitlab连接Jenkins 创建token后 , 现在的网页上就会出现一个token: token只出现一次,注意保存 将这个token在Jenkins上配置,现在开始配置Jenkins Jenk ...

  5. ReplayKit2:声音回调时间戳问题

    一.ReplayKit2 框架回调中 视频.micphone声音.系统声音三路回调 - (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffe ...

  6. Python 潮流周刊#54:ChatTTS 强大的文本生成语音模型

    本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...

  7. HDFS 常用操作命令

    HDFS 文件操作命令 注,其实常用命令不用网上搜,和linux下的命令很类似,触类旁通,直接在linux 上 hadoop fs 看一下就行了,不需要刻意去记 我把 linux 上的 help 列举 ...

  8. 如何解决系统报错:nf_conntrack: table full, dropping packets

    问题 在系统日志中(/var/log/messages),有时会看到大面积的下面的报错: nf_conntrack: table full, dropping packet 这说明系统接到了大量的连接 ...

  9. star 最多的 Go 语言本地化库|GitHub 2.8K

    如果你是一位 Go 用户,可以在我开源的学习仓库中,找到针对各种往期归档文章,及学习资料. B站:白泽talk,公众号[白泽talk],回复"电子书",即可获得包含<100个 ...

  10. 父类和子类对象的获取值的方式验证,通过父类属性的方式获取不到值,需要使用get方法

    父类和子类对象的获取值的方式验证,通过父类属性的方式获取不到值,需要使用get方法 静态属性通过类.属性的方式获取,对象获取使用get方法获取 package com.example.core.myd ...