Vue2 数据双向绑定原理是通过数据劫持 + 发布者-订阅者模式 的方式来实现,首先是通过 ES5 提供的 Object.defineProperty() 方法来劫持(监听)各属性的 getter、setter,并在当监听的属性发生变动时通知订阅者,是否需要更新,若更新就会执行对应的更新函数。
title 我是订阅者
对象原型的toString方法,以及数组的length属性都是不可枚举的。
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。
let obj = { foo: 123 }; //打印obj对象的foo属性的描述对象 Object.getOwnPropertyDescriptor(obj, 'foo') //输出的对象: // { // value: 123, // writable: true, // enumerable: true, 是否可以枚举 // configurable: true // } //Object的原型toString属性是不可枚举 Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable // false //数组的长度length属性是不可枚举 Object.getOwnPropertyDescriptor([], 'length').enumerable // false
$router 是VueRouter的实例,在script标签中想要导航到不同的URL,使用 $router.push方法
$route为router跳转对象,里面可以获取当前路由的name,path,query,parmas等。
方法一 resolve
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'HelloWorld', component: resolve => require(['@/components/HelloWorld'], resolve) } ] })
方法二.import按需加载(官方写法)
const comA = () => import('url') const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
方法三 webpack提供的require.ensure()
{ path: '/home', name: 'home', component: r =>require.ensure([], () => r(require('@/components/home')), 'demo') }
1.所需要渲染的区域
2.
3.
组件css作用域,避免子组件内部的css样式被父组件覆盖,起到隔离的作用。
- 通过 props 传递
- 通过 $emit 触发自定义事件
- 使用 ref
- EventBus
- $parent 或$root
- attrs 与 listeners
- Provide 与 Inject
- Vuex
每次调用产生一个新的地址空间防止数据被污染。
- 创建vue实例对象: beforeCreate、 created
- 挂载dom节点 : beforeMount、mounted
- 数据更新 : beforeUpdate、updated
activated: 被 keep-alive 缓存的组件激活时调用
deactivated: 被 keep-alive 缓存的组件失活时调用
- 组件销毁: beforeDestroy、destroyed
父子组件生命周期构造函数执行顺序
加载渲染过程: 父 beforecreate,created,beforeMount 子 beforecreate,created,beforeMount 子 mounted 父 mounted 子组件更新过程: 父 beforeUpdate 子 beforeUpdate 子 Updated 父 Updated 销毁过程: 父 beforeDestroy 子 beforeDestroy 子 destroyed 父 destroyed 如果常用的数据如果需要传递到子组件的话,最好在created 或者 beforemount,把数据请求回来,然后传递给子组件。
axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,简单的理解就是ajax的封装。
post请求的时候参数通过data进行传递
其他请求的时候参数通过params进行传递//get请求方式: axios.get("api", { // 传递参数 params: { key: value }, // 设置请求头信息,可以传递空值 headers: { key: value } }).then((response) => { // 请求成功 let res = response.data; console.log(res); }).catch(error => { // 请求失败, console.log(error); }); //post请求方式: let data = { key: value }, headers = { USERID: "", TOKEN: "" }; // 若无headers信息时,可传空对象占用参数位置 axios.post("api", qs.stringify(data), { headers } }).then((response) => { // 请求成功 let res = response.data; console.log(res); }).catch((error) => { // 请求失败, console.log(error); });
/**** 以下简单封装axios request.js ****/ // 导入axios import axios from 'axios' import { Message} from 'element-ui' //1. 创建新的axios实例, const service = axios.create({ // 公共接口 baseURL: process.env.BASE_API, // 超时时间 单位是ms timeout: 3 * 1000 }) // 2.请求拦截器 service.interceptors.request.use(config => { config.data = JSON.stringify(config.data); //数据转化,也可以使用qs转换 config.headers = { 'Content-Type':'application/json' //配置请求头 } //如有需要:注意使用token的时候需要引入cookie方法或者用本地localStorage等方法,推荐js-cookie //const token = getCookie('名称');//这里取token之前,你肯定需要先拿到token,存一下 //if(token){ //config.params = {'token':token} //如果要求携带在参数中 //config.headers.token= token; //如果要求携带在请求头中 //} return config }, error => { Promise.reject(error) }) // 3.响应拦截器 service.interceptors.response.use(response => { //接收到响应数据并成功后的一些共有的处理,关闭loading等 return response }, error => { /***** 接收到异常响应的处理开始 *****/ if (error && error.response) { // 1.公共错误处理 // 2.根据响应码具体处理 switch (error.response.status) { case 400: error.message = '错误请求' break; case 401: error.message = '未授权,请重新登录' break; case 403: error.message = '拒绝访问' break; case 404: error.message = '请求错误,未找到该资源' window.location.href = "/NotFound" break; case 405: error.message = '请求方法未允许' break; case 408: error.message = '请求超时' break; case 500: error.message = '服务器端出错' break; case 501: error.message = '网络未实现' break; case 502: error.message = '网络错误' break; case 503: error.message = '服务不可用' break; case 504: error.message = '网络超时' break; case 505: error.message = 'http版本不支持该请求' break; default: error.message = `连接错误${error.response.status}` } } else { // 超时处理 if (JSON.stringify(error).includes('timeout')) { Message.error('服务器响应超时,请刷新当前页') } error.message = '连接服务器失败' } Message.error(error.message) return Promise.resolve(error.response) }) var config = { method: type, url } if(type==='post'){ config.data = params; }else{ config.params = params; } const http = function(type,url,params){ return service(config) } export default http; //api.js import http from './http' /** * @parms resquest 请求地址 例如:http://197.82.15.15:8088/request/... * @param '/testIp'代表vue-cil中config,index.js中配置的代理 */ let resquest = "/testIp/request/" // get请求 export function getListAPI(params){ return http('get',`${resquest}/getList.json`,params) } // post请求 export function postFormAPI(params){ return http('post',`${resquest}/postForm.json`,params) } // put 请求 export function putSomeAPI(params){ return http('put',`${resquest}/putSome.json`,params) } // delete 请求 export function deleteListAPI(params){ return http('delete',`${resquest}/deleteList.json`,params) }
Vue 是异步修改 DOM 的并且不鼓励开发者直接接触 DOM,但有时候业务需要必须对数据更改,刷新后的 DOM 做相应的处理,这时候就可以使用 Vue.nextTick(callback)来帮助我们处理更新后的dom数据。
通俗讲:是将回调函数延迟在下一次dom更新数据后调用,就是当数据更新了,在dom中更新渲染后,立即自动执行该函数。
tips:因为vue中更新dom是异步的,vue在监测到数据改变时并不会立即更新视图,而是开启一个队列,把同一个事件循环中观察到数据变化的watcher推送进这个对列,同时缓冲所有数据变化并去重,在下一个事件循环中,才执行去重后的任务; 如果同一个watcher 被多次触发,只会被推送到队列中一次(去重)。
使用this.$nextTick()之所以能获取到更新后的值,并不是改变了vue的渲染流程,而是改变了获取最新值的时间,并不是立即获取,而是等vue渲染完后再获取,即
异步获取。
在vue中修改数据后,页面不会立刻更新,而是开启一个队列,并且缓存同一轮事件循环中的所有数据改变
。在缓冲时会除去重复的操作
,等到下一轮事件循环时,才开始更新。
Vue会将用户同步修改的多次数据缓存起来,等同步代码执行完,说明这一次的数据修改就结束了,然后才会去更新对应DOM。
使用内置组件
标签,作用是创造一个缓存的空间,用于保存组件状态或者避免重新全部渲染。 提供 include 和 exclude 属性,两者都支持字符串或正则表达式
1、
2、路由页面给需要的添加meta APP.js页面
{ path: '/', name: 'home', alias: '/home', components:{ home }, meta:{ keep:true //需要缓存 } },
- M:model模型---负责处理应用程序的数据和业务逻辑。
- V:View视图---负责数据的展示,通常是用户界面(UI)。
- C:controller控制器---接受用户的输入,处理请求,并更新模型和视图。它起到了一个中间人的作用,连接模型和视图。
- M: model数据模型 (data里定义)
- V: view视图 (页面标签)
- VM: ViewModel视图模型 (vue.js源码)----它负责处理视图的逻辑和状态,并通过数据绑定与视图进行交互。
.prevent: 提交事件不再重载页面;
.stop: 阻止单击事件冒泡;
.once: 只执行一次这个事件
.enter:监听键盘enter键
key值的作用是给元素添加一个唯一的标识符,提高vue渲染性能。当数据变化的时候,vue就会使用diff算法对比新旧虚拟Dom。 如果遇到相同的key值,则复用元素。如果遇到不同的key值则强制更新。
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
slice()
filter()
concat()
(1)计算属性有缓存机制,侦听器没有
(2)计算属性不支持异步操作, 侦听器支持异步操作
(3)计算属性是一个额外新增的属性, 侦听器只能侦听data中的属性
(4)计算属性有返回值return,侦听器不需要return
(5)计算属性可以监听多个数据变化(计算属性内部用到的数据变化了,就会执行计算属性方法), 侦听器只能侦听一个数据的变化
//computed: 1. 支持缓存,只在依赖的数据发生变化时,才会重新计算, 否则当多次调用computed属性时,调用的其实是缓存; 2. 不支持异步,当computed内有异步操作时无效,无法监听数据的变化 3. 如果一个数据需要经过复杂计算就用 computed 4. 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性, 是一个多对一或者一对一,(某个属性受多个属性影响) //watch: 1. 不支持缓存,每调用一次就计算一次; 2. watch支持异步; 3. 如果一个数据需要被监听并且对数据做一些操作就用 watch 4. 当一个属性发生变化时,需要执行对应的操作;一对多(监听多个数据)
基于浏览器都遵循同源策略的原因,客户端向非同源的服务器发起网络请求的时候就是跨域请求。
同源:协议,域名,端口都相同,反之非同源。
同源策略是一种安全策略,浏览器只允许本域名下的接口交互,非同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。
解决方法一:跨域资源共享(CORS)
CORS是W3C规范,是一个让Web服务器支持跨域访问的标准。
CORS是一种安全机制,CORS技术通过HTTP头部来告知浏览器,可以让应用程序绕过同源策略。
只要在服务端设置Access-Control-Allow-Origin就可以实现跨域请求,若是cookie请求,前后端都需要设置。
由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,并非当前页的
cookie。
//node.js实现服务端示例 var http = require('http'); var server = http.createServer(); server.on('request', function(req, res) { res.writeHead(200, { // 后端允许发送Cookie 'Access-Control-Allow-Credentials': 'true', // 重点字段: 允许访问的域(协议+域名+端口),允许所有域用*通配符 'Access-Control-Allow-Origin': 'http://localhost:8080', // HttpOnly:脚本无法读取cookie 'Set-Cookie': 'key=1;Path=/;Domain=mfaying.github.io;HttpOnly' }); res.write(JSON.stringify(req.method)); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...');
koa结合koa2-cors中间件实现
const Koa = require("koa"); const cors = require("koa2-cors"); const Router = require("koa-router"); const router = new Router({ prefix: "/api" }); const app = new Koa(); //koa请求跨域问题 app.use( cors({ origin: function(ctx) { return ctx.request.headers.origin || ""; //这里是重点,动态获取地址 //* }, credentials: true, exposeHeaders: ["WWW-Authenticate", "Server-Authorization"], allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], //设置所允许的HTTP请求方法 allowHeaders: ["Content-Type", "Authorization", "Accept"], //设置服务器支持的所有头信息字段 }) ); // 引入路由中间件 app.use(router.routes()).use(router.allowedMethods()); app.listen(3000, () => { console.log("正在监听3000端口号!"); });
后端SpringBoot项目中的跨域配置
/** * 跨域配置 */ @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 设置访问源地址 config.addAllowedOriginPattern("*"); // 设置访问源请求头 config.addAllowedHeader("*"); // 设置访问源请求方法 config.addAllowedMethod("*"); // 有效期 1800秒 config.setMaxAge(1800L); // 添加映射路径,拦截一切请求 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); // 返回新的CorsFilter return new CorsFilter(source); }
CORS攻击原理:
通常情况下,浏览器限制从其他站点加载资源,同源策略将在控制网页上的用户密钥声明的范围内工作。但是,存在一种情况,是用于验证许可证的站点需要向其他站点发送请求。
这时,如果服务器接受CORS请求,将响应访问控制允许的请求,那么攻击者就可以利用这种情况从一个新的站点调用API来攻击目标站点。攻击者可以通过设置自己的Origin头绕过浏览器限制,并向目标站点发送请求。在目标站点上,访问控制允许攻击者站点的请求,从而允许他们获取敏感数据和执行操作。
防范CORS攻击:
1. 使用最小特权原则
-- 最小特权原则是指,将最小权限授予最小模块,只授予执行任务所需的最小权限。
2. 使用预请求
-- 当浏览器准备发送不同于常规GET,POST和HEAD请求的请求类型时,它会发送预请求(Preflight Requests),即向目标站点发送OPTIONS请求,以获取服务器是否同意该请求的策略和方法。通过在浏览器正确配置OPTIONS请求,可以有效防止CORS攻击。在使用预请求时,应正确设置Access-Control-Request-Method、Access-Control-Request-Headers和Access-Control-Max-Age头部,这样可以确保请求的正常工作。
3. 保持资源安全
-- 如果您不想允许任何来源访问您的API或资源,则要向API添加接口,以确保所有请求都是从预定义的来源发起的。
还可以通过以下几种方法来保护资源:
// 设置访问控制允许的来源 res.setHeader('Access-Control-Allow-Origin', 'http://www.example.com')// 设置只能使用POST和GET方法 res.setHeader('Access-Control-Allow-Methods', 'GET, POST')
// 设置其他允许的HTTP头信息 res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, content-type')
- 对真实DOM的抽象描述,以js对象(VNode节点)作为基础的树,用对象的属性来描述节点,就是一个js对象。
- 虚拟DOM提高性能,不是说不操作DOM,而是减少操作DOM的次数,减少回流和重绘。虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。
- 虚拟Dom不比原生DOM快,没有任何框架可以比手动优化DOM更快,因为框架的DOM操作层需要应对任何上层可能产生的操作,所以他的实现需具有普适性。并且在内部,虚拟DOM还使用真实DOM来呈现页面或内容。
在进行新旧虚拟dom树的比较时,用到了diff算法。
1、diff 比较两个虚拟dom只会在同层级(①是否在同一层②是否有相同的父级)之间进行比较,不会跨层级进行比较。
2、diff是采用先序深度优先遍历得方式进行节点比较,即:该节点存在子节点,先比较子节点,然后再比较同级几点
// 在main.js中安装全局事件总线 //引入Vue import Vue from 'vue' //引入App import App from './App.vue' //关闭Vue的生产提示 Vue.config.productionTip = false //创建事件总线,就相当于创建了一个新的vue实例 Vue.prototype.$EventBus = new Vue() //创建vm new Vue({ el:'#app', render: h => h(App), beforeCreate() { //安装全局事件总线, //或 Vue.prototype.$bus = this }, }) //接收数据的组件: methods:{ demo(data) {.....} }, mounted() { this.$EventBus.$on('xxx', this.demo)// 或者是写箭头函数的回调 } //提供数据的组件使用 :this.$bus.$emit('xxx', 数据)
1、解绑自定义事件
2、清零定时器
3、解绑自定义dom事件,如window.scroll等
1、合理使用v-if和v-show
2、合理使用computed
3、使用v-for的时候动态添加key
4、自定义事件,dom事件在beforeDestory中及时销毁
5、合理使用异步组件
6、合理使用keep-alive
7、data层级不要太深
8、使用vue-loader在开发环境做编译模板
9、前端通用性能优化(图片懒加载/减少HTTP请求/合理设置HTTP缓存/资源合并与压缩/合并CSS
图片/将CSS放在header中/避免重复的资源请求/切分到多个域名)
10、使用ssr
代码层面:
1、v-if 和 v-show 区分使用场景
2、computed 和 watch 区分使用场景
3、v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
4、长列表性能优化
5、事件的销毁 addEventlisenter 事件监听
6、图片资源懒加载
8、路由懒加载
9、第三方插件的按需引入
10、优化无限列表性能
11、服务端渲染 SSR or 预渲染
12、组件模块尽量细化,一个组件一个功能,减少耦合
13、子组件样式尽量用scoped,防止污染父组件
14、(!important )不使用或减少用
Webpack 层面的优化
1、Webpack 对图片进行压缩
2、减少 ES6 转为 ES5 的冗余代码
3、提取公共代码
4、模板预编译
5、提取组件的 CSS
6、优化 SourceMap
7、构建结果输出分析
8、Vue 项目的编译优化
基础的 Web 技术的优化
1、开启 gzip 压缩
2、浏览器缓存
3、CDN 的使用
4、使用 Chrome Performance 查找性能瓶颈
vuex 是一个专门为 vue 构建的状态管理工具,主要是为了解决 多组件之间状态共享问题。强调的是集中式管理,(组件与组件之间的关系变成了组件与仓库之间的关系)。
state 是状态管理器的状态中心,里面是初始化的数据,不可以直接操作Store 中的数据。 mutation 同步操作,改变store的数据,变更Store 数据, 通过commit提交方法,this.$store.commit('xxx')。 action 异步操作,改变store的数据, 让motation中的方法能在异步操作中起作用 通过store.dispatch 来分发actions中通过commit提交mutations, 进行修改数据。 getter 是状态管理器中的计算属性,主要用来过滤一些数据,可以在多组件之间复用。 module 将 store 分割成模块,每个模块都具有state,mutation、action、getter、 甚至是嵌套子模块。
提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存,相似与data
//在标签中直接使用
{{$store.state.name}}
//定义后直接使用
this.$store.state.全局数据名称
//在组件中导入使用
import { mapState } from "vuex";
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
//使用commit触发Mutation操作
methods:{
//加法
btn(){
this.$store.commit("addcount",10) //每次加十
}
}
//使用辅助函数进行操作
Action和Mutation相似,一般不用Mutation 异步操作,若要进行异步操作,使用Action。
使用:一、this.$store.dispatch("asynAdd")
类似于vue中的computed,进行缓存,对于Store中的数据进行加工处理形成新的数据
Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块
含以下三个修饰符:
.lazy 默认情况下,v-model同步输入框的值和数据。可以通过这个修饰符,转变为在change
事件再同步。
.number 自动将用户的输入值转化为数值类型
.trim 自动过滤用户输入的首尾空格
数据驱动和组件化
路由钩子(路由守卫)
全局路由守卫
router.beforeEach(to, from, next),全局前置守卫
router.beforeResolve(to, from, next),全局的解析守卫
router.afterEach(to, from ,next) 全局的后置守卫
组件内守卫 路由独享的守卫
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
路由独享守卫
beforeEnter 组件内的守卫
从a页面进入b页面时触发的生命周期
1)beforeRouteLeave:路由组件的组件离开路由钩子,可取消路由离开;
2)beforeEach:路由全局前置守卫,可用于登录验证,全局路由loading等;
3)beforeRouteEnter:路由的组件进入路由前钩子;
4)beforeResolve:路由全局解析守卫;
5)afterEach:路由全局后置钩子;
6)beforeCreate:组件生命周期,不能访问this;
7)created:组件生命周期,可以访问this,不能访问dom;
8)beforeMount:组件生命周期;
9)deactivated:离开缓存组件a,或者触发a的beforeDestory和destoryed组件销毁钩子
10)mounted:访问/操作dom;
11)activated:进入缓存组件,进入a的嵌套子组件(如果有的话);
12)执行beforeRouteEnter回调函数next
1.hash路由 – 默认模式
工作原理:监听网页的hash值得变化===》onhashchange事件,获取location.hash
使用URL的hash来模拟一个完整的URL,于是URL改变时,页面不会重新加载,会给用户好像跳转了网页一样的感觉,但是实际上没有跳转,主要用在单页面应用(SPA)。
在地址栏会有一个#号,因为hash发生变化的url都会被浏览器记录下来,浏览器的前进后退都可以用了,同时点击后退时,页面字体颜色也会发生变化。
这样一来,尽管浏览器没有请求服务器,但是页面状态和url一一关联起来,
后来人们给它起了一个霸气的名字叫前端路由,成为了单页应用标配。
2.history路由-------有的back、forward、go 方法history ——利用了HTML5 History Interface 中新增的pushState() 和replaceState() 方法。
提供了对历史记录进行修改的功能。它能让开发人员在不刷新网页的情况下改变站点的 URLHistory 有 URL 重定向问题,需要在服务端去配置 url 重定向,否则会报 404 错误。
history模式下,**前端的url必须和实际向后端发起请求的url 一致**,
如 http://www.abc.com/book/id 。如果后端缺少对/book/id 的路由处理,将返回404错误。
3.Abstractabstract 模式针对的是没有浏览器环境的情况,比如 Weex 客户端开发,内部是没有浏览器 API 的,那么 Vue-Router 自身会对环境做校验,强制切换到 abstract 模式,
如果默认在 Vue-Router 的配置项中不写 mode 的值,
在浏览器环境下会默认启用 Hash 模式,在移动客户端下使用 abstract 模式。
对于已经创建的实例,Vue 不能动态添加根级别的响应式属性。但是,可以使用 `Vue.set(object,key, value)`方法向嵌套对象`添加响应式属性`。
获取dom元素 this.$refs.box
获取子组件中的data this.$refs.box.msg
调用子组件中的方法 this.$refs.box.open()
query传参和params传参
区别:用params传参,浏览器地址栏不显示参数,用query传参,浏览器地址栏显示参数,所以用params传值相对安全。
1)声明式导航
不带参跳转 对应的地址为/foo
url字符串拼接传参 对应的地址为/foo?id=123
query方式对象形式传参 对应的地址为/foo?id=123
params方式对象形式传参 对应地址为 /path/123 , 注意params和query一起使用params会失效,
params与name一起使用
2)编程式导航(路由实例对象router=new VueRouter())
字符串router.push('home')
对象router.push({ path: 'home' })
命名的路由 对应路径为/path/123
router.push({ name: 'user', params: { userId: '123' }})
带查询参数,变成 /register?plan=123
router.push({ path: 'register', query: { plan: '123' }})
接收参数
this.$route.params.id
this.$route.query.xxx
在路由对象内,通过props属性 开启传参功能
在组件对象内 通过props接收
原因: 为了降低路由和组件的耦合度。在子路由对象中设置props { path:'/detial:id', name:'detail', components:{ default:()=> import(/* webpackChunkName: "pro" */'@/views/detail/detail.vue') }, props:{ default:true } }
mixin提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。
当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。选项属性相同情况下,被混入对象的选项优先级最高。
Vue Loader 是一个 webpack 的 loader,它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件。
{{ msg }}Vue Loader 还提供了很多酷炫的特性:
- 允许为 Vue 组件的每个部分使用其它的 webpack loader,例如在
的部分使用 Sass 和在
的部分使用 Pug;
- 允许在一个
.vue
文件中使用自定义块,并对其运用自定义的 loader 链;- 使用 webpack loader 将
和
中引用的资源当作模块依赖来处理;
- 为每个组件模拟出 scoped CSS;
- 在开发过程中使用热重载来保持状态。
全局定义指令:在vue对象的directive方法里面有两个参数,一个是指令名称,另外一个是函数。组件内定义指令:directives
钩子函数:bind(绑定事件触发)、inserted(节点插入的时候触发)、update(组件内相关更新)钩子函数参数:el、binding
bind: 只调用一次,指令第一次绑定到元素时候调用,用这个钩子可以定义一个绑定时执行一次的初始化动作。
inserted:被绑定的元素插入父节点的时候调用(父节点存在即可调用,不必存在document中)
update:被绑定于元素所在模板更新时调用,而且无论绑定值是否有变化,通过比较更新前后的绑定值,忽略不必要的模板更新
componentUpdate :被绑定的元素所在模板完成一次更新更新周期的时候调用
unbind:只调用一次,指令与元素解绑的时候调用
Vue.directive("hello",{
bind:function(el,bingind,vnode){
//只调用一次,指令第一次绑定到元素时候调用,
//用这个钩子可以定义一个绑定时执行一次的初始化动作。
el.style["color"] = bingind.value;
console.log("1-bind");
},
inserted:function(){
//被绑定的元素插入父节点的时候调用(父节点存在即可调用,不必存在document中)
console.log("2-inserted");
},
update:function(){
//被绑定于元素所在模板更新时调用,而且无论绑定值是否有变化
//通过比较更新前后的绑定值,忽略不必要的模板更新
console.log("3-update");
},
componentUpdated:function(){
//被绑定元素所在模板完成一次更新周期时调用
console.log('4 - componentUpdated');
},
unbind:function(){
//只调用一次,指令与元素解绑时调用。
console.log('5 - unbind');
}
})
指令钩子函数会被传入以下参数:
1、el:指令所绑定的元素,可以用来直接操作 DOM。
2、binding:一个对象,包含以下 property:
name:指令名,不包括 v- 前缀。
value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可 用。
expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
3、vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
4、oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
使用Vue.extend方法创建一个组件,然后使用Vue.component方法注册组件。子组件需要数据,父组件传递数据给子组件使用props属性,即在子组件中定义props。子组件传递数据给父组件采用emit方法。
// Parent.vue
doSomething() {
console.log('父组件监听到 mounted 钩子函数 ...');
},
// Child.vue
mounted(){
console.log('子组件触发 mounted 钩子函数 ...');
},
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...
Flex布局是在CSS3中引入的,又称为弹性盒模型。该模型决定一个盒子在其他盒子中的分布方式以及如何处理可用的空间。
Flex布局对于设计比较复杂的页面非常有用,可以轻松的实现屏幕和浏览器窗口大小发生变化时保持元素的相对位置和大小不变,同时减少了依赖浮动布局实现元素位置的定义以及重置元素的大小。综合而言,Flex布局主要具有如下几点功能:
(1)在屏幕和浏览器窗口大小发生改变时也可以灵活地调整布局。
(2)控制元素在页面的布局方向
(3)按照不同于文档对象模型(DOM)所指定的排序方式对屏幕上的元素重新排序。(4)设为 Flex 布局以后,子元素的
float
、clear
和vertical-align
属性将失效。
//要使用Flex布局,需要先将容器的display属性设置为flex或inline-flex。
.container { display: flex | inline-flex; // display: -webkit-flex; /* Safari */ }
外面的大容器的属性的设置:
1. flex-direction 主轴方向
2. flex-wrap 主轴一行满了换行
3. flex-flow 1和2的组合
4. justify-content 主轴元素对齐方式
5. align-items 交叉轴元素对齐方式//单行
6. align-content 交叉轴行对齐方式//多行容器里面的子元素item「项目」的属性:
1.flex-grow:长大
2.flex-shrinik: 缩小
3.align-self: 覆盖container align-items 属性
4.order 排序
5.flex-basis: 有效宽度
6.flex:
是flex-grow
,flex-shrink
和flex-basis
的简写,默认值为0 1 auto
容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做
main start
,结束位置叫做main end
;交叉轴的开始位置叫做cross start
,结束位置叫做cross end
。项目默认沿主轴排列。单个项目占据的主轴空间叫做
main size
,占据的交叉轴空间叫做cross size
。/***6个容器的属性**/ .container { //主轴方向:子项目元素排列的方向 //水平方向:从左到右、从右到左、垂直方向: 从上到下、从下到上 flex-direction: row | row-reverse | column | column-reverse; //换行 flex-wrap //不换行、一行放不下时换行、弹性项目将从下到上换行成多行 flex-wrap: nowrap | wrap | wrap-reverse; //flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap flex-flow: row nowrap //justify-content属性定义了项目在主轴上的对齐方式。 //左对齐、右对齐、居中、两端对齐,项目之间的间隔都相等、每个项目两侧的间隔相等 justify-content: flex-start | flex-end | center | space-between | space-around; //属性定义项目在交叉轴上如何对齐。 //交叉轴的起点对齐、交叉轴的终点对齐、交叉轴的中点对齐、项目的第一行文字的基线对齐、 默认值(占满整个容器的高度) align-items: flex-start | flex-end | center | baseline | stretch; //与交叉轴的起点对齐、与交叉轴的终点对齐、与交叉轴的中点对齐、与交叉轴两端对齐, 轴线之间的间隔平均分布、每根轴线两侧的间隔都相等。 所以,轴线之间的间隔比轴线与边框的间隔大一倍、(默认值):轴线占满整个交叉轴 align-content:flex-start | flex-end | center | space-between | space-around | stretch; } } /**6个项目的属性**/ .container { //属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。 order:0; //flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。 flex-grow: 0; //flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。 flex-shrink: 1; //flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性, 计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。 flex-basis: auto; //flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。 flex: 0 1 auto; //align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。 默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。 align-self: auto | flex-start | flex-end | center | baseline | stretch;
1. v-for遍历必须为item添加key,且避免同时使用v-if
2. 长列表性能优化(针对只展示没有响应性的数据)--避免数据劫持-Object.freeze方法来冻结这个数据对象
3. 组件销毁的时候记得解绑事件监听
4.Vue.js 2.0组件级懒加载方案:
- 支持组件可见或即将可见时懒加载
- 支持组件延时加载
- 支持加载真实组件前展示骨架组件,提高用户体验
- 支持真实组件代码分包异步加载
希望组件通过异步的方式加载(目的是可以对其进行分包处理),那么Vue中给我们提供了一个函数:defineAsyncComponent。
5.不要将所有的数据都放到data中,不需要响应式的数据我们可以定义在实例(created钩子)上。
6.v-for元素绑定事件代理:将事件处理程序代理到父节点,减少内存占用率。
//使用事件代理
{{item}}7.函数式组件---函数式组件是无状态,它无法实例化,没有任何的生命周期和方法。
对于一些纯展示,没有响应式数据,没有状态管理,也不用生命周期钩子函数的组件,我们就可以设置成函数式组件,提高渲染性能,因为会把它当成一个函数来处理,所以开销很低。
8.函数式组件provide和inject组件通信(provide和inject帮助我们解决多层次嵌套嵌套通信问题)
9. 虚拟滚动---
如果是大数据很长的列表,全部渲染的话一次性创建太多 DOM 就会非常卡,这时就可以用虚拟滚动,只渲染少部分(含可视区域)区域的内容,然后滚动的时候,不断替换可视区域的内容,模拟出滚动的效果。原理是监听滚动事件,动态更新需要显示的 DOM,并计算出在视图中的位移。
10.使用 v-show 复用 DOM
v-show
:是渲染组件,然后改变组件的 display 为 block 或 nonev-if
:是渲染或不渲染组件。11.使用骨架屏
vue-skeleton-webpack-plugin插件
12.子组件分割
因为 Vue 的更新是组件粒度的,虽然第N次数据变化都会导致父组件的重新渲染,但是子组件却不会重新渲染,因为它的内部没有任何变化,耗时任务自然也就不会重新执行,因此性能更好
13.变量本地化
少用this.xx,多用本地变量缓存,或用解构方式。
14.不是大量的组件或插件使用就按需引入
15、路由懒加载 -----
{ path: '/home', component: () => import('@/components/Home') }
16.keep-alive缓存页面
17、事件的销毁
而对于
定时器
、addEventListener
注册的监听器等,就需要在组件销毁的生命周期钩子中手动销毁或解绑,以避免内存泄露。18、图片懒加载
第三方插件
vue-lazyload
npm i vue-lazyload -S // main.js import VueLazyload from 'vue-lazyload' Vue.use(VueLazyload) // 接着就可以在页面中使用 v-lazy 懒加载图片了
打包优化
- 压缩代码
- Tree Shaking/Scope Hoisting
- 使用 cdn 加载第三方模块
- 多线程打包 happypack
- splitChunks 抽离公共文件
- sourceMap 优化
优化首屏加载
1、请求优化:第三方的类库放到 CDN 上。
2、好的缓存策略:第三方类库或者静态资源设置为强缓存,max-age 设置为一个非常长的时间,有助于减轻服务器的压力。
3、gzip:开启 gzip 压缩能够有效的缩小传输资源大小。
4、http2:如果加载的静态资源很多,在一些网络较差的环境开启 http2 性能提升尤为明显。
5、懒加载:通过 import 动态加载页面组件,webpack 会把动态加载的页面组件分离成单独的一个 chunk.js 文件。
6、预渲染:添加 loading,或骨架屏幕尽可能的减少白屏时间。
7、合理使用第三方库: 按需加载第三方 ui 框架、类库,减少打包体积
8、使用可视化工具分析打包后的模块体积:webpack-bundle- analyzer 这个插件
9、封装:对全局组件,插件,过滤器,指令,utils 等做一些公共封装,有效减少代码量,更易维护。
10、图片懒加载: 使用图片懒加载可以优化同一时间减少 http 请求开销,避免显示图片导致的画面抖动,提高用户体验。
11、使用 svg 图标: 更好的图片质量,体积更小,并且不需要开启额外的http请求。
12、压缩图片: image-webpack-loader预处理器可有效进行图片压缩。
1、首先对原来项目做一个大概的梳理,重构,当然很多东西是可以继续拿来使用。
页面结构:页面结构及之间的关系梳理了一遍;
项目结构:梳理项目各重要的文件夹及文件并注明对应的内容或者作用;
第三方库、组件、插件梳理:第三方库或插件使用按需引入,提取公共函数、模块、组件;
重构方案:1、开发规范--代码检查工具 eslint 2、技术选型:开发模式,前后端分离;3、减少重复代码、抽取出最小的组件、封装公共可复用方法及组件;
2、代码规范要领:
1.函数要短小,一个函数只做一件事;2.每个函数一个抽象层级;命名使用描述性的名称;3.函数参数不能超过3个,超过要用对象;4.避免副作用;5.删除重复代码;6.使用更优雅写法(使用默认参数代替短路表达式,用 Object.assign 设置默认对象,函数式编程)7.不要把标记用作函数参数 8.对函数中条件处理(封装条件,避免否定条件)
1.阿里云矢量图标库引入方式(Unicode、Font class、Symbol SVG图片格式、在线引入iconfont)
//Unicode方式引入
//1、在自定义的全局样式表中引入自定义字体 `font-face,并定义使用iconfont的样式
@font-face {
font-family: "iconfont";
src: url('iconfont.eot'); /* IE9*/
src: url('iconfont.eot#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('iconfont.woff') format('woff'), /* chrome, firefox */
url('iconfont.ttf') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('iconfont.svg#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family:"iconfont" !important;
font-size:16px;
font-style:normal;
-webkit-font-smoothing: antialiased;
-webkit-text-stroke-width: 0.2px;
-moz-osx-font-smoothing: grayscale;
}
//2、挑选相应图标并获取字体编码,应用于页面
//Font class方式引入
1、进入项目以后选择 Font class 并下载到本地
2、把下载的文件粘贴到自己的项目中,记得放在 static目录下
//在全局样式表中加入如下代码
// 阿里字体图标设置
.icon, .iconfont {
font-family:"iconfont" !important;
font-size:16px;
font-style:normal;
-webkit-font-smoothing: antialiased;
-webkit-text-stroke-width: 0.2px;
-moz-osx-font-smoothing: grayscale;
}
(Symbol SVG图片格式)引入