阻塞和非阻塞
总结:同步、异步:只是对于热水壶。普通水壶代表同步;响水壶代表异步。虽然都能干活,但响水壶可以在自己完工之后,提示小杨水开了。
阻塞、非阻塞:仅仅代表小杨,立等的属于阻塞(1,3);干别的事了属于非阻塞(2,4)。
所以在上述同步阻塞、同步非阻塞、异步阻塞、异步非阻塞中,异步非阻塞情况下效率较高。
同步和异步的区分
同步含义是“一起”,异步含义是“一边……一边……”
在编程思想里面理解为:
同步:代码的书写顺序和代码的执行顺序一样
异步:代码的书写顺序和代码的执行顺序不一样
例如:setTimeout() btn.onclick = function(){}
console.log("start")
// 先获取button按钮 document文档--->html文档
var btn = document.getElementById("btn") // = 赋值运算符
// 给按钮注册一个点击事件 btn叫事件源 click叫事件名 function(){} 函数 当事件发生时,做什么
// 异步代码
btn.onclick = function(){
alert("登录成功了....")
}
console.log("end")
同步、异步执行顺序:(任务一和任务三是同步任务,任务二是异步任务)
总结:一种是同步任务,另一种是异步任务。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"异步任务队列"的任务,只有等主线程任务执行完毕,"异步任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行。
硅谷外卖
nodemon:自动重启项目工程
单页面应用
动手实现一个单页面应用
单页面应用 VS 多页面应用
vue单页面应用的原理
开发环境、生产环境
base64编码
小图片,减少请求的次数
图片Base64编码
stylus
stylus:CSS的预处理框架,即将stylus转换为css使用
stylus-loader:让webpack理解stylus
stylus和stylus-loader使用
eslint
解决点击响应延时 0.3s 问题
如何解决移动端Click事件300ms延迟的问题?
Vue移动端viewport设置和解决点击响应延时0.3s问题
Vue Router
Vue Router官方文档
使用 router-view 和 router-link 实现嵌套路由
vue之router-view组件的使用
$route和 $router
可以理解为$route用来获取路由信息的, $router是用来操作路由的
$router: 路由器对象, 包含一些操作路由的功能函数, 来实现编程式导航(跳转路由)
$route: 当前路由对象, 一些当前路由信息数据的容器, path/meta/query/params
插槽
插槽官方文档
彻底搞懂slot插槽,图文详解
一次性讲明白vue插槽slot
.gitignore
高阶函数
watch+$nextTick
this.$nextTick()官方介绍:将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。
VUE 关于理解$nextTick()的问题
MSite.vue:
watch: {
categorys (value) { // categorys数组中有数据了, 在异步更新界面之前执行
// 使用setTimeout可以实现效果, 但不是太好
/*setTimeout(() => {
// 创建一个Swiper实例对象, 来实现轮播
new Swiper('.swiper-container', {
loop: true, // 可以循环轮播
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
},
})
}, 100)*/
// 界面更新就立即创建Swiper对象
this.$nextTick(() => {// 一旦完成界面更新, 立即调用(此条语句要写在数据更新之后)
// 创建一个Swiper实例对象, 来实现轮播
new Swiper('.swiper-container', {
loop: true, // 可以循环轮播
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
},
})
new BScroll('.miste-content-wrapper', {
click: true
})
})
}
},
es6对象字面量语法
省略语法:
-
如果定义的属性名称与属性值变量同名,我们可以省略属性名称以及冒号
-
可以对属性名称书写表达式,通过[]动态的设置属性名称
之前可以通过[]来获取属性,现在我们可以通过[]来设置属性名
-
在对象中定义方法可以省略冒号以及function
js有些常量加个中括号
js的语法,在obj的属性名加[]表示属性名是变量。
let k = 'key';
let obj = {
[k]:0
}
// => {key:0}
mutations.js:
//中括号为计算属性名es6语法:作用是动态的写出当前函数名,方便后期维护
[RECEIVE_ADDRESS] (state, {address}) {
state.address = address
}
类似RECEIVE_ADDRESS = ‘receive_address’ 作用
使用vuex的时候,看到有些项目是在store文件夹下面新建一个mutation-types.js文件,内容像下面这样:
export const RECEIVE_ADDRESS = ‘receive_address’ // 接收地址
export const RECEIVE_USER_INFO = ‘receive_user_info’ // 接收用户信息
export const RESET_USER_INFO = ‘reset_user_info’ // 重置用户信息
//…
这样转一下大小写有什么作用?
答:单一职责,就是把 mutation 专门归类到一个文件中了,后面的小写理论上你可以随便写,只不过在一些调试工具显示的信息中会降低可读性,另一点就是,你 commit 的 mutation 的时候,也可以不使用 VERB_FOO 这种变量,直接写后面的小写的部分,但是这样造成的问题是,当你想要更改某一个 mutation 时,必须依赖于它的所有代码都改一遍,耦合性太高了(方便修改)。
VUEX中的dispatch()和commit()
- commit: 同步操作
存储 this.$store.commit(‘mutations方法名’,值)
取值 this.$store.state.mutations方法名 - dispatch: 异步操作
存储 this.$store.dispatch(‘action方法名’,值)
取值 this.$store.getters.action方法名
commit 和dispatch的区别在于commit是提交mutatious的同步操作,dispatch是分发actions的异步操作
伪类和伪元素
伪类:用于已有元素处于某种状态时为其添加对应的样式,这个状态是根据用户行为而动态变化的。
例如:当用户悬停在指定元素时,可以通过:hover来描述这个元素的状态,虽然它和一般css相似,可以为已有元素添加样式,但是它只有处于DOM树无法描述的状态下才能为元素添加样式,所以称为伪类。
伪元素:用于创建一些不在DOM树中的元素,并为其添加样式。例如,我们可以通过:before来在一个元素之前添加一些文本,并为这些文本添加样式,虽然用户可以看见这些文本,但是它实际上并不在DOM文档中。
CSS 巧用 `::before::after` 伪类
css伪元素:before和:after用法详解
props
正则
前端计划——JavaScript正则表达式快速入门
JS正则表达式入门,看这篇就够了
transform,transition,animation
css3中的变形(transform)、过渡(transtion)、动画(animation)
史上最简单的CSS动画,transform,transition和animation详解
axios
axios中文文档|axios中文网
【vue学习】axios
axios基本用法
短信验证码服务商
验证码(每次指定的src要不一样)
<img class="get_verification" src="http://localhost:4000/captcha" alt="captcha" @click="getCaptcha" ref="captcha">
getCaptcha () {
// 每次指定的src要不一样
this.$refs.captcha.src = 'http://localhost:4000/captcha?time='+Date.now()
}
保存用户信息:
state:
userInfo: {}, // 用户信息
mutation-types:
export const RECEIVE_USER_INFO = 'receive_user_info' // 接收用户信息
mutation:
[RECEIVE_USER_INFO] (state, {userInfo}) {
state.userInfo = userInfo
action:
// 同步记录用户信息
recordUser({commit}, userInfo) {
commit(RECEIVE_USER_INFO, {userInfo})
}
Login.vue:
// 将user保存到vuex的state
this.$store.dispatch('recordUser', user)
自动登录:
服务器端:
app.js:
app.use(session({
secret: '12345',
cookie: {maxAge: 1000*60*60*24 }, //设置maxAge是80000ms,即80s后session和相应的cookie失效过期
resave: false,
saveUninitialized: true,
}));
routes/index.js:
/*
根据session中的userid, 查询对应的user
*/
router.get('/userinfo', function (req, res) {
// 取出userid
const userid = req.session.userid
// 查询
UserModel.findOne({_id: userid}, _filter, function (err, user) {
// 如果没有, 返回错误提示
if (!user) {
// 清除浏览器保存的userid的cookie
delete req.session.userid
res.send({code: 1, msg: '请先登陆'})
} else {
// 如果有, 返回user
res.send({code: 0, data: user})
}
})
})
客户端:
api/index.js:
// 根据会话获取用户信息
export const reqUserInfo = () => ajax(BASE_URL+'/userinfo')
action:
// 异步获取用户信息
async getUserInfo({commit}) {
const result = await reqUserInfo()
if (result.code === 0) {
const userInfo = result.data
commit(RECEIVE_USER_INFO, {userInfo})
}
}
App.vue:
mounted () {
// this.$store.dispatch('getAddress')
this.getAddress()
this.getUserInfo()
},
methods: {
...mapActions(['getAddress', 'getUserInfo'])
},
退出登录
Profile.vue:
this.$store.dispatch('logout')
action:
// 异步登出
async logout({commit}) {
const result = await reqLogout()
if (result.code === 0) {
commit(RESET_USER_INFO)
}
mutation-types:
export const RESET_USER_INFO = 'reset_user_info' // 重置用户信息
mutation:
[RESET_USER_INFO] (state) {
state.userInfo = {}
}
使用mock模拟接口
Mockjs: 用来拦截 ajax 请求, 生成随机数据返回
http://mockjs.com/
https://github.com/nuysoft/Mock
下载:
npm install mockjs --save 4) 使用(mock/mockServer.js)
使用(mock/mockServer.js)
import Mock from 'mockjs'
import apiData from './data.json'
Mock.mock('/seller', {code:0, data:apiData.seller})
Mock.mock('/goods', {code:0, data:apiData.goods})
Mock.mock('/ratings', {code:0, data:apiData.ratings})
main.js:
import './mock/mockServer' // 加载mockServer即可
callback && callback()
有回调就执行,没有回调就不执行。
相当于
if(callback){
callback();
}
模板解析,一层表达式a,二层表达式a.b,三层表达式a.b.c。
当是三层表达式时,异步显示先显示初始数据,再显示带数据的数据。
【vue踩坑记录】1、“Error in render: “TypeError: Cannot read property ‘0’ of undefined””渲染错误问题
ShopHeader.vue:判断info.supports存在,才显示数据
v-if="info.supports"
position: absolute/relative实际使用
通过案例理解position:relative和position:absolute
css层的定位position、absolute、relative层叠加的五条叠加法则
加入transition 动画效果
ShopHeader.vue:
shop-brief-modal和fade-enter-active…同一级
<transition name="fade">
</transition>
&.fade-enter-active,&.fade-leave-active
transition opacity .5s
&.fade-enter,&.fade-leave-to
opacity 0
异步显示goods数据
- 触发异步action调用,把数据拿到vuex里
- 从vuex的state把数据拿到组件里来
- 在组件模板里展现数据
import {mapState} from 'vuex'
export default {
mounted() {
this.$store.dispatch('getShopGoods')
},
computed: {
...mapState(['goods']),
}
}
滑动效果分析
当前分类
当滑动右侧列表时,更新当前分类
点击某个分类项,右侧列表滑动到对应位置
类名:current标识当前分类
设计一个计算属性:currentIndex
根据哪些数据计算?
scrollY: 0, // 右侧滑动的Y轴坐标 (滑动过程时实时变化)
tops: [], // 所有右侧分类li的top组成的数组 (列表第一次显示后就不再变化)
1、在滑动过程中,实时收集scrollY
2、列表第一次显示后,收集tops
3、实现currentIndex的计算逻辑
:class="{current: index===currentIndex}"
引入better-scroll
npm install --save better-scroll
import BScroll from 'better-scroll'
ShopGoods.vue:
mounted() {
this.$store.dispatch('getShopGoods', () => {// 数据更新后执行
this.$nextTick(() => { // 列表数据更新显示后执行
this._initScroll()
this._initTops()
})
})
},
action.js:
// 异步获取商家商品列表
async getShopGoods({commit}, callback) {
const result = await reqShopGoods()
if (result.code === 0) {
const goods = result.data
commit(RECEIVE_GOODS, {goods})
// 数据更新了, 通知一下组件
callback && callback()
}
},
ShopGoods.vue:
// Array.prototype.slice.call(lis):将伪数组转换为真数组
Array.prototype.slice.call(lis).forEach(li => {
top += li.clientHeight
tops.push(top)
})
开发CartControl 组件
ShopGoods.vue:
import CartControl from '../../../components/CartControl/CartControl.vue'
components: {
CartControl
}
CartControl.vue:
props: {
food: Object
},
methods: {
updateFoodCount (isAdd) {
this.$store.dispatch('updateFoodCount', {isAdd, food: this.food})
}
}
要给food新增count属性
mutations.js:
[INCREMENT_FOOD_COUNT](state, {food}) {
console.log(state)
console.log(food)
if(!food.count) { // 第一次增加
// food.count = 1 // 新增属性(没有数据绑定)
/*
对象
属性名
属性值
*/
Vue.set(food, 'count', 1) // 让新增的属性也有数据绑定
// 将food添加到cartFoods中
state.cartFoods.push(food)
} else {
food.count++
}
}
问题: 更新状态数据, 对应的界面不变化
原因: 一般方法给一个已有绑定的对象中添加一个新的属性, 这个属性没有数据绑定
解决:
Vue.set(obj, ‘xxx’, value)才有数据绑定
this.$set(obj, ‘xxx’, value)才有数据绑定
开发 ShopCart 组件
ShopGoods.vue:
<Food :food="food" ref="food"/>
// 显示food组件 (在父组件中调用子组件对象的方法)
this.$refs.food.toggleShow()
Food.vue:
toggleShow () {
this.isShow = !this.isShow
}
解决事件冒泡:
CartControl.vue:
@click.stop="updateFoodCount(false)"
管理购物项数据:
- 设计成计算属性(相关数据发生改变就要完全重新计算)
- 使用vuex(效率更高)
购物车列表的滑动
ShopCart.vue:
if(this.isShow) {
this.$nextTick(() => {
// 实现BScroll的实例是一个单例,不然每点击一次创建一个BScroll对象,单击+商品数量不加1
if(!this.scroll) {
this.scroll = new BScroll('.list-content', {
click: true
})
} else {
this.scroll.refresh() // 让滚动条刷新一下: 重新统计内容的高度
}
})
}
异步显示列表ShopRatings组件
action.js:
// 异步获取商家商品列表
async getShopGoods({commit}, callback) {
const result = await reqShopGoods()
if (result.code === 0) {
const goods = result.data
commit(RECEIVE_GOODS, {goods})
// 数据更新了, 通知一下组件
callback && callback()
}
},
ShopRatings.vue:
mounted () {
this.$store.dispatch('getShopRatings', () => {
this.$nextTick(() => {
new BScroll(this.$refs.ratings, {
click: true
})
})
})
}
过滤数组
filterRatings () {
// 得到相关的数据
const {ratings, onlyShowText, selectType} = this
// 产生一个过滤新数组
return ratings.filter(rating => {
const {rateType, text} = rating
/*
条件1:
selectType: 0/1/2
rateType: 0/1
selectType===2 || selectType===rateType
条件2
onlyShowText: true/false
text: 有值/没值
!onlyShowText || text.length>0
*/
return (selectType===2 || selectType===rateType) && (!onlyShowText || text.length>0)
})
}
动态计算ul的宽度
ShopInfo.vue:
// ul的宽度没有被撑开,ul动态计算ul的宽度
const ul = this.$refs.picsUl
const liWidth = 120
const space = 6
const count = this.info.pics.length
ul.style.width = (liWidth + space) * count -space + 'px'
如果用css实现:
css3动态计算元素的高度及宽度
解决当前路由刷新异常的bug(/shop/info页面刷新,info初始值为空对象的问题)(watch+$nextTick)
(从/shop/goods切换过/shop/info可以,但是/shop/info刷新不行:shop.vue异步获取数据)
在当前路由刷新,分两步显示,先初始化,接着新来的数据进来后再更新
watch: {
info () {// 刷新流程--> 更新数据
this.$nextTick(() => {
this._initScroll()
})
}
}
实现没有搜索结果的提示显示
Search.vue:
watch: {
searchShops (value) {
if(!value.length) { // 没有数据
this.noSearchShops = true
} else {// 有数据
this.noSearchShops = false
}
}
},
@submit.prevent作用
Search.vue:
<form class="search_form" @submit.prevent="search">
.prevent 表示提交以后不刷新页面,prevent是preventDefault,阻止标签默认行为,有些标签有默认行为,例如a标签的跳转链接属性href等。
submit点击默认行为是提交表单,这里并不需要它提交,只需要执行search方法,故阻止为好。
缓存路由组件对象(浏览器端内存里把组件对象缓存起来)
<keep-alive>
<router-view/>
</keep-alive>
好处: 复用路由组件对象, 复用路由组件获取的后台数据
router-link相关属性:replace
<router-link to="/shop/goods" replace>点餐</router-link>
路由组件懒加载(需要的时候才去后台去请求组件代码)
src/router/index.js:
// import MSite from '../pages/MSite/MSite.vue'
// import Search from '../pages/Search/Search.vue'
// import Order from '../pages/Order/Order.vue'
// import Profile from '../pages/Profile/Profile.vue'
const MSite = () => import('../pages/MSite/MSite.vue')
const Search = () => import('../pages/Search/Search.vue')
const Order = () => import('../pages/Order/Order.vue')
const Profile = () => import('../pages/Profile/Profile.vue')
使用moment实现日期过滤器
npm install --save moment
gshop-client_final\src\fiters\index.js:
import Vue from 'vue'
import moment from 'moment'
// 自定义过滤器
Vue.filter('date-format', function (value, formatStr='YYYY-MM-DD HH:mm:ss') {
return moment(value).format(formatStr)
})
main.js:
import './fiters' // 加载过滤器
ShopRatings.vue:
<div class="time">{{rating.rateTime | date-format}}</div>
使用 date-fns 代替 moment:
npm install --save data-fns
gshop-client_final\src\fiters\index.js:
import Vue from 'vue'
// import moment from '[添加链接描述](https://uniapp.dcloud.io/)moment'
import format from 'date-fns/format'
// 自定义过滤器
Vue.filter('date-format', function (value, formatStr='YYYY-MM-DD HH:mm:ss') {
// return moment(value).format(formatStr)
return format(value, formatStr)
})