【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(可选)
    • 这是属性的值。例如:
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操作。
相关推荐
浪浪山小白兔几秒前
HTML5 常用事件详解
前端·html·html5
Python大数据分析@17 分钟前
通俗的讲,网络爬虫到底是什么?
前端·爬虫·网络爬虫
不爱学英文的码字机器35 分钟前
[操作系统] 环境变量详解
开发语言·javascript·ecmascript
Lysun00139 分钟前
vue2的$el.querySelector在vue3中怎么写
前端·javascript·vue.js
毛毛三由39 分钟前
【组件分享】商品列表组件-最佳实践
vue.js
jerry-891 小时前
Centos类型服务器等保测评整/etc/pam.d/system-auth
java·前端·github
工业甲酰苯胺1 小时前
深入解析 Spring AI 系列:解析返回参数处理
javascript·windows·spring
小爬菜1 小时前
Django学习笔记(启动项目)-03
前端·笔记·python·学习·django
想要打 Acm 的小周同学呀1 小时前
前端Vue2项目使用md编辑器
前端·编辑器·vue2·markdown 语法
计算机-秋大田1 小时前
基于SSM的家庭记账本小程序设计与实现(LW+源码+讲解)
java·前端·后端·微信小程序·小程序·课程设计