一、vue2中通过Object.defineProperty()进行数据劫持(事件监听数据变化)实现双向数据绑定
Object.defineProperty() :
定义:
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。(自己的理解:通过该方法可以为对象新增属性或者修改已有属性,可以监听到属性的变化)
在Vue2中我们可以利用该方法对数据对象(data)使用Object.defineProperty()进行监听data里面属性的变化进行双向数据绑定
二、Object.defineProperty() 的用法可以参考mdn官方文档,这里不进行说明,文档如下:
Object.defineProperty() - JavaScript | MDN
三、利用Object.defineProperty()
封装一个函数用于对象新增属性并且能监听该属性的变化
- function objAddProperty(obj, key,value){
- // 返回处理完毕的对象
- return Object.defineProperty(obj,key, {
- set(val) {
- //属性被赋值或者被修改时该函数自动执行,函数只有一个形参,
- //形参值为该属性被修改后的最新值
- console.log('obj.a被修改成...'+val)
- value = val
- },
- get(){
- //属性被访问时该函数自动执行
- //属性的值即为该函数的返回值
- console.log('obj.a被访问...')
- return value
- }
- })
- }
- //声明一个对象
- let obj={}
- // 为对象新增属性并进行监听该属性的变化
- obj= objAddProperty(obj, 'a',1)
- console.log(obj);
- obj.a=666
三、利用Object.defineProperty()
封装一个函数用于修改对象已有属性并且能监听该属性的变化,也可以实现不进行属性的修改,实现属性的劫持(Vue2中的数据劫持,劫持对象属性的变化)
1、劫持对象中的某个属性
- function observeKey(obj, key) {
- let value = obj[key];
- Object.defineProperty(obj, key, {
- set(val) {
- //属性被赋值或者被修改时该函数自动执行,函数只有一个形参,
- //形参值为该属性被修改后的最新值
- console.log('属性被修改成...'+val)
- value = val
- },
- get(){
- //属性被访问时该函数自动执行
- //属性的值即为该函数的返回值
- console.log('属性被访问...')
- return value
- }
- });
- }
- let obj = { a: 1 };
- observeKey(obj, "a");
- // 读取a,触发get函数
- console.log(obj.a);
- // 设置a,触发set函数
- obj.a = 2;
2、劫持对象中的所有属性
其实就是遍历对象的属性进行监听
- function observeObj(obj){
- //循环对象的属性进行监听
- for(let key in obj){
- observeKey(obj,key)
- }
- }
- function observeKey(obj, key) {
- let value = obj[key];
- Object.defineProperty(obj, key, {
- set(val) {
- //属性被赋值或者被修改时该函数自动执行,函数只有一个形参,
- //形参值为该属性被修改后的最新值
- console.log('属性被修改成...'+val)
- value = val
- },
- get(){
- //属性被访问时该函数自动执行
- //属性的值即为该函数的返回值
- console.log('属性被访问...')
- return value
- }
- });
- }
- let obj = { a: 1,b:2 };
- observeObj(obj)
- // 读取a,触发get函数
- console.log(obj.a);
- // 设置a,触发set函数
- obj.a = 2;
- // 读取b,触发get函数
- console.log(obj.b);
- // 设置a,触发set函数
- obj.b = 3;
注意:上面的有个缺陷,就是当属性值也是对象的时候,不能劫持属性值,如{a:1,c:{b:1}}
解决方案:判断对象的属性值数据类型,判断是否需要进行递归
- function observeObj(obj){
- //循环对象的属性进行监听
- for(let key in obj){
- if(obj[key] instanceof Object){
- observeObj(obj[key])
- }
- else{
- observeKey(obj,key)
- }
- }
- }
- function observeKey(obj, key) {
- let value = obj[key];
- Object.defineProperty(obj, key, {
- set(val) {
- //属性被赋值或者被修改时该函数自动执行,函数只有一个形参,
- //形参值为该属性被修改后的最新值
- console.log('属性被修改成...'+val)
- value = val
- },
- get(){
- //属性被访问时该函数自动执行
- //属性的值即为该函数的返回值
- console.log('属性被访问...')
- return value
- }
- });
- }
- let obj = { a: 1,c:{b:2} };
- observeObj(obj)
- // 读取a,触发get函数
- console.log(obj.a);
- // 设置a,触发set函数
- obj.a = 2;
- // 读取b,触发get函数
- console.log(obj.c.b);
- // 设置a,触发set函数
- obj.c.b = 3;
注意,observeObj这个函数,不能劫持对象的新增属性,只能劫持对象已有的属性。
四、Object.defineProperty()的缺陷
1、深度监听需要一次性递归(性能消耗大)
2、无法监听对象新增和删除的属性
五、简单模拟双向数据绑定
注意:不懂this._name的移步使用Object.definePropety报错 Maximum call stack size exceeded_不见浅诗~的博客-CSDN博客
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
- </head>
- <body>
- <div class="app">
- <input type="text">
- <p><span></span></p>
- <p><button>点击修改data数据模型的name值为马云</button></p>
- </div>
- <script>
- const button=document.querySelector("button")
- const span=document.querySelector("span")
- const input =document.querySelector('input')
- const data={
- name:'hsq'
- }
- input.value=data.name
- span.innerText=`data数据模型的值为${data.name}`
- Object.defineProperty(data,'name',{
- get(){
- return this._name
- },
- set(newValue){
- input.value=newValue
- this._name=newValue
- span.innerText=`data数据模型的值为${newValue}`
- console.log('data数据模型name属性值',newValue);
- }
- })
- input.addEventListener("input",function(e){
- console.log('输入框的值',e.target.value);
- data.name=e.target.value
- })
- button.addEventListener("click",function(){
- data.name='马云'
- })
- </script>
- </body>
- </html>