赞
踩
使用的Object.defineProperty()重新定义对象,给data的每个属性都添加了getter和setter方法。这时候会为对象的每个属性创建一个Dep实例 (依赖)。Dep实例可以订阅和通知相关的Watcher实例。, 这一步叫 数据劫持 或者 依赖收集
在数据发生更新后调用 set 时会通知发布者 notify 通知对应的订阅者做出数据更新,同时将新的数据根性到视图上显示。 这一步叫 派发更新
同时,为了解决对象属性添加和删除的问题,Vue提供了全局的Vue.set和Vue.delete方法,以及实例的vm.$set和vm.$delete方法。
解释:
(
)
总结:
在Vue2中,响应式原理是通过使用Object.defineProperty方法来实现的。当一个对象被传入Vue的observe函数中时,Vue会为对象的每个属性创建一个Dep实例。Dep实例可以订阅和通知相关的Watcher实例。
当一个属性被访问时,Watcher实例会将自己添加到该属性的Dep实例的订阅列表中。当该属性的值发生变化时,Dep实例会遍历订阅列表,通知所有相关的Watcher实例进行更新。
然而,Vue无法检测到对象属性的添加和删除。为了解决这个问题,Vue提供了全局的Vue.set方法或实例的vm.$set方法来添加属性,使用Vue.delete方法或实例的vm.$delete方法来删除属性。对于数组的变动,Vue无法检测到利用索引设置数组,但可以使用Vue.set方法或实例的vm.$set方法。此外,Vue也无法检测直接修改数组长度,但可以使用splice方法来实现。
总结起来,Vue2的响应式原理通过使用Object.defineProperty方法来实现属性的劫持和侦听,同时使用Dep和Watcher实例来建立属性和依赖之间的关系,并进行更新通知。同时,为了解决对象属性添加和删除的问题,Vue提供了全局的Vue.set和Vue.delete方法,以及实例的vm.$set和vm.$delete方法。
通过数据劫持 defineProperty + 发布订阅者模式,当 vue 实例初始化后 observer 会针对实例中的 data 中的每一个属性进行劫持并通过 defineProperty() 设置值后在 get() 中向发布者添加该属性的订阅者,
使用的Object.defineProperty()重新定义对象,给data的每个属性都添加了getter和setter方法。
在get的时候会调用dep.depend;
如果是数组,则调用dependArray(对数组里的每个元素进行递归调用dep.depend);
在set的时候会先判断数据是否更新,未更新不做操作,更新则observe(),且dep.notify()
1 在initState()方法里会对组件的props, methods, data,computed, watch等进行编译初始化=>
2 initData()会先获取组件内部的data数据,然后判断data里的数据和props,或者和methods里的名称重复,则抛出错误提示,然后就会去监听data,执行observe方法=>
3 Observer() => defineReactive, dependArray => defineProperty() => Observer() 递归
使用的Object.defineProperty()重新定义对象,给data的每个属性都添加了getter和setter方法。
在get的时候会调用dep.depend;如果是数组,则调用dependArray(对数组里的每个元素进行递归调用dep.depend);
在set的时候会先判断数据是否更新,未更新不做操作,更新则observe(),且dep.notify()
不同版本的vue的源码实现可能会有些不同,我这里的版本是2.6.14
首先我们要知道定义响应式是在哪个时间段实现的,从源码中我们可以看到,是在执行beforeCreate生命周期函数之后,Created之前。也就是说,这也就是我们在beforeCreate无法拿到Data中的数据的原因。
首先我们要知道定义响应式是在哪个时间段实现的,从源码中我们可以看到,是在执行beforeCreate生命周期函数之后,Created之前。也就是说,这也就是我们在beforeCreate无法拿到Data中的数据的原因。
第一步: 在initState方法里会对组件的props, methods, data,computed, watch等进行编译初始化
- export function initState (vm: Component) {
- vm._watchers = []
- const opts = vm.$options
- if (opts.props) initProps(vm, opts.props)
- if (opts.methods) initMethods(vm, opts.methods)
- if (opts.data) {
- initData(vm)
- } else {
- observe(vm._data = {}, true /* asRootData */)
- }
- if (opts.computed) initComputed(vm, opts.computed)
- if (opts.watch && opts.watch !== nativeWatch) {
- initWatch(vm, opts.watch)
- }
- }
-
第二步: initData()会先获取组件内部的data数据,然后判断data里的数据和props,或者和methods里的名称重复,则抛出错误提示,然后就会去监听data,执行observe方法
- function initData (vm: Component) {
- let data = vm.$options.data
- data = vm._data = typeof data === 'function'
- ? getData(data, vm)
- : data || {}
- if (!isPlainObject(data)) {
- data = {}
- process.env.NODE_ENV !== 'production' && warn(
- 'data functions should return an object:\n' +
- 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
- vm
- )
- }
- // proxy data on instance
- const keys = Object.keys(data)
- const props = vm.$options.props
- const methods = vm.$options.methods
- let i = keys.length
- while (i--) {
- const key = keys[i]
- if (process.env.NODE_ENV !== 'production') {
- if (methods && hasOwn(methods, key)) {
- warn(
- `Method "${key}" has already been defined as a data property.`,
- vm
- )
- }
- }
- if (props && hasOwn(props, key)) {
- process.env.NODE_ENV !== 'production' && warn(
- `The data property "${key}" is already declared as a prop. ` +
- `Use prop default value instead.`,
- vm
- )
- } else if (!isReserved(key)) {
- proxy(vm, `_data`, key) //将data上的属性代理到vm实例上。
- }
- }
- // observe data
- observe(data, true /* asRootData */)
- }
-
第三步:在这里observe()中,会先判断data中的数据是否是对象,然后判断data中是否已经有了ob(也就是Observer实例)最后判断是否满足监听的条件。才会创建一个新的Observer对象
- /**
- * Attempt to create an observer instance for a value,
- * returns the new observer if successfully observed,
- * or the existing observer if the value already has one.
- */
- export function observe (value: any, asRootData: ?boolean): Observer | void {
- if (!isObject(value) || value instanceof VNode) {
- return
- }
- let ob: Observer | void
- if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
- ob = value.__ob__
- } else if (
- shouldObserve &&
- !isServerRendering() &&
- (Array.isArray(value) || isPlainObject(value)) &&
- Object.isExtensible(value) &&
- !value._isVue
- ) {
- ob = new Observer(value)
- }
- if (asRootData && ob) {
- ob.vmCount++
- }
- return ob
- }
-
-
第四步:
每一个observer实例都有自己的一个Dep, 在new Oberver后,会判断传入的value也就是vm.data是不是数组。
如果是数组,会采用函数劫持的方法重写数组的方法,先判断数组支不支持原型链,支持就将当前数组的原型指向已经重写了Array里的7种方法的arrayMethod,当数组里的方法被调用时,Dep会notify通知视图更新,然后执行ObserveArray方法,如果数组里的数据是对象,则继续回调observe();
如果是对象,则调用this.walk(),在walk()中,会遍历data的属性执行defineReactive()定义响应式
- /**
- * Observer class that is attached to each observed
- * object. Once attached, the observer converts the target
- * object's property keys into getter/setters that
- * collect dependencies and dispatch updates.
- */
- export class Observer {
- value: any;
- dep: Dep;
- vmCount: number; // number of vms that have this object as root $data
-
- constructor (value: any) {
- this.value = value
- this.dep = new Dep()
- this.vmCount = 0
- def(value, '__ob__', this)
- if (Array.isArray(value)) {
- if (hasProto) {
- protoAugment(value, arrayMethods)
- } else {
- copyAugment(value, arrayMethods, arrayKeys)
- }
- this.observeArray(value)
- } else {
- this.walk(value)
- }
- }
-
- /**
- * Walk through all properties and convert them into
- * getter/setters. This method should only be called when
- * value type is Object.
- */
- walk (obj: Object) {
- const keys = Object.keys(obj)
- for (let i = 0; i < keys.length; i++) {
- defineReactive(obj, keys[i])
- }
- }
-
- /**
- * Observe a list of Array items.
- */
- observeArray (items: Array<any>) {
- for (let i = 0, l = items.length; i < l; i++) {
- observe(items[i])
- }
- }
- }
第五层:
使用的Object.defineProperty()重新定义对象,给data的每个属性都添加了getter和setter方法。
在get的时候会调用dep.depend;如果是数组,则调用dependArray(对数组里的每个元素进行递归调用dep.depend);
在set的时候会先判断数据是否更新,未更新不做操作,更新则observe(),且dep.notify()
以下是Dep的代码,我们可以将Dep看作一个观察者。
- /**
- * Define a reactive property on an Object.
- */
- export function defineReactive (
- obj: Object,
- key: string,
- val: any,
- customSetter?: ?Function,
- shallow?: boolean
- ) {
- const dep = new Dep()
-
- const property = Object.getOwnPropertyDescriptor(obj, key)
- if (property && property.configurable === false) {
- return
- }
-
- // cater for pre-defined getter/setters
- const getter = property && property.get
- const setter = property && property.set
- if ((!getter || setter) && arguments.length === 2) {
- val = obj[key]
- }
-
- let childOb = !shallow && observe(val)
- Object.defineProperty(obj, key, {
- enumerable: true,
- configurable: true,
- get: function reactiveGetter () {
- const value = getter ? getter.call(obj) : val
- 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
- /* eslint-disable no-self-compare */
- if (newVal === value || (newVal !== newVal && value !== value)) {
- return
- }
- /* eslint-enable no-self-compare */
- if (process.env.NODE_ENV !== 'production' && customSetter) {
- customSetter()
- }
- // #7981: for accessor properties without setter
- if (getter && !setter) return
- if (setter) {
- setter.call(obj, newVal)
- } else {
- val = newVal
- }
- childOb = !shallow && observe(newVal)
- dep.notify()
- }
- })
- }
-
第六层 解释 第五层:
depend方法就是将当前dep的实例添加到对应的Watcher中,
notify方法就是通知所有收集的Wacher进行更新,subs[i].update()
- /* @flow */
-
- import type Watcher from './watcher'
- import { remove } from '../util/index'
- import config from '../config'
-
- let uid = 0
-
- /**
- * A dep is an observable that can have multiple
- * directives subscribing to it.
- */
- export default class Dep {
- static target: ?Watcher;
- id: number;
- subs: Array<Watcher>;
-
- constructor () {
- this.id = uid++
- this.subs = [] //存储所有订阅的Watcher
- }
-
- addSub (sub: Watcher) {
- this.subs.push(sub)
- }
-
- removeSub (sub: Watcher) {
- remove(this.subs, sub)
- }
-
- depend () {
- if (Dep.target) {
- Dep.target.addDep(this)
- }
- }
-
- notify () {
- // stabilize the subscriber list first
- const subs = this.subs.slice()
- if (process.env.NODE_ENV !== 'production' && !config.async) {
- // subs aren't sorted in scheduler if not running async
- // we need to sort them now to make sure they fire in correct
- // order
- subs.sort((a, b) => a.id - b.id)
- }
- for (let i = 0, l = subs.length; i < l; i++) {
- subs[i].update()
- }
- }
- }
-
- // The current target watcher being evaluated.
- // This is globally unique because only one watcher
- // can be evaluated at a time.
- Dep.target = null
- const targetStack = []
-
- export function pushTarget (target: ?Watcher) {
- targetStack.push(target)
- Dep.target = target
- }
-
- export function popTarget () {
- targetStack.pop()
- Dep.target = targetStack[targetStack.length - 1]
- }
第七层.Watcher.js
当解析到模板字符串 {{ }} 时,会默认去 new Watcher 实例。
- /**
- * 每一次的 new Watcher 都是独立的,因此构造器接收的三个参数,虽然名字一样但确实不同的数据,就像是 vm.$watch() 接收的参数一样,
- * @param {*} target 需要监视的对象,当做修改时,他就是
- * @param {*} expression 这个对象中的某个属性,它是一个表达式 比如 obj.a.b.c
- * @param {*} callback 回调函数,需要执行的操作
- */
-
- import Dep from "./Dep";
-
- // 这个 uid 用于对每一个的 Watcher 实例添加唯一的 id
- var uid = 0
-
- // 在这里哪一步算是调用了 get 方法???????,解析到模板的时候
-
- export default class Watcher {
- constructor(target, expression, callback) {
- console.log('我是 Watcher 构造器');
- this.id = uid++;
-
- // 模板字符串中的整个表达式
- this.target = target;
-
- // 通过拆分表达式(对象中的对象...),获得需要 Watch 的那个数据。比如传入的是 a.b.c.d 我们需要监视属性 d,就需要拆分
- this.getter = parsePath(expression) // 有两种方法供使用 parsePath 会返回一个函数;如果用 reduce 方法,那么 getter 就会是一个具体的值,此时一定要修改下边的 get 方法!!!
-
- this.callback = callback
-
- // 调用该方法,进入依赖收集阶段
- this.value = this.get()
- }
- // 当更新 dep 中的依赖项时,会调用每一个 Watcher 实例身上的 update 方法
- update() {
- console.log('我是Watcher实例身上的update方法');
- this.run()
- }
-
- // 进入依赖收集阶段,让全局的 Dep.target 设置为 Watcher 本身
- get(){
- // Webpack 在打包的时候 Dep 是全局唯一的,不管多少个JS 文件在用 dep 的时候,都是这一个文件
- // 因此执行到这里
- console.log(this); // Watcher 实例
- Dep.target = this;
- // debugger;
- const obj = this.target;
-
- var value;
-
- // 防止找不到,用try catch一下,只要能找,就一直找
- try {
- value = this.getter(obj) // 获取需要监视的那个值。这里因为constructor 的时候 this.get() 返回的是一个函数
- } finally {
- Dep.target = null // 清空全局 target 的指向,同时也表示退出依赖收集阶段
- }
- return value
- }
-
- // 其实可以直接 getAndInvoke,但是 Vue 源码时这样写的
- run(){
- this.getAndInvoke(this.callback)
- }
-
- //
- getAndInvoke(callback){
- // 获取到修改后的新值 旧值是 this.value
- const value = this.get()
- if(value !== this.value || typeof value == 'object'){
- const oldValue = this.value;
- this.value = value;
- callback.call(this.target, value, oldValue)
- }
- }
- }
-
-
-
-
-
- // 拆分表达式:
- // 方法一:将 str 用 . 分割成数组 segments,然后循环数组,一层一层去读取数据,最后拿到的 obj 就是 str 中想要读的数据
- // 假设 let o = {a:{b:{c:{d:55}}}},我想要取得 d 的值,经过拆分后的 segments 数组的值为 ['a', 'b', 'c', 'd']
- // 第一次循环后 obj = {b:{c:{d:55}}}, 第二次 obj = {c:{d:55}}, 第三次 obj = {d:55}, 第四次 obj = 55
- function parsePath(str) {
- let segments = str.split(".");
- return function (obj) {
- for (let key of segments) {
- if (!obj) return; // 当没有传入 obj 时,直接 return
- obj = obj[key];
- }
- return obj;
- };
- }
- // 方法二 用 reduce 方法实现
- // function parsePathReduce(str) {
- // let segments = str.split(".");
- // let result = segments.reduce((total, item) => {
- // total = total[item]
- // return total
- // }, str)
- // return result
- // }
首先要了解三个最重要的对象:
Observer 对象:将 Vue 中的数据对象在初始化过程中转换为 Observer 对象。
Watcher 对象:将模板和 Observer 对象结合在一起生成 Watcher 实例,Watcher 是订阅者中的订阅者。
Dep对象:Watcher 对象和 Observer 对象之间纽带,每一个 Observe r都有一个 Dep 实例,用来存储订阅者 Watcher。
缺陷:只能够监听初始化实例中的 data 数据,动态添加值不能响应,要使用对应的 Vue.set()。
- // 数组方法重写
- let oldArrayPrototy = Array.prototype
- // 使用Object.create 将数组原型上的方法放到newArrayPrototy.prototype上
- let newArrayPrototy = Object.create(arrayFn)
- // 需要重写的数组方法列表
- let method = [
- 'push',
- 'pop',
- 'shift',
- 'unshift',
- 'reverse',
- 'sort',
- 'splice'
- ]
- method.forEach((item) => {
- //newArrayPrototy[item] 就是 arr.某一个方法
- newArrayPrototy[item] = function (...args) {
- // 关键部分
- let result = oldArrayPrototy[item].call(this, ...args)
- // 对传进来的数据做一些处理
- let insterted
- switch (item) {
- case 'push':
- case 'unshift':
- insterted = args
- case 'splice':
- insterted = args.slice(2)
- default:
- break
- }
- return result
- }
- })
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。