众所周知,Vue的两大重要概念:
- 数据驱动
- 组件系统
接下来我们浅析数据双向绑定
的原理
一、vue2
1、认识defineProperty
vue2中的双向绑定是基于defineProperty
的get操作
与set操作
,那么我们简单认识下defineProperty
,作用: 就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性。 那么我们先来看下Object.getOwnPropertyDescriptor()
,有定义方法就会有获取方法,对这就是与defineProperty
相对的方法,它可以获取属性值。
var a ={ b:1, c:2 } console.log(Object.getOwnPropertyDescriptor(a,'b')); //查看获取b属性
这就是打印出来的结果,configurable
的意思是可便利,enumerable
的意思是可枚举,writable
的意思是可写。
2、使用defineProperty实现简单的私有变量
知道了属性内部的属性值,我们可以使用defineProperty
来实现一个私有变量。
var a ={ b:1, c:2 } Object.defineProperty(a,'b',{ writable:false //不可写 }) console.log(Object.getOwnPropertyDescriptor(a,'b')); //查看获取b属性
发现,改变不了a.b
的值。
另外,还可以使用一个很快捷的方法实现私有变量
var a ={ b:1, c:2 } Object.freeze(a,'b'); //冻结,可理解为变为私有属性 // Object.seal(a,'b'); //configurable置为false console.log(Object.getOwnPropertyDescriptor(a,'b')); //查看获取b属性
同样,writable
为false。如果使用seal()
只能使configurable
为false。
3、简单认识defineProperty的get、set的方法
我们先不要创建全局变量,跟get方法return值,你会发现值为undefined
于是我们改了改
var a ={ b:1, c:2 } var _value=a.b; //b值赋值需要先赋给全局变量,不然使用不了 Object.defineProperty(a,'b',{ get:function(){ console.log('get'); return _value; // get方法必须ruturn值,值为全局变量。 }, set:function(newValue){ console.log(newValue) _value=newValue; // 赋值 } })
取到了。
4、使用defineProperty简单实现Vue双向绑定
test.js
// 使用defineProperty function vue () { this.$data={ a:1 // 数组的话不会更新 }; this.el=document.getElementById('app'); this._html=""; this.observe(this.$data); this.render(); }; vue.prototype.observe=function(obj){ var value; var self=this; for(var key in obj){ value =obj[key]; if(typeof value === 'object'){ this.observe(value) }else{ Object.defineProperty(this.$data,key,{ get:function(){ return value; }, set:function(newvalue){ value=newvalue; self.render(); } }) } } } vue.prototype.render=function(){ this._html='I am '+ this.$data.a; this.el.innerHTML=this._html; } // *************************************** //数组改变更新,装饰者模式 // var arraypro=Array.prototype; // var arrayob=Object.create(arraypro); // var arr = ["push","pop","shift"]; // // arr里的方法,既能保持原有的方法,又能触发更新。 // arr.forEach(function(method,index){ // arrayob[method]=function(){ // var ret=arraypro[method].apply(this,arguments); // console.log('更新'); // return ret; // } // }) // var arr=[]; // arr.__proto__=arrayob; // arr.push(1);
test.html
<!DOCTYPE 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>vue双向绑定原理</title> </head> <body> <div id="app"></div> </body> <script src="test.js"></script> <script> var vm = new vue(); setTimeout(function(){ console.log('changes'); console.log(vm.$data); vm.$data.a=222; },2000) </script> </html>
这样我们就简单实现了数据双向绑定。
二、vue3
1、简单认识Proxy
就是一种机制,用来拦截外界对目标对象的访问,可以对这些访问进行过滤或者改写,所以Proxy更像是目标对象的代理器。
var ob= { a:1, b:2 } // 新创建一个Proxy对象直接替换ob对象 ob=new Proxy(ob,{ get:function(target,key,receive){ // target指的是原始对象,key指的是属性值,receive指的是这个Proxy对象 console.log(target,key,receive) return target[key]; //get方法 依然需要return }, set:function(target,key,newvalue,receive){ console.log(target,key,newvalue,receive) // newvalue指新创建的值 target[key]=newvalue; } });
这里需要注意的是:第一次使用proxy时,在new的时候把原对象替换掉。就会触发get方法。
看到上方的代码,我们可以总结Proxy的几个优点:
- defineProperty只能监听某个属性,不能对全对象监听;
- 所以可以省去for in 提升效率;
- 可以监听数组,不用再去单独的对数组做异性操作。
2、使用Proxy简单实现数据双向绑定
test1.js
// 使用Proxy function vue () { this.$data={ a:1 }; this.el=document.getElementById('app'); this._html=""; this.observe(this.$data); this.render(); }; vue.prototype.observe=function(obj){ var self=this; this.$data=new Proxy(this.$data,{ get:function(target,key){ return target[key]; }, set:function(target,key,newvalue){ target[key]=newvalue; self.render(); } }) } vue.prototype.render=function(){ this._html='I am '+ this.$data.a; this.el.innerHTML=this._html; }
test.html
<!DOCTYPE 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>vue双向绑定原理</title> </head> <body> <div id="app"></div> </body> <script src="test1.js"></script> <script> var vm = new vue(); setTimeout(function(){ console.log('changes'); console.log(vm.$data); vm.$data.a=222; },2000) </script> </html>
3、Proxy还可以做什么呢?
(1)、校验类型
function createValidator(target,validator){ return new Proxy(target,{ _validator:validator, set:function(target,key,value,proxy){ if (target.hasOwnProperty(key)) { var validator1=this._validator[key]; if(validator1(value)){ return Reflect.set(target,key,value,proxy) } else{ throw Error('type error') } } } }) } var personvalidator={ name(val){ return typeof val==='string' }, age(val){ return typeof val==='number'&&val>18 } } class Person { constructor(name,age) { this.name=name; this.age=age; return createValidator(this,personvalidator); } } var tss=new Person('maomin',22);
(2)、真正私有变量
var api = { _secret: 'xxxx', _otherSec: 'bbb', ver: 'v0.0.1' }; api = new Proxy(api, { get: function (target, key) { // 以 _ 下划线开头的都认为是 私有的 if (key.startsWith('_')) { console.log('私有变量不能被访问'); return false; } return target[key]; }, set: function (target, key, value) { if (key.startsWith('_')) { console.log('私有变量不能被修改'); return false; } target[key] = value; }, has: function (target, key) { return key.startsWith('_') ? false : (key in target); } }); api._secret; // 私有变量不能被访问 console.log(api.ver); // v0.0.1 api._otherSec = 3; // 私有变量不能被修改 console.log('_secret' in api); //false console.log('ver' in api); //true