vue2中$set的原理_它对object属性做了啥?

简介: vue2中$set的原理_它对object属性做了啥?

背景

动态修改对象的属性时,vue2视图template不会跟新渲染的数据,来一探究竟

动态添加属性,不使用$set

测试案例:

config属性被我动态添加name:yma16

结论:

如果template动态添加则说明对象的属性添加删除具有响应式

<template>
  <div class="container">
    <h1>{{ msg }}</h1>
    <span>
      config渲染:
    </span>
    <span>
      {{ config }}
    </span>
    <hr />
    <div style="width: 600px;margin: 0 auto">
      <span>
        添加config属性
      </span>
      <el-form label-width="80px" :model="form" ref="formRef">
        <el-form-item
          label="key"
          :rules="{ required: true, message: 'key不能为空!', trigger: 'blur' }"
          prop="key"
        >
          <el-input v-model="form.key" placeholder="请输入key"></el-input>
        </el-form-item>
        <el-form-item label="value">
          <el-input v-model="form.value" placeholder="请输入value"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="submit">
            添加保存
          </el-button>
        </el-form-item>
      </el-form>
    </div>
  </div>
</template>
<script>
export default {
  name: 'Dashboard',
  data () {
    return {
      msg: '$set用法',
      config: {
        key: 'value'
      },
      form: {
        key: '',
        value: ''
      }
    }
  },
  methods: {
    submit () {
      this.$refs['formRef'].validate().then(valid => {
        console.log('valid', valid)
        if (valid) {
          this.config[this.form.key] = this.form.value
          this.$message.success('添加属性成功!')
          console.log('this.config', this.config)
        } else {
          this.$message.warning('请检查配置!')
        }
      })
    }
  }
}
</script>

效果

如下图所示。

结论:

vue2动态添加对象属性不具有响应式

动态添加属性,使用$set

改动点:

submit使用$set添加替换属性

<template>
  <div class="container">
    <h1>{{ msg }}</h1>
    <span>
      config渲染:
    </span>
    <span>
      {{ config }}
    </span>
    <hr />
    <div style="width: 600px;margin: 0 auto">
      <span>
        添加config属性
      </span>
      <el-form label-width="80px" :model="form" ref="formRef">
        <el-form-item
          label="key"
          :rules="{ required: true, message: 'key不能为空!', trigger: 'blur' }"
          prop="key"
        >
          <el-input v-model="form.key" placeholder="请输入key"></el-input>
        </el-form-item>
        <el-form-item label="value">
          <el-input v-model="form.value" placeholder="请输入value"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="submit">
            添加保存
          </el-button>
        </el-form-item>
      </el-form>
    </div>
  </div>
</template>
<script>
export default {
  name: 'Dashboard',
  data () {
    return {
      msg: '$set用法',
      config: {
        key: 'value'
      },
      form: {
        key: '',
        value: ''
      }
    }
  },
  methods: {
    submit () {
      this.$refs['formRef'].validate().then(valid => {
        console.log('valid', valid)
        if (valid) {
          this.$set(this.config, this.form.key, this.form.value)
          this.$message.success('添加属性成功!')
          console.log('this.config', this.config)
        } else {
          this.$message.warning('请检查配置!')
        }
      })
    }
  }
}
</script>

效果

结论:

vue2动态添加对象属性使用$set可以让对象具有响应式渲染

$set做了啥?

对象defineProperty

给对象添加属性

var myObject = {};
Object.defineProperty( myObject, "a", {
value: 2,
writable: false, // 不可写!
configurable: true,
enumerable: true
} );
myObject.a = 3;
myObject.a; // 2

对象的Getter和Setter

object获取和配置属性(用get达实现监听)

定义新属性b设置为a的2倍,达到监听变量a变化的效果

当a变量地址改变时属性b也会改变

var myObject = {
// 给 a 定义一个 getter
get a() {
return 2;
}
};
Object.defineProperty(
myObject, 
"b", 
{
get: function(){ return this.a * 2 },
enumerable: true
}
);
myObject.a; // 2
myObject.b; // 4

$set的源码

vue的原型链挂了set

set里面定义属性且可枚举

使用了枚举enumerable属性和注册configurable属性

/**
 * Define a reactive property on an Object.
 */
export function defineReactive(
  obj: object,
  key: string,
  val?: any,
  customSetter?: Function | null,
  shallow?: boolean,
  mock?: 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) &&
    (val === NO_INIITIAL_VALUE || arguments.length === 2)
  ) {
    val = obj[key]
  }
  let childOb = !shallow && observe(val, false, mock)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        if (__DEV__) {
          dep.depend({
            target: obj,
            type: TrackOpTypes.GET,
            key
          })
        } else {
          dep.depend()
        }
        if (childOb) {
          childOb.dep.depend()
          if (isArray(value)) {
            dependArray(value)
          }
        }
      }
      return isRef(value) && !shallow ? value.value : value
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val
      if (!hasChanged(value, newVal)) {
        return
      }
      if (__DEV__ && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else if (getter) {
        // #7981: for accessor properties without setter
        return
      } else if (!shallow && isRef(value) && !isRef(newVal)) {
        value.value = newVal
        return
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal, false, mock)
      if (__DEV__) {
        dep.notify({
          type: TriggerOpTypes.SET,
          target: obj,
          key,
          newValue: newVal,
          oldValue: value
        })
      } else {
        dep.notify()
      }
    }
  })
  return dep
}

重点

当一个数据为响应式时,vue会给该数据添加一个__ob__属性,因此可以通过判断target对象是否存在__ob__属性来判断target是否是响应式数据

dep.notify({
          type: TriggerOpTypes.SET,
          target: obj,
          key,
          newValue: newVal,
          oldValue: value
        })
      } else {
        dep.notify()
      }

当target对象是响应式数据时,我们将target的属性key也设置为响应式并手动触发通知其属性值的更新

defineReactive(ob.value, key, val)
ob.dep.notify()

notify 触发update更新

承接$set的notify

notify(info?: DebuggerEventExtraInfo) {
    // stabilize the subscriber list first
    const subs = this.subs.filter(s => s) as DepTarget[]
    if (__DEV__ && !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++) {
      const sub = subs[i]
      if (__DEV__ && info) {
        sub.onTrigger &&
          sub.onTrigger({
            effect: subs[i],
            ...info
          })
      }
      sub.update()
    }
  }

update 继承 DebuggerOptions

/**
 * @internal
 */
export interface DepTarget extends DebuggerOptions {
  id: number
  addDep(dep: Dep): void
  update(): void
}

onTrack和onTrigger

watch update

_update更新渲染节点

function _update(oldVnode, vnode) {
  const isCreate = oldVnode === emptyNode
  const isDestroy = vnode === emptyNode
  const oldDirs = normalizeDirectives(
    oldVnode.data.directives,
    oldVnode.context
  )
  const newDirs = normalizeDirectives(vnode.data.directives, vnode.context)
  const dirsWithInsert: any[] = []
  const dirsWithPostpatch: any[] = []
  let key, oldDir, dir
  for (key in newDirs) {
    oldDir = oldDirs[key]
    dir = newDirs[key]
    if (!oldDir) {
      // new directive, bind
      callHook(dir, 'bind', vnode, oldVnode)
      if (dir.def && dir.def.inserted) {
        dirsWithInsert.push(dir)
      }
    } else {
      // existing directive, update
      dir.oldValue = oldDir.value
      dir.oldArg = oldDir.arg
      // update 更新 vnode
      callHook(dir, 'update', vnode, oldVnode)
      if (dir.def && dir.def.componentUpdated) {
        dirsWithPostpatch.push(dir)
      }
    }
  }
  if (dirsWithInsert.length) {
    const callInsert = () => {
      for (let i = 0; i < dirsWithInsert.length; i++) {
        callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
      }
    }
    if (isCreate) {
      mergeVNodeHook(vnode, 'insert', callInsert)
    } else {
      callInsert()
    }
  }
  if (dirsWithPostpatch.length) {
    mergeVNodeHook(vnode, 'postpatch', () => {
      for (let i = 0; i < dirsWithPostpatch.length; i++) {
        callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
      }
    })
  }
  if (!isCreate) {
    for (key in oldDirs) {
      if (!newDirs[key]) {
        // no longer present, unbind
        callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
      }
    }
  }
}

update的调用地方太多

找了一个监听的update

/**
 * @internal since we are not exposing this in Vue 2, it's used only for
 * internal testing.
 */
export function effect(fn: () => any, scheduler?: (cb: any) => void) {
  const watcher = new Watcher(currentInstance, fn, noop, {
    sync: true
  })
  if (scheduler) {
    watcher.update = () => {
      scheduler(() => watcher.run())
    }
  }
}

结束

感谢阅读,如有不足或者错误欢迎指正!


目录
相关文章
|
6天前
|
搜索推荐 算法 数据挖掘
探索 Elasticsearch 8.X Terms Set 检索的应用与原理
探索 Elasticsearch 8.X Terms Set 检索的应用与原理
14 0
|
6天前
|
存储 NoSQL 关系型数据库
Redis Sorted Set 底层实现原理深度解读与排行榜实战
Redis Sorted Set 底层实现原理深度解读与排行榜实战
70 0
|
6天前
|
存储 监控 NoSQL
搞定 Redis 数据存储原理,别只会 set、get 了
搞定 Redis 数据存储原理,别只会 set、get 了
47 0
|
6天前
|
JavaScript 索引
Vue.set 方法原理
Vue.set 方法原理
14 0
|
6天前
|
监控 JavaScript 前端开发
【面试题】vue2双向绑定原理:深入响应式原理defineProperty、watcher、get、set
【面试题】vue2双向绑定原理:深入响应式原理defineProperty、watcher、get、set
|
9月前
|
前端开发
前端学习笔记202306学习笔记第四十一天-Es6-object.defineProperty监听属性的访问和设置1
前端学习笔记202306学习笔记第四十一天-Es6-object.defineProperty监听属性的访问和设置1
27 0
|
6天前
|
JavaScript
使用set和emit在uni-app中实现响应式属性和自定义事件
使用set和emit在uni-app中实现响应式属性和自定义事件
38 0
|
9月前
|
前端开发
前端学习笔记202306学习笔记第四十一天-Es6-object.defineProperty监听属性的访问和设置4
前端学习笔记202306学习笔记第四十一天-Es6-object.defineProperty监听属性的访问和设置4
29 0
|
9月前
|
存储 缓存 NoSQL
【Redis】集合(Hash、List、Set、ZSet)的底层实现原理
【Redis】集合(Hash、List、Set、ZSet)的底层实现原理
|
10月前
|
存储 编解码 NoSQL
三.Redis中那些你不知道的秘密-五大基本结构set的实现原理
Redis中的set和java中的set集合有相似之处,它的元素不会按照插入的向后顺序而存储,且元素是不允许重复的。set内部使用到了intset(整数集合)和hashtable(哈希表)两种方式来存储元素,如果set存储的元素是整数,且当元素个数小于512个会选择intset存储,目的是减少内存空间,遇到两种情况会发生变化,就是当存储的元素个数达到512(通过set-max-intset-entries 配置)或者添加了非整数值时如:‘b’,set会选择hashtable作为存储结构。

热门文章

最新文章