Vue、React

文章目录

Vue运行时版本和完整版

完整版有编译器,运行时没有编译器。

  • 完整版
    完整版同时包含编译器和运行时的版本。编译器用来将模板字符串编译成为JavaScript渲染函数的代码,但是它占了Vue完整版30%的体积。
    完整版你可以直接传入一个字符给template选项,或者挂载到一个元素上,并以其DOM内部的HTML作为模板。
    new Vue({
    template: ‘
    {{ hi }}

    })
  • 运行时版 运行时版引入vue.runtime.js,
    运行时版以牺牲编译器为代价,减少了自身的大小。但是该版本不能直接编译模板,需要使用如下的方式,挂载元素:
    但是当你使用vue-loader的时候,.vue文件内部的模板会在构建时预编译成 JavaScript。在最终打好的包里实际上是不需要编译器的
    new Vue({
    render (h) {
    return h(‘div’, this.hi)
    }
    })
    vue-loader作用:解析和转换 .vue 文件,提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template,
    .vue文件内部的模板会在构建时预编译成 JavaScript。再分别把它们交给对应的 Loader 去处理。在最终打好的包里实际上是不需要编译器的

Vue响应式原理

响应式主要做三件事:

  • 监听数据的变化,数据劫持
  • 收集数据的依赖
  • 但数据发生变化的时候更新渲染视图
    那在vue中是使用Object.defineProperty(vue3:Proxy)来进行数据劫持,也就是通过设定对象的getter/setter来监听数据的更新和获取,也就是observe.
    然后是收集依赖,使用Dep类,目的在于当数据的属性发生变化时,可以通知那些曾经使用了该数据的地方
    观察者 Watcher
  • 在 new Vue() 后, Vue 会调用_init 函数进行初始化,也就是init 过程,在 这个过程Data通过Observer转换成了getter/setter的形式,具体使用Obeject.defineProperty(vue3:Proxy),来对数据追踪变化,当被设置的对象被读取的时候会执行getter 函数,而在当被赋值的时候会执行 setter函数。
  • 当外界通过Watcher读取数据时,会触发getter从而将Watcher添加到依赖中。
  • 在修改对象的值的时候,会触发对应的setter, setter通知之前依赖收集得到的 Dep 中的每一个 Watcher,告诉它们自己的值改变了,需要重新渲染视图。这时候这些 Watcher就会开始调用 update 来更新视图。

Vue双向数据绑定

vue是通过数据劫持发布订阅者模式来实现双向数据绑定的
就是利用oberver来监听module数据的变化,通过compile来解析模板,最终通过watcher来建立oberver和module之间的通信桥梁
创建一个vue对象,分为el模板和data数据

  • 使用Object.definePropertype来劫持data中的每个属性的getter和setter,在数据发生变化的时候发布消息给订阅者,触发相应的回调。(vue3使用proxy劫持数据)也就是observer的功能
  • compile 解析模板,将模板中的变量替换成数据,然后动态渲染页面, 将双向绑定的指令对应的节点添加更新函数,添加监听数据的订阅者,一旦数据有变化,就更新视图
  • Watcher订阅者是Observer和Compile之间通信的桥梁,他负责往dep里添加订阅者,当收到数据变化的通知时,触发compile的回调,更新视图
    (vue中就是每当有这样的可能用到双向绑定的指令,就在一个Dep中增加一个订阅者)
    Vue2:https://blog.csdn.net/lq313131/article/details/126276260

<body>
    name: <span id="spanName"></span>
    <br>
    <input type="text" id="inputName">
</body>
<script>
    var span = document.getElementById('spanName')
    var input = document.getElementById('inputName')
    var obj = {
        name: ''
    }
    使用proxy数据劫持
    obj = new Proxy(obj, {
        get(target, prop) {
            return target[prop]
        },
        set(target, prop, value) {
            target[prop] = value   
            observer()    //当数据有更新,改变页面的内容
        }
    })
    function observer() {
        span.innerHTML = obj.name
        input.value = obj.name
    }

    input.oninput = function () {
        obj.name = input.value    //页面上的输入有变化,改变obj的数据
    }
</script>

new Vue()的过程,vue内部干了什么?(详细的,源码层面的)init()做了什么?流程是啥?

[图片]

new Vue的时候

  • 首先会初始化方法 ,调用自身的_init方法,

    • _init方法主要做:初始化自身实例的option和外部的options,然后合并options
    • 初始化生命周期 initLifecycle(vm);
    • 初始化事件 initEvents(vm);
    • 初始化渲染函数initRender(vm);
    • 回调 beforeCreate 钩子函数, callHook(vm, ‘beforeCreate’);
    • 初始化 vm(vue实例) 的状态 initState(vm);
    • vm 已经创建好 回调 created 钩子函数,callHook(vm, ‘created’);
    • 调用挂载实例的函数 vm. m o u n t ( v m . mount(vm. mount(vm.options.el);
      [图片]
  • vm. m o u n t ( v m . mount(vm. mount(vm.options.el);方法主要:

    • 先判断options中是否有render,再判断是否有template,然后,通过 compileToFunctions 将HTML模板编译成可以生成VNode的Render函数。
      (compileToFunctions 主要做:)
      • 获取实例的template,将template解析成ast语法树。
      • 标记静态节点,以便后续diff
      • 通过ast语法树生成render函数。
    • 调用 mount.call(this, el, hydrating){return mountComponent(this, el, hydrating) //vm._watcher 赋值}
  • mountComponent(this, el, hydrating)方法主要

    • 调用beforeMount钩子,callHook(vm, ‘beforeMount’);
    • 渲染DOMvm._update(vm._render(), hydrating);
    • 生成中间件 _watcher类,vm._watcher = new Watcher(vm, updateComponent, noop);
      (_watcher类负责往dep里添加订阅者,当收到数据变化的通知时,触发compile的回调,更新视图)
    • 调用mounted钩子, callHook(vm, ‘mounted’);
      参考的链接

使用 Object.defineProperty() 来进行数据劫持有什么缺点?

使用Object.defineProperty() 拦截不了一些变化,比如给对象添加新属性,是不能拦截的,
vue2中给每一个数组元素绑定上监听,实际消耗很大,而受益并不大。只是Vue内部重写了函数
在这里插入图片描述

所以vue3利用proxy代理器来进行数据劫持

vue3用proxy和reflect来做代理和劫持

参考
通过 Proxy 创建对于原始对象的代理对象,从而在代理对象中使用 Reflect 达到对于 JavaScript 原始操作的拦截。
why?

  • 框架健壮性
    使用 Object.defineProperty() 重复声明的属性 报错了,因为 JavaScript 是单线程语言,一旦抛出异常,后边的任何逻辑都不会执行,所以为了避免这种情况,我们在底层就要写 大量的 try catch 来避免,不够优雅。而使用 Reflect.defineProperty() 是有返回值的,所以通过 返回值 来判断你当前操作是否成功。
    在这里插入图片描述
    在这里插入图片描述
  • reflect可以修改属性访问中的 this 指向为传入的 receiver 对象。
    Proxy 中 get 陷阱的 receiver 不仅仅代表的是 Proxy 代理对象本身,同时也许他会代表继承 Proxy 的那个对象。其实本质上来说它还是为了确保陷阱函数中调用者的正确的上下文访问
const parent = {
  name: '19Qingfeng',
  get value() {
    return this.name;
  },
};
const handler = {
  get(target, key, receiver) {
    return Reflect.get(target, key);
    // 这里相当于return target[key]
  },
};
const proxy = new Proxy(parent, handler);
const obj = {
  name: 'wang.haoyu',
};
// 设置obj继承与parent的代理对象proxy
Object.setPrototypeOf(obj, proxy);
console.log(obj.value);  // 19Qingfeng

我们稍微分析下上边的代码:

  • 当我们调用 obj.value 时,由于 obj 本身不存在 value 属性。

  • 它继承的 proxy 对象中存在 value 的属性访问操作符,所以会发生屏蔽效果。此时会触发 proxy 上的 get value() 属性访问操作。

  • 同时由于访问了 proxy 上的 value 属性访问器,所以此时会触发 get 陷阱。进入陷阱时,target 为源对象也就是 parent ,key 为 value 。

  • 陷阱中返回 Reflect.get(target,key) 相当于 target[key]。此时,不知不觉中 this 指向在 get 陷阱中被偷偷修改掉了!!

  • 原本调用方的 obj 在 get 陷阱中被修改成为了对应的 target 也就是 parent 。自然而然打印出了对应的 parent[value] 也就是 19Qingfeng 。
    所以要用Reflect将this改正确
    即,get里面returnReflect.get(target,key,receiver)

props只读的,直接改变会报错,但是却有emit或者父级传递一个方法来改变,这是为什么呢?

props是对外接口,在面向对象编程中,父类向子类传递的props类似公有属性,是允许外部访问的。父组件通过这个值来影响子组件的状态,在子组件中修改props是会影响到父组件的,那如果子组件可以来修改props,父组件是不知道他的数据被改了的,就会造成父子组件状态不同步,状态混乱引发问题。所以就要用emit来告知父组件,让父组件自己来对数据进行修改。
因此,最优的方案就是约定props不允许直接修改,是只读的(虽然js特性的引用类型可以绕过去,但不推荐),需通过emit事件通过父组件修改,让父组件感知变化。也就是单项数据流。
https://www.zhihu.com/question/468676903

props的实现

props的更新

props变化后,子组件如何重新渲染?

重新渲染分为两种情况,

第一种是props直接修改了值,比如例子中的modyfyAge,那个直接触发了子组件props数据age的setter,所以子组件重新渲染;

第二种,props传递的数据是一个对象hobby,修改了对象的某个属性的值game,这个时候子组件也会重新渲染,但是它不是因为子组件props数据hobby的setter被触发,而是因为子组件的渲染watcher被依赖收集到了父组件的hobby的getter中了。hobby发生变化,触发setter,让子组件也重新渲染了。

MVC、MVVM区别

MVC(单向数据改变)
MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。Model是负责存储页面的数据,View是负责页面的显示功能,Controller是处理用户交互的部分(数据的改变引起视图的改变)可以使用onchange来改变数据
MVC的思想:一句话描述就是Controller负责将Model的数据用View显示出来,换句话说就是在Controller里面把Model的数据赋值给View。
MVVM(双向数据改变)
ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;
Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系,因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。

MVVM优缺点

优点:分离视图和模型,减少了代码的耦合度,自动更新DOM和数据
缺点:当一个视图的状态很多的时候,viewmodule的构建和维护成本比较高

Vue3的设计目标

在vue3以前,vue会有一些痛点:对状态的操作的逻辑是分散在不同的生命周期里的,不直观;API的设计风格是分散的option风格API,不利于逻辑的提取和复用;因为this指向的问题对于ts不能友好的兼容;这些问题,所以改进的目标就是:

  • 体积 更小
    • 移除了一些不常用的API(如:filter),
    • 引入了tree-shaking,来删除没有用到的代码,来减少体积
  • 编译更快
    • 新的diff算法
    • 静态提升(标记和提升所有的静态节点,diff的时候只需要对比动态节点内容;)
    • 缓存事件处理函数
  • 更友好
    • vue3使用composition API,便于逻辑地提取和复用,也有利于在代码中使用ts

Vue2和Vue3的区别

vue2 vs vue 3

  • Object.defineProperty Proxy
  • vue2.0中不管数据多大,都会在一开始就为其创建观察者;当数据很大时,这可能会在页面载入时造成明显的性能压力。而vue3.0只会对“被用于渲染初始可见部分的数据”创建观察者,而且vue3.0的观察者更高效
  • 生命周期。beforeCreate,created=>setup
  • Vue3 生周期钩子不是全局可调用的了,需要另外从vue中引入。然后在setup中使用
  • vue2中this表示的是这个组件,取Props的时候,就是直接this.xxx;而在vue3中,setup(props,context)会接受props的参数,从props.xxx来获取
  • vue2中,data,method都是分开的,所以当组件逻辑复杂,涉及props,data,computed等时,就会各个逻辑相互穿插,所以在vue3中,没有分散开,而是都放在setup里面,这样就不需要跳转到相应的模块来做事情,可以将同一个逻辑关注点的代码放在一起,增加代码可读性和可维护性,
  • vue2中Option API风格,使得比如像method里面的this不是指向method所在的对象,而是组件实例,这与原生js会有点冲突,也使得TS在vue2中不好用,然而在vue3中组合式composition API,就更加灵活,也更加适合TS

为什么选择用vue

vue的话它组件的template,css,还有一些逻辑功能是分开的,写起来好写,可读性和维护性也高
他是双向数据绑定,就可以不用频繁的操作DOM,把更多的精力用在逻辑上
vue在网上的资料也多

Vue和react的区别

Vue 从一开始的定位就是尽可能的降低前端开发的门槛,让更多的人能够更快地上手开发。很多时候我更希望自己做的东西能帮到那些中小型企业和个人开发者。所以从设计的角度上来说,Vue 首先考虑的是假设用户只掌握了 web 基础知识 (HTML, CSS, JS) 的情况下,如何能够最快理解和上手,实现一个看得见摸得着的应用。(尤雨溪)

  • 数据流,vue是响应式的,支持双向数据绑定,而react是函数式的,提倡单向数据流

  • 虚拟DOM

    • vue2引入了虚拟 DOM,可以更快的计算出虚拟DOM的差异,因为他在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
    • react,每当应用的状态被改编时候,全部子组件都会重新渲染(可以使用pureComponent/shouldComponentUpdate这个生命周期来控制),但vue将此视为默认的优化
  • 组件化,vue和react最大的不同就是模板的编写

    • Vue鼓励写近似常规HTML的模板。写起来很接近标准 HTML元素,多了一些属性和概念。
    • React推荐所有的模板通用JavaScript的语法扩展——JSX书写。
  • 监听数据变化的实现原理不同

    • Vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能
    • React 默认是通过比较引用的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的vDOM的重新渲染。这是因为 Vue 使用的是可变数据,而React更强调数据的不可变。
  • 高阶组件

    • react可以通过高阶组件(Higher Order Components-- HOC)来扩展,而vue需要通过mixins来扩展。
      原因高阶组件就是高阶函数,而React的组件本身就是纯粹的函数,所以高阶函数对React来说易如反掌。相反Vue.js使用HTML模板创建视图组件,这时模板无法有效的编译,因此Vue不采用HOC来实现。

Computed 和 Watch 的区别

  • computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的值发生改变时,下一次获取 computed 的值时才会重新计算 computed 的值。不支持异步
  • watch 侦听器 : 更多的是观察的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。支持异步操作。
    运用场景
    当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算。
    当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

Computed 和 Methods 的区别

可以将同一函数定义为一个 method 或者一个计算属性。对于最终的结果,两种方式是相同的
不同点:
computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相关依赖发生改变时才会重新求值;
method 调用总会执行该函数。

slot是什么?有什么作用?原理是什么?

slot又分三类,默认插槽,具名插槽和作用域插槽
组件的插槽也是为了让我们封装的组件更加具有扩展性。
让使用者可以决定组件内部的一些内容到底展示什么。
比如:导航栏组件,可以在里面做插槽,然后使用的时候按照需要来替换插槽
匿名插槽:又名匿名插槽,当slot没有指定name属性值的时候一个默认显示插槽,一个组件内只显示一个匿名插槽。
也就是带有name属性的slot,一个组件可以出现多个具名插槽。
具名插槽:带有name的插槽,一个组件可以有多个具名插槽,父级组件通过对应的slot属性来对应子组件的插槽的name来替换
作用域插槽:父组件替换插槽的标签,但是内容由子组件来提供。该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。
在父组件使用我们的子组件时,从子组件中拿到数据:我们通过获取到slotProps属性
在通过slotProps.data就可以获取到刚才我们传入的data了

过滤器的作用,如何实现一个过滤器

filters不会修改数据,而是过滤数据,改变用户看到的输出
使用场景:
需要格式化数据的情况,比如需要处理时间、价格等数据格式的输出格式。
过滤器是一个函数,它会把表达式中的值始终当作函数的第一个参数。

如何保存页面的当前的状态

在Vue中,还可以是用keep-alive来缓存页面,当组件在keep-alive内被切换时组件的activated、deactivated这两个生命周期钩子函数会被执行 被包裹在keep-alive中的组件的状态将会被保留:


常见的事件修饰符及其作用

  • .once : 该事件指触发一次
  • .stop :放置事件冒泡,相当于原生中的event.stopPropagation()
  • .prevent :取消默认事件,比如阻止表单提交等同于 JavaScript 中的 event.preventDefault()
  • .native - 监听组件根元素的原生事件
  • .capture :与事件冒泡的方向相反,事件捕获由外到内;

v-if和v-show的区别

  • v-if是动态的向DOM树中添加或删除元素,而v-show是改变元素的diaplay属性
  • v-if时惰性的,当第一次的条件时false,就什么都不做,只有在首次条件为true时,才做局部编译;而v-show无论初始条件是什么都会被编译

v-html,v-once,v-pre, v-cloak

v-html会先移除节点下的所有节点,将字符串解析成html标签,就是将innerHTML值设置为解析好的标签
在这里插入图片描述
v-once组件只会渲染一次,之后里面的数据变化他也不会变化
v-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法。
在这里插入图片描述
v-cloak:解决初始化页面时页面闪动问题
可以用v-cloak来设置浏览器没有编译完成时组件的样式
在这里插入图片描述

v-module是如何实现的?

v-module实际上是一个语法糖,它的原理就是使用v-bind动态绑定一个值(message),然后使用v-on来给当前的元素绑定input事件

  • 作用在表单元素上:使用v-bind绑定表单的value值和一个message变量,当表单触发input事件时时候,改变message
  <input type="text" v-bind:value v-on:input="message=$event.target.value">

  • 作用在组件上,本质就是父子组件通讯的语法糖,通过prop和 e m i t 实现,父组件使用 p r o p 将数据传给子组件,子组件使用 o n i n p u t 监听子组件 i n p u t . v a l u e 的变化,当输入发生变化的时候,使用 emit实现,父组件使用prop将数据传给子组件,子组件使用oninput监听子组件input.value的变化,当输入发生变化的时候,使用 emit实现,父组件使用prop将数据传给子组件,子组件使用oninput监听子组件input.value的变化,当输入发生变化的时候,使用emit将数据发送到父组件,父组件修改v-module绑定的值

data为什么是一个函数而不是一个对象?

js中的对象是引用类型,多个实例引用一个对象时,当一个实例的对象修改时,别的实例都会改变。在Vue中是想要更多的复用组件,每个组件中的数据都要是独立的,所以组件的data值不能是一个对象,使用函数的形式,在复用组件的时候每次都会返回一个新的data,这样每个组件都有自己的数据空间,各自维护,互不影响

对keep-alive的理解,它是如何实现的,具体缓存的是什么?

keep-alive是vue内置的组件,能在组件切换时保存它的状态在内存中,防止重复渲染DOM。

  • keep-alive有三个属性
    include 字符串或正则表达式,匹配到名字的组件可以被缓存
    exclude 字符串或正则表达式,匹配到名字的组件不可以被缓存
    max 数字,最多可以缓存多少个组件
  • 它有两个生命周期
    activated: keep-alive组件激活时调用
    deactivated: keep-alive组件停用时调用
  • LRU (least recently used)缓存策略
    从内存中找出最久未使用的数据并替换掉
    最常见的实现是使用一个链表保存缓存数据,详细算法实现如下∶
    新数据插入到链表头部
    每当缓存命中(即缓存数据被访问),则将数据移到链表头部
    链表满的时候,将链表尾部的数据丢弃。

$nextTick

vue为了高效率的更新DOM,Vue不可能对每一个数据变化都做一次渲染,它会把这些变化先放在一个异步的队列当中,同时它还会对这个队列里面的操作进行去重,然后在一次事件循环结束之后更新DOM,nextTick就是在一次DOM更新完毕之后调用的
nextTick实现原理:
nextTick会将回调函数放在异步任务重中,他是使用Promise.then、MutationObserver和setImmediate或者setTimout来让callback放入异步队列的,这样callbac就会在同步代码执行完了之后调用,此时就可以操作更新好的DOM了

源码是使用三个参数来做的
callback:我们要执行的操作,可以放在这个函数当中,我们没执行一次$nextTick就会把回调函数放到一个异步队列当中;
pending:标识,用以判断在某个事件循环中是否为第一次加入,第一次加入的时候才触发异步执行的队列挂载
timerFunc:用来触发执行回调函数,也就是Promise.then或MutationObserver或setImmediate 或setTimeout的过程

还有 n e x t T i c k 和 n e x t T i c k 区别就是 n e x t T i c k 多了一个 c o n t e x t 参数,用来指定上下文。但两个的本质是一样的, nextTick和nextTick区别就是nextTick多了一个context参数,用来指定上下文。但两个的本质是一样的, nextTicknextTick区别就是nextTick多了一个context参数,用来指定上下文。但两个的本质是一样的,nextTick是实例方法,nextTick是类的静态方法而已;实例方法的一个好处就是,自动给你绑定为调用实例的this罢了。

使用场景

  • 比如说,点击按钮出现输入框,输入框出现的时候自动获得焦点,设计的时候是 点击按钮修改isshow,给输入框加ref,用ref得到输入框,然后this. r e f s . i d . f o c u s ( ) 但是会发现输入框并没有获得焦点,因为执行的时候,虽然改了 i s s h o w ,但是页面还没有渲染出来,就获取 r e f ,是获取不到的,这时就要延时一下,等到页面重新渲染之后再获取,这是就使用 refs.id.focus() 但是会发现输入框并没有获得焦点,因为执行的时候,虽然改了isshow,但是页面还没有渲染出来,就获取ref,是获取不到的,这时就要延时一下,等到页面重新渲染之后再获取,这是就使用 refs.id.focus()但是会发现输入框并没有获得焦点,因为执行的时候,虽然改了isshow,但是页面还没有渲染出来,就获取ref,是获取不到的,这时就要延时一下,等到页面重新渲染之后再获取,这是就使用nextTick
  • 在created()钩子函数中,使用$nextTick,因为created()时,DOM还没有渲染,无法进行DOM操作

Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?

不会立即渲染,Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。只要侦听到数据变化, Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。当一次事件循环结束之后才会重新渲染视图

Vue 中给 data 中的对象属性添加一个新的属性时会发生什么?如何解决?

Vue 中给 data 中的对象属性添加一个新的属性时,可以发现对象上确实添加了新属性,但是视图却没有发生变化,因为创建Vue时,并没有这个新属性,他不是响应式的,因为vue2使用Object.definePropertype来进行数据劫持的,他并不能劫持到添加新属性,所以不能引起更新视图(vue3使用的是proxy来劫持的)
解决办法:使用Vue全局API this. s e t ( ) 来添加新属性 t h i s . set()来添加新属性 this. set()来添加新属性this.set(this.obj, ‘name’, ‘kang’)

Vue中封装的数组方法有哪些,其如何实现页面更新

因为vue使用Object.definePropertype来进行数据劫持的,他不能侦听到数组内部的变化,比如长度,所以vue就一些数组的函数进行了重写,会获取到数组的obeserver对象,如果有新的值,就侦听新值的变化
使其改变数组的同时可以引起视图的变化

  • push
  • pop
  • shift
  • unshift
  • reverse
  • sort
  • splice

单页面应用和多页面应用

  • SPA单页面应用,指只有一个主页面的应用,一开始只需要加载一次js、css等相关资源。所有内容都包含在主页面,对每一个功能模块组件化。单页应用跳转,就是切换相关组件,仅仅刷新局部资源。
  • MPA多页面应用 ,指有多个独立页面的应用,每个页面必须重复加载js、css等相关资源。多页应用跳转,需要整页资源刷新。

Vue template 到 render 的过程( Vue模版编译原理)

vue中的模板template无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的HTML语法,所有需要将template转化成一个JavaScript函数,这样浏览器就可以执行这一个函数并渲染出对应的HTML元素
Vue的模板编译在$mount之后,通过compile方法,经过parse、optimize、generate方法,最后生成render function来生成虚拟DOM,虚拟
DOM通过diff算法,来更新DOM。

  • 生成AST: parse过程,其实就是不断的截取字符串并解析它们生成AST。在此过程中,如果截取到非闭合标签就push到stack中,如果截取道结束标签就把这个标签pop出来。(抽象语法树:在 Vue 中我把它理解为嵌套的、携带标签名、属性和父子关系的 JS 对象,以树来表现 DOM 结构)
  • 优化AST:optimize的作用主要是对生成的AST进行静态内容的优化,标记静态节点,(所谓静态内容,指的是和数据没有关系,下一次更新渲染时可以直接跳过)
  • 生成代码:generation递归AST,将AST转化为render函数字符串

子组件可以直接改变父组件的数据吗?

子组件不可以直接改变父组件的数据。这样做主要是为了维护父子组件的单向数据流,即父级 props 的更新会流向子组件,但是反过来则不行,子组件只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。

Vue是如何收集依赖的?

在初始化 Vue 的每个组件时,会对组件的 data 进行初始化,就会将由普通对象变成响应式对象,在这个过程中便会进行依赖收集的相关逻辑,具体是通过一个Dep类,每个响应式对象就有一个Dep,每当templete中有一个双向绑定的数据,就会通过watcher往dep中添加一个依赖,

Vue性能优化

编码阶段

  • v-if和v-for不能连用
    原因分析:
    v-for 会比 v-if优先执行,当一个标签上面同时存在:v-for 和 v-if 的时候,会先执行v-for循环,
    然后去看循环出来的每个div上面flag的值,是真还是假。如果flag为true,就显示 ,否则就不显示
    这样就造成了不必要的性能浪费
    解决方案:
    先在计算属性computed里面做数组的筛选
  • 使用事件代理
  • SPA 页面采用keep-alive缓存组件,这样就不用重复渲染组件
  • 如果是频繁切换,就用v-show;在条件很难改变,比如某个模块在用户a出显示,就用v-if
  • 懒加载
  • 节流防抖

vue里的生命周期都在干什么,一般用用周期函数具体干什么事

  • beforeCreate(创建前)
    这个时候Vue实例还没有被创建,data和methods还没有初始化
    使用场景:可以在这里加一些loading效果,然后在created中移除
  • created(创建后)
    这时配置好了data,methods,computed,watch,但是还还没有挂载到$el上,还不能进行DOM操作
    使用场景:这个时候可以发送一些网络请求,获取数据
  • 为什么要在created里获取数据?
    因为beforeCreate里data和methods还没有初始化,获取数据的异步方法在methods里没有,就算获取到了数据,data还没有初始化
    created时data已经生成,时间也刚好不迟
  • beforeMounted(挂载前)
    此时模板已经编译好了,但是还是虚拟DOM,没有渲染在页面上
  • mounted(挂载后)
    el已经挂载在实例上,DOM渲染完成
    使用场景:一般我们的第一个业务逻辑会在这里开始,当需要操作DOM的时候执行,可以配合$nextTick 使用进行单一事件对数据的更新后更新DOM
  • beforeUpdate(更新前)
    详细: 执行它时,data中的数据已经被更新了,但是页面中的data还未被替换过来
    使用场景:适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
  • updated(更新后)
    类型:Function
    详细: 数据更新完成,且页面发生改变
    使用场景: 当数据更新需要做统一业务处理的时候使用,(我在页面使用better-scroll滑动时使用过,由于页面内容没有被完全加载出来,所以内容的scrollHeight不够,导致页面滚动不了.所以就在此时使用该钩子函数,在里面进行页面的刷新,完美解决问题)
  • beforeDestroy(实例销毁前)
    类型:Function
    详细: 实例销毁之前调用 , 实例仍然完全可用。
    使用场景: 主要解绑一些使用addEventListener监听的事件等
  • destroyed(实例被销毁后)
    类型:Function
    详细:实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
    使用场景: 加点儿提示toast之类的东西
  • deactivated(未被激活)
    类型:Function
    详细: 被 keep-alive 缓存的组件停用时调用。
  • beforeDestroy(实例销毁前)
    类型:Function
    详细: 实例销毁之前调用 , 实例仍然完全可用。
    使用场景: 主要解绑一些使用addEventListener监听的事件等

如果有一个组件被keep-alive包裹住,那怎么对这个组件进行释放

在页面deactivated的时候调用this.$destroy(‘组件名’)将此组件进行销毁

另外会有在keep-alive中的路由组件会有的周期,actived和deactivated
当组件切换走的时候,会将组件的状态添加进缓存中,此时会调用deactived,当组件被切换回来的时候,会去缓存中找这个组件,触发active

父子组件周期的执行顺序

渲染过程
父子间先创建,父子间beforeMount后子组件创建,子组件先挂载
更新过程
父子子父
销毁过程
父子子父

组件通信

父组件向子组件:props
子组件向父组件:子组件使用 e m i t 发送事件,父组件用 v − o n 监听父组件访问子组件:使用 emit发送事件,父组件用v-on监听 父组件访问子组件:使用 emit发送事件,父组件用von监听父组件访问子组件:使用children或 r e f s 子组件访问父组件:使用 refs 子组件访问父组件:使用 refs子组件访问父组件:使用parent
非父子组件使用eventBus

对前端路由的理解

在前端技术早期,一个 url 对应一个页面,如果要从 A 页面切换到 B 页面,那么必然伴随着页面的刷新。这个体验并不好,后来,改变发生了——Ajax 出现了,它允许人们在不刷新页面的情况下发起请求;与之共生的,还有“不刷新页面即可更新页面内容”这种需求。在这样的背景下,出现了 SPA(单页面应用)。但是SPA 其实并不知道当前的页面“进展到了哪一步”。为了解决这个问题,前端路由出现了。前端路由可以帮助我们在仅有一个页面的情况下,“记住”用户当前走到了哪一步,
具体做法就是 :拦截用户的刷新操作,避免服务端盲目响应、返回不符合预期的资源内容。
把刷新这个动作完全放到前端逻辑里消化掉。前端路由会感知 URL 的变化,根据这些变化、用 JS 去给它生成不同的内容

router路由懒加载

使用箭头函数和require或者import 动态加载

路由的hash和history模式的区别

Vue-Router有两种模式:hash模式和history模式。默认的路由模式是hash模式。

hash模式

这里的 hash 就是指 url 尾巴后的 # 号以及后面的字符。hash 也 称作 锚点,本身是用来做页面定位的。由于 hash 值变化不会导致浏览器向服务器发出请求,而且 hash 改变会触发 hashchange 事件,浏览器的进后退也能对其进行控制,所以人们在 html5 的 history 出现前,基本都是使用 hash 来实现前端路由的。

history模式

history模式的URL中没有#,用户在输入一个URL时,服务器会接收这个请求,并解析这个URL,然后做出相应的逻辑处理。

如果想要切换到history模式,就要进行以下配置(后端也要进行配置):

const router = new VueRouter({
mode: ‘history’,
routes: […]
})

vue history模式刷新404原因

因为在history模式下,只是动态的通过js操作window.history来改变浏览器地址栏里的路径,并没有发起http请求,但是当我直接在浏览器里输入这个地址的时候,就一定要对服务器发起http请求,但是这个目标在服务器上又不存在,所以会返回404
解决方法
在这里插入图片描述

r o u t e 和 route 和 routerouter 的区别

$route对象表示当前的路由信息,包含了当前 URL 解析得到的信息。包含当前的路径,参数,query对象等。
r o u t e r 对象是全局路由的实例 b a c k ( ) , g o ( ) , r e p l a c e t h i s . router对象是全局路由的实例back(),go(),replace this. router对象是全局路由的实例back(),go(),replacethis.router.go(-1)

params和query的区别

url地址显示:query更加类似于ajax中get传参,params则类似于post,说的再简单一点,前者在浏览器地址栏中显示参数,后者则不显示

如何定义动态路由?如何获取传过来的动态参数?

  • param方式
    配置路由格式:/router/:id
    传递的方式:在path后面跟上对应的值
    传递后形成的路径:/router/123
    参数获取 通过 $route.params.userid 获取传递的值
    按钮
  • query方式
    配置路由格式:/router,也就是普通配置
    传递的方式:对象中使用query的key作为传递方式
    传递后形成的路径:/route?id=123
    通过$route.query 获取传递的值
    档案

导航守卫

vue-router提供的导航守卫主要用来监听监听路由的进入和离开的.
vue-router提供了beforeEach和afterEach的钩子函数, 它们会在路由即将改变前和改变后触发.
导航钩子的三个参数解析:
to: 即将要进入的目标的路由对象.
from: 当前导航即将要离开的路由对象.
next: 调用该方法后, 才能进入下一个钩子

Vue-router跳转和location.href有什么区别

  • 使用 location.href= /url 来跳转,简单方便,但是刷新了页面
  • 使用 history.pushState( /url ) ,无刷新页面,静态跳转;
  • 引进 router ,然后使用 router.push( /url ) 来跳转,使用了 diff 算法,实现了按需加载,减少了 dom 的消耗。

vuex

采用了全局单例模式,将组件的共享状态抽离出来管理,让每个组件都可以共享状态

Vuex的理解

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理 应用的所有组件的状态
简单来说就是把需要共享的变量全部存储在一个对象里面。然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。并且这个对象里的数据都是响应式的,当组件从store中获取了数据,如果store里的数据发生改变,组件里的数据也会发生改变
有五种,分别是 State、 Getter、Mutation 、Action、 Module

state => 基本数据(数据源存放地)
getters => 从基本数据派生出来的数据
mutations => 提交更改数据的方法,同步
actions => 像一个装饰器,包裹mutations,使之可以异步。
modules => 模块化Vuex

使用场景
比如用户的登录状态、用户名称、头像、地理位置信息等等。
比如商品的收藏、购物车中的物品等等。
这些状态信息,都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的
核心流程
组件触发一些动作或事件,也就是actions,想要改变状态或获取数据,但是在vuex中状态是集中管理的,不能直接修改数据,所以就会把这个动作或事件(actions)提交给mutation,然后mutatiion去修改state中的数据,当state数据改变后,就会重新渲染到vue组件去,组件展示新数据。
在这里插入图片描述

Vuex中mutation和action的区别

  • mutation中的操作是一系列的同步函数,用于修改state中的变量的的状态。理论上是修改state的唯一途径
    Action 可以包含任意异步操作。 Action 提交的是 mutation,而不是直接变更状态。
  • mutation的参数是state,它包含store中的数据;store的参数是context,它是 state 的父级,包含 state、getters

vuex中为什么把把异步操作封装在action,把同步操作放在mutations?

mutation是用来改变状态的,有devtools可以捕捉状态变化的快照,mutation里面是异步操作的话,devtools就不知道什么时候回调函数被调用,实质上任何在回调函数中进行的状态的改变都是不可追踪的。所以可以将异步代码写在action中,就各司其职

vuex的严格模式是什么?

在严格模式下,如果state状态不是因为mutation引起的,就会抛出错误,这样可以保证所有的状态变化都会被调试工具追踪到,在Vue.store中设置
const store = new Vuex.Store({
strict:true,
})

虚拟DOM

虚拟DOM本身是js对象。 在代码渲染到页面之前,vue会把代码转换成一个对象(虚拟 DOM)。以对象的形式来描述真实DOM结构,最终渲染到页面。在每次数据发生变化前,虚拟DOM都会缓存一份,变化之时,现在的虚拟DOM会与缓存的虚拟DOM进行比较。在vue内部封装了diff算法,通过这个算法来进行比较,渲染时修改改变的变化,原先没有发生改变的通过原先的数据进行渲染。

虚拟 DOM 真正的价值从来都不是性能,而是不管数据怎么变化,都可以用最小的代价来更新 DOM,而且掩盖了底层的 DOM
操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护

虚拟DOM解析过程

  • 首先分析将要插入到文档的DOM树进行分析,用js对象的方式将他表示出来,将这个js对象保存出来,然后再将DOM树渲染
  • 当页面结构发生变化时,根据新结构生成一个对象树,然后将这个新树与旧树进行对比,记录两棵树的差异
  • 最后将有差异的地方应用到真正的DOM树中重新渲染

为什么要使用虚拟DOM?

虚拟DOM就是为了解决这个浏览器性能问题而被设计出来的。使用原生API操作DOM的时候,比如当你在一次操作时,需要更新10个DOM节点,理想状态是一次性构建完DOM树,再执行后续操作。但浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程。但是使用虚拟DOM的时候,就不会直接操作DOM,而是生成一个新的js对象后,使用diff算法跟之前的虚拟DOM作比较,最终将有差异的地方一次性运用到真正的DOM、中去渲染。
js对象模拟DOM节点的好处是,页面的更新可以先全部反映在js对象上,操作内存中的js对象的速度显然要快多了

diff算法

diff算法只是比较同一层的节点,放弃跨级比较,降低了时间复杂度
具体比较是:

  • 标签名不同,直接删除,不继续深度比较
  • 标签名和key相同,则认为是同一节点,不继续深度比较

vue中key的作用

key是用来唯一标识一个元素的

  • v-if 使用key,(比如说有一个需求,点击按钮改变input的type,当点击按钮的时候,虽然input的type发生了变化,但是输入框里的内容还是没有变),这就是因为vue是尽量减少元素的重复渲染,所以会尽可能的复用已有的元素,这个时候就要给这个元素添加key来唯一标识元素,这个情况下,唯一标识的key就不会出现复用
  • v-for使用key,(比如有一个列表 a,b,c,d,e,当要给bc之间添加一个f 时,diff算法就会把c更新为f,d更新为c…,这样的效率不会很高,但是要是给每个节点都有key的话,diff算法就只会往里面插入f,提高了效率)

为什么不建议用index作为key?

因为使用index作为key的时候,比如删除中间的一个节点,后面的节点的key都会发生变化,都要重新渲染,影响性能

css模块化的实现方式

  • 命名格式
  • css Module 会将类名转化为哈希值,解决命名冲突,CSS Modules 不能直接使用,而是需要进行打包,一般通过配置 css-loader 中的 modules 属性即可完成 css modules 的配置。使用的时候外部导入import styles from “./style.css”;
  • css in js ,styled-components用js来操作css ,生成的是内联样式
    原理:1,首先生成一个 componentId,SC 会确保这个 id 是唯一的
    2,在 head 中插入一个 style 节点,并返回 className;
    3,根据解析的 props 和 className 来创建这个 element。
  • vue scoped 他会给本组件里的所有标签添加上一个data属性等于一个随机数,css里面也是使用这个随机数,来实现css模块化

vue style的scope是怎么实现的?

vue会给当前组件的每一个DOM添加一个不重复的data值 <div data-v-[随机数] class=‘moumou’>
给css选择器中添加上相应的data属性,这样样式只会生效在当前组件
好处:让组件间的样式独立起来,互不影响
坏处:这个样式只对当前组件生效,不会对组件里的子组件生效,因为只会子组件第一个DOM节点加上data属性,其余的不添加,
可以用/deep/来解决
实际上,使用了样式穿透违反了scoped属性的意义,可以在vue组件中添加不含scoped属性的style标签,给组件最外层DOM节点添加唯一的class类名,使用该类名来修改其他组件的样式,达到既不产生样式全局污染也能修改其他组件样式的效果。

SPA 单页面应用

  • 首屏加载慢,可能的原因
    网络延迟
    加载的资源过大
  • 解决方案
    按需加载
    图片懒加载
    给代码压缩
    ssr

React中props.children和React.Children的区别

获取和遍历操作
当涉及组件嵌套,在父组件中使用props.children把所有子组件显示出来
this.props.children的值有三种状态
如果当前组件没有子节点,它的值就是undefined
如果当前组件只有一个子节点,它的值就是我object
如果当前组件有多个子节点,它的值就是array
当this.props.children的值不是数组时,使用js的map会报错,React提供了API React.Children来处理this.props.children,它已经将this.props.children的所有情况考虑在内了。

React.Children.map和js的map有什么区别?

JavaScript中的map不会对为null或者undefined的数据进行处理,而React.Children.map中的map可以处理React.Children为null或者undefined的情况。

状态提升

React的状态提升就是用户对子组件操作,子组件不改变自己的状态,通过自己的props把这个操作改变的数据传递给父组件,改变父组件的状态,从而改变受父组件控制的所有子组件的状态,这也是React单项数据流的特性决定的。(例子,父组件中有两个input子组件,如果想在第一个输入框输入数据,来改变第二个输入框的值)

React的严格模式如何使用,有什么用处?

StrictMode 是一个用来突出显示应用程序中潜在问题的工具,只检测子组件,不检测自组建的子组件。
StrictMode 目前有助于:

  • 识别不安全的生命周期
  • 关于使用过时字符串 ref API 的警告
  • 关于使用废弃的 findDOMNode 方法的警告
  • 检测意外的副作用
  • 检测过时的 context API

同时引用这三个库react.js、react-dom.js和babel.js它们都有什么作用?

react:包含react所必须的核心代码
react-dom:react渲染在不同平台所需要的核心代码
babel:将jsx转换成React代码的工具

React必须使用JSX吗?

不是,每个jsx元素只是react.createElement的语法糖,babel会将jsx写法转成react.creatrElemene,使用jsx完成的事情也可以完全用js来代替。

为什么 React 要用 JSX?

JSX 是一个 JavaScript 的语法扩展,结构类似 XML。JSX 主要用于声明 React 元素,但 React 中并不强制使用 JSX。即使使用了 JSX,也会在构建过程中,通过 Babel 插件编译为 React.createElement。所以 JSX 更像是 React.createElement 的一种语法糖。

React 中的高阶组件运用了什么设计模式?

装饰模式的特点是不需要改变 被装饰对象 本身,而只是在外面套一个外壳接口。JavaScript 目前已经有了原生装饰器的提案

react类组件和函数组件的区别

  • 类组件需要继承 class,函数组件不需要;
  • 类组件可以访问生命周期方法,函数组件不能;
  • 类组件中可以获取到实例化后的 this,并基于这个 this 做各种各样的事情,而函数组件不可以;
  • 类组件中可以定义并维护 state(状态),而函数组件不可以,只能借助useState;

类组件和函数组件之间,是面向对象和函数式编程这两套不同的设计思想之间的差异。而函数组件更加契合 React 框架的设计理念,React 组件本身的定位就是函数,一个输入数据、输出 UI 的函数。

为什么 useState 要使用数组而不是对象

如果 useState 返回的是数组,那么使用者可以对数组中的元素命名,代码看起来也比较干净
如果 useState 返回的是对象,在解构对象的时候必须要和 useState 内部实现返回的对象同名,想要使用多次的话,必须得设置别名才能使用返回值

React Hooks 解决了哪些问题?

  • 组件之间复用状态逻辑很难
    • 解决这类问题一般使用高阶组件或状态管理工具,而使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用
  • 复杂组件变得难以理解
    • 在不同的生命周期里面可能会把一些关联的逻辑拆分开来,而会把一些不相关的代码放在一起,这样容易有bug,也不好测试。有了hooks就可以不用按照生命周期来划分了,可以将关联的部分代码写在一起
  • 难以理解的 class
    • 使用类组件this指向问题,得使用bind (也可以使用箭头函数来解决)
      在这里插入图片描述

React Hook 的使用限制有哪些?

  • useEffect带来的心智负担更大
    • 因为使用useEffect,就得了解依赖数组的触发事件,避免以外的状态更新
  • 状态不同步
    • 函数的运行是独立的,每个函数都有一份独立的作用域。函数的变量是保存在运行时的作用域里面,当我们有异步操作的时候,经常会碰到异步回调的变量引用是之前的,也就是旧的(这里也可以理解成闭包)。
  • 只能在 React 的函数组件中调用 Hook。
  • 不要在循环、条件或嵌套函数中调用 Hook;
    • 每个节点都会对应一个fiber对象来记录节点的一些信息,fiber对象里memoizedState来存储上次渲染过程后节点的最终的state状态,在类式组件中,state是一整个对象,可以与memoizedState一一对应,而在函数式组件中react并不知道调用了几次useState,所以用了链式存储(链表),memoizedState是用来记录这个useState应该返回的结果的,而next指向的是下一次useState对应的`Hook对象。(那如果在条件语句中使用了hook,第一次创建了一条链表1,2,3,4,第二次条件不符合,导致useState(1)没执行,那取useState(2)的时候,拿到的hook对象是state1,逻辑就混乱了)-》破解方法:不用下标存储,而是用key的方法来存储
      在这里插入图片描述

useContext vs Redux

1.如果项目体量较小,只是需要一个公共的store存储state,而不讲究使用action来管理state,那context完全可以胜任。反之,则是redux的优点。

2.context的缺点:因为没有了action,state的值都是被直接修改,state的数据安全性不及redux。同时也不能使用redux的中间件,比如thunk/saga,在一些异步的情况需要自己来处理。
3.Redux有devtools可监测数据的变化

useCallbach和useMemo

共同作用:
1.仅仅 依赖数据 发生变化, 才会重新计算结果,也就是起到缓存的作用。

两者区别:
1.useMemo 计算结果是 return 回来的值, 主要用于 缓存计算结果的值 ,应用场景如: 需要 计算的状态
2.useCallback 计算结果是 函数, 主要用于 缓存函数,应用场景如: 需要缓存的函数,因为函数式组件每次任何一个 state 的变化 整个组件 都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,和减少资源浪费

useState是同步还是异步

参考
只要你进入了 react 的调度流程,那就是异步的。只要你没有进入 react 的调度流程,那就是同步的。什么东西不会进入 react 的调度流程? setTimeout setInterval ,直接在 DOM 上绑定原生事件等。这些都不会走 React 的调度流程,你在这种情况下调用 setState ,那这次 setState 就是同步的。 否则就是异步的。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值