0
点赞
收藏
分享

微信扫一扫

Vue 面试小结1

yeamy 2022-02-27 阅读 65
一、异步加载组件 vue2.0 和 3.0的区别

1.vue2.0 通过设置 component 的方式,加载先前导入过的组件。

2.vue3.0 通过defineAsynComponent方法,import 组件文件。

二、new Vue() 都做了些什么?

1.声明一个实例对象。src/core/instance/index.js ,这里的Vue是一个funciton形式实现的类。

2.执行Vue构造函数,this._init(option)初始化入参。

3.上下文转移到vm

4.如果 options._isComponent 为 true,则初始化内部组件实例;否则合并配置参数,并挂载到 vm.$options 上面;

5.初始化生命周期函数、初始化事件相关、初始化渲染相关;

 6.执行 beforeCreate 生命周期函数;

 7.在初始化 state/props 之前初始化注入 inject;

 8.初始化 state/props 的数据双向绑定;

 9.在初始化 state/props 之后初始化 provide;

 10.执行 created 生命周期函数;

11.挂载到 dom 元素

三、Vue.use做了什么

1.检查插件是否注册,若已注册,则直接跳出;

2.处理入参,将第一个参数之后的参数归集,并在首部塞入 this 上下文;

3.执行注册方法,调用定义好的 install 方法,传入处理的参数,若没有 install 方法并且插件本身为 function 则直接进行注册

四、vue中组件渲染/更新的逻辑

1.渲染时,将template引入后,Vue调用render函数解析模板,通过createElement方法把app对象渲染成一个虚拟节点,并通过observe方法为各个属性添加响应式。

2.更新时,属性的setter方法通知到组件的Watcher实例,重新执行vm.render()函数,进行虚拟dom.diff函数来更新节点。

五、双向数据绑定原理

1.Model 改变 View:

  defineReactive 中通过 Object.defineProperty 使 data 可响应;

  Dep 在 getter 中作依赖收集,在 setter 中作派发更新;

  dep.notify() 通知 Watcher 更新,最终调用 vm._render() 更新 UI;

2.View 改变 Model:

  其实同上理,View 与 data 的数据关联在了一起,

  View 通过事件触发 data 的变化,从而触发了 setter,这就构成了一个双向循环绑定了;

六、vue3 为何用 proxy 替代了 Object.defineProperty

1.vue2中的响应式原理是通过observe方法加上发布订阅模式来实现的。observe方法依次处理对象的各个属性它调用 defineReactive,如果是对象通过 Object.defineProperty()方法给每个属性注册setter,getter方法并添加订阅发布。

  如果是数组则通过Object.create(Array.prototype) 复制 Array 原型链为新的对象覆盖该数组原型的法,扩展它的 7 个变更法(push/pop/shift/unshift/splice/reverse/sort),

  订阅发布:在属性被调用的时候,get函数触发addSub方法,将该组件的Watcher实例加入道订阅者中进行保存,而当属性被修改的时候,set函数触发notify通知订阅者数组里面每个组件的watcher实例进行视图更新。

      视图更新的时候通过watcher重新执行render()函数。

2.vue2 响应式的不足:

 2.1 动态添加/删除属性的时候,只能通过 Vue.set/delete 方法强制更新。

 2.2 无法使用数组的下标对数组元素进行更新。

 2.3 defineReactive添加响应式(Watcher)监听的时候做了递归,对性能有影响。

3.Proxy为es6中的方法,该方法不需要遍历data中的每一个属性,对他们进行响应式的处理,而是直接代理了整个data对象,

  拦截这个对象所包含的所有属性的get/set方法。这样做数组的任何操作也都能触发响应。

  为了解决2.3的问题,vue3.0中定义了effect函数,effect会先将传入的回调函数保存起来,然后第一次执行cb(),

  在执行的过程中,触发了obj.name属性的get代理函数,在代理函数里面做订阅track(target), 而track函数则会将存在全局数组中的cb函数取出来,

  保存在map中,类似于map.set('name',cb)。之后,在name属性被修改的时候,会触发name属性的set代理函数,在代理函数里面执行trigger。

  最后,trigger会将之前存在map里的订阅着取出来执行,map.get('name')()。

七、computed 和 watch 的区别

1. computed 属性的更新需要依赖于其引用属性的更新触发标记 dirty: true,进而触发 computed 属性 getter 的时候才会触发其本身的更新,否则其不更新;

2. watch 属性则是依赖于本身已被 Dep 收集依赖的部分属性,即作为 data/props 中的某个属性的尾随 watcher,在监听属性更新时触发 watcher 的回调;否则监听则无意义;

3. 这里再引申一下使用场景:

 如果一个数据依赖于其他数据,那么就使用 computed 属性;

 如果你需要在某个数据变化时做一些事情,使用 watch 来观察这个数据变化;

八、说一下 vue 中所有带$的方法?

1. 实例 property

 vm.$data: Vue 实例观察的数据对象。Vue 实例代理了对其 data 对象 property 的访问。

 vm.$props: 当前组件接收到的 props 对象。Vue 实例代理了对其 props 对象 property 的访问。

 vm.$el: Vue 实例使用的根 DOM 元素。

 vm.$options: 用于当前 Vue 实例的初始化选项。

 vm.$parent: 父实例,如果当前实例有的话。

 vm.$root: 当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。

 vm.$children: 当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源。

 vm.$slots: 用来访问被插槽分发的内容。每个具名插槽有其相应的 property (例如:v-slot:foo 中的内容将会在 vm.$slots.foo 中被找到)。default property 包括了所有没有被包含在具名插槽中的节点,或 v-slot:default 的内容。

 vm.$scopedSlots: 用来访问作用域插槽。对于包括 默认 slot 在内的每一个插槽,该对象都包含一个返回相应 VNode 的函数。

 vm.$refs: 一个对象,持有注册过 ref attribute 的所有 DOM 元素和组件实例。

 vm.$isServer: 当前 Vue 实例是否运行于服务器。

 vm.$attrs: 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

 vm.$listeners: 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

2. 实例方法 / 数据

 vm.$watch( expOrFn, callback, [options] ): 观察 Vue 实例上的一个表达式或者一个函数计算结果的变化。回调函数得到的参数为新值和旧值。表达式只接受监督的键路径。对于更复杂的表达式,用一个函数取代。

 vm.$set( target, propertyName/index, value ): 这是全局 Vue.set 的别名。

 vm.$delete( target, propertyName/index ): 这是全局 Vue.delete 的别名。


3. 实例方法 / 事件

 vm.$on( event, callback ): 监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的额外参数。

 vm.$once( event, callback ): 监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除。

 vm.$off( [event, callback] ): 移除自定义事件监听器。

 如果没有提供参数,则移除所有的事件监听器;

 如果只提供了事件,则移除该事件所有的监听器;

 如果同时提供了事件与回调,则只移除这个回调的监听器。

 vm.$emit( eventName, […args] ): 触发当前实例上的事件。附加参数都会传给监听器回调。


4. 实例方法 / 生命周期

 vm.$mount( [elementOrSelector] )

 如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可以使用 vm.$mount() 手动地挂载一个未挂载的实例。

 如果没有提供 elementOrSelector 参数,模板将被渲染为文档之外的的元素,并且你必须使用原生 DOM API 把它插入文档中。

 这个方法返回实例自身,因而可以链式调用其它实例方法。

 vm.$forceUpdate(): 迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。

 vm.$nextTick( [callback] ): 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。

 vm.$destroy(): 完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。

 触发 beforeDestroy 和 destroyed 的钩子。


九、父组件和子组件生命周期钩子的顺序?

渲染过程:

父 beforeCreate->父 created->父 beforeMount->子 beforeCreate->子 created->子 beforeMount->子 mounted->父 mounted

子组件更新影响到父组件过程:

父 beforeUpdate->子 beforeUpdate->子 updated->父 updated

父/子 组件更新过程不影响其他:

父/子 beforeUpdate->父/子 updated

父组件更新影响到子组件:

父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

销毁过程:

父 beforeDestroy->子 beforeDestroy->子 destroyed->父 destroyed

十、nextTick的实现。

1. 回调函数先进入队列等待,

2. 执行timeFunc ,按浏览器的支持顺序,promise-->mutationObserver-->setImmediate-->setTimeout 使用微队列,

3. 执行flushCallbacks,标记pending 完成,然后先复制 callback,再清理 callback。

十一、缓存方案:

nginx 配置中添加header cacheControl:no-cache ;


        #非html缓存1个月

           if ($request_uri ~* .*[.](js|css|map|jpg|png|svg|ico)$) {

               add_header Cache-Control "public, max-age=2592000";

           }

           #html文件协商缓存,也就是每次都询问服务器,浏览器本地是是否是最新的,是最新的就直接用,非最新的服务器就会返回最新

           if ($request_filename ~* ^.*[.](html|htm)$) {

              add_header Cache-Control "public, no-cache";

          }

效果:发版之后,用户首次访问会根据不同的文件类型,获取 header 中的缓存策略,非html的缓存,浏览器会设置 ETag=随机数,

   当下次访问的时候,从浏览器中获取ETag 跟服务器上的ETag做比对,若一致,则返回304,使用本地缓存,若不一致,则取服务器端文件进行缓存更新。

 --- 可解决发版之后用户浏览器文件更新不及时的问题。

举报

相关推荐

0 条评论