【面试题】2023前端vue面试题及答案

news2024/11/15 10:19:29

Vue3.0 为什么要用 proxy?

在 Vue2 中, 0bject.defineProperty 会改变原始数据,而 Proxy 是创建对象的虚拟表示,并提供 set 、get 和 deleteProperty 等处理器,这些处理器可在访问或修改原始对象上的属性时进行拦截,有以下特点∶

  • 不需用使用 Vue.$set 或 Vue.$delete 触发响应式。

  • 全方位的数组变化检测,消除了Vue2 无效的边界情况。

  • 支持 Map,Set,WeakMap 和 WeakSet。

Proxy 实现的响应式原理与 Vue2的实现原理相同,实现方式大同小异∶

  • get 收集依赖

  • Set、delete 等触发依赖

  • 对于集合类型,就是对集合对象的方法做一层包装:原方法执行后执行依赖相关的收集或触发逻辑。

大厂面试题分享 面试题库

后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库

说说你对slot的理解?slot使用场景有哪些

一、slot是什么

在HTML中 slot 元素 ,作为 Web Components 技术套件的一部分,是Web组件内的一个占位符

该占位符可以在后期使用自己的标记语言填充

举个栗子

<templateid="element-details-template"><slotname="element-name">Slot template</slot></template><element-details><spanslot="element-name">1</span></element-details><element-details><spanslot="element-name">2</span></element-details>复制代码

template不会展示到页面中,需要用先获取它的引用,然后添加到DOM中,

customElements.define('element-details',
  classextendsHTMLElement {
    constructor() {
      super();
      const template = document
        .getElementById('element-details-template')
        .content;
      const shadowRoot = this.attachShadow({mode: 'open'})
        .appendChild(template.cloneNode(true));
  }
})
复制代码

在Vue中的概念也是如此

Slot 艺名插槽,花名“占坑”,我们可以理解为solt在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动填坑(替换组件模板中slot位置),作为承载分发内容的出口

二、使用场景

通过插槽可以让用户可以拓展组件,去更好地复用组件和对其做定制化处理

如果父组件在使用到一个复用组件的时候,获取这个组件在不同的地方有少量的更改,如果去重写组件是一件不明智的事情

通过slot插槽向组件内部指定位置传递内容,完成这个复用组件在不同场景的应用

比如布局组件、表格列、下拉选、弹框显示内容等

使用vue渲染大量数据时应该怎么优化?说下你的思路!

分析

企业级项目中渲染大量数据的情况比较常见,因此这是一道非常好的综合实践题目。

回答

  1. 在大型企业级项目中经常需要渲染大量数据,此时很容易出现卡顿的情况。比如大数据量的表格、树

  1. 处理时要根据情况做不同处理:

  • 可以采取分页的方式获取,避免渲染大量数据

  • vue-virtual-scroller (opens new window)等虚拟滚动方案,只渲染视口范围内的数据

  • 如果不需要更新,可以使用v-once方式只渲染一次

  • 通过v-memo (opens new window)可以缓存结果,结合v-for使用,避免数据变化时不必要的VNode创建

  • 可以采用懒加载方式,在用户需要的时候再加载数据,比如tree组件子树的懒加载

  1. 还是要看具体需求,首先从设计上避免大数据获取和渲染;实在需要这样做可以采用虚表的方式优化渲染;最后优化更新,如果不需要更新可以v-once处理,需要更新可以v-memo进一步优化大数据更新性能。其他可以采用的是交互方式优化,无线滚动、懒加载等方案

scoped样式穿透

scoped虽然避免了组件间样式污染,但是很多时候我们需要修改组件中的某个样式,但是又不想去除scoped属性
  1. 使用/deep/

<!-- Parent --><template><divclass="wrap"><Child /></div></template><stylelang="scss"scoped>.wrap /deep/ .box{
    background: red;
}
</style><!-- Child --><template><divclass="box"></div></template>复制代码
  1. 使用两个style标签

<!-- Parent --><template><divclass="wrap"><Child /></div></template><stylelang="scss"scoped>/* 其他样式 */</style><stylelang="scss">.wrap.box{
  background: red;
}
</style><!-- Child --><template><divclass="box"></div></template>复制代码

Vue中v-html会导致哪些问题

  • 可能会导致 xss 攻击

  • v-html 会替换掉标签内部的子元素

let template = require('vue-template-compiler'); 
let r = template.compile(`<div v-html="'<span>hello</span>'"></div>`) 

// with(this){return _c('div',{domProps: {"innerHTML":_s('<span>hello</span>')}})} console.log(r.render);

// _c 定义在core/instance/render.js // _s 定义在core/instance/render-helpers/index,jsif (key === 'textContent' || key === 'innerHTML') { 
    if (vnode.children) vnode.children.length = 0if (cur === oldProps[key]) continue// #6601 work around Chrome version <= 55 bug where single textNode // replaced by innerHTML/textContent retains its parentNode property if (elm.childNodes.length === 1) { 
        elm.removeChild(elm.childNodes[0]) 
    } 
}
复制代码

如果让你从零开始写一个vuex,说说你的思路

思路分析

这个题目很有难度,首先思考vuex解决的问题:存储用户全局状态并提供管理状态API。

  • vuex需求分析

  • 如何实现这些需求

回答范例

  1. 官方说vuex是一个状态管理模式和库,并确保这些状态以可预期的方式变更。可见要实现一个vuex

  • 要实现一个Store存储全局状态

  • 要提供修改状态所需API:commit(type, payload), dispatch(type, payload)

  1. 实现Store时,可以定义Store类,构造函数接收选项options,设置属性state对外暴露状态,提供commit和dispatch修改属性state。这里需要设置state为响应式对象,同时将Store定义为一个Vue插件

  1. commit(type, payload)方法中可以获取用户传入mutations并执行它,这样可以按用户提供的方法修改状态。 dispatch(type, payload)类似,但需要注意它可能是异步的,需要返回一个Promise给用户以处理异步结果

实践

Store的实现:

classStore {
    constructor(options) {
        this.state = reactive(options.state)
        this.options = options
    }
    commit(type, payload) {
        this.options.mutations[type].call(this, this.state, payload)
    }
}
复制代码

vuex简易版

/**
 * 1 实现插件,挂载$store
 * 2 实现store
 */letVue;

classStore {
  constructor(options) {
    // state响应式处理// 外部访问: this.$store.state.***// 第一种写法// this.state = new Vue({//   data: options.state// })// 第二种写法:防止外界直接接触内部vue实例,防止外部强行变更this._vm = newVue({
      data: {
        $$state: options.state
      }
    })

    this._mutations = options.mutationsthis._actions = options.actionsthis.getters = {}
    options.getters && this.handleGetters(options.getters)

    this.commit = this.commit.bind(this)
    this.dispatch = this.dispatch.bind(this)
  }

  get state () {
    returnthis._vm._data.$$state
  }

  set state (val) {
    returnnewError('Please use replaceState to reset state')
  }

  handleGetters (getters) {
    Object.keys(getters).map(key => {
      Object.defineProperty(this.getters, key, {
        get: () => getters[key](this.state)
      })
    })
  }

  commit (type, payload) {
    let entry = this._mutations[type]
    if (!entry) {
      returnnewError(`${type} is not defined`)
    }

    entry(this.state, payload)
  }

  dispatch (type, payload) {
    let entry = this._actions[type]
    if (!entry) {
      returnnewError(`${type} is not defined`)
    }

    entry(this, payload)
  }
}

constinstall = (_Vue) => {
  Vue = _Vue

  Vue.mixin({
    beforeCreate () {
      if (this.$options.store) {
        Vue.prototype.$store = this.$options.store
      }
    },
  })
}


exportdefault { Store, install }
复制代码

验证方式

importVuefrom'vue'importVuexfrom'./vuex'// this.$storeVue.use(Vuex)

exportdefaultnewVuex.Store({
  state: {
    counter: 0
  },
  mutations: {
    // state从哪里来的
    add (state) {
      state.counter++
    }
  },
  getters: {
    doubleCounter (state) {
      return state.counter * 2
    }
  },
  actions: {
    add ({ commit }) {
      setTimeout(() => {
        commit('add')
      }, 1000)
    }
  },
  modules: {
  }
})
复制代码

参考 前端进阶面试题详细解答

Vue与Angular以及React的区别?

Vue与AngularJS的区别

  • Angular采用TypeScript开发, 而Vue可以使用javascript也可以使用TypeScript

  • AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。

  • AngularJS社区完善, Vue的学习成本较小

Vue与React的区别

相同点:

  1. Virtual DOM。其中最大的一个相似之处就是都使用了Virtual DOM。(当然Vue是在Vue2.x才引用的)也就是能让我们通过操作数据的方式来改变真实的DOM状态。因为其实Virtual DOM的本质就是一个JS对象,它保存了对真实DOM的所有描述,是真实DOM的一个映射,所以当我们在进行频繁更新元素的时候,改变这个JS对象的开销远比直接改变真实DOM要小得多。

  1. 组件化的开发思想。第二点来说就是它们都提倡这种组件化的开发思想,也就是建议将应用分拆成一个个功能明确的模块,再将这些模块整合在一起以满足我们的业务需求。

  1. Props。Vue和React中都有props的概念,允许父组件向子组件传递数据。

  1. 构建工具、Chrome插件、配套框架。还有就是它们的构建工具以及Chrome插件、配套框架都很完善。比如构建工具,React中可以使用CRA,Vue中可以使用对应的脚手架vue-cli。对于配套框架Vue中有vuex、vue-router,React中有react-router、redux。

不同点

  1. 模版的编写。最大的不同就是模版的编写,Vue鼓励你去写近似常规HTML的模板,React推荐你使用JSX去书写。

  1. 状态管理与对象属性。在React中,应用的状态是比较关键的概念,也就是state对象,它允许你使用setState去更新状态。但是在Vue中,state对象并不是必须的,数据是由data属性在Vue对象中进行管理。

  1. 虚拟DOM的处理方式不同。Vue中的虚拟DOM控制了颗粒度,组件层面走watcher通知,而组件内部走vdom做diff,这样,既不会有太多watcher,也不会让vdom的规模过大。而React走了类似于CPU调度的逻辑,把vdom这棵树,微观上变成了链表,然后利用浏览器的空闲时间来做diff

Vue项目中你是如何解决跨域的呢

一、跨域是什么

跨域本质是浏览器基于同源策略的一种安全手段

同源策略(Sameoriginpolicy),是一种约定,它是浏览器最核心也最基本的安全功能

所谓同源(即指在同一个域)具有以下三个相同点

  • 协议相同(protocol)

  • 主机相同(host)

  • 端口相同(port)

反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域

一定要注意跨域是浏览器的限制,你用抓包工具抓取接口数据,是可以看到接口已经把数据返回回来了,只是浏览器的限制,你获取不到数据。用postman请求接口能够请求到数据。这些再次印证了跨域是浏览器的限制。

Class 与 Style 如何动态绑定

Class 可以通过对象语法和数组语法进行动态绑定

对象语法:

<div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>

data: {
  isActive: true,
  hasError: false
}
复制代码

数组语法:

<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>

data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}
复制代码

Style 也可以通过对象语法和数组语法进行动态绑定

对象语法:

<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

data: {
  activeColor: 'red',
  fontSize: 30
}
复制代码

数组语法:

<div v-bind:style="[styleColor, styleSize]"></div>

data: {
  styleColor: {
     color: 'red'
   },
  styleSize:{
     fontSize:'23px'
  }
}
复制代码

了解history有哪些方法吗?说下它们的区别

history 这个对象在html5的时候新加入两个api history.pushState() 和 history.repalceState() 这两个API可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录。

从参数上来说:

window.history.pushState(state,title,url)
//state:需要保存的数据,这个数据在触发popstate事件时,可以在event.state里获取//title:标题,基本没用,一般传null//url:设定新的历史纪录的url。新的url与当前url的origin必须是一样的,否则会抛出错误。url可以时绝对路径,也可以是相对路径。//如 当前url是 https://www.baidu.com/a/,执行history.pushState(null, null, './qq/'),则变成 https://www.baidu.com/a/qq/,//执行history.pushState(null, null, '/qq/'),则变成 https://www.baidu.com/qq/window.history.replaceState(state,title,url)
//与pushState 基本相同,但她是修改当前历史纪录,而 pushState 是创建新的历史纪录复制代码

另外还有:

  • window.history.back() 后退

  • window.history.forward()前进

  • window.history.go(1) 前进或者后退几步

从触发事件的监听上来说:

  • pushState()和replaceState()不能被popstate事件所监听

  • 而后面三者可以,且用户点击浏览器前进后退键时也可以

在Vue中使用插件的步骤

  • 采用ES6的import ... from ...语法或CommonJS的require()方法引入插件

  • 使用全局方法Vue.use( plugin )使用插件,可以传入一个选项对象Vue.use(MyPlugin, { someOption: true })

$route和$router的区别

  • $route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。

  • 而$router是“路由实例”对象包括了路由的跳转方法,钩子函数等

为什么要使用异步组件

  1. 节省打包出的结果,异步组件分开打包,采用jsonp的方式进行加载,有效解决文件过大的问题。

  1. 核心就是包组件定义变成一个函数,依赖import() 语法,可以实现文件的分割加载。

components:{ 
  AddCustomerSchedule:(resolve)=>import("../components/AddCustomer") // require([]) 
}
复制代码

原理

exportfunction ( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string ): VNode | Array<VNode> | void { 
    // async component let asyncFactory 
    if (isUndef(Ctor.cid)) { 
        asyncFactory = CtorCtor = resolveAsyncComponent(asyncFactory, baseCtor) // 默认调用此函数时返回 undefiend // 第二次渲染时Ctor不为undefined if (Ctor === undefined) { 
            returncreateAsyncPlaceholder( // 渲染占位符 空虚拟节点 
                asyncFactory, 
                data, 
                context, 
                children, 
                tag 
            ) 
        } 
    } 
}
functionresolveAsyncComponent ( factory: Function, baseCtor: Class<Component> ): Class<Component> | void { 
    if (isDef(factory.resolved)) { 
        // 3.在次渲染时可以拿到获取的最新组件 return factory.resolved 
    }
    const resolve = once((res: Object | Class<Component>) => { 
        factory.resolved = ensureCtor(res, baseCtor) 
        if (!sync) { 
            forceRender(true) //2. 强制更新视图重新渲染 
        } else { 
            owners.length = 0 
        } 
    })
    const reject = once(reason => { 
        if (isDef(factory.errorComp)) { 
            factory.error = trueforceRender(true) 
        } 
    })
    const res = factory(resolve, reject)// 1.将resolve方法和reject方法传入,用户调用 resolve方法后 
    sync = falsereturn factory.resolved 
}
复制代码

函数式组件优势和原理

函数组件的特点

  1. 函数式组件需要在声明组件是指定 functional:true

  1. 不需要实例化,所以没有this,this通过render函数的第二个参数context来代替

  1. 没有生命周期钩子函数,不能使用计算属性,watch

  1. 不能通过$emit 对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件

  1. 因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement

  1. 函数式组件的props可以不用显示声明,所以没有在props里面声明的属性都会被自动隐式解析为prop,而普通组件所有未声明的属性都解析到$attrs里面,并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止)

优点

  1. 由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件

  1. 函数式组件结构比较简单,代码结构更清晰

使用场景:

  • 一个简单的展示组件,作为容器组件使用 比如 router-view 就是一个函数式组件

  • “高阶组件”——用于接收一个组件作为参数,返回一个被包装过的组件

例子

Vue.component('functional',{ // 构造函数产生虚拟节点的functional:true, // 函数式组件 // data={attrs:{}}render(h){
        returnh('div','test')
    }
})
const vm = newVue({
    el: '#app'
})
复制代码

源码相关

// functional componentif (isTrue(Ctor.options.functional)) { // 带有functional的属性的就是函数式组件returncreateFunctionalComponent(Ctor, propsData, data, context, children)
}

// extract listeners, since these needs to be treated as// child component listeners instead of DOM listenersconst listeners = data.on// 处理事件// replace with listeners with .native modifier// so it gets processed during parent component patch.
data.on = data.nativeOn// 处理原生事件// install component management hooks onto the placeholder nodeinstallComponentHooks(data) // 安装组件相关钩子 (函数式组件没有调用此方法,从而性能高于普通组件)复制代码

Vue.set的实现原理


  • 给对应和数组本身都增加了dep属性

  • 当给对象新增不存在的属性则触发对象依赖的watcher去更新

  • 当修改数组索引时,我们调用数组本身的splice去更新数组(数组的响应式原理就是重新了splice等方法,调用splice就会触发视图更新)

基本使用

以下方法调用会改变原始数组:push(), pop(), shift(), unshift(), splice(), sort(), reverse(),Vue.set( target, key, value )
  • 调用方法:Vue.set(target, key, value )

  • target:要更改的数据源(可以是对象或者数组)

  • key:要更改的具体数据

  • value :重新赋的值

<divid="app">{{user.name}} {{user.age}}</div><divid="app"></div><script>// 1. 依赖收集的特点:给每个属性都增加一个dep属性,dep属性会进行收集,收集的是watcher// 2. vue会给每个对象也增加一个dep属性const vm = newVue({
        el: '#app',
        data: { // vm._data  user: {name:'poetry'}
        }
    });
    // 对象的话:调用defineReactive在user对象上定义一个age属性,增加到响应式数据中,触发对象本身的watcher,ob.dep.notify()更新 // 如果是数组 通过调用 splice方法,触发视图更新
    vm.$set(vm.user, 'age', 20); // 不能给根属性添加,因为给根添加属性 性能消耗太大,需要做很多处理// 修改肯定是同步的 -> 更新都是一步的  queuewatcher</script>复制代码

相关源码

// src/core/observer/index.js 44exportclassObserver { // new Observer(value)value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $dataconstructor (value: any) {
    this.value = value
    this.dep = newDep() // 给所有对象类型增加dep属性
  }
}
复制代码
// src/core/observer/index.js 201exportfunctionset (target: Array<any> | Object, key: any, val: any): any {
  // 1.是开发环境 target 没定义或者是基础类型则报错if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 2.如果是数组 Vue.set(array,1,100); 调用我们重写的splice方法 (这样可以更新视图)if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    // 利用数组的splice变异方法触发响应式  
    target.splice(key, 1, val)
    return val
  }
  // 3.如果是对象本身的属性,则直接添加即可if (key in target && !(key inObject.prototype)) {
    target[key] = val // 直接修改属性值  return val
  }
  // 4.如果是Vue实例 或 根数据data时 报错,(更新_data 无意义)const ob = (target: any).__ob__if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  // 5.如果不是响应式的也不需要将其定义成响应式属性if (!ob) {
    target[key] = val
    return val
  }
  // 6.将属性定义成响应式的defineReactive(ob.value, key, val)
  // 通知视图更新
  ob.dep.notify()
  return val
}
复制代码

大厂面试题分享 面试题库

后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库

我们阅读以上源码可知,vm.$set 的实现原理是:

  • 如果目标是数组 ,直接使用数组的 splice 方法触发相应式;

  • 如果目标是对象 ,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法)

Vue为什么没有类似于React中shouldComponentUpdate的生命周期

  • 考点: Vue的变化侦测原理

  • 前置知识: 依赖收集、虚拟DOM、响应式系统

根本原因是Vue与React的变化侦测方式有所不同
  • 当React知道发生变化后,会使用Virtual Dom Diff进行差异检测,但是很多组件实际上是肯定不会发生变化的,这个时候需要 shouldComponentUpdate 进行手动操作来减少diff,从而提高程序整体的性能

  • Vue在一开始就知道那个组件发生了变化,不需要手动控制diff,而组件内部采用的diff方式实际上是可以引入类似于shouldComponentUpdate相关生命周期的,但是通常合理大小的组件不会有过量的diff,手动优化的价值有限,因此目前Vue并没有考虑引入shouldComponentUpdate这种手动优化的生命周期

vue-router中如何保护路由

分析

路由保护在应用开发过程中非常重要,几乎每个应用都要做各种路由权限管理,因此相当考察使用者基本功。

体验

全局守卫:

const router = createRouter({ ... })
​
router.beforeEach((to, from) => {
  // ...// 返回 false 以取消导航returnfalse
})
复制代码

路由独享守卫:

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigationreturnfalse
    },
  },
]
复制代码

组件内的守卫:

constUserDetails = {
  template: `...`,
  beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
  },
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
  },
}
复制代码

回答

  • vue-router中保护路由的方法叫做路由守卫,主要用来通过跳转或取消的方式守卫导航。

  • 路由守卫有三个级别:全局、路由独享、组件级。影响范围由大到小,例如全局的router.beforeEach(),可以注册一个全局前置守卫,每次路由导航都会经过这个守卫,因此在其内部可以加入控制逻辑决定用户是否可以导航到目标路由;在路由注册的时候可以加入单路由独享的守卫,例如beforeEnter,守卫只在进入路由时触发,因此只会影响这个路由,控制更精确;我们还可以为路由组件添加守卫配置,例如beforeRouteEnter,会在渲染该组件的对应路由被验证前调用,控制的范围更精确了。

  • 用户的任何导航行为都会走navigate方法,内部有个guards队列按顺序执行用户注册的守卫钩子函数,如果没有通过验证逻辑则会取消原有的导航。

原理

runGuardQueue(guards)链式的执行用户在各级别注册的守卫钩子函数,通过则继续下一个级别的守卫,不通过进入catch流程取消原本导航

// 源码runGuardQueue(guards)
  .then(() => {
    // check global guards beforeEach
    guards = []
    for (const guard of beforeGuards.list()) {
      guards.push(guardToPromiseFn(guard, to, from))
    }
    guards.push(canceledNavigationCheck)

    returnrunGuardQueue(guards)
  })
  .then(() => {
    // check in components beforeRouteUpdate
    guards = extractComponentsGuards(
      updatingRecords,
      'beforeRouteUpdate',
      to,
      from
    )

    for (const record of updatingRecords) {
      record.updateGuards.forEach(guard => {
        guards.push(guardToPromiseFn(guard, to, from))
      })
    }
    guards.push(canceledNavigationCheck)

    // run the queue of per route beforeEnter guardsreturnrunGuardQueue(guards)
  })
  .then(() => {
    // check the route beforeEnter
    guards = []
    for (const record of to.matched) {
      // do not trigger beforeEnter on reused viewsif (record.beforeEnter && !from.matched.includes(record)) {
        if (isArray(record.beforeEnter)) {
          for (const beforeEnter of record.beforeEnter)
            guards.push(guardToPromiseFn(beforeEnter, to, from))
        } else {
          guards.push(guardToPromiseFn(record.beforeEnter, to, from))
        }
      }
    }
    guards.push(canceledNavigationCheck)

    // run the queue of per route beforeEnter guardsreturnrunGuardQueue(guards)
  })
  .then(() => {
    // NOTE: at this point to.matched is normalized and does not contain any () => Promise<Component>// clear existing enterCallbacks, these are added by extractComponentsGuards
    to.matched.forEach(record => (record.enterCallbacks = {}))

    // check in-component beforeRouteEnter
    guards = extractComponentsGuards(
      enteringRecords,
      'beforeRouteEnter',
      to,
      from
    )
    guards.push(canceledNavigationCheck)

    // run the queue of per route beforeEnter guardsreturnrunGuardQueue(guards)
  })
  .then(() => {
    // check global guards beforeResolve
    guards = []
    for (const guard of beforeResolveGuards.list()) {
      guards.push(guardToPromiseFn(guard, to, from))
    }
    guards.push(canceledNavigationCheck)

    returnrunGuardQueue(guards)
  })
  // catch any navigation canceled
  .catch(err =>isNavigationFailure(err, ErrorTypes.NAVIGATION_CANCELLED)
      ? err
      : Promise.reject(err)
  )
复制代码

源码位置(opens new window)

Vue-router 路由钩子在生命周期的体现

一、Vue-Router导航守卫

有的时候,需要通过路由来进行一些操作,比如最常见的登录权限验证,当用户满足条件时,才让其进入导航,否则就取消跳转,并跳到登录页面让其登录。 为此有很多种方法可以植入路由的导航过程:全局的,单个路由独享的,或者组件级的

  1. 全局路由钩子

vue-router全局有三个路由钩子;

  • router.beforeEach 全局前置守卫 进入路由之前

  • router.beforeResolve 全局解析守卫(2.5.0+)在 beforeRouteEnter 调用之后调用

  • router.afterEach 全局后置钩子 进入路由之后

具体使用∶

  • beforeEach(判断是否登录了,没登录就跳转到登录页)

router.beforeEach((to, from, next) => {  
    let ifInfo = Vue.prototype.$common.getSession('userData');  // 判断是否登录的存储信息if (!ifInfo) { 
        // sessionStorage里没有储存user信息    if (to.path == '/') { 
            //如果是登录页面路径,就直接next()      next();    
        } else { 
            //不然就跳转到登录      Message.warning("请重新登录!");     
            window.location.href = Vue.prototype.$loginUrl;    
        }  
    } else {    
        returnnext();  
    }
})

复制代码
  • afterEach (跳转之后滚动条回到顶部)

router.afterEach((to, from) => {  
    // 跳转之后滚动条回到顶部  window.scrollTo(0,0);
});

复制代码
  1. 单个路由独享钩子

beforeEnter 如果不想全局配置守卫的话,可以为某些路由单独配置守卫,有三个参数∶ to、from、next

exportdefault [    
    {        
        path: '/',        
        name: 'login',        
        component: login,        
        beforeEnter: (to, from, next) => {          
            console.log('即将进入登录页面')          
            next()        
        }    
    }
]

复制代码
  1. 组件内钩子

beforeRouteUpdate、beforeRouteEnter、beforeRouteLeave

这三个钩子都有三个参数∶to、from、next

  • beforeRouteEnter∶ 进入组件前触发

  • beforeRouteUpdate∶ 当前地址改变并且改组件被复用时触发,举例来说,带有动态参数的路径foo/∶id,在 /foo/1 和 /foo/2 之间跳转的时候,由于会渲染同样的foa组件,这个钩子在这种情况下就会被调用

  • beforeRouteLeave∶ 离开组件被调用

注意点,beforeRouteEnter组件内还访问不到this,因为该守卫执行前组件实例还没有被创建,需要传一个回调给 next来访问,例如:

beforeRouteEnter(to, from, next) {      
    next(target => {        
        if (from.path == '/classProcess') {          
            target.isFromProcess = true        
        }      
    })    
}

复制代码

二、Vue路由钩子在生命周期函数的体现

  1. 完整的路由导航解析流程(不包括其他生命周期)

  • 触发进入其他路由。

  • 调用要离开路由的组件守卫beforeRouteLeave

  • 调用局前置守卫∶ beforeEach

  • 在重用的组件里调用 beforeRouteUpdate

  • 调用路由独享守卫 beforeEnter。

  • 解析异步路由组件。

  • 在将要进入的路由组件中调用 beforeRouteEnter

  • 调用全局解析守卫 beforeResolve

  • 导航被确认。

  • 调用全局后置钩子的 afterEach 钩子。

  • 触发DOM更新(mounted)。

  • 执行beforeRouteEnter 守卫中传给 next 的回调函数

  1. 触发钩子的完整顺序

路由导航、keep-alive、和组件生命周期钩子结合起来的,触发顺序,假设是从a组件离开,第一次进入b组件∶

  • beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。

  • beforeEach:路由全局前置守卫,可用于登录验证、全局路由loading等。

  • beforeEnter:路由独享守卫

  • beforeRouteEnter:路由组件的组件进入路由前钩子。

  • beforeResolve:路由全局解析守卫

  • afterEach:路由全局后置钩子

  • beforeCreate:组件生命周期,不能访问tAis。

  • created;组件生命周期,可以访问tAis,不能访问dom。

  • beforeMount:组件生命周期

  • deactivated:离开缓存组件a,或者触发a的beforeDestroy和destroyed组件销毁钩子。

  • mounted:访问/操作dom。

  • activated:进入缓存组件,进入a的嵌套子组件(如果有的话)。

  • 执行beforeRouteEnter回调函数next。

  1. 导航行为被触发到导航完成的整个过程

  • 导航行为被触发,此时导航未被确认。

  • 在失活的组件里调用离开守卫 beforeRouteLeave。

  • 调用全局的 beforeEach守卫。

  • 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。

  • 在路由配置里调用 beforeEnteY。

  • 解析异步路由组件(如果有)。

  • 在被激活的组件里调用 beforeRouteEnter。

  • 调用全局的 beforeResolve 守卫(2.5+),标示解析阶段完成。

  • 导航被确认。

  • 调用全局的 afterEach 钩子。

  • 非重用组件,开始组件实例的生命周期:beforeCreate&created、beforeMount&mounted

  • 触发 DOM 更新。

  • 用创建好的实例调用 beforeRouteEnter守卫中传给 next 的回调函数。

  • 导航完成

Vue-router 导航守卫有哪些

  • 全局前置/钩子:beforeEach、beforeResolve、afterEach

  • 路由独享的守卫:beforeEnter

  • 组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

Vue的diff算法详细分析

1. 是什么

diff 算法是一种通过同层的树节点进行比较的高效算法

其有两个特点:

  • 比较只会在同层级进行, 不会跨层级比较

  • 在diff比较的过程中,循环从两边向中间比较

diff 算法在很多场景下都有应用,在 vue 中,作用于虚拟 dom 渲染成真实 dom 的新旧 VNode 节点比较

2. 比较方式

diff整体策略为:深度优先,同层比较

  1. 比较只会在同层级进行, 不会跨层级比较

  1. 比较的过程中,循环从两边向中间收拢

下面举个vue通过diff算法更新的例子:

新旧VNode节点如下图所示:

第一次循环后,发现旧节点D与新节点D相同,直接复用旧节点D作为diff后的第一个真实节点,同时旧节点endIndex移动到C,新节点的 startIndex 移动到了 C

第二次循环后,同样是旧节点的末尾和新节点的开头(都是 C)相同,同理,diff 后创建了 C 的真实节点插入到第一次创建的 D 节点后面。同时旧节点的 endIndex 移动到了 B,新节点的 startIndex 移动到了 E

第三次循环中,发现E没有找到,这时候只能直接创建新的真实节点 E,插入到第二次创建的 C 节点之后。同时新节点的 startIndex 移动到了 A。旧节点的 startIndex 和 endIndex 都保持不动

第四次循环中,发现了新旧节点的开头(都是 A)相同,于是 diff 后创建了 A 的真实节点,插入到前一次创建的 E 节点后面。同时旧节点的 startIndex 移动到了 B,新节点的startIndex 移动到了 B

第五次循环中,情形同第四次循环一样,因此 diff 后创建了 B 真实节点 插入到前一次创建的 A 节点后面。同时旧节点的 startIndex移动到了 C,新节点的 startIndex 移动到了 F

新节点的 startIndex 已经大于 endIndex 了,需要创建 newStartIdx 和 newEndIdx 之间的所有节点,也就是节点F,直接创建 F 节点对应的真实节点放到 B 节点后面

3. 原理分析

当数据发生改变时,set方法会调用Dep.notify通知所有订阅者Watcher,订阅者就会调用patch给真实的DOM打补丁,更新相应的视图

源码位置:src/core/vdom/patch.js

functionpatch(oldVnode, vnode, hydrating, removeOnly) {
    if (isUndef(vnode)) { // 没有新节点,直接执行destory钩子函数if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
        return
    }

    let isInitialPatch = falseconst insertedVnodeQueue = []

    if (isUndef(oldVnode)) {
        isInitialPatch = truecreateElm(vnode, insertedVnodeQueue) // 没有旧节点,直接用新节点生成dom元素
    } else {
        const isRealElement = isDef(oldVnode.nodeType)
        if (!isRealElement && sameVnode(oldVnode, vnode)) {
            // 判断旧节点和新节点自身一样,一致执行patchVnodepatchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
        } else {
            // 否则直接销毁及旧节点,根据新节点生成dom元素if (isRealElement) {

                if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
                    oldVnode.removeAttribute(SSR_ATTR)
                    hydrating = true
                }
                if (isTrue(hydrating)) {
                    if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
                        invokeInsertHook(vnode, insertedVnodeQueue, true)
                        return oldVnode
                    }
                }
                oldVnode = emptyNodeAt(oldVnode)
            }
            return vnode.elm
        }
    }
}
复制代码

patch函数前两个参数位为oldVnode 和 Vnode ,分别代表新的节点和之前的旧节点,主要做了四个判断:

  • 没有新节点,直接触发旧节点的destory钩子

  • 没有旧节点,说明是页面刚开始初始化的时候,此时,根本不需要比较了,直接全是新建,所以只调用 createElm

  • 旧节点和新节点自身一样,通过 sameVnode 判断节点是否一样,一样时,直接调用 patchVnode去处理这两个节点

  • 旧节点和新节点自身不一样,当两个节点不一样的时候,直接创建新节点,删除旧节点

下面主要讲的是patchVnode部分

functionpatchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
    // 如果新旧节点一致,什么都不做if (oldVnode === vnode) {
      return
    }

    // 让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化const elm = vnode.elm = oldVnode.elm// 异步占位符if (isTrue(oldVnode.isAsyncPlaceholder)) {
      if (isDef(vnode.asyncFactory.resolved)) {
        hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
      } else {
        vnode.isAsyncPlaceholder = true
      }
      return
    }
    // 如果新旧都是静态节点,并且具有相同的key// 当vnode是克隆节点或是v-once指令控制的节点时,只需要把oldVnode.elm和oldVnode.child都复制到vnode上// 也不用再有其他操作if (isTrue(vnode.isStatic) &&
      isTrue(oldVnode.isStatic) &&
      vnode.key === oldVnode.key &&
      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
    ) {
      vnode.componentInstance = oldVnode.componentInstancereturn
    }

    let i
    const data = vnode.dataif (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
      i(oldVnode, vnode)
    }

    const oldCh = oldVnode.childrenconst ch = vnode.childrenif (isDef(data) && isPatchable(vnode)) {
      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
    }
    // 如果vnode不是文本节点或者注释节点if (isUndef(vnode.text)) {
      // 并且都有子节点if (isDef(oldCh) && isDef(ch)) {
        // 并且子节点不完全一致,则调用updateChildrenif (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)

        // 如果只有新的vnode有子节点
      } elseif (isDef(ch)) {
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        // elm已经引用了老的dom节点,在老的dom节点上添加子节点addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)

        // 如果新vnode没有子节点,而vnode有子节点,直接删除老的oldCh
      } elseif (isDef(oldCh)) {
        removeVnodes(elm, oldCh, 0, oldCh.length - 1)

        // 如果老节点是文本节点
      } elseif (isDef(oldVnode.text)) {
        nodeOps.setTextContent(elm, '')
      }

      // 如果新vnode和老vnode是文本节点或注释节点// 但是vnode.text != oldVnode.text时,只需要更新vnode.elm的文本内容就可以
    } elseif (oldVnode.text !== vnode.text) {
      nodeOps.setTextContent(elm, vnode.text)
    }
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }
复制代码

patchVnode主要做了几个判断:

  • 新节点是否是文本节点,如果是,则直接更新dom的文本内容为新节点的文本内容

  • 新节点和旧节点如果都有子节点,则处理比较更新子节点

  • 只有新节点有子节点,旧节点没有,那么不用比较了,所有节点都是全新的,所以直接全部新建就好了,新建是指创建出所有新DOM,并且添加进父节点

  • 只有旧节点有子节点而新节点没有,说明更新后的页面,旧节点全部都不见了,那么要做的,就是把所有的旧节点删除,也就是直接把DOM 删除

子节点不完全一致,则调用updateChildren

functionupdateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0// 旧头索引let newStartIdx = 0// 新头索引let oldEndIdx = oldCh.length - 1// 旧尾索引let newEndIdx = newCh.length - 1// 新尾索引let oldStartVnode = oldCh[0] // oldVnode的第一个childlet oldEndVnode = oldCh[oldEndIdx] // oldVnode的最后一个childlet newStartVnode = newCh[0] // newVnode的第一个childlet newEndVnode = newCh[newEndIdx] // newVnode的最后一个childlet oldKeyToIdx, idxInOld, vnodeToMove, refElm

    // removeOnly is a special flag used only by <transition-group>// to ensure removed elements stay in correct relative positions// during leaving transitionsconst canMove = !removeOnly

    // 如果oldStartVnode和oldEndVnode重合,并且新的也都重合了,证明diff完了,循环结束while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      // 如果oldVnode的第一个child不存在if (isUndef(oldStartVnode)) {
        // oldStart索引右移
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left// 如果oldVnode的最后一个child不存在
      } elseif (isUndef(oldEndVnode)) {
        // oldEnd索引左移
        oldEndVnode = oldCh[--oldEndIdx]

      // oldStartVnode和newStartVnode是同一个节点
      } elseif (sameVnode(oldStartVnode, newStartVnode)) {
        // patch oldStartVnode和newStartVnode, 索引左移,继续循环patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]

      // oldEndVnode和newEndVnode是同一个节点
      } elseif (sameVnode(oldEndVnode, newEndVnode)) {
        // patch oldEndVnode和newEndVnode,索引右移,继续循环patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]

      // oldStartVnode和newEndVnode是同一个节点
      } elseif (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right// patch oldStartVnode和newEndVnodepatchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
        // 如果removeOnly是false,则将oldStartVnode.eml移动到oldEndVnode.elm之后
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        // oldStart索引右移,newEnd索引左移
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]

      // 如果oldEndVnode和newStartVnode是同一个节点
      } elseif (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left// patch oldEndVnode和newStartVnodepatchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
        // 如果removeOnly是false,则将oldEndVnode.elm移动到oldStartVnode.elm之前
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        // oldEnd索引左移,newStart索引右移
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]

      // 如果都不匹配
      } else {
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)

        // 尝试在oldChildren中寻找和newStartVnode的具有相同的key的Vnode
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)

        // 如果未找到,说明newStartVnode是一个新的节点if (isUndef(idxInOld)) { // New element// 创建一个新VnodecreateElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)

        // 如果找到了和newStartVnodej具有相同的key的Vnode,叫vnodeToMove
        } else {
          vnodeToMove = oldCh[idxInOld]
          /* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && !vnodeToMove) {
            warn(
              'It seems there are duplicate keys that is causing an update error. ' +
              'Make sure each v-for item has a unique key.'
            )
          }

          // 比较两个具有相同的key的新节点是否是同一个节点//不设key,newCh和oldCh只会进行头尾两端的相互比较,设key后,除了头尾两端的比较外,还会从用key生成的对象oldKeyToIdx中查找匹配的节点,所以为节点设置key可以更高效的利用dom。if (sameVnode(vnodeToMove, newStartVnode)) {
            // patch vnodeToMove和newStartVnodepatchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
            // 清除
            oldCh[idxInOld] = undefined// 如果removeOnly是false,则将找到的和newStartVnodej具有相同的key的Vnode,叫vnodeToMove.elm// 移动到oldStartVnode.elm之前
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)

          // 如果key相同,但是节点不相同,则创建一个新的节点
          } else {
            // same key but different element. treat as new elementcreateElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
          }
        }

        // 右移
        newStartVnode = newCh[++newStartIdx]
      }
    }
复制代码

while循环主要处理了以下五种情景:

  • 当新老 VNode 节点的 start 相同时,直接 patchVnode ,同时新老 VNode 节点的开始索引都加 1

  • 当新老 VNode 节点的 end相同时,同样直接 patchVnode ,同时新老 VNode 节点的结束索引都减 1

  • 当老 VNode 节点的 start 和新 VNode 节点的 end 相同时,这时候在 patchVnode 后,还需要将当前真实 dom 节点移动到 oldEndVnode 的后面,同时老 VNode 节点开始索引加 1,新 VNode 节点的结束索引减 1

  • 当老 VNode 节点的 end 和新 VNode 节点的 start 相同时,这时候在 patchVnode 后,还需要将当前真实 dom 节点移动到 oldStartVnode 的前面,同时老 VNode 节点结束索引减 1,新 VNode 节点的开始索引加 1

  • 如果都不满足以上四种情形,那说明没有相同的节点可以复用,则会分为以下两种情况:

  • 从旧的 VNode 为 key 值,对应 index 序列为 value 值的哈希表中找到与 newStartVnode 一致 key 的旧的 VNode 节点,再进行patchVnode,同时将这个真实 dom移动到 oldStartVnode 对应的真实 dom 的前面

  • 调用 createElm 创建一个新的 dom 节点放到当前 newStartIdx 的位置

小结

  • 当数据发生改变时,订阅者watcher就会调用patch给真实的DOM打补丁

  • 通过isSameVnode进行判断,相同则调用patchVnode方法

  • patchVnode做了以下操作:

  • 找到对应的真实dom,称为el

  • 如果都有都有文本节点且不相等,将el文本节点设置为Vnode的文本节点

  • 如果oldVnode有子节点而VNode没有,则删除el子节点

  • 如果oldVnode没有子节点而VNode有,则将VNode的子节点真实化后添加到el

  • 如果两者都有子节点,则执行updateChildren函数比较子节点

  • updateChildren主要做了以下操作:

  • 设置新旧VNode的头尾指针

  • 新旧头尾指针进行比较,循环向中间靠拢,根据情况调用patchVnode进行patch重复流程、调用createElem创建一个新节点,从哈希表寻找 key一致的VNode 节点再分情况操作

大厂面试题分享 面试题库

后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/381754.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Window问题详解(下)

建议先看一下 Window问题详解(上) 思路② 既然会超时,那该怎么办呢? 显然需要一个更快速的方法来解决这个问题! 我们先来观察一下图片: 我们发现,每一次选中的数都会增加下一个。 !!!!! 因此,我们可以根据此特性优化时间!! 第一次先求出前 k − 1 k-1 k−

hdfs file system shell的简单使用

文章目录1、背景2、hdfs file system shell命令有哪些3、确定shell操作的是哪个文件系统4、本地准备如下文件5、hdfs file system shell5.1 mkdir创建目录5.2 put上传文件5.3 ls查看目录或文件5.4 cat 查看文件内容5.5 head 查看文件前1000字节内容5.6 tail 查看文件后1000字节…

Kubernetes12:k8s集群安全机制 ***与证书生成***

Kubernetes12&#xff1a;k8s集群安全机制 1、概述 1&#xff09;访问一个k8s集群的时候&#xff0c;需要经过以下三个步骤才能完成具体操作 第一步&#xff1a;认证操作第二部&#xff1a;鉴权操作&#xff08;授权&#xff09;第三部&#xff1a;准入控制操作 2&#xff…

小白晋升大牛的13个项目

入门到放弃 “C/C真的太难学了,我准备放弃了!” 很多初学者在学完C和C的基本语法后&#xff0c;就停滞不前了&#xff0c;最终走向“从入门到放弃”。其实&#xff0c;我们初学者最需要的不是HelloWorld&#xff0c;也不是语法知识的堆砌&#xff0c;需要的只是实战项目的磨砺…

「TCG 规范解读」基础设施架构和协议 (1)

可信计算组织&#xff08;Ttrusted Computing Group,TCG&#xff09;是一个非盈利的工业标准组织&#xff0c;它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立&#xff0c;并采纳了由可信计算平台联盟&#xff08;the Trusted Computing Platform Alli…

9、STM32 SDIO FATFS(SD卡)

本篇文章使用STM32对SD卡通过SDIO配置&#xff0c;读写文件 在使用FATFS时值得注意得是若通信SDIO不启动DMA方式读写&#xff0c;容易导致其他任务中断打断读写时序&#xff0c;导致FATFS的执行出现异常&#xff0c;常见返回为FR_DISK_ERR, / (1) A hard error occurred in the…

【论文阅读】Robust Invertible Image Steganography (CVPR 2022)

作者来自北大深研院 网上已有介绍&#xff1a;https://news.pkusz.edu.cn/info/1002/6538.htm 针对传统图像隐写方法对高斯噪声、泊松噪声和有损压缩鲁棒性差的问题&#xff0c;提出了一种基于流的鲁棒可逆图像隐写框架RIIS。框架如下图 一、方法概述&#xff1a; 基于流的可…

计算机组成原理4小时速成6:输入输出系统,io设备与cpu的链接方式,控制方式,io设备,io接口,并行串行总线

计算机组成原理4小时速成6&#xff1a;输入输出系统&#xff0c;io设备与cpu的链接方式&#xff0c;控制方式&#xff0c;io设备&#xff0c;io接口&#xff0c;并行串行总线 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c…

计算机网络--网络层 IPv4地址概述(day05)

网络层 网络层提供的两种服务 IPv4地址概述 IPv4地址就是给因特网(Internet)上的每一台主机(或路由器&#xff09;的每一个接口分配一个在全世界范围内是唯一的32比特的标识符 IPv4地址的编址方法经历了如下三个历史阶段&#xff1a; 分类编址 1981划分子网 1985无分类编址…

JavaScript Array 数组对象

文章目录JavaScript Array 数组对象什么是数组?创建一个数组访问数组JavaScript Array 数组对象 数组对象的作用是&#xff1a;使用单独的变量名来存储一系列的值。 创建数组, 为其赋值&#xff1a; 实例 var mycars new Array(); mycars[0] "Saab"; mycars[1]…

【虚幻引擎】UE4源码解析FWorldContent、UWorld、ULevel、UGameInstance、UEngine

一、UEngine Engine&#xff0c;因为也是很基础的类&#xff0c;再加上开发过程中会经常访问到该类型&#xff0c;因此UE4引擎也在代码全局范围内定义了一个该类型的全局变量&#xff1a;UEngine* GEngine供开发者直接调用。该最基础的类型分化成了两个子类&#xff1a;UGameE…

【面试题】 JavaScript 字符串截取方法有哪些?

大厂面试题分享 面试题库后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面试题库在 JavaScript 中&#xff0c;可以使用 substr()、slice() 和 substring() 方法截取字符串。substring()substring() 方法返回一个字符串在开始…

Lock锁心得

lock接口最常见的实现类ReentrantLock&#xff0c;通常情况下&#xff0c;lock只允许一个线程访问共享资源&#xff0c;也有特殊情况&#xff0c;比如读写锁里的读锁。lock和synchronizrd是常见的锁&#xff0c;都可以让代码变得安全。但是功能上有差别&#xff0c;二者不是能替…

笃行不怠勾勒人才图,望城区人才工作为高质量发展增添强劲动力

功以才成&#xff0c;业以才广。人才是经济高质量发展的重要资源。党的二十大报告明确“深入实施人才强国战略”&#xff0c;指出“实施更加积极、更加开放、更加有效的人才政策”。作为湖南“一核两副三带四区”区域经济发展格局的战略支点&#xff0c;望城区积极发挥着政策主…

TCP协议详解—TCP各个报头属性的作用

文章目录一.TCP是什么二.TCP协议格式1.报头属性解释TCP首部长度/如何解包分用三.确认应答机制-tcp如何保证可靠性1.确认应答机制2.序号/确认序号-如何保证报文按序到达3.为什么要两个序号四.16位窗口大小-调整发送策略五.6个标志位一.TCP是什么 首先我们需要知道TCP是什么,TCP全…

你知道Object类和Objects的常用方法吗

文章目录Object的常用方法Objects的常用方法hashCodehashisNullequalsrequireNonNullcomparenonNull大家好&#xff0c;Leo又来了!!!最近在网上看到别人分析Objects中isNullfanfan方法来判断&#xff0c;我就想到之前写的一坨坨null&#xff0c;突然很惭愧&#xff0c;特地去研…

IIC总线式驱动开发(mpu6050)

目录 一、I2C总线背景知识 二、Exynos4412 I2C收发实现之裸机版 2.1 发送 2.2 接收 三、Linux内核对I2C总线的支持 四、MPU6050 五、应用层直接使用I2C通道 5.1 预备工作&#xff1a; 5.1.1 5.1.2 5.2 应用层直接使用i2c总线的代码实现 5.2.1 调用read、write实现接…

解决“vue“不是内部或外部命令问题

通过vue指令使用脚手架初始化项目时&#xff0c;在cmd中输入vue init webpack vue_project&#xff0c;终端报错&#xff1a; vue 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。解决方案&#xff1a; 1、确定npm是否已正确安装&#xff0c;在cmd中输入如下…

AcWing算法提高课-3.1.3香甜的黄油

宣传一下算法提高课整理 <— CSDN个人主页&#xff1a;更好的阅读体验 <— 题目传送门点这里 题目描述 农夫John发现了做出全威斯康辛州最甜的黄油的方法&#xff1a;糖。 把糖放在一片牧场上&#xff0c;他知道 N 只奶牛会过来舔它&#xff0c;这样就能做出能卖好价…

Dynamics 365 环境搭建 之 SQL Server 2019问题汇总

前言 随着产品的迭代&#xff0c;SQL Server 2016 已经是7-8年前的产品&#xff0c;新环境的搭建必然要随之更新。截至目前&#xff08;2023年3月&#xff09;Dynamics 365 9.1 op版本的软件要求如下。 Windows ServerSQL ServerWindows Server 2019 标准 Windows Server 20…