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)
}