Vue響應(yīng)式原理及雙向數(shù)據(jù)綁定示例分析
前言
之前公司招人,面試了一些的前端同學(xué),因?yàn)楣臼褂玫那岸思夹g(shù)是Vue,所以免不了問(wèn)到其響應(yīng)式原理和Vue的雙向數(shù)據(jù)綁定。但是這邊面試到的80%的同學(xué)會(huì)把兩者搞混,通常我要是先問(wèn)響應(yīng)式原理再問(wèn)雙向數(shù)據(jù)綁定原理,來(lái)面試的同學(xué)大都會(huì)認(rèn)為是一回事,那么這里我們就說(shuō)一下二者的區(qū)別。
響應(yīng)式原理
是Vue的核心特性之一,數(shù)據(jù)驅(qū)動(dòng)視圖,我們修改數(shù)據(jù)視圖隨之響應(yīng)更新,就很優(yōu)雅~
Vue2.x是借助Object.defineProperty()實(shí)現(xiàn)的,而Vue3.x是借助Proxy實(shí)現(xiàn)的,下面我們先來(lái)看一下2.x的實(shí)現(xiàn)。
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
//攔截get,當(dāng)我們?cè)L問(wèn)data.key時(shí)會(huì)被這個(gè)方法攔截到
get: function getter () {
//我們?cè)谶@里收集依賴
return obj[key];
},
//攔截set,當(dāng)我們?yōu)閐ata.key賦值時(shí)會(huì)被這個(gè)方法攔截到
set: function setter (newVal) {
//當(dāng)數(shù)據(jù)變更時(shí),通知依賴項(xiàng)變更UI
}
})
我們通過(guò)Object.defineProperty為對(duì)象obj添加屬性,可以設(shè)置對(duì)象屬性的getter和setter函數(shù)。之后我們每次通過(guò)點(diǎn)語(yǔ)法獲取屬性都會(huì)執(zhí)行這里的getter函數(shù),在這個(gè)函數(shù)中我們會(huì)把調(diào)用此屬性的依賴收集到一個(gè)集合中 ;而在我們給屬性賦值(修改屬性)時(shí),會(huì)觸發(fā)這里定義的setter函數(shù),在次函數(shù)中會(huì)去通知集合中的依賴更新,做到數(shù)據(jù)變更驅(qū)動(dòng)視圖變更。
3.x的與2.x的核心思想一致,只不過(guò)數(shù)據(jù)的劫持使用Proxy而不是Object.defineProperty,只不過(guò)Proxy相比Object.defineProperty在處理數(shù)組和新增屬性的響應(yīng)式處理上更加方便。
let nObj=new Proxy(obj,{
//攔截get,當(dāng)我們?cè)L問(wèn)nObj.key時(shí)會(huì)被這個(gè)方法攔截到
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey, receiver);
},
//攔截set,當(dāng)我們?yōu)閚Obj.key賦值時(shí)會(huì)被這個(gè)方法攔截到
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
}
})
Proxy的詳細(xì)使用方法參考ES6教程。
Vue的響應(yīng)式原理的實(shí)現(xiàn)細(xì)節(jié)相信大多數(shù)同學(xué)已經(jīng)很熟悉了,這里就不在展開細(xì)談了,如果還想更詳細(xì)的了解,或者想要做一個(gè)簡(jiǎn)易的Vue實(shí)現(xiàn),可以參考這篇Vue原理,相信你會(huì)有不小收獲。
雙向數(shù)據(jù)綁定
雙向數(shù)據(jù)綁定通常是指我們使用的v-model指令的實(shí)現(xiàn),是Vue的一個(gè)特性,也可以說(shuō)是一個(gè)input事件和value的語(yǔ)法糖。 Vue通過(guò)v-model指令為組件添加上input事件處理和value屬性的賦值。
<template> <input v-model='localValue'/> </template>
上述的組件就相當(dāng)于如下代碼
<template>
<!-- 這里添加了input時(shí)間的監(jiān)聽和value的屬性綁定 -->
<input @input='onInput' :value='localValue' />
<span>{{localValue}}</span>
</template>
<script>
export default{
data(){
return {
localValue:'',
}
},
methods:{
onInput(v){
//在input事件的處理函數(shù)中更新value的綁定值
this.localValue=v.target.value;
console.log(this.localValue)
}
}
}
</script>
<template>
<div>
<input @input='onInput' :value='localValue' />
<span>{{localValue}}</span>
</div>
</template>
<script>
// import Vue from 'vue';
export default{
data(){
return {
localValue:'hello',
}
},
methods:{
onInput(v){
this.localValue=v.target.value;
console.log(this.localValue)
}
}
}
</script>
<style>
.count {
color: red;
}
</style>
因此當(dāng)我們修改input輸入框中的值時(shí),我們通過(guò)v-model綁定的值也會(huì)同步修改,基于上述原理,我們可以很容易的實(shí)現(xiàn)一個(gè)數(shù)據(jù)雙向綁定的組件。
v-model實(shí)踐
首先我們定義一個(gè)Vue組件,相信大家已經(jīng)很熟悉了。
<tempalte>
<div class="count" @click="addCount">click me {{value}}</div>
</template>
<script>
export default{
props:{
//關(guān)鍵的第一步:設(shè)置一個(gè)value屬性
value:{
type:Number,
default:0
}
},
watch:{
//監(jiān)聽value變化,更新組件localvalue狀態(tài)
value(v){
this.localvalue=v;
}
},
methods:{
//關(guān)鍵的第二步:事件觸發(fā)localvalue變更,通過(guò)事件同步父組件狀態(tài)變更
addCount(){
this.localvalue++;
this.$emit('input',this.localvalue);
}
},
data(){
return{
//組件狀態(tài),遵守單項(xiàng)數(shù)據(jù)流原則,不直接修改props中的屬性
localvalue:0
}
},
created(){
//初始化獲取value值
this.localvalue=this.value;
}
}
</script>
上面的組件定了我們通過(guò)在props中添加value屬性,并且在值更新時(shí)觸發(fā)input事件。created鉤子和watch中為localvalue賦值是為了同步父組件狀態(tài)到子組件中。
通過(guò)上面??的組件定義,我們就可以在組件上使用v-model指令做雙向數(shù)據(jù)綁定了。
<template>
<add-one v-model="count"></add-one>
<span>父組件{{count}}</span>
</tempalte>
<script>
export default{
data() {
return {
count: 0,
};
},
methods: {
},
created(){
}
}
</script>
下面是實(shí)際效果

import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.esm.browser.js'
Vue.directive('mymodel', {
bind(el, binding, vnode, oldVnode) {
//組件和原生input標(biāo)簽需要分別處理,
el.value = binding.value;
if (vnode.tag == 'input') {
//監(jiān)聽綁定的變量
vnode.context.$watch(binding.expression, (v) => {
el.value = v;
})
//添加inout事件監(jiān)聽
el.addEventListener('input', (e) => {
//context是input所在的父組件,這一步是同步數(shù)據(jù)
vnode.context[binding.expression] = e.target.value;
})
} else { //組件
//vnode的結(jié)構(gòu)可以參見文檔。不過(guò)我覺得最直觀的方法就是直接在控制臺(tái)打印處理
let {
componentInstance,
componentOptions,
context
} = vnode;
const {
_props
} = componentInstance;
//處理model選項(xiàng)
if (!componentOptions.Ctor.extendOptions.model) {
componentOptions.Ctor.extendOptions.model = {
value: 'value',
event: 'input'
}
}
let modelValue = componentOptions.Ctor.extendOptions.model.value;
let modelEvent = componentOptions.Ctor.extendOptions.model.event;
//屬性綁定,這里直接修改了屬性,沒有想到更好的辦法,友好的意見希望可以提出
console.log(binding)
_props[modelValue] = binding.value;
context.$watch(binding.expression, (v) => {
_props[modelValue] = v;
})
//添加事件處理函數(shù),做數(shù)據(jù)同步
componentInstance.$on(modelEvent, (v) => {
context[binding.expression] = v;
})
}
},
inserted() {},
update() {},
componentUpdated() {},
unbind() {},
})
Vue.component('add-one', {
template: '<div class="count" @click="addCount">click me {{localvalue}}</div>',
props: {
value: {
type: Number | String,
default: -1
}
},
//自定義value和事件
// model: {
// value: 'count',
// event: 'change'
// },
watch: {
//監(jiān)聽value變化,更新組件localvalue狀態(tài)
value(v) {
this.localvalue = v;
}
},
methods: {
//事件觸發(fā)localvalue變更,通過(guò)事件同步父組件狀態(tài)變更
addCount() {
this.localvalue++;
this.$emit('input', this.localvalue);
}
},
data() {
return {
localvalue: 0
}
},
created() {
//初始化獲取value值
console.log(this.value)
this.localvalue = this.value;
}
})
new Vue({
el: '#app',
data() {
return {
count: 0,
};
},
methods: {},
created() {
}
})當(dāng)然我們也可以不使用value和input事件這樣的組合,為了更使得組件的定義更加符合語(yǔ)義,我們也可以自定義要實(shí)現(xiàn)雙向綁定的屬性和事件。 我們?cè)诮M件的model選項(xiàng)中設(shè)置value和event即可。如下:
export default{
//這里做了一個(gè)value和event的映射
model:{
value:'count',
event:'change'
},
props:{
//關(guān)鍵的第一步:設(shè)置一個(gè)value屬性
count:{
type:Number,
default:0
}
},
methods:{
//關(guān)鍵的第二步:事件觸發(fā)localvalue變更,通過(guò)事件同步父組件狀態(tài)變更
addCount(){
this.localvalue++;
this.$emit('change',this.localvalue);
}
},
}
通過(guò)上面的組件定義
<add-one v-model="count"></add-one>
就相當(dāng)于
<template>
<add-one @change='onChange' :count='count'></add-one>
<span>{{count}}</span>
</template>
<script>
export default{
data(){
return {
count:0,
}
},
methods:{
onChange(v){
this.count=v;
console.log(this.count)
}
}
}
</script>
只不過(guò)v-model指令幫我們做上面的事件添加,屬性綁定和狀態(tài)同步操作罷了。
以上就是Vue響應(yīng)式原理及雙向數(shù)據(jù)綁定示例分析的詳細(xì)內(nèi)容,更多關(guān)于Vue響應(yīng)式雙向數(shù)據(jù)綁定的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何使用vue開發(fā)公眾號(hào)網(wǎng)頁(yè)
因?yàn)轫?xiàng)目需要,近期做了一個(gè)公眾號(hào)網(wǎng)頁(yè)開發(fā)。在此期間也踩了一些坑,解決這些坑之后,準(zhǔn)備對(duì)這個(gè)項(xiàng)目進(jìn)行復(fù)盤。記錄下項(xiàng)目從開發(fā)到上線所解決的問(wèn)題,并對(duì)使用vue進(jìn)行公眾號(hào)開發(fā)的步驟進(jìn)行一個(gè)總結(jié)。方便以后有問(wèn)題進(jìn)行查閱。希望對(duì)你有所幫助2021-05-05
Vue實(shí)現(xiàn)移除數(shù)組中特定元素的方法小結(jié)
這篇文章主要介紹了Vue如何優(yōu)雅地移除數(shù)組中的特定元素,文中介紹了單個(gè)去除和批量去除的操作方法,并通過(guò)代碼示例講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2024-03-03
vue自定義橫向滾動(dòng)條css導(dǎo)航兩行排列布局實(shí)現(xiàn)示例
這篇文章主要為大家介紹了vue自定義橫向滾動(dòng)條css導(dǎo)航兩行排列布局實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
vue實(shí)現(xiàn)form表單與table表格的數(shù)據(jù)關(guān)聯(lián)功能示例
這篇文章主要介紹了vue實(shí)現(xiàn)form表單與table表格的數(shù)據(jù)關(guān)聯(lián)功能,涉及vue.js表單事件響應(yīng)及頁(yè)面元素屬性動(dòng)態(tài)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-01-01
vue中循環(huán)表格數(shù)據(jù)出現(xiàn)數(shù)據(jù)聯(lián)動(dòng)現(xiàn)象(示例代碼)
在Vue中循環(huán)生成表格數(shù)據(jù)時(shí),可能會(huì)遇到數(shù)據(jù)聯(lián)動(dòng)的現(xiàn)象,即修改一個(gè)表格中的數(shù)據(jù)后,其他表格的數(shù)據(jù)也會(huì)跟著變化,這種現(xiàn)象通常是因?yàn)樗斜砀竦臄?shù)據(jù)引用了同一個(gè)對(duì)象或數(shù)組導(dǎo)致的,本文介紹vue中循環(huán)表格數(shù)據(jù)出現(xiàn)數(shù)據(jù)聯(lián)動(dòng)現(xiàn)象,感興趣的朋友一起看看吧2024-11-11
vue+element-ui:使用el-dialog時(shí)彈框不出現(xiàn)的解決
這篇文章主要介紹了vue+element-ui:使用el-dialog時(shí)彈框不出現(xiàn)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10

