当前位置: 首页 > article >正文

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

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(可选)
    • 这是属性的值。例如:
let obj = {};
Object.defineProperty(obj, 'name', {
    value: 'John'
});
console.log(obj.name); // 输出:John
  • 如果没有提供value参数,并且在对象中该属性不存在,那么这个属性的值将是undefined
  1. writable(可选)
    • 它是一个布尔值,用于确定属性的值是否可以被修改。默认值是false。例如:
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。例如:
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。例如:
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.kler.cn/a/419837.html

相关文章:

  • 【时时三省】(C语言基础)结构体的自引用
  • 【AI系统】TVM 实践案例
  • 行为型设计模式之《责任链模式》实践
  • debian 11 虚拟机环境搭建过坑记录
  • [每周一更]-(第125期):模拟面试|NoSQL面试思路解析
  • Linux 进程管理详解
  • 华为HarmonyOS 让应用快速拥有账号能力 - 获取用户手机号
  • yolo11经验教训----之一
  • QT的槽函数的四种写法
  • ME6210:常用在个人通信设备电源里的低静态、低压差线性稳压器
  • @antv/x6 再vue中 ,自定义图形,画流程图、数据建模、er图等图形
  • linux网络抓包工具
  • 网际协议(IP)与其三大配套协议(ARP、ICMP、IGMP)
  • 【在Linux世界中追寻伟大的One Piece】多线程(三)
  • 为什么编程语言会设计不可变的对象?字符串不可变?NSString *s = @“hello“变量s是不可变的吗?Rust内部可变性的意义?
  • 源码分析之Openlayers中的Collection类
  • Web开发基础学习——HTML中\<div>元素的理解
  • arkTS:使用ArkUI实现用户信息的持久化管理与自动填充(PersistentStorage)
  • Java 面经之 Spring
  • 【Git系列】Git 提交记录过滤:排除特定关键词的实用指南
  • 【MySQL-6】MySQL的复合查询
  • 动态代理如何加强线上安全
  • 云服务器架构有什么区别?X86计算、Arm、GPU/FPGA/ASIC和裸金属全解析
  • 2024年通信网络与软件工程国际学术会议(ICCNSE 2024)
  • 嵌入式Linux之wifi配网脚本分析
  • 排序算法中稳定性的意义和作用