位置: IT常识 - 正文

Vue 原理整理(vue3 原理)

编辑:rootadmin
Vue 原理整理

目录

1. 组件化基础=>(MVVM模型)

2. Vue的响应式原理

3.为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?

4.computed 的实现原理

5. computed 和 watch 有什么区别及运用场景? 

6. Vue 中的 key 到底有什么用?

7.谈一谈 nextTick 的原理   

8.vue 是如何对数组方法进行变异的 ? 

9.Vue 组件 data 为什么必须是函数 ? 

10. 谈谈 Vue 事件机制, 手写$on,$off,$emit,$once 

11.说说 Vue 的渲染过程

12.聊聊 keep-alive 的实现原理和缓存策略 

13. vm.$set()实现原理是什么? 

14.虚拟DOM(vdom)和diff算法 

15.模板编译

15.组件渲染更新过程

16.前端路由原理

17.Proxy

1. 组件化基础=>(MVVM模型)

推荐整理分享Vue 原理整理(vue3 原理),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:vue原理实现,vue原理图,vue-cli原理,vue底层原理和源代码分析,vue的原理是什么意思,vue底层原理和源代码分析,vue的原理是什么意思,vuecli原理,内容如对您有帮助,希望把文章链接给更多的朋友!

传统组件,知识静态渲染,更新依赖于操作DOM。

Vue的核心理念是数据驱动的理念,所谓的数据驱动的理念:当数据发生变化的时候,用户界面也会发生相应的变化,开发者并不需要手动的去修改dom。

优点:

        不需要在代码中去频繁的操作dom了,这样提高了开发的效率,同时也避免了在操作Dom的时候出现的错误。

Vue.js的数据驱动是通过MVVM这种框架来实现的,MVVM 框架主要包含三部分:Model, View, ViewMode

数据驱动视图 - Vue MVVM

MVVM是Model-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModel。Model代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到ViewModel层并自动将数据渲染到页面中,视图变化的时候通知viewModel层更新数据。

2. Vue的响应式原理

 核心实现类:

Observer : 它的作用是给对象的属性添加 getter 和 setter,用于依赖收集和派发更新。Dep : 用于收集当前响应式对象的依赖关系,每个响应式对象包括子对象都拥有一个 Dep 实例(里面 subs 是 Watcher 实例数组),当数据有变更时,会通过 dep.notify()通知各个 watcher。Watcher : 观察者对象 , 实例分为渲染 watcher (render watcher),计算属性 watcher (computed watcher),侦听器 watcher(user watcher)三种

Watcher 和 Dep 的关系         watcher 中实例化了 dep 并向 dep.subs 中添加了订阅者,dep 通过 notify 遍历了 dep.subs 通知每个 watcher 更新。

依赖收集         initState 时,对 computed 属性初始化时,触发 computed watcher 依赖收集         initState 时,对侦听属性初始化时,触发 user watcher 依赖收集         render()的过程,触发 render watcher 依赖收集         re-render 时,vm.render()再次执行,会移除所有 subs 中的 watcer 的订阅,重新赋值。派发更新         组件中对响应的数据进行了修改,触发 setter 的逻辑         调用 dep.notify()         遍历所有的 subs(Watcher 实例),调用每一个 watcher 的 update 方法。

原理:

        当创建 Vue 实例时,vue 会遍历 data 选项的属性,利用 Object.defineProperty 为属性添加 getter 和 setter 对数据的读取进行劫持(getter 用来依赖收集,setter 用来派发更新),并且在内部追踪依赖,在属性被访问和修改时通知变化。

        每个组件实例会有相应的 watcher 实例,会在组件渲染的过程中记录依赖的所有数据属性(进行依赖收集,还有 computed watcher,user watcher 实例),之后依赖项被改动时,setter 方法会通知依赖与此 data 的 watcher 实例重新计算(派发更新),从而使它关联的组件重新渲染。

总结:

        vue.js 采用数据劫持结合发布-订阅模式,通过 Object.defineproperty 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调。

Object.defineProperty 实现响应式

监听对象,监听数组复杂对象,深度监听const obj = {};const data = {};const name = 'zhangsan';Object.defineProperty(data, 'name', { get: function () { console.log('get'); return name; }, set: function (newVal) { console.log('set'); obj.name = newVal; }})console.log(data.name);data.name = 'lisi';console.log(obj.name);

Object.defineProperty 的缺点

深度监听需要递归到底,一次性计算量大无法监听新增属性、删除属性(要使用 Vue.set  Vue.delete)无法原生监听数组,需要特殊处理【对数组的方法重写,['push', 'pop', 'shift', 'unshift', 'splice','sort','reverse'] 这几个方法更改数组才会响应式变化,直接更改索引不会响应式改变】

对数组的特殊处理:

// 触发更新视图function updateView() { console.log('视图更新')}// 重新定义数组原型const oldArrayProperty = Array.prototype// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型const arrProto = Object.create(oldArrayProperty);['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => { arrProto[methodName] = function () { updateView() // 触发视图更新 oldArrayProperty[methodName].call(this, ...arguments) // Array.prototype.push.call(this, ...arguments) }})// 重新定义属性,监听起来function defineReactive(target, key, value) { // 深度监听 observer(value) // 核心 API Object.defineProperty(target, key, { get() { return value }, set(newValue) { if (newValue !== value) { // 深度监听 observer(newValue) // 设置新值 // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值 value = newValue // 触发更新视图 updateView() } } })}// 监听对象属性function observer(target) { if (typeof target !== 'object' || target === null) { // 不是对象或数组 return target } // 污染全局的 Array 原型 // Array.prototype.push = function () { // updateView() // ... // } if (Array.isArray(target)) { target.__proto__ = arrProto } // 重新定义各个属性(for in 也可以遍历数组) for (let key in target) { defineReactive(target, key, target[key]) }}// 准备数据const data = { name: 'zhangsan', age: 20, info: { address: '北京' // 需要深度监听 }, nums: [10, 20, 30]}// 监听数据observer(data)// 测试// data.name = 'lisi'// data.age = 21// // console.log('age', data.age)// data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set// delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete// data.info.address = '上海' // 深度监听data.nums.push(4) // 监听数组3.为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?

Object.defineProperty 本身有一定的监控到数组下标变化的能力,但是在 Vue 中,从性能/体验的性价比考虑,尤大大就弃用了这个特性(Vue 为什么不能检测数组变动 )。为了解决这个问题,经过 vue 内部处理后可以使用以下几种方法来监听数组    ['push', 'pop', 'shift', 'unshift', 'splice','sort','reverse']

由于只针对了以上 7 种方法进行了 hack 处理,所以其他数组的属性也是检测不到的,还是具有一定的局限性。

Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue 2.x 里,是通过 递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象是才是更好的选择。

Proxy 可以劫持整个对象,并返回一个新的对象。Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。 

4.computed 的实现原理

computed 本质是一个惰性求值的观察者。

computed 内部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立刻求值,同时持有一个 dep 实例。

其内部通过 this.dirty 属性标记计算属性是否需要重新求值。

当 computed 的依赖状态发生改变时,就会通知这个惰性的 watcher,

computed watcher 通过 this.dep.subs.length 判断有没有订阅者,

有的话,会重新计算,然后对比新旧值,如果变化了,会重新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 重新渲染,本质上是一种优化。)

没有的话,仅仅把 this.dirty = true。 (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。)

5. computed 和 watch 有什么区别及运用场景? 

区别:

computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。

watch 侦听器 : 更多的是「观察」的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。 

运用场景

当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算。

当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

6. Vue 中的 key 到底有什么用?

key 是给每一个 vnode 的唯一 id,依靠 key,我们的 diff 操作可以更准确、更快速 (对于简单列表页渲染来说 diff 节点也更快,但会产生一些隐藏的副作用,比如可能不会产生过渡效果,或者在某些节点有绑定数据(表单)状态,会出现状态错位。)

diff 算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的 key 与旧节点进行比对,从而找到相应旧节点.

更准确 : 因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确,如果不加 key,会导致之前节点的状态被保留下来,会产生一系列的 bug。

更快速 : key 的唯一性可以被 Map 数据结构充分利用,相比于遍历查找的时间复杂度 O(n),Map 的时间复杂度仅仅为 O(1),源码如下: 

function createKeyToOldIdx(children, beginIdx, endIdx) { let i, key; const map = {}; for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key; if (isDef(key)) map[key] = i; } return map;}7.谈一谈 nextTick 的原理   

JS 运行机制

JS 执行是单线程的,它是基于事件循环的。事件循环大致分为以下几个步骤:

所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。 主线程不断重复上面的第三步。

主线程的执行过程就是一个 tick,而所有的异步结果都是通过 “任务队列” 来调度。 消息队列中存放的是一个个的任务(task)。 规范中规定 task 分为两大类,分别是 macro task 和 micro task,并且每个 macro task 结束后,都要清空所有的 micro task。 

for (macroTask of macroTaskQueue) { // 1. Handle current MACRO-TASK handleMacroTask(); // 2. Handle all MICRO-TASK for (microTask of microTaskQueue) { handleMicroTask(microTask); }}

在浏览器环境中 :

        常见的 macro task 有 setTimeout、MessageChannel、postMessage、setImmediate

        常见的 micro task 有 MutationObsever 和 Promise.then

异步更新队列

        可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。

        如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。

        然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。

        Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

        在 vue2.5 的源码中,macrotask 降级的方案依次是:setImmediate、MessageChannel、setTimeout

vue 的 nextTick 方法的实现原理:         vue 用异步队列的方式来控制 DOM 更新和 nextTick 回调先后执行 microtask 因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕考虑兼容问题,vue 做了 microtask 向 macrotask 的降级方案

8.vue 是如何对数组方法进行变异的 ? 

源码:

const arrayProto = Array.prototype;export const arrayMethods = Object.create(arrayProto);const methodsToPatch = ["push","pop","shift","unshift","splice","sort","reverse"];/** * Intercept mutating methods and emit events */methodsToPatch.forEach(function(method) { // cache original method const original = arrayProto[method]; def(arrayMethods, method, function mutator(...args) { const result = original.apply(this, args); const ob = this.__ob__; let inserted; switch (method) { case "push": case "unshift": inserted = args; break; case "splice": inserted = args.slice(2); break; } if (inserted) ob.observeArray(inserted); // notify change ob.dep.notify(); return result; });});/** * Observe a list of Array items. */Observer.prototype.observeArray = function observeArray(items) { for (var i = 0, l = items.length; i < l; i++) { observe(items[i]); }};

 简单来说,Vue 通过原型拦截的方式重写了数组的 7 个方法,首先获取到这个数组的ob,也就是它的 Observer 对象,如果有新的值,就调用 observeArray 对新的值进行监听,然后手动调用 notify,通知 render watcher,执行 update

9.Vue 组件 data 为什么必须是函数 ? 

new Vue()实例中,data 可以直接是一个对象,为什么在 vue 组件中,data 必须是一个函数呢? 

因为组件是可以复用的,JS 里对象是引用关系,如果组件 data 是一个对象,那么子组件中的 data 属性值会互相污染,产生副作用。

所以一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。new Vue 的实例是不会被复用的,因此不存在以上问题。

10. 谈谈 Vue 事件机制, 手写$on,$off,$emit,$once 

Vue 事件机制 本质上就是 一个 发布-订阅 模式的实现。 

class Vue { constructor() { // 事件通道调度中心 this._events = Object.create(null); } $on(event, fn) { if (Array.isArray(event)) { event.map(item => { this.$on(item, fn); }); } else { (this._events[event] || (this._events[event] = [])).push(fn); } return this; } $once(event, fn) { function on() { this.$off(event, on); fn.apply(this, arguments); } on.fn = fn; this.$on(event, on); return this; } $off(event, fn) { if (!arguments.length) { this._events = Object.create(null); return this; } if (Array.isArray(event)) { event.map(item => { this.$off(item, fn); }); return this; } const cbs = this._events[event]; if (!cbs) { return this; } if (!fn) { this._events[event] = null; return this; } let cb; let i = cbs.length; while (i--) { cb = cbs[i]; if (cb === fn || cb.fn === fn) { cbs.splice(i, 1); break; } } return this; } $emit(event) { let cbs = this._events[event]; if (cbs) { const args = [].slice.call(arguments, 1); cbs.map(item => { args ? item.apply(this, args) : item.call(this); }); } return this; }}11.说说 Vue 的渲染过程

 调用 compile 函数,生成 render 函数字符串 ,编译过程如下:

parse 函数解析 template,生成 ast(抽象语法树)optimize 函数优化静态节点 (标记不需要每次都更新的内容,diff 算法会直接跳过静态节点,从而减少比较的过程,优化了 patch 的性能)generate 函数生成 render 函数字符串

调用 new Watcher 函数,监听数据的变化,当数据发生变化时,Render 函数执行生成 vnode 对象 调用 patch 方法,对比新旧 vnode 对象,通过 DOM diff 算法,添加、修改、删除真正的 DOM 元素

12.聊聊 keep-alive 的实现原理和缓存策略 

源码

export default { name: "keep-alive", abstract: true, // 抽象组件属性 ,它在组件实例建立父子关系的时候会被忽略,发生在 initLifecycle 的过程中 props: { include: patternTypes, // 被缓存组件 exclude: patternTypes, // 不被缓存组件 max: [String, Number] // 指定缓存大小 }, created() { this.cache = Object.create(null); // 缓存 this.keys = []; // 缓存的VNode的键 }, destroyed() { for (const key in this.cache) { // 删除所有缓存 pruneCacheEntry(this.cache, key, this.keys); } }, mounted() { // 监听缓存/不缓存组件 this.$watch("include", val => { pruneCache(this, name => matches(val, name)); }); this.$watch("exclude", val => { pruneCache(this, name => !matches(val, name)); }); }, render() { // 获取第一个子元素的 vnode const slot = this.$slots.default; const vnode: VNode = getFirstComponentChild(slot); const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions; if (componentOptions) { // name不在inlcude中或者在exlude中 直接返回vnode // check pattern const name: ?string = getComponentName(componentOptions); const { include, exclude } = this; if ( // not included (include && (!name || !matches(include, name))) || // excluded (exclude && name && matches(exclude, name)) ) { return vnode; } const { cache, keys } = this; // 获取键,优先获取组件的name字段,否则是组件的tag const key: ?string = vnode.key == null ? // same constructor may get registered as different local components // so cid alone is not enough (#3269) componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : "") : vnode.key; // 命中缓存,直接从缓存拿vnode 的组件实例,并且重新调整了 key 的顺序放在了最后一个 if (cache[key]) { vnode.componentInstance = cache[key].componentInstance; // make current key freshest remove(keys, key); keys.push(key); } // 不命中缓存,把 vnode 设置进缓存 else { cache[key] = vnode; keys.push(key); // prune oldest entry // 如果配置了 max 并且缓存的长度超过了 this.max,还要从缓存中删除第一个 if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode); } } // keepAlive标记位 vnode.data.keepAlive = true; } return vnode || (slot && slot[0]); }};

原理

获取 keep-alive 包裹着的第一个子组件对象及其组件名 根据设定的 include/exclude(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例 根据组件 ID 和 tag 生成缓存 Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该 key 在 this.keys 中的位置(更新 key 的位置是实现 LRU 置换策略的关键) 在 this.cache 对象中存储该组件实例并保存 key 值,之后检查缓存的实例数量是否超过 max 的设置值,超过则根据 LRU 置换策略删除最近最久未使用的实例(即是下标为 0 的那个 key) 最后组件实例的 keepAlive 属性设置为 true,这个在渲染和执行被包裹组件的钩子函数会用到,这里不细说 

LRU 缓存淘汰算法

LRU(Least recently used)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。 

Vue 原理整理(vue3 原理)

keep-alive 的实现正是用到了 LRU 策略,将最近访问的组件 push 到 this.keys 最后面,this.keys[0]也就是最久没被访问的组件,当缓存实例超过 max 设置值,删除 this.keys[0] 

13. vm.$set()实现原理是什么? 

对于【检测不到对象属性的添加和删除】【无法监控到数组下标的变化】可以用 vue.$set。

受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃),Vue 无法检测到对象属性的添加或删除。

由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。

那么 Vue 内部是如何解决对象新增属性不能响应的问题的呢? 

内部实现:

export function set(target: Array | Object, key: any, val: any): any { // target 为数组 if (Array.isArray(target) && isValidArrayIndex(key)) { // 修改数组的长度, 避免索引>数组长度导致splice()执行有误 target.length = Math.max(target.length, key); // 利用数组的splice变异方法触发响应式 target.splice(key, 1, val); return val; } // target为对象, key在target或者target.prototype上 且必须不能在 Object.prototype 上,直接赋值 if (key in target && !(key in Object.prototype)) { target[key] = val; return val; } // 以上都不成立, 即开始给target创建一个全新的属性 // 获取Observer实例 const ob = (target: any).__ob__; // target 本身就不是响应式数据, 直接赋值 if (!ob) { target[key] = val; return val; } // 进行响应式处理 defineReactive(ob.value, key, val); ob.dep.notify(); return val;}

注意:

如果目标是数组,使用 vue 实现的变异方法 splice 实现响应式如果目标是对象,判断属性存在,即为响应式,直接赋值如果 target 本身就不是响应式,直接赋值如果属性不是响应式,则调用 defineReactive 方法进行响应式处理14.虚拟DOM(vdom)和diff算法 DOM操作非常耗费性能以前用jQuery,可以自行控制DOM操作时机,手动调整vue和react都是数据驱动试图,如何有效控制DOM操作?

解决方案——vdom

有一定的复杂度,想减少计算次数比较难难不能把计算,更多的转移为JS计算?因为JS执行比较快vdom——用JS模拟DOM结构,计算出最小的变更,操作DOM

面试题:用JS模拟DOM元素 

        包含三部分:标签tag,附着在标签上的属性、样式、事件props,子元素children 

 

通过snabbdom 学习vdom

vue3重写了vdom的代码,优化了性能但vdom的理念不变,面试考点不变h函数、vnode数据结构、patch函数

vdom总结

用js模拟DOM结构(vnode)新旧vnode对比,得出最小的更新范围,最后更新DOM数据驱动视图的模式下,有效控制DOM操作

diff算法

两个数做diff,如这里的vdom diff 

vnode   ->patch ->new vnode

树diff的时间复杂度O(n^3)

第一,遍历tree1;第二,遍历tree2 第三,排序 1000个节点,要计算1亿次,算法不可用 优化时间复杂度到O(n)

只比较同一层级,不跨级比较 tag不相同,直接删掉重建,不再深度比较 tag和key,两者都相同,则认为是相同的节点,不再深度比较

diff算法总结

patchVnodeaddVnodes removeVnodesupdateChildren(key的重要性)

vdom和diff总结

细节不重要,updateChildren更新过程也不重要,不要深究vnode核心概念很重要:h vnode patch diff key 等vnode的存在价值更重要:数据驱动试图,控制DOM操作 15.模板编译

 

 with语法

 

 模板编译

总结 

vue中使用render代替template  

 总结 

15.组件渲染更新过程

vue原理的三大知识点

组件渲染/更新过程 

初次渲染过程

第二步是因为,执行render函数会触发getter操作 

 

更新过程

 

触发setter,看是修改的data是否在getter中已经被监听,如果是,就执行render函数

patch的diff算法,会计算出最小差异,更新在DOM上 

完整流程图

模板编译完,生成render函数,执行render函数生成vnode (虚拟DOM的树)

执行render函数的时候会touch getter,即执行函数的时候回触发Data里的getter

触发的时候就会收集依赖,即在模板中出发了哪个变量的getter就会把哪个给观察起来(watcher)

在修改Data的时候,看这个Data是否是之前作为依赖被观察起来的

如果是,就重新出发re-render,重新渲染,重新生成vdom tree,重新touch

异步渲染

        1.$nextTick:

                vue是异步渲染,$nextTick会待Dom渲染完之后调用

                页面渲染时会将data的修改做整合,多次data修改只会渲染一次

        2.汇总data的修改,一次性更新试图

        3.减少DOM操作次数,提高性能

16.前端路由原理

网页url组成部分

hash的特点

hash 变化会触发网页跳转,即浏览器的前进、后退hash 变化不会刷新页面,SPA必须的特点hash 永远不会提到server端(前端自测用) <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>hash test</title></head><body> <p>hash test</p> <button id="btn1">修改 hash</button> <script> // hash 变化,包括: // a. JS 修改 url // b. 手动修改 url 的 hash // c. 浏览器前进、后退 window.onhashchange = (event) => { console.log('old url', event.oldURL) console.log('new url', event.newURL) console.log('hash:', location.hash) } // 页面初次加载,获取 hash document.addEventListener('DOMContentLoaded', () => { console.log('hash:', location.hash) }) // JS 修改 url document.getElementById('btn1').addEventListener('click', () => { location.href = '#/user' }) </script></body></html>

H5 history

用url规范的路由,但跳转时不刷新页面history.pushStatewindow.onpopstate

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>history API test</title></head><body> <p>history API test</p> <button id="btn1">修改 url</button> <script> // 页面初次加载,获取 path document.addEventListener('DOMContentLoaded', () => { console.log('load', location.pathname) }) // 打开一个新的路由 // 【注意】用 pushState 方式,浏览器不会刷新页面 document.getElementById('btn1').addEventListener('click', () => { const state = { name: 'page1' } console.log('切换路由到', 'page1') history.pushState(state, '', 'page1') // 重要!! }) // 监听浏览器前进、后退 window.onpopstate = (event) => { // 重要!! console.log('onpopstate', event.state, location.pathname) } // 需要 server 端配合,可参考 // https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90 </script></body></html>

总结:

hash ——window.onhashchangeH5 history - history.pushState 和 window.onpopstateH5 history 需要后端支持

两者选择

to B 的系统推荐用 hash ,简单易用,对url规范不敏感。to C的系统,可以考虑选择 H5 history,但需要服务端支持。能选择简单,就选择简单,要考虑成本和收益to c的要是不需要管seo、搜索引擎也不需要用H5 history 简单一点就好17.Proxy

什么是代理呢,可以理解为在对象之前设置一个“拦截”,当该对象被访问的时候,都必须经过这层拦截。意味着你可以在这层拦截中进行各种操作。比如你可以在这层拦截中对原对象进行处理,返回你想返回的数据结构。

ES6 原生提供 Proxy 构造函数,MDN上的解释为:Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

用法:

const p = new Proxy(target, handler);target: 所要拦截的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。handler:一个对象,定义要拦截的行为。const p = new Proxy({}, { get(target, propKey) { return '哈哈,你被我拦截了'; }});console.log(p.name);// 哈哈,你被我拦截了

数据响应:

const obj = { name: 'app', age: '18', a: { b: 1, c: 2, },}const p = new Proxy(obj, { get(target, propKey, receiver) { console.log('你访问了' + propKey); console.log("------", target, propKey, receiver) return Reflect.get(target, propKey, receiver); }, set(target, propKey, value, receiver) { // 可以这样判断是新增的还是修改的。 // --------------------- let oldValue = target[propKey] console.log(propKey, oldValue, value) if (!oldValue) { console.log('新增属性') } else if (oldValue !== value) { console.log('修改属性') } // ---------------------- console.log("++++", target, propKey, receiver) console.log('你设置了' + propKey); console.log('新的' + propKey + '=' + value); Reflect.set(target, propKey, value, receiver); }});p.age = '20';console.log(p.age);p.newPropKey = '新属性';console.log(p.newPropKey);

 可以看到,新增的属性,并不需要重新添加响应式处理,因为 Proxy 是对对象的操作,只要你访问对象,就会走到 Proxy 的逻辑中。

Reflect(ES6引入) 是一个内置的对象,它提供拦截 JavaScript 操作的方法。将Object对象一些明显属于语言内部方法(比如Object.defineProperty())放到Reflect对象上。修改某些Object方法的返回结果,让其变得更合理。让Object操作都变成函数行为。具体内容查看MDN

本文链接地址:https://www.jiuchutong.com/zhishi/299266.html 转载请保留说明!

上一篇:ElementUI的Form表单使用slot-scope=“scope“获取当前表格行数据实现数据回显、修改表单操作(elementui form rules)

下一篇:基于OpenCV的人脸识别(基于opencv的人脸检测算法)

  • 网易云怎么设置主页呢(网易云怎么设置闹钟铃声)

    网易云怎么设置主页呢(网易云怎么设置闹钟铃声)

  • 抖音转发的视频怎样删除(抖音转发的视频无法查看怎么回事)

    抖音转发的视频怎样删除(抖音转发的视频无法查看怎么回事)

  • 4500毫安电池能用多久(4500毫安电池能用几年)

    4500毫安电池能用多久(4500毫安电池能用几年)

  • 群聊天记录怎么恢复(群聊天记录怎么删除让所有人看不见)

    群聊天记录怎么恢复(群聊天记录怎么删除让所有人看不见)

  • 屏幕镜像怎么在转圈(屏幕镜像怎么在电脑上用)

    屏幕镜像怎么在转圈(屏幕镜像怎么在电脑上用)

  • QQ怎么用电脑录像(qq怎么电脑录屏幕视频)

    QQ怎么用电脑录像(qq怎么电脑录屏幕视频)

  • 翻新机能用吗(如何辨别翻新机和原装机)

    翻新机能用吗(如何辨别翻新机和原装机)

  • 苹果为什么不出5G手机(苹果为什么不出曲面屏手机)

    苹果为什么不出5G手机(苹果为什么不出曲面屏手机)

  • 闲鱼屏蔽全部商品多久恢复(闲鱼屏蔽全部商品怎么弄)

    闲鱼屏蔽全部商品多久恢复(闲鱼屏蔽全部商品怎么弄)

  • 钉钉直播分屏会有记录吗

    钉钉直播分屏会有记录吗

  • 为什么airpodspro左右电量不一样(为什么airpodspro有滋滋滋的声音)

    为什么airpodspro左右电量不一样(为什么airpodspro有滋滋滋的声音)

  • 路由器后面有四个孔,应该用哪一个(路由器后面有四个孔有什么用)

    路由器后面有四个孔,应该用哪一个(路由器后面有四个孔有什么用)

  • 钉钉屏幕共享视频没有声音怎么办(钉钉屏幕共享视频有声音吗)

    钉钉屏幕共享视频没有声音怎么办(钉钉屏幕共享视频有声音吗)

  • 微信无缘无故被永久封号怎么办(微信无缘无故被投诉了、是怎么回事)

    微信无缘无故被永久封号怎么办(微信无缘无故被投诉了、是怎么回事)

  • soul怎么查看对方性别(soul怎么查看对方在线时间)

    soul怎么查看对方性别(soul怎么查看对方在线时间)

  • 苹果x的耳机怎么用(苹果x耳机怎么缠回纸盒)

    苹果x的耳机怎么用(苹果x耳机怎么缠回纸盒)

  • 华为mate30步数在哪设置(华为mate30手机步数)

    华为mate30步数在哪设置(华为mate30手机步数)

  • 荣耀20怎么设置nfc(荣耀20怎么设置闹钟铃声)

    荣耀20怎么设置nfc(荣耀20怎么设置闹钟铃声)

  • frontpage可以用来发布网站吗(frontpage的功能)

    frontpage可以用来发布网站吗(frontpage的功能)

  • 抖音为什么保存相册失败(抖音为什么保存不了视频到本地相册)

    抖音为什么保存相册失败(抖音为什么保存不了视频到本地相册)

  • 韩剧tv怎么看国产剧(韩剧tv怎么看国语版)

    韩剧tv怎么看国产剧(韩剧tv怎么看国语版)

  • cpu的组成(cpu的组成和功能)

    cpu的组成(cpu的组成和功能)

  • 对象存储OSS之ossbrowser的使用(oss对象储存的副本储存原则)

    对象存储OSS之ossbrowser的使用(oss对象储存的副本储存原则)

  • 机动车发票税率怎么算
  • 当期所得税计入什么科目
  • 办公费税前扣除标准2023
  • 个税申报月份错了怎么改
  • 简述入伙、退伙的含义与退伙的形式
  • 增值税加计扣除账务处理
  • 制造费用结转成什么
  • 公司名称房产和房地产区别
  • 企业所得税季报资产总额怎么填
  • 公司购入汽车分录
  • 调试阶段是什么意思
  • 机关和事业单位哪个好
  • 四季度企业所得税怎么报
  • 国税零申报怎么报
  • 现金流量表中现金流量净额怎么算
  • 免税项目进项税为什么不可以抵扣
  • 应纳个税的工资怎么计算
  • 外贸企业出口货物会计账务处理
  • 开票人为什么不能改
  • 商业汇票付款方通过哪个会计科目核算
  • 发工资时忘记扣个税了怎么做账
  • 预提成本的会计处理
  • mac终端怎么运行命令
  • 为什么可供出售的金融资产是非流动资产
  • 普通年金的概念和内涵
  • 汽车行业返利账务处理
  • win7怎么更改开机启动项
  • PHP If Else(elsefi) 语句
  • 固定资产成本中的相关税费
  • wordpress怎么搜索域名
  • Uniapp 使用mocjk
  • 未签订销售合同
  • 什么叫非同一控制下
  • php常用的魔术方法有哪些
  • 营改增后预交增值税
  • thinkphp隐藏index.php
  • 财务的几张报表
  • php开发程序
  • 探望生病职工的话语
  • 进项税额转出加计抵减会计分录
  • 什么是行政单位任命的事业单位工作人员
  • 应付现金股利属于什么账户
  • mysql,if
  • 一般纳税人之外还有什么
  • 金税盘 申报
  • 用人单位垫付生育津贴垫付金额和垫付天数
  • 房租收入税费
  • 汽车装修费计入哪个科目
  • 所得税后净现金流量计算
  • 以公司名义开的口腔诊所法人和负责人是两个人么
  • 兼职会计如何做账报税
  • 讲课费需要提供发票吗
  • 施工方怎么开发票
  • 建账选用什么会计制度
  • 一个分页存储过多的文件
  • 批量替换多个wps文档的内容
  • mysql免安装版本
  • vmware虚拟机安装Linux教程
  • 轻松备份怎么用
  • debian7安装教程
  • win7系统运行慢,如何提速
  • winxp升级win7教程图文
  • 电脑ip地址设置在哪里
  • fp3是什么文件格式
  • win10周年更新版是什么意思
  • express.json()
  • cocos2dx-3.0(23) ScrollView 缩放 及 touch新用法
  • bat批处理命令大全
  • 从零开始学什么
  • python lambda的用法
  • 安卓多线程有几种实现方法
  • 说几条javascript的基本规范
  • 深圳国税电子税务局网上办税服务厅
  • 上海个人所得税优惠政策
  • 如何计算白酒的出酒率
  • 工会经费按年还是季度申报
  • 精准扶贫有哪些分类措施
  • 河北税务医保缴费怎么操作
  • 环保税征税范围噪音
  • 解放服务站总部电话
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设