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())
    }
  }
}

结束

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


目录
相关文章
|
15天前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
35 3
|
23天前
|
JavaScript
Vue 的响应式原理中 Object.defineProperty 有什么缺陷
Vue 的响应式原理主要依赖于 `Object.defineProperty`,但该方法存在一些缺陷:无法检测到对象属性的添加和删除,且对大量数据进行代理时性能较差。Vue 3 中改用了 Proxy 来解决这些问题。
|
3月前
|
XML 缓存 API
【Azure API 管理】使用APIM进行XML内容读取时遇见的诡异错误 Expression evaluation failed. Object reference not set to an instance of an object.
【Azure API 管理】使用APIM进行XML内容读取时遇见的诡异错误 Expression evaluation failed. Object reference not set to an instance of an object.
|
4月前
|
SQL 安全 Java
Android经典面试题之Kotlin中object关键字实现的是什么类型的单例模式?原理是什么?怎么实现双重检验锁单例模式?
Kotlin 单例模式概览 在 Kotlin 中,`object` 关键字轻松实现单例,提供线程安全的“饿汉式”单例。例如: 要延迟初始化,可使用 `companion object` 和 `lazy` 委托: 对于参数化的线程安全单例,结合 `@Volatile` 和 `synchronized`
53 6
|
5月前
|
存储 Java 索引
JavaSE——集合框架一(6/7)-Set系列集合:LinkedHashSet的底层原理、TreeSet集合(介绍,自定义排序规则,排序示例)
JavaSE——集合框架一(6/7)-Set系列集合:LinkedHashSet的底层原理、TreeSet集合(介绍,自定义排序规则,排序示例)
42 1
|
5月前
|
存储 Java 索引
JavaSE——集合框架一(5/7)-Set系列集合:Set集合的特点、底层原理、哈希表、去重复原理
JavaSE——集合框架一(5/7)-Set系列集合:Set集合的特点、底层原理、哈希表、去重复原理
50 1
|
6月前
|
前端开发 容器
使用 object-fit 属性完美过渡图片
这篇文章介绍了CSS属性object-fit的用法。object-fit属性用于指定元素的内容如何适应指定容器的高度和宽度。该属性一般适用于img和video标签,可以进行剪切、缩放或拉伸操作。文章中展示了通过object-fit属性来统一设置多张图片的样式,保持原始比例并改变显示位置的示例,以及使用object-position属性实现简单的过渡效果。
使用 object-fit 属性完美过渡图片
|
6月前
|
存储 C++ 容器
【STL】map和set的原理及其使用
【STL】map和set的原理及其使用
|
6月前
|
搜索推荐 算法 数据挖掘
探索 Elasticsearch 8.X Terms Set 检索的应用与原理
探索 Elasticsearch 8.X Terms Set 检索的应用与原理
82 0
|
6月前
|
存储 NoSQL 关系型数据库
Redis Sorted Set 底层实现原理深度解读与排行榜实战
Redis Sorted Set 底层实现原理深度解读与排行榜实战
96 0