枯藤老树昏鸦,小桥流水人家,古道西风瘦马,撸码人在天涯
博客主要分以下几大类
- vite原理与实践记录
- .babelrc与babel.config.js配置文件的区别
- 借助rollup构建npm包最佳实践
- lerna管理Monorepo项目实践
- 了解ESLint各个parser之间的关系
- Tapable源码浅析
随笔
Home Page: https://blog.willson-wang.com/
License: MIT License
枯藤老树昏鸦,小桥流水人家,古道西风瘦马,撸码人在天涯
博客主要分以下几大类
列表滚动的时候,右侧的导航标识也需要跟着变化;
点击or滑动右边导航标识的时候,左侧的列表可以滚动到对应的位置;
实现思路,监听列表的滚动事件,事实获取滚动的scrollY值,根据scrollY值,来计算当前滚动到的index索引;计算方式如下,首先通过getBoundingClientRect()获取每个滚动小块的height高度,并保存到一个数组中;然后判断,如果scrollY小于height[0],那么index = 0 or 负数;如果scrollY > height [i] && scrollY < height[i + 1],那么index = i + 1(因为0没有计算在内); 如果scrollY 不符合上面两个条件则等于height.length - 2,这个2自己根据情况定,目的是防止导航标识跟随到最底部;
scrollY(newY) {
const listHeight = this.listHeight;
if (-newY < this.menuHeaderElHeight) {
this.currentIndex = -1;
return;
}
newY = newY + this.menuHeaderElHeight;
if (-newY < listHeight[0]) {
this.currentIndex = 0;
return;
}
for (let i = 0; i < listHeight.length; i++) {
let height1 = listHeight[i];
let height2 = listHeight[i + 1];
if (-newY >=height1 && -newY < height2) {
// 如果修改结构,需要修改currentIndex的计算方式
this.currentIndex = i + 1;
this.diff = height2 + newY;
return;
}
}
this.currentIndex = listHeight.length - 2;
}
实现思路,绑定touchstart及touchmove事件,通过当前点击的索引来设置当前需要滚动到的元素,如果当前的索引小于0,则置0 or 其它;如果大于height.length - 2则都置为height.length - 2;而move得时候是通过计算(move时获取的touches[0].pageY - 点击时的touches[0].pageY)/ 一个导航标识的高度 | 0;
_scrollTo(index, flag) {
if (index < 0) {
this.$refs.scroll.scrollTo(0, 0);
this.scrollY = 0;
return;
} else if (index > this.listHeight.length - 2) {
index = this.listHeight.length - 2;
}
console.log(index);
this.$refs.scroll.scrollToElement(this.groudList[+index], 0);
this.scrollY = this.$refs.scroll.scroll.y;
},
<template>
<div class="city-map relative">
<div class="city-map__header">
<span class="city-map__back" @click="$router.back()"><i class="brokericon icon-arrow-left"></i></span>
<div>
<span><i class="brokericon icon-search"></i></span>
<input type="text" placeholder="请输入城市名称" v-model="searchCity">
</div>
</div>
<div class="city-map__content" v-show="!searchResult.length && !searchCity && list.menu.length">
<vue-better-scroll class="scroller-container"
:class="designTime && 'design'"
ref="scroll"
:probeType="3"
:listenScroll="listenScroll"
:pullDownRefresh="pullDownRefreshObj"
:pullUpLoad="pullUpLoadObj"
@pullingDown="$emit('pullingDown')"
@scroll="scrollHandler"
@pullingUp="$emit('pullingUp')">
<div class="city-map__menu">
<slot name="header"></slot>
<ul class="city-map__group" v-for="(group, index) in list.menu" :key="index">
<li class="city-map__group__item city-map__group__item--scroll">
<h2 class="city-map__group__anchor">{{group.name}}</h2>
<ul class="city-map__group__list">
<li v-for="(item, index) in group.items" :key="index" @click="click(item)">
<div>{{item.city_name}}</div>
</li>
</ul>
</li>
</ul>
</div>
</vue-better-scroll>
<ul class="city-map__nav" @touchstart="onTouchstartHandler" @touchmove.stop.prevent="onTouchmoveHandler" @touchend="onTouchendHandler">
<li :data-index="-1" :class="fixedCurrentIndex === -1 ? 'activted' : ''" v-if="this.$slots['header']">#</li>
<li v-for="(item, index) in list.nav" :key="index" :data-index="index" :class="fixedCurrentIndex === index ? 'activted' : ''">
{{item}}
</li>
</ul>
<div ref="fixed"
v-show="isFixedTitle && fixedTitle"
v-html="fixedTitle"
class="city-map__group__anchor--fixed city-map__group__anchor">
</div>
</div>
<div class="city-map__search" v-show="searchResult.length || searchCity">
<ul v-if="searchResult.length">
<li v-for="(city, index) in searchResult" class="city-map__search__item" :key="index" @click="click(city, true)">{{city.city_name}}</li>
</ul>
<div v-else class="no-data">
<span class="no-data-img" />
<p>暂无数据哦~</p>
</div>
</div>
<div v-if="!list.menu.length" class="loading">
<inline-loading></inline-loading>加载中
</div>
</div>
</template>
<script>
import VueBetterScroll from "vue2-better-scroll";
import { mapState, mapActions } from "vuex";
import { InlineLoading } from 'vux';
const cities = require('./cities.json').data.cities;
export default {
name: 'cityMap',
components: {
VueBetterScroll,
InlineLoading
},
props: {
city: Object,
isFixedTitle: {
type: Boolean,
default: false
}
},
data() {
return {
pullDownRefreshObj: false,
pullUpLoadObj: false,
listenScroll: true,
currentIndex: -1,
diff: -1,
scrollY: -1,
subTitleEl: null,
searchResult: [],
searchCity: '',
touchmoving: false,
fixedCurrentIndex: -1,
touchIndex: -1,
locCity: {},
cities: {},
}
},
computed: {
designTime() {
return this.isDesignTime
},
fixedTitle() {
return this.list.menu[this.currentIndex] ? this.list.menu[this.currentIndex].name : ''
},
list() {
let nav = [];
let menu = [];
const cities = this.cities && this.cities.nested && this.cities.nested.by_pinyin || {};
for (let prop in cities) {
if (cities.hasOwnProperty(prop)) {
nav.push(prop);
menu.push({
name: prop,
items: cities[prop]
})
}
}
return {nav, menu}
},
},
methods: {
getRect(el) {
if (!el) return;
return el.getBoundingClientRect && el.getBoundingClientRect()
},
scrollHandler(e) {
this.scrollY = e.y;
},
_calculateHeight() {
// 这里是获取了包含固定项的,如果换个类名就需要同步,修改下scrollY内的currentIndex的计算方式
this.groudList = this.$el.getElementsByClassName('city-map__group__item--scroll');
this.subTitleEl = this.$el.getElementsByClassName('city-map__group__anchor--fixed')[0];
this.subTitleHeight = this.subTitleEl ? this.getRect(this.subTitleEl).height : 0;
this.navItemEles = this.$el.getElementsByClassName('city-map__nav')[0].children;
this.navItemEl = this.navItemEles[0];
this.navItemHeight = this.navItemEl ? this.getRect(this.navItemEl).height : 0;
this.menuHeaderElHeight = (this.$slots['header'] && this.getRect(this.$slots['header'][0].elm).height) || 0;
this.listHeight = [];
if (!this.groudList) return;
let height = 0;
for (let i = 0; i < this.groudList.length; i++) {
let item = this.groudList[i];
height += item.clientHeight;
this.listHeight.push(height);
}
console.log(this.listHeight);
},
showToast(ele) {
this.$vux.toast.show({
text: ele.innerHTML,
position: 'middle',
type: 'text',
width: '12%'
})
},
onTouchstartHandler(e) {
const firstTouch = e.touches[0]
const target = e.targetTouches[0].target.nodeName === 'LI' && e.targetTouches[0].target;
if (!target) return;
let navIndex = target.dataset && target.dataset.index;
this.touch.y1 = firstTouch.pageY;
this.touch.navIndex = navIndex;
this.touchIndex = navIndex;
this.showToast(target);
this._scrollTo(navIndex, +navIndex < 0);
},
onTouchmoveHandler(e) {
const firstTouch = e.touches[0];
this.touch.y2 = firstTouch.pageY;
let delTa = (this.touch.y2 - this.touch.y1) / this.navItemHeight | 0;
let navIndex = parseInt(this.touch.navIndex) + delTa;
this.touchIndex = navIndex;
this.touchmoving = true;
this._scrollTo(navIndex, +navIndex < 0);
},
onTouchendHandler() {
this.touchmoving = false;
},
_scrollTo(index, flag) {
if (index < 0) {
this.$refs.scroll.scrollTo(0, 0);
this.scrollY = 0;
return;
} else if (index > this.listHeight.length - 2) {
index = this.listHeight.length - 2;
}
console.log(index);
this.$refs.scroll.scrollToElement(this.groudList[+index], 0);
this.scrollY = this.$refs.scroll.scroll.y;
},
click(city, flag) {
this.$emit('checkAddr', city);
}
},
watch: {
currentIndex(val) {
this.fixedCurrentIndex = val;
this.touchmoving && this.showToast(this.navItemEles[val < 0 ? 0 : val]);
},
scrollY(newY) {
const listHeight = this.listHeight;
if (-newY < this.menuHeaderElHeight) {
this.currentIndex = -1;
return;
}
newY = newY + this.menuHeaderElHeight;
if (-newY < listHeight[0]) {
this.currentIndex = 0;
return;
}
for (let i = 0; i < listHeight.length; i++) {
let height1 = listHeight[i];
let height2 = listHeight[i + 1];
if (-newY >=height1 && -newY < height2) {
// 如果修改结构,需要修改currentIndex的计算方式
this.currentIndex = i + 1;
this.diff = height2 + newY;
return;
}
}
this.currentIndex = listHeight.length - 2;
},
diff(newVal) {
this.subTitleHeight = this.subTitleHeight ? this.subTitleHeight : this.getRect(this.subTitleEl).height;
let fixedTop = (newVal > 0 && newVal < this.subTitleHeight) ? newVal - this.subTitleHeight : 0;
if (this.fixedTop === fixedTop) return;
this.fixedTop = fixedTop;
this.$refs.fixed.style['transform'] = `translate3d(0,${fixedTop}px,0)`;
},
searchCity(val) {
if (!val) {
this.searchResult = [];
this.$nextTick(() => {
this._scrollTo(this.currentIndex, true);
})
} else {
this.searchResult = [];
this.list.menu.forEach((item) => {
item.items.forEach((city) => {
if (city.city_name.indexOf(val) > -1) {
this.searchResult.push(city);
}
})
})
// this.searchResult = [...new Set(this.searchResult)];
}
},
cities(val) {
this.$nextTick(() => {
this._calculateHeight();
})
},
touchIndex(val) {
if (val && +val > this.currentIndex) {
this.fixedCurrentIndex = +val;
} else {
this.fixedCurrentIndex = this.currentIndex;
}
}
},
activated() {
this.groudList = [];
this.listHeight = [];
this.subTitleHeight = 0;
this.touch = {};
this.searchCity = '';
this.cities = cities;
this.$nextTick(() => {
this._calculateHeight();
})
}
}
</script>
<style lang="less" scoped>
@import '~vux/src/styles/1px.less';
ul, li {
list-style: none;
padding: 0;
margin: 0;
}
.city-map {
display: flex;
height: 100%;
flex-direction: column;
&__header {
flex: 0 0 45px;
display: flex;
align-items: center;
border-bottom: 1px solid #ddd;
>span {
flex: 0 0 56px;
height: 30px;
text-align: center;
line-height: 30px;
i {
color: #939899;
font-size: 22px;
}
}
>div {
flex: 0 0 274px;
align-items: center;
position: relative;
height: 30px;
span {
position: absolute;
top: 50%;
left: 12px;
transform: translate(0, -50%);
i {
color: #aeaeae;
font-size: 16px;
}
}
input {
color: #000;
background-color: #e3e6e6;
font-size: 14px;
width: 100%;
border: 0;
outline: none;
border-radius: 3px;
text-indent: 34px;
height: 30px;
line-height: 30px;
}
}
}
&__content {
flex: 1;
position: relative;
overflow: hidden;
.scroller-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
bottom: 0;
overflow: hidden;
}
}
&__group {
&__item {
display: flex;
flex-direction: column;
justify-content: center;
}
&__anchor {
flex: 0 0 41px;
width: 100%;
line-height: 41px;
border-bottom: 1px solid #f5f5f5;
background-color: #f5f5f5;
font-size: 12px;
color: #999;
padding-left: 22px;
font-weight: normal;
}
&__list {
display: flex;
flex-direction: column;
justify-content: center;
>li {
flex: 0 0 50px;
>div {
height: 50px;
line-height: 50px;
border-bottom: 1px solid #e2e2e2;
font-size: 16px;
color: #000;
padding-left: 22px;
background-color: #fff;
}
&:last-of-type div {
border-bottom: 1px solid #f5f5f5;
}
}
}
}
&__nav {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 40px;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
li {
flex: 0 0 24px;
width: 100%;
font-size: 14px;
line-height: 24px;
color: #000;
font-weight: 700;
}
.activted {
color: orange;
}
}
&__search {
flex: 1;
position: relative;
overflow: hidden;
&__item {
height: 50px;
line-height: 50px;
border-bottom: 1px solid #ddd;
font-size: 16px;
color: #000;
padding-left: 22px;
background-color: #fff;
}
}
.no-data{
margin-top: 90px;
font-size: 12px;
color: #999;
text-align: center;
.no-data-img{
background: url("./img/noData.jpg") no-repeat center;
display: inline-block;
background-size: 100% 100%;
width: 80px;
height: 70px;
}
}
.loading {
margin-top: 90px;
text-align: center;
}
}
</style>
<style lang="less" scoped>
.relative {
position: relative;
}
html.ios,
html.iphone {
&.is-app,
&[data-runtime="app"] {
.relative {
position: relative;
padding-top: 20px
}
}
}
@iphonex: ~"only screen and (device-width: 375PX) and (device-height: 812PX) and (-webkit-device-pixel-ratio: 3)";
@media @iphonex {
html.ios,
html.iphone {
&.is-app,
&[data-runtime="app"] {
.relative{
padding-top: 44px;
}
}
}
}
</style>
Promise异步编程的一种解决方案,被纳入ES6的标准,两个特点,Promise对象的状态不受外界的影响,即只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态;一旦状态改变,就不会再变,任何时候都可以得到这个结果;
var timeout = function (time = 2000) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('222');
}, time)
})
}
timeout().then((res) => {
console.log(res);
})
// p1是一个 Promise,3 秒之后变为rejected。p2的状态在 1 秒之后改变,resolve方法返回的是p1。
// 由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的
// then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('fail'))
}, 3000)
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(p1);
}, 1000)
})
p2.then((res) => {
console.log('res', res);
}).catch((error) => {
console.log('error', error);
});
// then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写
// 法,即then方法后面再调用另一个then方法
//采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是
// 一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才
// 会被调用。
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p3');
}, 2000)
})
const p4 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p4');
}, 4100)
})
p3.then((res) => {
console.log('res p3', res);
// 注意这里可以return 任何值,可以是之前固定了状态的promise3,也可以是一个新的promise,也可以是
// 一个常量,都会在后一个then中的回调函数中拿到,但是需要注意的是,如果返回的是一个新的
// promise,则需要等待promise内的resolve or reject执行玩之后才能够执行then内的回调
return p4;
}).then((res) => {
console.log('res p3 then2', res); // undefined,因为前一个then没有return值
})
// 如果 Promise 状态已经变成resolved,再抛出错误是无效的。同理如果状态变成reject,在resolve就无效
// 了,另外需要注意的是,promise内抛出错误与reject抛出错误是等效的
// Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下个
// catch语句捕获;跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,
// Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。
const p5 = new Promise((resolve, reject) => {
resolve('p5');
throw new Error('p5 faile');
});
p5.then((res) => {
console.log('res p5', res);
}).catch((error) => {
console.log('p5 error', error);
});
//Promise.prototype.finally() es8引入的语法,要考虑兼容性问题
const p6 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p6');
}, 1000)
})
const p7 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p7')
}, 2000)
})
const p8 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p8')
}, 4000)
})
// Promise.all方法传入的是一个数组promise,如果本身不是promise则会使用promise.resolve()被转化为
// promise对象,promise.all方法的返回值是包含每个promise结果的返回值数组,只有每一个promise都
// resolve才能够成功,如果有一个reject or error则会执行promise.all的catch方法;
// 如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()
// 的catch方法
// promise.all的方法常用于同时请求几个异步接口,但是每个异步接口之间又不互相依赖
const p9 = Promise.all([p6, p7, p8])
p9.then((res) => {
console.log('res p9 all', res);
}).catch((error) => {
console.log('error p9 all', error);
})
// pormise.race方法的使用与promise.all方=方法是一样的,区别市race方法是只要某个promise的状态先该
// 变了,那么race方法返回的promise的状态也会进行改变并与率先改变的primise状态保持一致
// promise.race方法常用于设置某个请求是否超时
const p10 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p10');
}, 1000)
})
const p11 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p11')
}, 500)
})
const p12 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p12')
}, 4000)
})
const p13 = Promise.race([p10, p11, p12])
p13.then((res) => {
console.log('res p12 race', res);
}).catch((error) => {
console.log('error p12 race', error);
})
const p14 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p14')
}, 2000)
})
const p15 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('请求超时')
}, 1500)
})
const p16 = Promise.race([p14, p15])
p16.then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
})
// promise.resolve()方法,将现有对象or原始值转化为promise对象or直接生成一个状态值为resolved的
// promise对象
// Promise.resolve('aaa') === new Promise((resolve) => {resolve('aaa')})
// 需要注意的是根据传入的参数来返回不同的值
// 1.传入的参数是promise对象,则promise.resolve方法不做任何修改原样返回
const p17 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p17')
}, 2000)
})
const p18 = Promise.resolve(p17);
p18.then((res) => {
console.log(res);
})
// 2. 参数是一个thenable对象,Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行
// thenable对象的then方法
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p19 = Promise.resolve(thenable);
p19.then(function(value) {
console.log(value);
});
// 3. 参数不是具有then方法的对象,或根本就不是对象,则Promise.resolve方法返回一个新的 Promise 对
//象,状态为resolved。由于对象or字符串不属于异步操作(判断方法是字符串对象不具有 then 方法),返
// 回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve方法的参
// 数,会同时传给回调函数。
const p20 = Promise.resolve({
name: 'jack',
age: 20
})
p20.then((res) => {
console.log(res);
})
// 4. 不带有任何参数,Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise
// 对象,立即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件
// 循环”的开始时
const p21 = Promise.resolve();
p21.then((res) => {
console.log(res, 'p21');
})
// promise.reject()方法返回一个新的promise对象,且该实例的状态为rejected,注意,Promise.reject()方法
// 的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致
const info = {
name: 'jack',
age: 20
}
const p22 = Promise.reject(info);
p22.then((res) => {
console.log(res);
}).catch((e) => {
console.log(e === info, e); // true
})
// promise.try()
// 这种写法有一个缺点,就是如果f是同步函数,那么它会在本轮事件循环的末尾执行。
const f1 = () => console.log('now');
const p23 = Promise.resolve().then(f1);
console.log('next');
// 那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API 呢?回答是
// 可以的,并且还有两种写法。第一种写法是用async函数来写,async () => f()会吃掉f()抛出的错误。所以,
// 如果想捕获错误,要使用promise.catch方法。另一种写法是使用new promise
const f2 = () => console.log('now2');
(async () => f2())();
console.log('next2');
const f3 = () => console.log('now3');
(
() => new Promise((resolve) => {
resolve(f3())
})
)()
console.log('next3');
// 使用promise.try()来处理函数f不管函数是同步函数还是异步操作,想用 Promise 来处理它。因为这样就可
// 以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误
// const f4 = () => console.log('now4');
// Promise.try(f4).then((res) => {
// console.log('try', res);
// }).catch((err) => {
// console.log('try err', err);
// })
// console.log('next4');
Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二
// 是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”;主要用来解决
// 以同步的编码方式来实现异步的过程
function *hello () {
yield 'i';
yield 'love'
yield 'you'
return 'ending'
}
// Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用
// Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象;下
// 一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部
// 指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。
// 换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
// next方法返回一个对象,它的value属性就是当前yield表达式的值hello,done属性的值false or true,表
// 示遍历还没有结束和遍历结束
// 总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调
// 用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状
// 态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。
const h = hello(); // 返回的是一个指向内部状态的指针对象
console.log(h);
console.log(h.next()); // {value: "i", done: false}
console.log(h.next()); // {value: "love", done: false}
console.log(h.next()); // {value: "you", done: false}
console.log(h.next()); // {value: "ending", done: true}
console.log(h.next()); // {value: undefined, done: true}
const arr = [1, [[2, 3], 4], [5, 6]];
function *flat(a) {
const length = a.length;
for (let i = 0; i < length; i++) {
const item = a[i];
if (typeof item !== 'number') {
yield *flat(item);
}else {
yield item;
}
}
}
// for...of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法。
for (let f of flat(arr)) {
console.log(f);
}
// generator函数处理异步请求
function request(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data = {
name: 'jack',
age: 18
})
}, 3000)
})
}
function *gen() {
var result = yield request('http://xxx.com');
console.log('yield之后');
}
const g = gen();
const result = g.next();
console.log(result);
result.value.then((res) => {
console.log('res', res);
}).then(() => {
g.next(); // 需要在第一次调用next方法返回promise之后,在promise成功之后在继续调用next方法,可
// 以继续执行下一个yiled之前的代码
}).catch((err) => {
console.log('err', err);
})
async 函数是什么?一句话,它就是 Generator 函数的语法糖;ES2017标准引入的;使用的时候需要注意,async函数返回的是一个promise;await后面不管跟什么表达式都会被转化为promise对象
function request(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(url);
}, 2000)
})
}
const gen = function *() {
const f1 = yield request('http://xxxx.com/api/getCode');
const f2 = yield request('http://xxxx.com/api/getInfo');
console.log(f1);
console.log(f2);
}
const g = gen();
const r1 = g.next()
r1.value.then((res) => {
console.log('res', res);
var r2 = g.next(res);
r2.value.then((res2) => {
console.log('res2', res2);
g.next(res2);
})
})
// 改成async的书写方式
async function aGen () {
const f1 = await request('http://xxxx.com/api/getCode');
const f2 = await request('http://xxxx.com/api/getInfo');
console.log('aGen', f1);
console.log('aGen', f2);
return {name: 'jack'}
}
aGen().then((data) => {
console.log(data);
});
// 比较async与gennerator
// 书写上async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已
// 1. Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async
// 函数的执行,与普通函数一模一样,只要一行aGen(),完全不像 Generator 函数,需要调用next方法,或
// 者用co模块,才能真正执行,得到最后结果。
// 2. 更好的语义,async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表
// 示紧跟在后面的表达式需要等待结果。
// 3. 更广的适用性,co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命
// 令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)
// 4. 返回值是 Promise,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令
// 就是内部then命令的语法糖
// 基本用法,获取有依赖关系的异步操作,因为async函数返回一个 Promise 对象,可以使用then方法添加
// 回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面
// 的语句
function getCode() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('84898');
}, 1000)
})
}
function getInfo(code) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
code,
name: 'lose'
})
}, 3000)
})
}
async function getUserInfo () {
const code = await getCode();
const result = await getInfo(code);
console.log('result', result);
return result;
}
getUserInfo().then((res) => {
console.log('getUserInfo res', res);
}).catch((err) => {
console.log('getUserInfo err', err);
})
// async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状
// 态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行
// then方法指定的回调函数。
async function f1() {
return await 123; // await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。
}
f1().then((res) => {
console.log('res', res);
})
// 只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。
async function f2() {
await Promise.reject('error f2');
console.log('是否执行'); // 不执行了
await Promise.resolve('succ f2');
}
f2().then((res) => {
console.log('res f2', res);
}).catch((err) => {
console.log('res error', err);
})
// 注意await后面的异步操作,是否有依赖关系,如果有则依次执行,没有则并发执行;
// 1. 有依赖关系,需要依次执行,比较耗时
function getVarilCode () {
return new Promise((resolve) => {
console.log('先获取手机验证码');
setTimeout(() => {
resolve('4678');
}, 500)
})
}
function getList () {
return new Promise((resolve) => {
console.log('获取用户信息列表');
setTimeout(() => {
resolve([
{
book: 'js',
id: 1
},
{
book: 'php',
id: 2
}
]);
}, 1000)
})
}
async function login(code) {
const list = await getList(); // 注意这个await只能放在promise外面,不能放里面,因为awite只能放
// async关键字的函数内
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: 'kils',
age: 20,
sex: '男',
list,
});
}, 2000)
})
}
async function getAutoUserInfo () {
const newDate = +new Date();
const code = await getVarilCode(); // await表达式返回的是对应prmise对象,resolve or reject的值,因
// 为await表达式会把后面不是promise的对象转化为promise对象, 所以async函数返回的是一个promise对
// 象,而await表达式返回的是一个具体的值
const result = await login(code);
const test = await '123';
const oldDate = +new Date();
console.log('result', result, oldDate - newDate, test);
return result;
}
getAutoUserInfo().then((res) => {
console.log('getUserInfo res', res);
}).catch((err) => {
console.log('getUserInfo err', err);
})
// 无依赖关系,可以并行执行,然后等待每个并行执行异步操作的结果
function f3() {
return new Promise((resolve) => {
console.log('并行执行 f3');
setTimeout(() => {
resolve('f3');
}, 2000)
})
}
function f4() {
return new Promise((resolve) => {
console.log('并行执行 f4');
setTimeout(() => {
resolve('f4');
}, 2000)
})
}
async function all() {
const newDate = +new Date();
const r1 = f3();
const r2 = f4();
const result1 = await r1;
const result2 = await r2;
console.log('all', +new Date() - newDate, result1, result2);
}
all();
// async函数实现的原因
function spawn(genF) {
return new Promise((resolve, reject) => {
// 获取generator函数
const gen = genF();
function step(nextF) {
let next;
try{
next = nextF();
}catch(e){
return reject(e);
}
// 如果结束,则返回最后一次next.value
if(next.done) {
return resolve(next.value);
}
// 包装一下next.value,便于promise方式调用
Promise.resolve(next.value).then((val) => {
// 如果done不是true,则继续调用next方法
step(() => {
return gen.next(val);
});
}, (e) => {
// 如果报错则,reject错误
step(() => {
return gen.throw(e);
});
})
}
// 执行第一次next方法
step(() => {
return gen.next(undefined);
});
})
}
function f5() {
return new Promise((resolve) => {
console.log('并行执行 f5');
setTimeout(() => {
resolve('f5');
}, 2000)
})
}
function f6() {
return new Promise((resolve) => {
console.log('并行执行 f6');
setTimeout(() => {
resolve('f6');
}, 2000)
})
}
function asyncCopy(args) {
// 返回spawn函数调用的结果
return spawn(function *() {
const newDate = +new Date();
const r1 = f5();
const r2 = f6();
const result1 = yield r1;
const result2 = yield r2;
console.log('asyncCopy all', +new Date() - newDate, result1, result2);
})
}
asyncCopy();
按顺序调用异步操作,分别用Promise/Generator/Async来实现
const animations = [toTop, toRight, toBottom];
function toTop(ele) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('toTop');
}, 1000)
})
}
function toRight(ele) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('toRight');
}, 2000)
})
}
function toBottom(ele) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('toBottom');
}, 2000)
})
}
function animationsPromise (ele, animations) {
let ret = null;
let p = Promise.resolve();
for(let anim of animations) {
// 利用then函数必须要等道promise的状态发生改变之后才会继续后面的then操作
p = p.then((val) => {
console.log('val', val);
ret = val;
return anim(ele);
})
}
console.log('p', p);
return p.catch((e) => {}).then((res) => {
console.log('last res', res);
return res ? res : ret;
})
}
// animationsPromise(null, animations).then((res) => {
// console.log('res', res);
// }).catch((err) => {
// console.log('err', err);
// });
function animationsGenerator(elem, animations) {
function spawn(genF) {
return new Promise((resolve, reject) => {
// 获取generator函数
const gen = genF();
function step(nextF) {
let next;
try{
next = nextF();
}catch(e){
return reject(e);
}
// 如果结束,则返回最后一次next.value
if(next.done) {
return resolve(next.value);
}
// 包装一下next.value,便于promise方式调用
Promise.resolve(next.value).then((val) => {
// 如果done不是true,则继续调用next方法
step(() => {
return gen.next(val);
});
}, (e) => {
// 如果报错则,reject错误
step(() => {
return gen.throw(e);
});
})
}
// 执行第一次next方法
step(() => {
return gen.next(undefined);
});
})
}
return spawn(function *() {
let ret = null;
try{
for (let prop of animations) {
ret = yield prop(elem)
}
}catch(e) {
}
return ret;
})
}
animationsGenerator(null, animations).then((res) => {
console.log('animationsGenerator res', res);
}).catch((err) => {
console.log('animationsGenerator err', err);
});
// 依次执行,并返回最后一次的结果
async function animationsAsync(elem, animations) {
let ret = null;
try {
for(let anim of animations) {
ret = await anim(elem);
}
} catch(e) {
/* 忽略错误,继续执行 */
console.log('e', e);
}
return ret;
}
// animationsAsync(null, animations).then((res) => {
// console.log('animationsAsync res', res);
// }).catch((err) => {
// console.log('animationsAsync err', err);
// });
顺序调用与并发调用的区别
function getUrl(url, time = 1000) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
code: 0,
data: {
time: +new Date(),
url,
list: [1, 2, 5]
}
});
}, time)
})
}
function getCode() {
return getUrl('/api/getCode', 1000);
}
function getInfo() {
return getUrl('/api/getInfo', 2000);
}
// 链式调用处理顺序promise,该方法多个顺序promise调用是,书写不便
function fn() {
function recode(results, value){
results.push(value);
return results;
}
// bind方法,fun.bind(thisArg[, arg1[, arg2[, ...]]]),当绑定函数被调用时,这些参数将置于实参之前传递给
// 被绑定的方法。
const pushValue = recode.bind(null, []); //
// return getCode().then(pushValue).then(getInfo).then(pushValue);
// const arr = [];
// return getCode().then((res) => {
// arr.push(res);
// }).then(() => {
// return getInfo();
// }).then((res) => {
// arr.push(res);
// return arr;
// })
const arr = [];
return getCode().then((res) => {
arr.push(res);
}).then(getInfo).then((res) => {
arr.push(res);
return arr;
})
}
// fn().then((res) => {
// console.log('fn res', res);
// }).catch((err) => {
// console.log('fn err', err);
// })
// for循环,调用顺序promise
function fn1() {
function recode(results, value){
results.push(value);
return results;
}
const pushValue = recode.bind(null, []);
const task = [getCode, getInfo];
let p = Promise.resolve();
// 通过不断对promise进行处理,不断的覆盖 p 变量的值,以达到对p对象的累积处理效果。一定要先声明
// 一个p变量
for(let prop of task) {
p = p.then(prop).then(pushValue);
}
return p;
}
// fn1().then((res) => {
// console.log('fn1 res', res);
// }).catch((err) => {
// console.log('fn1 err', err);
// })
// 利用reduce方法来进行处理
function fn2() {
function recode(results, value){
results.push(value);
return results;
}
const pushValue = recode.bind(null, []);
const task = [getCode, getInfo];
return task.reduce(function (promise, task) {
return promise.then(task).then(pushValue);
}, Promise.resolve());
}
// fn2().then((res) => {
// console.log('fn1 res', res);
// }).catch((err) => {
// console.log('fn1 err', err);
// })
// 使用async来处理,顺序处理
async function fn3() {
const tasks = [getCode, getInfo];
let res;
for (let task of tasks) {
res = await task();
console.log(res);
}
return res;
}
// fn3().then((res) => {
// console.log('fn3 res', res);
// }).catch((err) => {
// console.log('fn3 err', err);
// })
// 使用async来处理,并发处理,并发与顺序的区别是,并发是await promise的结果,顺序是await整个promise过程
async function fn4() {
const newDate = +new Date();
const tasksPromise = [getCode(), getInfo()];
let res;
for (let task of tasksPromise) {
res = await task;
console.log(res);
}
console.log(+new Date - newDate);
return res;
}
fn4().then((res) => {
console.log('fn4 res', res);
}).catch((err) => {
console.log('fn4 err', err);
})
浏览器的主要组件包括用户界面、浏览器引擎、呈现引擎(即解析html及css的引擎)、网络、用户界面后端、JavaScript 解释器(js引擎如chrome的v8)、数据存储
清楚浏览器与javascript的关系
宿主关系,即javascript代码的执行依赖于浏览器,因为浏览器内置了javascript解析器(js引擎,如chrome v8)
event loop到底属于谁的运行机制
属于浏览器的一套基于事件驱动的循环检测机制,而不是属于js的循环检测机制
根据规范每个浏览器至少要有一个事件循环机制
一个eventloop有一个or多个task queues,一个task queues是一系列有序的task集合
task主要包括Events(并非所有事件都使用任务队列调度,许多任务在其他任务中调度。)、Parsing(解析html)、Callbacks、Using a resource(获取资源)、Reacting to DOM manipulation(dom操作如元素插入文档时)
js是单线程的缘故
因为js的主要目的是用于用户交互,而同时存在多个线程的话,可能会造成干扰,影响交互
什么是同步,什么是异步,什么是同步操作(同步任务),什么是异步操作(异步任务)
一般而言,操作分为:发出调用和得到结果两步。发出调用,立即得到结果是为同步。发出调用,但无法立即得到结果,需要额外的操作才能得到预期的结果是为异步。
同步:就是调用之后一直等待,直到返回结果。
异步:异步则是调用之后,不能直接拿到结果,通过一系列的手段才最终拿到结果(调用之后,拿到结果中间的时间可以介入其他任务)
同步操作: 就是执行同步的过程;直接返回一个值的函数
异步操作: 就是执行异步的过程;如ajax,定时器
什么是主线程,什么是任务队列,eventloop
主线程是浏览器的一个设定,是浏览器的一个运行池,是浏览器的的运行机制,而不是js的运行机制,是所有任务都在上面执行的线程
任务队列:是一系列包含各种事件,异步操作,定时器等各种队列的一个集合,是在主线程上的一切调用,而队列是任务的集合
任务:分为macrotask(setTimeout、setInterval、setImmediate、I/O、UI交互事件),microtask(Promise、process.nextTick、MutaionObserver)而主线程与任务队列之间又通过一个eventloop来保证主线程最大程度上来执行js代码(主线程永远在执行中。主线程会不断检查任务队列,即进行某个操作时,会产生某个事件,同时也会设置一个watcher,事件循环的过程中从该watcher上处理事件,处理完已有的事件后,处理下一个watcher,检查完所有watcher后,进入下一轮检查,对某类事件不关心时,则没有相关watcher),避免出现阻塞等现象,即主线程上的代码执行完毕之后,就会去拿任务队列里面的任务来放到主线程执行,依此循环往复,同时任务队列之间是可以插队的,如定时器任务,
javascript是一门事件驱动的脚本语言,而事件驱动就是将一切抽象为事件。IO操作完成是一个事件,用户点击一次鼠标是事件,Ajax完成了是一个事件,一个图片加载完成是一个事件
定时器是浏览器有一个专门的队列来存放定时器,并且有一个专门的机制来判断定时器插入任务队列的时机,即到达时间点后,会形成一个事件(timeout事件)。不同的是一般事件是靠底层系统或者线程池之类的产生事件,定时器事件是靠事件循环不停检查系统时间来判定是否到达时间点来产生事件
换个说法当我们进行定时器调用时,首先会设置一个定时器watcher。事件循环的过程中,会去调用该watcher,检查它的事件队列上是否产生事件(比对时间的方式)
DOM,AJAX,setTimeout是浏览器提供的api,而不是javascript提供的api
发起ajax前的逻辑和ajax的callback的逻辑,是2个任务(事件),所以不存在一个事件状态这种东西,ajax回来以后浏览器只是简单的往事件队列里丢一个任务而已,之前发起ajax的那个任务早就结束消失了
非堵塞就是 js 可以异步执行,即有异步任务时,不会影响到异步任务之后的代码执行
js代码的执行过程
执行最旧的task(一次) -> 检查是否存在microtask,然后不停执行,直到清空队列(多次)-> render 重复之前步骤
console.log(1)
setTimeout(() => {
console.log(2)
new Promise(resolve => {
console.log(4)
resolve()
}).then(() => {
console.log(5)
})
})
new Promise(resolve => {
console.log(7)
resolve()
}).then(() => {
console.log(8)
})
setTimeout(() => {
console.log(9)
new Promise(resolve => {
console.log(11)
resolve()
}).then(() => {
console.log(12)
})
})
//1、7、8、2、4、5、9、11、12
代码执行的过程为
同步任务的代码输出为:1、7
执行microtask,直到misrotask队列为空:8
执行第一任务内的同步任务输出为:2、4
执行第一个任务内的microtask,直到misrotask队列为空: 5
执行第二任务内的同步任务输出为:9、11
执行第二个任务内的microtask,直到misrotask队列为空: 12
依此类推
eventloop已经涉及到底层的一些原理,因为眼界有限,总结会有点片面,不过可以记录下来,等之后有了更深的理解之后再回头来看。
参考链接
https://html.spec.whatwg.org/multipage/webappapis.html#event-loops
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setTimeout
http://blog.csdn.net/lin_credible/article/details/40143961
https://juejin.im/post/5a6155126fb9a01cb64edb45
<template>
<div class="swiper-wrap" ref="swiper" :style="{height}">
<div class="swiper-item clearfix" :style="{width: width * list.length + 'px'}">
<div v-for="(item, index) in newList" :key="index">
<img :src="item.img" :alt="'banner' + index" :style="{width: width + 'px', height}">
</div>
</div>
<div class="swiper-dots">
<span v-for="(item, index) in list" :key="index" :class="[index === currentIndex ? 'active' : '']"></span>
</div>
<div class="swiper-prev" @click="changeItem('prev')"><</div>
<div class="swiper-next" @click="changeItem('next')">></div>
</div>
</template>
<script>
import Swiper from './swiper'
export default {
name: 'swiper',
props: {
list: {
type: Array,
default: []
},
direction: {
type: String,
default: 'horizontal'
},
showDots: {
type: Boolean,
default: false
},
dotsPosition: {
type: String,
default: 'center'
},
height: {
type: String,
default: '180px'
},
auto: {
type: Boolean,
default: false
},
loop: {
type: Boolean,
default: false
},
duration: {
type: Number,
default: 300
},
interval: {
type: Number,
default: 2000
}
},
computed: {
width() {
console.log(typeof window !== 'undefined', window.innerWidth);
return typeof window !== 'undefined' && window.innerWidth
},
newList() {
if (this.loop) {
const tempArr = JSON.parse(JSON.stringify(this.list));
tempArr.unshift(this.list[this.list.length - 1]);
tempArr.push(this.list[0]);
return tempArr;
}
return this.list;
}
},
data() {
return {
currentIndex: 0
}
},
methods: {
changeItem(key) {
this.swiper.changeItem(key);
}
},
moundted() {
const { direction, auto, loop, duration, interval } = this;
const options = {
direction,
auto,
loop,
duration,
interval,
data: this.newList
}
this.swiper = new Swiper(this.$refs.swiper, options);
}
}
</script>
<style lang="less" scoped>
.swiper-wrap {
width: 100%;
position: relative;
overflow: hidden;
.swiper-item {
>div {
float: left;
}
}
.swiper-dots {
position: absolute;
left: 50%;
bottom: 10px;
transform: translate(-50%, 0);
font-size: 0;
span {
display: inline-block;
vertical-align: middle;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: transparent;
margin-right: 5px;
border: 1px solid #fff;
&:last-of-type {
margin-right: 0;
}
}
.active {
background-color: rgb(0, 174, 133);
border-color: rgb(0, 174, 133)
}
}
.swiper-prev, .swiper-next {
position: absolute;
top: 50%;
width: 30px;
height: 30px;
line-height: 30px;
font-size: 20px;
text-align: center;
margin-top: -15px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.8);
}
.swiper-prev {
left: 5%;
}
.swiper-next {
right: 5%;
}
}
</style>
const DEFAULT_OPTIONS = {
direction: 'horizontal',
auto: false,
loop: false,
duration: 300,
interval: 2000
}
class Swiper {
constructor(wrap, options) {
this.options = Object.assign({}, DEFAULT_OPTIONS, options);
this.wrap = wrap;
this.initSwiper();
this.initEvent();
}
initSwiper() {
}
initEvent() {
}
changeItem(key) {
}
}
export default Swiper;
autoPlay () {
const { interval } = this.options
this.timer1 && clearTimeout(this.timer1)
var _move = () => {
this.timer1 = setTimeout(() => {
this.go(++this.currentIndex, 'next')
_move()
}, interval)
}
_move()
}
focusHandler () {
this.focusTime = +new Date()
console.log('focus', +new Date(), this.focusTime - this.blurTime)
this.options.auto && this.autoPlay()
}
blurHandler () {
this.blurTime = +new Date()
console.log('blur', +new Date())
this.stop();
}
init () {
if (!this.data.length) return
this.getListWidthsOrHeight()
this.bindEvent()
this.stop()
this.options.auto && this.autoPlay()
}
recomputed() {
const rect = this.$refs.swiper.getBoundingClientRect()
this.width = rect.width + 'px'
const initTransform = this.direction === 'horizontal' ? rect.width : parseInt(this.height)
this.transformx = this.loop && this.isTransition ? `-${initTransform}px` : 0
this.left = this.loop && !this.isTransition ? `-${initTransform}` : 0
}
mounted () {
this.$nextTick(() => {
this.recomputed()
this.initSwiper()
})
},
<meta name="viewport" content="width=device-width, initial-scale=1.0">
参考:http://www.cnblogs.com/2050/p/3877280.html
E[attr]:只使用属性名,但没有确定任何属性值;
E[attr="value"]:指定属性名,并指定了该属性的属性值;
E[attr~="value"]:指定属性名,并且具有属性值,此属性值是一个词列表,并且以空格隔开,其中词列表中包含了一个value词,而且等号前面的“〜”不能不写
E[attr^="value"]:指定了属性名,并且有属性值,属性值是以value开头的;
E[attr$="value"]:指定了属性名,并且有属性值,而且属性值是以value结束的;
E[attr*="value"]:指定了属性名,并且有属性值,而且属值中包含了value;
E[attr|="value"]:指定了属性名,并且属性值是value或者以“value-”开头的值(比如说zh-cn);
需要注意的是
对于E[attr="value"]这种属性值选择器有一点需要注意:属性和属性值必须完全匹配,特别是对于属性值是词列表的形式时,如:
<a href="" class="links item" title="open the website">7</a>
a[class="links"] {} 不能匹配上,只有a=[class="links item"] {}才能匹配上
属性选择器中有波浪(〜)时属性值有value时就相匹配,没有波浪(〜)时属性值要完全是value时才匹配
七种属性选择器中E[attr="value"]和E[attr*="value"]是最实用的,其中E[attr="value"]能帮我们定位不同类型的元素,特别是表单form元素的操作,比如说input[type="text"],input[type="checkbox"]等,而E[attr*="value"]能在网站中帮助我们匹配不同类型的文件,比如说你的网站上不同的文件类型的链接需要使用不同的icon图标,用来帮助你的网站提高用户体验,就像前面的实例,可以通过这个属性给".doc",".pdf",".png",".ppt"配置不同的icon图标,如a[href$="png"]{background:orange;color:green;}
只有ie6不支持,所以使用的时候没有兼容性问题
伪类选择器分三类
第一类:动态伪类选择器,因为这些伪类并不存在于HTML中,而只有当用户和网站交互的时候才能体现出来,动态伪类包含两种,第一种是我们在链接中常看到的锚点伪类,如":link",":visited";另外一种被称作用户行为伪类,如“:hover”,":active"和":focus"。顺序为爱恨原则LoVe/HAte
对于:hover在IE6下只有a元素支持,:active只有IE7-6不支持,:focus在IE6-7下不被支持。
第二类:UI元素状态伪类,":enabled",":disabled",":checked"伪类称为UI元素状态伪类,这些主要是针对于HTML中的Form元素操作,最常见的比如我们"type="text"有enable和disabled两种状态,前者为可写状态后者为不可状态;另外"type="radio"和"type="checkbox""有"checked"和"unchecked"两种状态。来看两个实例,比如说你想将"disabled"的文本框与别的文本框区别出来,你就可以这样应用
input[type="text"]:disabled {border:1px solid #999;background-color: #fefefe;}
IE6-8不支持":checked",":enabled",":disabled"这三种选择器。
第三类:CSS3的:nth选择器
:first-child选择某个元素的第一个子元素;
:last-child选择某个元素的最后一个子元素;
:nth-child()选择某个元素的一个或多个特定的子元素;
:nth-last-child()选择某个元素的一个或多个特定的子元素,从这个元素的最后一个子元素开始算;
:nth-of-type()选择指定的元素;
:nth-last-of-type()选择指定的元素,从元素的最后一个开始计算;
:first-of-type选择一个上级元素下的第一个同类子元素;
:last-of-type选择一个上级元素的最后一个同类子元素;
:only-child选择的元素是它的父元素的唯一一个了元素;
:only-of-type选择一个元素是它的上级元素的唯一一个相同类型的子元素;
:empty选择的元素里面没有任何内容。
p:empty {display: none;} 选中p标签没有内容的元素
:nth-child(length);/*参数是具体数字*/
:nth-child(n);/*参数是n,n从0开始计算*/,:nth-child(0)没有选中任何元素,:nth-child(1)选中第一个元素
:nth-child(n*length)/*n的倍数选择,n从0开始算*/
:nth-child(n+length);/*选择大于length后面的元素*/
:nth-child(-n+length)/*选择小于length前面的元素*/
:nth-child(n*length+1);/*表示隔几选一*/
:nth-child(2n)”也等于":nth-child(even)"选择偶数项
:nth-child(2n-1)”也等于":nth-child(odd)"选择奇数项
注意:nth-child(n);与:nth-of-type()的区别
否定选择器(:not)需要注意的是权重的计算是根据:not(选择器)内的选择器权重来进行计算,:not本身权重为0
input:not([type="submit"]) {border: 1px solid red;} 选中除type="submit"之外的input元素
ie9+以上没有兼容性问题
::before和::after这两个主要用来给元素的前面或后面插入内容,这两个常用"content"配合使用,见过最多的就是清除浮动
伪元素如果没有设置“content”属性,伪元素是无用的,通常插入的伪元素在默认情况下是内联元素,如果要设置宽高需要我们显示的设置display属性
伪元素也是会继承有继承规则的css样式如font-size,font-family等,而不具有继承性的css样式是不会被继承的,如padding与margin之类的
before和after伪元素插入的内容不会被注入到目标元素的前或后注入,而是目标元素内容的前后
.clearfix:before,
.clearfix:after {
content: ".";
display: block;
height: 0;
visibility: hidden;
}
.clearfix:after {clear: both;}
.clearfix {zoom: 1;}
链接:
css选择器支持一览表:https://kimblim.dk/css-tests/selectors/
after与before伪元素有哪些用途:https://css-tricks.com/pseudo-element-roundup/
after与before伪元素的一些使用场景:http://www.cnblogs.com/wonyun/p/5807191.html
移动端开发参考:https://www.w3cplus.com/mobile/basic-knowledge-of-mobile.html
input, textarea, div {
-webkit-tap-highlight-color:rgba(0, 0, 0, 0);
}
iframe postMessage 跨域 cookie操作
有一个这样的场景,两个站点之间是通过嵌套的方式进行关联;A站点维护一套账号体系,B站点也维护一套账号体系;但是二者是打通的,即如果在B站点内的注册的账号一定会在B站点内有一个对应的关联账号;而我们这种方式即运行在app内,也可以运行在h5上;而app内是底层一些清除cookie的方法,所以当A账号退出登陆时,也会清除B站点域下的cookie;而h5上是通过A退出登陆时,会通知到我们后台A账号退出登陆了,这是我们后台会把A对应的B站点下的账号的cookie清掉;这样就可以保持二者的登录态是统一的;但是这里漏了一种场景,即A账号不是主动退出时;我们后端是没有收到A账号退出登陆的消息的,所以我们换个账号登录时,在B站点下就会存在串账号的问题,因为B站点下还有之前A账号对应的cookie;所以为了解决这个问题,一是排查A站点下为什么会无故退出;二是采取补偿措施,就是在A站点登录的时候,都会主动去清一次B站点下的cookie;
pc端
移动端
// 创建iframe,并想iframe内发送消息
function createIframe () {
const iframe = document.createElement('iframe')
iframe.id="my_iframe"
iframe.className = 'my-iframe'
iframe.src = 'xxxx'
iframe.onload = function () {
// 这里需要注意两个地方,第一个参数是传递的参数,最好是字符串,如果是对象使用JSON.stringfiy处理一下,第二个参数表示可以接收到postMessage传递过去消息的域,如果不想指定就写上*,不过为了安全性,最好写上域
iframe.contentWindow.postMessage('message', 'xxxx')
}
iframe.onerror = function () {}
document.body.appendChild(iframe)
}
// 监听所有iframe内发送来的消息
function receiveIframeMessage () {
window.addEventListener('message', function (event) {
if (event.origin === 'xxx' && event.data === 'yyy') {
removeIframe()
}
}, false)
}
// 删除创建的iframe
function removeIframe () {
const iframe = document.getElementId('my_iframe')
document.body.removeChild(iframe)
}
window.addEventListener('message', function (event) {
if (event.origin === 'xxx' && event.data === 'yyy') {
clearCookie().then(() => {
window.parent.postMessage('cookie清除成功', '*')
}).catch(() => {
window.parent.postMessage('cookie清除失败', '*')
})
}
}, false)
在本地调试正常之后,构建到测试环境的时候,发现居然没有生效,A站成功发送来消息,但是B站点监听不到消息postMessage发送过来的消息,一开始以为代码写错了,以及是发送的时候域写错了,把域改成*还是收不到;后面又一以为设置监听的地方不对,试了几个地方都没找到原因,后面在想,本地都能调通,说明代码应该是没什么问题的,那就是接收的时候有问题,会不会是A站发送的时候,B站点内还没设置好监听呢?因为使用的vue,然后在mounted钩子内监听的,于是在A站点内发送消息的时候给个延迟,果然就收到了,所以这里修改了下,采用轮询的方式来发送消息,知道收到B站点反馈过来已经操作成功的消息就结束;
function createIframe () {
const iframe = document.createElement('iframe')
iframe.id="my_iframe"
iframe.className = 'my-iframe'
iframe.src = 'xxxx'
var timer = null;
var myPostMessage = function () {
// 轮询的目的是,iframe内还没设置好监听,避免通信不成功
timer = setTimeout(() => {
var iframeEle = document.getElementById('my_iframe')
iframeEle && iframe.contentWindow.postMessage('message', `xxxx`)
if (iframeEle) {
myPostMessage()
}
}, 500)
}
iframe.onload = function () {
myPostMessage()
}
iframe.onerror = function () {
console.log('iframe onerror')
}
document.body.appendChild(iframe)
}
跨域的行为分为3类
链接
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage
var myRegExp = {
// 检查字符串是否为合法QQ号码
isQQ: function(str) {
// 1 首位不能是0 ^[1-9]
// 2 必须是 [5, 11] 位的数字 \d{4, 9}
var reg = /^[1-9][0-9]{4,9}$/gim;
if (reg.test(str)) {
console.log('QQ号码格式输入正确');
return true;
} else {
console.log('请输入正确格式的QQ号码');
return false;
}
},
// 检查字符串是否为合法手机号码
isPhone: function(str) {
var reg = /^(0|86|17951)?(13[0-9]|15[012356789]|18[0-9]|14[57]|17[678])[0-9]{8}$/;
if (reg.test(str)) {
console.log('手机号码格式输入正确');
return true;
} else {
console.log('请输入正确格式的手机号码');
return false;
}
},
// 检查字符串是否为合法Email地址
isEmail: function(str) {
var reg = /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;
// var reg = /\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/;
if (reg.test(str)) {
console.log('Email格式输入正确');
return true;
} else {
console.log('请输入正确格式的Email');
return false;
}
},
// 检查字符串是否是数字
isNumber: function(str) {
var reg = /^\d+$/;
if (reg.test(str)) {
console.log(str + '是数字');
return true;
} else {
console.log(str + '不是数字');
return false;
}
},
// 去掉前后空格
trim: function(str) {
var reg = /^\s+|\s+$/g;
return str.replace(reg, '');
},
// 检查字符串是否存在中文
isChinese: function(str) {
var reg = /[\u4e00-\u9fa5]/gm;
if (reg.test(str)) {
console.log(str + ' 中存在中文');
return true;
} else {
console.log(str + ' 中不存在中文');
return false;
}
},
// 检查字符串是否为合法邮政编码
isPostcode: function(str) {
// 起始数字不能为0,然后是5个数字 [1-9]\d{5}
var reg = /^[1-9]\d{5}$/g;
// var reg = /^[1-9]\d{5}(?!\d)$/;
if (reg.test(str)) {
console.log(str + ' 是合法的邮编格式');
return true;
} else {
console.log(str + ' 是不合法的邮编格式');
return false;
}
},
// 检查字符串是否为合法身份证号码
isIDcard: function(str) {
var reg = /^(^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$)|(^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[Xx])$)$/;
if (reg.test(str)) {
console.log(str + ' 是合法的身份证号码');
return true;
} else {
console.log(str + ' 是不合法的身份证号码');
return false;
}
},
// 检查字符串是否为合法URL
isURL: function(str) {
var reg = /^https?:\/\/(([a-zA-Z0-9_-])+(\.)?)*(:\d+)?(\/((\.)?(\?)?=?&?[a-zA-Z0-9_-](\?)?)*)*$/i;
if (reg.test(str)) {
console.log(str + ' 是合法的URL');
return true;
} else {
console.log(str + ' 是不合法的URL');
return false;
}
},
// 检查字符串是否为合法日期格式 yyyy-mm-dd
isDate: function(str) {
var reg = /^[1-2][0-9][0-9][0-9]-[0-1]{0,1}[0-9]-[0-3]{0,1}[0-9]$/;
if (reg.test(str)) {
console.log(str + ' 是合法的日期格式');
return true;
} else {
console.log(str + ' 是不合法的日期格式,yyyy-mm-dd');
return false;
}
},
// 检查字符串是否为合法IP地址
isIP: function(str) {
// 1.1.1.1 四段 [0 , 255]
// 第一段不能为0
// 每个段不能以0开头
//
// 本机IP: 58.50.120.18 湖北省荆州市 电信
var reg = /^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}$/gi;
if (reg.test(str)) {
console.log(str + ' 是合法的IP地址');
return true;
} else {
console.log(str + ' 是不合法的IP地址');
return false;
}
}
}
数字:^[0-9]*$
n位的数字:^\d{n}$
至少n位的数字:^\d{n,}$
m-n位的数字:^\d{m,n}$
零和非零开头的数字:^(0|[1-9][0-9]*)$
非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$
带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$
正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$
有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$
非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
非负整数:^\d+$ 或 ^[1-9]\d*|0$
非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$
正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$
汉字:^[\u4e00-\u9fa5]{0,}$
英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
长度为3-20的所有字符:^.{3,20}$
由26个英文字母组成的字符串:^[A-Za-z]+$
由26个大写英文字母组成的字符串:^[A-Z]+$
由26个小写英文字母组成的字符串:^[a-z]+$
由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
可以输入含有^%&',;=?$\"等字符:[^%&',;=?$\x22]+
禁止输入含有~的字符:[^~\x22]+
/*eslint-disable */
const request = (function () {
// 系统参数;
const defaults = {
timeout: 5000,
reqState: false,
url: '',
header: '',
data: '',
method: '',
dataType: 'json',
success: (res) => {
console.log('success', res);
},
fail: () => { },
complete: function () { },
};
// http适配器
function XhrAdapter(config) {
return new Promise((resolve, reject) => {
console.log('config', config, config.success);
config.success = (res) => {
console.log('success1', res);
resolve(res);
};
config.fail = (res) => {
reject(res);
};
console.log('request', config, config.success);
wx.request(config);
});
}
// 派发请求
function dispatchRequest(config) {
const adapter = new XhrAdapter(config);
return adapter.then(function onAdapterResolution(response) {
return response;
}, function onAdapterRejection(reason) {
return Promise.reject(reason);
});
}
function InterceptorManager() {
this.handlers = [];
}
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected,
});
return this.handlers.length - 1;
};
InterceptorManager.prototype.forEach = function forEach(fn) {
this.handlers.forEach(function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
function Axios(defaultConfig) {
this.defaults = defaultConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
['delete', 'get'].forEach((method) => {
Axios.prototype[method] = function ({url, config}) {
return this.request(Object.assign({
header: config || {},
} || {}, {
method: method,
url: url,
}));
};
});
['post', 'put'].forEach((method) => {
Axios.prototype[method] = function (url, data, config) {
return this.request(Object.assign({
header: config || {},
} || {}, {
method: method,
url: url,
data: data,
}));
};
});
Axios.prototype.request = function request(config) {
config.method = config.method.toLowerCase();
config = Object.assign(defaults, this.defaults, config);
console.log(config);
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
function createInstance(defaultConfig) {
let context = new Axios(defaultConfig);
return context;
}
let axios = createInstance(defaults);
return axios;
})();
// 自定义请求拦截器
request.interceptors.request.use((data) => {
console.log(`url:${data.url} method:${data.method}`);
wx.showLoading({
title: '加载中',
});
return data;
}, error =>{
return Promise.reject(error);
});
// 自定义返回拦截器
request.interceptors.response.use((data) => {
wx.hideLoading();
return data;
}, error => {
return Promise.reject(error);
});
export default request;
const DEFAULT_CONTENT_TYPE = {
'Content-Type': 'application/x-www-form-urlencoded',
};
function getDefaultAdapter() {
return xhrAdapter;
}
const defaults = {
adapter: getDefaultAdapter(),
timeout: 0,
headers: {
common: {
Accept: 'application/json, text/plain, */*',
},
},
};
function encode(val) {
return encodeURIComponent(val)
.replace(/%40/gi, '@')
.replace(/%3A/gi, ':')
.replace(/%24/g, '$')
.replace(/%2C/gi, ',')
.replace(/%20/g, '+')
.replace(/%5B/gi, '[')
.replace(/%5D/gi, ']');
}
function isURLSearchParams(val) {
return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams;
}
function isDate(val) {
return Object.prototype.call(val) === '[object Date]';
}
function enhanceError(error, config, code, request, response) {
error.config = config;
if (code) {
error.code = code;
}
error.request = request;
error.response = response;
return error;
}
function createError(message, config, code, request, response) {
const error = new Error(message);
return enhanceError(error, config, code, request, response);
}
function settle(resolve, reject, response) {
const validateStatus = response.config.validateStatus;
// Note: status is not exposed by XDomainRequest
if (!response.status || !validateStatus || validateStatus(response.status)) {
resolve(response);
} else {
reject(createError(
`Request failed with status code ${response.status}`,
response.config,
null,
response.request,
response,
));
}
}
function buildUrl(url, params) {
if (!params) {
return url;
}
let serializedParams;
if (isURLSearchParams(params)) {
serializedParams = params.toString();
} else {
const parts = [];
params.forEach((val, key) => {
if (val === null || typeof val === 'undefined') {
return;
}
if (Array.prototype.isArray(val)) {
key += '[]';
} else {
val = [val];
}
val.forEach((v) => {
if (isDate(v)) {
v = v.toISOString();
} else if (Object.prototype.isObject(v)) {
v = JSON.stringify(v);
}
parts.push(`${encode(key)}=${encode(v)}`);
});
});
serializedParams = parts.join('&');
}
if (serializedParams) {
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
}
return url;
}
function transformData(data, headers, fns) {
/* eslint no-param-reassign:0 */
fns.forEach((fn) => {
data = fn(data, headers);
});
return data;
}
function dispatchRequest(config) {
config.headers = config.headers || {};
// Transform request data
config.data = transformData(
config.data,
config.headers,
config.transformRequest,
);
config.headers = Object.assign(
config.headers.common || {},
config.headers[config.method] || {},
config.headers || {},
);
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'].forEach(
(method) => {
delete config.headers[method];
},
);
const adapter = config.adapter || defaults.adapter;
return adapter(config).then((response) => {
response.data = transformData(
response.data,
response.headers,
config.transformResponse,
);
}, (reason) => {
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse,
);
}
return Promise.reject(reason);
});
}
function InterceptorManager() {
this.handlers = [];
}
InterceptorManager.prototype.use = function (fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected,
});
return this.handlers.length - 1;
};
InterceptorManager.prototype.eject = function (id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
InterceptorManager.prototype.forEach = function (fn) {
this.handlers.forEach((h) => {
if (h !== null) {
fn(h);
}
});
};
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
Axios.prototype.request = function (config) {
if (typeof config === 'string') {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
config = Object.assign(this.defaults, config);
config.method = config.method ? config.method.toLowerCase() : 'get';
const chain = [dispatchRequest, undefined];
let promise = Promise.resolve(config);
this.interceptors.request.forEach((interceptor) => {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach((interceptor) => {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
// Provide aliases for supported request methods
['delete', 'get', 'head', 'options'].forEach((method) => {
/* eslint func-names:0 */
Axios.prototype[method] = function (url, config) {
return this.request(Object.assign(config || {}, {
method: method,
url: url,
}));
};
});
['post', 'put', 'patch'].forEach((method) => {
/* eslint func-names:0 */
Axios.prototype[method] = function (url, data, config) {
return this.request(Object.assign(config || {}, {
method: method,
url: url,
data: data,
}));
};
});
function xhrAdapter(config) {
return new Promise((resolve, reject) => {
let requestData = config.data;
const requestHeaders = config.headers;
let request = new XMLHttpRequest();
const loadEvent = 'onreadystatechange';
request.open(config.method.toUpperCase(), buildUrl(config.url, config.params), true);
request.timeout = config.timeout;
request[loadEvent] = function handleLoad() {
if (!request) {
return;
}
const responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;
const response = {
data: responseData,
// IE sends 1223 instead of 204 (https://github.com/axios/axios/issues/201)
status: request.status === 1223 ? 204 : request.status,
statusText: request.status === 1223 ? 'No Content' : request.statusText,
headers: null,
config: config,
request: request,
};
settle(resolve, reject, response);
request = null;
};
request.onabort = function handleAbort() {
if (!request) {
return;
}
reject(createError('Request aborted', config, 'ECONNABORTED', request));
// Clean up request
request = null;
};
request.onerror = function handleError() {
// Real errors are hidden from us by the browser
// onerror should only fire if it's a network error
reject(createError('Network Error', config, null, request));
// Clean up request
request = null;
};
request.ontimeout = function handleTimeout() {
reject(createError(`timeout of ${config.timeout}ms exceeded`, config, 'ECONNABORTED',
request));
// Clean up request
request = null;
};
if ('setRequestHeader' in request) {
requestHeaders.forEach((val, key) => {
if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
// Remove Content-Type if data is undefined
delete requestHeaders[key];
} else {
// Otherwise add header to the request
request.setRequestHeader(key, val);
}
});
}
if (config.responseType) {
try {
request.responseType = config.responseType;
} catch (e) {
// Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
// But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
if (config.responseType !== 'json') {
throw e;
}
}
}
if (requestData === undefined) {
requestData = null;
}
// Send the request
request.send(requestData);
});
}
['delete', 'get', 'head'].forEach((method) => {
defaults.headers[method] = {};
});
['put', 'post', 'patch'].forEach((method) => {
defaults.headers[method] = Object.assign({}, DEFAULT_CONTENT_TYPE);
});
function bind(fn, thisArg) {
return function wrap() {
const args = new Array(arguments.length);
for (let i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
return fn.apply(thisArg, args);
};
}
function createInstance(defaultConfig) {
const context = new Axios(defaultConfig);
const instance = bind(Axios.prototype.request, context);
return instance;
}
const axios = createInstance(defaults);
export default axios;
记得刚开始使用webpack的时候,自己踩了些坑,最近恰好想在捋下webpack,所以就记录下来。
下面列一下一些碰到的疑问
开发的时候webpack怎么不支持index.html页面的热更新
因为webpack-dev-server只处理与入口文件有依赖的资源,而我们在各个模块内一般都是引入js资源or css资源,img资源,字体资源,视频音频资源,所有热更新无法覆盖到项目目录下的所有文件,只有与入库文件有依赖关系的资源才会被监视,才会在修改的时候去进行热更新(启用了热更新的前提)
解决方法是:引入html-withimg-loader,在rules内配置该loader,同时在入库文件内引入index.html即可实现index.html的热更新
webpack打包的时候,html页面内img引入的图片路径没有做处理
这是webpack本身就不提供这个选项,解决方法有两个
第一个方法,在js文件内引入改图片资源如import imgURL from './1.jpg'然后通过dom来进行操作
第二个方法是使用html-withimg-loader这个loader,在rules内配置规则即可,需要注意的时这个loader
只能处理img标签的src属性,不能处理video等标签的资源引入,所以video的标签资源的引入还需要动
态设置
output配置项内的path及publicPath有什么区别
path参数的含义是指定入口文件输出的路径(文件夹),如path: resolve(__dirname, 'dist'), //__dirname webpack执行时的文件目录,这里就是跟目录,也就是输出到根目录下的dist文件夹,即改配置属性不会去影响资源属性的路径,记住一点path是webpack所有文件的输出的路径,必须是绝对路径,比如:output输出的js,url-loader解析的图片,HtmlWebpackPlugin生成的html文件,都会存放在以path为基础的目录下;
publicPath参数的含义是指补全生成的index.html(href,script等引入的路径,但不包括img引入的图片路径)及各个css,文件内引入图片,音频等资源的路径,如publicPath:'http://127.0.0.1:8020/es6/myEs6/dist/',那么在打包的时候引入资源文件的路径就是publicPath + 开发的时候写的相对路径,如background-image: url('../../static/images/4.jpg');打包后的路径为background-image:url(http://127.0.0.1:8020/es6/myEs6/dist/static/images/4.74ca2aa.jpg);如果pablicPath不配置则默认为''字符串,则html及css内加载的图片资源路径就是开发时写的相对路径去掉.变成绝对路径,如果publicPath配置为./则,则css内图片路径会会去掉相对路径./or ../而html内引入的css及js文件则会生成./文件名,如果publicPath配置成/则css内即html内引入资源的路径都会是绝对路径background-image:url(/static/images/4.74ca2aa.jpg)即publicPath的作用就是为index.html及css文件内引入图片,音频等文件添加访问路径前缀(静态资源最终访问路径 = output.publicPath + 资源loader或插件等配置路径),如css内引用图片的路径为url('../images/4.jpg'),publicPath为/webpack/webpack-src/dist/那么最终输出的路径为/webpack/webpack-src/dist/static/images/4.415452jpg
环境变量process.env.NODE_ENV怎么去获取到具体的值
我们在webpack内使用环境变量的作用是帮助我们进行区分开发环境、测试环境及正式环境,那么要在webpack的配置文件内哪到值需要设置两个地方
第一处是
new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"development"' // 定义环境变量 } })
第二处是
"dev": set NODE_ENV=development&&webpack-dev-server --open
url-loader与file-loader是什么关系
url-loader的作用是处理css内orjs内引入的图片资源,当图片资源小于limit时,url会将图片转换为base64的图片进行输出,如果资源大于limit则不进行转换,将使用file-loader处理以图片的路径输出,此时可以指定图片输出的路径name: 'static/images/[name].[hash:7].[ext]',注意这里的路径是基于output内的path开始的,最终输出的路径为path/static/images/[name].[hash:7].[ext]
file-loader的作用是处理图片音频等资源并输出,url-loader的基础,注意的时可以直接配置outputPath,publicPath,useRelativePath等配置属性
音频视频文件怎么引入
配置loader,然后使用require or import导入
HtmlWebpackPlugin插件内的template属性与inject属性具体作用是什么
template: ‘index.html’ //需要参考的模板 注意前面可以带路径‘views/index.html’,注意这里是相当于根路径
inject: 'body' // 向template或者templateContent中注入所有静态资源,true或者body:所有JavaScript资源插入到body元素的底部;head: 所有JavaScript资源插入到head元素中;false: 所有静态资源css和JavaScript都不会注入到模板文件中
使用webpack-dev-server搭建本地服务器时,它的contentBase属性与publicPath属性有什么作用
contentBase: path.join(__dirname, 'dist') // 告诉webpack-dev-server,在 localhost:8080 下建立服务,服务的文件来自dist目录,这里需要注意的时webpack-dev-server默认生成的dist目录是在内存中,不是项目目录内的dist
publicPath的作用output内的publicPath作用类似
webpack内的chunk、hash、chunkhash分别是什么意思
chunk:表示一个文件,默认情况下webpack的输入是一个入口文件,输出也是一个文件,这个文件就是一个chunk,chunkId就是产出时给每个文件一个唯一标识id,chunkhash就是文件内容的md5值
hash:计算所有 chunks 的 hash,及这一次构建过程的hash,适合 chunk 拆分不多的小项目,但所有资源全打上同一个 hash,无法完成持久化缓存的需求
chunkhash: 每个文件生成时的chunkhash,即这一次构建的过程中每个文件生成的hash;JS 资源的 [chunkhash] 由 webpack 计算,Images/Fonts 的 [hash] 由 webpack/url-loader计算,css的hash由extract-text-webpack-plugin计算给出,注意生成hash值是一个编译的过程,所有在开发的时候不需要给输出文件配置hash值
webpack内置的CommonsChunkPlugin有什么作用
抽出公共模块,这里我们的代码js代码一般分为三类,1.引入的第三方框架和库;2.自己写的公共代码;3.其它的单独js文件,我们需要将不经常变的第一类js文件or第二类js文件抽离到公共的一个js文件内,保证浏览器的长时间缓存,提高加载效率
package.json内怎么去配置webpack命令
"build": "set NODE_ENV=production&&webpack --progress --hide-modules --color --config webpack.prod.conf.js",
"dev": "set NODE_ENV=development&&webpack-dev-server --open --inline --hot --compress --history-api-fallback --progress --color --config webpack.dev.conf.js"
注意的是webpack启动时默认会去找跟目录下的webpack.config.js文件,而我们如果要让定制webpack.base.conf.js等配置文件生效,则需要在命令行处配置读取文件用--config来进行配置
参考链接
https://webpack.js.org/concepts/
https://doc.webpack-china.org/concepts/
最近在项目中开发自定义滚动条组件,用到了拖拽及滚动,而用到拖拽及滚动的时候就不得不用到一些获取元素尺寸、滚动高度、鼠标位置、元素距离视口的位置等属性,于是写完组件之后抽了个时间总结了下,希望加深理解
同理其它属性值是一样的
判断元素是否出现滚动条
根据scorllHeight、scrollWidth与clientHeight、clientWidth的大小关系来进行判断,因为二者获取到的宽高是相同的部分,所有没有滚条条时scrollHeight = clientHeight; scrollWidth = clientWidth;
判断水平方向滚动条 scrollWidth > clinetWidth 允许滚动的水平范围scrollLeft => scrollWidth - clientWidth
判断垂直方向滚条条 scrollHeight > clientHeight 允许滚动的垂直范围scrollTop => scrollHeight - clientHeight
拖拽
在mousedown的时候获取鼠标点击的当前位置
在当前屏幕内拖拽
disX = e.clientX - e.target.offsetLeft 或者直接写e.offsetX;
disY = e.clientY - e.target.offsetTop 或者直接写e.offsetY;
在整个文档内拖拽
disX = e.pageX - e.offsetX;
disY = e.pageY - e.offsetY;
在mousemove的时候
left = e.clinetX - disX;
top = e.clinetY - disY;
获取当前屏幕的尺寸
screenWidth = document.documentElement.clientWidth || document.body.clientWidth;
screenHeight = document.documentElement.clientHeight || document.body.clientHeight;
监听window对象上的scroll事件时获取浏览器滚动条的scrollTop
scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
获取浏览器滚动条的宽度
第一种思路设置元素overflow为scroll,计算设置scroll之前的clientWidth与之后的clientWidth,之差就是滚动条的宽度,原因是clientWidth是不包含滚动条宽度的,且有滚动条之后滚动条会占据clientWidth的宽度;
function getScrollBar () {
var div = document.createElement('div'),
noScrollWidth,
scrollWidth;
div.style.width = '200px';
div.style.height = '300px';
div.style.position = 'absolute';
div.style.left = '99999px';
noScrollWidth = document.body.appendChild(div).clientWidth;
div.style.overflowY = 'scroll';
scrollWidth = div.clientWidth;
document.body.removeChild(div);
return noScrollWidth - scrollWidth;
}
第二个思路直接设置overflow:scroll,然后使用offsetWidth - clientWidth之差来获取滚动条的宽度,因为offsetWidth是包含滚动条的,而clinetWidth是不包含滚动条宽度的
function getScrollBar () {
var div = document.createElement('div'),
clientWidth,
offsetWidth;
div.style.width = '200px';
div.style.height = '300px';
div.style.overflowY = 'scroll'
div.style.position = 'absolute';
div.style.left = '99999';
clientWidth = document.body.appendChild(div).clientWidth;
offsetWidth = div.offsetWidth;
return offsetWidth - clientWidth;
}
关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器,第一种事件会立刻执行,第二种事件会在 n 秒后第一次执行,第一种事件停止触发后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件
<div class="box"></div>
var box = document.getElementsByClassName("box")[0];
var WAIT_TIME = 500;
var throttle1 = function (func, time){
var previous = 0,
_this,
args;
return function (){
var now = +new Date(); //使用时间搓
_this = this;
args = arguments;
if(now - previous > time){
func.apply(_this, args);
previous = now;
}
}
}
var throttle2 = function (func, time){
var _this,
timeout,
args;
return function (){
_this = this;
args = arguments;
if(!timeout){
timeout = setTimeout(function (){
func.apply(_this, args);
timeout = null;
}, time);
}
}
}
var throttle3 = function (func, time){//时间戳与定时器的结合
var previous = 0,
_this,
timeout,
args;
var later = function (){
previous = +new Date();
timeout = null;
func.apply(_this, args);
}
return function (){
var now = +new Date();
var remaining = time - (now - previous); //下次执行func剩余的时间
_this = this;
args = arguments;
if(remaining < 0 || remaining > time){
if(timeout){
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(_this, args);
}else if(!timeout) {
timeout = setTimeout(later, remaining);
}
}
}
var throttle4 = function (func, time, options){//时间戳与定时器的结合
var previous = 0,
_this,
timeout,
args;
if (!options) options = {};
var later = function (){
previous = options.leading === false ? 0 : new Date().getTime();
timeout = null;
func.apply(_this, args);
if (!timeout) _this = args = null;
}
return function (){
var now = +new Date();
if(!previous && options.leading === false) previous = now;
var remaining = time - (now - previous); //下次执行func剩余的时间
_this = this;
args = arguments;
if(remaining < 0 || remaining > time){
if(timeout){
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(_this, args);
if (!timeout) _this = args = null;
}else if(!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
}
}
var getUserAction = function (){
console.log(this);
console.log(arguments);
}
// box.onmousemove = throttle1(getUserAction, 1000);
// box.onmousemove = throttle2(getUserAction, 1000);
// box.onmousemove = throttle3(getUserAction, 1000);
// box.onmousemove = throttle4(getUserAction, 1000,{
// leading:false 表示禁用第一次执行 trailing: false 表示禁用停止触发的回调
// });
box.onmousemove = throttle4(getUserAction, 1000,{
trailing: false
});
display: none;与visibility: hidden;的区别
联系:它们都能让元素不可见
区别:
specified value,computed value,used value计算方法
background-position
bottom, left, right, top
height, width
margin-bottom, margin-left, margin-right, margin-top
min-height, min-width
padding-bottom, padding-left, padding-right, padding-top
text-indent
link与@import的区别
PNG,GIF,JPG的区别及如何选
GIF:
JPEG:
PNG:
CSS有哪些继承属性
关于文字排版的属性如:
font
word-break
letter-spacing
text-align
text-rendering
word-spacing
white-space
text-indent
text-transform
text-shadow
line-height
color
visibility
cursor
什么是FOUC?如何避免
Flash Of Unstyled Content:用户定义样式表加载之前浏览器使用默认样式显示文档,用户样式加载渲染之后再从新显示文档,造成页面闪烁。解决方法:把样式表放到文档的head
stacking context,布局规则
z轴上的默认层叠顺序如下(从下到上):
如何创建stacking context:
什么是BFC,BFC有什么用?
BFC是block formatting context,也就是块级格式化上下文,是用于布局块级盒子的一块渲染区域,及一种块级盒子的布局方式,至于有什么用,是因为我们常说的文档流其实分为定位流、浮动流和普通流三种。而普通流其实就是指BFC中的FC。FC是formatting context的首字母缩写,直译过来是格式化上下文,它是页面中的一块渲染区域,有一套渲染规则,决定了其子元素如何布局,以及和其他元素之间的关系和作用,而BFC则是FC的一种扩展方式,可以给我们布局的时候提供一些便利
换个方式也就是说,变成了BFC的块级盒子,具有了一个普通盒子不具有的功能,它成了一个独立的隔离的容器,外面的元素无法影响到BFC盒子内的元素,BFC盒子内的元素也无法影响到外面的元素,如包含浮动的元素,清除浮动等
触发BFC的方式
主要作用
// 阻止元素被浮动元素覆盖
.normal {
overflow: auto; // 设置触发BFC的属性
height: 100px;
background-color: pink;
}
.float {
float: left;
width: 100px;
height: 50px;
background-color: red;
}
<div class="box">
<div class="float">float</div>
<div class="normal"></div>
</div>
// 包含浮动元素,避免高度塌陷
.wrap {
overflow: auto; // 设置触发BFC的属性
background-color: green;
}
.content {
float: left;
width: 200px;
height: 100px;
background-color: blue;
}
<div class="wrap">
<div class="content">content</div>
</div>
// 阻止margin会发生重叠
.box1,.box2-child {
height: 100px;
background-color: #ccc;
margin: 20px 0;
}
.box2-child {
background-color: #0099FF;
}
.box2 {
overflow: auto;
}
<div class="main">
<div class="box1">box1</div>
<div class="box2">
<div class="box2-child">box2</div>
</div>
</div>
行内元素float:left后orposition:absolute/fiex后是否变为块级元素?
浮动后or定位后,行内元素会成为类块状元素(即具有块级元素的特征),可以设置宽高与margin,但是需要注意的是由于浮动与position是脱离了文档流,所以变成块元素的宽度不会是独占一行,而是其内容的宽度or自设的宽度来决定。同理块级元素浮动即定位之后的宽度也一样由内容宽度决定or自设宽度决定;
::before 和 :after中双冒号和单冒号 有什么区别?
单冒号(:)用于CSS3伪类,双冒号(::)用于CSS3伪元素
用于区分伪类和伪元素
伪类表状态
伪元素是真的有元素
设置元素水平成功垂直居中的方式有哪些
// 第一种 position: absolute + transform 实现垂直居中,宽高自适应,兼容ie9+ (优先使用此方式)
.box {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-color: pink;
}
<div class="box">position: absolute + transform</div>
// 第二种 position: absolute,top+left+top+bottom等0实现水平垂直居中,需要设置具体宽高,兼容ie6+(其次使用此方式)
.box1 {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
width: 50%;
height: 50px;
background-color: pink;
}
<div class="box1">position: absolute,top+left+top+bottom</div>
// 第三种 flex实现
// 父元素
.wrap3 {
width: 100%;
height: 200px;
display: flex;
flex-direction: column;
justify-content: center;
background-color: blue;
margin-top: 10px;
}
// 居中子元素
.box3 {
width: 10%;
flex: 0 0 50px;
background-color: pink;
align-self: center;
}
<div class="wrap3">
<div class="box3">flex布局</div>
</div>
// 第四种 table-cell实现
.wrap4 {
display: table;
width: 100%;
height: 200px;
margin-top: 10px;
}
.box4 {
display: table-cell;
background-color: blue;
vertical-align: middle;
text-align: center;
}
.content {
width: 50%;
height: 50px;
margin: auto;
background-color: pink;
}
<div class="wrap4">
<div class="box4">
<div class="content">display:table-cell</div>
</div>
</div>
// 第一种方式,定宽的元素float脱离文档流,右侧元素给对应宽度的margin-left(优先推荐使用该方法)
.wrap {
width: 100%;
height: 200px;
margin-top: 10px;
background-color: blue;
}
.left {
float: left;
width: 200px;
background-color: pink;
}
.right {
margin-left: 200px;
height: 200px;
background-color: gray;
}
<div class="wrap">
<div class="left">left,左侧浮动,脱离文档流,右侧设置一个margin-left</div>
<div class="right">right</div>
</div>
// 第二种方式左右两侧都浮动,左侧设置固定宽度,及一个固定宽度的margin-right负值,右侧设置宽度100%,然后在右侧div内设一个子元素,给子元素设置一个margin-left
.left1 {
float: left;
margin-right: -200px;
width: 200px;
background-color: pink;
}
.right1 {
float: left;
width: 100%;
}
<div class="wrap">
<div class="left1">left</div>
<div class="right1">
<div class="content">right</div>
</div>
</div>
// 第三种方式 flex,这种方法需要注意兼容性,ie10+,移动端可以用
.wrap1 {
display: flex;
width: 100%;
height: 200px;
margin-top: 10px;
background-color: blue;
}
.left2 {
flex: 0 0 200px;
background-color: pink;
}
.right2 {
flex: 1;
background-color: gray;
}
<div class="wrap1">
<div class="left2">left,这种方法需要注意兼容性,ie10+,移动端可以用</div>
<div class="right2">right</div>
</div>
选择器的权重,除去行内样式,及!important,其它的权重如下所示
注意nth-child与nth-of-type选择器之间的区别
tr:nth-child(2n+1) === tr:nth-child(odd)选择奇数行;
tr:nth-child(2n) === tr:nth-child(even)选择偶数行;
span:nth-child(0n+1) === span:nth-child(1) === first-child匹配第一个元素;
span:nth-child(-n+3)匹配前三个元素
Vuex 文档里面这么说的,vuex是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化;
我自己的理解就是vuex是一个通信工具,它可以让我们在vue的各个组件之间进行实时的数据通信,同时以又以一种特定的方式来设置状态,改变状态,从而实现可预测的变化;在传统的项目开发中,我们一般有以下几种方式来实现组件or模块or页面之间的通信,如EventBus, 事件广播emit and broadcast,locastorage, cookie、传参等,而这些方式多多少少都有一些缺点,而vuex则是一种更便捷及更高效的实现组件or模块之前通信的一种工具;
个人建议学习vuex的时候,可以从下面3点来进行学习
一、vuex内有哪些东西 -> vuex里面有6个关键属性,如下所示
state状态值存储的地方,所有需要公用or需要经过vuex处理的数据都需要先在state内定义;注意的是为了保证vuex内定义的状态是响应式的,最好先在state内定义该状态,然后在通过mutation来进行改变;
mutation改变state内状态值的方法集合,即state内的状态只有通过mutations内定义的方法才可以去改变,这也是保证状态以一种可预测的方式方式变化的原因;
action也是改变state内状态值的方法集合,与mutation不同的是,action不是直接操作state而是通过操作mutation来改变state,另外一个区别是action内可以包含任意异步操作,而mutation内不能;
getter用来获取state值的方法集合,主要有两个特点,第一个过滤获取到的state值,第二个就是与计算属性类似,会对值进行缓存,当依赖发生改变的时候才会重新获取;
module即用来对state进行模块划分的,这样的好处就是便于管理与维护不同模块内定义的state,内部定义state,mutation,action与全局的一样;
mutation-type就是方便维护mutation,能够在一个文件内保存所以的mutation
二、vuex内怎么去定义state、mutation、action、module、getter
定义state
state.js
const state = { userInfo: {}, showLoading: false, showPageTagsList: [] }; export default state;
定义mutation
mutations.js 注意mutation传入的第一个参数是state,第二个参数是载荷也就是提交mutation时传入的参数
export default { [types.SAVE_USERINFO] (state, userInfo) { state.userInfo = userInfo.data; Storage.setSessionStorage('userInfo', JSON.stringify(userInfo.data)); } };
定义action
import { loginByuserName } from '@/api/login/login'; import * as types from './mutations-type'; export default { LoginByuserName ({ commit }, userInfo) { const username = userInfo.username.trim(); return new Promise((resolve, reject) => { loginByuserName(username, userInfo.password).then(response => { const data = response.data; commit(types['SAVE_USERINFO'], data); resolve(); }).catch(error => { reject(error); }); }); } };
定义getter
export default { userInfo: state => state.userInfo, productMdInfo: state => state.product.productMdInfo, showLoading: state => state.showLoading };
定义mutation-type
export const SAVE_USERINFO = 'SAVE_USERINFO'; export const SAVE_PRODUCTMD_INFO = 'SAVE_PRODUCTMD_INFO'; export const TOGGLE_LOADING = 'TOGGLE_LOADING'; export const ADD_NEW_TAG = 'ADD_NEW_TAG';
最后传入到vuex.store方法内
export default new Vuex.Store({ modules: { statistics, order, product }, state, mutations, getters, actions, strict: debug, plugins: debug ? [createLogger()] : [] });
三、vue组件内怎样去使用vuex
通过$state来进行操作
获取state => this.$store.state.count来进行获取某个state属性,注意这里能够通过this.$store来获取到store对象的原因是在vue初始化app实例的时候就把store注入到vue实例内了;
提交mutation => this.$store.commit('SAVE_USERINFO ', {type: 'a', name: 'jack'});
提交action => this.$store.dispatch('LoginByuserName ', {type: 'a', name: 'jack'});
通过mapState、mapGetters 、mapMutations 、mapActions 来进行操作
获取state => 在计算属性内使用...mapState(['userInfo']) 这等价于userInfo () {reurn store.state.userInfo}
提交mutation => 在方法内先解构...mapMutations(['SAVE_USERINFO ']) 等价为 SAVE_USERINFO () {return this.$store.commit('SAVE_USERINFO ', {type: 'a', name: 'jack'});}
提交action => 现在方法内进行解构与mutation一样
通过mapGetters 获取到某个state状态状态值,在计算属性内进行结构...mapGetters(['userInfo'])
vuex使用起来不难,难的是这种架构**,有时间的话会去看下源码;
export default {
namespaced: true,
state: {
msgList: {}
},
getters: {
getListByType: state => type => (state.msgList[type] || {})
},
mutations: {
setMsgList(state, playload) {
const type = playload.group_id;
const {data} = playload.result;
console.log('setMsgList', state, type);
if (state.msgList[type]) {
state.msgList = {...state.msgList, [type]: {
list: state.msgList[type].list.concat(data),
}};
} else {
state.msgList = {...state.msgList, [type]: {
list: data,
}};
}
},
cleanMsg(state, type) {
console.log('cleanMsg', state, type);
if (type && state.msgList[type]) {
state.msgList[type] = {
list: [],
}
}else {
state.msgList = {}
}
}
},
actions: {
async getMsgList({commit, state}, playload) {
const {group_id, lastest_create_unixtime = 0, oldest_create_unixtime = 0} = playload;
const result = await getMessageList({
group_id,
limit: 20,
});
const { data } = result;
commit('setMsgList', {result, group_id});
return data;
}
}
}
因为公司项目需求,然后在github上面找了一个headless cms directus,在安装directus的时候需要php、mysql、apache这三样东西,于是上网找了资料进行安装,遂把安装过程中出现的一些问题它记录下来;操作系统是windows。
点击去之后选择操作系统的位数,32还是64,然后点击下载;
启动命令
httpd –k start
停止命令
httpd –k stop
点击windows download进入里面选择操作系统对应位数的版本,然后点击下载
操作系统对应位数的版本,然后点击下载
启动mysql服务
net start mysql
退出mysql命令
mysql > \q
暂停mysql服务
net stop mysql
wampserver是一个集成了php、apache、mysql的工具,能够帮助我们快速搭建php开发环境;
下载 按照此教程进行下载https://www.cnblogs.com/Sabre/p/6728818.html
按照这个教程进行安装http://blog.csdn.net/wuguandi/article/details/53561253
安装完成之后,就可以直接在该目录的www目录下进行开了
修改www根目录及配置多目录访问
1. 修改wampserver的安装目录,在打开里面的“script”文件夹,用记事本打开里面的config.inc.php
// 注意,windows下表示路径的“\”在这里必须改为“/”)
// $wwwDir = $c_installDir.'/www'; => $wwwDir = 'F:/directus-build' 新的根目录
2. 修改wamp目录下Apach目录下面的httpd.conf文件
# DocumentRoot "${INSTALL_DIR}/www"
# <Directory "${INSTALL_DIR}/www/">
替换成需要的新目录
DocumentRoot "F:/directus-build/"
<Directory "F:/directus-build/">
3. 修改wamp目录下Apach目录下面的httpd-vhosts.conf文件
// 替换DocumentRoot与Directory 后面的路径
<VirtualHost *:80>
ServerName localhost
ServerAlias localhost
DocumentRoot "F:/directus-build/"
<Directory "F:/directus-build/">
Options +Indexes +Includes +FollowSymLinks +MultiViews
AllowOverride All
Require local
</Directory>
</VirtualHost>
到此为止就可以通过localhost访问新的www根目录了
4. 配置多站点,只需要在httpd-vhosts.conf文件内添加新的host
<VirtualHost *:80>
ServerName www.abc.com
ServerAlias www.abc.com
DocumentRoot "F:/php-demo/"
<Directory "F:/php-demo/">
Options +Indexes +Includes +FollowSymLinks +MultiViews
AllowOverride All
Require local
</Directory>
</VirtualHost>
5. 修改本机的hosts文件C:\Windows\System32\drivers\etc
// 添加如下内容,以此类推,然后我们就可以通过www.abc.com来访问F:/php-demo/目录下的内容了
127.0.0.1 localhost
127.0.0.1 www.abc.com
wampserver内的phpMyAdmin的初始登录名为root,密码为空
wampserver内mysql默认开启严格模式,可以直接使用设置选项禁用or自己修改my.ini文件,取消严格模式参考链接:https://www.cnblogs.com/lujs/p/6288806.html,设置严格模式 参考链接:http://blog.csdn.net/fdipzone/article/details/50616247
wampserver开启rewrite_module重写功能启用.htaccess文件,参考链接:http://blog.csdn.net/sgly2005/article/details/50718538
后端这条路上还是任重而道远啊!
const http = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
})
http.interceptors.request.use((config) => {
return config;
}, (error) => {
return Promise.reject(error)
})
http.interceptors.response.use((response) => {
if (response.code === 0) {
return response.data;
}
return Promise.reject({
msg: response.errMsg,
code: response.code
})
}, (err) => {
return Promise.reject(err)
})
function get(url, opts = {params: {}}) {
return new Promise((resolve, reject) => {
// 这里直接做一些对url及传入params的处理
http.get(url, opts).then((res) => {
resolve(res);
}).catch((err) => {
// 这里可以统一处理提示错误的方式
reject(err);
})
})
}
function post(url, data, opts) {
return new Promise((resolve, reject) => {
http.post(url, data, opts).then((res) => {
resolve(res);
}).catch((err) => {
reject(err);
})
})
}
let $httpResolve;
let apiDomain = '';
let $httpPromise = new Promise((resolve) => {
$httpResolve = resolve;
})
if (axios) {
// 这里可以换成任何基于axios封装的方法
$httpResolve(axios);
}
function makeHttp(method, arg) {
return $httpPromise
.then(($http) => {
return $http[method](...arg);
})
.then((res) => {
if (res.data.retCode === '-1500004') {
throw new Error('缺少参数');
}
return res;
})
}
function get(...args) {
const [url, ...rest] = args;
return $httpPromise.then(() => {
return makeHttp('get', [addBaseUrl(addBaseParam(url)), ...rest]);
})
}
function post(...args) {
const [url, ...rest] = args;
return $httpPromise.then(() => {
return makeHttp('post', [addBaseUrl(addBaseParam(url)), ...rest]);
})
}
function request(...args) {
const [url, ...rest] = args;
return $httpPromise.then(() => {
return makeHttp('request', [addBaseUrl(addBaseParam(url)), ...rest]);
})
}
// 利用async函数,对返回值进行处理
const wrap = (fn) => {
return async (...args) => {
const res = await fn(...args);
const { retCode, errMsg, data} = res.data;
if (retCode === 0) {
return data;
}
throw new BuildInHttpError(errMsg, retCode, res);
}
}
function BuildInHttpError(message, retCode, data) {
this.message = message;
this.data = data;
this.retCode = retCode;
}
function addBaseParam(url) {
if(/^\/api\//.test(url)) {
const param = {
token: 'afsad66332',
orgCode: 'kklhjsk'
}
return `${url}${url.indexOf('?') > 0 ? '&' : '?'}${Qs.stringify(param)}`
}
return url;
}
function addBaseUrl(url) {
if(/^\/api\//.test(url)) {
return `${apiDomain}${url}`
}
return url;
}
export default {
get,
post,
request,
apiGet: wrap(get),
apiPost: wrap(post),
apiRequest: wrap(request)
}
border-radius也是经常使用的一个CSS3属性,最近也经常在项目中使用,所以总结记录一番;
border-radius CSS3属性,兼容ie9+及现代浏览器,共有2个属性值,如下所示
border-radius: <length-percentage>{1,4} [ / <length-percentage>{1,4} 半径的第一个语法取值可取1~4个值 半径的第二个语法取值也可取1~4个值
常用的几种写法
一个属性值 border-radius: 20px; => border-radius: 20px 20px 20px 20px / 20px 20px 20px 20px;
两个属性值 border-radius: 20px 10px; => border-radius: 20px 10px 20px 10px / 20px 10px 20px 10px;
三个属性值 border-radius: 20px 10px 5px; => border-radius: 20px 10px 5px 10px / 20px 10px 5px 10px;
四个属性值 border-radius: 20px 10px 5px 15px; => border-radius: 20px 10px 5px 15px / 20px 10px 5px 15px;
设置两组属性值 border-radius: 40px 10px 5px 15px / 10px 5px 25px 10px; (40/10左上角, 10/5右上角, 5/25右下角, 15/10左下角),每个值代表的意思如下所示40px(左上上边半径) 10px(右上上边半径) 5px(右下下边半径) 15px(左下下边半径) / 10px(左上左边半径) 5px(右上右边半径) 25px(右下右边半径) 10px(左下左边半径);第一组值要么是上边要么是下边,第二组值要么是左边要么是右边;
不使用简写属性
border-top-left-radius: 40px; => border-top-left-radius: 40px 40px; border-top-right-radius:20px; border-bottom-right-radius: 30px; border-bottom-left-radius: 40px;
总结:理解一点一个元素总共可以设置4个圆角,而一个圆角是需要两个值来进行设置的
参考链接
https://developer.mozilla.org/zh-CN/docs/Web/CSS/border-radius
图片上传及图片压缩上传,这是我们日常开发中必不可少的功能,将图片转换为可识别的blob链接主要依赖于URl.createObjectURL方法;将图片转换为base64格式主要依赖于FileRender方法;将图片压缩依赖于canvas的drawImage方法
将图片文件对象转换为可识别的url,使用URL对象的createObjectURL静态方法,该方法返回一个返回一个DOMString ,包含一个唯一的blob链接(该链接协议为以blob:,后跟唯一标识浏览器中的对象的掩码)
从CanIUse上看pc端兼容ie10+,火狐、chrome、safari基本都支持
从CanIUse上看移动端兼容安卓4.0+,ios6.1+,其它大部分都是最新的版本才支持
function onchange (e) {
const
const url = window.URL.createObjectURL(e.target.files[0]) ||
window.webkitURL.createObjectURL(e.target.files[0])
img.src = url
}
将图片文件对象转换为base64,使用window对象下的FileReader方法,然后通过readAsDataURL方法读取指定blob中的内容,最后监听onload事件,在onload事件内获取到base64图片;
从CanIUse上看pc端兼容ie10+,火狐、chrome、safari基本都支持
从CanIUse上看移动端兼容安卓3.0+,ios6.0+,其它大部分都是最新的版本才支持
function onchange (e) {
const fileReader = window.FileReader()
fileReader.onload = function (res) {
img.src = res.target.result
}
fileReader.readAsDataURL(e.target.files[0])
}
利用canvas对图片进行压缩,压缩的原理就是缩放图片的尺寸及降低图片的质量来完成压缩
// drawImage的语法,共9个参数,img一定要是dom对象or虚拟的image实例,不能是url,sx, sy分别指画布的左上角坐标, sWidth, sHeight指图片在canvas上的宽高;dx, dy, dWidth, dHeight这4个坐标是针对图片元素的,表示图片在canvas画布上显示的大小和位置。sx,sy表示图片上sx,sy这个坐标作为左上角,然后往右下角的swidth,sheight尺寸范围图片作为最终在canvas上显示的图片内容。
cxt.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
// toDataURL,toBlob,mimeType表示canvas导出来的base64图片的类型,默认是png格式,也即是默认值是'image/png',我们也可以指定为jpg格式'image/jpeg'或者webp等格式。file对象中的file.type就是文件的mimeType类型,在转换时候正好可以直接拿来用(如果有file对象)。
qualityArgument表示导出的图片质量,只要导出为jpg和webp格式的时候此参数才有效果,默认值是0.92,是一个比较合理的图片质量输出参数,通常情况下,我们无需再设定。
canvas.toDataURL(mimeType, qualityArgument)
canvas.toBlob(callback, mimeType, qualityArgument)
function compressImage (src) {
const canvas = document.getElementById('uploadImage')
const cxt = canvas.getContext('2d')
const img = new Image()
img.src = src
img.onload = () => {
// 固定画布尺寸,也就是固定来输出图片的尺寸
canvas.witdh = 750
canvas.height = 562.5
cxt.drawImage(img, 0, 0, 750, 562.5)
// toDataURL返回一个base64链接
const newUrl = canvas.toDataURL('image/jpeg', 0.6)
// 或者可以使用toBlob方法返回一个二进制blob对象传到后台
canvas.toBlob((blob) => {
// 上传的逻辑,blob对象对img的src是无法识别的
}, 'image/jpeg', 0.6)
}
img.onerror = () => {}
}
所以从上面来看,如果我们使用的是vue等框架,那么则不需要考虑太多的兼容性,URL方式FileReader方法都是可选的,不过在实际生产中FileReader的兼容性更好
一个完整的图片上传实例
<template>
<div>
<div >
<p>表单上传</p>
<!-- enctype有三个值application/x-www-form-urlencoded(默认值),multipart/form-data, text/plain -->
<form action="/api/broker/customer/upload-image?token=fomozg1498552329&orgCode=yajuleadmin_test" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="上传" accept="image/jpeg, image/png">
</form>
</div>
<div>
<p>formData上传</p>
<input type="file" name="file" @change="changeFile">
</div>
<div>
<p>base64上传</p>
<input type="file" name="file" @change="changeFile2">
</div>
<div>
<img style="width: 200px; height: 200px" :src="imgSrc" alt="">
</div>
<div>
<canvas id="uploadImg"></canvas>
</div>
</div>
</template>
<script>
import http from 'broker-http';
import axios from 'axios'
export default {
props: {},
data() {
return {
imgSrc: ''
}
},
computed: {},
watch: {},
methods: {
uploadImg(params) {
// return http.apiPost('/api/broker/customer/upload-image', params)
// return $app.http.post('/api/broker/customer/upload-image', params)
return axios.post('/api/broker/customer/upload-image', params)
},
changeFile(e) {
console.log(e);
const file = e.target.files[0];
// URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。兼容ie10+
// const url = window.URL.createObjectURL(file);
const form = new FormData();
form.append('token', 'fomozg1498552329');
form.append('orgCode', 'yajuleadmin_test');
form.append('from', 'paas_app');
form.append('file', file);
form.append('file', 'file');
this.uploadImg(form).then((res) => {
console.log(res);
})
// this.imgSrc = url;
},
changeFile2(e) {
const file = e.target.files[0];
const fileReader = new window.FileReader();
fileReader.onload = (e) => {
// 转base64
this.imgSrc = e.target.result;
const form = new FormData();
form.append('token', 'fomozg1498552329');
form.append('orgCode', 'yajuleadmin_test');
form.append('from', 'paas_app');
form.append('file', e.target.result);
form.append('file', 'file');
this.createCanvas(e.target.result);
// this.uploadImg(form).then((res) => {
// console.log(res);
// })
}
fileReader.readAsDataURL(file)
},
createCanvas(src) {
var canvas = document.getElementById("uploadImg");
var cxt = canvas.getContext('2d');
canvas.width = 640;
canvas.height = 400;
var img = new Image();
img.src = src;
img.onload = function() {
// var w=img.width;
// var h=img.height;
// canvas.width= w;
// canvas.height=h;
// 将图像绘制于Canvas画布当中,
cxt.drawImage(img, 0, 0,640,400); // 表示将图片从画布得左上方0,0得位置画起,宽为640高为400,如果不写这来给你个参数,则使用图片本身得宽高
//canvas.toDataURL(type, encoderOptions);type图片格式,默认为 image/png。encoderOptions在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略;该方法返回的是一个包含data URI的字符串,该字符串可直接作为图片路径地址填入<img>标签的src属性当中
// $(".showPic").show().attr('src', canvas.toDataURL("image/jpeg", 0.9));
console.log(canvas.toDataURL("image/jpeg", 0.9));
// this.uploadImg({}).then((res) => {
// console.log(res);
// })
// axios({
// url: "/front/uploadByBase64.do",
// type: "POST",
// data: {
// "imgStr": canvas.toDataURL("image/jpeg", 0.9).split(',')[1]
// },
// success: function(data) {
// console.log(data);
// $(".showPic").show().attr('data-url',"/"+ data.url);
// }
// });
}
img.onload = () => {
const originWidth = img.width
const originHeight = img.height
let resultWidth = INIT_WIDTH
let resultHeight = INIT_HEIGHT
// 按比例缩放图片尺寸
if (originWidth > resultWidth || originHeight > resultHeight) {
if (originWidth > resultWidth && originHeight > resultHeight) {
const scal = Math.max(originWidth / resultWidth, originHeight / resultHeight)
resultWidth = originWidth / scal
resultHeight = originHeight / scal
} else if (originWidth > resultWidth) {
const scal = originWidth / resultWidth
resultHeight = originHeight / scal
} else if (originHeight > resultHeight) {
const scal = originHeight / resultHeight
resultWidth = originWidth / scal
}
} else {
resultWidth = originWidth
resultHeight = originHeight
}
console.log({originWidth, originHeight, resultWidth, resultHeight})
canvas.width = resultWidth
canvas.height = resultHeight
cxt.drawImage(img, 0, 0, resultWidth, resultHeight)
}
}
},
beforeCreate() {},
created() {}
}
</script>
参考链接
https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader
https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
https://www.zhangxinxu.com/wordpress/2017/07/html5-canvas-image-compress-upload/
要了解深浅拷贝,首先要了解js里面的数据类型,js里面共有两种数据类型,第一类5种基本类型,第二类1种引用类型,二者的主要区别如下
传值与传址的区别,两者都是针对变量在赋值的时候而言的,传值指的时值得传递(即基本类型的赋值),传址指的是引用的赋值(及引用类型的赋值)
var shallowCopy = function (src){
var obj = {};
for(var prop in src){
if(src.hasOwnProperty(prop)){
obj[prop] = src[prop]
}
}
return obj
}
深拷贝核心**就是递归去复制所有的引用类型,然后在复制的时候需要区分下数组与对象,可以复制函数
// 第一种方式
var deepCopy = function (source,target){
var c = target || {};
for(var prop in source){
if(typeof source[prop] === "object"){
if(source[prop].constructor === Array){
c[prop] = [];
}else if (source[prop].constructor === Object){
c[prop] = {};
}
deepCopy(source[prop],c[prop]);
}else {
c[prop] = source[prop]
}
}
return c
}
// 第二种方式
var deepCopy2 = function (obj) {
return JSON.parse(JSON.stringify(obj));
}
// 第三种方式与第一种一样只是换个写法
var deepCopy3 = function(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy3(obj[key]) : obj[key];
}
}
return newObj;
}
directives.directive("myLaydate", ['$timeout', function($timeout) {
return {
require: "?ngModel",
restrict: "A",
scope: {
ngModel: "=",
maxDate: "@",
minDate: "@"
},
link: function(scope, ele, attrs, ngModel) {
var date = null,
config = {};
// console.log(scope.$parent.startTime);
var timer = null;
$timeout.cancel(timer);
timer = $timeout(function() {
//初始化参数
config = {
elem: "#" + attrs.id,
format: attrs.format != undefined && attrs.format != "" ? attrs.format : "YYYY-MM-DD hh:mm:ss",
max: attrs.hasOwnProperty("maxDate") ? attrs.maxDate : laydate.now(+5),
min: attrs.hasOwnProperty("minDate") ? attrs.minDate : laydate.now(-5),
istime: true,
istoday: false,
choose: function(data) {
scope.$apply(setViewValue);
},
clear: function() {
ngModel.$setViewValue("");
}
};
//console.log(ngModel); //这个ngModel是一个控制器,但不是ctrl1,我猜是angular自定义针对这个指令的控制器
//初始化日期实例
date = laydate(config);
//监听日期最大值
if (attrs.hasOwnProperty("maxDate")) {
attrs.$observe("maxDate", function(val) {
config.max = val;
});
}
//监听日期最小值
if (attrs.hasOwnProperty("minDate")) {
attrs.$observe("minDate", function(val) {
config.min = val;
});
}
//模型值同步到视图上
ngModel.$render = function() {
ele.val(ngModel.$viewValue || '');
}
//监听元素上的事件
ele.on("blur keyup change", function() {
scope.$apply(setViewValue);
});
//更新模型上的视图值
function setViewValue() {
var val = ele.val();
ngModel.$setViewValue(val);
}
setViewValue();
}, 0);
}
}
}]);
directives.directive("independentpage", ['httpService', function(httpService) {
return {
restrict: "E",
templateUrl: "independentPage",
scope: {
// pagedata: "=pageData",
// haspage: "=hasPage",
pagedata: "=",
haspage: "=",
config: "@",
carrierid: "=",
carrierval: "="
},
link: function(scope, ele, attrs) {
scope.$watch("pagedata", function(newData, oldData) {
if (!newData) return;
if (newData.offset != 1) return;
finish(newData);
});
//获取总条数
var finish = function(data) {
//console.log(scope.pagedata);
if (!scope.config) return;
scope.pagedata = angular.fromJson(data);
scope.haspage = angular.fromJson(scope.haspage);
scope.config = angular.fromJson(scope.config);
scope.total = scope.pagedata.total;
//获取当前页
scope.offset = parseInt(scope.pagedata.offset);
//获取跳转页面的当前页
scope.selectOffset = scope.pagedata.offset;
//获取当前页面需要展示的条数
scope.limit = scope.pagedata.limit;
//计算需要分页数量
scope.pages = Math.ceil(scope.total / scope.limit);
//控制分页现实的条数
scope.newPages = scope.pages > 5 ? 5 : scope.pages;
//设置一个现实页码空数组
scope.pageList = [];
//添加显示的数据
scope.current_math = "" + scope.limit || "20";
scope.select_math = [{
id: 1,
value: '20'
}, {
id: 2,
value: '50'
}, {
id: 3,
value: '100'
}, {
id: 4,
value: '1000'
}];
//循环添加页码
if (!scope.haspage) {
//保证无分页时,有显示第一页页码
scope.pageList = [1];
} else {
for (var i = 0; i < scope.newPages; i++) {
scope.pageList.push(i + 1);
}
}
//点击分页按钮获取当前分页数值
scope.selectPage = function(page) {
if (page == "" || isNaN(page)) return;
var _page = parseInt(page),
newpageList = [];
if (_page < 1 || _page > scope.pages) return;
if (_page == 1 || _page == 2) {
for (var i = 0; i < scope.newPages; i++) {
newpageList.push(i + 1);
}
scope.pageList = newpageList;
};
if (_page > 2) {
//重新进行分页
if (_page == scope.pages - 1) {
for (var i = (_page - 4); i < ((_page + 2) > scope.pages ? scope.pages : (_page + 2)); i++) {
if (i >= 0) {
newpageList.push(i + 1);
}
}
} else if (_page == scope.pages) {
for (var i = (_page - 5); i < ((_page + 2) > scope.pages ? scope.pages : (_page + 2)); i++) {
if (i >= 0) {
newpageList.push(i + 1);
}
}
} else {
for (var i = (_page - 3); i < ((_page + 2) > scope.pages ? scope.pages : (_page + 2)); i++) {
newpageList.push(i + 1);
}
}
scope.pageList = newpageList;
}
scope.offset = _page;
scope.isActivePage(_page);
scope.selectOffset = _page;
scope.config.dataObj.offset = scope.offset;
scope.config.dataObj.limit = scope.limit;
//console.log(scope.config);
httpService.getDatas(scope.config.url, scope.config.dataObj, scope.getCommonListSucc);
};
//设置当前选中页样式
scope.isActivePage = function(page) {
return scope.offset == page;
};
//上一页
scope.Previous = function() {
scope.selectPage(scope.offset - 1);
};
//下一页
scope.Next = function() {
scope.selectPage(scope.offset + 1);
};
//首页
scope.firstPage = function() {
if (scope.offset == 1) {
return;
} else {
scope.selectPage(1);
}
}
//尾页
scope.lastPage = function() {
if (scope.offset == scope.pages) {
return;
} else {
scope.selectPage(scope.pages);
}
}
//分页请求成功回调
scope.getCommonListSucc = function(data) {
//console.log(data);
if (data.api_name == "customer/getCustomerList") {
scope.$parent.customerLists = data.list;
scope.pagedata = data.paging_data;
console.log(scope.carrierid);
scope.carrierid = [];
scope.carrierval = [];
console.log(scope.carrierid);
console.log(scope);
scope.haspage = data.has_paging;
} else if (data.api_name == "productinfo/getProductInfoList") {
if (scope.$parent.productLists) {
scope.$parent.productLists = data.list;
} else {
scope.$parent.childLists = data.list;
}
scope.pagedata = data.paging_data;
scope.haspage = data.has_paging;
} else if (data.api_name == "locationinfo/getLocationList") {
scope.$parent.kuweiLists = data.list;
scope.pagedata = data.paging_data;
scope.haspage = data.has_paging;
} else if (data.api_name == "Zonegroup/getZoneGroupList") {
scope.$parent.areaLists = data.list;
scope.pagedata = data.paging_data;
scope.haspage = data.has_paging;
} else if (data.api_name == "Zoneinfo/getZoneInfoList") {
scope.$parent.hsLists = data.list;
scope.pagedata = data.paging_data;
scope.haspage = data.has_paging;
} else if (data.api_name == "Locationgroupone/getLocationGroupOneList") {
scope.$parent.lOneLists = data.list;
scope.pagedata = data.paging_data;
scope.haspage = data.has_paging;
} else if (data.api_name == "Locationgrouptwo/getLocationGroupTwoList") {
scope.$parent.lTwoLists = data.list;
scope.pagedata = data.paging_data;
scope.haspage = data.has_paging;
} else if (data.api_name == "asntrace/getAsntraceList") {
scope.$parent.traceLists = data.list;
scope.pagedata = data.paging_data;
scope.haspage = data.has_paging;
} else if (data.api_name == "Inventory/getCheckTaskList") {
scope.$parent.getInventoryTaskListSucc(data);
} else if (data.api_name == "Stocktranslog/getlotList") {
scope.$parent.getTraceBatchListSucc(data);
} else if (data.api_name == "Shipmentorder/inventoryDetection") {
scope.$parent.stockCheckList = data.list;
scope.pagedata = data.paging_data;
scope.haspage = data.has_paging;
}
}
//改变显示数据条数时请求
scope.pageMathChange = function() {
//console.log(scope.config);
scope.config.dataObj.offset = 1;
scope.config.dataObj.limit = scope.current_math;
httpService.getDatas(scope.config.url, scope.config.dataObj, scope.getCommonListSucc);
}
}
}
}
}]);
//全选
directives.directive("allCheck", function() {
return {
restrict: "A",
scope: {
scanList: "="
},
link: function(scope, ele, attrs) {
//全选
$(ele).on("click", function(event) {
var e = window.event || event;
e && e.stopPropagation ? e.stopPropagation() : window.event.cancelbubble = true;
var status = $(this).prop("checked");
console.log(status);
var tds = $(ele).parents(".com-table").find(".check-item");
scope.$parent.initPutawayList.length = 0;
tds.each(function(i, check) { //第一个参数为元素结合的下标,第二个参数为元素本身(原生dom对象)
$(check).prop("checked", status);
if (status) {
scope.$parent.initPutawayList.push($(this).attr('id'));
} else {
scope.$parent.initPutawayList.length = 0;
}
});
});
scope.$watch("scanList", function(newValue, oldValue) {
console.log(newValue, oldValue);
if (typeof(newValue) == "undefined") return;
$(ele).length ? $(ele).prop("checked", false) : "";
//清空数据操作
scope.$parent.initPutawayList.length = 0;
});
}
}
});
//单选
directives.directive("itemCheck", function() {
return {
restrict: "A",
scope: {},
link: function(scope, ele, attrs) {
//单选
$(ele).on("click", function(event) {
var e = window.event || event;
e && e.stopPropagation ? e.stopPropagation() : window.event.cancelbubble = true;
var status = $(this).prop("checked"),
tds = $(ele).parents(".com-table").find(".check-item"),
ths = $(ele).parents(".com-table").find(".check-all"),
isAll = true;
console.log(status);
if (status) {
tds.each(function(i, check) {
if (!$(check).prop("checked")) {
isAll = false;
return false;
}
return true;
});
}
$(ths).prop("checked", status && isAll);
scope.$parent.initPutawayList.length = 0;
tds.each(function(i, check) {
if ($(check).prop("checked")) {
scope.$parent.initPutawayList.push($(check).attr('id'));
}
});
});
}
}
});
directives.directive("contentmenu", function() {
return {
restrict: "EA",
scope: {
contextMenuParent: "@",
contextMenuChild: "@",
cancel_order: "&closeMyOrder",
close_order: "&cancelMyOrder",
shipment_order: "&shipmentMyOrder",
distribution_order: "&distributionMyOrder",
cancel_allocation_order: "&cancelMyAllocationOrder",
create_wave_plan: "&createMyWavePlan",
picking_order: "&pickingMyOrder",
cancel_picking_order: "&cancelPickingMyOrder",
distribution_lin: "&distributionMyOrder",
cancel_allocation_lin: "&cancelMyAllocationOrder",
picking_lin: "&pickingMyOrder",
cancel_picking_lin: "&cancelPickingMyOrder",
},
replace: true,
templateUrl: "contentmenu_tpl",
link: function(scope, ele, attrs) {
scope.isParentShow = true;
scope.contextMenuParent = [];
scope.contextMenuChild = [];
var contentShow = function(e) {
var e = e || window.event,
x = e.pageX,
y = e.pageY;
//判断菜单出现在右边的位置
var clientx = $(window).width(),
eleWidth = $(ele).outerWidth();
if (clientx - x < eleWidth) {
//17是为了避免水平滚动条的出现
ele.css({
"left": x - eleWidth - 17 + "px",
"top": y + "px"
}).addClass("content-active");
} else {
ele.css({
"left": x + "px",
"top": y + "px"
}).addClass("content-active");
}
//执行当前行上的点击事件
// $(e.target).parent().click();
if ($(this).attr("id") === "parent_table") {
scope.$apply(function() {
scope.isParentShow = true;
scope.contextMenuParent = angular.fromJson(scope.contextMenuParent);
});
} else if ($(this).attr("id") === "child_table") {
scope.$apply(function() {
scope.isParentShow = false;
scope.contextMenuChild = angular.fromJson(scope.contextMenuChild);
});
}
console.log(scope);
return false;
}
$("#parent_table").on("contextmenu", contentShow);
$("#child_table").on("contextmenu", contentShow);
$(document).on("contextmenu", function() {
scope.isParentShow = true;
return false;
})
$(document).on("click", function() {
ele.removeClass("content-active");
});
ele.on("click", function(e) {
var e = e || window.event,
funcname = e.target.getAttribute("fun");
scope.$apply(function() {
scope.$parent.initStatus = true;
console.log(scope);
funcname && scope[funcname]();
//scope.$parent.cancelOrder(); //指令内部调用控制器内函数的第一种方式
// scope.cancelOrder(); //di二种方式
// scope.cancelMyOrder(); //第三种方式
});
});
}
}
});
directives.directive('sureSingleReview', function() {
return {
restrict: "A",
link: function(scope, ele, attrs) {
//用定时器模拟多线程,一个读取sku的线程,一个复核调用打印的线程
var testArr = [],
timer1 = null,
timer2 = null,
flag1 = true,
flag2 = true,
num = 0;
ele.on("keypress", function(event) {
console.log(event.target);
if (event.keyCode == 13) {
$(this).focus();
$(this).select();
$(this).prop("disabled", true);
testArr.push($(this).val());
flag1 = false;
flag2 = false;
num++;
if (num == 1) {
test1();
test2();
}
}
});
var test1 = function() {
clearTimeout(timer1);
timer1 = setTimeout(function test3() {
if (flag1) return;
$(ele).prop("disabled", false);
$(ele).focus();
timer1 = setTimeout(test3, 200);
}, 200);
}
var test2 = function() {
clearTimeout(timer2);
timer2 = setTimeout(function test4() {
if (flag2) return;
if (testArr.length && $("#" + attrs.ele).val()) {
var currentSku = testArr.shift();
scope.fnSureReview(currentSku);
scope.$digest();
} else {
flag1 = true;
flag2 = true;
num = 0;
$(ele).prop("disabled", false);
// $(ele).focus();
$("#active-tip").find(".cfbtn").eq(0).focus();
testArr.length = 0;
}
timer2 = setTimeout(test4, 400);
}, 400);
}
}
}
})
directives.directive("createIframe", function() {
return {
restrict: "EA",
controller: ['$scope', function($scope) {
this.createIframe = function(url, auto, type) { // 区分打印类型
$("#mainIframe0").length && $("#mainIframe0").remove();
var iframe = document.createElement("iframe");
iframe.name = "mainIframe0";
iframe.id = "mainIframe0";
iframe.className = "mainIframe";
iframe.src = url;
if (iframe.attachEvent) {
$(".iframe-wrap-show").css("display", "block");
if (type) {
$(".iframe-wrap-show").css("opacity", "0");
}
iframe.attachEvent("onload", function() {
$(".iframe-wrap-show .next").removeClass("print_disabeld");
$(".iframe-wrap-show .prev").removeClass("print_disabeld");
$(".iframe-wrap-show .start_print").removeClass("print_disabeld");
$(".iframe-wrap-show .close_print").removeClass("print_disabeld");
var oIframe = window.frames["mainIframe0"];
oIframe.focus();
if (auto) {
oIframe.printlabel();
}
});
} else {
iframe.onload = function() {
$(".iframe-wrap-show").css("display", "block");
if (type) {
$(".iframe-wrap-show").css("opacity", "0");
}
$(".iframe-wrap-show .next").removeClass("print_disabeld");
$(".iframe-wrap-show .prev").removeClass("print_disabeld");
$(".iframe-wrap-show .start_print").removeClass("print_disabeld");
$(".iframe-wrap-show .close_print").removeClass("print_disabeld");
var oIframe = window.frames["mainIframe0"];
oIframe.focus();
if (auto) {
oIframe.printlabel();
}
};
}
$(".iframe-wrap-show .iframe-content").prepend(iframe);
},
this.createIframeSingle = function(url, classname) {
$("#dataIframe").length && $("#dataIframe").remove();
var iframe = document.createElement("iframe");
iframe.name = "dataIframe";
iframe.id = "dataIframe";
iframe.className = "dataIframe";
iframe.src = url;
if (iframe.attachEvent) {
iframe.attachEvent("onload", function() {
var oIframe = window.frames["dataIframe"];
oIframe.focus();
});
} else {
iframe.onload = function() {
var oIframe = window.frames["dataIframe"];
oIframe.focus();
};
}
$("." + classname).prepend(iframe);
}
}],
controllerAs: "createIframeCtrl"
}
});
directives.directive("printFile", ['DialogService', 'TranslateService', function(DialogService, TranslateService) {
return {
restrict: "EA",
require: "^createIframe",
scope: {
orderNo: "=",
soNo: "=",
secondExportClose: "&"
},
link: function(scope, ele, attrs, createIframeCtrl) {
//console.log(scope.soNo);
ele.on("click", function() {
if (attrs.isFirst === "1") {
//打印
if ($(this).hasClass("btn-bg")) return;
if (!scope.$parent.canPrint) return;
var str = TranslateService.t_lang('send_order_hint');
DialogService.InfoMd(str, function() {
$(".delivery-mask").css("display", "block");
$(".print-file-wrap").css("display", "block");
$(".print-file-btns").attr("num", "1");
var url = layout_config.viewUrl + "outstock/deliveryorder/printViewInvoice?order_no=" + scope.orderNo + "&isfirst=1";
createIframeCtrl.createIframeSingle(url, "print-file-wrap");
});
} else if (attrs.isFirst === "2") {
//补打印
if ($(this).hasClass("btn-bg")) return;
if (typeof(scope.soNo) == "undefined" || !scope.soNo) return;
$(".delivery-mask").css("display", "block");
$(".print-file-wrap").css("display", "block");
$(".print-file-btns").attr("num", "2");
// SL170419000010
var url = layout_config.viewUrl + "outstock/deliveryorder/printViewInvoice?order_no=" + scope.soNo + "&isfirst=2";
createIframeCtrl.createIframeSingle(url, "print-file-wrap");
scope.$apply(function() {
scope.secondExportClose();
});
}
});
}
}
}]);
services.service("httpService", ['$http', 'TranslateService', 'BackLogin', function($http, TranslateService, BackLogin) {
return {
getDatas: function(url, obj1, succCallBack, errorCallBack) {
return $http({
method: "GET",
url: url,
params: obj1 || {}
}).success(function(data) {
succCallBack && succCallBack(data);
}).error(function(data) {
errorCallBack && errorCallBack(data);
})
},
postDatas: function(url, obj1, succCallBack, errorCallBack) {
return $http({
method: "POST",
url: url,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
transformRequest: function(obj) {
var str = [];
for (var p in obj) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
return str.join("&");
},
data: obj1 || {}
}).success(function(data) {
succCallBack && succCallBack(data);
}).error(function(data) {
errorCallBack && errorCallBack(data);
})
}
}
}]);
services.factory('myLoading', ["$rootScope", "BackLogin", function($rootScope, BackLogin) {
var count = 0;
return {
request: function(config) {
$rootScope.loading = true;
return config;
},
response: function(response) {
$rootScope.loading = false;
return response;
},
responseError: function(rejection) {
console.log(rejection);
if (!rejection.data.status) {
count++;
$rootScope.loading = false;
if (count > 1) return;
console.log($("#backLoginEle"));
if ($("#backLoginEle").length) return; //只弹出专门的退出登录弹窗
BackLogin.locationLogin(rejection.data);
}
return rejection;
}
};
}]);
services.service("EventBus", function() {
var eventMap = {};
return {
on: function(eventType, handler) {
if (!eventMap[eventType]) {
eventMap[eventType] = [];
}
eventMap[eventType].push(handler);
},
off: function() {
for (var i = 0; i < eventMap[eventType].length; i++) {
if (eventMap[eventType][i] === handler) {
eventMap[eventType].splice(i, 1);
break;
}
}
},
fire: function(event) {
var eventType = event.type;
if (eventMap && eventMap[eventType]) {
for (var i = 0; i < eventMap[eventType].length; i++) {
eventMap[eventType][i](event);
}
}
}
}
});
angular的数据绑定采用什么机制,简要的说下实现的原理;
双向数据绑定 实现原理是通过脏值检测机制 angular内部对每一个变量都会绑定一个$wacth方法,并且把这些方法push到一个$watch数组中,当触发事件or请求数据等时会触发angular进入脏值
检测,angular内部会调用$digest方法,会去对$wacth的变量进行遍历,当一定时间范围内所有变量oldvalue与newvalue相等时,就会去更新视图停止此次轮循,实现模型与视图的双向数据绑定,另外同一轮脏值检测遍历
的次数不会超过10次,超过时会抛出异常;
3.angular中控制器与控制器之间进行通信的方式有哪些,举出你常用的方式,并简述下优缺点;
1.广播方式 向上广播$emit,向下广播$bordcast,接受广播使用$on,注意的时向上广播可以在某一层时停止,而向下广播不能再某层停止,当scope数比较深时,不建议使用广播的方式来进行通信;
2.服务方式 利用服务来实现在各个控制器中的通信,建议使用这种方式,快速,方便;
3.继承方式 控制器与控制器之间的作用域scope会形成scope树,父子级间可以通过继承的方式来进行通信;
4.angular应用中常用的路由库有哪些,各自的区别是什么;
1.angular自带的ngRouter路由模块,主要缺点不支持视图之间的嵌套
2.angular ui-router 支持多层视图之间的嵌套,提供了一些路由之间跳转的方法等
5.angular自定义指令内scope继承的方式有几种,分别列出来并简述,这几种的区别;
共3种方式
1.scope不定义or值为false,表示指令内部的作用域就是当前控制器内的作用域,可以在指令内部直接使用;
2.scope值为true时,表示继承自当前控制器的作用域,可以在指令内部访问控制器内部的变量,但是在控制器内不能访问指令的变量;
3.scope值为一个对象时,表示一个独立的作用域与当前控制器所在的作用域无继承关系,与外部控制器进行数据交互有三种绑定策略
1.@ 单项绑定
2.= 双向绑定
3.& 方法绑定
6.ng-if跟ng-show/hide的区别有哪些? ng-repeat迭代数组的时候,如果数组中有相同值,会有什么问题,如何解决? ng-click中写的表达式,能使用JS原生对象上的方法,比如Math.max之类的吗?为什么?
ng-if跟ng-show/hide的区别有哪些 ng-if是通过创建与删除dom的方式来控制元素的显示与隐藏,另外ng-if会创建独立的作用域,ng-show/hide是通过display来进行控制元素的显示与隐藏,且不会创建独立作用域;
ng-repeat迭代数组的时候,如果数组中有相同值,会抛出异常,这是angular它会根据一个唯一表示符来保证ng-repeat生成的每个元素都市唯一的,解决方法是在后面加一个track by $index
ng-click中写的表达式不能使用js原生的方法,原因是angular表达式只能识别angular保证的方法,如果需要在表达式中使用js原生方法,需要在控制器or过滤器中进行包装之后才能使用;
box-shadow虽然一直在使用,但是没有总结过,这几天在项目中频繁的用到,于是总结记录一番。
box-shadow CSS3 的属性,目前兼容ie9+及现代浏览器,共有6个属性值,如下所示
box-shadow: outside|inside offset-x offset-y blur-radius spread-radius color
几种常用的场合
设置单边阴影如下边阴影,关键在spread-radius扩大收缩参数,这个时候需要设置成负值,不然水平方向会有阴影,其它同理box-shadow: 0 10px 10px -5px #ccc;
;
每边设置不同的阴影,需要注意的时候,因该是渲染了四次,只不过每组组侧重的阴影不一样,才能设置不同颜色的阴影,这里的blur-radius不能大,大的话阴影之间会互相渗透
box-shadow: 3px 0 1px red, -3px 0 10px #ccc, 0 -3px 1px blue, 0 3px 1px #000;
参考链接
https://developer.mozilla.org/zh-CN/docs/Web/CSS/box-shadow
开始一个新项目,我们需要使用git来管理我们的代码,于是总结了下,常用的一些场景;
git clone 项目链接 克隆下来之后实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin
所以此时使用git remote查看远程分支显示结果为origin or 使用git remote -v显示更详细的信息,结果会显示抓取(fetch)与推送(push)的origin地址,如果没有推送权限是看不到push地址的
此时就看怎么开发了,如果已经远程建了分支,那么只需要git branch查看本地分支,git branch -a 查看远程分支;然后使用git checkout 远程分支名(等价于git checkout -b branch-name origin/branch-name)在本地创建和远程分支对应的分支
建立本地分支与远程某分支的关联git branch --set-upstream branch-name origin/branch-name
开发完成之后git add / git commit -m 之后,先更新下远程分支git pull origin 远程分支名(注意,这里简写了,原因是本地分支与远程分支名称一致);git push origin 远程分支名
git push origin master 将本地的master分支推送到远程仓库(主分支)
一个常规的开发,上传流程就结束了
使用git branch --set-upstream-to命令
git branch --set-upstream-to=origin/master master 将远程master分支与本地master分支关联起来,方便在master分支上直接使用命令,git pull or git push 而不是使用git pull origin master or git push origin master
git branch --set-upstream-to=origin/dev dev 同master分支是一样的
git checkout -b dev 创建本地dev分支
git push origin dev:dev 创建远程dev分支并与本地dev分支关联起来
git checkout -b dev origin/dev // 创建远程分支
git push origin dev // push到远程成的
如果远程仓库没有dev分支 git push origin dev:dev 创建远程dev分支,并与本地dev分支关联起来,注意如果没有远程dev分支,直接add、commit之后,执行push命令即可,push命令也可以在该本地分支
创建之后执行也是一样的
如果有远程dev分支,直接使用git checkout dev === git checkout dev origin/dev即可创建本地分支dev,并且和远程origin/dev分支关联,本地dev分支的初始代码和远程的dev分支代码一样
删除远程dev分支 git push origin :dev
删除远程dev分支 git push origin --delete dev
删除本都dev分支 git branch -d dev
git pull <远程主机> <远程分支>:<本地分支>
git pull origin master:my_test origin仓库的master分支拉取并合并到本地的my_test分支上
git pull origin master 如果省略本地分支,则将自动合并到当前所在分支上
这里有一点需要注意的是在一个a分支上执行git pull origin b分支,是会把远程b分支的代码直接合并到本地a分支上来的;所以git pull的是会一定要注意pull的远程分支
git push <远程主机名> <本地分支名>:<远程分支名>命令中的本地分支是指将要被推送到远端的分支,而远程分支是指推送的目标分支,即将本地分支合并到远程分支。
如果省略远程分支名,则表示将本地分支推送与之存在”追踪关系”的远程分支(通常两者同名),
如果该远程分支不存在,则会被新建;
如果省略本地分支名,则表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支,这条命令是删除远程master分支git push origin :master ===> git push origin --delete master
如果当前分支与远程分支之间存在追踪关系(即分支名相同),则本地分支和远程分支都可以省略。git push origin 将当前分支推送到origin主机的对应分支
git checkout -b dev
修改文件
git add .
git commit -m 'xx'
git checkout master
git merge dev
git pull
git push 整个流程就结束了,然后继续切换到dev分支上继续开发,继续上述的步骤
如果远程仓库没有dev分支
git checkout -b dev
git push origin dev:dev //创建远程dev分支
修改文件
git add .
git commit -m 'xx'
//这里有两种情况,推荐使用第一种方式
第一种,现在本地切换到本地master分支,合并之后再push
git checkout master
git merge dev
git pull
git push //整个流程结束,然后切换到dev分支,pull与push一下dev分支,然后继续开发
第二种是直接再dev分支上push,然后在github or gitlab上根据提示合并代码
git pull
git push
git checkout master
git pull
如果远程仓库有dev分支
git checkout dev
修改内容
git add .
git commit -m 'xx'
合并的方式同上
解决test上分支的冲突
进入到有冲突的那个项目内
git pull 拉取最新代码,注意是test分支
git checkout 到修改提交代码的分支,然后git pull
git checkout test分支,然后git merge 刚刚提交代码有冲突的分支,然后会提示有代码冲突
打开有冲突的文件,然后手动解决冲突
解决完成之后,点击resolve,然后点击ok
git push 就在test分支上,然后git status查看下,整个过程冲突就算解决了
解决release分支上的冲突
在本地电脑上进入到有冲突的那个项目内
进入到对应的开发分支,然后把本地最新的master分支合到开发分支
然后提交代码,让测试重新build
上面这些操作步骤是可以简化的,我们不需要去checkout到开发的分支并且把代码拉下来;直接进入到test or release分支之后,直接git merge origin/branch_name 直接去merge远程分支即可,然后解决冲突在重新上传test or release分支
npm login --registry=https://xxx.cn/repository/xxx/ --scope=@xxx登录,输入用户名与密码还有邮箱,云客的用户名是xxx 密码是xxx
在本地包目录如angular_xx目录内执行yarn link构建软链接,然后在使用仓库的目录内执行yarn link 目标仓库名称 如yarn link @xxxke/angular-xx,这个时候在使用仓库的目录内直接修改
就会直接在本地包目录来也同步完成修改
调式完成之后,在本地包内,记录下change.log及在package.json内改下version +1,然后git add / git commit / git pull / git push / yarn publish发布 注意本地包开发都是在master分支
然后在使用仓库的目录更改package.json文件内,刚刚发布包的最新版本号,然后在yarn-lock文件内也找到对应的包,并更改版本号,然后执行yarn or yarn install 则可以重新yarn start了
注意直接在node_modules内改本地包,改完需要yarn start重启一次,才会生效
查看gitlab服务器的服务器地址,就是打开一个项目,然后查看它的ssh链接,前面那部分就是地址,然后可以通过ssh -T 服务器地址来判断当前电脑是否关联了gitlab
如[email protected]:xxx/p_xxx_back.git 使用ssh -T [email protected]
ssh -T [email protected] 查看是否关联github成功
第一种方式
第二种方式
git log -p src/preview-kit/linux-2.6.34/drivers/usb/gadget/mv_gadget.js 显示关于这个文件所提交的commit,同时把修改的内容也显示出来
当我们需要紧急去其它分支处理问题时,需要把当前分支上已经修改的内容进行冻结,方便切换到其它分支进行开发
git stash
git stash pop 解除冻结
这里需要注意的一点是,当我在当前分支git stash之后,切换到其它分支,当其它分支有冲突,并解决了冲突提交之后,在回到之前开发的分支,并使用git stash pop之后,会报错,这时候的解决方法是,使用git status查看是否有修改or新增的文件,如果有push完之后,就可以继续git stash pop
git config --list 查看项目git配置项
// 配置全局用户名与邮箱
git config global user.name 'xxx'
git config global user.email 'xxx'
// 配置当前项目用户名与邮箱
git config user.name 'xxx'
git config user.email 'xxx'
git merge --abort
1 现在a分支上查看需要合并的commit_id
2 git checkout b
3 git cherry-pick commit_id
如果没有冲突,直接git push origin a ,如果有冲突,解决完冲突之后在git add . git commit -m 'xxx' git push ;
如果不想要这一次的合并来,直接执行git cherry-pick --abort
直接丢弃本次的commit内容
git reset --hard HEAD^
直接将本次的commit的内容还原到暂存区
git reset --soft HEAD^
添加附注标签
git tag -a v0.1 -m 'xxxx' 在当前分支最新的commit_id上创建标签
git push origin v0.1
添加简单标签
git tag v0.1
git push origin v0.1
两者的区别是git show v0.1查看tag信息的时候,附注标签是能过看到提交信息及注释,而简单标签是看不到的
针对某个commit_id打tag
git tag -a v0.1 -m 'xxxx' commit_id
app.js
import express from 'express';
import fs from 'fs';
import path from 'path';
import MovieRouter from './router/movieRouter'
const bodyParser = require('body-parser');
const multer = require('multer');
const upload = multer(); // for parsing multipart/form-data
const app = express();
app.use(bodyParser.json()); // for parsing application/json
app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
app.use('/js', express.static('./views/js'));
app.get('/', (req, res) => {
res.sendFile(path.resolve('views/index.html'), {
header: 'text/html'
})
});
app.use('/movie', MovieRouter);
app.listen(9527, () => {
console.log('启用成功');
});
movieRouter.js
import express from 'express'
import axios from 'axios'
const router = express.Router();
const apiDomain = 'http://api.douban.com/v2/movie/';
router.get('/search', async(req, res) => {
try {
const { keyWord } = req.query;
const result = await axios.get(`${apiDomain}search`, {params: {q: keyWord}});
res.json({
code: result.status,
result: result.data
})
} catch (err) {
res.json({
code: -100086,
error: error
})
}
})
/**
* 榜单类接口,如Top250等
* */
router.get('/:type', async (req, res) => {
try {
const { type } = req.params;
console.log('type', type)
const result = await axios.get(`${apiDomain}${type}`);
res.json({
code: result.status,
result: result.data
})
} catch (error) {
res.json({
code: -100086,
error: error
})
}
})
/**
* 电影详情类接口
* */
router.get('/subject/:id', async(req, res) => {
try {
const { id } = req.params;
console.log('id', id);
const result = await axios.get(`${apiDomain}subject/${id}`);
res.json({
code: result.status,
result: result.data
})
} catch (err) {
res.json({
code: -100086,
error: error
})
}
})
export default router;
musicRouter.js
import express from 'express'
import axios from 'axios'
const router = express.Router();
const apiDomain = 'http://tingapi.ting.baidu.com/v1/restserver/ting';
/**
* 获取歌单
*
* @type Number 类型 1-新歌榜,2-热歌榜,11-摇滚榜,12-爵士,16-流行,21-欧美金曲榜,22-经典老歌榜,23-情歌对唱榜,24-影视金曲榜,25-网络歌曲榜
* @size Number 返回条数
* @offset Number 当前页面
* */
router.get('/billList', async (req, res) => {
try {
const { type, size, offset } = req.query;
console.log('type', type);
const result = await axios.get(`${apiDomain}`, {params: {method: 'baidu.ting.billboard.billList', type, size, offset}});
res.json({
code: result.status,
result: result.data
})
} catch (error) {
res.json({
code: -100086,
error: error
})
}
})
/**
* 图书搜索
*
* @keyWord Sting | Number 关键字
* */
router.get('/search', async(req, res) => {
try {
const { keyWord } = req.query;
console.log('keyWord', keyWord);
const result = await axios.get(`${apiDomain}`, {params: {method: 'baidu.ting.search.catalogSug', query: keyWord}});
res.json({
code: result.status,
result: result.data
})
} catch (error) {
res.json({
code: -100086,
error: error
})
}
})
/**
* 获取推荐列表
*
* @song_id Number 歌曲id
* @num Number 返回条数
* */
router.get('/recommandList', async (req, res) => {
try {
const { song_id, num } = req.query;
console.log('song_id', song_id)
const result = await axios.get(`${apiDomain}`, {params: {method: 'baidu.ting.song.getRecommandSongList', song_id, num}});
res.json({
code: result.status,
result: result.data
})
} catch (error) {
res.json({
code: -100086,
error: error
})
}
})
/**
* 获取歌手信息
*
* @tinguid Number 歌手id
* */
router.get('/getMusicInfo', async(req, res) => {
try {
const { tinguid } = req.query;
console.log('tinguid', tinguid);
const result = await axios.get(`${apiDomain}`, {params: {method: 'baidu.ting.artist.getInfo', tinguid}});
res.json({
code: result.status,
result: result.data
})
} catch (error) {
res.json({
code: -100086,
error: error
})
}
})
/**
* 获取歌手歌曲列表
* */
router.get('/getSongList', async(req, res) => {
try {
const { tinguid, limits, use_cluster, order } = req.query;
console.log('tinguid', tinguid);
const result = await axios.get(`${apiDomain}`, {params: {method: 'baidu.ting.artist.getSongList', tinguid, limits, order}});
res.json({
code: result.status,
result: result.data
})
} catch (error) {
res.json({
code: -100086,
error: error
})
}
})
/**
* 获取歌词
* */
router.get('/getLry', async(req, res) => {
try {
const { songid } = req.query;
console.log('songid', songid);
const result = await axios.get(`${apiDomain}`, {params: {method: 'baidu.ting.song.playAAC', songid}});
res.json({
code: result.status,
result: result.data
})
} catch (error) {
res.json({
code: -100086,
error: error
})
}
})
export default router;
bookRouter.js
import express from 'express'
import axios from 'axios'
const router = express.Router();
const apiDomain = 'https://api.douban.com/v2/book/';
/**
* 图书搜索
* */
router.get('/search', async(req, res) => {
try {
const { keyWord, tag, start, count } = req.query;
console.log('keyWord', keyWord);
const result = await axios.get(`${apiDomain}search`, {params: {q: keyWord, tag, start, count}});
console.log(result);
res.json({
code: result.status,
result: result.data
})
} catch (error) {
res.json({
code: -100086,
error: error
})
}
})
/**
* 根据id获取图书信息
* */
router.get('/:id', async (req, res) => {
try {
const { id } = req.params;
console.log('idid', id)
const result = await axios.get(`${apiDomain}${id}`);
res.json({
code: result.status,
result: result.data
})
} catch (error) {
res.json({
code: -100086,
error: error
})
}
})
/**
* 根据id返回丛书信息
* */
router.get('/isbn/:name', async(req, res) => {
try {
const { name } = req.params;
console.log('name', name);
const result = await axios.get(`${apiDomain}isbn/${name}`);
res.json({
code: result.status,
result: result.data
})
} catch (error) {
res.json({
code: -100086,
error: error
})
}
})
export default router;
package.json
{
"name": "douban",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"start": "babel-node app.js"
},
"dependencies": {
"axios": "^0.18.0",
"body-parser": "^1.18.3",
"express": "^4.16.3",
"multer": "^1.3.1",
"qs": "^6.5.2"
},
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-eslint": "^7.2.3",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"eslint": "^3.19.0",
"eslint-config-standard": "^10.2.1",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.7.1",
"eslint-plugin-html": "^3.0.0",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.2.0",
"eslint-plugin-promise": "^3.4.0",
"eslint-plugin-standard": "^3.0.1"
}
}
引入mockjs的目的是,提高我们的开发效率,不需要等待后端接口给出之后,才能够进行开发调试;
mockjs两个最大的特点:
1. 够拦截ajax请求,保证我们能够快速开发,只需要在后端给出接口的时候,把接口替换就ok了;
2. 足够多的方法产生随机的不同类型的数据;
npm install --save mockjs
如loss-order.js
// 这里的fetch是封装的基于axios的ajax请求
import fetch from 'utils/fetch';
import CONFIG from '@/assets/js/config';
export function getLossOrderList (queryData) {
const data = Object.assign({}, CONFIG.ajaxData, queryData);
return fetch.getAjax('/loss_order/loss_order_One/lossOrderList', data);
};
1. 第一种方法,利用mockjs的Random方法来构造函数
2. 第二种方法,利用Mock.mock并加@来构造数据
第一种方式
import { paramURL } from '@/utils';
import Mock from 'mockjs';
const Random = Mock.Random;
export default {
getLossOrderList: (config) => {
const param = paramURL(config.url);
let result = {};
result.key = ['订单号', '销售平台', 'SKU', '产品品牌', '所属仓库', '订单类型', '平台订单号', '物流方式\跟踪号', '出货状态', '完成状态', '金额', '北京付款时间'];
const value = [];
const count = 100;
const start = (Number(param.offset) - 1) * Number(param.limit);
const end = Number(param.offset) * Number(param.limit);
// 第一种造数据的方式,引入Random来进行造数据
for (let i = 0; i < count; i++) {
value.push({
orderId: Random.increment(),
orderNo: 'CO' + Random.now('day', 'yyyyMMdd') + 'LZD',
salesPlat: Random.first(),
sku: Random.float(0, 100000000000, 2),
skuId: Random.increment(),
productBrand: Random.cword('零一二三四五六七八九十', 3),
warehouse: Random.cword('光明清溪', 2),
warehouseId: '172',
orderType: '普通',
platOrderNo: Random.integer(0),
logistics: Random.cword('零一二三四五六七八九十', 5),
sailStatus: '未出货',
complateStatus: '备货中',
price: Random.float(0, 100, 4) + 'USD',
payTime: Random.now('second')
})
}
// 第二种方式直接使用Mock.mock并加@来构造数据
for (let i = 0; i < count; i++) {
value.push(Mock.mock({
orderId: '@increment',
orderNo: 'CO' + '@now("day", "yyyyMMdd")' + 'LZD',
salesPlat: '@first',
sku: '@float(0, 100000, 2, 4)',
skuId: '@increment()',
productBrand: "@cword('零一二三四五六七八九十', 3)",
warehouse: '@cword("光明清溪", 2)',
warehouseId: '172',
orderType: '普通',
platOrderNo: '@integer(0)',
logistics: '@cword("零一二三四五六七八九十", 5)',
sailStatus: '未出货',
complateStatus: '备货中',
price: '@float(0, 100, 2, 4)' + 'USD',
payTime: '@now("second")'
}))
}
result.value = value.slice(start, end);
result.pagingData = {
limit: +param.limit,
offset: +param.offset,
total: count
}
return result;
}
}
import Mock from 'mockjs';
import lossOrderAPI from './loss-order';
Mock.setup({
// 指定被拦截的 Ajax 请求的响应时间,单位是毫秒
timeout: '350-600'
});
// 亏损订单接口
Mock.mock(/\/loss_order\/loss_order_One\/lossOrderList/, 'get', lossOrderAPI.getLossOrderList);
export default Mock;
前端开发到目前为止,已经是多团队,跨项目开发,那么在各个团队及各个项目来回切换的时候,怎么去提高我们的开发效率,显然规范化能够帮助我们解决这个问题,那么到目前为止eslint是一个很棒的js代码规范化选择,我们可以根据官方的规则来进行定制,也可以根据一些成型的规则来引入如standard等;每个团队可以根据自己公司的要求来进行选择,下面是如何在项目中引入eslint的步骤。
eslint安装有两种方式
eslint使用方式,不配置package.json
eslint使用方式,进行package.json设置
eslint修复错误代码
参考链接
https://github.com/eslint/eslint
https://github.com/standard/standard
http://eslint.cn/docs/rules/
因为项目内需要用到富文本编辑器,于是在找了很多富文本编辑器之后,最终找到tinymce更符合我们项目的富文本编辑器;
npm install --save tinymce
<template>
<div class="tinymce-container editor-container">
<textarea class='tinymce-textarea' :id="tinymceId" ></textarea>
</div>
</template>
<script>
// 引入tinymce及需要的plugins
import tinymce from 'tinymce/tinymce';
import 'tinymce/themes/modern/theme';
import 'tinymce/plugins/advlist';
import 'tinymce/plugins/autolink';
import 'tinymce/plugins/lists';
import 'tinymce/plugins/link';
import 'tinymce/plugins/image';
import 'tinymce/plugins/charmap';
import 'tinymce/plugins/print';
import 'tinymce/plugins/preview';
import 'tinymce/plugins/anchor';
import 'tinymce/plugins/textcolor';
import 'tinymce/plugins/searchreplace';
import 'tinymce/plugins/visualblocks';
import 'tinymce/plugins/code';
import 'tinymce/plugins/fullscreen';
import 'tinymce/plugins/insertdatetime';
import 'tinymce/plugins/media';
import 'tinymce/plugins/table';
import 'tinymce/plugins/contextmenu';
import 'tinymce/plugins/paste';
import 'tinymce/plugins/help';
// file-loader的作用是更改资源引入路径的loader,如打包后index.html内的图片引入路径是相对于index.html的相对路径而不是原图片的路径,url-loader是将图片进行编码,减少http请求,将图片生成一个dataURl;注意url-loader有一个参数limit,当图片的大小小于limit的时候使用url-loader进行处理转换成dataURL,当大于limit时使用file-loader进行处理,name表示输出的文件名规则,如果不加path参数,则会以hash文件名输出如(0dcbbaa7013869e351f.png),加上[path]表示输出文件的相对路径与当前文件相对路径相同,加上[name].[ext]则表示输出文件的名字和扩展名与当前相同,context,file-loader处理文件的上下文环境
// require.context方法的作用就是通过正则匹配来引入相应的文件模块,require.context(directory, useSubdirectories, regExp)有三个参数,第一个directory要检索的目录,第二个useSubdirectories是否检索子目录,第三个regExp匹配文件的正则表达式
require.context('!file-loader?name=[path][name].[ext]&context=node_modules/tinymce!tinymce/skins', true, /.*/);
require.context('!file-loader?name=[path][name].[ext]&context=node_modules/tinymce!tinymce/langs', true, /.*/);
export default {
name: 'tinymce',
props: {
tinymceId: {
type: String,
default: 'tinymce' + +new Date()
},
value: {
type: String,
default: ''
},
// 文本编辑器工具栏
toolbar: {
type: Array,
default () {
return [
'newdocument | undo redo | searchreplace print preview code cut copy paste | alignleft aligncenter alignright alignjustify numlist bullist indent outdent subscript superscript removeformat | fullscreen',
'h1 p charmap | fontselect fontsizeselect styleselect | forecolor backcolor bold italic underline strikethrough blockquote | image media table tabledelete emoticons anchor link unlink | formats insertdatetime insertfile help'
];
}
},
// 菜单栏
menubar: {
type: String,
default: ''
},
height: {
type: Number,
default: 400
},
// 插件栏,方便我们去调用一个内置的功能,如打印等
plugins: {
type: Array,
default () {
return ['advlist autolink lists link image charmap print preview anchor textcolor', 'searchreplace visualblocks code fullscreen', 'insertdatetime media table contextmenu paste code help'];
}
}
},
data () {
return {
hasChange: false,
hasInit: false
};
},
watch: {
value (val) {
if (!this.hasChange && this.hasInit) {
this.$nextTick(() => {
// 设置编辑器的值
window.tinymce.get(this.tinymceId).setContent(val);
});
}
}
},
mounted () {
const _this = this;
// tinymce.documentBaseURL = ''
tinymce.init({
selector: `#${this.tinymceId}`,
height: this.height,
language: 'zh_CN',
mobile: { // 在移动端显示时的配置
theme: 'mobile',
plugins: [ 'autosave', 'lists', 'autolink' ],
toolbar: [ 'undo', 'bold', 'italic', 'styleselect' ]
},
skin: 'lightblue',
font_formats: '微软雅黑=微软雅黑;宋体=宋体;新宋体=新宋体;黑体=黑体;仿宋=仿宋;楷体=楷体;隶书=隶书;幼圆=幼圆;Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats',
fontsize_formats: '8px 10px 12px 14px 16px 18px 20px 22px 24px 26px 28px 32px 36px',
resize: 'true false', // 水平垂直方向上进行拉伸
preview_styles: 'font-size color',
body_class: 'panel-body',
branding: false, // 禁用tinymce插件的商标
color_picker_callback: function (callbacks, value) { // 允许提供自己的颜色选择器
callbacks('#FF00FF');
},
object_resizing: false,
toolbar: this.toolbar,
menubar: this.menubar,
plugins: this.plugins, // 加载插件,默认是不加载任何插件
end_container_on_empty_block: true,
powerpaste_word_import: 'clean',
code_dialog_height: 450,
code_dialog_width: 1000,
advlist_bullet_styles: 'square',
advlist_number_styles: 'default',
block_formats: 'Paragraph=p;Heading 1=h1;Heading 2=h2;Heading 3=h3;Heading 4=h4;Heading 5=h5;Heading 6=h6;',
imagetools_cors_hosts: ['wpimg.wallstcn.com', 'wallstreetcn.com'],
imagetools_toolbar: 'watermark',
default_link_target: '_blank',
link_title: false,
// 实例化完成之后的钩子函数
init_instance_callback: editor => {
if (_this.value) {
// 设置默认值
editor.setContent(_this.value);
}
_this.hasInit = true;
// 监听事件,动态赋值
editor.on('NodeChange Change KeyUp', () => {
this.hasChange = true;
this.$emit('input', editor.getContent({ format: 'raw' }));
});
},
// setup允许在编辑器实例化之前进行自定义配置
setup (editor) {
editor.addButton('h1', {
title: 'h1', // tooltip text seen on mouseover
text: 'h1',
type: 'splitbutton',
onclick () {
editor.execCommand('mceToggleFormat', false, 'h1');
},
onPostRender () {
const btn = this;
editor.on('init', () => {
editor.formatter.formatChanged('h1', state => {
btn.active(state);
});
});
},
menu: [
{
text: 'h2',
onclick () {
editor.execCommand('mceToggleFormat', false, 'h2');
}
},
{
text: 'h3',
onclick () {
editor.execCommand('mceToggleFormat', false, 'h3');
}
},
{
text: 'h4',
onclick () {
editor.execCommand('mceToggleFormat', false, 'h4');
}
},
{
text: 'h5',
onclick () {
editor.execCommand('mceToggleFormat', false, 'h5');
}
},
{
text: 'h6',
onclick () {
editor.execCommand('mceToggleFormat', false, 'h6');
}
}
]
});
editor.addButton('F', {
title: 'F', // tooltip text seen on mouseover
text: 'F',
type: 'listbox',
onselect: function (e) {
editor.insertContent(this.value());
},
values: [
{ text: 'H1', value: 'h1' },
{ text: 'H2', value: 'h1' },
{ text: 'H3', value: 'h1' },
{ text: 'H4', value: 'h1' },
{ text: 'H5', value: 'h1' },
{ text: 'H6', value: 'h1' }
],
onclick () {
editor.execCommand('mceToggleFormat', false, this.value());
},
onPostRender () {
const btn = this;
editor.on('init', () => {
editor.formatter.formatChanged('F', state => {
btn.active(state);
});
});
}
});
editor.addButton('h2', {
title: '小标题', // tooltip text seen on mouseover
text: '小标题',
onclick () {
editor.execCommand('mceToggleFormat', false, 'h2');
},
onPostRender () {
const btn = this;
editor.on('init', () => {
editor.formatter.formatChanged('h2', state => {
btn.active(state);
});
});
}
});
editor.addButton('p', {
title: '正文',
text: '正文',
onclick () {
editor.execCommand('mceToggleFormat', false, 'p');
},
onPostRender () {
const btn = this;
editor.on('init', () => {
editor.formatter.formatChanged('p', state => {
btn.active(state);
});
});
}
});
}
});
},
destroyed () {
tinymce.get(this.tinymceId).destroy();
}
};
</script>
<style lang="less" scoped>
.tinymce-container {
position: relative
}
.tinymce-textarea {
visibility: hidden;
z-index: -1;
}
</style>
<tinymce v-model="myContent1" tinymce-id="tinymce1" ></tinymce>
import Tinymce from '@/components/tinymce';
vue骨架屏的实现
lazy-load实现图片懒加载
flex布局简称弹性盒模型布局,是2009年w3c提出的一种可以简洁、快速弹性布局的属性。主要**是给予容器控制内部元素高度和宽度的能力,是目前移动端布局常用的一种方式
flex布局主要包括flex容器与flex项目,而flex项目又可以是一个新的flex容器,依次类推;只要给一个元素设置了display: flex;那么浏览器在渲染的时候就会该元素为一个flex容器,其内的子元素为flex项目;
flex中的有两条轴分别代表水平和垂直方向,常常称为主轴与交叉轴,默认情况下主轴为水平方向(从左至右),交叉轴为垂直方向,因为flex-direction的默认值为row
flex-direction: row(行,左至右)(默认值) || column(列,上至下) || row-reverse(行,右至左) || column-reverse(列, 下至上); 控制Flex项目沿着主轴(Main Axis)的排列方向
flex-wrap: wrap(当主轴方向无法显示足够的flex项目时,则会换行显示,从主轴的默认排列方向)(默认值) || nowrap(不换行显示,所有的flex项目显示在这一行,当flex容器的宽度超过视窗宽度则出现滚动条) || wrap-reverse(当主轴方向无法显示足够的flex项目时,则会换行显示,从主轴的默认排列方向的反方向开始排列);
flex-flow: flex-direction flex-wrap 是这两个属性的简写属性
justify-content(类似text-align属性)(只对主轴有效): flex-start(让flex项目从主轴默认开始的方向对齐)(默认值) || flex-end(让flex项目从主轴默认结束的方向对齐) || center(延主轴居中对齐) || space-between(让除了第一个和最一个Flex项目的两者间间距相同(两端对齐)) || space-around(让每个Flex项目具有相同的空间,即让每个flex项目的左右有一个相同的margin值)
align-items(类似text-align属性)(只对交叉轴有效): flex-start(让flex项目从交叉轴默认开始的方向对齐) || flex-end(让flex项目从交叉轴默认结束的方向对齐)(默认值) || center(延交叉轴居中对齐) || stretch(让所有的flex项目与flex容器等高 or 等宽根据主轴的方向来,如果是row方向则等高,column方向则等宽)(默认值) || baseline(延基线对齐)
align-content(与align-items类似只是少了baseline属性值,也是设置flex项目的对齐方式): flex-start(让多行flex项目从交叉轴默认开始的方向对齐) || flex-end(让多行flex项目从交叉轴默认结束的方向对齐)(默认值) || center(延交叉轴居中对齐) || stretch(延交叉轴的方向拉伸flex的项目,让flex项目占满flex容器一个合适的高度or宽度)(默认值)
order(定义flex项目在主轴方向的排列顺序):number(所有的flex项目order默认值为0,值越大排列越靠后,值越小排列越靠前,允许正负值);
flex-grow(控制Flex项目在容器有多余的空间如何放大(扩展)): 0(默认值为0) or 正值,0表示Flex项目不会增长,填充Flex容器可用空间,正值表示flex项目会随着flex容器变大
flex-shrink(控制Flex项目在没有额外空间如何缩小): 0 or正值(默认值为1),0表示Flex项目不会变小,填充Flex容器可用空间,正值表示flex项目会随着flex容器缩小
flex-basis(指定Flex项目的初始大小):% || em || rem || px (默认值为auto);注意的是flex-basis: 0px不能写成flex-basis:0;
flex(简写属性): flex-grow flex-shrink flex-basis; flex: 0 1 auto;(默认属性值)
align-self(改变flex项目沿着侧轴的位置,而不影响相邻的弹性项目):auto(继承父元素的align-items的值) || flex-start || flex-end || center || baseline || stretch(默认值);当不想局限于flex容器align-items对齐方式时就可以使用align-self属性来自动设置
绝对flex项目与相对flex项目:一个相对Flex项目内的间距是根据它的内容大小来计算的。而在绝对Flex项目中,只根据 flex 属性来计算,而不是内容; flex: auto; or flex: 1 1 auto;的flex项目为相对flex项目; flex:1; or flex: 1 1 0;的项目为绝对flex项目
html,body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
.app {
display: flex;
height: 100%;
flex-direction: column;
}
.header {
flex: 0 0 50px;
background: red;
}
.main {
flex: 1;
background-color: blue;
overflow: auto;
}
.content {
height: 100vh;
}
<div class="app">
<div class="header">header</div>
<div class="main">
<div class="content">
content
</div>
</div>
</div>
参考链接:https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Flexible_Box_Layout/Using_CSS_flexible_boxes
单张图片的背景大小可以使用以下三种方法中的一种来规定:
使用关键词 contain
使用关键词 cover
设定宽度和高度值
当通过宽度和高度值来设定尺寸时,你可以提供一或者两个数值:
如果仅有一个数值被给定,这个数值将作为宽度值大小,高度值将被设定为auto。
如果有两个数值被给定,第一个将作为宽度值大小,第二个作为高度值大小。
每个值可以是<length>, 是 <percentage>, 或者 auto.
iphone5下
backgrund-size: auto/contain/cover/100% auto/100% 100%;是一致的效果,图片高保真并铺满空间
iphone6下
background-size: auto; 图片按原尺寸显示并四边留白
background-size: contain/cover/100% auto/100% 100%;是一致的效果图片高保真并铺满空间
安卓机下
background-size: auto; 图片裁剪
background-size: contain/cover/100% auto/100% 100%;是一致的效果图片高保真并铺满空间
结论1:背景图片的展示依赖原图尺寸与div容器尺寸,当div容器尺寸大于or等于图片实际尺寸的时候,background-size只为auto的时候,会展示不同的效果,当图片原尺寸小于容器尺寸的时候,四边会留白,当图片尺寸大于原容器尺寸的时候,图片会裁剪;其它几个值都是保持高保证并铺满整个容器;
iphone5下
backgrund-size: auto/cover/100% auto;宽度沾满空间,高度被裁剪
backgrund-size: contain/100% 100%;图片缩放全部展示在容器内,两边留白
backgrund-size: 100% 100%;图片变形,但是铺满空间
iphone6下
background-size: auto; 宽度按原尺寸展示,高度被裁剪
background-size: contain;图片缩放全部展示在容器内,两边留白
background-size: cover/100% auto;图片宽度占满空间,高度被裁剪
background-size: 100% 100%;图片变形,但是铺满空间
安卓机下
background-size: auto; 图片裁剪
background-size: contain;图片缩放,宽度留白
background-size: cover/100% auto;宽度铺满,高度被裁剪
background-size: 100% 100%;图片变形,但是铺满空间
结论2:背景图片的原图尺寸大于div容器的尺寸时,会根据background-size的值来进行不同的展示,
background-size: auto; 尽量用图片原尺寸占满空间,就是把原图片的尺寸看成一个矩形,容器的宽高看成一个矩形,居中展示的是两个矩形的交集,所以当图片的原尺寸大于容器的部分会被裁剪掉,小于的部分全部展示并留白,等于的部分刚好铺满;
background-size: contain; 通过缩小or放大,让整张图片都在容器内,所以当图片的原尺寸中的某一项大于容器某一项的尺寸时,为了保证整张图片都能过展示在容器内,会对大于的一边进行缩放,同时为了保证图片不失真,会对另一边同时进行缩放,所以等于or小于的那一边就会出现留白,当图片的原尺寸中的某一项小于容器某一项的尺寸时,两边同时放大,已宽度为准,高度上下可能会留白
background-size: cover/100% auto;通过放大or搜小宽度,然后根据缩放的比列来计算高度,当放大or缩小后的图片尺寸大于容器尺寸时,超出的图片会被裁剪;
background-size: 100% 100%; 图片占满容器,如果放大or缩小后的尺寸,大于or小于容器的尺寸都会失真;
iphone5下
backgrund-size: auto/contain/100% auto;宽度沾满空间,高度两边留白
backgrund-size: cover; 图片放大高度全部展示在容器内,宽度被裁剪,高保真
backgrund-size: 100% 100%;图片变形,但是铺满空间
iphone6下
background-size: auto; 宽度按原尺寸展示,四边留白
background-size: contain/100% auto;图片宽度占满空间,高度两边留白
background-size: cover;图片高度占满空间,宽度被裁剪,高保真
background-size: 100% 100%;图片变形,但是铺满空间
安卓机下
background-size: auto; 图片裁剪
background-size: contain/100% auto;图片宽度占满,高度两边留白
background-size: cover;图片高度铺满,宽度被裁剪,高保证
background-size: 100% 100%;图片变形,但是铺满空间
结论3:背景图片的原图尺寸大于div容器的尺寸时,会根据background-size的值来进行不同的展示,
background-size: auto; 尽量用图片原尺寸占满空间,就是把原图片的尺寸看成一个矩形,容器的宽高看成一个矩形,居中展示的是两个矩形的交集,所以当图片的原尺寸大于容器的部分会被裁剪掉,小于的部分全部展示并留白,等于的部分刚好铺满;
background-size: contain/100% auto; 通过缩小or放大,让整张图片都在容器内,所以当图片的原尺寸中的某一项大于容器某一项的尺寸时,为了保证整张图片都能过展示在容器内,会对大于的一边进行缩放,同时为了保证图片不失真,会对另一边同时进行缩放,所以等于or小于的那一边就会出现留白,当图片的原尺寸中的某一项小于容器某一项的尺寸时,宽度会占满容器,高度显示原图片尺寸高度,高度上下超出部分留白
background-size: cover;通过放大or缩小宽度,然后根据缩放的比列来计算高度,当放大or缩小后的图片尺寸大于容器尺寸时,超出的图片会被裁剪,小于容器的尺寸时会铺满容器;
background-size: 100% 100%; 图片占满容器,如果放大or缩小后的尺寸,大于or小于容器的尺寸都会失真;
背景图片的展示跟原图片尺寸与容器尺寸相关;
background-size: auto; 尽量用图片原尺寸占满空间,就是把原图片的尺寸看成一个矩形,容器的宽高看成一个矩形,居中展示的是两个矩形的交集,所以当图片的原尺寸大于容器的部分会被裁剪掉,小于的部分全部展示并留白,等于的部分刚好铺满;不推荐使用该值;(一句话,取原图片尺寸与容器尺寸的交集并剧中展示);
background-size: contain; 通过缩小or放大,让整张图片都在容器内,所以当图片的原尺寸中的某一项大于容器某一项的尺寸时,为了保证整张图片都能过展示在容器内,会对大于的一边进行缩放,同时为了保证图片不失真,会对另一边同时进行缩放,所以等于or小于的那一边就会出现留白,当图片的原尺寸中的某一项小于容器某一项的尺寸时,宽度会占满容器,高度显示原图片尺寸高度,高度上下超出部分留白;注意放大后的图片高度是不会超过原尺寸高度;(一句话,整张图片始终会全部显示在容器内,超过部分会被裁剪,小于部分会被留白);
background-size: cover; 通过缩小or放大,让整张图片已最好的比例展示在容器内,当原图片尺寸大于容器尺寸时,图片会被缩放,缩放后的高度大于容器高度则被裁剪,小于容器高度则会铺满;当原尺寸小于容器尺寸时,图片会被放大,放大后超出的部分会被裁剪;(一句话,会按图片原尺寸宽or高来放大与缩小得出当大or缩小之后的图片,然后与容器尺寸取交集,超出部分裁剪,注意这里始终会让一边铺满,另一边被裁剪);
background-size: 100% auto; 宽度铺满整个容器,高度根据宽度放大or缩小的比例进行对应的放大or搜小,当放大后的高度大于容器高度,则会被裁剪,小于容器高度则会按原图高度展示,高度两边留白;(一句话概括,宽度始终占满容器,高度随宽度比例计算,最大高度不会超过原尺寸高度);
background-size: 100% 100%; 只有当容器的尺寸与图片原尺寸一致时才不会失真,其它情况都会失真;
其实就是三个尺寸之间的关系,图片原尺寸,图片被放大or缩小之后的尺寸,容器尺寸;
当我们对图片的展示要求较高时,最好的图片展示方式就是固定图片原尺寸,然后background-size: 100%; 容器宽高100%/图片原尺寸高度;注意这里的图片原尺寸至少是二倍图;
参考连接:
https://developer.mozilla.org/zh-CN/docs/Web/CSS/background-size
<template>
<div class="ui-channel-manage" ref="channel">
<p class="ui-channel-manage__title" ref="title">
频道管理
</p>
<div class="ui-channel-manage__content" >
<ul class="clearFix" ref="list">
<li v-for="item in channelList" :key="item.key" @touchstart="handlerTouchStart($event, item.key)" :class="{'ui-active': currentIndex === item.key}">
<span>{{item.title}}</span>
<strong @click="closeTag(item.key)">x</strong>
</li>
</ul>
</div>
<div class="ui-channel-manage__back" ref="back">
<p v-show="isEditor">
编辑
</p>
<div v-show="!isEditor">
<span>取消</span>
<span>确定</span>
</div>
</div>
</div>
</template>
<script>
import {mapGetters} from 'vuex';
export default {
name: 'channelManage',
data () {
return {
isEditor: false,
disX: null,
disY: null,
disBlank: null,
channelRect: null,
currentTarget: null,
currentIndex: null,
startIndex: null,
cursorDown: false,
coordinateLi: [],
initChannelList: [
{key: 0, title: 'SKU库存管理'},
{key: 1, title: '待处理订单'},
{key: 2, title: '亏损订单'},
{key: 3, title: '待处理包裹'},
{key: 4, title: '促销管理'},
{key: 5, title: '客服异常订单'},
{key: 6, title: '订单异常管理'},
{key: 7, title: '图片管理'},
{key: 8, title: '订单规则条件管理'}]
};
},
computed: {
...mapGetters([
'showSideBar'
]),
channelList () {
return this.initChannelList;
}
},
watch: {
showSideBar (val) {
if (val) {
this.getAllCoordinateLi();
}
}
},
methods: {
closeTag (index) {
this.initChannelList.splice(index, 1);
this.initChannelList.forEach(item => {
if (item.key > index) {
item.key -= 1;
}
});
},
getAllCoordinateLi () {
const lis = this.$refs.list.children;
Array.from(lis).forEach((item, index) => {
const rect = item.getBoundingClientRect();
this.coordinateLi.push({
top: rect.top,
left: rect.left,
bottom: rect.bottom,
right: rect.right,
index: index
});
});
},
judgePosition () {
const rect = this.currentTarget.getBoundingClientRect();
const left = rect.left;
const bottom = rect.bottom;
console.log(this.coordinateLi);
this.coordinateLi.forEach(item => {
console.log(left, item.left, left > item.left, bottom, item.top, bottom > item.top);
if (left > item.left && bottom > item.top) {
console.log(this.$refs.list.children, item.index);
this.currentIndex = item.index;
}
});
},
handlerTouchStart (e, index) {
e.stopImmediatePropagation();
this.cursorDown = true;
const rect = e.target.getBoundingClientRect();
this.disX = e.targetTouches[0].pageX - rect.left;
this.disY = e.targetTouches[0].pageY - rect.top;
let target = null;
if (e.target.nodeName === 'SPAN' || e.target.nodeName === 'STRONG') {
target = e.target.parentNode;
}
const dom = target || e.target;
this.currentTarget = dom.cloneNode(true);
this.currentTarget.style.position = 'absolute';
this.currentTarget.style.zIndex = '99';
this.currentTarget.style.right = '1000px';
this.$refs.list.appendChild(this.currentTarget);
this.startIndex = index;
this.channelRect = this.$refs.channel.getBoundingClientRect();
// { passive: false }
document.addEventListener('touchmove', this.handlerTouchMove, { passive: false });
document.addEventListener('touchend', this.handlerTouchEnd);
document.onselectstart = () => false;
},
handlerTouchMove (e) {
if (!this.cursorDown) return;
e.preventDefault();
let left = e.targetTouches[0].pageX - this.channelRect.left - this.disX;
let top = e.targetTouches[0].pageY - this.disY;
this.currentTarget.style.left = left + 'px';
this.currentTarget.style.top = top + 'px';
this.judgePosition();
},
handlerTouchEnd (e) {
if (!this.cursorDown) return;
document.removeEventListener('touchmove', this.handlerTouchMove, { passive: false });
this.cursorDown = false;
document.onselectstart = null;
this.disX = null;
this.disY = null;
this.$refs.list.removeChild(this.currentTarget);
const currentItem = this.initChannelList[this.currentIndex];
console.log(this.initChannelList, this.currentIndex, this.startIndex);
this.initChannelList[this.currentIndex] = this.initChannelList[this.startIndex];
this.initChannelList[this.startIndex] = currentItem;
this.initChannelList[this.currentIndex].key = this.currentIndex;
this.initChannelList[this.startIndex].key = this.startIndex;
console.log(this.initChannelList);
this.getAllCoordinateLi();
this.currentTarget = null;
this.currentIndex = null;
this.startIndex = null;
}
},
beforeDestroy () {
document.removeEventListener('touchend', this.handlerTouchEnd);
}
};
</script>
<style lang="less" scoped>
@import (reference) '../assets/less/index.less';
.ui-channel-manage {
display: flex;
flex-direction: column;
height: 100%;
&__title {
line-height: 92px;
flex: 0 0 92px;
font-size: 40px; /*px*/
color: @color-info1;
background-color: @color-white;
z-index: 2;
padding-left: 35px;
text-align: left;
font-weight: bold;
}
&__content {
flex: 1;
text-align: left;
padding-left: 35px;
display: flex;
flex-flow: wrap;
justify-content: flex-start;
flex-direction: column;
ul > {
flex: 0 0 auto;
>li {
position: relative;
float: left;
width: 252px;
height: 70px;
line-height: 70px;
text-align: center;
background-color: #f7f7f7;
border-radius: 10px;
margin-bottom: 25px;
>span {
font-size: 26px; /*px*/
line-height: 26px;
color: @color-info1;
}
>strong {
position: absolute;
right: 13px;
top: 8px;
font-weight: normal;
font-size: 16px; /*px*/
line-height: 1;
}
}
>li:nth-child(odd) {
margin-right: 35px;
}
.ui-active {
background-color: @color-primary;
transform: scale(1.1);
>span {
color: #fff;
}
}
}
}
&__back {
flex: 0 0 90px;
line-height: 90px;
font-size: 32px; /*px*/
color: @color-primary;
background-color: @color-white;
z-index: 2;
font-weight: bold;
display: flex;
flex-direction: column;
>div {
width: 100%;
flex: 0 0 90px;
display: flex;
align-items: center;
>span {
flex: 0 0 50%;
background-color: @color-white;
color: @color-info1;
}
>span:last-child {
background-color: @color-primary;
color: @color-white;
}
}
}
}
</style>
var 声明一个变量,没有块及作用域,存在变量提升,
let 声明一个变量,有块及作用域,存在暂时性死区(即在变量未声明之前使用都会报错),不允许在同一个块及作用域内重复声明
const 声明一个常量(注意声明引用类型的常量时,是可以改变引用类型的值的),有块及作用域, 存在暂时性死区(即在变量未声明之前使用都会报错), 不允许在同一个块及作用域内重复声明
数组的结构赋值 就是对数组下标进行一个映射,通过映射关系来进行取值,如let [a, b, c] = [4, 5, 5],需要注意的是左右两边的变量与值的个数可以不等,当变量结构不成功时返回的值为undefined
对象的结构赋值 就是对对象的key进行一个映射,let {foo, bar} = {foo: 'jack', bar: 'rose'} ==> let {foo: foo, bar: bar} = {foo: 'jack', bar: 'rose'},当解构不成时,返回undefined
使用场景:交换变量的值let x = 1; let y = 2; [x, y] = [y, x]; 从某个对象内取值 如let {height, width} = this; 函数参数的结构赋值及函数参数的默认值;从某个模块内引入某个方法 import {getName} from './name'; 遍历map结构 for (let [key, value] of map)
新增了includes方法,该方法用于查找某个字符串内是否包含需要查找的某个字符or字符串,包含返回true,不包含返回false; startsWidth,表示需要查询的字符串or字符是否在头部,是的话返回true,否的话返回false;endWidth是否在尾部,是的话返回true,否的话返回false;另外这个三个方法都允许传入第二个参数,表示从哪个字符的位置开始算起,endWidth相反表示从哪个字符的前面算起,注意这三个方法的index默认为0
string.repeat(count) 字符串重复次数,该方法会对count进行取整,转换为数字;注意该值不能为infinity,传入的话会直接报错,另外也不能传空,传空传0返回空字符串
模板字符串hello ${world}
注意的是这个${}大括号内就是一个js执行环境,所以里面可以是变量,也可以进行运算,调用函数等
新增Number.isFinite()与Number.isNaN 与传统的isFinite与isNaN相比的区别就是该方法只对Number类型的值有效,对其它类型的值无效,即isFinite与isNaN会对参数进行一个隐式转换如字符创,布尔值会转换成number类型
新增Number.parseInt与Number.parseFloat方法,这两个方法与全局的parseInt与pareseFloat是同一个方法,这样做的目的是减少全局方法
新增Number.isInteger判断一个number类型的值是否为整数,注意不会进行隐式转换只对number类型有效;需要注意的是数值的精度超过限制时isInteger会判断不准确,所以我们在精度要求较高的场景下就不能使用该方法
允许传入默认参数 function Point(x = 0, y = 0){} 传统设置默认参数的方法function log(x, y) {y = y || 'World'}及与结构赋值的结合使用// 写法一 function m1({x = 0, y = 0} = {}) { return [x, y]; } // 写法二 function m2({x, y} = { x: 0, y: 0 }) { return [x, y]; }
rest参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中(rest是一个真正的数组) function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; }注意的是注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
es6 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。原因是函数内部的严格模式,同时适用于函数体和函数参数。但是,函数执行的时候,先执行函数参数,然后再执行函数体。这样就有一个不合理的地方,只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数却应该先于函数体执行。
箭头函数,注意箭头函数的写法就好了,注意的是箭头函数的this绑定的原因是箭头函数内部本身是,没有this的,它拿的的箭头函数外层的this;还要注意的是箭头函数内没有arguments用rest来替代,也不能new 箭头函数 const fn = v => v; const fn = () => 'aa'; const fn = (a, b) => a + b; const fn = (a, b) => ({name: a, id: b}) const fn = (a, b) => {return a + b}
//由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。
var name = 'window'
var person1 = {
name: 'person1',
show1: function () {
console.log(this.name)
},
show5: show5,
show2: () => console.log(this.name),
show3: function () {
return function () {
console.log(this.name)
}
},
show4: function () {
return () => console.log(this.name)
}
}
var person2 = { name: 'person2' }
function show5(){
console.log(this.name);
}
//show1这种定义函数方式与show5这种方式是一样的,所以show1的作用域在飞严格模式下是指向window的
person1.show1()//person1
person1.show1.call(person2)//person2
person1.show2()//window
person1.show2.call(person2)//window call放无法改变箭头函数内的this指向
person1.show3()()//window
person1.show3().call(person2)//person2
person1.show3.call(person2)()//window
person1.show4()()//window->person1因为是person1调show4方法,所以箭头函数外部函数作用域中的this指向person1,所以箭头函数里面的this,也指向箭头函数person1
person1.show4().call(person2)//window ->person1,call 方法无法改变箭头函数内的this指向
person1.show4.call(person2)()//person2
/**
* Question 2
*/
//头函数的this,因为没有自身的this,所以this只能根据作用域链往上层查找,直到找到一个绑定了this的函数作用域(即最靠近箭头函数的普通函数作用域,或者全局环境),并指向调用该普通函数的对象
var name = 'window'
function Person (name) {
this.name = name;
this.show1 = function () {
console.log(this.name)
}
this.show2 = () => console.log(this.name)
this.show3 = function () {
return function () {
console.log(this.name)
}
}
this.show4 = function () {
return () => console.log(this.name)
}
}
var personA = new Person('personA')
var personB = new Person('personB')
personA.show1()//personA
personA.show1.call(personB)//personB
personA.show2()//personA
personA.show2.call(personB)//personA
personA.show3()()//window
personA.show3().call(personB)//personB
personA.show3.call(personB)()//window
personA.show4()()//personA
personA.show4().call(personB)//personA
personA.show4.call(personB)()//personB
扩展运算符...写法是(...[1, 2, 2]),它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,常用的场合1.替换apply方法;2.求数组内的最大值与最小值;3.将一个数组添加到另一个数组的前面or后面
Array.from()方法用于将两类对象转为真正的数组:类似数组的对象(array-like object,这里包括自定义的类数组对象,不限于nodeList与arguments)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map),另外第二个参数可以传入一个函数,用于处理数组中的每个item
Array.of()用来创建数组,将一组值转换为数组,弥补Array()或new Array();区别是Array.of(3)生成的时一个包含3的数组[3],new Array(3)生成一个包含三个空值的数组[ , , ,]
find()用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。
entries()返回一个迭代器对象(注意返回的不是一个数组),用于数组key与value的遍历;keys()是对键名的遍历;values()是对键值的遍历;要注意的是不能使用for in 及for循环来进行变量
includes(val, index)与字符串的includes方法类似;ndexOf方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判;includes使用的是不一样的判断算法,就没有这个问题。
属性的简洁写法,允许变量与函数作为对象的属性与方法 即{foo} => {foo: foo}
Object.is(val1, val2)比较两个值是否相等,内部使用的时===,需要注意的是,一是+0不等于-0,二是NaN等于自身。正常===情况下NaN是不等于NaN
Object.assign()合并对象类似extend方法;Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)需要注意的是Object.assign方法实行的是浅拷贝,而不是深拷贝;对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加;Object.assign可以用来处理数组,但是会把数组视为对象(会用下标去进行覆盖)不建议用于数组上面。常用的场合1.合并多个对象;2.为属性指定默认值
Object.keys(obj)返回一个包含改对象key值的数组,Object.values(obj)返回包含该对象values的数组(需要注意的时如果属性key是number类型的话,value是按照key的大小排序之后的输出顺序),Object.entries()返回包含该键值对的二维数组(常见的用途是将对象转换成map结构)
set是一种新的数据结构(类数组),属于引用数据类型,typeof的值为object,特点是该结构的内部成员是无重复项的,即每个成员都是唯一的,该数据结构通过new Set()来进行获取,传入的参数可以是空or数组,传入其它类型的参数会报错,一般用于数组去重[...new Set([1, 2, 6, 3, 7, 7, 5])],不能通过下标索引来进行取值
set的实例方法add(value)添加元素(可以添加简单值也可以添加对象),delete(value)删除元素(只能删除简单值,不能删除对象),has(value)判断是否有改元素,clear()清空所以元素
set的遍历操作,keys(), values(), entries(),forEach()与数组的forEach一样(需要注意的是因为set的键值就是键名,所以第一个参数value与第二个参数key永远是相等的),另外Set的遍历顺序就是插入顺序,Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法
map是一种新的数据结构(类对象),属于引用类型,typeif志伟object, 特点是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键,需要注意的时new Map时可以不传入参数,也可以传入参数,传入参数时需要注意格式
map的实例方法与set类似,分别是set(key,value), get(key), delete(key), has(key), clear()
map的遍历方法,keys(), values(), entries(),forEach()与数组的forEach一样,Map 的遍历顺序就是插入顺序
map的常用场景,map转数组[...map]; 数组转map=>new Map([['name', 'jack'], ['title', 'name']]),map转对象及对象转map
promise就是异步编程的一种解决方案,有两个特点1.对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态;2.一旦状态改变,就不会再变,任何时候都可以得到这个结果;Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段
基本用法 new promise((resolve, reject) => {})
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理,需要注意的时只有所有的promise成员都成功了才会resolve,而只要有一个成员失败了,就好reject
Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例,const p = Promise.race([p1, p2, p3]);与promise.all不同的是,只要成员中有一个率先改变状态,不论成功or失败,p就会执行resolve or reject
Promise.resolve(),现有对象转为 Promise 对象,需要注意的是根据参入的参数会有不一样的结果
Promise.reject()与Promise.resolve方法类似
Iterator(遍历器),遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员),Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable),原生具备 Iterator 接口的数据结构如下Array、Map、Set、String、arguments、NodeList
for of一种遍历方式, 一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法,即原生具备 Iterator 接口的数据结构Array、Map、Set、String、arguments、NodeList都可以使用for of来进行遍历,另外数组、Set、Map调用entries(),keys(),values()之后的返回值同样具有Iterator接口,即也可以使用for of来进行遍历
对象通过Object.keys(someObject)来使用for of遍历for (var key of Object.keys(someObject))
for in与for of的区别,for in 有如下缺点for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键,某些情况下,for...in循环会以任意顺序遍历键名,而for of 不同于forEach方法,它可以与break、continue和return配合使用。提供了遍历所有数据结构的统一操作接口,总之,for...in循环主要是为遍历对象而设计的,不适用于遍历数组
定义类的方式 construct内的是实例上的属性or方法,一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加,类不存在变量提升,如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”,父类的静态方法,可以被子类继承。。ES6 明确规定,Class 内部只有静态方法,没有静态属性
类通过extends关键字类实现继承, 子类的constructor内必须调用super方法
export提供当前模块对外暴露的接口,export的写法
import提供当前模块可以从其它模块引入的接口, import的写法
import()的写法
移动端项目必不可少的问题就是怎么去调试线上项目,以最快最简便的方式去快速定位问题;
我总结下自己常用调试及定位问题的方法,通过加入控制台查看代码是否有无报错;通过charles等抓包工具进行断点、转发、代理到本地等方式进行调试线上bug;通过chrome webview的方式调试app or 微信内打开的h5页面等,下面主要记录前两种方式
方法一、代码内引入,以eruda为例
const erudaDebug = {
hasInit: false,
initDruda: (config = {}) => {
if (!erudaDebug.hasInit) {
require.ensure([], require => {
eruda = require('./eruda')
eruda.init(config)
eruda.show()
erudaDebug.hasInit = true
})
} else {
erudaDebug.show()
}
},
show: () => {
eruda && eruda.show()
},
hide: () => {
eruda && eruda.hide()
}
}
方法二、通过cdn引入,以eruda为例
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/eruda.min.js"></script>
<script>
eruda.init()
</script>
如果不懂charles抓包,先查看这里十分钟学会Charles抓包(iOS的http/https请求)
方法一、接口断点调试
比如我要在接口请求or返回时修改请求参数or修改返回参数
方法二、接口转发调试
比如我要将本地的某个接口请求转发到测试环境or生产环境
方法三、文件本地代理调试
调试线上生产环境代码,将代码下载到本地,然后通过map local功能,进行本地修改调试
方法四、线上环境rewrite到本地环境调试
比如直接将生产的站点重写到本地环境,接口保持请求不变
这里需要使用rewrite功能,rewrite的功能功能有,将某个站点重定向到新的地址、修改站点请求头、参数、返回头,返回参数等;这里以将站点重定向到本地开发环境为例
先补充一点replace方法的知识,以便理解rewrite使用正则来进行替换重定向
str.replace(regexp|substr, newSubStr|function) 我们只看第一个参数是正则表达式的场景
str.replace(regexp, newSubStr) // newSubStr内可以使用$$ 插入一个 "$"。$& 插入匹配的子串。$` 插入当前匹配的子串左边的内容。$' 插入当前匹配的子串右边的内容。$n,假如第一个参数是 RegExp对象,并且 n 是个小于100的非负整数,那么插入第 n 个括号匹配的字符串。提示:索引是从1开始
str.replace(regexp, function) // 函数的返回值作为替换字符串。 (注意:上面提到的特殊替换参数在这里不能被使用。) 另外要注意的是,如果第一个参数是正则表达式,并且其为全局匹配模式,那么这个方法将被多次调用,每次匹配都会被调用。该函数的参数:match 匹配的子串。(对应于上述的$&。)p1,p2, ... 假如replace()方法的第一个参数是一个RegExp 对象,则代表第n个括号匹配的字符串。(对应于上述的$1,$2等。)例如,如果是用 /(\a+)(\b+)/ 这个来匹配,p1 就是匹配的 \a+,p2 就是匹配的 \b+。offset 匹配到的子字符串在原字符串中的偏移量。(比如,如果原字符串是 'abcd',匹配到的子字符串是 'bc',那么这个参数将会是 1; string 被匹配的原字符串。(精确的参数个数依赖于 replace() 的第一个参数是否是一个正则表达式(RegExp)对象,以及这个正则表达式中指定了多少个括号子串,如果这个正则表达式里使用了命名捕获, 还会添加一个命名捕获的对象
看几个具体的例子
const str = 'abc12345#$*%';
const reg1 = /([^\d]*)(\d*)([^\w]*)/;
const replaceFn1 = (match, p1, p2, p3, offset, string) => {
return `${p1}-${p2}-${p3}`
}
const newStr1 = str.replace(reg1, "$1-$2-$3") // abc-12345-#$*%
const newStr2 = str.replace(reg1, replaceFn1) // abc-12345-#$*%
var reg3 = /(\w+)\s(\w+)/;
var str1 = "John Smith";
var newstr5 = str1.replace(reg3, "$2, $1"); // Smith, John
const reg4 = /https:\/\/baidu.com.cn\/(?!api\/index\.php)(.*)/
const str2 = 'https://baidu.com.cn/page/customer_management/customer_management.shtml?fromApp=5003&token=cdkqqf1407307954'
const str3 = 'https://baidu.com.cn/api/index.php?r=common/my-applicationto-project/get-applicationto-project&token=cdkqqf1407307954'
console.log('replace', str2.replace(reg4, 'http://127.0.0.1:8006/$1'))
const reg5 = /https:\/\/baidu.com.cn\/(?!(api|Broker|broker)\/.*)(.*)/
const str4 = 'https://baidu.com.cn/api/broker/broker/get-broker-info?token=eurkya1565078719'
const str5 = 'https://baidu.com.cn/front_static/index?style=red&token=eurkya1565078719'
const str6 = 'https://baidu.com.cn/api/index.php?r=broker/index/get-building-list&token=eurkya1565078719&pageIndex=1'
const str7 = 'https://baidu.com.cn/Broker/UserCenter/index?token=eurkya1565078719&style=red'
const fn1 = function (match, p1, p2, offset, string) {
return `http://127.0.0.1:9001/${p2}`
}
console.log('replace', str4.replace(reg5, 'http://127.0.0.1:9001/$2'))
console.log('replace', str5.replace(reg5, 'http://127.0.0.1:9001/$2'))
console.log('replace', str6.replace(reg5, 'http://127.0.0.1:9001/$2'))
console.log('replace', str7.replace(reg5, 'http://127.0.0.1:9001/$2'))
console.log('replace', str5.replace(reg5, fn1))
1、进入rewrite界面
参考链接:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace
https://tool.oschina.net/uploads/apidocs/jquery/regexp.html
https://github.com/liriliri/eruda
husky是一个git 钩子插件,提供了pre-commit pre-push等封装好了的钩子,我们可以在这些钩子触发的时候执行某些命令或者操作
使用方式
yarn add husky --dev | npm install husky --save-dev
// 第一种方式直接写在package.json内
{
"husky": {
"hooks": {
"pre-commit": "yarn lint",
"...": "..."
}
}
}
// 第二种方式直接在根目录下建立.huskyrc, .huskyrc.json or .huskyrc.js等文件,如果使用这种方式husky的版本需要大于1.0.0
// .huskyrc
{
"hooks": {
"pre-commit": "yarn lint"
}
}
使用方式
yarn add lint-staged --dev | npm install lint-staged --save-dev
// 第一种方式直接写在package.json内
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"linters": { // linters 被匹配的项及匹配之后需要执行的命令
"*.{js,vue}": [ // 允许按命令执行多项命令
"eslint --fix", // 也可以直接时script内的命令,如yarn lint,这里需要注意的时当执行script内的命令时,检查的就是script命令内的所有文件了
"git add"
],
"*.css": "stylelint",
"*.scss": "stylelint --syntax=scss"
},
"ignore": []
},
// 第二种方式可以在根目录下建立lintstagedrc or lint-staged.config.js 文件
{
"linters": {
"*.{js,vue}": [
"eslint --fix",
"git add"
]
},
"ignore": []
}
这里说下eslint与prettier之间的区别,二者的侧重点不同,前者是代码规范检查,如是否可以使用var,尾逗号,函数括号前面是否留空格,便于团队保持比较统一的代码风格;而prettier则是代码格式化插件,可以根据一定的规则对我们的js、css、less、jsx、vue等文件进行格式化,保证团队输出的代码是统一的;所以二者除了小部分规则有交集之外,二者是可以在我们的开发种相辅相成的;
引入方式
// 第一种方式直接安装prettier插件
yarn add prettier --dev | npm install prettier --save-dev
在根目录下建立.prettierrc配置文件
{
"tabWidth": 4,
"semi": false,
"singleQuote": true,
"parser": "flow", // 默认格式化js的方式
"printWidth": 100,
}
在package.json内配合husky、lint-staged使用
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"post-commit": "git update-index --again"
}
},
"lint-staged": {
"linters": {
"*.js": ["prettier --parser flow --write", "eslint --fix", "git add"], // 格式化js文件
"*.vue": ["prettier --parser vue --write", "eslint --fix", "git add"], // 格式化vue文件
"*.json": ["prettier --parser json --write", "git add"] // 格式化json文件
},
"ignore": ["dist/**/*.js"] // 忽略掉dist目录下的文件
},
还可以使用单独的命令
"pt": "prettier --debug-check {src,test}/**/*.js",
"pt:vue": "prettier --parser vue --debug-check {src,test}/**/*.vue",
"pt:fix": "prettier --write {src,test}/**/*.js",
"pt:fixvue": "prettier --parser vue --write {src,test}/**/*.vue"
注意这里的parser有好几种,我在配置的时候,好像不能根据文件自动识别,然后进行格式化,如parser方式为flow时,无法格式化vue文件,所以vue文件需要用parser引擎指定为vue
// 第二种方式则是使用eslint-plugin-prettier 具体的配置方式参考prettier提供的文档
团队的代码规范化只有当我们真正碰到的时候,才会去认真对待及找寻方法进行解决,所以趁此机会记录一下,并让我们在以后开始任何新项目or接手任何项目的时候,能够时刻把代码规范化这些能过提升我们工作效率及减少bug的方式加入进去;
参考链接:
https://prettier.io/docs/en/install.html
https://github.com/okonet/lint-staged
https://github.com/typicode/husky
project
package.json
node_modules/
packages/
component1
.git
src
package.json
component1
.git
src
package.json
真正的代码都在component这一层,且每一个为独立的git仓库,所有的依赖都安装在最外层的node_modules目录下;当我们需要使用到git的pre-commit等钩子时,存在三个问题,第一个就是不能使用成熟的githooks插件如husky等,第二个就是所有的依赖都安装在最外层,内层component1这层是不允许装依赖的;第三个问题就是就算自己写了pre-commit等钩子,团队其它成员怎么用;
最开始因为git的hooks都是shell脚本,而自己对shell脚本不怎么熟,但是知道可以使用node来写shell脚本,所以直接写的是node的方案;
通过查找资料我们可以知道node内提供来一个child_process模块,而这个模块提供来一些异步以及通过去执行command的方法;如child_process.exec(command[, options][, callback])、child_process.spawn(command[, args][, options])、child_process.execSync(command[, options])、child_process.spawnSync(command[, args][, options])方法等,具体的使用方法查找node文档即可,不过主要有两点就是这几个可执行命令的方法都是基于spawn的封装;第二点就是exec可以直接执行命令,如exec('git log') 而spawn方法则是需要使用参数的形式spawn('git', ['log'])
通过git diff命令可以获取到暂存区的文件,如获取js文件git diff --cached --name-only --diff-filter=ACM -- '*.js'
通过execSync or exec命令来获取到暂存区的文件,execSync('git diff --cached --name-only --diff-filter=ACM -- '*.js'').toString().trim().split('\n') 注意execSync与exec的返回值取的的地方不一样;execSync方法返回值一般是buffer or 字符串这里使用toString()方法将buffer转换成字符串,然后在转换成包含各个文件名的数组
如果使用的是execSync方法来执行eslint命令,需要用try catch的方式在catch内捕捉错误,一开始没有使用try catch方法,而是直接使用返回值,想要用返回值来判断,但是这样导致报 comman failed的错误,而我把这个命令直接放在bash内执行的时候,又是正常的;这就是我没有使用try catch来捕捉的结果
// 使用try catch来捕捉eslint返回的信息,而不是直接使用返回值
try {
execSync(`${PRETTIER_PATH} --parser ${filetype.includes('js') ? 'flow' : 'vue'} --write ${files[i]}`)
execSync(`${ESLINT_PATH} ${files[i]}`, {cwd})
} catch (error) {
PASS = false
console.log('error', error.stdout.toString())
}
// 不是直接使用返回值,这样会直接报comman failed
const result = execSync(`${ESLINT_PATH} ${files[i]}`, {cwd})
#!/usr/bin/env node
const execSync = require('child_process').execSync
const path = require('path')
const ESLINT_PATH = path.normalize("./node_modules/.bin/eslint")
const PRETTIER_PATH = path.normalize("./node_modules/.bin/prettier")
console.log('检查是否安装eslint')
try {
execSync(ESLINT_PATH)
}catch(e) {
console.log('未安装eslint', e.stdout.toString(), e.stderr.toString())
process.exit(1)
}
const STAGE_FILES_JS_COMMAND="git diff --cached --name-only --diff-filter=ACM -- '*.js'"
const STAGE_FILES_VUE_COMMAND="git diff --cached --name-only --diff-filter=ACM -- '*.vue'"
const STAGE_FILES_JS = execSync(STAGE_FILES_JS_COMMAND).toString().trim().split('\n')
const STAGE_FILES_VUE = execSync(STAGE_FILES_VUE_COMMAND).toString().trim().split('\n')
console.log('STAGE_FILES_JS', STAGE_FILES_JS, STAGE_FILES_VUE)
let promises = null
const cwd = process.cwd() || process.env.PWD;
function checkFile (files, filetype) {
if (files.length) {
let PASS = true
for (let i = 0; i < files.length; i++) {
try {
execSync(`${PRETTIER_PATH} --parser ${filetype.includes('js') ? 'flow' : 'vue'} --write ${files[i]}`)
execSync(`${ESLINT_PATH} ${files[i]}`, {cwd})
} catch (error) {
PASS = false
console.log('error', error.stdout.toString())
}
}
if (!PASS) {
console.log(`eslint ${filetype}未通过`)
process.exit(1)
} else {
console.log(`eslint ${filetype}通过`)
}
} else {
console.log(`暂存区没有需要检查的${filetype}文件`)
}
}
checkFile(STAGE_FILES_JS, 'js')
checkFile(STAGE_FILES_VUE, 'vue')
process.exit(0)
yarn createHooks => "createHooks": "node createHooks.js create"
console.log('create hooks', process.argv)
const fs = require('fs')
const type = process.argv[2]
const packagesList = ['./packages/test/', './packages/git-hooks/']
const templateUrl = './hooks/pre-commit'
function createHooks() {
console.log('createHooks')
for (let i = 0; i < packagesList.length; i += 1) {
if (!fs.existsSync(`${packagesList[i]}.git/`)) {
break
}
if (!fs.existsSync(`${packagesList[i]}.git/hooks/`)) {
break
}
if (!fs.existsSync(`${packagesList[i]}.git/hooks/pre-commit`)) {
const preCommit = fs.readFileSync(templateUrl)
fs.writeFileSync(`${packagesList[i]}.git/hooks/pre-commit`, preCommit, {
encoding: 'utf8',
mode: 0o777,
})
}
}
}
function deleteHooks() {
}
function deleteAllHooks() {
}
console.log('type', type)
switch (type) {
case 'create':
createHooks()
break
case 'delet':
deleteHooks()
break
case 'deletAll':
deleteAllHooks()
break
default:
createHooks()
}
html5有哪些新特性、移除了那些元素?
Canvas和SVG有什么区别?
什么是web语义化,有什么好处
从浏览器地址栏输入url到显示页面的步骤(以HTTP为例)
提高web网站性能
content方面
1. 减少HTTP请求:合并文件、CSS精灵、inline Image
2. 减少DNS查询:DNS查询完成之前浏览器不能从这个主机下载任何任何文件。方法:DNS缓存、将资源分布到恰当数量的主机名,平衡并行下载和DNS查询
3. 避免重定向:多余的中间访问
4. 使Ajax可缓存
5. 非必须组件延迟加载
6. 未来所需组件预加载
7. 减少DOM元素数量
8. 将资源放到不同的域下:浏览器同时从一个域下载资源的数目有限,增加域可以提高并行下载量
9. 减少iframe数量,甚至禁用iframe标签,iframe会阻塞主页面的Onload事件搜索引擎的检索程序无法解读这种页面,不利于SEO,iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载使用iframe之前需要考虑这两个缺点。如果需要使用iframe,最好是通过javascript
动态给iframe添加src属性值,这样可以绕开以上两个问题
10. 不要404
Server方面
1. 使用CDN
2. 添加Expires或者Cache-Control响应头
3. 对组件使用Gzip压缩
4. 配置ETag
5. Flush Buffer Early
6. Ajax使用GET进行请求
7. 避免空src的img标签
Cookie方面
css方面
Javascript方面
图片方面
HTML5的离线储存怎么使用,工作原理能不能解释一下?
在用户没有与因特网连接时,可以正常访问站点或应用,在用户与因特网连接时,更新用户机器上的缓存文件
原理:HTML5的离线存储是基于一个新建的.appcache文件的缓存机制(不是存储技术),通过这个文件上的解析清单离线存储资源,这些资源就会像cookie一样被存储了下来。之后当网络在处于离线状态下时,浏览器会通过被离线存储的数据进行页面展示
如何使用:
浏览器是怎么对HTML5的离线储存资源进行管理和加载的呢?在线的情况下,浏览器发现html头部有manifest属性,它会请求manifest文件,如果是第一次访问app,那么浏览器就会根据manifest文件的内容下载相应的资源并且进行离线存储。如果已经访问过app并且资源已经离线存储了,那么浏览器就会使用离线的资源加载页面,然后浏览器会对比新的manifest文件与旧的manifest文件,如果文件没有发生改变,就不做任何操作,如果文件改变了,那么就会重新下载文件中的资源并进行离线存储。离线的情况下,浏览器就直接使用离线存储的资源。
CACHE MANIFEST
#v0.11
CACHE:
js/app.js
css/style.css
NETWORK:
resourse/logo.png
FALLBACK:
/ /offline.html
vue的响应式指的是data、props内的数据改变时,对应个的视图也会动态改变,那么vue是怎么做到的呢?
让我们从源码的角度来捋一遍,以data为例,大致流程如下所示:
-> 调用initData()
-> 调用proxy(vm, _data
, keys[i]), observe(data, true)
-> 判断是否已经Observer实例有则拿来用,没有则初始化一个实例 new Observer(value)
-> 判断data每个key对应的value,如果是纯对象则this.walk(value) 如果是数组 this.observeArray(value)
-> 对象直接调用defineReactive(obj, keys[i], obj[keys[i]]) 数组则进行遍历每个成员调用observe(items[i])
-> 在mountComponent内调用new Watcher来收集依赖,区分数据
-> Object.defineProperty的getter内调用dep.depend()进行依赖收集 , setter内通过dep.notify()来发布消息,最终达到响应式的效果;
// 初始化data操作
function initData (vm: Component) {
let data = vm.$options.data
// 如果data是函数则调用getData进行获取
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
// 获取包含key的数组
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
// 判断data内的key是否与methods内的方法名是否有重名
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
// 判断data内的key是否与props内的属性名是否有重名
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
// 对data进行一次代理,便于我们能够直接通过this.a 来访问this._data.a
proxy(vm, `_data`, key)
}
}
// observe data
// 对data内的数据进行observe
observe(data, true /* asRootData */)
}
// 当传入的data是一个函数时,返回一个data对象
export function getData (data: Function, vm: Component): any {
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, `data()`)
return {}
}
}
// 对data进行代理操作
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
// 尝试创建Observe实例,如果没有则创建,如果有则返回现有的Observe实例
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 判断是否已经有了Observe实例
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 直接new 一个Observe实例
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
// Observer 构造函数
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
// 将 Observer 实例添加到传入value数组的__ob__属性上,如传入的value是data,那么就在data对象上添加__ob__属性
def(value, '__ob__', this)
// 如果是数组,调用observeArray方法
if (Array.isArray(value)) {
// 判断是否支持__propto__属性,如果支持调用protoAugment方法否则调用copyAugment方法
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
// 是对象则调用walk方法
this.walk(value)
}
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
// 对每个属性调用defineReactive方法进行getter与setter设置
defineReactive(obj, keys[i], obj[keys[i]])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
// 对数组中的每项成员继续调用observe方法,直至结束
observe(items[i])
}
}
}
// 如果有__proto__属性则,直接将传入数组实例的原型上的7个方法改成自定义的7个数组方法
function protoAugment (target, src: Object, keys: any) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
// 如果不支持__proto__属性则覆盖数组实例的原型上的7个方法
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
// 定义对象属性的getter与setter
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// new 一个Dep实例,用来收集依赖,一个容器,用于存放订阅者(watcher)即发布消息
const dep = new Dep()
// 获取传入对象对应key的属性描述,如果该property.configurable === false则retrurn调
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
// 获取之前定义的getter与setter
const getter = property && property.get
const setter = property && property.set
// 对属性值进行observe,如果是对象or数组则重复改步骤,如果是简单类型则不用,如data: {msg: {a: 1, b: 2}},则方便对msg: {a: 1, b: 2}继续进行observe
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// 区分普通调用还是watcher内的get调用
if (Dep.target) {
// 订阅消息
dep.depend()
// 如果有子项,同理继续订阅消息
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
// set之前先获取一次值
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
// 比较新值与旧值
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// 如果有setter函数则执行,否则直接把新值赋给val
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 对新值重新observe
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
// 用于存放订阅者与发布消息
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
// 新增订阅列表,订阅Watcher
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// 发布消息
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
// 调用每个watcher的update方法
subs[i].update()
}
}
}
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
const targetStack = []
// 设置Dep的target属性,区分watcher内调用的getter与普通调用的getter
export function pushTarget (_target: Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
// 观察者
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
// 调用get方法来收集依赖
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
// 区分是watcher内调用的getter方法还是普通的getter方法调用
pushTarget(this)
let value
const vm = this.vm
try {
// 调用对象属性的getter方法 这里不怎么懂的是,为什么所有的用于视图渲染的数据都能够被调用一次
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
/**
* Add a dependency to this directive.
*/
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
// 依赖发生变化时触发
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
// 同步执行
this.run()
} else {
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run () {
if (this.active) {
// 调用watcher的get方法用于收集新的依赖于重新渲染视图
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
try {
// 更新视图
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
// 重写数组实例上的7中方法,便于数据响应
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 如果有新的元素插入则调用observeArray
if (inserted) ob.observeArray(inserted)
// notify change
// 发布消息
ob.dep.notify()
return result
})
})
// 挂载组件
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
/* istanbul ignore if */
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
)
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm
)
}
}
}
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
// 组件data/props等数据初始化之后调用Watcher收集依赖
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
总结: 大致思路是捋清楚了,但是中间的一些实现细节,还是有待去研究,不过总的来说,不得不感叹这种实现的方式,太精妙了。
在main.js里面注册store的时候,s不能大些,大些的话会无效,导致在组件里面使用mutations or actions无效,即注册的时候只能使用store,不能使用Store
在router配置文件里面,当配置子路由的时候,子路由的path前面是不需要带/的,/值需要在一级路由前面带上,因为在vue-router里面,把/开头的嵌套路径当做根路径;
jquery的ajax的post请求默认为表单请求即请求头的content-type:application/x-www-form-urlencoded(简单的ajax请求,跨域请求的时候不会发起预请求),angular1.x版本的$http内的post请求默认为json格式的请求,即请求头为content-type:application/json(复杂的ajax请求,跨域请求时会发起预请求),vue的辅助插件axios的post请求,默认为json请求即请求头为content-type: application/json
v-bind:href的简写:href;v-on:click的简写为@click,即自定义事件在父组件可以通过@自定义事件名or v-on自定义事件名来监听
computed内的计算属性只能够动态获取简单类型值得变化,引用类型的值如果第一次是空,可以获取到最新值,如果不是空则获取不到,这个时候需要用watch来监听对象属性的变化
props只能用于父子之间传递数据不能再祖孙组件之间进行传值,如果祖孙之间需要传至需要一层层的传递;
this.$emit子组件向父组件传递值时,只能够通知到父组件,不能通知到祖组件,如果需要传递到祖组件则需要一层一层的传递;
slot插槽的作用,插槽的作用是帮助我们向某个组件内插入不同的指定内容,通过name属性来指定内容需要被插入的插槽位置,注意插槽还可以传递作用域即可以把组件内的值通过作用域传给插槽,然后插槽内就可以使用传入的作用域内的值,需要注意的是事件不能够通过作用域传递;2.5之前的版本,带作用域的slot需要放再template内,2.5之后不再需要
<template>
<div>
<slot name="header"></slot>
<slot name="body" :scope="list"></slot>
<slot></slot>
</div>
</template>
<child>
<div slot="header">这是头部</div>
<div slot="body" slot-scope="scope">{{scope.text}}</div>
<div>这是尾部</div>
</child>
computed计算属性,data内定义的属性二者之间有什么区别
v-model详解 vue内v-model的实现方式是:value="txt" @input="txt = $event.target.value"这两段代码实现的
基本用法如下所示
<input type="text" v-model="txt"/> => <input :value="txt" @input="txt = $event.target.value" />
组件之间的用法
子组件child-input
<input type="text" v-model="txt"/> => <input :value="txt" @input="txt = $event.target.value" />
<input-child v-model="content"></input-child> =>
<input-child :value="content" @input="content = arguments[0]></input-child>
vue及angular等框架,核心**数用数据来进行驱动,而不是dom来进行驱动,所以我们在做全选与反选时包括类似的操作应该使用数据来进行驱动而不是dom来驱动,这里需要利用data属性的双向绑定与computed属性的依赖变化
先在data里面定义一个checkList: []
在计算属性内获取到从父组件传入的v-for的列表数据内对checkList进行赋值操作,使用isChecked属性来绑定每个checkbox
在计算属性内定义当前被选中的计算属性selectCount,通过遍历,通过isChecked属性来筛选
计算属性内定义全选属性selectAll 通过get与set方法来进行全选与全不选操作,全选实现的思路是通过被选中数量计算属性与渲染当前列表的数据长度是否相等来进行判断,全不选是通过遍历及isChecked来进行设置
计算属性内定义获取当前被选中的元素数组checkedGroup,遍历,isChecked来进行判断
nextTick,将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新;这里有两个概念,一个是响应式原理,一个是异步更新队列
制作一个分页器的核心在两个部分,第一个就是分页器页码的生成规则,第二点就是数据总数,每页展示的条数,及当前页码这是三个关键数字;而分页器的难点就在分页器码的生成规则,需要直接好好的去总结下分页器页码的生成规则
在将 v-bind 用于 class 和 style 时,表达式结果的类型除了字符串之外,还可以是对象或数组。注意的是在组件上也是可以直接使用的,class会被添加到定义组件时的最外层元素上
字符串:class="className"
对象:class="{ active: isActive, 'text-danger': hasError }"
数组:class = "[activeClass, errorClass]"
能检测到变化的方式
第一种成功添加的方式,Vue 包含一组观察数组的变异方法,所以它们也将会触发视图更新,splice,sort,reverse及添加与删除首尾的四个方法
this.arrsucc.push('999')
第二种方式覆盖原先的数组,非变异 (non-mutating method) 方法,例如:filter(), concat() 和 slice() 。这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组:
this.arrsucc = this.arrsucc.concat(['1000'])
this.arrsucc = [...this.arrsucc, '9999']
第三种方式
this.$set(this.arrsucc, 0, 'rose')
this.$set(this.arrsucc, 1, 'jack')
不能检测到变化的方式
// 直接通过index索引来添加
this.arrerr[1] = 88
// 直接改变数组长度
this.arrerr.length = 0
第一种是在data里面定义了的属性;
data() {
return {
obj: {name : 'jack'}
}
}
第二种通过set方法来实现
this.$set(this.someObject,'b',2);
第三种通过覆盖整个对象来实现
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
this.someObject = {...this.someObject, { a: 1, b: 2 }
v-if or v-for使用的时候可以用一个template来包裹需要操作的标签,这里的template不会被渲染出来,只是其它一个方便操作的作用,v-show的时候不能使用template标签
注意在prop进行传值时,因为HTML 特性是不区分大小写的。所以,当使用的不是字符串模板(如.html后缀的文件,字符串模板指的时.vueor其它后缀结尾的文件)时,camelCase (驼峰式命名) 的 prop 需要转换为相对应的 kebab-case (短横线分隔式命名): 而不是,所以便于将.vue后缀的文件改成.html后缀文件不出现报错的话,建议都写成kebab-case (短横线分隔式命名)
为了保证数据的单向流即父组件数据更新时,子组件对应的数据也会更新,而子组件内的数据更新时,不会影响到数据内的属性,因为JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。所以为了避免这种情况的
出现最好的方法就是在子组件的data内声明一个新的变量来进行接收,or在计算属性内得到一个新的值
注意 prop 会在组件实例创建之前进行校验,所以在 default 或 validator 函数里,诸如 data、computed 或 methods 等实例属性or方法还无法使用
组件上的.sync 修饰符,改修饰符的作用是实现父组件内的某个变量与组件内的某个变量进行双向绑定,需要注意的是需要在子组件显示的触发this.$emit('update:foo', newValue)
<comp :foo.sync="bar"></comp>
拓展为<comp :foo="bar" @update:foo="val => bar = val"></comp>
子组件内需要显示的emit该事件this.$emit('update:foo', newValue)
单元素or组件的过渡即需要使用到动画,需要用到transition,只要给transition添加一个name,然后就可以根据这个name来定义动画了,过渡的类名总共6个,这里的v可以使用transition上的name来替换如fade-enter fade-enter-to
混合也就是提取出公共的数据or方法,然后混合到需要的组件内,or在全局对象上进行混合操作,添加到每个组件上,需要注意的就是一个覆盖原则,如同名钩子函数,混合之后会变成一个数组且混合对象的 钩子将在组件自身钩子 之前 调用;如methods, components 和 directives将被混合为同一个对象。两个对象键名冲突时,取组件对象的键值对;全局混合一旦使用全局混合对象,将会影响到 所有 之后创建的 Vue 实例
var mixin = {
created: function () {
console.log('混合对象的钩子被调用')
}
}
new Vue({
mixins: [mixin],
created: function () {
console.log('组件钩子被调用')
}
})
1.在webpack.base.conf.js内的pugins参数内配置
plugins: [
// loader-options-plugin 和其他插件不同,它用于将 webpack 1 迁移至 webpack 2,webpack 2 推荐的使用方式是直接传递 options 给 loader/插件
new webpack.LoaderOptionsPlugin({
vue: {
postcss: function() {
return [px2rem({ remUnit: 75, exclude: /node_modules/ })];
}
}
})
],
2.是在vue-loader-conf.js内进行配置
postcss: [require('postcss-px2rem-exclude')({ 'remUnit': 75, exclude: /node_modules/ })]
3.在postcssrc.js文件内添加
module.exports = {
"plugins": {
"postcss-import": {},
"postcss-url": {},
// to edit target browsers: use "browserslist" field in package.json
"autoprefixer": {},
'postcss-px2rem-exclude': {
remUnit: 75,
exclude: /node_modules/
}
}
}
注意的时1.2两种方式只针对vue文件,而3方式是针对所有使用了postcss-loader处理的文件
直接写px,编译后会直接转化成rem ---- 除开下面两种情况,其他长度用这个
在px后面添加/*no*/,不会转化px,会原样输出。 --- 一般border需用这个
在px后面添加/*px*/,会根据dpr的不同,生成三套代码。---- 一般字体需用这个
在dev-server的proxyTable配置项那里进行配置,使用dev-server能够解决跨域问题的原因是,dev-server起了一个node服务器,而同源策略只限于浏览器,使用我们通过请求node服务器的接口,通过node服务器去反向代理帮我们去请求真实的接口,最终拿到数据
proxyTable: {
'/api': { // 接口前缀
target: 'https://xxxx.com.cn/', // 后端接口地址
changeOrigin: true, // 是否转接
cookieDomainRewrite: { // 是否可以重写cookie
"*": ""
},
pathRewrite: {
'^/api': '/api' // 是否重写接口,即get('/api/basic/getlist') => get('/api/basic/getlist') 不变
'/*': '' // 接口地址不变
'^/api': '/' // 即get('/api/basic/getlist') => get('/basic/getlist') 变了
}
},
'/index.php': {
target: 'http://xxxx.cn/',
changeOrigin: true,
secure: false,
}
},
// get请求,http是axios.create创建的一个实例
http.get('/index.php?r=yykf/b2c/project/get-project-list', {params})
http.get('/api/user/messages', { params })
// post请求,需要注意的是,我们直接传对象时,php后台可能因为接收不到参数报504 or 500错误,使用我们需要对参数进行stringify一下,相当于把请求头由application/json变成了表单的请求方式
http.post('/index.php?r=yykf/b2c/appointment/save', querystring.stringify(data));
之前在工作中的时候,使用axios,jquery等xhr工具的时候,总是有疑问;如jquery的post请求,当请求头的content-type为'application/x-www-form-urlencoded'时,data可以直接传对象,而不需要对对象进行JSON.stringify处理,当请求头的content-type为'application/json'时,data不可以直接传对象,而是需要JSON.stringify处理;使用axios的post方法时,content-type为'application/json'时,data可以直接传对象,而不需要qs.stringify等处理;当content-type为'application/x-www-form-urlencoded'时,data又需要qs.stringify等处理;于是抽时间来总结一下
function request () {
return new Promise((resolve) => {
const xhr = new XMLHttpRequest();
const params1 = "token=acc12356&url=post-form";
const fileEle = document.getElementById('file');
const params2 = new FormData();
params2.append('token', 'acc12356');
params2.append('url', 'post-form');
params2.append('file', fileEle.files[0]);
const params3 = JSON.stringify({token: 'acc12356', url: 'post-form'})
xhr.open('POST', '/post-json', true);
/*
当设置请求头为'application/x-www-form-urlencoded'时,
1. 当参数为params1,key value对的形式,也是表单默认的参数发送方式,后端能够正常拿到值
2. 当参数为params2,form-data的形式,后端能够正常拿到值,但是content-type不会变成multipart/form-data;
3. 当参数为params3,对象的形式,如果不对对象进行处理,则后端收到的是{ 'object Object': '' },如果对对象先进行JSON.stringify则后端接收到的参数是把整个对象做为了一个key的对象{ '{"token":"acc12356","url":"post-form"}': '' }
所以如果content-type为'application/x-www-form-urlencoded',则我们最终传入send放到的参数会被转换成字符串,form-data除外,如果字符串是key value值对的形式则后端可以正常拿到,如果不是则会把整个字符串作为key,所以如果是表单的形式来提交,需要对参数转换成key value对
**/
/*
当设置请求头为'application/json'时,
1. 当参数为params1,key value对的形式,也是表单默认的参数发送方式,直接报错
2. 当参数为params2,form-data的形式,直接报错
3. 当参数为params3,对象的形式,如果不对对象进行处理,则后端收到的是{ 'object Object': '' },如果对对象先进行JSON.stringify则后端接收到的参数是正常的对象{ token: 'acc12356', url: 'post-form' }
所以如果content-type为'application/json',则我们最终传入send放到的参数会被转换成字符串,form-data除外,如果字符串是json字符串的形式则后端可以正常拿到,如果不是则会直接报错
**/
xhr.setRequestHeader('content-type', 'application/json; charset=utf-8');
xhr.onload = function(e) {
if(this.status == 200||this.status == 304){
resolve(this.responseText);
}
};
// 如果没有设置content-type,如传入的是一个字符串则content-type会被设为text/plain;charset=UTF-8
// 如果没有设置content-type,如传入的是一个form-data则content-type会被设为multipart/form-data; boundary=----WebKitFormBoundarygbyZU9wiB2Uc300W
// 如果没有设置content-type,如传入的是一个对象(会被转换为[object object]),如果传入的是一个JSON字符串则会被转换为JSON对象,但是content-type会被设置为text/plain;charset=UTF-8,但是后端拿不到参数
xhr.send(params3);
})
}
这里需要注意的是,send方法可以传入的参数有几类;content-type与send传入的参数又有什么关系;
send传入的参数有ArrayBuffer,Blob,Document,DOMString,FormData,null共6类;我们只需要了解常用的DOMString,FormData类型;
content-type与send传入的参数关系是:当content-type为'application/x-www-form-urlencoded',这是send的参数需要是key value对这样后端才能接收到参数;当content-type为'application/json'时send的参数需要时json字符串对象,这样后端才能在req.body内获取到参数;
至于jquery的post方法,当content-type为'application/x-www-form-urlencoded'时(jquery post默认的content-type),data可以直接传对象,而不需要对对象进行JSON.stringify处理,反之的原因是,当content-type为'application/x-www-form-urlencoded'时,send方法需要传入的是key value值对,而通过jquery源码是能够看到内部是对传入的参数data有做处理的,如果传入的data是一个对象,则会转化成key,value值对,如果传入的直接是字符串是不做处理的,所以为什么jquery的post方法content-type不同时,处理data的方式不同;
1. jquery post content-type: 'application/x-www-form-urlencoded'
const postFormJq = function (data) {
return new Promise((resolve) => {
// 默认表单请求, 这里data不需要JSON.stringify的原因与postJsonJq的是一样的
$.post('/post-form', data, (res) => {
resolve(res);
});
})
}
2. jquery post content-type: 'application/json'
const postJsonJq = function (data) {
return new Promise((resolve) => {
// 单个传参数的形式,不支持修改content-type,需要传对象的形式才支持
// $.post('/post-json', data, (res) => {
// resolve(res);
// });
$.post({
url: '/post-json',
data: JSON.stringify(data), // jquery直接传对象会报错的原因,是jquery内部有一段代码处理,如传入的data不是字符串,则会被转换为key value字符串对,而application/json;是不接受这种参数的,所以报错
contentType: 'application/json; charset=utf-8',
success: (res) => {
resolve(res);
}
})
})
}
3. jquery get query内取参数
const getListJq = function (params) {
return new Promise((resolve) => {
$.get('/get-list', params, (data) => {
resolve(data);
});
})
}
4. jquery get params内取参数
const getListParamsJq = function (params) {
return new Promise((resolve) => {
$.get('/get-list-params/1234563/akcnal',{}, (data) => {
resolve(data);
});
})
}
至于axios的post方法,当content-type为'application/json'时(axios默认的content-type),data可以直接传对象,而不需要qs.stringfily处理的原因是,当content-type为'application/json'时,send方法需要传入的参数是json字符串,而通过axios源码可以发现,axios内部是对传入的data参数做了JSON.stringify处理的,所以当传入的是对象时,会进行JSON.stringify处理,如果直接传入的是字符串是不做处理的,所以为什么axios的post方法content-type不同时,处理data的方式不同;
1. axios post application/x-www-form-urlencoded
const postForm = function (data) {
// 因为axios默认的content-type:application/json,所以如果我们需要使用application/x-www-form-urlencoded表单的方式来发送数据给后端的话,有以下几种方式
// 方法一使用URLSearchParams来编码参数,当axios判断参数是,注意这种方式,通过req.body无法获取
// const params = new URLSearchParams();
// for (let prop in data) {
// params.append(prop, data[prop]);
// }
// console.log('params', params);
// return axios.post('/post-form', params);
// 第二种方法使用qs模块的stringify方法对参数进行处理
// console.log(Qs.stringify(data));
// return axios.post('/post-form', Qs.stringify(data));
// 第三种方式,设置content-type,不加Qs.stringfy处理后端拿到的数据是这样的{ '{"token":"acc12356","url":"post-form"}': '' },加了Qs.stringfy处理{ token: 'acc12356', url: 'post-form' };使用我们在使用的时候,只需要对参数做处理了,就不需要主动去添加content-type头了
// axios post方法内只对传入的data数据进行了JSON.stringify,所以当我们直接传入data进去的时候,会被转换成json字符串对象,而这个时候content-type如果是application/json则后端能够正常获取到参数,如果为application/x-www-form-urlencoded则后端不能正常获取到参数;这也是为什么如果是表单提交,需要对data处理转换成key value对
return axios.post('/post-form', Qs.stringfy(data), {
headers: { 'content-type': 'application/x-www-form-urlencoded' }
});
}
2. axios post 'application/json'
const postJson = function (data) {
return axios.post('/post-json', data);
}
3. axios post multipart/form-data; boundary=[xxx]
这里需要注意的是axios内当检测到传入的参数是form-data时会删除默认的content-type: application/json,这也我们在浏览器内会看到content-type: multipart/form-data; boundary=[xxx];而jquery是没有做这一步处理的
const postFormData = function (data) {
return axios.post('/post-form-data', data);
}
4. axios get query内取参数
const getList = function (params) {
return axios.get('/get-list', {params});
}
5. axios get params内取参数
const getListParams = function (params) {
return axios.get('/get-list-params/1234563/akcnal',);
}
const fs = require('fs');
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const multer = require('multer');
const upload = multer(); // for parsing multipart/form-data
const app = express();
app.use(bodyParser.json()); // for parsing application/json
app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
app.use('/static', express.static('../front/static'));
app.get('/', (req, res) => {
res.sendFile(path.resolve('../front/index.html'), {
header: 'text/html'
})
});
app.get('/get-list', (req, res) => {
console.log(req.query);
res.json({ url: 'post-form', code: 200, ...req.query })
})
app.get('/get-list-params/:id/:token', (req, res) => {
console.log(req.params);
res.json({ url: 'post-form', code: 200, ...req.params })
})
app.post('/post-form',upload.array(), (req, res) => {
console.log(req.body);
res.json({ url: 'post-form', code: 200 })
})
app.post('/post-form-data', upload.array(), (req, res) => {
console.log(req, req.body);
fs.rename(req.file.path, "upload/" + req.file.originalname, function(err) {
if (err) {
throw err;
}
console.log('上传成功!');
})
res.json({ url: 'post-form-data', code: 200 })
})
app.post('/post-json', (req, res) => {
console.log(req.body);
res.json({ url: 'post-json', code: 200 })
})
app.listen(8088, () => {
console.log('启动成功');
});
<div>
<button id="btn1">get请求</button>
<button id="btn2">post请求application/x-www-form-urlencoded</button>
<button id="btn3">post请求multipart/form-data</button>
<button id="btn4">post请求application/application/json</button>
<div>
<input type="file" name="file" id="file">
</div>
<div>
<button class="btn">get请求</button>
<button class="btn">post请求application/x-www-form-urlencoded</button>
<button class="btn">post请求multipart/form-data</button>
<button class="btn">post请求application/application/json</button>
<button class="btn">post请求xhr</button>
</div>
</div>
<script src="/static/js/axios.js"></script>
<script src="/static/js/qs.js"></script>
<script src="/static/js/jquery.js"></script>
<script>
const buttons = document.getElementsByTagName('button');
buttons[0].onclick = function () {
getList({token: 'acc12356', url: 'get-list'}).then((res) => {
console.log('buttons[0]', res);
})
getListParams({token: 'acc12356', url: 'get-list-params'}).then((res) => {
console.log('buttons[0]', res);
})
}
buttons[1].onclick = function () {
postForm({token: 'acc12356', url: 'post-form'}).then((res) => {
console.log('buttons[1]', res);
})
}
buttons[2].onclick = function () {
const fileEle = document.getElementById('file');
console.log(fileEle, fileEle.files);
const form = new FormData();
form.append('file', 'file');
form.append('file', fileEle.files[0]);
form.append('token', 'acc12356');
form.append('url', 'post-form-data');
postFormData(form).then((res) => {
console.log('buttons[2]', res);
})
}
buttons[3].onclick = function () {
postJson({token: 'acc12356', url: 'post-json'}).then((res) => {
console.log('buttons[3]', res);
})
}
const btns = document.getElementsByClassName('btn');
btns[0].onclick = function () {
getListJq({token: 'acc12356', url: 'get-list'}).then((res) => {
console.log('btns[0]', res);
})
getListParamsJq({token: 'acc12356', url: 'get-list-params'}).then((res) => {
console.log('btns[0]', res);
})
}
btns[1].onclick = function () {
postFormJq({token: 'acc12356', url: 'post-form'}).then((res) => {
console.log('btns[1]', res);
})
}
btns[2].onclick = function () {
const fileEle = document.getElementById('file');
console.log(fileEle, fileEle.files);
const form = new FormData();
form.append('file', 'file');
form.append('file', fileEle.files[0]);
form.append('token', 'acc12356');
form.append('url', 'post-form-data');
postFormDataJq(form).then((res) => {
console.log('btns[2]', res);
})
}
btns[3].onclick = function () {
postJsonJq({token: 'acc12356', url: 'post-json'}).then((res) => {
console.log('btns[3]', res);
})
}
btns[4].onclick = function () {
request().then((res) => {
console.log('btns[4]', res);
})
}
</script>
参考链接:
https://segmentfault.com/a/1190000004322487
https://github.com/axios/axios/blob/master/lib/adapters/xhr.js
函数防抖应用于频繁触发的事件,防止浏览器崩溃,常见的应用事件有window对象的resize事件,scroll事件,还有就是mousedown,mousemove,keyup,keydown等事件
<div class="box"></div>
var box = document.getElementsByClassName("box")[0];
var WAIT_TIME = 500;
//第一种方式 就是当move事件停止之后500ms触发
var debounce1 = function (func, time){
var timeout;
return function (){
var _this = this,
args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function (){
func.apply(_this, args);
},time);
}
}
var getUserAction = function (){
console.log(this);
console.log(arguments);
}
// box.onmousemove = debounce1(getUserAction, WAIT_TIME);
//第二种方式、当move事件开始的时候就触发,然后等到move事件停止后在执行一次
var debounce2 = function (func, time, immediate){
var timeout;
return function (){
var _this = this,
args = arguments;
if(timeout) clearTimeout(timeout);
if(immediate){
var callNow = !timeout;
timeout = setTimeout(function (){
timeout = null;
func.apply(_this, args); //move停止之后执行
},time);
if(callNow) func.apply(_this, args); //第一次执行
}else {
timeout = setTimeout(function (){
func.apply(_this, args);
},time);
}
}
}
box.onmousemove = debounce2(getUserAction, WAIT_TIME, true);
var x;
if (!x) { //为false
}
x + 2 // NaN
var a = null;
if(!a) {
}
a + 2 // 2
'1.1' + '1.1' = '1.1.1.1';
+'1.1' + 2 = 3.1;
+'12a' => NaN
var unusualPropertyNames = {
"": "An empty string",
"!": "Bang!"
}
console.log(unusualPropertyNames.""); // 语法错误: Unexpected string
console.log(unusualPropertyNames[""]); // An empty string
console.log(unusualPropertyNames.!); // 语法错误: Unexpected token !
console.log(unusualPropertyNames["!"]); // Bang!
// 使用 \符号来进行换行书写,最终的字符串还是单行,实际上斜线和换行都不会出现在字符串的值中
var str = "this string \
is broken \
across multiple\
lines."
console.log(str); // this string is broken across multiplelines.
// 使用换行符\n + \来书写多行字符串
var poem =
"Roses are red,\n\
Violets are blue.\n\
Sugar is sweet,\n\
and so is foo."
console.log(poem);
Roses are red,
Violets are blue.
Sugar is sweet,
and so is foo.
\n | 换行符
\t | Tab (制表符)
\v | 垂直制表符
\' | 单引号
\" | 双引号
\\ | 反斜杠字符(\)
一时技痒,想找点源码来看看,于是找到了undescore这一个陪伴我们走过了好几个年头的工具函数库,来学习一下代码的组织方式及一些方法的实现思路,现整理如下,从underscore的第一个提交版本开始即0.1.0版本
总的思路是直接在window下添加一个对象,然后在这个对象内定义属性与方法,整个库的方法是按钮类型来划分,如集合类方法,数组类方法,函数类方法,对象类方法,工具方法等;
each(遍历数组or对象)方法实现的思路: 先判断是否支持forEach方法,支持则直接调用,不支持则判断是否是数组,是则调用for循环,使用call方法来调用回调函数,考虑兼容性判断是否支持each方法,如果支持则调用,最后判断是否是对象,如果是则对对象则修改下传入参数的结构,如果以上都不是则直接抛出错误;
关键点: call方法 及对对象传入参数的处理
each : function(obj, iterator, context) {
var index = 0;
try {
// 判断是否有传入的数据类型是否有forEach,有则执行
if (obj.forEach) {
obj.forEach(iterator, context);
} else if (obj.length) {
// 没有forEach,然后判断是否是数组,是数组则使用for循环,然后调用回调函数,把参数传入
for (var i=0; i<obj.length; i++) iterator.call(context, obj[i], i);
} else if (obj.each) {
// 是否有each方法
obj.each(function(value) { iterator.call(context, value, index++); });
} else {
// 如果以上条件都不满足就使用for in 遍历,是对对象的一个扩展
var i = 0;
for (var key in obj) {
var value = obj[key], pair = [key, value];
pair.key = key;
pair.value = value;
iterator.call(context, pair, i++);
}
}
} catch(e) {
if (e != '__break__') throw e;
}
// 最后返回传入的数据
return obj;
},
map(对数组or对象进行操作,并返回操作后的值的集合)方法实现的思路: 先判断是否支持map方法,如果支持则直接调用map方法,如果不支持则直接调用each方法,在each方法内push回调函数的返回值,并返回包含回调函数返回值的数组
关键点: 接收返回值并返回一个包含返回值的数组
map : function(obj, iterator, context) {
// 如果支持map方法则使用原生的map方法
if (obj && obj.map) return obj.map(iterator, context);
// 否则使用each方法
var results = [];
_.each(obj, function(value, index) {
results.push(iterator.call(context, value, index));
});
// 返回iterator处理过后的回调
return results;
}
inject方法实现的思路: 调用each方法,在each方法内调用回调函数,并把回调函数赋值给传入的一个形参,并最终返回这个形参,该方法是一个中间方法,做闭包用
关键点: memo形参即iterator回调函数必须返回memo形参
inject : function(obj, memo, iterator, context) {
_.each(obj, function(value, index) {
memo = iterator.call(context, memo, value, index);
});
return memo;
}
detect(返回满足条件的第一个元素)方法实现的思路: 调用each方法,在each方法内调用回调函数,对回调函数的值做判断,如果满足回调函数的值为真则终止循环,直接返回value值
关键点: 回调函数需要有返回值
detect : function(obj, iterator, context) {
var result;
_.each(obj, function(value, index) {
if (iterator.call(context, value, index)) {
result = value;
throw '__break__';
}
});
return result;
}
select(返回集合中满足条件的值的数组)方法实现的思路: 先判断是否支持filter方法,如果支持则直接调用,如果不支持则调用each方法,然后判断回调函数的值,如果true则push,反之则不然,最后返回一个包含满足条件的数组
关键点: 回调函数需要有返回值与detect方法的区别就是一个是获取的所有满足条件一个是获取第一个满足条件的值
select : function(obj, iterator, context) {
// 判断是否支持filter方法
if (obj.filter) return obj.filter(iterator, context);
var results = [];
_.each(obj, function(value, index) {
if (iterator.call(context, value, index)) results.push(value);
});
return results;
}
reject(返回集合中不满足条件的值的数组)方法实现的思路与select一致,只是判断条件取反
reject : function(obj, iterator, context) {
var results = [];
_.each(obj, function(value, index) {
if (!iterator.call(context, value, index)) results.push(value);
});
return results;
}
all(判断集合内所有的选项是否都满足条件,满足返回true,不满足返回false)实现的思路: 先判断是否有传入回调函数,没有则默认一个,判断是否支持every方法,支持则调用,不支持则调用each方法,定义一个变量,一旦出现不满足条件的情况,将变量至为false并终止循环返回结果
关键点: 定义一个flag变量
all : function(obj, iterator, context) {
iterator = iterator || function(v){ return v; };
if (obj.every) return obj.every(iterator, context);
var result = true;
_.each(obj, function(value, index) {
// 出现false就停止遍历
result = result && !!iterator.call(context, value, index);
if (!result) throw '__break__';
});
return result;
}
any(判断集合内是否有满足条件的选项,如果有则返回true,否则返回false)方法实现的思路与all一致,只是判断条件不一样
any : function(obj, iterator, context) {
iterator = iterator || function(v) { return v; };
if (obj.some) return obj.some(iterator, context);
var result = false;
_.each(obj, function(value, index) {
if (result = !!iterator.call(context, value, index)) throw '__break__';
});
return result;
}
include(判断集合内是否包含目标元素,如果包含则返回true,否则返回false)实现的思路: 判断传入的集合是否是数组,如果是则直接调用indexOf进行判断,否则调用each方法判断,定义一个flag变量,如果有则将flag变量置为true,反之
关键点: 判断类型,flag变量
include : function(obj, target) {
// 如果是数组,则用indexOf判断
if (_.isArray(obj)) return _.indexOf(obj, target) != -1;
var found = false;
// 如果是对象
_.each(obj, function(pair) {
if (pair.value === target) {
found = true;
throw '__break__';
}
});
return found;
}
invoke(将传入的集合,按照传入的某个方法进行操作之后在返回)实现的思路: 截取第三个即以后传入的参数,调用map方法,判断是否有传入回调函数,如果有则执行,如果没有则返回
关键点: 截取参数
invoke : function(obj, method) {
var args = _.toArray(arguments).slice(2);
return _.map(obj, function(value) {
return (method ? value[method] : value).apply(value, args);
});
}
pluck(按某个key提取集合内对应的value值,并用数组返回)实现思路: 调用each方法,将传入的key对应的value push到一个空数组内并返回
关键点: key值
pluck : function(obj, key) {
var results = [];
_.each(obj, function(value){ results.push(value[key]); });
return results;
}
max(获取集合内的最大值)实现思路: 如果是数组且没有传入回调函数,则使用Math.max方法进行获取最大值,如果是对象则调用each方法,定义一个flag对象用于保存原始值即当前比较的最大值,最后返回flag对象的value值
关键点: 类型判断,flag对象
max : function(obj, iterator, context) {
// 如果没有传入回调函数,且传入的集合为数组。则直接使用Math的max方法
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
var result;
_.each(obj, function(value, index) {
// 用一个对象对报错上一个值,当当前值大于上一个值的时候,重新赋值一次,最后输出result.value最大值
var computed = iterator ? iterator.call(context, value, index) : value;
if (result == null || computed >= result.computed) result = {value : value, computed : computed};
});
return result.value;
}
min(获取集合内的最小值)方法: 实现的思路与max方法一致,只是判断条件不一样
sortBy(返回按某个字段排序之后的集合)实现思路:
sortedIndex
toArray(将类数组对象转换为数组)实现思路: 判断传入的参数是否为空,如果为空则返回一个空数组,然后判断是否是一个数组,如果是一个数组则直接返回,最后调用map方法
关键点: 类型判断,传入map方法的回调
toArray : function(iterable) {
if (!iterable) return [];
if (_.isArray(iterable)) return iterable;
// 否则调用map方法返回一个数组
return _.map(iterable, function(val){ return val; });
}
size(获取集合的长度)实现思路: 这里是先想传入的集合调用toArray处理,然后在取它的length属性
关键点: toArray处理
size : function(obj) {
return _.toArray(obj).length;
}
first(获取数组的第一个元素)
first : function(array) {
return array[0];
}
last(获取数组的最后一个元素)
last : function(array) {
return array[array.length - 1];
}
compact(去除数组中的空值)实现思路,调用select方法,获取有值的项
关键点: 传入select方法的回调
compact : function(array) {
return _.select(array, function(value){ return !!value; });
}
flatten(扁平化数组)实现的思路: 调用inject方法,在回调里面判断每一项,如果是数组则继续调用flatten方法,并将扁平化之后的结果与上一次的结果进行合并,如果不是则push到传入的一个形参数组内,最终返回这个形参数组
关键点: 闭包,形参数组、对每一项值的判断,如果是数组则继续调用flatten
flatten : function(array) {
return _.inject(array, [], function(memo, value) {
// 如果是数组则继续调用flatten,调用完之后与上一次的结果concat,最终返回扁平化之后的数组
if (_.isArray(value)) {
return memo.concat(_.flatten(value));
}
memo.push(value);
return memo;
});
}
without(返回一个不包含传入项的新数组)实现思路是将传入的参数,转换为一个数组,然后调用select方法,在回调函数内判断传入数组的每一项是否在参数数组内,如果在则返回false,如果不在则返回true,最终返回不包含传入项的新数组
关键点: 闭包,参数数组, select回调内的判断条件
without : function(array) {
// 将传入的参数转换为数组
var values = array.slice.call(arguments, 0);
// 对该数组调用select方法
return _.select(array, function(value){
// 判断第一个参数数组内的值,是否在传入参数values内,如果在则返回false,如果不在返回true,从而将一个数组内需要剔除的值剔除掉
return !_.include(values, value);
});
}
uniq(数组去重)实现思路: 调用inject方法,在回调内进行判断,如果是第一项or不包含在形参数组内则push到形参数组内,最后返回形参数组
关键点: 闭包, 形参数组,判断条件
uniq : function(array, isSorted) {
return _.inject(array, [], function(memo, el, i) {
// 第一个元素 or _.include(memo, el)不包含在memo内的元素则memo.push(el)
if (0 == i || (isSorted ? _.last(memo) != el : !_.include(memo, el))) memo.push(el);
return memo;
});
}
intersect(返回一个在所有传入参数内都包含的新数组)实现思路: 获取第二个及其后面的参数并转换为数组,并已第一个数组调用select方法,在select的回调内调用all方法进行判断,只有第一个数组内的当前项在后面的数组内都有时select返回true,否则返回false
关键点: 闭包, 去重,判断其余项是否含有当前项
intersect : function(array) {
// 获取第一个参数之外的其它参数并转换为数组
var rest = _.toArray(arguments).slice(1);
// 调用select方法,先对第一个参数的数组去重,select的第二个函数为回调函数,及回调内的值为true则复核选择条件,最终返回一个包含符合条件的数组
return _.select(_.uniq(array), function(item) {
// 调用all方法,判断后面的每项参数内是否包含第一个参数内的元素,如果都包含返回true,否则返回false
return _.all(rest, function(other) {
return _.indexOf(other, item) >= 0;
});
});
}
zip(返回一个组合后的新数组)实现思路: 将传入的参数转换为数组,获取数组的最大长度,new一个空数组,调用pluck按下标获取元素,最终返回一个二维数组
关键点: 获取传入参数数组中最大长度,利用pluck方法按下标来取值
zip : function() {
var args = _.toArray(arguments);
var length = _.max(_.pluck(args, 'length'));
var results = new Array(length);
for (var i=0; i<length; i++) results[i] = _.pluck(args, String(i));
return results;
}
indexOf(获取某个元素在数组内的下标)实现思路,判断是否支持indexOf方法,如果不支持则利用for循环遍历,判断是否有传入项,有则返回下标,没有则返回-1
indexOf : function(array, item) {
if (array.indexOf) return array.indexOf(item);
var length = array.length;
for (i=0; i<length; i++) if (array[i] === item) return i;
return -1;
}
bind(绑定函数执行上下文)实现思路: 截取传入的参数,返回一个函数,在返回的函数内合并参数,最终调用apply方法
关键点: apply方法
bind : function(func, context) {
// 如果传入的执行上下文为空则直接返回
if (!context) return func;
var args = _.toArray(arguments).slice(2);
return function() {
// 合并_.bind()时传入的参数与调用_.bind()()时传入的参数进行合并
var a = args.concat(_.toArray(arguments));
// 调用apply方法,传入参数
return func.apply(context, a);
};
}
bindAll(给某些方法一起绑定上下文环境)实现思路: 获取最后一个参数为上下文执行环境,调用each方法遍历绑定bind方法
关键点: 将方法利用bind重新绑定一次
bindAll : function() {
var args = _.toArray(arguments);
// 第一个参数为上下文
var context = args.pop();
_.each(args, function(methodName) {
context[methodName] = _.bind(context[methodName], context);
});
}
delay(延迟多少秒执行)实现思路: 利用setTimeout延迟调用
delay : function(func, wait) {
var args = _.toArray(arguments).slice(2);
return window.setTimeout(function(){ return func.apply(func, args); }, wait);
}
defer(延迟一毫米执行)实现思路是内部调用了delay方法
defer : function(func) {
return _.delay.apply(_, [func, 1].concat(_.toArray(arguments).slice(1)));
}
wrap(返回一个先执行wrapper函数在执行func函数的新函数)实现思路是返回调用wrap的时候返回一个函数,在该函数内去使用apply来调用wrapper函数
wrap : function(func, wrapper) {
return function() {
var args = [func].concat(_.toArray(arguments));
return wrapper.apply(wrapper, args);
};
},
keys(返回包含对象key值的数组)实现思路是传入'key'调用pluck方法,之所以能够传入key获取到对象的key是因为each方法遍历对象的时候对传入回调的参数做了一下处理
keys : function(obj) {
return _.pluck(obj, 'key');
}
values(返回包含对象value的数组)实现思路与key一致
values : function(obj) {
return _.pluck(obj, 'value');
}
extend(扩展对象的属性or方法)实现思路for in 遍历需要被扩展的对象,利用中括号语法进行添加属性or方法
extend : function(destination, source) {
for (var property in source) destination[property] = source[property];
return destination;
}
clone(返回一个拷贝后的对象)
clone : function(obj) {
return _.extend({}, obj);
}
isEqual(判断是否相等)
isEqual : function(a, b) {
// Check object identity.
if (a === b) return true;
// Different types?
// 如果不是一个类型则直接返回false
var atype = typeof(a), btype = typeof(b);
if (atype != btype) return false;
// Basic equality test (watch out for coercions).
if (a == b) return true;
// One of them implements an isEqual()?
if (a.isEqual) return a.isEqual(b);
// If a is not an object by this point, we can't handle it.
if (atype !== 'object') return false;
// Nothing else worked, deep compare the contents.
var aKeys = _.keys(a), bKeys = _.keys(b);
// Different object sizes?
if (aKeys.length != bKeys.length) return false;
// Recursive comparison of contents.
for (var key in a) if (!_.isEqual(a[key], b[key])) return false;
return true;
}
isElement(判断是否是dom节点)
isElement : function(obj) {
return !!(obj && obj.nodeType == 1);
}
isArray(判断是否是数组)
isArray : function(obj) {
return Object.prototype.toString.call(obj) == '[object Array]';
}
isFunction(判断是否是函数)
isFunction : function(obj) {
return typeof obj == 'function';
}
isUndefined(判断是否是undefined)
isUndefined : function(obj) {
return typeof obj == 'undefined';
}
uniqueId(返回一个自增id)
uniqueId : function(prefix) {
var id = this._idCounter = (this._idCounter || 0) + 1;
return prefix ? prefix + id : id;
}
template(编译一段模板)
template : function(str, data) {
var fn = new Function('obj',
'var p=[],print=function(){p.push.apply(p,arguments);};' +
'with(obj){p.push(\'' +
str
.replace(/[\r\t\n]/g, " ")
.split("<%").join("\t")
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%>/g, "',$1,'")
.split("\t").join("');")
.split("%>").join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');");
return data ? fn(data) : fn;
}
接着上一篇,继续分析,显然underscore0.1.0版本,还不是最优的版本,也不能去覆盖更多的使用场景,所以必然会去继续优化。所以从0.2.0开始看,但是当自己去总结的时候如果一个版本一个版本去总结分析的话,也不合理,因为有些小版本只是添加了一个方法,or修改了某个方法的兼容性,索性从0.2.0一直到1.0.0这中间的版本合到一起来分析,学下作者当时的优化思路及新增的方法是为了哪些业务场景;
总结下0.2.0版本到1.0.0版本之间,作者主要新增及优化了哪些内容,如下所示:
使用root变量来保存当前的全局对象,root = this; 保证浏览器端与服务端(node)端的兼容性
使用previousUnderscore保存了underscore加载完之前的全局对象上的_变量,previousUnderscore = root._
使用了breaker变量来替换中断循环字符串'break',breaker = typeof StopIteration !== 'undefined' ? StopIteration : 'break'; 查了下资料StopIteration这个方法大部分浏览器不支持;
使用ArrayProto,ObjProto分别保存了数组的原型即对象原型 ArrayProto = Array.prototype, ObjProto = Object.prototype
使用变量保存了一些数组or对象原型上的方法
var slice = ArrayProto.slice,
unshift = ArrayProto.unshift,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty,
propertyIsEnumerable = ObjProto.propertyIsEnumerable;
var
nativeForEach = ArrayProto.forEach,
nativeMap = ArrayProto.map,
nativeReduce = ArrayProto.reduce,
nativeReduceRight = ArrayProto.reduceRight,
nativeFilter = ArrayProto.filter,
nativeEvery = ArrayProto.every,
nativeSome = ArrayProto.some,
nativeIndexOf = ArrayProto.indexOf,
nativeLastIndexOf = ArrayProto.lastIndexOf,
nativeIsArray = Array.isArray,
nativeKeys = Object.keys;
把_定义成了一个函数var _ = function(obj) { return new wrapper(obj); };便于支持面向对象的写法
适配commonJs if (typeof exports !== 'undefined') exports._ = _;
暴露underscore全局函数 root._ = ; 注意0.1.0里面直接是window. = {}来定义的哦
each方法进行了优化,优化如下1.判断条件,2.去掉了each方法支持的条件,3.变成了一个单独的函数表达式而不是直接定义在underscore上面, 4.改变了遍历对象时传入回调内的参数格式,这样优化的好处了保证遍历数组or对象,回调函数参数的一致性,代码更健壮,即向ECMASCRIPT标准靠拢;
这里有一点需要提下的是,遍历为什么要放到try catch内,原因是forEach遍历是无法中断的,而要想中断forEach的话我们可以借助throw关键字,这个关键字是可以中断当前程序的执行,如果是在try catch内的话,在catch中是能够捕获到throw抛出的异常的,这样既能够中断循环又不会让整个程序抛出异常,如果不放在try catch内而是直接throw抛异常,则会直接中断整个代码的执行
0.1.0
each : function(obj, iterator, context) {
var index = 0;
try {
// 判断是否有传入的数据类型是否有forEach,有则执行
if (obj.forEach) {
obj.forEach(iterator, context);
} else if (obj.length) {
// 没有forEach,然后判断是否是数组,是数组则使用for循环,然后调用回调函数,把参数传入
for (var i=0; i<obj.length; i++) iterator.call(context, obj[i], i);
} else if (obj.each) {
// 是否有each方法
obj.each(function(value) { iterator.call(context, value, index++); });
} else {
// 如果以上条件都不满足就使用for in 遍历,是对对象的一个扩展
var i = 0;
for (var key in obj) {
var value = obj[key], pair = [key, value];
pair.key = key;
pair.value = value;
iterator.call(context, pair, i++);
}
}
} catch(e) {
if (e != '__break__') throw e;
}
// 最后返回传入的数据
return obj;
}
1.0.0
var each = _.forEach = function(obj, iterator, context) {
try {
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (_.isNumber(obj.length)) {
for (var i = 0, l = obj.length; i < l; i++) iterator.call(context, obj[i], i, obj);
} else {
for (var key in obj) {
if (hasOwnProperty.call(obj, key)) iterator.call(context, obj[key], key, obj);
}
}
} catch(e) {
if (e != breaker) throw e;
}
return obj;
};
inject方法改名为reduce方法,并增加了一个判断语句,及增加了一个传入回调函数的参数,整体实现不变
0.1.0
inject : function(obj, memo, iterator, context) {
_.each(obj, function(value, index) {
memo = iterator.call(context, memo, value, index);
});
return memo;
}
1.0.0
reduce = function(obj, memo, iterator, context) {
if (nativeReduce && obj.reduce === nativeReduce) return obj.reduce(_.bind(iterator, context), memo);
each(obj, function(value, index, list) {
memo = iterator.call(context, memo, value, index, list);
});
return memo;
}
reduceRight(与reduce的用法一致,只是从集合后面开始进行操作)
reduceRight = function(obj, memo, iterator, context) {
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) return obj.reduceRight(_.bind(iterator, context), memo);
// clone一个数组的原因是,不影响到原数组
var reversed = _.clone(_.toArray(obj)).reverse();
return _.reduce(reversed, memo, iterator, context);
}
select方法更名为filter,实现原理基本不变
0.1.0
select : function(obj, iterator, context) {
// 判断是否支持filter方法
if (obj.filter) return obj.filter(iterator, context);
var results = [];
_.each(obj, function(value, index) {
if (iterator.call(context, value, index)) results.push(value);
});
return results;
}
1.0.0
filter = function(obj, iterator, context) {
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
var results = [];
each(obj, function(value, index, list) {
// 因为是直接靠返回来进行判断所以可以直接写成与操作符,代码更简洁
iterator.call(context, value, index, list) && results.push(value);
});
return results;
}
all方法改成了every方法,实现原理不变,不过这里把之前的默认函数抽出去了
0.1.0
all : function(obj, iterator, context) {
iterator = iterator || function(v){ return v; };
if (obj.every) return obj.every(iterator, context);
var result = true;
_.each(obj, function(value, index) {
// 出现false就停止遍历
result = result && !!iterator.call(context, value, index);
if (!result) throw '__break__';
});
return result;
}
1.0.0
every = function(obj, iterator, context) {
// 抽成了一个单独的函数,便于公用
iterator = iterator || _.identity;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
var result = true;
each(obj, function(value, index, list) {
// 优化写法,先判断后赋值,让代码更简洁,抽出了breakLoop方法
if (!(result = result && iterator.call(context, value, index, list))) _.breakLoop();
});
return result;
}
any方法换成some方法,实现原理基本不变
0.1.0
any : function(obj, iterator, context) {
iterator = iterator || function(v) { return v; };
if (obj.some) return obj.some(iterator, context);
var result = false;
_.each(obj, function(value, index) {
if (result = !!iterator.call(context, value, index)) throw '__break__';
});
return result;
}
1.0.0
// 优化方式与every方法一致
some = function(obj, iterator, context) {
iterator = iterator || _.identity;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
var result = false;
each(obj, function(value, index, list) {
if (result = iterator.call(context, value, index, list)) _.breakLoop();
});
return result;
}
include优化了写法
0.1.0
include : function(obj, target) {
// 如果是数组,则用indexOf判断
if (_.isArray(obj)) return _.indexOf(obj, target) != -1;
var found = false;
// 如果是对象
_.each(obj, function(pair) {
if (pair.value === target) {
found = true;
throw '__break__';
}
});
return found;
}
1.0.0
include = function(obj, target) {
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
var found = false;
each(obj, function(value) {
// 把赋值操作移到了判断语句之前
if (found = value === target) _.breakLoop();
});
return found;
}
pluck优化了内部实现方式,将内部的each遍历,改成了map遍历,因为map方法本身就返回一个数组,从而不需要在去定义与返回数组,简化代码
0.1.0
pluck : function(obj, key) {
var results = [];
_.each(obj, function(value){ results.push(value[key]); });
return results;
}
1.0.0
pluck = function(obj, key) {
return _.map(obj, function(value){ return value[key]; });
}
max方法优化了内部实现
0.1.0
max : function(obj, iterator, context) {
// 如果没有传入回调函数,且传入的集合为数组。则直接使用Math的max方法
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
var result;
_.each(obj, function(value, index) {
// 用一个对象对报错上一个值,当当前值大于上一个值的时候,重新赋值一次,最后输出result.value最大值
var computed = iterator ? iterator.call(context, value, index) : value;
if (result == null || computed >= result.computed) result = {value : value, computed : computed};
});
return result.value;
}
1.0.0
max = function(obj, iterator, context) {
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
// 给了result一个比较的初始值,最小值,省调了result == null的判断,让代码更健壮,更合理
var result = {computed : -Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
优化了if语句的写法
computed >= result.computed && (result = {value : value, computed : computed});
});
return result.value;
}
min方法优化了内部实现,与max方法同理
toArray优化了内部实现,增加了对自身支持toArray方法及arguments,对象的转换方式
0.1.0
toArray : function(iterable) {
if (!iterable) return [];
if (_.isArray(iterable)) return iterable;
// 否则调用map方法返回一个数组
return _.map(iterable, function(val){ return val; });
}
1.0.0
toArray = function(iterable) {
if (!iterable) return [];
// 曾加了对自身具有toArray方法的支持判断
if (iterable.toArray) return iterable.toArray();
if (_.isArray(iterable)) return iterable;
// 增加了对arguments参数类型的转换方式
if (_.isArguments(iterable)) return slice.call(iterable);
// 改变了对象转换为数组的方式,因为values的实现就是return _.map(obj, _.identity)
return _.values(iterable);
}
first方法进行了拓展,允许传入第二个参数及第三个参数,第二个参数用来获取数组前n个元素,第三个参数用来直接获取数组的第一个元素
0.1.0
first : function(array) {
return array[0];
}
1.0.0
first = function(array, n, guard) {
return n && !guard ? slice.call(array, 0, n) : array[0];
}
rest(用于获取传入index之后的数组元素)
rest = function(array, index, guard) {
// 如果index有值or guard为true则取index,反之取1
return slice.call(array, _.isUndefined(index) || guard ? 1 : index);
}
lastIndexOf(从右至左查找数组内是否有某个元素,有则返回index,没有则返回-1)实现思路如果支持lastIndexOf,则直接调用lastIndexOf,反之从arr,length开始遍历,而不是0开始遍历
lastIndexOf = function(array, item) {
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
var i = array.length;
while (i--) if (array[i] === item) return i;
return -1;
}
range(按某个固定值,返回包含start到stop之间的值的数组)实现思路判断传入参数的个数,然后判断起始值,结束值,确定遍历的length,最后遍历
range = function(start, stop, step) {
var a = _.toArray(arguments);
// 判断参数的个数
var solo = a.length <= 1;
// 如果只有一个参数,start取0,反之取传入的第一个参数,
// 如果只有一个参数,stop取0,反之取传入的第二个参数
// step取第三个参数,如果第三个参数为空则默认给1
var start = solo ? 0 : a[0], stop = solo ? a[0] : a[1], step = a[2] || 1;
// 获取遍历的次数
var len = Math.ceil((stop - start) / step);
if (len <= 0) return [];
var range = new Array(len);
for (var i = start, idx = 0; true; i += step) {
// 当i - stop or stop - i的值大于0时,停止变量,返回数组,大于0则停止遍历的原因是当step>0时,i=start,此时start - stop是要<0的,所以当>=0时表示i的值已经到stop or大于stop了;当step<0时,stop是要小于start的,所以stop - i 还是<0,后面同理
if ((step > 0 ? i - stop : stop - i) >= 0) return range;
range[idx++] = i;
}
}
bind优化了实现方式,这里优化只是实现更合理一些,当传入的上下文对象为空时,不是直接返回func而是传一个空给上下文对象,含糊调用的时候上下文对象就是{}对象
0.1.0
bind : function(func, context) {
// 如果传入的执行上下文为空则直接返回
if (!context) return func;
var args = _.toArray(arguments).slice(2);
return function() {
// 合并_.bind()时传入的参数与调用_.bind()()时传入的参数进行合并
var a = args.concat(_.toArray(arguments));
// 调用apply方法,传入参数
return func.apply(context, a);
};
}
1.0.0
bind = function(func, obj) {
var args = _.rest(arguments, 2);
return function() {
return func.apply(obj || {}, args.concat(_.toArray(arguments)));
};
}
bindAll优化了参数处理的方式
0.1.0
bindAll : function() {
var args = _.toArray(arguments);
// 第一个参数为上下文
var context = args.pop();
_.each(args, function(methodName) {
context[methodName] = _.bind(context[methodName], context);
});
}
1.0.0
bindAll = function(obj) {
// 获取除第一个参数之外的其它参数
var funcs = _.rest(arguments);
// 如果funcs为0,那么获取传入对象内方法集合,兼容参数的写法
if (funcs.length == 0) funcs = _.functions(obj);
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
return obj;
}
delay、defer优化了内部获取参数的方式
delay = function(func, wait) {
var args = _.rest(arguments, 2);
return setTimeout(function(){ return func.apply(func, args); }, wait);
}
defer = function(func) {
return _.delay.apply(_, [func, 1].concat(_.rest(arguments)));
}
compose(允许按顺序调用函数队列(从后往前调),并获取所有函数调用完之后的返回值)
compose = function() {
var funcs = _.toArray(arguments);
return function() {
// 获取_.compose()()调用时的参数
var args = _.toArray(arguments);
for (var i=funcs.length-1; i >= 0; i--) {
// 从后往前调用函数,处理args
args = [funcs[i].apply(this, args)];
}
// 返回args,所以这个函数主要目的是按函数依次处理_.compose()()传入的参数,并返回最终的处理结果
return args[0];
};
}
keys优化内部实现,实现思路是先判断是否支持元素的keys事件,如果不支持则,先判断是否是数组,如果是数组则,取数组内的所有元素,并返回,如果是对象则使用for in遍历,加一层hasOwnProperty判断
0.1.0
keys : function(obj) {
return _.pluck(obj, 'key');
}
keys = nativeKeys || function(obj) {
if (_.isArray(obj)) return _.range(0, obj.length);
var keys = [];
for (var key in obj) if (hasOwnProperty.call(obj, key)) keys.push(key);
return keys;
}
values优化内部实现,因为map返回值为函数,所以直接调用map方法即可
0.1.0
values : function(obj) {
return _.pluck(obj, 'value');
}
values = function(obj) {
return _.map(obj, _.identity);
};
functions(获取对象内的方法,并按顺序返回一个包含改对象内所有方法的数组)
functions = function(obj) {
// 按key对应的value来进行过滤,然后调用sort来进行排序
return _.filter(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort();
}
extend优化获取参数的方式
0.1.0
extend : function(destination, source) {
for (var property in source) destination[property] = source[property];
return destination;
}
1.0.0
extend = function(obj) {
// 获取除第一个参数之外的其它参数,即允许同时扩展多个对象
each(_.rest(arguments), function(source) {
for (var prop in source) obj[prop] = source[prop];
});
return obj;
}
clone拓展了内部功能
0.1.0
clone : function(obj) {
return _.extend({}, obj);
}
1.0.0
// 支持克隆数组
clone = function(obj) {
if (_.isArray(obj)) return obj.slice(0);
return _.extend({}, obj);
}
isEqual优化内部判断方式及拓展了判断支持的范围
0.1.0
isEqual : function(a, b) {
// Check object identity.
if (a === b) return true;
// Different types?
// 如果不是一个类型则直接返回false
var atype = typeof(a), btype = typeof(b);
if (atype != btype) return false;
// Basic equality test (watch out for coercions).
if (a == b) return true;
// One of them implements an isEqual()?
if (a.isEqual) return a.isEqual(b);
// If a is not an object by this point, we can't handle it.
if (atype !== 'object') return false;
// Nothing else worked, deep compare the contents.
var aKeys = _.keys(a), bKeys = _.keys(b);
// Different object sizes?
if (aKeys.length != bKeys.length) return false;
// Recursive comparison of contents.
for (var key in a) if (!_.isEqual(a[key], b[key])) return false;
return true;
}
1.0.0
isEqual = function(a, b) {
// Check object identity.
if (a === b) return true;
// Different types?
var atype = typeof(a), btype = typeof(b);
if (atype != btype) return false;
// Basic equality test (watch out for coercions).
if (a == b) return true;
// One is falsy and the other truthy.
if ((!a && b) || (a && !b)) return false;
// One of them implements an isEqual()?
if (a.isEqual) return a.isEqual(b);
// Check dates' integer values.
if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
// Both are NaN?
if (_.isNaN(a) && _.isNaN(b)) return true;
// Compare regular expressions.
if (_.isRegExp(a) && _.isRegExp(b))
return a.source === b.source &&
a.global === b.global &&
a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline;
// If a is not an object by this point, we can't handle it.
if (atype !== 'object') return false;
// Check for different array lengths before comparing contents.
if (a.length && (a.length !== b.length)) return false;
// Nothing else worked, deep compare the contents.
var aKeys = _.keys(a), bKeys = _.keys(b);
// Different object sizes?
if (aKeys.length != bKeys.length) return false;
// Recursive comparison of contents.
for (var key in a) if (!_.isEqual(a[key], b[key])) return false;
return true;
}
isEmpty(判断数组or对象是否为空)实现思路数组利用length来判断,对象遍历来判断
isEmpty = function(obj) {
if (_.isArray(obj)) return obj.length === 0;
for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
return true;
}
isArray(判断是否是数组)实现思路是先判断是否支持isArray方法,不支持则直接判断传入的数据是否有数组原型上特有的方法
isArray = nativeIsArray || function(obj) {
return !!(obj && obj.concat && obj.unshift);
}
isArguments(判断是否是arguments)实现思路是判断length属性是否为number类型,是否有数组原型上的concat方法,字符串上的substr方法,函数的apply方法及对象的length属性是否可枚举从而判断是原型上的属性还是对象上的属性
isArguments = function(obj) {
// 具有length属性,排除数组,排除字符串,排除函数、排除自定义对象上的length属性
return obj && _.isNumber(obj.length) && !obj.concat && !obj.substr && !obj.apply && !propertyIsEnumerable.call(obj, 'length');
}
isFunction(判断是否是函数)优化了内部实现,之所以不使用typeof来直接判断是因为typeof在检查正则时,在不同的浏览器下也会检查为function,如Chrome 1-12中,所以使用constructor属性及原型上的call与apply方法来进行判断
0.1.0
isFunction : function(obj) {
return typeof obj == 'function';
}
1.0.0
isFunction = function(obj) {
return !!(obj && obj.constructor && obj.call && obj.apply);
}
isString(判断是否是字符串)通过判断空字符及是否具有原型上的charCodeAt及substr方法来进行判断
isString = function(obj) {
return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
}
isNumber(判断是否是数字)通过判断是否等于自身,这里使用了一个+,这个+的作用是可以进行隐式转化,可以将其它类型的值转换为number类型的值,类似于parseInt但是比parseInt更强大,另外就是直接使用Object.proptotype.toString.call()方法来进行判断
isNumber = function(obj) {
return (obj === +obj) || (toString.call(obj) === '[object Number]');
}
isBoolean(判断是否是布尔值)通过判断true和false来进行判断
isBoolean = function(obj) {
return obj === true || obj === false;
}
isDate(判断是否是日期类型)通过判断是否有原型上的getTimezoneOffset 与setUTCFullYear方法来进行判断
isDate = function(obj) {
return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear);
}
isRegExp (判断是否是正则)通过判断是否有原型上的test、exec方法及是否使用了i标志,ignoreCase 属性表明正则表达式是否使用了 "i" 标志,使用了则表示true,没使用则表示false
isRegExp = function(obj) {
return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false));
}
isNaN(判断是否是NaN)
isNaN = function(obj) {
return _.isNumber(obj) && isNaN(obj);
}
isNull(判断是否是null)
isNull = function(obj) {
return obj === null;
}
noConflict(避免产生冲突)实现的思路是先保存underscore加载之前的_变量值,在调用noConflict()方法的时候,在将之前保存的值赋给全局对象下的_属性,最后返回this
ko = noConflict();然后直接使用ko代替_来调用方法,其它不变
noConflict = function() {
root._ = previousUnderscore;
return this;
}
identity (返回自身)抽出来的一个公共函数
identity = function(value) {
return value;
}
times(调用指定的函数n次)实现思路就是一个for循环
times = function (n, iterator, context) {
for (var i = 0; i < n; i++) iterator.call(context, i);
}
breakLoop(中断循环)抽出来的一个公共函数,breaker是基础设置那里定义的一个变量
breakLoop = function() {
throw breaker;
}
mixin(混合方法)实现思路就是一个简单的遍历,用于将wrapper原型上绑定上underscore上的静态方法
each(_.functions(obj), function(name){
addToWrapper(name, _[name] = obj[name]);
});
}
uniqueId优化了内部实现在0.1.0中是包值赋给了_上的idCounter属性,而在1.0.0中是直接定义了idCounter这个变量,利用的是闭包的特性
0.1.0
uniqueId : function(prefix) {
var id = this._idCounter = (this._idCounter || 0) + 1;
return prefix ? prefix + id : id;
}
1.0.0
var idCounter = 0;
uniqueId = function(prefix) {
var id = idCounter++;
return prefix ? prefix + id : id;
}
template优化了内部实现
0.1.0
template : function(str, data) {
var fn = new Function('obj',
'var p=[],print=function(){p.push.apply(p,arguments);};' +
'with(obj){p.push(\'' +
str
.replace(/[\r\t\n]/g, " ")
.split("<%").join("\t")
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%>/g, "',$1,'")
.split("\t").join("');")
.split("%>").join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');");
return data ? fn(data) : fn;
}
1.0.0
templateSettings = {
start : '<%',
end : '%>',
interpolate : /<%=(.+?)%>/g
}
template = function(str, data) {
var c = _.templateSettings;
var endMatch = new RegExp("'(?=[^"+c.end.substr(0, 1)+"]*"+escapeRegExp(c.end)+")","g");
var fn = new Function('obj',
'var p=[],print=function(){p.push.apply(p,arguments);};' +
'with(obj){p.push(\'' +
str.replace(/[\r\t\n]/g, " ")
.replace(endMatch,"\t")
.split("'").join("\\'")
.split("\t").join("'")
.replace(c.interpolate, "',$1,'")
.split(c.start).join("');")
.split(c.end).join("p.push('")
+ "');}return p.join('');");
return data ? fn(data) : fn;
}
_.each = _.forEach;
_.foldl = _.inject = _.reduce;
_.foldr = _.reduceRight;
_.select = _.filter;
_.all = _.every;
_.any = _.some;
_.head = _.first;
_.tail = _.rest;
_.methods = _.functions;
定义wrapper函数用于支持面向对象的写法
var wrapper = function(obj) { this._wrapped = obj; };
定义实现链式调用方法
var result = function(obj, chain) {
return chain ? _(obj).chain() : obj;
}
定义将wrapper原型添加上underscore上的静态方法
var addToWrapper = function(name, func) {
wrapper.prototype[name] = function() {
var args = _.toArray(arguments);
// 将this._wrapped压入arguments内
unshift.call(args, this._wrapped);
//调用_[name].apply(_,args)
return result(func.apply(_, args), this._chain);
};
}
.mixin(); //将underscore上的静态方法混合到wrapper的原型上
// 将pop、push等原型上的方法扩展到wrapper的原型上来,注意这些方法只扩展到了wrapper的原型
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
method.apply(this._wrapped, arguments);
return result(this._wrapped, this._chain);
};
})
each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain);
};
})
链式调用的入口函数
wrapper.prototype.chain = function() {
this._chain = true;
return this;
}
链式调用之后的取值函数
wrapper.prototype.value = function() {
return this._wrapped;
}
资源链接及参考链接
https://github.com/jashkenas/underscore/tree/1.0.0
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/typeof
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/ignoreCase
console.log(parseInt(10.23));
console.log(~~10.23);
console.log(10.23 >> 0);
console.log(10.23 | 0);
// 第一种方法
var maxArr = function (arr){
return Math.max.apply(null, arr);
}
var minArr = function (arr){
return Math.min.apply(null, arr);
}
// 第二种方法
arr.sort(function(a,b){return a - b;});//小到大
console.log(arr[arr.length - 1])//最大值
console.log(arr[0])//最小值
var getRandomColor = function (){
return '#'+Math.random().toString(16).substr(2,6);
}
var deepCopy = function (obj){
//注意函数不能使用该方法进行深拷贝,因为stringify方法无法对函数进行转换
return JSON.parse(JSON.stringify(obj));
}
var util = (function (){
var classtype;
var init = function (){
classtype = {};
"Boolean Number String Function Array Date RegExp Object Error".split(" ").forEach(function (item, index){
classtype["[object " + item + "]"] = item.toLowerCase();
});
}
return {
checkType: function (obj){
if(!classtype){
init();
}
if(obj == null){
return obj + "";
}
return typeof obj === "object" || typeof obj === "function" ? classtype[Object.prototype.toString.call(obj)] || "object" : typeof obj;
}
}
})();
// 去两边空格
function trim(str) {
return str.replace(/(^\s+)|(\s+$)/g, "");
}
// 去所有空格
function removeAllSpace(str) {
return str.replace(/\s+/g, "");
}
常规方法是遍历整个对象,然后对对象的某个key对应的value进行更改,现在使用JSON.stringify方法,先将对象转化成字符串,然后了利用正则来进行转化
var a = {
name: 'jack',
isRequired: true
}
JSON.parse(JSON.stringify(a).replace(/true/g, 1));
当对象内需要替换的属性值相同的较多时,而只需要替换某一个key对应的value,则上面的方法就不适用了
var b = {
name: 'jack',
isRequired: true,
isShow: true
}
var str = JSON.stringify(b, (key, value) => {
if ( key === 'isRequired') {
return value ? 1 : 0;
}
return value;
})
JSON.parse(str);
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
一般在使用日历插件的时候,需要设置最大日期与最小日期
最小日期一般很好获取,要么是个定值要么是当前值
const START_END_TIME = 7;
获取YYYY-MM-DD的日期格式
utilFormatDate(date) {
const year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
let fixMonth = month >= 10 ? month : '0' + month;
let fixDay = day >= 10 ? day : '0' + day;
let newDate = `${year}-${fixMonth}-${fixDay}`;
return newDate;
},
getStartAndEndDate() {
const date = new Date();
const startDate = this.utilFormatDate(date);
// 设置相隔START_END_TIME天数,如7获取7天后的月日,-1获取昨天的月日等等
date.setDate(date.getDate() + START_END_TIME)
const endDate = this.utilFormatDate(date);
console.log(startDate, endDate);
return {
startDate,
endDate
}
}
const {startDate, endDate} = this.getStartAndEndDate();
focus/blur与focusin/focusout的区别与联系
mouseover/mouseout与mouseenter/mouseleave的区别与联系
cookie及其操作
document.cookie = 'name=qiu; max-age=9999; path=/; domain=domain; secure';
document.cookie = 'name=aaa; path=/; domain=domain; secure';
sessionStorage,localStorage,cookie区别
javascript跨域通信
通过jsonp跨域
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参并指定回调执行函数为onBack
script.src = 'http://www.....:8080/login?user=admin&callback=onBack';
document.head.appendChild(script);
// 回调执行函数
function onBack(res) {
alert(JSON.stringify(res));
}
document.domain + iframe跨域
此方案仅限主域相同,子域不同的跨域应用场景
1.)父窗口:(http://www.domain.com/a.html)
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
document.domain = 'domain.com';
var user = 'admin';
</script>
2.)子窗口:(http://child.domain.com/b.html)
document.domain = 'domain.com';
// 获取父窗口中变量
alert('get js data from parent ---> ' + window.parent.user);
什么闭包,闭包有什么用
===运算符判断相等的流程是怎样的
==运算符判断相等的流程是怎样的
<,>,<=,>=的比较规则
所有比较运算符都支持任意类型,但是比较只支持数字和字符串,所以需要执行必要的转换然后进行比较,转换规则如下:
函数内部arguments变量有哪些特性,有哪些属性,如何将它转换为数组
iframe有那些缺点?
This指向的理解
Ajax原理
Ajax的原理简单来说是在用户和服务器之间加了—个中间层(AJAX引擎),通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据
var xhr = null;
xhr = new XMLHttpRequest()
// 2. 连接服务器
xhr.open('get', url, true)
// 3. 发送请求
xhr.send(null);
// 4. 接受请求
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
success(xhr.responseText);
} else { // fail
fail && fail(xhr.status);
}
}
}
异步加载JS的方式有哪些?
defer和async的区别
说说你对AMD和CommonJs的理解
CommonJS是服务器端模块的规范,Node.js采用了这个规范。CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数
AMD推荐的风格通过返回一个对象做为模块对象,CommonJS的风格通过对module.exports或exports的属性赋值来达到暴露模块对象的目的
严格模式常见的限制
addEventListener 兼容ie9+,所以如果项目是在ie9+以上浏览器内运行,直接就可以用,不用考虑兼容性
判断js内某个字符串全是数字
如何获取浏览器滚动条高度
forEach,map, forin不能遍历dom合集,要遍历dom集合需要使用for or 将dom集合转换成数组
var oLis = document.getElmentsByTagName('li');
// 使用for循环遍历
for (var i = 0; i < oLis.length; i++) {}
// 将合集转换成数组,然后在遍历
var arrOlis = Array.prototype.slice.call(oLis);
var arrOlis = Array.from(oLis);
var arrOlis = [...oLis];
Object.assign
addClass removeClass hasClass toggleClass
function hasClass (dom, className) {
var reg = new RegExp('(^|\\s)' + className+ '(\\s|$)');
return reg .test(dom.className);
}
function addClass (dom, clsName) {
if (!hasClass(dom, clsName)) {
let classStr = dom.getAttribute('class');
dom.className = classStr.trim() + ' ' + clsName;
}
};
function removeClass (dom, clsName) {
if (hasClass(dom, clsName)) {
let classStr = dom.getAttribute('class');
let newClassStr = classStr.replace(clsName, '');
dom.className = newClassStr;
}
}
function toggleClass (dom, clsName) {
if (hasClass(dom, clsName)) {
removeClass(dom, clsName);
} else {
addClass(dom, clsName);
}
};
appendChild方法插入的元素怎么样去使用transition动画
requestAnimationFrame方法可以在什么样的场景下使用
elementsFromPoint方法的作用是什么
apply 与 call
为什么ES5及以下的JS无法完美继承数组,最优的方式是什么
throw语句
try {
throw '__break__';
} catch (e) {
这里的e就是throw后面的内容
}
propertyIsEnumerable
forEach方法
undefined与void 运算符
for...in语句
0.1 +0.2===0.30000000000000004的原因
var numObj = 12345.6789;
numObj.toFixed(); // 返回 "12346":进行四舍五入,不包括小数部分
numObj.toFixed(1); // 返回 "12345.7":进行四舍五入
numObj.toFixed(6); // 返回 "12345.678900":用0填充
(1.23e+20).toFixed(2); // 返回 "123000000000000000000.00"
Babel的相关知识
Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。举例来说,ES6在Array对象上新增了Array.from方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片
babel-preset-env 的工作方式类似 babel-preset-latest,唯一不同的就是 babel-preset-env 会根据配置的 env 只编译那些还不支持的特性。即babel-preset-env包含了babel-preset-2015,babel-preset-2016等,但不包括babel-polyfill及babel-preset-stag
babel-polyfill的正确使用方式
beforeunload事件
encodeURIComponent
JSbarcode二维码生成插件
base64的图片
input file属性提交本地文件
input[type='file'] {
position: absolute;
z-index: 999;
height: 34px;
left: 0;
top: 0;
width: 100%;
padding: 0;
background: none;
border: 0;
opacity: 0;
filter: alpha(opacity=0);
font-size: 100px;
cursor: pointer;
}
生成表格单元格的方法
jquery removeClass
jquery end
jquery closest("选择器")
jquery data
font-family设置字体类型
关于iframe引入页面进行打印的小结
原生js及jquery,父页面获取iframe页面的window对象,document对象,及元素的方式,iframe页面获取父页面window对象,document对象,及元素的方法
js获取iframe元素的window对象 window.iframes["name名字"] 兼容chrome,ff,ie9+
document.getElementById("iframeid").contentWindow or this.contentWindow
jquery获取iframe元素的window对象
$("iframe")[0].contentWindow
js获取iframe的document对象 window.iframes["name名字"].document or document.getElementById("iframeid").contentWindow.document or this.contentWindow.document
jquery获取iframe的document对象 $("iframe")[0].contentWindow.document or $(this.contentWindow.document)
js获取获取iframe的元素
window.iframes["name名字"].document.getElementById("test")
jquery获取获取iframe的元素 $($("iframe")[0].document).find("#test") or $(this.contentWindow.document).find(".ui-label-content-body")
js iframe获取父页面的window对象
window.parent or parent
jquery获取iframe元素的window对象
$(window.parent) or $(parent)
js获取iframe的document对象
window.parent.document
jquery获取iframe的document对象
$(window.parent.document) or $(parent.document)
js获取获取iframe的元素
window.parent.document.getElementById("test") or parent.document.getElementById("test")
jquery获取获取iframe的元素 $("#test", parent.document) or $(window.parent.document).find("#test"); or $(parent.document).find("#test");
当动态创建多个iframe时,怎么去保证当前iframe里面的值就是创建当前iframe时主页面中的值
浏览器输出的对象的key值不是按后台返回的key值顺序来显示
同名的函数与变量函数的提示在变量之后,即先找变量,找到a,然后将a赋值为undefined,然后找函数,发现函数有同名变量,然后会去覆盖同名变量a
// code种类code11、code39、code93、code128、ean8、ean13、std25、int25、msi、datamatrix
$("#bcTarget").barcode("156130510575933", "code128",{
barWidth: 2, //单条条码宽度(即最小条码宽度)
barHeight: 50, //单体条码高度
addQuietZone: false, //是否添加空白区(内边距)
moduleSize: 5,
showHRI: true, //是否显示底部条码描述
marginHRI: 5, //底部条码的margin-top
bgColor: "#FFF", //设置条码的背景颜色(包括底部的描述在内)
color: "#000",//设置条码的字体颜色(包括底部的描述在内)
fontSize: 10,
output: "css", //渲染方式 css/bmp/svg/canvas
posX: 10, //没什么用
posY: 20 //没什么用
});
$canvas = $("<canvas></canvas>");
$canvas.JsBarcode("PD170622000001", { //注意这个插件只支持img,canvas,svg这三种标签生成条形码
format: "CODE128", //选择条码生成的类型,这里选code128表示一般商用
lineColor: "#000", //条形码颜色
width: 2, // 条码间距
height: 40, // 条码高度
text: "PD170622000001",
fontSize: 14,
displayValue: true, //是否显示条码下面的值
font: "Arail",
textAlign: "left",
textPosition: "bottom",
textMargin: 2,
// background: #ccc,
margin: 0,
fontOptions: "bold",
valid: function (){ //生成之后的回调
console.log(111111);
}
});
$canvas.appendTo($("#box"));
需要注意的是生成的条码得保证打印的时候不失真,不漏指针,保证扫描枪能够正常扫描,根据在项目中的使用,目前最好的方式是jquery-barcode生成bmp格式的条码是不会出现失真及漏针的情况!
echart.js是一款超级强大的图表制作插件,可以满足我们展示业务数据,如柱形图,饼状图,折线图,仪表图等,因为这次项目内大量地方用到数据展示,所以引入了echart.js插件用于制作图表,先总结下开发的时候遇到的小问题
首先引入echarts.js,使用npm install --save echarts
定制组件,因为复用的地方比较多,所以抽出来成了组件,共封装了四个组件,折线图,柱状图,饼状图,仪表图,封装这四个组件的方法类似
按需引入,因为echarts的组件较多导致文件较大,所以这里需要使用什么就引入什么,bar柱状图,gauge仪表盘,line折线图,pie饼状图
const echarts = require('echarts/lib/echarts'); // 引入echarts主模块 require('echarts/lib/chart/bar'); // 引入柱状图 require('echarts/lib/component/tooltip'); // 引入提示框 require('echarts/lib/component/title'); // 引入标题组件 require('echarts/theme/macarons'); // echarts 主题
柱状图通过xAxis,与yAxis的type属性的值来决定哪个是类目轴哪个是数值轴,category类目轴,value表示数值轴
xAxis: { // type参数决定那个是类目轴哪个是数值轴 type: this.isShowX ? 'category' : 'value', },
yAxis: { type: this.isShowX ? 'value' : 'category', },
怎样让echart.js制作出来的图表随着浏览器的窗口大小的变化而变化
window.addEventListener('resize', this.chart.resize)
window.removeEventListener('resize', this.chart.resize);
,注意要在this.chart.dispose();之前注销怎么去动态设置图表的值,让图表随着后端请求来的值进行实时更新
柱状图,折线图不显示坐标轴,通过设置xAxis,yAxis下的axisLine,不显示背景色通过设置xAxis,yAxis下的splitLine与splitArea
legend属性用于设置切换索引
echart.js的使用不难,难的时配置参数的查找;
封装有该组件的项目地址
单例模式故名思议只允许实例化一次的对象类;常用于提供命名空间、模块化开发、及提供私有方法与属性
1. 提供命名空间及模块化开发
var WMS = {
util: {
util_random: function (){
return Math.random();
}
},
Tool: {
tool1_getEle: function (id){
return document.getElementById(id)
}
},
Ajax: {
get: function (){
},
post: function (){
}
}
}
2. 提供私有变量及方法(主要是利用自执行函数来完成)
//还可以用来设置静态变量
var Conf = (function (){
var conf = {
MAX_NUM: 500,
MIN_NUM: 1,
length: 10
}
var getConfValue = function (attr){
return conf[attr] ? conf[attr] : null;
}
return {
get: getConfValue
}
})();
//取值
var minNnm = Conf.get("MIN_NUM");
// 惰性单例
var lazySingle = (
var _instance = null;
var fn = function () {
var name = null;
return {
getName: function () {
return name;
},
setName: function (name) {
name = name;
}
}
}
return function () {
if (!_instance) {
_instance = fn();
}
return _instance
}
)();
lazySingle().setName('jack');
lazySingle().getName();
工厂模式故名思议就是由一个工厂对象来创建某一种产品对象类的实例
var Basketball = function () {
}
Basketball.prototype = {
getName: function () {
return this.name;
},
setName: function (name) {
this.name = name;
}
}
var Football = function () {
}
Football.prototype = {
getName: function () {
return this.name;
},
setName: function (name) {
this.name = name;
}
}
var Tennis = function () {
}
Tennis.prototype = {
getName: function () {
return this.name;
},
setName: function (name) {
this.name = name;
}
}
// 工厂类
var Factory = function (name) {
switch (name) {
case 'NBA':
return new Basketball();
case 'wordcup':
return new Football();
case 'FrenchOpen':
return new Tennis();
}
}
var footnall = Factory('wordcup');
footnall.getName();
观察者模式,也称发布-订阅者模式,主要目的是用于解决主体对象与观察者之间的功能解耦,一帮用于不同组件间的通讯,核心方法包括三个,注册方法,发布方法,注销方法,还有一个消息容器
var Observer = (function (){
var _message = {};
return {
regist: function (type,fn){ //注册方法
if(typeof _message[type] === 'undefined'){ //如果没有改类型,则在容器内添加一个数组
_message[type] = [fn];
}else {
_message[type].push(fn); //如果已经注册了该消息类型,那么把回调推送到消息队列中
}
},
fire: function (type,args){//发布方法
if(!_message[type])return;
var events = {
type: type,
args: args || {},
};
for(var i=0; i<_message[type].length; i++){
_message[type][i].call(this,events);
}
},
remove: function (type,fn){//删除方法
if(_message[type] instanceof Array){
var i = _message[type].length - 1;
for(;i>=0;i--){
_message[type][i] === fn && _message[type].splice(i,1)
}
}
}
}
})();
//注意一定要先订阅,才能在发布的时候收到消息,因为只有先注册,把注册的回调push到容器中,那么在发布的时候才能利用call方法来调已经注册在容器中的方法,并且把内容传入到回调函数中
Observer.regist("test",function (e){
console.log(e.args);
});
Observer.fire("test",{age: 18, name: "jack"});
// 举个例子
//消息的展示
(function (){
var addMsgItem = function (e){
var text = e.args.text,
msg = document.getElementById("msg"),
li = document.createElement("li"),
span = document.createElement("span");
span.innerHTML = "X";
li.innerHTML = text;
span.onclick = function (){
msg.removeChild(li);
Observer.fire("removeCommentMsg",{
num: -1
});
}
li.appendChild(span);
msg.appendChild(li);
}
Observer.regist("addCommentMsg",addMsgItem);
})();
//头部消息数量的变化
(function (){
var numEle = document.getElementById("msgNum");
numEle.innerHTML = document.getElementById("msg").children.length;
var changeNum = function (e){
var num = e.args.num;
console.log(parseInt(numEle.innerHTML));
numEle.innerHTML = parseInt(numEle.innerHTML) + num;
}
Observer.regist("addCommentMsg",changeNum);
Observer.regist("removeCommentMsg",changeNum);
})();
//发布评论
(function (){
var btn = document.getElementById("btn"),
textarea = document.getElementById("text");
btn.onclick = function (){
if(!textarea.value)return;
Observer.fire("addCommentMsg",{
num: 1,
text: textarea.value
})
textarea.value = "";
}
})();
外观模式,就是对一些底层的,有兼容性的方法做一层封装,便于统一调用
var addEvent = function (ele,type,fn){
if(ele.addEventListener){
ele.addEventListener(type,fn,false);
}else if(ele.attachEvent){
ele.attachEvent("on"+type,fn);
}else {
ele["on"+type] = fn;
}
}
//单例模式加外观模式定义一个小型方法库
var util = {
addEvent: function (ele,type,fn){
if(ele.addEventListener){
ele.addEventListener(type,fn,false);
}else if(ele.attachEvent){
ele.attachEvent("on"+type,fn);
}else {
ele["on"+type] = fn;
}
},
getEvent: function (e){
return e || window.event;
},
getTarget: function (e){
var e = this.getEvent(e);
return e.target || e.srcElement;
},
preventDefault: function (e){
var e = this.getEvent(e);
if(e.preventDefault) {
e.preventDefault();
}else {
e.returnValue = false;
}
},
stopPropogation: function (e){
var e = this.getEvent(e);
if(e.stopPropagation){
e.stopPropagation();
}else {
e.cancelBubble = true;
}
}
}
var oBtn = document.getElementById("btn");
var test = function (e){
var e = util.getEvent(e),
target = util.getTarget(e);
util.preventDefault(e);
util.stopPropogation(e);
console.log("test");
}
util.addEvent(oBtn,"click",test);
装饰者模式就是在不改变原来对象的基础上,通过对其进行包装拓展(添加新的属性or方法)以满足更复杂的业务需求的方式
var decorator = function (id,fn){
var oBtn = document.getElementById(id);
if(typeof oBtn.onclick === "function"){
var oldFn = oBtn.onclick;//获取之前绑定在click事件上的回调函数
oBtn.onclick = function (){
oldFn();
fn();
}
}else {
oBtn.onclick = fn;
}
}
状态模式,即当一个对象的内部状态发生改变时,会导致器行为的改变,一般用于多个if,else分支,及抽象多个公共部分的情况
var showResult2 = (function (){
var myStatus = {
status0: function (){
console.log("statu0");
},
status2: function (){
console.log("statu2");
},
status3: function (){
console.log("statu3");
},
status4: function (){
console.log("statu4");
},
status5: function (){
console.log("statu5");
},
}
var show = function (result){
myStatus["status" + result] && myStatus["status" + result]();
}
return {
show: show
}
})();
showResult2.show(3);
最近在项目开发中碰到跨域cookie与跨webview的接口携带cookie问题,于是总结一下cookie及前端的角度去看待cookie
为什么会有cookie,及客户端发起请求的时候,浏览器为什么会主动帮助我们在请求头里面添加cookie?
HTTP Cookie(也叫Web Cookie或浏览器Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie使基于无状态的HTTP协议记录稳定的状态信息成为了可能;这页就是为什么会有cookie及浏览器为什么会主动在请求头里面添加cookie;
参数 | 是否必填 | 作用 | 默认值 |
---|---|---|---|
name | 是 | key值(string) | - |
value | 是 | value (string) | - |
domain | 否 | 例如 'example.com', '.example.com' (包括所有子域名), 'subdomain.example.com' 设置cookie访问的域 | 默认为当前文档位置的路径的域名部分 (string或null) |
path | 否 | 例如 '/', '/mydir' 区分cookie作用路径 | 默认为当前文档位置的目录。(string or null) |
exprires or max-age | 否 | cookie有效期 | - |
HttpOnly | 否 | 是否只允许http接口访问cookie | false |
secure | 否 | 是否只允许https接口访问cookie | false |
两者通过是否设置expires or max-age的值来判断,如果没有设置则是会话cookie,即只存在当前浏览器打开的时间内,如果浏览器关闭则默认会话结束,会话cookie会被浏览器清除;而永久cookie则是根据设置的值来确定是否过期,如果过期则删除;
这里有个小问题需要注意的是mac电脑上,关闭浏览器之后打开,发现cookie没有被清掉是,因为我们点击浏览器的x按钮mac电脑只是关闭了当前的浏览器窗口,并没有推出浏览器,这里需要使用command + q
退出之后再打开就会发现会话cookie被清除了;windows下不会有这个问题
cookie参数的详解
expires设置cookie的有效时间,日期必须是GMT的日期格式,可以通过new Date().toUTCSting() or new Date().toGMTSting()来获取;expires="Sun, 10 Jun 2018 08:27:03 GMT"表示在这个日期内有效,超过该日期则失效,浏览器会主动清除; 如果设置的时候日期格式不正确or没有设置该字段,则取默认值1969-12-31T23:59:59.000Z;此时该cookie就是一个会话cookie;如果设置的时候是一个失效GMT的日期则设置不会成功,因为浏览器会清楚失效的cookie;
max-age也是设置cookie的有效日期,只不过expires是http1.0协议中的字段,max-age是http1.1协议中的字段;max-age的值有三种,正值/负值/零;正值表示cookie在创建的时间+max-age的值内有效;负值表示该cookie是会话cookie;零用于删除cookie;需要注意的是,如果在设置cookie的时候,max-age设置的值为0 or 负值则该cookie不会被创建;如果已有该cookie如果max-age设置为0 or 负数则该cookie会被删除;当expires与max-age同时存在时,以max-age设置的时间为准;max-age是以秒来计算,如设置过期时间为一个月document.cookie = 'test=1111; max-age=2592000(302460*60)'
HttpOnly是否允许js去访问该cookie;默认情况设置cookie是不会带上该字段, 即允许js操作该cookie;该作用是只允许服务端来操作该cookie,不允许客户端来操作该cookie;这样限制的目的是保证客户信息的安全,避免被xss攻击时通过一段script脚本内的document.cookie来获取用户信息相关的cookie;最后需要注意的是该字段是只有服务端设置cookie的时候才会生效,客户端通过js设置该字段时是无效的;
secure用来设置cookie只有在安全的协议传输时才会被带上,如https等;默认情况下设置cookie时,是不会被带上的;一般不会设置该字段,应该这样可以保证在http or https的请求中该cookie都能够被带上;还有我们可以通过js来直接设置该字段;
domain 和 path, domain是域名,path是路径,这两者决定了cookie能被哪些url访问;domain的默认值为设置当前cookie的网页的域名;path的默认值为设置当前cookie的网页所在的目录;如domain=www.baidu.com,path=/;若请求的URL(URL 可以是js/html/img/css资源请求,但不包括 XHR 请求)的域名是“baidu.com”或其子域如“api.baidu.com”、“dev.api.baidu.com”,且 URL 的路径是“/ ”或子路径“/home”、“/home/login”,则浏览器会自动将此 cookie 添加到该请求的 cookie 头部中;需要注意的是不包括xhr的原因是当跨域请求是,就算domain path都满足,浏览器也不会将相应的cookie带上,这时则需要前后端配合才能够跨域携带cookie;还有需要注意的是domain是可以设置为页面本身的域名(本域),或页面本身域名的父域,但不能是公共后缀 public suffix。举例说明下:如果页面域名为 www.baidu.com, domain可以设置为“www.baidu.com”,也可以设置为“baidu.com”,但不能设置为“.com”或“com”。
cookie的设置
基础设置
document.cookie = 'type=js'; === document.cookie = 'type=js;domain=localhost(当前域名);path=/;expires= 1969-12-31T23:59:59.000Z;';
设置带失效时间的cookie
document.cookie = 'type=js; max-age=60*60;'
document.cookie = 'type=js; expires="Sun, 10 Jun 2018 09:02:01 GMT"'
设置domain与path的cookie
document.cookie = 'type=js; max-age=60*60;domain=www.baidu.com; path=/' 需要注意不能在自己网页的js中设置cookie的domain为其它域名的domain
设置安全协议连接才允许访问的cookie
document.cookie = 'type=js; max-age=60*60; domain=www.baidu.com; path=/; secure'
cookie的读取
直接通过document.cookie来读取cookie,不过这个是根据该页面的所在文件的目录,获取该域下domain及path相同及父目录下的的没有设置HttpOnly的所有cookie;例如有两个cookie,document.cookie = 'type1=js; path=/;',document.cookie = 'type2=js; path=/page;'在根目录对应的页面内执行document.cookie只能够获取到type1这个cookie,不能获取/page下的cookie;而在/page目录下的页面通过document.cookie则可以获取到type2和type1这两个cookie;允许读取父目录下的cookie;同理domain也是一样的,子可以读取父,但是父不能读取子;
cookie的修改
cookie的修改就是直接覆盖,获取之后,修改其它可配置的字段;
cookie的删除
cookie的删除,会话cookie除外,可以通过max-age设置成0 or 负值;or将expires的值设置成一个过期的 时间;这里需要注意,如果设置expires值的时候,如果不是一个GMT时间,则浏览器不会立马删除该cookie,会把该cookie变成会话cookie;如果设置是一个过期的GMT时间才会立马删除;另外通过expires设置的cookie可以通过max-age为负数or0来删除;而设置max-age的cookie则不能通过设置expires的值来删除,因为max-age的优先级比expires高
// 删除当前页面也就是path下的所有cookie的方法
function deletCookies(path = '/') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i];
const eqPos = cookie.indexOf('=');
const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" + path;
}
}
function deletCookies(path = '/') {
const cookies = document.cookie.split(";");
cookies.forEach(function(c) {
document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=" + path);
});
}
// 删除所有目录下的cookie,需要注意的是这个方法需要在最子的目录页面上执行
function deletAllCookies() {
const paths = ['/', '/page', 'basic'];
for (let i = 0; i < paths.length; i++) {
deletCookies(paths[i]);
}
}
怎么解决跨域xhr携带cookie;
默认情况下,在发生跨域时,cookie 作为一种 credential 信息是不会被传送到服务端的。必须要进行额外设置才可以。原因是在CORS标准中做了规定,默认情况下,浏览器在发送跨域请求时,不能发送任何认证信息(credentials)如"cookies"和"HTTP authentication schemes"。除非xhr.withCredentials为true(xhr对象有一个属性叫withCredentials,默认值为false)。
所以根本原因是cookies也是一种认证信息,在跨域请求中,client端必须手动设置xhr.withCredentials=true,且服务端也必须允许request能携带认证信息(即response header中包含Access-Control-Allow-Credentials:true),这样浏览器才会自动将cookie加在request header中。
另外,要特别注意一点,一旦跨域request能够携带认证信息,服务端一定不能将Access-Control-Allow-Origin设置为*,而必须设置为请求页面的域名。
如使用axios来进行请求数据时,需要设置该属性如下所示
axios.defaults.withCredentials = true;
cookie渐渐被淘汰的原因
参考链接:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies
https://segmentfault.com/a/1190000004556040
数组去重的方法很多,于是把平常自己用到的总结了一下
var fn1 = function (arr){
var arr1 = [];
for(var i=0; i<arr.length; i++){
if(arr1.indexOf(arr[i]) === -1){
arr1.push(arr[i]);
}
}
return arr1
}
var fn3 = function (arr){
var arrObj = {},
arr2 = [];
for(var j=0; j<arr.length; j++){
//区分1与'1'
var typeEle = typeof arr[j] + arr[j];
if(!arrObj[typeEle]){
arrObj[typeEle] = 1;
arr2.push(arr[j]);
}
}
return arr2
}
数组的下标去重,原理就是当数组内当前元素的下标与自身的下标相等时,表示该数组里面只有一个当前元素,当下标不相等时,表示不只一个当前元素
var fn4 = function (arr){
var ret = [];
arr.forEach(function (item,index,ar){
if(ar.indexOf(item) === index){
ret.push(item);
}
})
return ret;
}
先排序后比较,当后一项不等于前一项时,就不是重复项
var fn6 = function (arr){ // 当数组内有字母时,去重不准确
var ret = [];
arr = arr.sort(function (x,y){
return x-y
})
var end = arr[0];
ret.push(end);
for(var i=1; i<arr.length; i++){
if(arr[i] !== end){
ret.push(arr[i]);
end = arr[i];
}
}
return ret;
}
filter不会改变原数组,会返回一个新的过滤后的数组,filter方法是通过true与false来确定返回的数组中是否包含改元素,true包含,false不包含
var fn12 = function (arr){
return arr.filter(function (item, index, array){
return index === array.indexOf(item);
})
}
var fn7 = function (arr){
var max = 1,
typeEle,
ret = [],
retObj = {},
maxItem = [];
for(var i=0; i<arr.length; i++){
typeEle = typeof arr[i] + arr[i];
if(!retObj[typeEle]){
ret.push(arr[i]);
retObj[typeEle] = 1;
}else {
retObj[typeEle]++;
}
if(retObj[typeEle] === max){
maxItem.push(arr[i]);
}else if (retObj[typeEle] > max){
maxItem.length = 0;
max = retObj[typeEle];
maxItem.push(arr[i]);
}
}
return {
maxItem: maxItem, //重复项最多的项
ret: ret, //去重后的数组
max: max, //重复的最大次数
retObj: retObj //无重复项的对象
}
}
使用数组的sort方法排序,当有字母的时候不建议用
var fn8 = function (arr){
arr.sort(function (x,y){
// return x-y //小到大
return y-x //大到小
});
return arr;
}
var fn9 = function (arr){
var temp;
for(var i=0; i<arr.length-1; i++){
for(var j=0; j<arr.length-1-i; j++){
if(arr[j]>arr[j+1]){//从小到大
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
return arr;
}
var fn10 = function (arr){
var max,
k;
for(var i=0; i<arr.length-1; i++){
max = arr[i];
k = i;
for(var j=i+1; j<arr.length; j++){
if(arr[j]>max){
max = arr[j];
k = j;
}
}
arr[k] = arr[i];
arr[i] = max;
}
return arr;
}
git add
before you commit.#! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序,如#!/bin/sh or #!/bin/bash or #!/bin/node
第一种是作为可执行程序,需要注意的是,一定要写成 ./test.sh,而不是 test.sh,运行其它二进制的程序也一样,直接写 test.sh,linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 test.sh 是会找不到命令的,要用 ./test.sh 告诉系统说,就在当前目录找
chmod +x ./test.sh #使脚本具有执行权限
./test.sh #执行脚本
第二种是作为解释器参数
/bin/sh test.sh
/bin/php test.php
定义变量时,变量名不加美元符号,变量名和等号之间不能有空格,变量名跟js的变量名一样,不能以数字开头等;name="jack"
使用变量时,需要在变量名前加$,变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界;变量能过被重新赋值;
name="jack"
echo $name
echo ${name}
只读变量,在变量名前加readonly关键字
name="rose"
readonly name
删除变量,使用unset操作符,不能删除只读变量
name="rose"
unset name
echo $name // 为空
字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号,也可以不用引号。
单引跟双引的区别:单引号字符串中的变量是无效的,双引号中才可以;双引号里可以出现转义字符,单引号不行;建议使用双引
获取字符串的长度${#name}
截取字符串${#name:1:3}
bash支持一维数组(不支持多维数组),并且没有限定数组的大小,通过下标来取值,下标从0开始
定义数组:数组名=(值1 值2 ... 值n)
读取数组:${数组名[下标]},使用 @ 符号可以获取数组中的所有元素
获取数组的长度:length=${#array_name[@]} or length=${#array_name[*]}
arr=(1 3 5 7)
echo ${arr[1]} ${#arr[@]}
单行注释:#
多行注释::<<EOF EOF or :<<! !
我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……
获取第n个参数$n
获取参数的个数 $#
获取所有参数的字符串 $*
获取最后命令的退出状态 $? 0表示没有错误,其它表示有错误
echo $1 $2 $3 $# $* $?
算数运算符 + - * / = == != 针对的是数字
关系运算符 -eq(等于) -ne(不等) -gt(大于) -lt(小于) -ge(大于等于) -le(大于小于) 只支持数字,不支持字符串,除非字符串的值是数字
布尔运算符 ! -o -a
逻辑运算符 && ||
字符串运算符 = != -z -n str
文件测试运算符
操作符 | 说明 | 举例 |
---|---|---|
-b file | 检测文件是否是块设备文件,如果是,则返回 true。 | [ -b $file ] 返回 false。 |
-c file | 检测文件是否是字符设备文件,如果是,则返回 true。 | [ -c $file ] 返回 false。 |
-d file | 检测文件是否是目录,如果是,则返回 true。 | [ -d $file ] 返回 false。 |
-f file | 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 | [ -f $file ] 返回 true。 |
-g file | 检测文件是否设置了 SGID 位,如果是,则返回 true。 | [ -g $file ] 返回 false。 |
-k file | 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 | [ -k $file ] 返回 false。 |
-p file | 检测文件是否是有名管道,如果是,则返回 true。 | [ -p $file ] 返回 false。 |
-u file | 检测文件是否设置了 SUID 位,如果是,则返回 true。 | [ -u $file ] 返回 false。 |
-r file | 检测文件是否可读,如果是,则返回 true。 | [ -r $file ] 返回 true。 |
-w file | 检测文件是否可写,如果是,则返回 true。 | [ -w $file ] 返回 true。 |
-x file | 检测文件是否可执行,如果是,则返回 true。 | [ -x $file ] 返回 true。 |
-s file | 检测文件是否为空(文件大小是否大于0),不为空返回 true。 | [ -s $file ] 返回 true。 |
-e file | 检测文件(包括目录)是否存在,如果是,则返回 true。 | [ -e $file ] 返回 true。 |
注意使用到运算符的地方,必须需要注意的是条件表达式要放在方括号之间,并且要有空格,例如: [$a==$b] 是错误的,必须写成 [ $a == $b ]
a=10
b=5
echo `expr $a + $b`
if [ $a == $b ]
then
echo "a 等于 b"
fi
if [ $a != $b ]
then
echo "a 不等于 b"
fi
if [ $a -eq $b ]
then
echo "$a -eq $b : a 等于 b"
else
echo "$a -eq $b: a 不等于 b"
fi
str="i am fine"
str1="i am fine"
if [ -z "$str" ]
then
echo '为0'
else
echo '不为0'
fi
if [ "$str1" ]
then
echo '不为空'
else
echo '为空'
fi
file="./test.sh"
file1="./src"
if [ -x $file ]
then
echo "文件可执行"
else
echo "文件不可执行"
fi
if [ -s $file ]
then
echo "文件不为空"
else
echo "文件为空"
fi
if [ -d $file1 ]
then
echo "文件是目录"
else
echo "文件不是目录"
fi
if [ -e $file ]
then
echo "文件存在"
else
echo "文件不存在"
fi
echo 重定向到文件 也就是把内容输出到某个位置
echo "it a test" > myfile.txt
echo 显示普通字符串的时候可以省略引号
Shell中的 test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试
test命令其实就是运算符中的[]
a=5
b=10
// 二者等价
if [ $a -eq $b ]
then
echo "$a -eq $b : a 等于 b"
else
echo "$a -eq $b: a 不等于 b"
fi
if test $a -eq $b
then
echo "$a -eq $b : a 等于 b"
else
echo "$a -eq $b: a 不等于 b"
fi
// 二者等价
if [ -e $file ]
then
echo "文件存在"
else
echo "文件不存在"
fi
if test -e $file
then
echo "文件存在"
else
echo "文件不存在"
fi
if else语句跟普通的js内的流程语句还是有区别的,如else分支没有语句执行,就不要写这个else;if后面要跟then,需要使用fi来结尾
if condition
then
command1
command2
...
commandN
fi
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi
for循环
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
while
let 命令,它用于执行一个或多个表达式,变量计算中不需要加上 $ 来表示变量
while condition
do
command
done
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done
break 与continue关键字
for loop in 1 2 3 4 5
do
if test $loop -gt "3"
then
echo '循环break'
break
else
echo "The value is: $loop"
fi
done
for loop in 1 2 3 4 5
do
if test $loop -eq "3"
then
echo '循环continue'
continue
else
echo "The value is: $loop"
fi
done
命令 | 说明 |
---|---|
command > file | 将输出重定向到 file。 |
command < file | 将输入重定向到 file。 |
command >> file | 将输出以追加的方式重定向到 file。 |
n > file | 将文件描述符为 n 的文件重定向到 file。 |
n >> file | 将文件描述符为 n 的文件以追加的方式重定向到 file。 |
n >& m | 将输出文件 m 和 n 合并。 |
n <& m | 将输入文件 m 和 n 合并。 |
<< tag | 将开始标记 tag 和结束标记 tag 之间的内容作为输入。 |
需要注意的是文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null
command > /dev/null
echo '输出重定向' > redict.txt
echo '输出重定向覆盖' > redict.txt
echo '输出重定向追加' >> redict.txt
在shell脚本内直接执行npm script内的命令
#!/bin/sh
# To enable this hook, rename this file to "pre-commit".
echo 'hello wolrd'
npm run prettier && npm run lint:fix && npm run lint
git add .
pre-commit钩子内进行eslint检查与代码格式化,实现方案1
#!/bin/sh
# To enable this hook, rename this file to "pre-commit".
echo "pre-commit"
# git rev-parse --show-toplevel显示顶级目录的绝对路径
ESLINT="$(git rev-parse --show-toplevel)/node_modules/.bin/eslint"
PRETTIER="$(git rev-parse --show-toplevel)/node_modules/.bin/prettier"
STAGE_FILES_JS=$(git diff --cached --name-only --diff-filter=ACM -- '*.js')
STAGE_FILES_VUE=$(git diff --cached --name-only --diff-filter=ACM -- '*.vue')
if test ${#STAGE_FILES_JS} -gt 0
then
echo '开始eslint检查js'
# 第一种检查方式
# which eslint &> /dev/null
# if [[ "$?" == 1 ]]; then
# echo '没有安装eslint'
# exit 1
# fi
# which prettier &> /dev/null
# if [[ "$?" == 1 ]]; then
# echo '没有安装prettier'
# exit 1
# fi
# 第二种检查方式
# if [[ ! -x "$ESLINT" ]]; then
# echo "\t\033[41mPlease install ESlint\033[0m (npm i --save --save-exact --dev eslint)"
# exit 1
# fi
# 跟第一种检查方式一样,只不过使用相对路径
which ./node_modules/.bin/eslint &> /dev/null
if [[ "$?" == 1 ]]; then
echo '没有安装eslint'
exit 1
fi
if [[ ! -x "$PRETTIER" ]]; then
echo "\t\033[41mPlease install prettier\033[0m (npm i --save --save-exact --dev prettier)"
exit 1
fi
PASS1=true
for FILE1 in $STAGE_FILES_JS
do
# 如果是本地安装的eslint则需要使用变量,且需要使用"$ESLINT" "$FILE"而不是eslint "$FILE"
"$PRETTIER" "$FILE1" --write --parser flow
./node_modules/.bin/eslint "$FILE1"
if [[ "$?" == 1 ]]; then
PASS1=false
fi
done
if ! $PASS1; then
echo 'eslint js未通过'
exit 1
else
echo 'eslint js通过'
fi
else
echo '暂存区没有需要检查的js文件'
fi
if test ${#STAGE_FILES_VUE} -gt 0
then
echo '开始eslint检查vue'
PASS2=true
for FILE2 in $STAGE_FILES_VUE
do
# 如果是本地安装的eslint则需要使用变量,且需要使用"$ESLINT" "$FILE"而不是eslint "$FILE"
"$PRETTIER" "$FILE2" --write --parser vue
# "$ESLINT" "$FILE2"
./node_modules/.bin/eslint "$FILE2"
if [[ "$?" == 1 ]]; then
PASS2=false
fi
done
if ! $PASS2; then
echo 'eslint vue未通过'
exit 1
else
echo 'eslint vue通过'
fi
else
echo '暂存区没有需要检查的vue文件'
fi
exit 0
pre-commit钩子内进行eslint检查与代码格式化,实现方案2
#!/bin/bash
# pre-commit
echo 'pre-commit start'
# 检查是否安装eslint,注意路径,根据自己的实际项目路径填写
eslint="../../node_modules/.bin/eslint"
prettier="../../node_modules/.bin/prettier"
echo $(git rev-parse --show-prefix)
if test -x $eslint && test -x $prettier
then
echo "eslint及prettier存在"
elif test -x $eslint
then
echo "prettier依赖不存在请进行安装"
exit 1
else
echo "eslint依赖不存在请进行安装"
exit 1
fi
# 获取当前git仓库内暂存区文件
STATGE_FILES=$(git diff --cached --name-only --diff-filter=ACM '*.js' '*.vue')
FILE_VUE_TYPE="vue"
if test ${#STATGE_FILES} -gt 0
then
echo "当前暂存区内有${#STATGE_FILES}个js or vue文件"
# 遍历暂存区文件,使用eslint检查及prettier格式化代码
PAAS=true
for file in $STATGE_FILES
do
# 通过${file##*.}来获取文件后缀,判断是否是js文件还是vue文件
if test ${file##*.} = $FILE_VUE_TYPE
then
$prettier $file --parser vue --write
else
$prettier $file --parser flow --write
fi
# eslint检查
$eslint $file --color
# 判断eslint执行的结果如果最后执行的命令返回不是0表示eslint检查不通过
if test $? -ne 0
then
PAAS=false
fi
done
if ! $PAAS
then
echo "eslint不通过"
exit 1
else
echo "eslint通过"
fi
else
echo "当前暂存区内有没js及vue文件"
fi
exit 0
参考链接:
https://gist.github.com/linhmtran168/2286aeafe747e78f53bf
https://www.tutorialspoint.com/unix/shell_scripting.htm
//页面的加载完毕可以使用onreadystatechange事件来进行dom结构的加载,通过readyState的状态值来进行判断,dom事件加载完毕,complate加载完成
//interactive(交互)也就是正在加载的意思
var complateLoading = function (){
if(document.readyState == "complete"){
var oMask = document.getElementById("mask");
oMask.style.display = "none";
}
}
var ajaxRequest = function (){ //这是第一种方式 就是利用beforesend方法(ajax请求前)来显示loading,success,error方法完成之后再none 掉loading
var oMask = document.getElementById("mask");
$.ajax({
type:"get",
url:"http://127.0.0.1:8020/小结文档/json/nes.json",
data: "",
beforeSend: function (){
oMask.style.display = "block";
},
success: function (data){
console.log(data);
oMask.style.display = "none";
},
error: function (data){
console.log(data);
oMask.style.display = "none";
}
});
}
var ajaxRequestAll = function (){
$.ajax({
type:"get",
url:"http://127.0.0.1:8020/小结文档/json/new.json",
data: "",
success: function (data){
console.log(data);
},
error: function (data){
console.log(data);
}
});
}
$.ajaxSetup({ //第二个思路就是利用ajaxSetup来进行全局配置loading
beforeSend: function (){ //请求之前的操作
console.log("beforeSend");
var oMask = document.getElementById("mask");
oMask.style.display = "block";
},
complete: function (){ //不论请求成功还是失败
console.log("complete");
var oMask = document.getElementById("mask");
oMask.style.display = "none";
}
});
document.onreadystatechange = complateLoading;
怎么判断css动画开始与结束,怎样去优化css动画性能;
为什么要限制http请求中url的长度;
http请求头内包含哪些字段,各有什么作用;
详解http
手写promise
如果递归调用解析多层嵌套的数组;
弄清webpack的pluging机制及loader机制,并写一个pluging与loader
实现LazyMan
实现思路不能按照常规的思路去实现,因为常规思路会卡在sleepFirst无法实现,而最终实现的思路是通过一个数组类保存每一步的操作,然后通过push or unshift压入到数组内,然后定义一个next方法,在next方法内去取一个元素然后执行,这样就可以保证是按照数组的顺序来执行
实现一个LazyMan,可以按照以下方式调用:
LazyMan(“Hank”)输出:
Hi! This is Hank!
LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~
LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出
Hi This is Hank!
Eat dinner~
Eat supper~
LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper
var LazyMan = function (opt) {
return new Wrap(opt);
}
var Wrap = function (opt) {
this.task = [];
this.opt = opt;
var _this = this;
var fn = (function (opt) {
return function () {
console.log('Hi! This is' + opt + '!');
_this.next();
}
})(opt);
this.task.push(fn);
setTimeout(function () {
_this.next();
}, 0);
}
Wrap.prototype = {
next: function () {
var fn = this.task.shift();
fn && fn.apply(this, arguments);
},
eat: function (name) {
var _this = this;
var fn = (function (name) {
return function () {
console.log('Eat ' + name);
_this.next();
}
})(name);
this.task.push(fn);
return this;
},
sleep: function (time) {
var _this = this;
var fn = (function () {
return function () {
setTimeout(function () {
console.log('Wake up after ' + time);
_this.next();
}, time);
}
})(time);
this.task.push(fn);
return this;
},
sleepFirst: function (time) {
var _this = this;
var fn = (function () {
return function () {
setTimeout(function () {
console.log('Wake up after ' + time);
_this.next();
}, time);
}
})(time);
this.task.unshift(fn);
return this;
}
}
LazyMan('Hank');
LazyMan('Hank').sleep(1000).eat('dinner');
LazyMan('Hank').eat('dinner').eat('supper');
LazyMan('Hank').sleepFirst(2000).eat('supper');
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.