2-3 css部分
- 两栏布局
- 三栏布局
- 双飞翼和圣杯布局:和三栏布局要求相同,不过中间列要写在前面保证优先渲染。
1.自适应缩放要注意,要给max-widht或者min-width。就flex,float,position这三种方式,相对稳定的是flex。flex:auto,flex:none
代表1 1 auto 0 0 auto grow默认是0,shrink默认是1,basis默认是盒子宽度-auto
- 三角形
border:1px solid #ccc 分解border-width border-style: solid; border-color: tomato transparent transparent transparent;
- 使用 css 实现一个宽高自适应的正方形
1.直接用vw单位
2.padding-top:% padding的百分比值参照的不是容器的高度,而是宽度 。
3.利用第二点的性质,给元素添加伪元素,伪元素margin-top:30%。要记得触发父元素为BFC,不然撑不起来
- 实现扇形(border+旋转)
- 垂直居中(left:0,margin:0,top:0,right:0,margin:auto)
- 清除浮动(伪元素clear:both),父级触发BFC
- 弹出框
- 导航栏(滑动)
CSS 部分完,总结,Flex 无敌。
2-4 js 部分
- hash路由
window.addEventlistener('hashchange',(e)=>{
location.hash
} )
1.监听
2.操作BOM
- history路由
调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。
2-4 css部分
- 扩大可点击区域
关键实现:伪元素
具体分析:利用伪元素和定位达到鼠标移到边缘时候出现手型且可点击
.expand-range {
position: relative;
cursor: pointer;
}
.expand-range::after {
content: '';
position: absolute;
top: -10px;
right: -10px;
bottom: -10px;
left: -10px;
}
scss方式
@mixin expand-range($top: -10px, $right: $top, $bottom: $top, $left: $right, $position: relative,$cursor:pointer) {
position: $position;
cursor: $cursor;
&:after {
content: '';
position: absolute;
top: $top;
right: $right;
bottom: $bottom;
left: $left;
}
}
//使用:.test { @include expand-range($top: -5px) }
-
巧用层叠上下文
元素的层叠关系,背景,负z-index,盒子z-index
利用伪元素实现圆角矩形并叠加在父元素的背景之上文字之下
/*边框内圆角*/
div {
position: relative;
z-index: 1;
height: 200px;
padding: 10px;
background: #333;
}
div::after {
content: '';
position: absolute;
left: 10px;
top: 10px;
right: 10px;
bottom: 10px;
z-index: -1;
border-radius: 5px;
background: cyan;
}
- clip-path
clip-path: circle(50px at 50px 50px)
以50px 50px
的地方为圆心裁剪一个半径 50px 的圆;clip-path: ellipse(30px 40px at 50px 50px)
以50px 50px
的地方为圆心裁剪一个横向半径 30px,纵向半径 40px 的椭圆;clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%)
按照多个坐标剪裁一个多边形,此处是菱形。
- min-content (自适应宽度)
min-content max-content 自适应宽度 width:min-content 根据内容的最小值自动缩放,反之
- box-shadow
水平,垂直,blur,spread,color,inset(从外层的阴影(开始时)改变阴影内侧阴影)
使用
box-shadow
可以模拟实现多重边框,但是由于阴影不占空间,所以无法触发点击事件,鼠标hover边框时无法出现小手,所以需要配合inset
关键字使用弄清楚参数之后可以
单侧投影: box-shadow: 0 5px 4px -4px black;
逗号分隔设置多个阴影色: box-shadow: 5px 0 5px -5px black, -5px 0 5px -5px black;
不规则投影图形多个组合:filter: drop-shadow(2px 2px 10px rgba(0,0,0,.5));
- 解析background
background-clip:属性规定背景的绘制区域 ,由于
background
属性默认会覆盖整个盒模型包括边框border
(所以要裁切到规定规定的区域,让border显示) 半透明边框还是得自己积累,这讲的花里胡哨
- linear-gradient
至少两种颜色,默认上到下(height)
颜色后面的百分比是,高度为百分比是开始这个颜色
方向to left top 就是到左上角,那么就是右下角到左上角
直接给角度也是按从左到右或者其他的to left或者to right
linear-gradient(0deg, blue, green 40%, red);线性渐变
repeating-linear-gradient(red, yellow 10%, green 20%); 是否重复
radial-gradient(circle, red, yellow, green); 默认椭圆 径向渐变
- animation
贝塞尔曲线
总结:背景颜色,渐变,动画,position,box-shadow
2-4 vue部分
- 说说你对 SPA 单页面的理解,它的优缺点分别是什么?
仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。也就是说只有一个html,webpack打包,组件,模块化,
优点:前后端分离,页面的跳转不会重新加载整个页面,提高了用户体验
确定:SEO难度大,首屏加载速度慢。服务端渲染再次得到了需求
- v-show 与 v-if 有什么区别?
- Class 与 Style 如何动态绑定?(都是对象和数组,稍微有点区别而已,style数组的值是写好的在data上)
- 怎样理解 Vue 的单向数据流?(props)
- computed 和 watch 的区别和运用的场景?
依赖项,监听项,异步,缓存(computed计算缓存也可避免每次组件渲染都计算一次)
- 直接给一个数组项赋值,Vue 能检测到变化吗?
array的变化追踪通过监听array原型上方法的改变,通过拦截器从新是操作数组
原型方法改变数组的方法有7种 push,pop,shift,unshift,splice,sort,和reverse
对Array的变化侦测是通过拦截原型的方式实现的。正式因为这种方式,其实有些数组操作Vue.js是拦截不到的,
this.list[0] = 2
this.list.length = 0
通过splice触发原型方法去改变数组,或者vue提供的this.$set(target,key,value)
- 谈谈你对 Vue 生命周期的理解?
1.初始化,2.data,3.虚拟DOM未挂载,4.操作DOM
- Vue 的父组件和子组件生命周期钩子函数执行顺序?
父组件操作虚拟DOM的时候才会碰到子组件
- 在哪个生命周期内调用异步请求?
在created阶段,data可以获取了的,可以进行赋值的。
能更快获取到服务端数据,减少页面 loading 时间;
ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;
- 父组件可以监听到子组件的生命周期吗?
// Parent.vue
<Child @hook:mounted="doSomething" >
当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。
- 谈谈你对 keep-alive 的了解?
Keep-alive 和router
router-view是router提供的组件 Keep-alive是vue内置组件
属性:include,exclude
<keep-alive>
<router-view />
</keep-alive>
直接嵌套二级组件是不会缓存的
那么就会缓存在Ndode节点中了,就不会重新创建,那么也就不会触发生命周期了 activated,deactivated
组件内路由导航:beforeRouteEnter,beforeRouteLeave
如果想记住二级切换的是哪个组件
data(){
return {
path:/home
}
}
activated(){
this.$router.push(this.path)
},
deactivated(){},
beforeRouteLeave(to,from,next){
this.path =this.$route.path
next()
}
这样就是记住上一个二级路由
- 组件中 data 为什么是一个函数?
为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?
组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝
- v-model 的原理?
在表单 input、textarea、select 等元素上创建双向数据绑定
<input v-model='something'>
相当于
<input v-bind:value="something" v-on:input="something = $event.target.value">
如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:
父组件:
<ModelChild v-model="message"></ModelChild>
子组件:
<div>{{value}}</div>
props:{
value: String
},
methods: {
test1(){
this.$emit('input', '小红')
},
},
- Vue 组件间通信有哪几种方式?
Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信
props / $emit
适用 父子组件通信children`:访问父 / 子实例
EventBus ($emit / $on)
适用于 父子、隔代、兄弟组件通信
$attrs
/$listeners
适用于 隔代组件通信
provide / inject
适用于 隔代组件通信Vuex 适用于 父子、隔代、兄弟组件通信
- 能说下 vue-router 中常用的 hash 和 history 路由模式实现原理吗?
(1)hash 模式的实现原理
location.hash 的值就是 URL 中 # 后面的内容它的 location.hash 的值为 '#search':
URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。
(2)history 模式的实现原理
HTML5 提供了 History API 来实现 URL 的变化。history.pushState() 和 history.repalceState()
在不进行刷新的情况下,操作浏览器的历史纪录
window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。
- 什么是 MVVM?
View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建
Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口
MVVM 框架实现了双向绑定,这样 ViewModel 的内容会实时展现在 View 层,前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新。这样 View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。
- Vue 是如何实现数据双向绑定的?
Vue 主要通过以下 4 个步骤来实现数据双向绑定的:
实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
- Vue 框架怎么实现对象和数组的监听?
Vue 框架是通过遍历数组 和递归遍历对象,从而达到利用 Object.defineProperty() 也能对对象和数组(部分方法的操作)进行监听。
- Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题
2-6 ES6部分
- ES6面向对象
在ES6中新增加了类的概念,可以使用class关键字声明一个类
原理:类本身指向构造函数,所有方法定义在
prototype
上,可看作构造函数的另一种写法(Class === Class.prototype.constructor
)
通过class关键字创建类,类名我们还是习惯性定义首写字母大写
类里面有个constructor函数,可以接受传递过来的参数,同时返回实例对象
constructor 函数只要new生成实例时,就会自动调用这个函数,如果我们不写这个函数,类也会自动生成这个函数
生成实例 new 不能省略
最后注意语法规范,创建类 类名后面不要加小括号,生成实例 类名后面要加小括号,构造函数不需要加function
我们类里面的所有函数不需要写function
多个函数方法之间不需要添加逗号分隔
class Name {
constructor(x,y){
this.x = x;
this.y = y;
}
say(){}
good(){}
}
// 继承
class Father{
constructor(x,y){
this.x = x;
this.y = y;
}
say(){
console.log('father')
}
}
class Son extends Father{
constructor(x,y){
super(x,y)
}
}
// 私有属性方法
const name = Symbol("name");
const print = Symbol("print");
class Person{
constructor(age){
this[name] = 'aking'
this.age = age;
}
[print](){
console.log(`${this[name]} is ${this.age} years old`)
}
}
// 混合继承
function CopyProperties(target, source) {
for (const key of Reflect.ownKeys(source)) {
if (key !== "constructor" && key !== "prototype" && key !== "name") {
const desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
function MixClass(...mixins) {
class Mix {
constructor() {
for (const mixin of mixins) {
CopyProperties(this, new mixin());
}
}
}
for (const mixin of mixins) {
CopyProperties(Mix, mixin);
CopyProperties(Mix.prototype, mixin.prototype);
}
return Mix;
}
class Student extends MixClass(Person, Kid) {}
2-7 vue通信的细节
- props
数据格式computed
数据监听watch
- this.$xxx
this.$children
,this.$parent
this.$refs
这种通信方式,更加的简单直接获取vue实例,对vue实例下的数据和方法直接获取或者引用。注意获取组件的位置,顺序是否正确
provide inject
用法 和react.context
非常相似,provide
相当于Context.Provider
,inject
相当于Context.Consumer
,让父组件通信不受到组件深层次子孙组件的影响。
provide
提供内容不可能被兄弟组件获取到的,所以兄弟组件的通信不肯能靠这种方式来完成。父组件对子组件的状态一无所知。也不能主动向子组件发起通信
通过this下面的数据直接获取vue
实例这种方法比较暴力,因为我们所谓的组件,最终都会是一个对象,存放组件的各种信息,组件和组件通过this.$children
和this.$parent
指针关联起来。因为在项目中只有一个root
根组件,理论上,我们可以找到通过this.$children
this.$parent
来访问页面上的任何一个组件 ,但是实际上如何精确匹配到目标组件,确是一个无比棘手的问题。
- provide inject
// 父组件
provide(){
return {
/* 将自己暴露给子孙组件 ,这里声明的名称要于子组件引进的名称保持一致 */
father:this
}
},
// 这里我们通过provide把本身暴露出去。⚠️⚠️⚠️这里声明的名称要与子组件引进的名称保持一致
// 子组件
/* 引入父组件 */
inject:['father'],
methods:{
send(){
this.father.sonSay(this.mes)
}
},
// 父组件
// 如果我们向外提供了方法,如果方法里面有操作this行为,需要绑定this
provide(){
return {
/* 将通信方法暴露给子孙组件(注意绑定this) */
grandSonSay:this.grandSonSay.bind(this),
sonSay:this.sonSay.bind(this)
}
},
methods:{
/* 接受孙组件信息 */
grandSonSay(value){
this.grandSonMes = value
},
/* 接受子组件信息 */
sonSay(value){
this.sonMes = value
},
},
// 子组件
/* 引入父组件方法 */
inject:['sonSay'],
methods:{
send(){
this.sonSay(this.mes)
}
},
- vuex
vuex
中actions
允许我们做一些异步操作,然后通过commit
可以把数据传入对应的mutation
,至于actions
为什么可以执行异步,是因为里面底层通过Promise.resolve
能够获取异步任务完成的状态。
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
fatherMes: 'fatherMes',
sonMes: 'sonMes',
fatherMesAsync: 'fatherMesAsync'
},
mutations: {
sayFaher(state, value) {
state.fatherMes = value
},
saySon(state, value) {
state.sonMes = value
},
sayAsyncFather(state, value) {
state.fatherMesAsync = value
}
},
actions: {
asyncSayFather({ commit }, payload) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(payload)
console.log(payload)
}, 2000)
}).then(res => {
console.log('后执行')
commit('sayAsyncFather', res)
})
}
}
})
<template>
<div class="father">
<input v-model="mes" /> <button @click="send">同步:对子组件说</button
><br />
<input v-model="asyncMes" />
<button @click="asyncSend">异步:对子组件说</button><br />
<div>子组件对我说:{{ sonMes }}</div>
</div>
</template>
<script>
export default {
/* 父组件 */
name: 'father',
data() {
return {
mes: '',
asyncMes: ''
}
},
computed: {
sonMes() {
return this.$store.state.sonMes
}
},
mounted() {
console.log(this.$store)
},
methods: {
/* 触发mutations,传递数据给子组件 */
send() {
this.$store.commit('sayFaher', this.mes)
},
/* 触发actions,传递数据给子组件 */
asyncSend() {
this.$store.dispatch('asyncSayFather', this.asyncMes)
}
}
}
</script>
- 总结:通信就是组件实例之间的数据相互传递,组件实例相互暴露。
vue最终版
- v-bind="attrs" v-on="$listeners"
这个配合
v-bind="$attrs"
在封装一些组件的时候非常有用,比如实现属性透传。在子组件中应当添加inheritAttrs: false(避免父作用域的不被认作props的特性绑定应用在子组件的根元素上)。
vm.$attrs
包含了父作用域中不作为prop
被识别 (且获取) 的attribute
绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过v-bind="$attrs"
传入内部组件——在创建高级别的组件时非常有用。v-bind="$props": 可以将父组件的所有props下发给它的子组件
未识别的事件可通过v-on="$listeners"传入(.native绑原生事件是没用的)
post: {
id: 1,
title: 'My Journey with Vue',
placeholder:'默认'
}
<!-- 利用 v-bind 可以传入一个对象的所有 property,类似 v-bind="Obj" -->
<blog-post v-bind="post"></blog-post>
<!--等价于-->
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>
<input type="text" v-bind="$attrs"/>
// top组件,传递了name,age,gender,sdf四个属性到子组件center,然后接收了两个isClick()和asd()方法
<template>
<section>
<centers
name="name"
age="18"
gender="666"
sdf="asd"
@isClick="isClick"
@asd="asd"
></centers>
</section>
</template>
<script>
import centers from '~/components/center';
export default {
components: {
centers
},
methods: {
asd() {
console.log(999);
},
isClick() {
console.log(666);
}
}
};
</script>
// 未识别的事件可通过v-on="$listeners"传入(.native绑原生事件是没用的) center组件
<template>
<section>
<div class="mt-10">
<bottom v-bind="$attrs" v-on="$listeners" />
</div>
</section>
</template>
<script>
import bottom from '~/components/bottom';
export default {
components: {
bottom
},
props: {
name: {
type: String,
default: 'default'
},
age: {
type: String,
default: 'default'
}
}
};
</script>
//bottom组件
<template>
<section>
<div>
{{ $attrs['gender'] }} 在$attrs里面只会有props没有注册的属性
<br>
{{ gender }}
</div>
</section>
</template>
<script>
export default {
props: {
gender: {
type: String,
default: ''
}
},
mounted() {
console.log(this.$attrs);
console.log(this.$listeners);
this.$listeners.isClick();
this.$listeners.asd();
}
};
</script>
- Props 校验
Vue.component('my-component', {
// 带有默认值的对象
propA: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
}
})
Vue.component('my-component', {
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
})
- 插槽
默认插槽
后备内容 默认内容
具名插槽
作用域插槽 v-slot:defalut = { item }
想在一个插槽中使用子组件的数据和事件
直接在子组件中通过
v-bind
的方式将数据或者事件传递给父组件中
<!-- 具名插槽 -->
<h3>具名插槽</h3>
<Child2>
<template v-slot:footer><div>我是底部</div></template>
<template #header><div>我是头部</div></template>
<template v-slot:default>
<div>我是内容</div>
</template>
</Child2>
<div class="child">
<slot name="header"></slot>
<slot></slot>
<div>Hello, I am from Child.</div>
<slot name="footer"></slot>
</div>
<div class="child">
<div>Hello, I am from Child.</div>
<!-- 将user和callMe通过 v-bind 的方式传递 -->
<slot :user="user" :callMe="callMe"></slot>
</div>
<!--然后在父组件中的插槽内,通过类似 v-slot:default="slotProps" 接受子组件传递过来的数据-->
<Child3>
<!-- slotProps 可以自定义-->
<template v-slot:default="slotProps">
<div>我的名字:{{slotProps.user.name}}</div>
<div>我的年龄:{{slotProps.user.age}}</div>
<button @click="slotProps.callMe">Clicl Me</button>
</template>
</Child3>
<!-- 以上 slotProps 可以自定义,而且可以使用解构赋值的语法 -->
<!-- 解构赋值 -->
<template v-slot:other="{ user, callMe}">
<div>我的名字:{{user.name}}</div>
<div>我的年龄:{{user.age}}</div>
<button @click="callMe">Clicl Me</button>
</template>
-
动态的指令参数
以动态的将指令参数传递给组件。假设你有一个组件 ,有时候你需要绑定一个点击事件 click,有时候需要绑定一个双击事件 dblclick,
<template>
...
<my-button @[someEvent]="handleSomeEvent()"/>
...
</template>
<script>
...
data(){
return{
...
someEvent: someCondition ? "click" : "dblclick"
}
},
methods:{
handleSomeEvent(){
// do something
}
}
...
</script>
- hookEvent 的使用
可以模板声明式的监听子组件的生命周期钩子
监听子组件的生命周期,自己的实例生命周期致力于解决创建和销毁写在一起的形式
比如,我们调用了一个很耗费性能的第三方组件 List,这个组件可能需要渲染很久,为了更好的用户体验,我们想在 List 组件进行更新的时候添加一个 loading 的动画效果
<List @hook:updated="handleTableUpdated"></List >
另外,我们还可以通过下面的方式给一个 Vue 组件添加生命周期处理函数
vm.$on('hooks:created', cb)
vm.$once('hooks:created', cb)
mounted(){
const thirdPartyPlugin = thirdPartyPlugin()
this.$on('hook:beforeDestroy',()=>{
thirdPartyPlugin.destroy()
})
}
- key 值的使用
会遇到如
/path/:id
这样只改变id
号的场景,但渲染不同的组件。由于router-view
是复用的,单纯的改变id
号并不会刷新router-view
,
v-for :key
- CSS scoded 和深度作用选择器
因为父组件设置了
scoped
之后,父组件的样式将不会渗透到子组件中有些像
.a >>> .b { /* ... */ }Sass
之类的预处理器无法正确解析>>>
。这种情况下你可以使用/deep/
或::v-deep
操作符取而代之——两者都是>>>
的别名,同样可以正常工作
- watch immediate deep $watch
这里 watch 的一个特点是,最初绑定的时候是不会执行的,要等到 id 改变时才执行监听计算。这可能导致我们页面第一次渲染出错
watch: {
id: {
handler(newValue) {
this.getDetails(newValue);
},
// 代表在wacth里声明了id后这个方法之后立即先去执行handler方法
immediate: true
// 通过指定deep属性为true, watch会监听对象里面每一个值的变化
deep: true
}
}
但是如果要在页面初始化时候加载数据,我们还需要在created或者mounted生命周期钩子里面再次调用$_loadData方法。不过,现在可以不用这样写了,通过配置watch的立即触发属性,就可以满足需求了
我们可以在需要的时候通过this.$watch来监听数据变化。那么如何取消监听呢,上例中this.$watch返回了一个值unwatch,是一个函数,在需要取消的时候,执行 unwatch()即可取消
export default {
data() {
return {
formData: {
name: '',
age: 0
}
}
},
created() {
this.$_loadData()
},
methods: {
// 模拟异步请求数据
$_loadData() {
setTimeout(() => {
// 先赋值
this.formData = {
name: '子君',
age: 18
}
// 等表单数据回填之后,监听数据是否发生变化
const unwatch = this.$watch(
'formData',
() => {
console.log('数据发生了变化')
},
{
deep: true
}
)
// 模拟数据发生了变化
setTimeout(() => {
this.formData.name = '张三'
}, 1000)
}, 1000)
}
}
}
- v-once 和 v-pre 提升性能
我们知道
Vue
的性能优化很大部分在编译这一块,Vue
源码就有类似标记静态节点的操作,以在patch
的过程中跳过编译,从而提升性能。
- v-cloak 解决页面闪烁问题
// template 中
<div class="#app" v-cloak>
<p>{{value.name}}</p>
</div>
// css 中
[v-cloak] {
display: none;
}
- 表单输入控制——表单修饰符/change事件/filter
<input v-model.number="age" type="number">
<input v-model.trim="msg">
给表单绑定事件,在事件处理中进行表单输入控制
<input v-model="value2" type="text" @change="inputChange(value2)" />
methods: {
inputChange: function(val) {
if (!val) return ''
val = val.toString()
this.value2 = val.charAt(0).toUpperCase() + val.slice(1)
}
}
filter:
<input v-model="value1" type="text" />
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
watch: {
value1(val) {
this.value1 = this.$options.filters.capitalize(val);
}
}
- 事件:特殊变量 $event
有时候,我们绑定事件后,想传入除了原生事件对象之外的其他参数
<!-- 注意这里调用的方法有两个参数 -->
<input v-model="value1" @change="inputChange('hello', $event)">
methods: {
inputChange(msg, e) {
console.log(msg, e);
}
}
在自定义事件中,$event 是从其子组件中捕获的值
场景:你想监听 el-input 的传递过来的值的同时,传递其他的参数。
<el-input
v-model="value2"
@change="change($event, 'hello')"
placeholder="Input something here"
/>
methods: {
change(e, val) {
console.log("event is " + e); // el-input 输入的值
console.log(val); // hello
}
}
- 调试template
// 这里最好是判断一下,只有在测试环境中才使用
// main.js
Vue.prototype.$log = window.console.log;
// 组件内部
<div>{{$log(info)}}</div>
继续肝
- require.context()
实际上是 webpack 的方法,vue 工程一般基于 webpack,所以可以使用 require.context(directory,useSubdirectories,regExp) 接收三个参数: directory:说明需要检索的目录 useSubdirectories:是否检索子目录 regExp: 匹配文件的正则表达式,一般是文件名
const path = require('path')
const files = require.context('@/components/home', false, /\.vue$/)
const modules = {}
files.keys().forEach(key => {
const name = path.basename(key, '.vue')
modules[name] = files(key).default || files(key)
})
components:modules
- .sync
// 父组件
<home :title.sync="title" />
//编译时会被扩展为
<home :title="title" @update:title="val => title = val"/>
// 子组件
// 所以子组件可以通过$emit 触发 update 方法改变
mounted(){
this.$emit("update:title", '这是新的title')
}
- .EventBus
1.就是声明一个全局Vue实例变量 EventBus , 把所有的通信数据,事件监听都存储到这个变量上; 2.类似于 Vuex。但这种方式只适用于极小的项目 3.原理就是利用on和on和emit 并实例化一个全局 vue 实现数据共享
// 在 main.js
Vue.prototype.$eventBus=new Vue()
// 传值组件
this.$eventBus.$emit('eventTarget','这是eventTarget传过来的值')
// 接收组件
this.$eventBus.$on("eventTarget",v=>{
console.log('eventTarget',v);//这是eventTarget传过来的值
})
- 路由传参
三种方案对比 方案二参数不会拼接在路由后面,页面刷新参数会丢失 方案一和三参数拼接在后面,丑,而且暴露了信息
// 路由定义
{
path: '/describe/:id',
name: 'Describe',
component: Describe
}
// 页面传参
this.$router.push({
path: `/describe/${id}`,
})
// 页面获取
this.$route.params.id
// 路由定义
{
path: '/describe',
name: 'Describe',
component: Describe
}
// 页面传参
this.$router.push({
name: 'Describe',
params: {
id: id
}
})
// 页面获取
this.$route.params.id
// 路由定义
{
path: '/describe',
name: 'Describe',
component: Describe
}
// 页面传参
this.$router.push({
path: '/describe',
query: {
id: id
`}
)
// 页面获取
this.$route.query.id
- 路由按需加载
{
path:'/',
name:'home',
components:()=>import('@/components/home')
}
import()方法由es6提出,import()方法是动态加载,返回一个Promise对象,then方法的参数是加载到的模块。
类似于Node.js的require方法,主要import()方法是异步加载的。
- 动态组件
<transition>
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
</transition>
- components和 Vue.component
components:局部注册组件
export default{
components:{home}
}
Vue.component:全局注册组件
Vue.component('home',home)
- Vue.extend
组件构造器
// 创建构造器
var Profile = Vue.extend({
template: '<p>{{extendData}}</br>实例传入的数据为:{{propsExtend}}</p>',//template对应的标签最外层必须只有一个标签
data: function () {
return {
extendData: '这是extend扩展的数据',
}
},
props:['propsExtend']
})
// 创建的构造器可以挂载到元素上,也可以通过 components 或 Vue.component()注册使用
// 挂载到一个元素上。可以通过propsData传参.
new Profile({propsData:{propsExtend:'我是实例传入的数据'}}).$mount('#app-extend')
// 通过 components 或 Vue.component()注册
Vue.component('Profile',Profile)
- mixins
场景:有些组件有些重复的 js 逻辑,如校验手机验证码,解析时间等,mixins 就可以实现这种混入 mixins 值是一个数组
const mixin = {
created(){
this.dealTime()
},
methods:{
dealTime(){
console.log('这是mixin的dealTime里面的方法')
}
}
}
export default{
mixins:[mixin]
}
- Vue.vue()
场景:我们使用element时会先import,再Vue.use()一下,实际上就是注册组件,触发install方法,这个在组件调用经常使用到;会自动组织多次注册相同的插件
- install
var MyPlugin = {};
MyPlugin.install = function (Vue, options) {
// 2. 添加全局资源,第二个参数传一个值默认是update对应的值
Vue.directive('click', {
bind(el, binding, vnode, oldVnode) {
//做绑定的准备工作,添加时间监听
console.log('指令my-directive的bind执行啦');
},
inserted: function(el){
//获取绑定的元素
console.log('指令my-directive的inserted执行啦');
},
update: function(){
//根据获得的新值执行对应的更新
//对于初始值也会调用一次
console.log('指令my-directive的update执行啦');
},
componentUpdated: function(){
console.log('指令my-directive的componentUpdated执行啦');
},
unbind: function(){
//做清理操作
//比如移除bind时绑定的事件监听器
console.log('指令my-directive的unbind执行啦');
}
})
// 3. 注入组件
Vue.mixin({
created: function () {
console.log('注入组件的created被调用啦');
console.log('options的值为',options)
}
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
console.log('实例方法myMethod被调用啦');
}
}
//调用MyPlugin
Vue.use(MyPlugin,{someOption: true })
//3.挂载
new Vue({
el: '#app'
});
- Vue.nextTick
2.1.0 新增 场景:页面加载时需要让文本框获取焦点 用法:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
mounted(){ //因为 mounted 阶段 dom 并未渲染完毕,所以需要$nextTick
this.$nextTick(() => {
this.$refs.inputs.focus() //通过 $refs 获取dom 并绑定 focus 方法
})
}
- vue.derective
场景:官方给我们提供了很多指令,但是我们如果想将文字变成指定的颜色定义成指令使用,这个时候就需要用到Vue.directive
1.bind 只调用一次,指令第一次绑定到元素时候调用,用这个钩子可以定义一个绑定时执行一次的初始化动作。 2.inserted:被绑定的元素插入父节点的时候调用(父节点存在即可调用,不必存在document中) 3.update: 被绑定与元素所在模板更新时调用,而且无论绑定值是否有变化,通过比较更新前后的绑定值,忽略不必要的模板更新 4.componentUpdate :被绑定的元素所在模板完成一次更新更新周期的时候调用 5.unbind: 只调用一次,指令月元素解绑的时候调用
// 全局定义
Vue.directive("change-color",function(el,binding,vnode){
el.style["color"]= binding.value;
})
// 使用
<template>
<div v-change-color=“color”>{{message}}</div>
</template>
<script>
export default{
data(){
return{
color:'green'
}
}
}
</script>
- vue.filter
// 使用
// 在双花括号中
{{ message | capitalize }}
// 在 `v-bind` 中
<div v-bind:id="rawId | formatId"></div>
// 全局注册
Vue.filter('stampToYYMMDD', (value) =>{
// 处理逻辑
})
// 局部注册
filters: {
stampToYYMMDD: (value)=> {
// 处理逻辑
}
}
// 多个过滤器全局注册
// /src/common/filters.js
let dateServer = value => value.replace(/(\d{4})(\d{2})(\d{2})/g, '$1-$2-$3')
export { dateServer }
// /src/main.js
import * as custom from './common/filters/custom'
Object.keys(custom).forEach(key => Vue.filter(key, custom[key]))
- 事件修饰符
.stop:阻止冒泡 .prevent:阻止默认行为 .self:仅绑定元素自身触发 .once: 2.1.4 新增,只触发一次 .passive: 2.3.0 新增,滚动事件的默认行为 (即滚动行为) 将会立即触发,不能和.prevent 一起使用
.native
.capture
- 缓存和动画
//或include="a,b" :include="/a|b/",a 和 b 表示组件的 name //因为有些页面,如试试数据统计,要实时刷新,所以就不需要缓存 //路由标签 // c 表示组件的 name值
注:匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配
- 全局路由钩子
// 1.router.beforeEach
router.beforeEach((to, from, next) => {
console.log('全局前置守卫:beforeEach -- next需要调用') //一般登录拦截用这个,也叫导航钩子守卫
if (path === '/login') {
next()
return
}
if (token) {
next();
}
})
- 组件路由钩子
1.beforeRouteEnter 在渲染该组件的对应路由被确认前调用,用法和参数与router.beforeEach类似,next需要被主动调用 此时组件实例还未被创建,不能访问this 可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数
beforeRouteEnter (to, from, next) {
// 这里还无法访问到组件实例,this === undefined
next( vm => {
// 通过 `vm` 访问组件实例
})
}
2.beforeRouteUpdate (v 2.2+) 在当前路由改变,并且该组件被复用时调用,可以通过this访问实例, next需要被主动调用,不能传回调
3.beforeRouteLeave 导航离开该组件的对应路由时调用,可以访问组件实例 this,next需要被主动调用,不能传回调
- Vue.route
this.$router.push():跳转到不同的url,但这个方法回向history栈添加一个记录,点击后退会返回到上一个页面
this.$router.replace():不会有记录
this.$router.go(n):n可为正数可为负数。正数前进, 负数后退,类似 window.history.go(n)
表示当前跳转的路由对象,属性有: name:路由名称 path:路径 query:传参接收值 params:传参接收值 fullPath:完成解析后的 URL,包含查询参数和 hash 的完整路径 matched:路由记录副本 redirectedFrom:如果存在重定向,即为重定向来源的路由的名字
this.route.query.id:获取通过 query 传参的参数
- router-view 的 key
场景:由于 Vue 会复用相同组件, 即 /page/1 => /page/2 或者 /page?id=1 => /page?id=2 这类链接跳转时, 将不在执行created, mounted之类的钩子
<router-view :key="$route.fullPath"></router-view>
这样组件的 created 和 mounted 就都会执行
- vue-loader
它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件。简而言之,webpack 和 Vue Loader 的结合为你提供了一个现代、灵活且极其强大的前端工作流,来帮助撰写 Vue.js 应用。
- img 加载失败
场景:有些时候后台返回图片地址不一定能打开,所以这个时候应该加一张默认图片
// page 代码
<img :src="imgUrl" @error="handleError" alt="">
<script>
export default{
data(){
return{
imgUrl:''
}
},
methods:{
handleError(e){
e.target.src=reqiure('图片路径') //当然如果项目配置了transformToRequire,参考上面 33.2
}
}
}
</script>
- 基础指令
v-if,v-else,v-show,v-for,v-text,v-html,v-on,v-model,v-bind v-pre,v-clock,v-once
- vue-loader是什么?使用它的用途有哪些?
vue 文件的一个加载器,将 template/js/style
转换成 js
模块。
用途:js
可以写 es6
、 style
样式可以 scss
或 less
、 template
可以加 jade
等