简单探讨一下vue2.x和vue3.x中实现数据绑定的原理有什么不同,首先vue2.x使用的是Object.defineProperty方法,这个方法不兼容IE9以下的版本;而vue3.x使用的是Proxy方法,至于什么是Proxy,待会会简单介绍一下
vue2.x版本,主要还是通过Object.defineProperty实现双向数据绑定的,我们通过写一个非常非常简化版的vue框架和demo来实现这个双向数据绑定,来看下实现过程
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div id="app">div>
<script src="./vue.js">script>
<script>
var vm = new Vue({
el: 'app'
});
setTimeout(function() {
vm.$data.name = 'Alpha';
vm.$data.age = 53;
console.log(vm);
}, 2000);
script>
bdy>
html>
可以看到这个模板跟那种简单的直接script标签引入vue的方法没有什么不同。
(1) 大体框架
function Vue(param) {
this.$el = document.getElementById(param.el);
this.$data = {name: 'Encore', age: 23};
this.htmlContent = '';
this.render();
this.observe(this.$data);
}
Vue.prototype.observe = function(obj) {}
Vue.prototype.render = function() {}
主要分为3个部分,Vue本身的构造函数、vue原型对象上的observe方法 (主要用于绑定数据改动监听以及数据的分发)、render方法(根据数据的改动去渲染视图层)
每行代码的意义如下:
(2) observe方法的具体逻辑
Vue.prototype.observe = function(obj) {
var value = '',
_this = this;
for (var key in obj) {
value = obj[key];
(function(key, value) {
if (typeof obj[key] === 'object') {
this.observe(obj[key]);
} else {
Object.defineProperty(_this.$data, key, {
get: function() {
console.log('get value');
return value;
},
set: function(newVal) {
value = newVal;
_this.render();
}
});
}
})(key, value);
}
}
首先,这个方法传进来的参数obj实际上就是vue实例的data对象,然后在第5行对这个data对象去做一个遍历,第6行获取当前key值对应的value保存下来(用于在get方法返回)。
在第8行用了一个闭包的原因是,保持对每次遍历中不同key和value值的引用(不然循环结束时value是data对象的最后一个属性值,调用set方法时无法对除了最后一个属性值外的其他属性值做操作)
第9行,先判断当前遍历到的data对象中的属性值是否是对象,如果是的话,再次调用observe方法实现递归操作
第11行,如果当前遍历或递归到的属性值不是对象了,而是简单数据类型,那么此时通过Object.defineProperty方法对该属性key值进行数据绑定,这个方法接收get方法和set方法。get方法的作用是当我们在浏览器上通过console.log(obj.a)去取值时,就会调用get方法,最终取到的是return语句输出的内容。而set方法的作用是当我们在浏览器上通过obj.a=111时会调用set方法。那么此时操作就比较简单了,当我们每次去给data对象中的某个内容赋值时,都会触发set方法,那么set方法要负责的内容就是把对应key的value给更新成赋予的那个值(18行),并且触发渲染函数去更新视图(19行)即可
(3) render方法
这里的render方法是写的非常非常简单的,这里只是做个参考实际不可能这样写
Vue.prototype.render = function() {
this.htmlContent = '' +
'User Profile
' +
'name: '
+ this.$data.name + '' +
'age: '
+ this.$data.age + '' +
'';
this.$el.innerHTML = this.htmlContent;
}
这个render方法就很简单,拿到data对象中仅有的两个值,拼接成一个html模板,然后前面绑定的DOM结点通过innerHTML去渲染这个模板即可
vue3.x主要使用的是proxy对象,proxy对象的定义是:Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
proxy和defineProperty功能是类似的,只是用法有些不同
使用语法如下:
let p = new Proxy(target, handler);
参数如下:
var ob = {
a: 1,
b: 2
}
var obj = new Proxy(ob, {
// target就是第一个参数ob, receive就是返回的obj(返回的proxy对象)
get: function(target, key, receive) {
// 返回该属性值
return target[key];
},
set: function(target, key, newVal, receive) {
// 执行赋值操作
target[key] = newVal;
}
})
在浏览器调试发现的问题
发现通过ob.a去取值根本没有调用get方法。实际上上面这种ob.a的用法是错误的,当我们使用Proxy去代理一个对象了之后,我们就不应该直接对源对象去做操作,而是应该对返回的proxy对象去做操作。
可以看到,当我们执行obj.a时,就会触发set方法里面的输出日志操作了
相比于vue2.x,使用proxy的优势如下
html的内容跟2.x的例子一模一样,只需要改写vue.js即可
// 仅改写observe方法即可
function Vue(param) {
this.$el = document.getElementById(param.el);
this.$data = {name: 'Encore', age: 23};
this.htmlContent = '';
this.render();
this.observe(this.$data);
}
Vue.prototype.observe = function(obj) {
var value = '',
_this = this;
this.$data = new Proxy(this.$data, {
get: function(target, key) {
return target[key];
},
set: function(target, key, newVal) {
target[key] = newVal;
_this.render();
}
});
}
Vue.prototype.render = function() {
this.htmlContent = '' +
'User Profile
' +
'name: '
+ this.$data.name + '' +
'age: '
+ this.$data.age + '' +
'';
this.$el.innerHTML = this.htmlContent;
}
我们只需要关注一下observe方法,这里改为使用proxy方法(第11行),此时我们只需要整个$data对象传入即可,不再需要使用for…in循环和闭包操作,减少了好几行代码,操作简单了很多。
12行的get方法,也不需要用一个额外的value变量来保存值,而是直接target[key]去读取值即可,set方法也一样,赋值改为target[key] = newVal即可,不对value变量做处理了
这种方法实现起来思路比2.x还清晰了一些,更容易理解
Proxy的问题在于浏览器兼容有点问题,IE下完全不兼容,如果要兼容的话应该是需要添加polyfill等内容(这个具体的解决方法要看一下尤大神是怎么处理的)