1. Vue的响应式原理
数据驱动视图,UI =render(state),state状态发生变化,vue经过模板编译,虚拟DOM,patch过程更新视图。vue2.x会使用object.defineProperty重新当以data中的所有属性,当页面使用对应属性,首先进行数据收集,如果是属性变化会通知相关依赖进行更新操作。vue3.x改用proxy替换object.defineproperty,因为Proxy可以直接监听对象和数组的变化。
1. Vue的双向绑定
双向绑定是通过数据劫持结合发布者-订阅者模式来实现的,get是收集依赖,set进行数据变化更新操作,实现一个监听器observer,用来劫持并监听所有属性,如果有变动,通知订阅者,实现一个订阅器watcher,可以收到属性的变化并执行响应的函数,从而更新视图,实现一个解析器compile,可以扫描解析每个节点的相关指令,并根据初始化模板数据一起初始化响应的订阅器
1. Vue的neextTick原理
在下次DOM更新循环之后执行延迟回调,nextTick主要使用了宏任务和微任务,根据执行环境分别尝试采用promise,mutationObserver,setImmediate定义一个异步方法,多次调用nextTick会将方法存入任务队列中,通过异步方法清空当前队列
4.Vue组件的生命周期
一个组件从创建,数据初始化,挂载,更新,渲染,更新,销毁的过程称为生命周期。
1. 创建前后:
2. 载入前后:
3. 销毁前后:
4. 更新前后:
Vue父子组件的生命周期调用顺序
组件的调用顺序都是先父后子,渲染完成时先子后父
组件的销毁时先父后子,销毁完成是先子后父
computed和watch的区别
computed具有缓存功能,只有依赖的数据发生了变化才触发computed
watcher没有缓存功能,可以监听数据执行回调,深度监听的话对象中的属性时,可以打开deep-true选项,watcher支持异步,有两个参数,newData,和oldData,=,两个属性,deep:深入观察没一层层遍历对象,immediate:组件加载立即执行回调函数
组件data为什么是函数
一个组件被复用多次的话,也就会创建多个实例,本质上这些实例用的都是同一个构造函数,如果data是对象的话,对象属于引用类型,会影响到所有的实例
组件间如何通信
1. 父子组件通信
- 父组件通过props向子组件传值
- 子组件通过emit,on向父组件传值
2. 兄弟组件通信
3. 跨组件通信
diff算法事件复杂度
1. 判断是否是静态节点
2. VNode是否是文本节点
3. VNode是元素节点
正常比较DOM-Diff是 O(n^3),Vue2的diff算法采用了双端比较优化算法和只有当新旧children都为多个子节点时才需要用黑犀牛的Diff算法进行同层级比较时间复杂度就是O(n)
vue中keep-alive有什么用
我们可以把一些不常变动的组件或者需要缓存的组件用包裹起来,这样就会帮我们把组件保存在内存中,而不是直接的销毁
好处:保留组件的状态或避免多次渲染,提高了页面性能
有一个淘汰策略LRU,
生命周期钩子:activated:缓存组件激活的时候,deactivated:缓存组件销毁的时候
Vue中history和hash实现的原理
https://juejin.cn/post/6844903589123457031
class Routers {
constructor() {
// 储存hash与callback键值对
this.routes = {
};
// 当前hash
this.currentUrl = '';
// 记录出现过的hash
this.history = [];
// 作为指针,默认指向this.history的末尾,根据后退前进指向history中不同的hash
this.currentIndex = this.history.length - 1;
this.refresh = this.refresh.bind(this);
this.backOff = this.backOff.bind(this);
// 默认不是后退操作
this.isBack = false;
window.addEventListener('load', this.refresh, false);
window.addEventListener('hashchange', this.refresh, false);
}
route(path, callback) {
this.routes[path] = callback || function() {
};
}
refresh() {
this.currentUrl = location.hash.slice(1) || '/';
if (!this.isBack) {
// 如果不是后退操作,且当前指针小于数组总长度,直接截取指针之前的部分储存下来
// 此操作来避免当点击后退按钮之后,再进行正常跳转,指针会停留在原地,而数组添加新hash路由
// 避免再次造成指针的不匹配,我们直接截取指针之前的数组
// 此操作同时与浏览器自带后退功能的行为保持一致
if (this.currentIndex < this.history.length - 1) {
this.history = this.history.slice(0, this.currentIndex + 1);
}
this.history.push(this.currentUrl);
this.currentIndex++;
}
this.routes[this.currentUrl]();
console.log('指针:', this.currentIndex, 'history:', this.history);
this.isBack = false;
}
// 后退功能
backOff() {
// 后退操作设置为true
this.isBack = true;
this.currentIndex <= 0
? (this.currentIndex = 0)
: (this.currentIndex = this.currentIndex - 1);
location.hash = `#${
this.history[this.currentIndex]}`;
this.routes[this.history[this.currentIndex]]();
}
}
history模式:
class Routers {
constructor() {
this.routes = {
};
// 在初始化监听popstate事件
this._bindPopState();
}
// 初始化路由 replaceState
init(path) {
history.replaceState({
path: path}, null, path);
this.routes[path] && this.routes[path]();
}
// 将路径和对应对调函数加入hashMap存储
route(path, callback) {
this.routes[path] = callback || function() {
};
}
// 触发路由对应回调 pushState
go(path) {
history.pushState({
path: path}, null, path);
this.routes[path] && this.routes[path]();
}
// 监听popstate事件
_bindPopState() {
window.addEventListener('popstate', e => {
const path = e.state && e.state.path;
this.routes[path] && this.routes[path]();
});
}
}
window.Router = new Routers();
Router.init(location.pathname); // 初始化路由
const content = document.querySelector('body');
const ul = document.querySelector('ul');
function changeBgColor(color) {
content.style.backgroundColor = color;
}
Router.route('/', function() {
changeBgColor('yellow');
});
Router.route('/blue', function() {
changeBgColor('blue');
});
Router.route('/green', function() {
changeBgColor('green');
});
ul.addEventListener('click', e => {
if (e.target.tagName === 'A') {
e.preventDefault();
Router.go(e.target.getAttribute('href'));
}
});
vuex的原理
https://juejin.cn/post/6844904081119510536