一、异步加载组件 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,使用本地缓存,若不一致,则取服务器端文件进行缓存更新。
--- 可解决发版之后用户浏览器文件更新不及时的问题。