Vue框架核心之数据劫持

本文来自网易云社区

前瞻

当前前端界空前繁荣,各种框架横空出世,包括各类mvvm框架横行霸道,比如Angular、Regular、Vue、React等等,它们最大的优点就是可以实现数据绑定,再也不需要手动进行DOM操作了,它们实现的原理也基本上是脏检查或数据劫持。那么本文就以Vue框架出发,探索作者运用Object.defineProperty来实现数据劫持的奥秘(本文所选取的相关代码源自于Vue v2.0.3版本的源码)。

回顾一下Object.defineProperty

  • 语法
     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:‘lhl‘}
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 = ‘hxx‘ //控制台会打印出 "监听到数据发生了变化"

上面的这个例子可以看出,我们完全可以控制对象属性的设置和读取。在Vue中,作者在很多地方都非常巧妙的运用了Object.defineProperty这个方法,具体用在哪里并且它又解决了哪些问题,下面就做详细的介绍:

监听对象属性的变化

这个应该是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()这个方法来实现了数据的双向绑定,同时也达到了很好的模块间解耦,在日常开发中,你也可以用好这个方法来优化对象获取和修改属性方式,或者自己实现一个MVVM的双向数据绑定等。

本篇文章是我对Vue的浅薄之悟,如有理解不足之处,还请大家批评指正,Thank you ~

本文来自网易云社区,经作者黎浩梁授权发布。

原文:Vue框架核心之数据劫持

新用户大礼包:https://www.163yun.com/gift

更多网易研发、产品、运营经验分享请访问网易云社区

原文地址:https://www.cnblogs.com/163yun/p/9488807.html

时间: 2024-11-09 12:02:32

Vue框架核心之数据劫持的相关文章

[Vue源码]一起来学Vue双向绑定原理-数据劫持和发布订阅

有一段时间没有更新技术博文了,因为这段时间埋下头来看Vue源码了.本文我们一起通过学习双向绑定原理来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学Vue模板编译原理(一)-Template生成AST 一起来学Vue模板编译原理(二)-AST生成Render字符串 一起来学Vue虚拟DOM解析-Virtual Dom实现和Dom-diff算法 这些文章统一放在我的git仓库:https://github.com/yzsun

Vue之九数据劫持实现MVVM的数据双向绑定

vue是通过数据劫持的方式来做数据绑定的,其中最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,达到监听数据变动的目的. 如果不熟悉defineProperty,猛戳这里 整理了一下,要实现mvvm的双向绑定,就必须要实现以下几点:1.实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者2.实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数3.实现

0 到 1 掌握:Vue 核心之数据双向绑定

实现数据的双向绑定: 1.实现一个监听器 Observer ,用来劫持并监听所有属性,如果属性发生变化,就通知订阅者: 2.实现一个订阅器 Dep,用来收集订阅者,对监听器 Observer 和 订阅者 Watcher 进行统一管理: 3.实现一个订阅者 Watcher,可以收到属性的变化通知并执行相应的方法,从而更新视图: 4.实现一个解析器 Compile,可以解析每个节点的相关指令,对模板数据和订阅器进行初始化. Reference: 0 到 1 掌握:Vue 核心之数据双向绑定 http

Vue框架(一)——Vue导读、Vue实例(挂载点el、数据data、过滤器filters)、Vue指令(文本指令v-text、事件指令v-on、属性指令v-bind、表单指令v-model)

Vue导读 1.Vue框架 vue是可以独立完成前后端分离式web项目的js框架 三大主流框架之一:Angular.React.Vue vue:结合其他框架优点.轻量级.中文API.数据驱动.双向绑定.MVVM设计模式.组件化开发.单页面应用 Vue环境:本地导入和cdn导入 2.Vue是渐进式js框架 通过对框架的了解与运用程度,来决定其在整个项目中的应用范围,最终可以独立以框架方式完成整个web前端项目.3.怎么使用vue 去官网下载然后导入 <div id="app">

自己实现 一个 Vue框架,包含了Vue的核心原理

Vue工作机制 vue工作机制 Vue响应式的原理 依赖收集与追踪 编译compile html: <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> </head> <body> <div id="app"> <!-- 插值绑定 --> <p>{{name}}<

用vue框架mock数据

用vue 2.0 mock数据 方法一 webpack打包的项目中可以依赖express var express = require('express'): var app = express(): var appData = require("../data.json"); var seller = appData.seller; var apiRouter = express.Router(); apiRouter.get("/seller", function

vue框架(一)

vue是什么? vue:一个构建用户界面的框架. 1在HTML元素显示数据 {{}} v-text v-html 2指令: 通过指令,来给DOM元素赋值或者其它操作:v-text v-html 根据表达式的真假值,动态地插入.移除元素:v-text v-html v-if\v-else 根据表达式的真假值,动态地显示.隐藏元素:v-show 根据数值渲染元素列表:v-for 绑定元素的属性,可以动态改变:v-bind 根据命令监听且执行事件:v-on v-model:数据双向绑定 它是把视图和数

Vue 框架-08-基础实战 demo

Vue 框架-08-基础实战 demo 前面介绍了有 7 篇了,都是小实例,没有相对完整的应用,虽然有些功能挺实用,但还是有的不常用的,今天记录一篇关于前几篇基础内容的实战 demo,也是对 Vue 基础的简单应用. 来看截图: 源代码 html 文件: <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>VueLearn-cnblogs/xpwi<

为什么手机wap端都喜欢用VUE.js框架?Vue框架特点

Vue.js是一个轻巧.高性能.可组件化的MVVM库,同时拥有非常容易上手的API: ? ?Vue.js是一个构建数据驱动的Web界面的库. ? ?Vue.js是一套构建用户界面的 渐进式框架.与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计.Vue 的核心库只关注视图层,并且非常容易学习,非常容易与其它库或已有项目整合.另一方面,Vue 完全有能力驱动采用单文件组件和 Vue 生态系统支持的库开发的复杂单页应用.数据驱动+组件化的前端开发. 简而言之:Vue.js是一个构建数据驱动