【vue】响应式(object.defineProperty)、可配置的参数、vue渲染机制

server/2024/12/2 18:16:56/

Vue 2的响应式原理主要是基于Object.defineProperty来实现的。

  1. 数据劫持

    • 当一个Vue实例被创建时,它会遍历data选项中的所有属性。对于每个属性,使用Object.defineProperty来进行数据劫持。这个方法允许精确地定义一个对象的属性,包括属性的值、可枚举性、可配置性和可写性。
    • 重点是定义了属性的getset函数。get函数用于获取属性的值,在这个函数内部可以进行一些依赖收集的操作。例如,当模板中使用了这个属性,就会在get被调用时,将这个模板中的使用点(watcher)收集起来。set函数用于设置属性的值,当属性的值被修改时,set函数会被触发。在set函数内部,会通知之前收集到的所有依赖(watcher)进行更新,从而实现数据变化到视图更新的过程。
  2. 依赖收集和通知更新(Watcher和Dep)

    • Dep(Dependency):可以看作是一个依赖收集器,是一个发布者。它主要用于收集依赖(watcher),在数据劫持的get中,会将当前的watcher添加到Dep实例中。每个响应式数据都会有一个对应的Dep实例。
    • Watcher:是一个订阅者,主要用于观察数据的变化。它可以是一个组件渲染的观察者,也可以是一个用户自定义的计算属性的观察者等。当数据发生变化时,Dep会通知所有订阅了它的WatcherWatcher收到通知后,会执行相应的更新操作,如更新组件的DOM节点,重新计算计算属性的值等。

简单来说,当数据发生变化时,通过set触发Dep发布消息,Watcher收到消息后更新视图,而视图中的数据绑定表达式被解析时,会触发get进行依赖收集,这就实现了数据和视图之间的响应式绑定。

Object.defineProperty()方法中,用于定义对象属性时可以配置以下参数:

  1. value(可选)
    • 这是属性的值。例如:
javascript">let obj = {};
Object.defineProperty(obj, 'name', {value: 'John'
});
console.log(obj.name); // 输出:John
  • 如果没有提供value参数,并且在对象中该属性不存在,那么这个属性的值将是undefined
  1. writable(可选)
    • 它是一个布尔值,用于确定属性的值是否可以被修改。默认值是false。例如:
javascript">let obj = {};
Object.defineProperty(obj, 'age', {value: 30,writable: true
});
obj.age = 31;
console.log(obj.age); // 输出:31
  • 如果writablefalse,尝试修改属性的值将会在严格模式下抛出错误,在非严格模式下修改操作会被忽略。
  1. enumerable(可选)
    • 这也是一个布尔值,用于决定属性是否可以被枚举,比如在for...in循环或者Object.keys()方法中是否会出现。默认值是false。例如:
javascript">let obj = {};
Object.defineProperty(obj, 'city', {value: 'New York',enumerable: true
});
for (let key in obj) {console.log(key); // 输出:city
}
  • 如果enumerablefalse,该属性在上述枚举操作中不会出现,但仍然可以通过直接访问属性名来获取其值。
  1. configurable(可选)
    • 同样是布尔值,它决定了这个属性是否可以被删除,以及是否可以重新配置属性的其他特性(除了valuewritable被设置为false后的情况)。默认值是false。例如:
javascript">let obj = {};
Object.defineProperty(obj, 'country', {value: 'USA',configurable: true
});
delete obj.country;
console.log(obj.country); // 输出:undefined
  • 如果configurablefalse,尝试删除属性或者重新配置其特性(如改变enumerable等)将会在严格模式下抛出错误,在非严格模式下操作会被忽略。

以下是一些Vue上层相关的底层面试题:

一、关于Vue实例

  1. Vue实例的生命周期钩子函数
    • 问题:请简述Vue实例的生命周期钩子函数有哪些,以及它们分别在什么时候被调用?
    • 答案:
      • beforeCreate:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。此时,datamethodscomputed等都还不可用。
      • created:在实例创建完成后被调用。此时实例已完成以下的配置:数据观测 (data observer)、属性和方法的运算、watch/event 事件回调。然而,挂载阶段还没开始,$el 不可用。
      • beforeMount:在挂载开始之前被调用。相关的 render 函数首次被调用。
      • mountedel 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。一般在此进行DOM操作的初始化,例如获取元素的高度、宽度等操作,因为此时DOM已经渲染完成。
      • beforeUpdate:数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前。此时DOM还没更新。
      • updated:由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时DOM已经更新。
      • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
      • destroyed:Vue实例销毁后调用。此时,Vue实例的所有指令都被解绑,所有的事件监听器被移除,子实例也被销毁。
  2. Vue实例的数据代理原理
    • 问题:请解释Vue是如何实现数据代理的?
    • 答案:
      • Vue通过Object.defineProperty()方法来实现数据代理。在Vue实例创建时,它会遍历data选项中的属性。
      • 对于每个属性,它会在Vue实例上定义一个同名的属性(如this.foo对应data中的foo属性)。
      • 这个同名属性的getset访问器函数被定义为读取和修改data中对应属性的值。当读取this.foo时,实际上是调用datafoo属性的get访问器;当修改this.foo时,是调用datafoo属性的set访问器。

二、关于Vue组件

  1. 组件通信方式
    • 问题:Vue组件有哪些通信方式?请举例说明。
    • 答案:
      • 父子组件通信
        • 父传子(props):父组件通过props向子组件传递数据。例如,父组件有一个message属性,在子组件中通过props: ['message']接收,然后在子组件的模板中可以使用{{message}}显示。
        • **子传父( e m i t ) ∗ ∗ :子组件通过 ‘ emit)**:子组件通过` emit:子组件通过emit触发事件向父组件传递数据。例如,子组件中有一个按钮,点击按钮时this.$emit(‘child - event’, data),父组件通过@child - event="parentMethod"监听这个事件,并在parentMethod`方法中接收子组件传递的数据。
      • 非父子组件通信(兄弟组件或跨多层级组件通信)
        • 中央事件总线(Event Bus):创建一个Vue实例作为事件总线。例如,var bus = new Vue()。一个组件可以通过bus.$emit('event - name', data)发送事件,其他组件通过bus.$on('event - name', callback)接收事件和数据。
        • Vuex:对于大型项目中复杂的状态管理和组件通信,使用Vuex。它有state(存储状态)、mutations(修改状态的方法)、actions(异步操作)和getters(获取状态的派生数据)。组件可以通过this.$store.state访问状态,通过this.$store.commit('mutation - name', data)提交修改状态的操作等。
  2. 组件的插槽(Slots)
    • 问题:请解释Vue组件中的插槽有哪些类型,以及它们的作用?
    • 答案:
      • 默认插槽(单个插槽)
        • 当在父组件中使用子组件时,如果在子组件标签内放置内容,这些内容会被渲染到子组件中的<slot>标签所在位置。例如,子组件ChildComponent中有一个<slot></slot>,父组件<ChildComponent>这是父组件传递的内容</ChildComponent>,“这是父组件传递的内容”就会被渲染到子组件的<slot>处。
      • 具名插槽
        • 当子组件有多个不同位置需要父组件填充内容时,可以使用具名插槽。在子组件中定义<slot name="header"></slot><slot name="footer"></slot>等。父组件使用<template v - slot:header><template v - slot:footer>来分别向对应的具名插槽传递内容。
      • 作用域插槽
        • 作用域插槽用于让父组件能够访问子组件中的数据。子组件在<slot>标签上绑定数据,如<slot :data="childData"></slot>。父组件通过<template v - slot="slotProps">slotProps是自定义的变量名)来接收子组件传递的数据,并在父组件中可以根据这些数据进行渲染,如{{slotProps.childData}}

三、关于Vue的渲染机制(除了Diff算法相关)

  1. 模板编译过程
    • 问题:简述Vue模板的编译过程。
    • 答案:
      • 解析(parse):将模板字符串解析成抽象语法树(AST)。这个过程会把模板中的HTML标签、指令、插值表达式等解析成JavaScript对象的形式。例如,模板<div>{{message}}</div>会被解析成一个包含tag: 'div'text: nullchildren: [ { type: 2, expression:'message', text: '{{message}}' } ]等属性的AST对象。
      • 优化(optimize):对解析后的AST进行优化,标记静态节点。静态节点是指在组件的生命周期内不会改变的节点,标记静态节点可以在后续的虚拟DOM更新过程中跳过这些节点的比较,提高性能。
      • 生成(generate):将优化后的AST转换成渲染函数(render函数)。渲染函数返回虚拟DOM(VNode),例如with(this){return _c('div',[_v(_s(message))])},其中_c_v_s等是Vue内部的渲染函数助手,用于创建VNode、创建文本节点、将数据转换为文本等操作。
  2. 渲染函数(Render Function)与模板的区别和联系
    • 问题:请说明Vue中渲染函数与模板的区别和联系,以及在什么情况下会选择使用渲染函数?
    • 答案:
      • 区别
        • 语法形式:模板是基于HTML的语法,通过指令(如v - ifv - for等)来添加逻辑;渲染函数是纯JavaScript函数,通过调用Vue内部的渲染函数助手来创建虚拟DOM。
        • 灵活性:渲染函数比模板更灵活。模板有一定的语法限制,而渲染函数可以实现更复杂的逻辑和渲染效果。例如,在模板中很难实现根据不同条件动态生成不同的HTML结构,但在渲染函数中可以通过JavaScript的条件判断来实现。
      • 联系
        • 模板最终会被编译成渲染函数。无论是使用模板还是直接编写渲染函数,目的都是生成虚拟DOM来渲染页面。
      • 选择使用渲染函数的情况
        • 当需要实现高度定制化的组件,模板的语法无法满足需求时,例如需要根据复杂的业务逻辑动态生成DOM结构。
        • 当需要对组件的渲染性能进行极致优化时,因为渲染函数可以更精确地控制虚拟DOM的生成和更新,通过优化渲染函数可以减少不必要的DOM操作。

http://www.ppmy.cn/server/146797.html

相关文章

Linux驱动开发第3步_INPUT子系统框架下的外部中断

1、了解WKUP唤醒引脚 1)、打开STM32MP157数据手册&#xff0c;搜索“WKUP”&#xff0c;见下表&#xff1a; 由表可知&#xff0c;PA0&#xff0c;PA2&#xff0c;PC13&#xff0c;PI8&#xff0c;PI11和PC1均具有唤醒功能。 2)、打开STM32MP157参考手册&#xff0c;了解GIC中…

Linux——自定义简单shell

shell 自定义shell目标普通命令和内建命令&#xff08;补充&#xff09; shell实现实现原理实现代码 自定义shell 目标 能处理普通命令能处理内建命令要能帮助我们理解内建命令/本地变量/环境变量这些概念理解shell的运行 普通命令和内建命令&#xff08;补充&#xff09; …

将面具贴到人脸上的过程

使用OpenCV进行人脸面具贴合和变形以适应人脸的3D透视角度&#xff0c;通常需要以下步骤&#xff1a; 人脸检测&#xff1a;首先需要检测图像中的人脸位置。特征点检测&#xff1a;在检测到的人脸区域中&#xff0c;找到关键特征点&#xff0c;如眼睛、鼻子、嘴巴等。透视变换…

git push使用

推送指定分支 将当前分支推送远程 git push origin HEAD:<branch-name> 这里的 HEAD 是一个特殊的指针&#xff0c;它指向当前分支的最新提交。这条命令会将当前分支的更改推送到远程的 master 分支。 示例 git push origin HEAD:main 当前分支是test&#xff0c;远…

二维码有哪些网络安全风险隐患?

中国网民规模达10亿人&#xff0c;每个人几乎在智能手机用户在其移动设备上使用过二维码。随着人们越来越习惯使用二维码&#xff0c;黑客开始使用它们来寻找另一种窃取凭证和访问敏感信息的方法。 国际知名网络安全专家、东方联盟创始人郭盛华透露&#xff1a;“黑客针对企业的…

OODA循环在网络安全运营平台建设中的应用

OODA循环最早用于信息战领域&#xff0c;在空对空武装冲突敌对双方互相较量时&#xff0c;看谁能更快更好地完成“观察—调整—决策—行动”的循环程序。 双方都从观察开始&#xff0c;观察自己、观察环境和敌人。基于观察&#xff0c;获取相关的外部信息&#xff0c;根据感知…

Vue入门级教程二:组件化开发

在Vue 入门级教程一中&#xff0c;我们已经对 Vue.js 的基础概念、数据绑定和指令有了初步的了解。本教程将在此基础上&#xff0c;进一步深入探讨 Vue 的组件化开发、事件处理以及计算属性等重要特性&#xff0c;帮助你更全面地掌握 Vue 框架&#xff0c;构建出功能更为丰富的…

【C语言】结构体嵌套

结构体嵌套是指在一个结构体中定义另一个结构体作为其成员。这种方式可以实现更复杂的数据结构设计&#xff0c;便于对数据进行分层管理和组织&#xff0c;广泛应用于实际开发中&#xff0c;例如操作系统内核、嵌入式系统、网络协议解析等。下面是对结构体嵌套的详细介绍&#…