发布时间:2025-06-24 20:15:52  作者:北方职教升学中心  阅读量:727


作用" style="background-color:transparent;">一、如vue-router

  • 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。如vue-router
  • 二、采用函数的形式,initData时会将其作为工厂函数都会返回全新data对象

    第四章 动态给vue的data添加一个新的属性时会发生什么?怎样解决?

    image.png

    一、结论

    • 根实例对象data可以是对象也可以是函数(根实例是单例),不会产生数据污染情况
    • 组件实例对象data必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。

      第一章 v-if和v-for的优先级是什么?

      <p v-for="(value,key) in item" :key="key"> {{ value}}</p><button @click="addProperty">动态添加新属性</button>

      实例化一个vue实例,定义data属性和methods方法

      const app = new Vue({    el:"#app",   	data:()=>{       	item:{            oldProperty:"旧属性"        }    },    methods:{        addProperty(){            this.items.newProperty = "新属性"  // 为items添加新属性            console.log(this.items)  // 输出带有newProperty的items        }    }})

      点击按钮,发现结果不及预期,数据虽然更新了(console打印出了新属性),但页面并没有更新

      三、直接添加属性的问题">一、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在Vue中每一个.vue文件都可以视为一个组件

      组件的优势

      • 降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,例如输入框,可以替换为日历、什么是首屏加载

              首屏时间(First Contentful Paint),指的是浏览器从响应用户输入网址地址,到首屏内容渲染完成的时间,此时整个网页不一定要全部渲染完成,但需要展示当前视窗需要的内容

      首屏加载可以说是用户体验中最重要的环节

      关于计算首屏时间

      利用performance.timing提供的数据:

      image.png

      通过DOMContentLoad或者performance来计算出首屏时间

      // 方案一:document.addEventListener('DOMContentLoaded', (event) => {    console.log('first contentful painting');});// 方案二:performance.getEntriesByName("first-contentful-paint")[0].startTime// performance.getEntriesByName("first-contentful-paint")[0]// 会返回一个 PerformancePaintTiming的实例,结构如下:{  name: "first-contentful-paint",  entryType: "paint",  startTime: 507.80000002123415,  duration: 0,};

      二、解决方案

    Vue 不允许在已经创建的实例上动态添加新的响应式属性

    若想实现数据与视图同步更新,可采取下面三种解决方案:

    • Vue.set()
    • Object.assign()
    • $forcecUpdated()
    Vue.set()

    Vue.set( target, propertyName/index, value )

    参数

    • {Object | Array} target
    • {string | number} propertyName/index
    • {any} value

    返回值:设置的值

    通过Vue.set向响应式对象中添加一个property,并确保这个新 property同样是响应式的,且触发视图更新

    关于Vue.set源码(省略了很多与本节不相关的代码)

    源码位置:src\core\observer\index.js

    function set (target: Array<any> | Object, key: any, val: any): any {  ...  defineReactive(ob.value, key, val)  ob.dep.notify()  return val}

    这里无非再次调用defineReactive方法,实现新增属性的响应式

    关于defineReactive方法,内部还是通过Object.defineProperty实现属性拦截

    大致代码如下:

    function defineReactive(obj, key, val) {    Object.defineProperty(obj, key, {        get() {            console.log(`get ${key}:${val}`);            return val        },        set(newVal) {            if (newVal !== val) {                console.log(`set ${key}:${newVal}`);                val = newVal            }        }    })}
    Object.assign()

    直接使用Object.assign()添加到对象的新属性不会触发更新

    应创建一个新的对象,合并原对象和混入对象的属性

    this.someObject = Object.assign({},this.someObject,{newProperty1:1,newProperty2:2 ...})
    $forceUpdate

    如果你发现你自己需要在 Vue中做一次强制更新,99.9% 的情况,是你在某个地方做错了事

    $forceUpdate迫使Vue 实例重新渲染

    PS:仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。原理分析

    首先可以看看vue初始化data的代码,data的定义可以是函数也可以是对象

    源码位置:/vue-dev/src/core/instance/state.js

    function initData (vm: Component) {  let data = vm.$options.data  data = vm._data = typeof data === 'function'    ? getData(data, vm)    : data || {}    ...}

    data既能是object也能是function,那为什么还会出现上文警告呢?

    别急,继续看下文

    组件在创建的时候,会进行选项的合并

    源码位置:/vue-dev/src/core/util/options.js

    自定义组件会进入mergeOptions进行选项合并

    Vue.prototype._init = function (options?: Object) {    ...    // merge options    if (options && options._isComponent) {      // optimize internal component instantiation      // since dynamic options merging is pretty slow, and none of the      // internal component options needs special treatment.      initInternalComponent(vm, options)    } else {      vm.$options = mergeOptions(        resolveConstructorOptions(vm.constructor),        options || {},        vm      )}    ...  }

    定义data会进行数据校验

    源码位置:/vue-dev/src/core/instance/init.js

    这时候vm实例为undefined,进入if判断,若data类型不是function,则出现警告提示

    strats.data = function (  parentVal: any,  childVal: any,  vm?: Component): ?Function {  if (!vm) {    if (childVal && typeof childVal !== "function") {      process.env.NODE_ENV !== "production" &&        warn(          'The "data" option should be a function ' +            "that returns a per-instance value in component " +            "definitions.",          vm        );      return parentVal;    }    return mergeDataOrFn(parentVal, childVal);  }  return mergeDataOrFn(parentVal, childVal, vm);};

    四、两者的区别

    两者的区别主要表现在以下几个方面:

    • 编写形式
    • 注册形式
    • 使用场景

    编写形式

    编写组件

    编写一个组件,可以有很多方式,我们最常见的就是vue单文件的这种格式,每一个.vue文件我们都可以看成是一个组件

    vue文件标准格式

    <template></template><script>export default{     ...}</script><style></style>

    我们还可以通过template属性来编写一个组件,如果组件内容多,我们可以在外部定义template组件内容,如果组件内容并不多,我们可直接写在template属性上

    <template id="testComponent">     // 组件显示的内容    <div>component!</div>   </template>Vue.component('componentA',{     template: '#testComponent'      template: `<div>component</div>`  // 组件内容少可以通过这种形式})

    编写插件

    vue插件的实现应该暴露一个 install 方法。这块内容只会在指令的表达式返回 true值的时候被渲染

    v-for 指令基于一个数组来渲染一个列表。注意事项

    1. 永远不要把 v-if 和 v-for 同时用在同一个元素上,带来性能方面的浪费(每次渲染都会先循环再进行条件判断)
    2. 如果避免出现这种情况,则在外层嵌套template(页面渲染不生成dom节点),在这一层进行v-if判断,然后在内部进行v-for循环
    <template v-if="isShow">    <p v-for="item in items"></template>

    如果条件出现在循环内部,可通过计算属性computed提前过滤掉那些不需要显示的项

    computed: {    items: function() {      return this.list.filter(function (item) {        return item.isShow      })    }}

    第二章 SPA首屏加载速度慢的怎么解决?

    image.png

    一、插件是什么">二、范围等组件作具体的实现

  • 调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单

  • 提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级

  • import ElementUI from 'element-ui'Vue.use(ElementUI)

    但实际上我用到的组件只有按钮,分页,表格,输入与警告 所以我们要按需引用

    import { Button, Input, Pagination, Table, TableColumn, MessageBox } from 'element-ui';Vue.use(Button)Vue.use(Input)Vue.use(Pagination)

    组件重复打包

    假设A.js文件是一个常用的库,现在有多个路由使用了A.js文件,这就造成了重复下载

    解决方案:在webpackconfig文件中,修改CommonsChunkPlugin的配置

    minChunks: 3

    minChunks为3表示会把使用3次及以上的包抽离出来,放进公共依赖文件,避免了重复加载组件

    图片资源的压缩

    图片资源虽然不在编码过程中,但它却是对页面性能影响最大的因素,对于所有的图片资源,我们可以进行适当的压缩,对页面上使用到的icon,可以使用在线字体图标,或者雪碧图,将众多小图标合并到同一张图上,用以减轻http请求压力。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组或者对象,而 item 则是被迭代的数组元素的别名

    在 v-for 的时候,建议设置key值,并且保证每个key值是独一无二的,这便于diff算法进行优化

    两者在用法上

    <Modal v-if="isShow" /><li v-for="item in items" :key="item.id">    {{ item.label}}</li>

    二、解决方案

    常见的几种SPA首屏优化方式

    • 减小入口文件积
    • 静态资源本地缓存
    • UI框架按需加载
    • 图片资源的压缩
    • 组件重复打包
    • 开启GZip压缩
    • 使用SSR

    减小入口文件体积

    常用的手段是路由懒加载,把不同路由对应的组件分割成不同的代码块,待路由被请求的时候会单独打包路由,使得入口文件变小,加载速度大大增加

    image.png

    vue-router配置路由的时候,采用动态加载路由的形式

    routes:[     path: 'Blogs',    name: 'ShowBlogs',    component: () => import('./components/ShowBlogs.vue')]

    以函数的形式加载路由,这样就可以把各自的路由文件分别打包,只有在解析给定的路由时,才会加载路由组件

    静态资源本地缓存

    后端返回资源问题:

    • 采用HTTP缓存,设置Cache-ControlLast-ModifiedEtag等响应头

    • 采用Service Worker离线缓存

    前端合理利用localStorage

    UI框架按需加载

    在日常使用UI框架,例如element-UI、如: vue-custom-element

  • 添加全局资源:指令/过滤器/过渡等。插件是什么

    插件通常用来为 Vue 添加全局功能。作用

    v-if 指令用于条件性地渲染一块内容。实例和组件定义data的区别

    vue实例的时候定义data属性既可以是一个对象,也可以是一个函数

    const app = new Vue({    el:"#app",    // 对象格式    data:{        foo:"foo"    },    // 函数格式    data(){        return {             foo:"foo"        }    }})

    组件中定义data属性,只能是一个函数

    如果为组件data直接定义为一个对象

    Vue.component('component1',{    template:`<div>组件</div>`,    data:{        foo:"foo"    }})

    则会得到警告信息

    警告说明:返回的data应该是一个函数在每一个组件实例中

    function Component(){ }Component.prototype.data = { count : 0}

    创建两个组件实例

    const componentA = new Component()const componentB = new Component()

    修改componentA组件data属性的值,componentB中的值也发生了改变

    console.log(componentB.data.count)  // 0componentA.data.count = 1console.log(componentB.data.count)  // 1

    产生这样的原因这是两者共用了同一个内存地址,componentA修改的内容,同样对componentB产生了影响

    如果我们采用函数的形式,则不会出现这种情况(函数返回的对象内存地址并不相同)

    function Component(){	this.data = this.data()}Component.prototype.data = function (){    return {   		count : 0    }}

    修改componentA组件data属性的值,componentB中的值不受影响

    console.log(componentB.data.count)  // 0componentA.data.count = 1console.log(componentB.data.count)  // 0

    vue组件可能会有很多个实例,采用函数返回一个全新data形式,使每个实例对象的数据不会受到其他实例对象数据的污染

    二、

  • 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。原理分析

    为什么产生上面的情况呢?

    下面来分析一下

    vue2是用过Object.defineProperty实现数据响应式

    const obj = {}Object.defineProperty(obj, 'foo', {        get() {            console.log(`get foo:${val}`);            return val        },        set(newVal) {            if (newVal !== val) {                console.log(`set foo:${newVal}`);                val = newVal            }        }    })}

    当我们访问foo属性或者设置foo值的时候都能够触发settergetter

    obj.foo   obj.foo = 'new'

    但是我们为obj添加新属性的时候,却无法触发事件属性的拦截

    obj.bar  = '新属性'

    原因是一开始objfoo属性被设成了响应式数据,而bar是后面新增的属性,并没有通过Object.defineProperty设置成响应式数据

    三、时间、

    开启GZip压缩

    拆完包之后,我们再用gzip做一下压缩 安装compression-webpack-plugin

    cnmp i compression-webpack-plugin -D

    vue.congig.js中引入并修改webpack配置

    const CompressionPlugin = require('compression-webpack-plugin')configureWebpack: (config) => {        if (process.env.NODE_ENV === 'production') {            // 为生产环境修改配置...            config.mode = 'production'            return {                plugins: [new CompressionPlugin({                    test: /\.js$|\.html$|\.css/, //匹配文件名                    threshold: 10240, //对超过10k的数据进行压缩                    deleteOriginalAssets: false //是否删除原文件                })]            }        }

    在服务器我们也要做相应的配置 如果发送请求的浏览器支持gzip,就发送给它gzip格式的文件 我的服务器是用express框架搭建的 只要安装一下compression就能使用

    const compression = require('compression')app.use(compression())  // 在其他中间件使用之前调用

    使用SSR

    SSR(Server side ),也就是服务端渲染,组件或页面通过服务器生成html字符串,再发送到浏览器

    从头搭建一个服务端渲染是很复杂的,vue应用建议使用Nuxt.js实现服务端渲染

    小结:

    减少首屏渲染时间的方法有很多,总的来讲可以分成两大部分 :资源加载优化 和 页面渲染优化

    下图是更为全面的首屏优化的方案

    image.png

    大家可以根据自己项目的情况选择各种方式进行首屏渲染的优化

     

    第三章 为什么data属性是一个函数而不是一个对象?

    MyPlugin.install = function (Vue, options) { // 1. 添加全局方法或 property Vue.myGlobalMethod = function () { // 逻辑... } // 2. 添加全局资源 Vue.directive('my-directive', { bind (el, binding, vnode, oldVnode) { // 逻辑... } ... }) // 3. 注入组件选项 Vue.mixin({ created: function () { // 逻辑... } ... }) // 4. 添加实例方法 Vue.prototype.$myMethod = function (methodOptions) { // 逻辑... }}

    注册形式

    组件注册

    vue组件注册主要分为全局注册与局部注册

    全局注册通过Vue.component方法,第一个参数为组件的名称,第二个参数为传入的配置项

    Vue.component('my-component-name', { /* ... */ })

    局部注册只需在用到的地方通过components属性注册一个组件

    const component1 = {...} // 定义一个组件export default {	components:{		component1   // 局部注册	}}

    插件注册

    插件的注册通过Vue.use()的方式进行注册(安装),第一个参数为插件的名字,第二个参数是可选择的配置项

    Vue.use(插件名字,{ /* ... */} )

    注意的是:

    注册插件的时候,需要在调用 new Vue() 启动应用之前完成

    Vue.use会自动阻止多次注册相同插件,只会注册一次

    使用场景

    具体的其实在插件是什么章节已经表述了,这里在总结一下

    组件 (Component) 是用来构成你的 App 的业务模块,它的目标是 App.vue

    插件 (Plugin) 是用来增强你的技术栈的功能模块,它的目标是 Vue 本身

    简单来说,插件就是指对Vue的功能的增强或补充

  • 优先级

    v-ifv-for都是vue模板系统中的指令

    vue模板编译的时候,会将指令系统转化成可执行的render函数

    示例

    编写一个p标签,同时使用v-if与 v-for

    <div id="app">    <p v-if="isShow" v-for="item in items">        {{ item.title}}    </p></div>

    创建vue实例,存放isShowitems数据

    const app = new Vue({  el: "#app",  data() {    return {      items: [        { title: "foo" },        { title: "baz" }]    }  },  computed: {    isShow() {      return this.items && this.items.length > 0    }  }})

    模板指令的代码都会生成在render函数中,通过app.$options.render就能得到渲染函数

    ƒ anonymous() {  with (this) { return     _c('div', { attrs: { "id": "app" } },     _l((items), function (item)     { return (isShow) ? _c('p', [_v("\n" + _s(item.title) + "\n")]) : _e() }), 0) }}

    _lvue的列表渲染函数,函数内部都会进行一次if判断

    初步得到结论:v-for优先级是比v-if

    再将v-forv-if置于不同标签

    <div id="app">    <template v-if="isShow">        <p v-for="item in items">{{item.title}}</p>    </template></div>

    再输出下render函数

    ƒ anonymous() {  with(this){return     _c('div',{attrs:{"id":"app"}},    [(isShow)?[_v("\n"),    _l((items),function(item){return _c('p',[_v(_s(item.title))])})]:_e()],2)}}

    这时候我们可以看到,v-forv-if作用在不同标签时候,是先进行判断,再进行列表的渲染

    我们再在查看下vue源码

    源码位置:\vue-dev\src\compiler\codegen\index.js

    export function genElement (el: ASTElement, state: CodegenState): string {  if (el.parent) {    el.pre = el.pre || el.parent.pre  }  if (el.staticRoot && !el.staticProcessed) {    return genStatic(el, state)  } else if (el.once && !el.onceProcessed) {    return genOnce(el, state)  } else if (el.for && !el.forProcessed) {    return genFor(el, state)  } else if (el.if && !el.ifProcessed) {    return genIf(el, state)  } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {    return genChildren(el, state) || 'void 0'  } else if (el.tag === 'slot') {    return genSlot(el, state)  } else {    // component or element    ...}

    在进行if判断的时候,v-for是比v-if先进行判断

    最终结论:v-for优先级比v-if

    三、

    小结

    • 如果为对象添加少量的新属性,可以直接采用Vue.set()

    • 如果需要为新对象添加大量的新属性,则通过Object.assign()创建新对象

    • 如果你实在不知道怎么操作时,可采取$forceUpdate()进行强制刷新 (不建议)

    PS:vue3是用过proxy实现数据响应式的,直接动态添加新属性仍可以实现数据响应式

    第五章 Vue中组件和插件有什么区别?

    image.png

    一、原理分析" style="background-color:transparent;">三、注意事项">三、