2020大厂前端面试之vue专题(三)

2023-11-17

21.v-model中的实现原理及如何自定义v-model

  • v-model 可以看成是 value+input方法 的语法糖 input v-model checkbox v-model select v-model
    组件的v-model 就是value+input的语法糖

理解:

  • 组件的 v-model 是 value+input方法 的语法糖
<el-checkbox :value="" @input=""></el-checkbox>
<el-checkbox v-model="check"></el-checkbox>
  • 可以自己重新定义 v-model 的含义
Vue.component('el-checkbox',{
template:`<input type="checkbox" :checked="check"
@change="$emit('change',$event.target.checked)">`,
model:{
prop:'check', // 更改默认的value的名字
event:'change' // 更改默认的方法名
},
props: {
check: Boolean
},
})

原理:

  • 会将组件的 v-model 默认转化成value+input
const VueTemplateCompiler = require('vue-template-compiler');
const ele = VueTemplateCompiler.compile('<el-checkbox v-model="check"></elcheckbox>');
// with(this) {
// return _c('el-checkbox', {
// model: {
// value: (check),
// callback: function ($$v) {
// check = $$v
// },
// expression: "check"
// }
// })
// }
function transformModel (options, data: any) {
const prop = (options.model && options.model.prop) || 'value'
const event = (options.model && options.model.event) || 'input'
;(data.attrs || (data.attrs = {}))[prop] = data.model.value
const on = data.on || (data.on = {})
const existing = on[event]
const callback = data.model.callback
if (isDef(existing)) {
if (
Array.isArray(existing)
? existing.indexOf(callback) === -1
: existing !== callback
) {
on[event] = [callback].concat(existing)
}
} else {
on[event] = callback
}
}
  • 原生的 v-model ,会根据标签的不同生成不同的事件和属性
    const VueTemplateCompiler = require('vue-template-compiler');
    const ele = VueTemplateCompiler.compile('<input v-model="value"/>');
/**
    with(this) {
    return _c('input', {
    directives: [{
    name: "model",
    rawName: "v-model",
    value: (value),
    expression: "value"
    }],
    domProps: {
    "value": (value)
    },
    on: {
    "input": function ($event) {
    if ($event.target.composing) return;
    value = $event.target.value
    }
    }
    })
    }
    */

编译时:不同的标签解析出的内容不一样

if (el.component) {
genComponentModel(el, value, modifiers)
// component v-model doesn't need extra runtime
return false
} else if (tag === 'select') {
genSelect(el, value, modifiers)
} else if (tag === 'input' && type === 'checkbox') {
genCheckboxModel(el, value, modifiers)
} else if (tag === 'input' && type === 'radio') {
genRadioModel(el, value, modifiers)
} else if (tag === 'input' || tag === 'textarea') {
genDefaultModel(el, value, modifiers)
} else if (!config.isReservedTag(tag)) {
genComponentModel(el, value, modifiers)
// component v-model doesn't need extra runtime
return false
}

运行时:会对元素处理一些关于输入法的问题

inserted (el, binding, vnode, oldVnode) {
if (vnode.tag === 'select') {
// #6903
if (oldVnode.elm && !oldVnode.elm._vOptions) {
mergeVNodeHook(vnode, 'postpatch', () => {
directive.componentUpdated(el, binding, vnode)
})
} else {
setSelected(el, binding, vnode.context)
}
el._vOptions = [].map.call(el.options, getValue)
} else if (vnode.tag === 'textarea' || isTextInputType(el.type)) {
el._vModifiers = binding.modifiers
if (!binding.modifiers.lazy) {
el.addEventListener('compositionstart', onCompositionStart)
el.addEventListener('compositionend', onCompositionEnd)
// Safari < 10.2 & UIWebView doesn't fire compositionend when
// switching focus before confirming composition choice
// this also fixes the issue where some browsers e.g. iOS Chrome
// fires "change" instead of "input" on autocomplete.
el.addEventListener('change', onCompositionEnd)
/* istanbul ignore if */
if (isIE9) {
el.vmodel = true
}}}}

22.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,js
if (key === 'textContent' || key === 'innerHTML') {
if (vnode.children) vnode.children.length = 0
if (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])
}
}

23. Vue父子组件生命周期调用顺序

加载渲染过程

  • 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

子组件更新过程

  • 父beforeUpdate->子beforeUpdate->子updated->父updated

父组件更新过程

  • 父beforeUpdate->父updated

销毁过程

  • 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

理解:

  • 组件的调用顺序都是先父后子,渲染完成的顺序肯定是先子后父
  • 组件的销毁操作是先父后子,销毁完成的顺序是先子后父

原理:

在这里插入图片描述

function patch(oldVnode, vnode, hydrating, removeOnly) {
        if (isUndef(vnode)) {
            if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
            return
        }
        let isInitialPatch = false
        const insertedVnodeQueue = [] // 定义收集所有组件的insert hook方法的数组
        // somthing ...
        createElm(
            vnode,
            insertedVnodeQueue,
            oldElm._leaveCb ? null : parentElm,
            nodeOps.nextSibling(oldElm)
        )
        // somthing...
        // 最终会依次调用收集的insert hook
        invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
        return vnode.elm
    }
    function createElm(
        vnode,
        insertedVnodeQueue,
        parentElm,
        refElm,
        nested,
        ownerArray,
        index
    ) {
        // createChildren会递归创建儿子组件
        createChildren(vnode, children, insertedVnodeQueue)
        // something...
    }
    // 将组件的vnode插入到数组中
    function invokeCreateHooks(vnode, insertedVnodeQueue) {
        for (let i = 0; i < cbs.create.length; ++i) {
            cbs.create[i](emptyNode, vnode)
        }
        i = vnode.data.hook // Reuse variable
        if (isDef(i)) {
            if (isDef(i.create)) i.create(emptyNode, vnode)
            if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
        }
    }
    // insert方法中会依次调用mounted方法
    insert(vnode: MountedComponentVNode) {
        const { context, componentInstance } = vnode
        if (!componentInstance._isMounted) {
            componentInstance._isMounted = true
            callHook(componentInstance, 'mounted')
        }
    }
    function invokeInsertHook(vnode, queue, initial) {
        // delay insert hooks for component root nodes, invoke them after the
        // element is really inserted
        if (isTrue(initial) && isDef(vnode.parent)) {
            vnode.parent.data.pendingInsert = queue
        } else {
            for (let i = 0; i < queue.length; ++i) {
                queue[i].data.hook.insert(queue[i]); // 调用insert方法
            }
        }
    }

    Vue.prototype.$destroy = function () {
        callHook(vm, 'beforeDestroy') //
        // invoke destroy hooks on current rendered tree
        vm.__patch__(vm._vnode, null) // 先销毁儿子
        // fire destroyed hook
        callHook(vm, 'destroyed')
    }

24.Vue组件如何通信? 单向数据流

  • 父子间通信 父->子通过 props 、子-> 父 o n 、 on、 onemit (发布订阅)
  • 获取父子组件实例的方式 p a r e n t 、 parent、 parentchildren
  • 在父组件中提供数据子组件进行消费 Provide、inject 插件
  • Ref 获取实例的方式调用组件的属性或者方法
  • Event Bus 实现跨组件通信 Vue.prototype.$bus = new Vue
  • Vuex 状态管理实现通信 $attrs $listeners

25.Vue中相同逻辑如何抽离?

  • Vue.mixin 用法 给组件每个生命周期,函数等都混入一些公共逻辑
    Vue.mixin = function (mixin: Object) {
        this.options = mergeOptions(this.options, mixin); // 将当前定义的属性合并到每个
        组件中
        return this
    }
    export function mergeOptions(
        parent: Object,
        child: Object,
        vm?: Component
    ): Object {
        if (!child._base) {
            if (child.extends) { // 递归合并extends
                parent = mergeOptions(parent, child.extends, vm)
            }
            if (child.mixins) { // 递归合并mixin
                for (let i = 0, l = child.mixins.length; i < l; i++) {
                    parent = mergeOptions(parent, child.mixins[i], vm)
                }
            }
        }
        const options = {} // 属性及生命周期的合并
        let key
        for (key in parent) {
            mergeField(key)
        }
        for (key in child) {
            if (!hasOwn(parent, key)) {
                mergeField(key)
            }
        }
        function mergeField(key) {
            const strat = strats[key] || defaultStrat
            // 调用不同属性合并策略进行合并
            options[key] = strat(parent[key], child[key], vm, key)
        }
        return options
    }

26.为什么要使用异步组件?

理解:

  • 如果组件功能多打包出的结果会变大,我可以采用异步的方式来加载组件。主要依赖 import() 这
    个语法,可以实现文件的分割加载。
components:{
AddCustomerSchedule:(resolve)=>import("../components/AddCustomer") //
require([])
}

原理:

export function (
        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 = Ctor
            Ctor = resolveAsyncComponent(asyncFactory, baseCtor) // 默认调用此函数时返回
            undefiend
            // 第二次渲染时Ctor不为undefined
            if (Ctor === undefined) {
                return createAsyncPlaceholder( // 渲染占位符 空虚拟节点
                    asyncFactory,
                    data,
                    context,
                    children,
                    tag
                )
            }
        }
    }
    function resolveAsyncComponent(
        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 = true
                forceRender(true)
            }
        })
        const res = factory(resolve, reject)// 1.将resolve方法和reject方法传入,用户调用
        resolve方法后
        sync = false
        return factory.resolved
    }

27.什么是作用域插槽?

理解:

1.插槽:
<app><div slot="a">xxxx</div><div slot="b">xxxx</div></app>
slot name="a"
slot name="b"
  • 创建组件虚拟节点时,会将组件的儿子的虚拟节点保存起来。当初始化组件时,通过插槽属性将儿
    子进行分类 {a:[vnode],b[vnode]}
  • 渲染组件时会拿对应的slot属性的节点进行替换操作。(插槽的作用域为父组件)
2.作用域插槽:
  • 作用域插槽在解析的时候,不会作为组件的孩子节点。会解析成函数,当子组件渲染时,会调用此
    函数进行渲染。(插槽的作用域为子组件)

原理:

在这里插入图片描述

1.插槽:
    const VueTemplateCompiler = require('vue-template-compiler');
    let ele = VueTemplateCompiler.compile(`
<my-component>
<div slot="header">node</div>
<div>react</div>
<div slot="footer">vue</div>
</my-component>
`)
    /**
    with(this) {
    return _c('my-component',
    [_c('div', {
    attrs: {
    "slot": "header"
    },
    slot: "header"
    }, [_v("node")] // _文本及诶点
    ), _v(" "), _c('div', [_v("react")]), _v(" "), _c('div', {
    attrs: {
    "slot": "footer"
    },
    slot: "footer"
    }, [_v("vue")])])
    }
    */
    const VueTemplateCompiler = require('vue-template-compiler');
    let ele = VueTemplateCompiler.compile(`
<div>
<slot name="header"></slot>
<slot name="footer"></slot>
<slot></slot>
</div>
`);
/**
    with(this) {
    return _c('div', [_v("node"), _v(" "), _t(_v("vue")])]), _v(" "),
    _t("default")], 2)
    }
    **/
// _t定义在 core/instance/render-helpers/index.js
作用域插槽:
let ele = VueTemplateCompiler.compile(`
<app>
<div slot-scope="msg" slot="footer">{{msg.a}}</div>
</app>
`);
/**
with(this) {
return _c('app', {
scopedSlots: _u([{ // 作用域插槽的内容会被渲染成一个函数
key: "footer",
fn: function (msg) {
return _c('div', {}, [_v(_s(msg.a))])
}
}])
})
}
}
*/
const VueTemplateCompiler = require('vue-template-compiler');
VueTemplateCompiler.compile(`
<div>
<slot name="footer" a="1" b="2"></slot>
</div>
`);
/**
with(this) {
return _c('div', [_t("footer", null, {
"a": "1",
"b": "2"
})], 2)
}
**/

28.谈谈你对 keep-alive 的了解?

理解:

  • keep-alive 可以实现组件的缓存,当组件切换时不会对当前组件进行卸载,常用的2个属性
    include / exclude ,2个生命周期 activated , deactivated LRU算法

原理:

 export default {
        name: 'keep-alive',
        abstract: true, // 抽象组件
        props: {
            include: patternTypes,
            exclude: patternTypes,
            max: [String, Number]
        },
        created() {
            this.cache = Object.create(null) // 创建缓存列表
            this.keys = [] // 创建缓存组件的key列表
        },
        destroyed() { // keep-alive销毁时 会清空所有的缓存和key
            for (const key in this.cache) { // 循环销毁
                pruneCacheEntry(this.cache, key, this.keys)
            }
        },
        mounted() { // 会监控include 和 include属性 进行组件的缓存处理
            this.$watch('include', val => {
                pruneCache(this, name => matches(val, name))
            })
            this.$watch('exclude', val => {
                pruneCache(this, name => !matches(val, name))
            })
        },
        render() {
            const slot = this.$slots.default // 会默认拿插槽
            const vnode: VNode = getFirstComponentChild(slot) // 只缓存第一个组件
            const componentOptions: ?VNodeComponentOptions = vnode &&
                vnode.componentOptions
            if (componentOptions) {
                // 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
                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 // 如果组件没key 就自己通过 组件的标签和key和cid 拼接一个key
                if (cache[key]) {
                    vnode.componentInstance = cache[key].componentInstance // 直接拿到组件实// make current key freshest
                    remove(keys, key) // 删除当前的 [b,c,d,e,a] // LRU 最近最久未使用法
                    keys.push(key) // 并将key放到后面[b,a]
                } else {
                    cache[key] = vnode // 缓存vnode
                    keys.push(key) // 将key 存入
                    // prune oldest entry
                    if (this.max && keys.length > parseInt(this.max)) { // 缓存的太多超过了max
                        就需要删除掉
                        pruneCacheEntry(cache, keys[0], keys, this._vnode) // 要删除第0个 但是现
                        在渲染的就是第0}
                }
                vnode.data.keepAlive = true // 并且标准keep-alive下的组件是一个缓存组件
            }
            return vnode || (slot && slot[0]) // 返回当前的虚拟节点
        }
    }

29.Vue中常见性能优化

1.编码优化:

    1. 不要将所有的数据都放在data中,data中的数据都会增加getter和setter,会收集对应的
      watcher
    1. vue 在 v-for 时给每项元素绑定事件需要用事件代理
    1. SPA 页面采用keep-alive缓存组件
    1. 拆分组件( 提高复用性、增加代码的可维护性,减少不必要的渲染 )
    1. v-if 当值为false时内部指令不会执行,具有阻断功能,很多情况下使用v-if替代v-show
    1. key 保证唯一性 ( 默认 vue 会采用就地复用策略 )
    1. Object.freeze 冻结数据
    1. 合理使用路由懒加载、异步组件
    1. 尽量采用runtime运行时版本
    1. 数据持久化的问题 (防抖、节流)

2. Vue 加载性能优化:

  • 第三方模块按需导入 ( babel-plugin-component )
  • 滚动到可视区域动态加载 ( https://tangbc.github.io/vue-virtual-scroll-list )
  • 图片懒加载 (https://github.com/hilongjw/vue-lazyload.git)

3.用户体验:

  • app-skeleton 骨架屏
  • app-shell app壳
  • pwa serviceworker

4. SEO 优化:

  • 预渲染插件 prerender-spa-plugin
  • 服务端渲染 ssr

5.打包优化:

  • 使用 cdn 的方式加载第三方模块
  • 多线程打包 happypack
  • splitChunks 抽离公共文件
  • sourceMap 生成

6.缓存,压缩

  • 客户端缓存、服务端缓存
  • 服务端 gzip 压缩

30.Vue3.0你知道有哪些改进?

  • Vue3 采用了TS来编写
  • 支持 Composition API
  • Vue3 中响应式数据原理改成 proxy
  • vdom 的对比算法更新,只更新 vdom 的绑定了动态数据的部分

31.实现hash路由和history路由

  • onhashchange #
  • history.pushState h5 api

32.Vue-Router中导航守卫有哪些?

完整的导航解析流程 (runQueue)

    1. 导航被触发。
    1. 在失活的组件里调用离开守卫。
    1. 调用全局的 beforeEach 守卫。
    1. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
    1. 在路由配置里调用 beforeEnter 。
    1. 解析异步路由组件。
    1. 在被激活的组件里调用 beforeRouteEnter 。
    1. 调用全局的 beforeResolve 守卫 (2.5+)。
    1. 导航被确认。
    1. 调用全局的 afterEach 钩子。
    1. 触发 DOM 更新。
    1. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

33.action 和 mutation区别

  • mutation 是同步更新数据(内部会进行是否为异步方式更新数据的检测) $watch 严格模式下会报
  • action 异步操作,可以获取数据后调佣 mutation 提交最终数据

34.简述Vuex工作原理

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

2020大厂前端面试之vue专题(三) 的相关文章

随机推荐

  • (转)基于FPGA技术的FAST行情解码研究

    http mp weixin qq com s BviH6gAqej6lHd9XxFKUfg 交易技术前沿 基于FPGA技术的FAST行情解码研究 钟浪辉 陈敏 陈坚 刘啸林 秦轶轩 李道双 2017 09 08 上交所技术服务 本文选自
  • 数据库分表分库理论

    1 数据切分 关系型数据库本身比较容易成为系统瓶颈 单机存储容量 连接数 处理能力都有限 当单表的数据量达到1000W或100G以后 由于查询维度较多 即使添加从库 优化索引 做很多操作时性能仍下降严重 此时就要考虑对其进行切分了 切分的目
  • 第十四届蓝桥杯模拟赛(第一期)—保姆级解释(C语言版)

    1 二进制位数 问题描述 十进制整数 2 在十进制中是 1 位数 在二进制中对应 10 是 2 位数 十进制整数 22 在十进制中是 2 位数 在二进制中对应 10110 是 5 位数 请问十进制整数 2022 在二进制中是几位数 incl
  • cmake(03) : 平台,架构及编译器判断

    1 cmake检测平台架构及编译器的原理 cmake在检测编译器的时候 用了一种很暴力的方法 可以在不运行实际代码的情况下直接知道目标平台的信息 做法是这样的 首先生成一个 cpp文件 包含一些平台检测的 ifdef Identify kn
  • Linux 环境下 docker 安装 ES 7.15.2 和 kibana 7.15.2 详细步骤

    目标 在一台机器内设置3个ES节点和1个kibana节点正常运行 条件 本机器内的IP 192 168 211 130 1 首先安装docker 步骤详见链接https blog csdn net m0 55380752 article d
  • 期货投机和套利交易

    一 期货投机的概念 1 期货投机的定义 指交易者通过预测期货合约未来价格的变化 以在期货市场上获取价差收益为目的的期货交易行为 期货交易具有保证金的杆杠机制 双向交易和对冲机制 当日无负债的结算制度 强行平仓制度 使得期货投机易有高收益 高
  • 微信小程序_安装第三方的UI组件库(详细步骤)

    微信小程序的UI组件库 在我了解的 有两种方式 一种是微信小程序的官方文档自带的小程序 另一种是vant的小程序的UI组件库 一 官方自带的小程序的安装步骤 官方文档 https developers weixin qq com minip
  • Mysql管理

    一 Mysql 一 前言 MySQL是一个关系型数据库管理系统 由瑞典MySQL AB 公司开发 目前属于 Oracle 旗下产品 MySQL 是最流行的关系型数据库管理系统之一 在 WEB 应用方面 MySQL是最好的 RDBMS Rel
  • C++11:转移语义

    为什么需要转移语义 gt File Name main cpp gt Author Xianghao Jia gt mail xianghaojia sina com gt Created Time Mon 09 Dec 2019 04 2
  • ubuntu创建新用户并设置samba服务

    1 新建自己的用户并查看 sudo useradd m s bin bash 用户名 sudo passwd 用户名 ls home t 或者 1创建一个新的普通用户 m 表示用户 s表示shell环境 sudo useradd m gue
  • Selenium:网页屏幕截图

    前言 在学习 Selenium 做 UI自动化时 往往会遇到需要截图的时候 框架自带截图方法 方法 方法释义 save screenshot filename 截取当前屏幕截图 并保存为指定文件 此方法没必要使用 get screensho
  • iOS音视频—Shell脚本语言(语法-echo命令&参数传递)

    That wonderful world is waiting for me Shell脚本语言 语法 echo命令 1 显示普通字符串 echo iPhoneX 标配 8388 2 显示转义字符 echo iPhoneX 顶配 9688
  • 每日一题:路径计数

    路径计数 题目 Daimayuan Online Judge f i j 表示从左上角走到 i j 的方案数 状态转移 i j 由 i 1 j 和 i j 1 转移而来 初始状态 得使得f 1 1 为1 所以初始化f 1 0 或者f 0 1
  • 基于单光子探测的多脉冲周期符合远距离测距

    激光测距技术通过发射主动激光信号对目标进行探测 接收由目标漫反射回来的回波信号并进行统计 处理及换算 从而得到目标的距离 速度信息 实现对目标距离信息的探测 凭借其系统简单 操作灵活 高精度等特点 被广泛运用于民用 科研及军事等各类场合 基
  • Lambda表达式使用详细讲解

    目录 1 新思想 1 1函数式编程思想 1 2 函数式接口 2 通往lambda之路 2 1 什么是lambda表示式 2 2 lambda表示式有哪些特点 2 3 lambda表示式使用场景 2 4 lambda表示式语法 2 5 Lam
  • [Unity] Input.mousetion 屏幕坐标转世界坐标。

    代码如下 Vector3 screenPos Input mousePosition screenPos z 5 0f Vector3 p1 Camera main ScreenToWorldPoint screenPos Vector3
  • 释放数据价值这道难题,Smartbi V11有解

    未来简史 预言 数据将成为人们未来的信仰 未来已来 将至已至 如今 数据所扮演的角色与作用超乎想象 从政府将数据要素列入生产要素之中 到数据驱动型业务场景涌现 企业与组织对于数据及其价值的认可度明显提升 如何充分释放数据价值已成为所有企业与
  • Dijkstra与Bellman-Ford算法对比

    文章目录 TOC Dijkstra Dijkstra 伪代码 Dijkstra 为什么不能有负权重 Dijkstra算法复杂度 Bellman Ford算法 Bellman Ford算法伪代码 Bellman Ford判断是否有负权 Bel
  • 大文件上传如何做断点续传?

    是什么 不管怎样简单的需求 在量级达到一定层次时 都会变得异常复杂 文件上传简单 文件变大就复杂 上传大文件时 以下几个变量会影响我们的用户体验 服务器处理数据的能力 请求超时 网络波动 上传时间会变长 高频次文件上传失败 失败后又需要重新
  • 2020大厂前端面试之vue专题(三)

    21 v model中的实现原理及如何自定义v model v model 可以看成是 value input方法 的语法糖 input v model checkbox v model select v model 组件的v model