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è)谶@里收集依賴(lài) return obj[key]; }, //攔截set,當(dāng)我們?yōu)閐ata.key賦值時(shí)會(huì)被這個(gè)方法攔截到 set: function setter (newVal) { //當(dāng)數(shù)據(jù)變更時(shí),通知依賴(lài)項(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)用此屬性的依賴(lài)收集到一個(gè)集合中 ;而在我們給屬性賦值(修改屬性)時(shí),會(huì)觸發(fā)這里定義的setter
函數(shù),在次函數(shù)中會(huì)去通知集合中的依賴(lài)更新,做到數(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)很熟悉了,這里就不在展開(kāi)細(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)聽(tīng)和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)聽(tīng)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)聽(tīng)綁定的變量 vnode.context.$watch(binding.expression, (v) => { el.value = v; }) //添加inout事件監(jiān)聽(tīng) el.addEventListener('input', (e) => { //context是input所在的父組件,這一步是同步數(shù)據(jù) vnode.context[binding.expression] = e.target.value; }) } else { //組件 //vnode的結(jié)構(gòu)可以參見(jiàn)文檔。不過(guò)我覺(jué)得最直觀的方法就是直接在控制臺(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; //屬性綁定,這里直接修改了屬性,沒(méi)有想到更好的辦法,友好的意見(jiàn)希望可以提出 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)聽(tīng)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動(dòng)態(tài)擴(kuò)展表頭的表格及數(shù)據(jù)方式(數(shù)組嵌套對(duì)象)
這篇文章主要介紹了Vue動(dòng)態(tài)擴(kuò)展表頭的表格及數(shù)據(jù)方式(數(shù)組嵌套對(duì)象),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03Vue頁(yè)面監(jiān)聽(tīng)鍵盤(pán)按鍵的方法總結(jié)
在Vue頁(yè)面中,可以使用多種方法來(lái)監(jiān)聽(tīng)鍵盤(pán)按鍵,這篇文章主要為大家整理了五種常用的方法,文中的示例代碼講解詳細(xì),需要的小伙伴可以參考下2023-10-10Vue組件間的通信pubsub-js實(shí)現(xiàn)步驟解析
這篇文章主要介紹了Vue組件間的通信pubsub-js實(shí)現(xiàn)原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03vue項(xiàng)目中的數(shù)據(jù)變化被watch監(jiān)聽(tīng)并處理
這篇文章主要介紹了vue項(xiàng)目中的數(shù)據(jù)變化被watch監(jiān)聽(tīng)并處理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04Elementui表格組件+sortablejs實(shí)現(xiàn)行拖拽排序的示例代碼
這篇文章主要介紹了Elementui表格組件+sortablejs實(shí)現(xiàn)行拖拽排序,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08vue.js實(shí)現(xiàn)簡(jiǎn)單購(gòu)物車(chē)功能
這篇文章主要為大家詳細(xì)介紹了vue.js實(shí)現(xiàn)簡(jiǎn)單購(gòu)物車(chē)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05Element?plus中el-input框回車(chē)觸發(fā)頁(yè)面刷新問(wèn)題以及解決辦法
在el-form表單組件中el-input組件輸入內(nèi)容后按下Enter鍵刷新了整個(gè)頁(yè)面,下面這篇文章主要給大家介紹了關(guān)于Element?plus中el-input框回車(chē)觸發(fā)頁(yè)面刷新問(wèn)題以及解決辦法,需要的朋友可以參考下2024-03-03vue項(xiàng)目安裝scss常見(jiàn)報(bào)錯(cuò)處理方式
這篇文章主要介紹了vue項(xiàng)目安裝scss常見(jiàn)報(bào)錯(cuò)處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09vite創(chuàng)建一個(gè)標(biāo)準(zhǔn)vue3+ts+pinia項(xiàng)目
本文主要介紹了vite創(chuàng)建一個(gè)標(biāo)準(zhǔn)vue3+ts+pinia項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05