前端的三大框架
vue、react、angular
组件系统、数据驱动
v-model,数据发生变化,同步视图,视图发生变化,同步数据
在父向子传值的时候,如果改变父组件的值,子组件会跟着同步更新,反之不允许
文档
为什么自定义指令?
vue提供的系统指令满足不了我们的需求,那么我们就需要自定义指令
通过Vue.directive进行自定义指令的定义
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。定义的时候是一个方法,使用的时候当作属性使用
只要return后面的数据发生变化,该计算属性就会重新计算
计算属性具有缓存功能
watch侦听器如果监听的是一个对象,需要开启深度监听
watch:{
num:{
// 监听数据发生变化的处理函数
handler(newNum) {
console.log(newNum)
},
// 是否开启深度监听
deep: true
}
}
<div id="app">
<div>
{{ date | formatDate('-')}}
div>
<div>
{{ date | formatDate('/')}}
div>
div>
<script src="vue.js">script>
<script>
Vue.filter('formatDate',(data,line)=>{
console.log(data,line)
const y = data.getFullYear()
const m = (data.getMonth()+1).toString().padStart(2,0)
const d = data.getDate().toString().padStart(2,0)
return y+line+m+line+d
})
new Vue({
el: '#app',
data:{
date: new Date
}
})
script>
生命周期:是指一个对象从创建到运行到销毁的整个过程,被称为生命周期
生命周函数:在不同的生命周期阶段会自动执行对应的函数,而这些函数则被成为生命周期函数
// 创建阶段
beforeCreate() {
// 这个生命周函数,代表开始创建实例了
console.log('beforeCreate',this.num)
},
created () {
// 代表数据和方法已经初始化成功了,此处dom还没有挂载到页面上
console.log('created',this.num,this.$el)
},
beforeMount () {
// 挂在之前
console.log('beforeMount',this.$el)
},
mounted () {
// dom已经挂载了
console.log('mounted',this.$el)
},
// 运行更新阶段
beforeUpdate () {
// 数据更新,页面还没有同步
console.log('beforeUpdated',this.num,document.getElementById('app').innerHTML)
},
updated () {
// 数据更新,页面已经同步
console.log('updated',this.num,document.getElementById('app').innerHTML)
},
// 销毁阶段
beforeDestroy () {
// 销毁之前
console.log('beforeDestroy')
},
destroyed () {
// 已经销毁了
console.log('destroy')
}
通过Vue.component来创建一个全局组件,第一个参数是组件名字,第二个参数是组件的配置对象,可以通过template配置组件的结构,data定义数据等等
为什么组件内部的data是一个函数?
为了保证组件在每次使用的时候保证数据是当前组件唯一的,互不影响
在组件内部通过components来创建一个局部组件
全局组件和局部组件的区别
局部组件:只能在当前的父组件中使用
全局组件: 在任意地方使用
在组件内部通过directives来创建一个局部指令
全局指令和局部指令的区别
局部指令:只能在当前的组件中使用
全局指令: 在任意地方使用
在组件内部通过filters来创建一个局部过滤器
全局过滤器和局部过滤器的区别
局部过滤器:只能在当前的组件中使用
全局过滤器: 在任意地方使用
定义一个事件中心,或者是第三方
接收值的组件:通过该事件中心的$on来定义自定义事件的事件函数来接收值
eventBus.$on('getTab1',(data)=>{
console.log('接收tab1传递的值',data)
})
另一个兄弟组件怎么传:通过事件中心的$emit触发对应的 刀了 on的事件,并且把值传递过去
eventBus.$emit('getTab1',this.num)
Vue.component('my-sub1',{
template:'#my-sub1',
data() {
return {
money: 10000000
}
},
provide:{
money:1000
},
components:{
'sub-a':{
template:'子组件 ',
components:{
'sub-b':{
template:'子组件{{money}}',
inject:['money']
}
}
}
}
})
new Vue({
el: '#app'
})
默认插槽:
具名插槽
在组件标签中间通过定义slot的名字传递子节点
<my-banner>
<div slot="header">
头部
div>
<div slot="footer">
底部
div>
my-banner>
组件内部利用slot的name进行对应接收
<template id="banner">
<div>
<slot name="header">slot>
<slot name="footer">slot>
div>
template>
作用域插槽
在组件内部定义数据,将数据传递给插槽的结构
通过给slot动态绑定属性
<template id="my-li">
<ul>
<li v-for="item in arr">
<slot :row="item"></slot>
</li>
</ul>
</template>
插槽内部:通过slot-scope=“scope”来接收
<my-li>
<template slot-scope="scope">
<p>{{scope.row}}p>
template>
my-li>
<my-li>
<template slot-scope="scope">
<a href="04-侦听器.html">{{scope.row}}a>
template>
my-li>
利用Promise处理异步解决回调地狱的问题
Promise的all的方法
Promise的race的方法
面试题:
现在有三个接口地址,需要三个接口地址请求完事之后进行下一步的逻辑处理(不一定按顺序请求完成)
http://www.liulongbin.top:3005/api/getcomments/13?pageindex=1
http://www.liulongbin.top:3005/api/getnewslist
http://www.liulongbin.top:3005/api/getlunbo
// .then回调
axios.get('http://www.liulongbin.top:3005/api/getcomments/13?pageindex=1').then(res=>{
console.log(res)
axios.get('http://www.liulongbin.top:3005/api/getnewslist').then(res=>{
console.log(res)
axios.get('http://www.liulongbin.top:3005/api/getlunbo').then(res=>{
console.log(res)
})
})
})
// .then返回新的Promise继续调用.then
axios.get('http://www.liulongbin.top:3005/api/getcomments/13?pageindex=1').then(res=>{
return axios.get('http://www.liulongbin.top:3005/api/getnewslist')
}).then(res=>{
return axios.get('http://www.liulongbin.top:3005/api/getlunbo')
}).then(res=>{
console.log('三个请求完事')
})
// async await
const asyncHandle = async function(){
const res1 = await axios.get('http://www.liulongbin.top:3005/api/getcomments/13?pageindex=1')
const res2 = await axios.get('http://www.liulongbin.top:3005/api/getnewslist')
const res3 = await axios.get('http://www.liulongbin.top:3005/api/getlunbo')
console.log(res1,res2,res3)
}
asyncHandle()
// Promise all方法
const getComments = new Promise(()=>{
axios.get('http://www.liulongbin.top:3005/api/getcomments/13?pageindex=1')
})
Promise.all([
axios.get('http://www.liulongbin.top:3005/api/getcomments/13?pageindex=1'),
axios.get('http://www.liulongbin.top:3005/api/getnewslist'),
axios.get('http://www.liulongbin.top:3005/api/getlunbo')
]).then(res=>{
console.log(res)
})
请求拦截
axios.interceptors.request.use
响应拦截
axios.interceptors.response.use
什么是路由?
路由就是对应关系,组件和url地址,根据不同的地址显示不同的组件,路由也是实现spa(单页面应用程序)的主要核心,因为单页面应用程序,就是只有一个html,在这个html里面切换组件,根据url,例如地址为/home,在这个页面中就显示home组件
前端路由:url和组件
后端路由:根据不同的地址请求不同的接口
路由
params传参
在跳转的时候可以通过/home/10
路由规则:
new VueRouter({
routes: [
{
path: '/home/:id',
component: Home
}
]
})
组件内部怎么来接收参数
this.$route.params.id
query传参
在跳转的时候可以通过/home?id=10
组件内部怎么来接收参数
this.$route.query.id
路由history模式注意的问题
全局钩子:都会对所有的路由进行拦截
beforeEach:进入之前
afterEach:已经进入了
路由独享钩子:可以针对某一个路由进行拦截,这个需要写在路由规则里
{
path: '/',
name: 'home',
beforeEnter: (to,from,next)=>{
console.log('即将进入home')
},
component: Home
}
组件内的守卫:
针对组件进行拦截
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
next()
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
console.log('即将离开about')
if(confirm('当前表单没有提交?确定要离开首页?')){
next()
}
}
配置文档
利用vue.config.js关闭esLint
文档
注册组件
import Vue from 'vue'
import { Button, Select } from 'element-ui'
Vue.use(Button)
Vue.use(Select)
使用组件
<el-select v-model="value" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
el-option>
el-select>
复制数据
options: [{
value: '选项1',
label: '黄金糕'
}, {
value: '选项2',
label: '双皮奶'
}, {
value: '选项3',
label: '蚵仔煎'
}, {
value: '选项4',
label: '龙须面'
}, {
value: '选项5',
label: '北京烤鸭'
}],
value: ''
文档
在进入/离开的过渡中,会有 6 个 class 切换。
v-enter
:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。v-enter-active
:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。v-enter-to
: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter
被移除),在过渡/动画完成之后移除。v-leave
: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。v-leave-active
:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。v-leave-to
: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave
被删除),在过渡/动画完成之后移除。首先给需要添加动画的元素用transition包裹起来
利用类名实现动画
.v-enter,.v-leave-to{
transform: translate(200px,0)
}
.v-enter-active,.v-leave-active{
transition: transform 1s;
}
.v-enter-to,.v-leave{
transform: translate(0,0)
}
自定义动画类名
<transition name="box2">
<div class="box" v-show="flag">
div>
transition>
.box2-enter,.box2-leave-to{
opacity: 0;
}
.box2-enter-active,.box2-leave-active{
transition: opacity 1s;
}
.box2-enter-to,.box2-leave{
opacity: 1;
}
引入css动画库
通过enter-active-class、leave-active-class设置动画
<transition
enter-active-class="animated bounceIn"
leave-active-class="animated bounceOut"
>
<div class="box" v-show="flag">
div>
transition>
本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7dTAIgrU-1583927822393)(images/虚拟DOM.png)]
虚拟dom高效更新执行过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lv79JuEf-1583927822394)(images/diff算法.png)]
面试语术:
你知道虚拟dom吗?简单又谈一下?
虚拟dom可以实现高效更新,(后面如果自己能说一气说出来)
如何实现高效更新的?
利用新旧虚拟dom树进行对比,从而进行局部进行更新
如何进行新旧dom树进行对比?
利用diff算法,主要是tree diff树对比,component diff 组件对比,element diff 元素对比
加上一些其他话术
所以虚拟dom在前端中不管是vue、react等等都采用
什么是跨域?
在浏览器里面域名、端口、ip地址、协议,有任何一项不同,则跨域
A网站:http://localhost:8080/#/
B网站:http://localhost:3000/#/
处理跨域的方式?
JsonP(只能处理get请求)、cors(后端开启)、代理服务器
module.exports = {
devServer: {
host: 'localhost',
port: 8080,
proxy: {
'/api': {
target: 'http://localhost:3000',// 要跨域的域名
changeOrigin: true, // 是否开启跨域
},
'/get': {
target: 'http://localhost:3000',// 要跨域的域名
changeOrigin: true, // 是否开启跨域
}
}
}
}
某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。
在构建小程序分包项目时,构建会输出一个或多个分包。每个使用分包小程序必定含有一个主包。所谓的主包,即放置默认启动页面/TabBar 页面,以及一些所有分包都需用到公共资源/JS 脚本;而分包则是根据开发者的配置进行划分。
在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示。
目前小程序分包大小有以下限制:
对小程序进行分包,可以优化小程序首次启动的下载时间,以及在多团队共同开发时可以更好的解耦协作。
代码演示,分包加载之前
pages: [
'pages/tabs/home',
'pages/tabs/cates',
'pages/tabs/search',
'pages/tabs/cart',
'pages/tabs/my',
'pages/goods_list',
'pages/goods_detail/main',
'pages/order',
'pages/orderList'
]
抽离之后
pages: [
'pages/tabs/home',
'pages/tabs/cates',
'pages/tabs/search',
'pages/tabs/cart',
'pages/tabs/my',
'pages/order',
'pages/orderList'
],
subpackages: [
{
name: 'goods',
root: 'goods',
pages: [
'goods_list',
'goods_detail/main'
]
}
]
图片或者图标等资源文件使用线上资源
因为v-for的优先级大于v-if,所以会先根据所有数据生成结构,然后在根据,v-if进行按需渲染
<div id="app">
<ul>
<li v-for="item in arr" v-if="item%2===0">{{ item }}li>
ul>
div>
<script src="./vue.js">script>
<script>
new Vue({
el: '#app',
data: {
arr: [
1,2,3,4
]
}
})
script>
我们可以采用计算属性来避免此问题,这样的话,先通过计算属性,计算出要渲染的数据,然后直接循环计算属性即可
<div id="app">
<ul>
<li v-for="item in newArr">{{ item }}li>
ul>
div>
<script src="./vue.js">script>
<script>
new Vue({
el: '#app',
data: {
arr: [
1,2,3,4
]
},
computed: {
newArr() {
return this.arr.filter(item=>{
return item%2===0
})
}
}
})
script>
定义一个button组件,在index.js中引入并注册
在components中创建一个index.js
import sgButton from './button.vue'
function install (Vue) {
Vue.component(sgButton.name, sgButton)
}
export default {
install
}
在main.js中导入该js文件,当调用Vue.use方法,会默认调用内部的install方法
面试题
你有封装过插件吗? Vue.use的原理是什么?
当调用Vue.use方法,会默认调用内部的install方法,install这个方法默认的第一个形参就是Vue,这样的话我们就可以通过Vue注册一些全局组件,给Vue扩展一下方法。
vue中实现语言切换的方式如何实现的
1.NPM 项目安装
cnpm i vue-i18n
2.使用方法
/* 国际化使用规则 */
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
const messages = {
en: {
message: {
hello: 'world hello'
}
},
zh: {
message: {
hello: '世界'
}
}
}
const i18n = new VueI18n({
locale: 'zh',
messages
})
export default {
data () {
return {
hello: this.$t('message.hello')
}
},
i18n
}
3.页面数据使用
{{ $t("message.hello") }}
4.案例练习
<div id="app">
<button>切换语言button>
<ul>
<li>首页li>
<li>新闻li>
<li>关于li>
<li>概况li>
ul>
div>
ul{
list-style: none;
li{
width: 20%;
height: 70px;
line-height: 70px;
background: green;
color: #fff;
float: left;
margin-left: 2%;
text-align: center;
line-height: 70px;
}
}
zh.js
export default {
nav: ['首页', '新闻', '概况', '关于']
}
en.js
export default {
nav: ['home', 'news', 'gk', 'about']
}
import zh from './i18n/zh.js'
import en from './i18n/en.js'
Vue.use(VueI18n)
const i18n = new VueI18n({
locale: 'en',
messages: {
zh,
en
}
})
<template>
<div id="app">
<button @click="changeLang">切换语言button>
<ul>
<li v-for="(item, index) in $t('nav')" :key="index">{{ item }}li>
ul>
div>
template>
<script>
export default {
methods: {
changeLang () {
this.$i18n.locale = this.$i18n.locale === 'en' ? 'zh' : 'en'
}
}
}
script>
禁止重新加载