博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
vue 数据劫持详解
阅读量:5968 次
发布时间:2019-06-19

本文共 5320 字,大约阅读时间需要 17 分钟。

hot3.png

 

首先 Object.defineProperty(obj,prop,descriptor) 用法介绍:

  • 参数

    obj:目标对象

    prop:需要定义的属性或方法的名称

    descriptor:目标属性所拥有的特性

  • 可供定义的特性列表

    value:属性的值

    writable:如果为false,属性的值就不能被重写。

    get: 一旦目标属性被访问就会调回此方法,并将此方法的运算结果返回用户。

    set:一旦目标属性被赋值,就会调回此方法。

    configurable:如果为false,则任何尝试删除目标属性或修改属性性以下特性(writable, configurable, enumerable)的行为将被无效化。

    enumerable:是否能在for...in循环中遍历出来或在Object.keys中列举出来

什么是数据劫持

通过上面对Object.defineProperty的介绍,我们不难发现,当我们访问或设置对象的属性的时候,都会触发相对应的函数,然后在这个函数里返回或设置属性的值。既然如此,我们当然可以在触发函数的时候动一些手脚做点我们自己想做的事情,这也就是“劫持”操作。在Vue中其实就是通过Object.defineProperty来劫持对象属性的setter和getter操作,并“种下”一个监听器,当数据发生变化的时候发出通知。先简单的举个例子:

var data = {    name:'fang'}Object.keys(data).forEach(function(key){    Object.defineProperty(data,key,{        enumerable:true,        configurable:true,        get:function(){            console.log('get');        },        set:function(){            console.log('监听到数据发生了变化');        }    })});data.name //控制台会打印出 “get”data.name = 'fangpeng' //控制台会打印出 "监听到数据发生了变化"

vue原理:

监听对象属性的变化

Vue数据绑定之前,它通过observe每个对象的属性,添加到订阅器dep中,当数据发生变化的时候发出一个notice。 相关源代码如下:(作者采用的是ES6+flow写的,代码在src/core/observer/index.js模块里面)

export function defineReactive (  obj: Object,  key: string,  val: any,  customSetter?: Function) {  const dep = new Dep()//创建订阅对象  const property = Object.getOwnPropertyDescriptor(obj, key)//获取obj对象的key属性的描述  //属性的描述特性里面如果configurable为false则属性的任何修改将无效  if (property && property.configurable === false) {    return  }  // cater for pre-defined getter/setters  const getter = property && property.get  const setter = property && property.set  let childOb = observe(val)//创建一个观察者对象  Object.defineProperty(obj, key, {    enumerable: true,//可枚举    configurable: true,//可修改    get: function reactiveGetter () {      const value = getter ? getter.call(obj) : val//先调用默认的get方法取值      //这里就劫持了get方法,也是作者一个巧妙设计,在创建watcher实例的时候,通过调用对象的get方法往订阅器dep上添加这个创建的watcher实例      if (Dep.target) {        dep.depend()        if (childOb) {          childOb.dep.depend()        }        if (Array.isArray(value)) {          dependArray(value)        }      }      return value//返回属性值    },    set: function reactiveSetter (newVal) {      const value = getter ? getter.call(obj) : val//先取旧值      if (newVal === value) {        return      }      //这个是用来判断生产环境的,可以无视      if (process.env.NODE_ENV !== 'production' && customSetter) {        customSetter()      }      if (setter) {        setter.call(obj, newVal)      } else {        val = newVal      }      childOb = observe(newVal)//继续监听新的属性值      dep.notify()//这个是真正劫持的目的,要对订阅者发通知了    }  })}

以上是Vue监听对象属性的变化,那么问题来了,我们经常在传递数据的时候往往不是一个对象,很有可能是一个数组,那是不是就没有办法了呢,答案显然是否则的。那么下面就看看作者是如何监听数组的变化:

监听数组的变化

我们还看先看这段源码:

const arrayProto = Array.prototype//原生Array的原型export const arrayMethods = Object.create(arrayProto);[  'push',  'pop',  'shift',  'unshift',  'splice',  'sort',  'reverse'].forEach(function (method) {  const original = arrayProto[method]//缓存元素数组原型  //这里重写了数组的几个原型方法  def(arrayMethods, method, function mutator () {    //这里备份一份参数应该是从性能方面的考虑    let i = arguments.length    const args = new Array(i)    while (i--) {      args[i] = arguments[i]    }    const result = original.apply(this, args)//原始方法求值    const ob = this.__ob__//这里this.__ob__指向的是数据的Observer    let inserted    switch (method) {      case 'push':        inserted = args        break      case 'unshift':        inserted = args        break      case 'splice':        inserted = args.slice(2)        break    }    if (inserted) ob.observeArray(inserted)    // notify change    ob.dep.notify()    return result  })})...//定义属性function def (obj, key, val, enumerable) {  Object.defineProperty(obj, key, {    value: val,    enumerable: !!enumerable,    writable: true,    configurable: true  });}

上面的代码主要是继承了Array本身的原型方法,然后又做了劫持修改,可以发出通知。Vue在observer数据阶段会判断如果是数组的话,则修改数组的原型,这样的话,后面对数组的任何操作都可以在劫持的过程中控制。结合Vue的思想,我简单的写个小demo方便更好的理解:

var arrayMethod = Object.create(Array.prototype);['push','shift'].forEach(function(method){    Object.defineProperty(arrayMethod,method,{        value:function(){            var i = arguments.length            var args = new Array(i)            while (i--) {              args[i] = arguments[i]            }            var original = Array.prototype[method];            var result = original.apply(this,args);            console.log("已经控制了,哈哈");            return result;        },        enumerable: true,        writable: true,        configurable: true    })})var bar = [1,2];bar.__proto__ = arrayMethod;bar.push(3);//控制台会打印出 “已经控制了,哈哈”;并且bar里面已经成功的添加了成员 ‘3’

整个过程看起来好像没有什么问题,似乎Vue已经做到了完美,其实不然,Vue还是不能检测到数据项和数组长度改变的变化,例如下面的调用:

vm.items[index] = "xxx";vm.items.length = 100;

我们尽量避免这样的调用方式,如果确实需要,作者也帮我们实现了一个$set操作,这里就不做介绍了。

实现对象属性代理

正常情况下我们是这样实例化一个Vue对象:

var VM = new Vue({    data:{        name:'lhl'    },    el:'#id'})

按理说我们操作数据的时候应该是VM.data.name = ‘hxx’才对,但是作者觉得这样不够简洁,所以又通过代理的方式实现了VM.name = ‘hxx’的可能。 相关代码如下:

function proxy (vm, key) {  if (!isReserved(key)) {    Object.defineProperty(vm, key, {      configurable: true,      enumerable: true,      get: function proxyGetter () {        return vm._data[key]      },      set: function proxySetter (val) {        vm._data[key] = val;      }    });  }}

表面上看起来我们是在操作VM.name,实际上还是通过Object.defineProperty()中的get和set方法劫持实现的。

总结

Vue框架很好的利用了Object.defineProperty()这个方法来实现了数据的双向绑定,同时也达到了很好的模块间解耦

转载于:https://my.oschina.net/pengpengpengone/blog/1837257

你可能感兴趣的文章
在WPF中实现平滑滚动
查看>>
java并发编程实践 part 01 --> 线程创建方式
查看>>
工作中常用,实用工具推荐!
查看>>
用汇编实现add函数
查看>>
MSSQL · 最佳实践 · 使用混合密钥实现列加密
查看>>
快速了解什么是自然语言处理
查看>>
springboot设置静态资源不拦截的方法
查看>>
micropython esp8266+ssd1306(OLED) 显示中文(示例)
查看>>
哈希表的C实现(三)---传说中的暴雪版
查看>>
斑马快跑已获3亿元新一轮融资,已成为全国第五大网约车平台
查看>>
linux内存源码分析 - 内存压缩(同步关系)
查看>>
Java入门之初识设计模式---单列模式
查看>>
谈谈Ajax(二)
查看>>
windows下捕获dump
查看>>
百度推出惊雷算法,打击快排产业,但重点是这个工具
查看>>
yolo3(目标检测)实测
查看>>
爱是一个美好的天气
查看>>
使用Keras进行深度学习:(二)CNN讲解及实践
查看>>
首场阿里云 MVP Tech Show 西安同城会成功举办
查看>>
Spark2.2.0 分布式离线搭建
查看>>