发布时间:2025-06-24 20:15:52 作者:北方职教升学中心 阅读量:727
作用" style="background-color:transparent;">一、如vue-router
Vue
实例方法,通过把它们添加到 Vue.prototype
上实现。如vue-router
二、采用函数的形式,initData
时会将其作为工厂函数都会返回全新data
对象第四章 动态给vue的data添加一个新的属性时会发生什么?怎样解决?
一、结论- 根实例对象
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
提供的数据:

通过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
指令基于一个数组来渲染一个列表。注意事项
- 永远不要把
v-if
和 v-for
同时用在同一个元素上,带来性能方面的浪费(每次渲染都会先循环再进行条件判断) - 如果避免出现这种情况,则在外层嵌套
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首屏加载速度慢的怎么解决?

一、插件是什么">二、范围等组件作具体的实现
调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单
提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级
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
文件,这就造成了重复下载
解决方案:在webpack
的config
文件中,修改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
减小入口文件体积
常用的手段是路由懒加载,把不同路由对应的组件分割成不同的代码块,待路由被请求的时候会单独打包路由,使得入口文件变小,加载速度大大增加

在vue-router
配置路由的时候,采用动态加载路由的形式
routes:[ path: 'Blogs', name: 'ShowBlogs', component: () => import('./components/ShowBlogs.vue')]
以函数的形式加载路由,这样就可以把各自的路由文件分别打包,只有在解析给定的路由时,才会加载路由组件
静态资源本地缓存
后端返回资源问题:
采用HTTP
缓存,设置Cache-Control
,Last-Modified
,Etag
等响应头
采用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
值的时候都能够触发setter
与getter
obj.foo obj.foo = 'new'
但是我们为obj
添加新属性的时候,却无法触发事件属性的拦截
obj.bar = '新属性'
原因是一开始obj
的foo
属性被设成了响应式数据,而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
实现服务端渲染
小结:
减少首屏渲染时间的方法有很多,总的来讲可以分成两大部分 :资源加载优化 和 页面渲染优化
下图是更为全面的首屏优化的方案

大家可以根据自己项目的情况选择各种方式进行首屏渲染的优化
第三章 为什么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
的功能的增强或补充
优先级
- 根实例对象
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
提供的数据:通过
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
指令基于一个数组来渲染一个列表。注意事项- 永远不要把
v-if
和v-for
同时用在同一个元素上,带来性能方面的浪费(每次渲染都会先循环再进行条件判断) - 如果避免出现这种情况,则在外层嵌套
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首屏加载速度慢的怎么解决?
一、插件是什么">二、范围等组件作具体的实现
调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单
提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级
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
文件,这就造成了重复下载解决方案:在
webpack
的config
文件中,修改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
减小入口文件体积
常用的手段是路由懒加载,把不同路由对应的组件分割成不同的代码块,待路由被请求的时候会单独打包路由,使得入口文件变小,加载速度大大增加
在
vue-router
配置路由的时候,采用动态加载路由的形式routes:[ path: 'Blogs', name: 'ShowBlogs', component: () => import('./components/ShowBlogs.vue')]
以函数的形式加载路由,这样就可以把各自的路由文件分别打包,只有在解析给定的路由时,才会加载路由组件
静态资源本地缓存
后端返回资源问题:
采用
HTTP
缓存,设置Cache-Control
,Last-Modified
,Etag
等响应头采用
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
值的时候都能够触发setter
与getter
obj.foo obj.foo = 'new'
但是我们为
obj
添加新属性的时候,却无法触发事件属性的拦截obj.bar = '新属性'
原因是一开始
obj
的foo
属性被设成了响应式数据,而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
实现服务端渲染小结:
减少首屏渲染时间的方法有很多,总的来讲可以分成两大部分 :资源加载优化 和 页面渲染优化
下图是更为全面的首屏优化的方案
大家可以根据自己项目的情况选择各种方式进行首屏渲染的优化
第三章 为什么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-if
与v-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
实例,存放isShow
与items
数据
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) }}
_l
是vue
的列表渲染函数,函数内部都会进行一次if
判断
初步得到结论:v-for
优先级是比v-if
高
再将v-for
与v-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-for
与v-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
实现数据响应式的,直接动态添加新属性仍可以实现数据响应式