文章目录
1 前言
4月跟着慕客网的视频写了去哪儿网app的小项目,这段时间面试会经常问到其中的知识点,这两天把视频重新浏览了一遍,又有了新的收获,在此做梳理。
2 CSS
2.1 宽高比例自适应
width: 100%;
height: 0;
padding-bottom: 50%;
但是如果再放置子元素可能会有问题,这时需要用到子绝父相布局。
<div class="scale">
<div class="item">
这里是所有子元素的容器
</div>
</div>
<style>
* {
margin: 0;
padding: 0;
}
.scale {
width: 100%;
padding-bottom: 50%;
height: 0;
position: relative;
}
.item {
width: 100%;
height: 100%;
background-color: green;
position: absolute;
}
</style>
2.2 子组件穿透
参考文章 https://segmentfault.com/a/1190000015932467
scoped及其原理
style标签拥有scoped属性之后,其CSS属性就只能用于当前vue组件,可以使得组件样式互不污染。原理是通过PostCSS给组件中的所有dom添加了一个独一无二的data属性并对应改为属性选择器。
.example[data-v-5558831a] {
color: red;
}
<template>
<div class="example" data-v-5558831a>scoped测试案例</div>
</template>
scoped穿透
用于需要在局部组件中修改第三方组件库样式的情况。
stylus使用>>>,sass和less使用/deep/
外层 >>> 第三方组件
样式
.wrapper >>> .swiper-pagination-bullet-active
background: #fff
外层 /deep/ 第三方组件 {
样式
}
.wrapper /deep/ .swiper-pagination-bullet-active{
background: #fff;
}
2.3 溢出内容显示省略号
单行文本
overflow: hidden;//超出隐藏
white-space: nowrap;//溢出不换行
text-overflow: ellipsis;//显示省略号
多行文本(针对webkit内核浏览器有效)
<style type="text/css">
.test1 {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
width: 250px;
height: 200px;
border: 1px solid red;
}
</style>
<body>
<div class="test1">
测试文字测试文字测试文字测试文字测试文字测试文字测试文字测试文字测试文字测试文字测试文字测试文字
</div>
</body>
如果 -webkit-line-clamp改为2则省略号会显示在第二行末尾。
顺便复习浏览器内核
1、IE浏览器内核:Trident内核,也被称为IE内核;
2、Chrome浏览器内核:Chromium内核 → Webkit内核 → Blink内核;
3、Firefox浏览器内核:Gecko内核,也被称Firefox内核;
4、Safari浏览器内核:Webkit内核;
5、Opera浏览器内核:最初是自主研发的Presto内核,后跟随谷歌,从Webkit到Blink内核;
6、360浏览器、猎豹浏览器内核:IE+Chrome双内核;
7、搜狗、遨游、QQ浏览器内核:Trident(兼容模式)+Webkit(高速模式);
8、百度浏览器、世界之窗内核:IE内核;
3 Vue
基础
3.1 vue动画
使用场景:详情页展示景点图片时需要渐隐渐现效果
实现方式:
先定义通用的动画组件fade.vue,结合插槽实现动画
<template>
<transition>
<slot></slot>
</transition>
</template>
<script>
export default {
name: 'FadeAnimation'
}
</script>
<style lang="stylus" scoped>
.v-enter, .v-leave-to
opacity: 0
.v-enter-active, .v-leave-active
transition: opacity .5s
</style>
Banner.vue
<fade-animation>
<commonGallery :imgs="galleryImgs" v-show="showGallery" @close="handleGalleryClose"> </commonGallery>
</fade-animation>
复习
CSS动画
参考文章
http://www.w3school.com.cn/css3/css3_animation.asp
https://juejin.im/post/5cdd178ee51d456e811d279b
第二篇文章非常详细地阐述了各个属性的取值和含义
keyframes规则定义+绑定到选择器
@keyframes first{
from {background:red;}
to{background:yellow;}
}
//或者
@keyframes first{
0% {background: red;}
25% {background: yellow;}
50% {background: blue;}
100% {background: green;}
}
div{
animation: first 5s;
//具体属性
//animation: first 5s linear 0s 1 alternate both;
}
对于chrome和safari使用-webkit-keyframes和-webkit-animation
对于Firefox使用-moz-keyframes和-moz-animation
对于Opera使用-o-keyframes和-o-animation
JS动画
主要是通过setInerval()和改变函数left、top等值实现
//匀速运动
<style>
* {
margin: 0;
padding: 0;
}
.content {
width: 180px;
height: 180px;
background: green;
position: relative;
}
</style>
<body>
<div class='content'></div>
<button onclick='move()'>move</button>
<script>
var timer = null;
function move() {
var content = document.getElementsByClassName('content')[0];
var pos = content.offsetLeft;
var end = 500;
var speed = 10;
timer = setInterval(function () {
//减速
//var step = (end - pos) * 0.1;
//pos += step;
pos += 50;
content.style.left = pos + 'px';
if (pos > end) {
clearInterval(timer);
}
}, 50);
}
</script>
</body>
3.2 axios
参考文章
https://www.jianshu.com/p/8bc48f8fde75
https://www.kancloud.cn/yunye/axios/234845
axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 库,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范,它本身具有以下特征:
1.从浏览器中创建 XMLHttpRequest
2.支持 Promise API
3.客户端支持防止CSRF
4.提供了一些并发请求的接口(重要,方便了很多的操作)
5.从 node.js 创建 http 请求
6.拦截请求和响应
7.转换请求和响应数据
8.取消请求
9.自动转换JSON数据
PS:防止CSRF:就是让你的每个请求都带一个从cookie中拿到的key, 根据浏览器同源策略,假冒的网站是拿不到你cookie中得key的,这样,后台就可以轻松辨别出这个请求是否是用户在假冒网站上的误导输入,从而采取正确的策略。
补充
执行POST请求
axios.post('/user',{
firstName:'Fred',
lastName:'Flintstone'
}).then(function(response){
console.log(response);
}).catch(function(error){
console.log(error);
});
执行多个并发请求
同步异步/并发并行的区别 并发不一定并行 但并行一定并发
参考文章 https://blog.csdn.net/luobo140716/article/details/50146013
function getUserAccount(){
return axiois.get('/user/123');
}
function getUserPermission(){
return axiois.get('/user/123/permission');
}
axios.all([getUserAccount(),getUserPermission()])
.then(axios.spread(
function(acct,perms){
//两个请求都执行完成
}
)
);
通过向axios传递相关配置创建请求
//发送POST请求
axios({
method:'post',
url:'/user/123',
data:{
firstName:'Fred',
lastName:'Flintstone'
}
});
以Detail.vue为例
methods: {
getDetailInfo () {
axios.get('/api/detail.json?id=', {
params: {
id: this.$route.params.id
}
}).then(this.handleGetDataSucc)
},
handleGetDataSucc (res) {
res = res.data
if (res.ret && res.data) {
const data = res.data
this.sightName = data.sightName
this.bannerImg = data.bannerImg
this.gallaryImgs = data.gallaryImgs
this.list = data.categoryList
}
}
},
mounted () {
this.getDetailInfo()
}
3.3 vuex数据共享
使用场景:城市列表页和主页当前城市需要共享数据。
使用原因:页面共享并且当点击列表任意一项后,当前城市和首页所在城市都要修改,适合使用vuex。
state.js
let defaultCity = '上海'
try {
if (localStorage.city) {
defaultCity = localStorage.city
}
} catch (e) { }
export default { // 全局公用的数据
city: defaultCity
}
mutation.js
export default {
changeCity (state, city) {
state.city = city
try {
localStorage.city = city // 存
} catch (e) {
}
}
}
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
Vue.use(Vuex)
export default new Vuex.Store({
state,
mutations,
getters: {// 类似于computed 需要用state数据计算出新的数据时可以使用 避免数据冗余
doubleCity (state) {
return state.city + ' ' + state.city
}
}
})
List.vue
import { mapState, mapMutations } from 'vuex'
methods: {
handleCity (city) {
this.changeCity(city)
this.$router.push('/')
},
...mapMutations(['changeCity']) // 把名为changeCity的mutation映射到当前组件名为changeCity的方法
},
computed: {
...mapState({ // 映射到当前组件的计算属性currentCity中
currentCity: 'city'
})
}
3.4 better-scroll插件
使用场景:城市列表滑动
使用原因:列表局部滚动,并且better-scroll支持滚动到指定位置,适合实现字母滑动产生列表滑动的效果。
//最外层的div,包括当前城市、热门城市和列表
<div class="list" ref="wrapper"></div>
import Bscroll from 'better-scroll'
mounted () {
this.scroll = new Bscroll(this.$refs.wrapper)
}
3.5 兄弟组件联动
使用场景:Alphabet组件滑动到某个字母时列表对应显示该字母开头的城市
实现方式:可以采用bus总线,这里采用的是City组件转发
Alphabet.vue
<template>
<ul class="list">
<li class="item" v-for="item of letters" :key="item"
@click="handleLetterClick"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
:ref="item">{{ item }}</li>
</ul>
</template>
<script>
export default {
name: 'CityAlphabet',
props: {
cities: Object
},
computed: {
letters () {
const letters = []
for (let i in this.cities) {
letters.push(i)
}
return letters
}
},
methods: {
handleLetterClick (e) {
this.$emit('change', e.target.innerText)
}
}
}
</script>
City.vue
<city-alphabet :cities="cities" @change="handleLetterChange"></city-alphabet>
methods: {
handleLetterChange (e) {
this.letter = e
}
}
<city-list :cities="cities" :hotCities="hotCities" :letter="letter"></city-list>
List.vue
props: {
cities: Object,
hotCities: Array,
letter: String
}
watch: {
letter () {
if (this.letter) {
const ele = this.$refs[this.letter][0]
this.scroll.scrollToElement(ele)
}
}
}
3.6 生命周期
参考文章
https://segmentfault.com/a/1190000008010666
https://segmentfault.com/q/1010000011521681
总结
beforeCreate:vue实例刚被创建,el、data都是undefined
created:el仍为undefined,data有对应的值
beforeMount:el和data都有对应的值,但el还是虚拟dom
mounted:el成功渲染为真实dom
注意
要看到更新区别需要在updated和beforeUpdate钩子中加入log:
console.log(‘真实dom结构:’ + document.getElementById(‘app’).innerHTML);
因为el更新了,两者区别在于是否渲染到DOM节点中
优化
3.6 函数防抖
视频中老师讲这是节流,但是查阅很多文章后发现这种方式是防抖。
参考文章:
防抖与节流
https://juejin.im/post/5ce3e400f265da1bab298359
https://zhuanlan.zhihu.com/p/38313717
防抖:当持续触发某个事件时,一定时间间隔内没有触发该事件,事件处理函数才会执行一次,如果间隔内再次触发了事件,则重新延时。
例子:持续触发scroll事件时,并不立即执行handle,当1s内没有触发scroll事件时则触发一次handle函数。
function debounce(fn,wait){
let timeout=null;
return function(){
if(timeout!=null){
clearTimeout(timeout);
}
timeout=setTimeout(fn,wait);
}
}
function handle(){
console.log('debounce');
}
window.addEventListener('scroll',debounce(handle,1000));
节流:当持续触发事件时,有规律地每隔一个时间间隔执行一次事件处理函数。
例子:持续触发scroll事件时,并不立即执行handle函数,每隔1s才会执行一次。
function throttle(fn,wait){
var prev=Date.now();
return function(){
var now=Date.now();
if(now-prev>wait){
fn();
prev=Date.now();
}
}
}
function handle(){
console.log('throttle');
}
window.addEventListener('scroll',throttle(handle,1000));
防抖和节流都可以用于mousemove、scroll、resize、input等事件。
设置一个timer和16ms的时间间隔,如果当前仍有滑动行为,则清除上次的timer。
Alphabet.vue
data () {
return {
touchStatus: false,
startY: 0,
timer: null
}
},
updated () {
this.startY = this.$refs['A'][0].offsetTop
},
methods: {
handleTouchMove (e) {
if (this.touchStatus) {
if (this.timer) {
clearTimeout(this.timer)
}
this.timer = setTimeout(() => {
const touchY = e.touches[0].clientY - 66
const index = Math.floor((touchY - this.startY) / 20)
if (index >= 1 && index <= this.letters.length) {
this.$emit('change', this.letters[index - 1])
}
}, 16)
}
}
}
3.7 keep-alive
参考文章 https://juejin.im/post/5b407c2a6fb9a04fa91bcf0d
使用场景:从主页切换到城市列表页再返回时希望保存滑动状态
keep-alive介绍
在vue构建的单页面应用(SPA)中,路由模块一般使用vue-router。vue-router不保存被切换组件的状态,进行push或者replace时,旧组件会被销毁,新组建会被新建,走一遍完整的生命周期。
对于某些需求,比如跳转到列表页面,需要保持滚动深度,等返回的时候依然在这个位置,可以使用keep-alive解决。
补充:什么是单页面应用和多页面应用?
参考文章 https://juejin.im/post/5a0ea4ec6fb9a0450407725c
SPA(单页面应用):公共资源(js、css等)只加载一次,页面跳转是局部刷新,常见PC端网站
MPA(多页面应用):公共资源(js、css等)选择性加载,页面跳转是全部刷新,常见APP端应用
- SPA不利于SEO(搜索引擎优化)
单页应用实际是把视图(View)渲染从Server交给浏览器,Server只提供JSON格式数据,视图和内容都是通过本地JavaScript来组织和渲染。而搜索搜索引擎抓取的内容,需要有完整的HTML和内容,单页应用架构的站点,并不能很好的支持搜索。
keep-alive使用方式
keep-alive是一个抽象组件,实际不会被渲染在DOM树中,其作用是在内存中缓存组件等到下次渲染依然保持状态,并且触发activated钩子函数。
因为缓存的需要经常出现在页面切换时,所以常常与router-view一起出现。
<keep-alive>
<router-view/>
</keep-alive>
对于只想渲染某一些页面/组件,可以使用keep-alive组件的属性include/exclude,表示要/不缓存的组件名称(组件定义时的name属性),接收的类型为string/RegExp/string数组。比如
<keep-alive :include="['ListView','DetailView']">
<router-view/>
</keep-alive>
如何实现条件缓存
比如在A->B->C中,C->B时保持缓存,A->B时放弃缓存,即B是条件缓存的。解决方案是将B动态地从include数组中增加/删除。
- 在vuex中定义一个全局的缓存数组。keepAlive和noKeepAlive两个mutation利用push和splice来控制组件增删。
export default{
namesapced: true,
state:{
keepAliveComponents:[]//缓存数组
},
mutations:{
keepAlive(state,component){
//防止重复添加
!state.keepAliveComponents.includes(component)&&state.keepAliveComponents.push(component)//增加组件
},
noKeepAlive(state,component){
const index=state.keepAliveComponents.indexOf(component)
index!=-1&&state.keepAliveComponents.splice(index,1)//删除组件
}
}
}
- 在父页面中定义keep-alive,并传入全局的缓存数组。
//App.vue
<div class="app">
<!--传入include数组-->
<keep-alive :include="keepAliveComponents">
<router-view></router-view>
</keep-alive>
</div>
export default{
computed:{
...mapState({
keepAliveComponents: state=>state.global.keepAliveComponents
})
}
}
- 缓存:在路由配置页中,约定使用meta属性keepAlive,值为true表示组件需要缓存。在全局路由钩子beforeEach中对该属性进行处理,每次进入该组件都进行缓存。
const router=new Router({
routes:[
{
path:'/A/B',
name:'B',
component:B,
meta:{
title:'B页面',
keepAlive: true//指定B组件的缓存性
}
}
]
})
router.beforeEach((to,from,next)=>{
// 在路由全局钩子beforeEach中,根据keepAlive属性,统一设置页面的缓存性
// 作用是每次进入该组件,就将它缓存
if(to.meta.keepAlive){
this.$store.commit('global/keepAlive',to.name)
}
})
- 取消缓存:使用路由的组件层钩子beforeRouteLeave。
export default{
name:'B',
created(){
},
beforeRouteLeave(to,from,next){
if(to.name!=='C'){
this.$store.commit('global/noKeepAlive',from.name)
}
next()
}
}
注意: keep-alive中include操作的是组件名,所以每定义一个组件都要显式声明name属性,否则缓存不起作用。而且显式name对devTools有提示作用。
App.vue
<template>
<div id="app">
<keep-alive exclude="Detail"> <!--请求过一次就保存到内存 Detail不被放入缓存 根据不同id请求不同内容-->
<router-view></router-view>
</keep-alive>
</div>
</template>
Home.vue
activated () { // 使用keepAlive后增加的生命周期函数 页面重新显示的时候执行
if (this.lastCity !== this.city) {
this.lastCity = this.city
this.getHomeInfo()
}
}
3.8 全局事件解绑
使用场景:控制景点详情页面Header渐隐渐现效果时需要监听scroll事件进行距离判断,改变opacity,在window上添加了scroll监听事件后主页也会受到影响,为此需要解绑全局事件。
实现方式:
activated () {
// 全局组件的解绑
window.addEventListener('scroll', this.handleScroll)
},
deactivated () {
// 页面即将被隐藏/替换
window.removeEventListener('scroll', this.handleScroll)
}