vue双向数据绑定原理
Observer:能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
Compile:对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
Watcher:作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
参考:https://blog.csdn.net/yihanzhi/article/details/79731813
大致原理:https://segmentfault.com/a/1190000013294870
proxy
vue里代替defineproperty原因:
- Proxy无需一层层递归为每个属性添加代理,一次即可完成以上操作
- 性能上更好,并且原本的实现有一些数据更新不能监听到,但Proxy可以完美监听到任何方式的数据改变
- 缺点:浏览器兼容性
生命周期
- 创建前/后:在 beforeCreated 阶段,Vue 实例的挂载元素 el和数据对象data以及事件还未初始化。在created阶段,Vue实例的数据对象data以及方法的运算有了,el 和数据对象 data 以及事件还未初始化。
- 载入前/后:在 beforeMount 阶段,render 函数首次被调用,Vue 实例的 $el 和 data 都初始化了,但还是挂载在虚拟的 DOM 节点上。在 mounted 阶段,Vue 实例挂载到实际的 DOM 操作完成,一般在该过程进行 Ajax 交互。
- 更新前/后:在数据更新之前调用,即发生在虚拟 DOM 重新渲染和打补丁之前,调用 beforeUpdate。在虚拟 DOM 重新渲染和打补丁之后,会触发 updated 方法。
- 销毁前/后:在执行实例销毁之前调用 beforeDestory,此时实例仍然可以调用。在执行 destroy 方法后,对 data 的改变不会再触发周期函数,说明此时 Vue 实例已经解除了事件监听以及和 DOM 的绑定,但是 DOM 结构依然存在。beforeDestroy 钩子函数的执行时机是在 $destroy 函数执行最开始的地方,接着执行了一系列的销毁动作,包括从 parent 的 $children 中删掉自身,删除 watcher,当前渲染的 VNode 执行销毁钩子函数等,执行完毕后再调用 destroy 钩子函数。在 $destroy 的执行过程中,它又会执行 vm.patch(vm._vnode, null) 触发它子组件的销毁钩子函数,这样一层层的递归调用,所以 destroy 钩子函数执行顺序是先子后父,和 mounted 过程一样
- activated 和 deactivated 钩子函数是专门为 keep-alive 组件定制的钩子
vue路由原理
都可以经过修改URL,在不重新请求页面的情况下更新页面视图。
会对mode做一些校验:若浏览器不支持HTML5History方式(通过supportsPushState变量判断),则mode设为hash;若不是在浏览器环境下运行,则mode设为abstract;
hash:
- 比如 ‘http://www.baidu.com/#/abc’ hash 的值为 ‘#/abc’
- 虽然在地址里,但是不会在 HTTP 请求中,因此改变 hash 不会重新加载页面,会触发 onhashchange 事件
history:
- 通过h5中新增的方法pushState() replaceState(),可以新增修改历史记录
- 虽然改变了当前的 URL,但你浏览器不会立即向后端发送请求(重新加载会发送请求)
- 通过onpopstate 事件,监听history变化
history优势:
- pushState设置的新url可以是与当前url同源的任意url,而hash只可修改#后面的部分
- pushState设置的新url可以与当前url一模一样,这样也会把记录添加到栈中,而hash设置的新值必须与原来不一样才会触发记录添加到栈中
- pushState通过stateObject可以添加任意类型的数据记录中,而hash只可添加短字符串
- pushState可额外设置title属性供后续使用
history模式的问题:
对于单页应用来说,理想的使用场景是仅在进入应用时加载index.html,后续在的网络操作通过ajax完成,不会根据url重新请求页面,但是如果用户直接在地址栏中输入并回车,浏览器重启重新加载等特殊情况。
hash模式仅改变hash部分的内容,而hash部分是不会包含在http请求中的(hash带#):所以hash模式下遇到根据url请求页面不会有问题
而history模式则将url修改的就和正常请求后端的url一样(history不带#)(http://oursite.com/user/id),如果这种向后端发送请求的话,后端没有配置对应/user/id的get路由处理,会返回404错误。
https://blog.csdn.net/weixin_58089129/article/details/120618550
https://blog.csdn.net/weixin_43953753/article/details/86912845
https://segmentfault.com/a/1190000014822765?utm_source=tag-newest#articleHeader1
vuex原理:
https://www.jianshu.com/p/d95a7b8afa06
https://mp.weixin.qq.com/s/igkif-J_BHd1q5mZ7TewCw
https://blog.csdn.net/weixin_44003190/article/details/104334140
vuex =>
return {
Store: Store,
install: install,
version: '3.1.1',
mapState: mapState,
mapMutations: mapMutations,
mapGetters: mapGetters,
mapActions: mapActions,
createNamespacedHelpers: createNamespacedHelpers
}
vuex的store是如何注入到组件中的?
Vue.use(Vuex)
–> 有install方法就执行install,没有直接执行函数
–> 执行install(不重复install判断)
–> applyMixin() = Vue.mixin({ beforeCreate: vuexInit })
function vuexInit () {
var options = this.$options;
// store injection
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store() : options.store;
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store;
}
}
vuex的state和getters是如何映射到各个组件实例中响应式更新状态呢?
const wrappedGetters = store._wrappedGetters
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
computed[key] = partial(fn, store) // fn(store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
store._vm = new Vue({
// new vue:state存入vue实例组件的data中,data是响应式的,实现双向数据绑定
data: {
$$state: state
},
// getters:借助vue的计算属性computed实现数据实时监听
computed
})
关于modules
getters:
- 是不能注册相同名称的方法
- 如果没有注册命名空间,想获取a模块中的getters中的方法,用法为this.$store.getters.属性名
- 如果注册了命名空间,想获取a模块中的getters中的方法,用法为this.$store.getters[‘a/xx’]
mutations
- 可以注册相同名称的方法,相同方法会存入到数组中按顺序执行
- 如果没有注册命名空间,想获取a模块中的mutations中的方法,用法为this.$store.commit(‘属性名’)
- 如果注册了命名空间,想获取a模块中的mutations中的方法,用法为this.$store.commit[‘a/xx’]
actions
actions和mutations原理几乎一模一样,主要区别如下
- 调用时接受的参数不是state而是一个经过处理的对象可以解构出state,commit等属性
- 调用时没有命名空间是this.$ store.dispatch(xx) ,有命名空间是this.$store.dispatch(‘a/xx’)
computed
参考:https://segmentfault.com/a/1190000010408657
https://blog.csdn.net/NewTyun/article/details/104013076
function initComputed (vm, computed) {
var watchers = vm._computedWatchers = Object.create(null);
for (var key in computed) {
var userDef = computed[key];
var getter = typeof userDef === 'function' ? userDef : userDef.get;
{
if (getter === undefined) {
warn(("No getter function has been defined for computed property \"" + key + "\"."), vm);
getter = noop;
}
}
// create internal watcher for the computed property.
watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions);
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
if (!(key in vm)) {
defineComputed(vm, key, userDef);
} else {
if (key in vm.$data) {
warn(("The computed property \"" + key + "\" is already defined in data."), vm);
} else if (vm.$options.props && key in vm.$options.props) {
warn(("The computed property \"" + key + "\" is already defined as a prop."), vm);
}
}
}
}
function defineComputed (target, key, userDef) {
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = createComputedGetter(key);
sharedPropertyDefinition.set = noop;
} else {
sharedPropertyDefinition.get = userDef.get
? userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop;
sharedPropertyDefinition.set = userDef.set
? userDef.set
: noop;
}
Object.defineProperty(target, key, sharedPropertyDefinition);
}
function createComputedGetter (key) {
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();)
}
return watcher.value
}
}
}
Watcher.prototype.evaluate = function evaluate () {
this.value = this.get();
this.dirty = false;
};
//顺藤摸瓜 再看get是怎么实现的
Watcher.prototype.get = function get () {
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
}
bus
vue中的bus事件,一般作为中央事件总线来使用
简单例子:比如在A,B组件为兄弟组件,现在A要调用B的中C事件
1.创建一个bus.js
import Vue from 'vue'
const Bus = new Vue()
export { Bus }
2.在A,B组件中引入bus.js
import { Bus } from 'bus'
3.在A组件中定义要调用B事件的bus事件名
例如:Bus.$emit('callC')
4.在B组件中调用C方法
Bus.$on('callC',this.C)
Bus.$on里有两个参数,第一个是在A组件定义的名字,第二个参数是B组件要调用的方法
vue和react对比
https://blog.csdn.net/m0_37631322/article/details/80719756
相同点:
都是组件化开发,props传递
都是虚拟dom
配套框架
区别
模板 vs JSX
对象属性和状态管理
虚拟DOM Diff算法
- 虚拟 dom
- 虚拟 dom 是利用 js 描述元素与元素的关系,用 js 对象来表示真实的 DOM 树结构,创建一个虚拟 DOM 对象
- 由于在浏览器中操作 DOM 是很昂贵的。
- 频繁的操作 DOM,会产⽣⼀定的性能问题.
- 在组件渲染的时候会调用 render 函数,这个函数会生成一个虚拟 dom,再根据这个虚拟 dom 生成真实的 dom,然后这个真实的 dom 会挂载到我们的页面中。
- 如果只是渲染一个页面后期不改动的话 那么虚拟 dom 其实成本更高 因为 都要渲染成真实的 dom
- 如果组件内有响应的数据,数据发生改变的时候 render 函数会生成一个新的虚拟 dom ,新的虚拟 dom 树和旧的虚拟 dom 树进行对比,找到要要修改的虚拟 dom 的部分,去修改相对应部分的真实 dom
- diff 算法
-
diff 算法就是对虚拟 dom 进行对比,并返回一个 patch 对象,这个对象的作用是存储两个节点不同的地方,最后用 patch 里记录的信息去局部更新真实的 dom
-
diff 算法的步骤
-
js 对象表示真实的 dom 结构,就是我们说的生成一个虚拟 dom,再用虚拟 dom 构建一个真的 dom 树,放到页面中。
-
状态改变的时候生成一个新的虚拟 dom 跟旧的进行对比,这个对比的过程就是 diff 算法,通过 patch 对象记录差异
-
把记录的差异用在第一个虚拟 dom 构建的真实的 dom 上,视图就更新了
-
Vue 的 diff 算法是平级⽐较,不考虑跨级⽐较的情况。内部采⽤深度递归的⽅式+双指针⽅式⽐较
原理简述:
(1)先去同级比较,然后再去比较子节点
(2)先去判断一方有子节点一方没有子节点的情况
(3)比较都有子节点的情况
(4)递归比较子节点
https://blog.csdn.net/qq_42072086/article/details/107965196