vue2.x和vue3.x中的双向数据绑定原理有什么不同

简单探讨一下vue2.x和vue3.x中实现数据绑定的原理有什么不同,首先vue2.x使用的是Object.defineProperty方法,这个方法不兼容IE9以下的版本;而vue3.x使用的是Proxy方法,至于什么是Proxy,待会会简单介绍一下

一. vue2.x实现原理

vue2.x版本,主要还是通过Object.defineProperty实现双向数据绑定的,我们通过写一个非常非常简化版的vue框架和demo来实现这个双向数据绑定,来看下实现过程

1 我们创建一个html模板:


<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的方法没有什么不同。

2 编写vue.js

(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方法(根据数据的改动去渲染视图层)
每行代码的意义如下:

  • 其中$el是根据html模板中传入的el属性去页面上查找对应id的元素作为vue实例绑定的DOM节点
  • $data赋值为一个写死的对象(实际上应该也是html模板传过来的,这里先不讨论)
  • htmlContent就是最后要渲染到页面上的模板(实际上不应该是一个简单的字符串,这里也先不讨论)
  • this.render()方法就是在vue实例初始化的时候先调用一遍渲染方法去渲染页面
  • this.observe()方法就是去调用绑定数据监听的方法

(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去渲染这个模板即可

3. 劣势

  • 使用了闭包,占用了不少内存资源,很多情况下vue实例里面的data会有很多内容
  • 方法有点挫,还要用一个value来保存vue实例的data对象里面的属性值

二. vue3.x实现原理

1. Proxy的介绍

vue3.x主要使用的是proxy对象,proxy对象的定义是:Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
proxy和defineProperty功能是类似的,只是用法有些不同
使用语法如下:

let p = new Proxy(target, handler);

参数如下:

  • target: 用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  • handler: 一个对象,其属性是当执行一个操作时定义代理的行为的函数。

2. Proxy的demo

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;
	}
})

在浏览器调试发现的问题
vue2.x和vue3.x中的双向数据绑定原理有什么不同_第1张图片
发现通过ob.a去取值根本没有调用get方法。实际上上面这种ob.a的用法是错误的,当我们使用Proxy去代理一个对象了之后,我们就不应该直接对源对象去做操作,而是应该对返回的proxy对象去做操作。
vue2.x和vue3.x中的双向数据绑定原理有什么不同_第2张图片
可以看到,当我们执行obj.a时,就会触发set方法里面的输出日志操作了

3. Proxy的优势

相比于vue2.x,使用proxy的优势如下

  • 1 defineProperty只能监听某个属性,不能对全对象监听
  • 2 可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)
  • 3 可以监听数组,不用再去单独的对数组做特异性操作

4. 使用proxy改写vue.js

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还清晰了一些,更容易理解

5. Proxy带来的问题

vue2.x和vue3.x中的双向数据绑定原理有什么不同_第3张图片
Proxy的问题在于浏览器兼容有点问题,IE下完全不兼容,如果要兼容的话应该是需要添加polyfill等内容(这个具体的解决方法要看一下尤大神是怎么处理的)

你可能感兴趣的:(vue.js,JavaScript)