前言
之前公司招人,面试了一些的前端同学,因为公司使用的前端技术是Vue
,所以免不了问到其响应式原理和Vue
的双向数据绑定。但是这边面试到的80%的同学会把两者搞混,通常我要是先问响应式原理再问双向数据绑定原理,来面试的同学大都会认为是一回事,那么这里我们就说一下二者的区别。
响应式原理
是Vue的核心特性之一,数据驱动视图,我们修改数据视图随之响应更新,就很优雅~
Vue2.x
是借助Object.defineProperty()
实现的,而Vue3.x
是借助Proxy
实现的,下面我们先来看一下2.x的实现。
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
//拦截get,当我们访问data.key时会被这个方法拦截到
get: function getter () {
//我们在这里收集依赖
return obj[key];
},
//拦截set,当我们为data.key赋值时会被这个方法拦截到
set: function setter (newVal) {
//当数据变更时,通知依赖项变更UI
}
})
我们通过Object.defineProperty
为对象obj
添加属性,可以设置对象属性的getter
和setter
函数。之后我们每次通过点语法获取属性都会执行这里的getter
函数,在这个函数中我们会把调用此属性的依赖收集到一个集合中 ;而在我们给属性赋值(修改属性)时,会触发这里定义的setter
函数,在次函数中会去通知集合中的依赖更新,做到数据变更驱动视图变更。
3.x的与2.x的核心思想一致,只不过数据的劫持使用Proxy
而不是Object.defineProperty
,只不过Proxy相比Object.defineProperty在处理数组和新增属性的响应式处理上更加方便。
let nObj=new Proxy(obj,{
//拦截get,当我们访问nObj.key时会被这个方法拦截到
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey, receiver);
},
//拦截set,当我们为nObj.key赋值时会被这个方法拦截到
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
}
})
Proxy
的详细使用方法参考ES6教程。
Vue
的响应式原理的实现细节相信大多数同学已经很熟悉了,这里就不在展开细谈了,如果还想更详细的了解,或者想要做一个简易的Vue
实现,可以参考这篇Vue原理,相信你会有不小收获。
双向数据绑定
双向数据绑定通常是指我们使用的v-model
指令的实现,是Vue
的一个特性,也可以说是一个input
事件和value
的语法糖。 Vue
通过v-model
指令为组件添加上input
事件处理和value
属性的赋值。
<template>
<input v-model='localValue'/>
</template>
上述的组件就相当于如下代码
<template>
<!-- 这里添加了input时间的监听和value的属性绑定 -->
<input @input='onInput' :value='localValue' />
<span>{{localValue}}</span>
</template>
<script>
export default{
data(){
return {
localValue:'',
}
},
methods:{
onInput(v){
//在input事件的处理函数中更新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>
因此当我们修改input输入框中的值时,我们通过v-model绑定的值也会同步修改,基于上述原理,我们可以很容易的实现一个数据双向绑定的组件。
v-model实践
首先我们定义一个Vue
组件,相信大家已经很熟悉了。
<tempalte>
<div class="count" @click="addCount">click me {{value}}</div>
</template>
<script>
export default{
props:{
//关键的第一步:设置一个value属性
value:{
type:Number,
default:0
}
},
watch:{
//监听value变化,更新组件localvalue状态
value(v){
this.localvalue=v;
}
},
methods:{
//关键的第二步:事件触发localvalue变更,通过事件同步父组件状态变更
addCount(){
this.localvalue++;
this.$emit('input',this.localvalue);
}
},
data(){
return{
//组件状态,遵守单项数据流原则,不直接修改props中的属性
localvalue:0
}
},
created(){
//初始化获取value值
this.localvalue=this.value;
}
}
</script>
上面的组件定了我们通过在props
中添加value
属性,并且在值更新时触发input
事件。created
钩子和watch
中为localvalue
赋值是为了同步父组件状态到子组件中。
通过上面?的组件定义,我们就可以在组件上使用v-model
指令做双向数据绑定了。
<template>
<add-one v-model="count"></add-one>
<span>父组件{{count}}</span>
</tempalte>
<script>
export default{
data() {
return {
count: 0,
};
},
methods: {
},
created(){
}
}
</script>
下面是实际效果
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标签需要分别处理,
el.value = binding.value;
if (vnode.tag == 'input') {
//监听绑定的变量
vnode.context.$watch(binding.expression, (v) => {
el.value = v;
})
//添加inout事件监听
el.addEventListener('input', (e) => {
//context是input所在的父组件,这一步是同步数据
vnode.context[binding.expression] = e.target.value;
})
} else { //组件
//vnode的结构可以参见文档。不过我觉得最直观的方法就是直接在控制台打印处理
let {
componentInstance,
componentOptions,
context
} = vnode;
const {
_props
} = componentInstance;
//处理model选项
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;
})
//添加事件处理函数,做数据同步
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: {
//监听value变化,更新组件localvalue状态
value(v) {
this.localvalue = v;
}
},
methods: {
//事件触发localvalue变更,通过事件同步父组件状态变更
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() {
}
})
当然我们也可以不使用value
和input
事件这样的组合,为了更使得组件的定义更加符合语义,我们也可以自定义要实现双向绑定的属性和事件。 我们在组件的model
选项中设置value
和event
即可。如下:
export default{
//这里做了一个value和event的映射
model:{
value:'count',
event:'change'
},
props:{
//关键的第一步:设置一个value属性
count:{
type:Number,
default:0
}
},
methods:{
//关键的第二步:事件触发localvalue变更,通过事件同步父组件状态变更
addCount(){
this.localvalue++;
this.$emit('change',this.localvalue);
}
},
}
通过上面的组件定义
<add-one v-model="count"></add-one>
就相当于
<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>
只不过v-model
指令帮我们做上面的事件添加,属性绑定和状态同步操作罢了。
以上就是Vue响应式原理及双向数据绑定示例分析的详细内容,更多关于Vue响应式双向数据绑定的资料请关注编程网其它相关文章!