Giter Club home page Giter Club logo

blog's Introduction

blog's People

Contributors

willson-wang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

blog's Issues

记一次indexList组件的开发

indexList组件开发只要捋清楚了核心逻辑那么实现起来就会比较简单了,于是总结一下,便于下次查阅;

indexList的两个基本需求

  1. 列表滚动的时候,右侧的导航标识也需要跟着变化;

  2. 点击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;
    }

点击or滑动右边导航标识的时候,左侧的列表可以滚动到对应的位置

实现思路,绑定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;
    },

一种效果的实现方式

indexlist

<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/Generator/Async相关的异步操作

Promise

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');

Gennerator

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

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

浏览器内的事件循环机制及macrotask与microtask

  1. 浏览器的主要组件包括用户界面、浏览器引擎、呈现引擎(即解析html及css的引擎)、网络、用户界面后端、JavaScript 解释器(js引擎如chrome的v8)、数据存储

  2. 清楚浏览器与javascript的关系
    宿主关系,即javascript代码的执行依赖于浏览器,因为浏览器内置了javascript解析器(js引擎,如chrome v8)

  3. event loop到底属于谁的运行机制
    属于浏览器的一套基于事件驱动的循环检测机制,而不是属于js的循环检测机制
    根据规范每个浏览器至少要有一个事件循环机制
    一个eventloop有一个or多个task queues,一个task queues是一系列有序的task集合
    task主要包括Events(并非所有事件都使用任务队列调度,许多任务在其他任务中调度。)、Parsing(解析html)、Callbacks、Using a resource(获取资源)、Reacting to DOM manipulation(dom操作如元素插入文档时)

  4. js是单线程的缘故
    因为js的主要目的是用于用户交互,而同时存在多个线程的话,可能会造成干扰,影响交互

  5. 什么是同步,什么是异步,什么是同步操作(同步任务),什么是异步操作(异步任务)
    一般而言,操作分为:发出调用和得到结果两步。发出调用,立即得到结果是为同步。发出调用,但无法立即得到结果,需要额外的操作才能得到预期的结果是为异步。
    同步:就是调用之后一直等待,直到返回结果。
    异步:异步则是调用之后,不能直接拿到结果,通过一系列的手段才最终拿到结果(调用之后,拿到结果中间的时间可以介入其他任务)
    同步操作: 就是执行同步的过程;直接返回一个值的函数
    异步操作: 就是执行异步的过程;如ajax,定时器

  6. 什么是主线程,什么是任务队列,eventloop
    主线程是浏览器的一个设定,是浏览器的一个运行池,是浏览器的的运行机制,而不是js的运行机制,是所有任务都在上面执行的线程
    任务队列:是一系列包含各种事件,异步操作,定时器等各种队列的一个集合,是在主线程上的一切调用,而队列是任务的集合
    任务:分为macrotask(setTimeout、setInterval、setImmediate、I/O、UI交互事件),microtask(Promise、process.nextTick、MutaionObserver)而主线程与任务队列之间又通过一个eventloop来保证主线程最大程度上来执行js代码(主线程永远在执行中。主线程会不断检查任务队列,即进行某个操作时,会产生某个事件,同时也会设置一个watcher,事件循环的过程中从该watcher上处理事件,处理完已有的事件后,处理下一个watcher,检查完所有watcher后,进入下一轮检查,对某类事件不关心时,则没有相关watcher),避免出现阻塞等现象,即主线程上的代码执行完毕之后,就会去拿任务队列里面的任务来放到主线程执行,依此循环往复,同时任务队列之间是可以插队的,如定时器任务,

  7. javascript是一门事件驱动的脚本语言,而事件驱动就是将一切抽象为事件。IO操作完成是一个事件,用户点击一次鼠标是事件,Ajax完成了是一个事件,一个图片加载完成是一个事件

  8. 定时器是浏览器有一个专门的队列来存放定时器,并且有一个专门的机制来判断定时器插入任务队列的时机,即到达时间点后,会形成一个事件(timeout事件)。不同的是一般事件是靠底层系统或者线程池之类的产生事件,定时器事件是靠事件循环不停检查系统时间来判定是否到达时间点来产生事件
    换个说法当我们进行定时器调用时,首先会设置一个定时器watcher。事件循环的过程中,会去调用该watcher,检查它的事件队列上是否产生事件(比对时间的方式)

  9. DOM,AJAX,setTimeout是浏览器提供的api,而不是javascript提供的api

  10. 发起ajax前的逻辑和ajax的callback的逻辑,是2个任务(事件),所以不存在一个事件状态这种东西,ajax回来以后浏览器只是简单的往事件队列里丢一个任务而已,之前发起ajax的那个任务早就结束消失了

  11. 非堵塞就是 js 可以异步执行,即有异步任务时,不会影响到异步任务之后的代码执行

  12. 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

swiper

<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')">&lt;</div>
    <div class="swiper-next" @click="changeItem('next')">&gt;</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()
    })
  },

移动端开发小结

什么是viewport

<meta name="viewport" content="width=device-width, initial-scale=1.0">

参考:http://www.cnblogs.com/2050/p/3877280.html

7种css3属性选择器的使用

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不支持,所以使用的时候没有兼容性问题

css3伪类选择器

伪类选择器分三类
第一类:动态伪类选择器,因为这些伪类并不存在于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

开发中碰到的问题汇总

  1. ios下点击input,textarea,div等元素or监听了点击的元素,会出现一个灰色的背景色or蒙层;解决办法如下
input, textarea, div {
    -webkit-tap-highlight-color:rgba(0, 0, 0, 0);
}

iframe postMessage 跨域 cookie操作

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;

第一次实践方式,直接在A站内调用清除B站点下cookie的接口;实际上接口是调用成功,但是cookie没有被清掉;原因是ajax接口是在A域下调用的,存在跨域问题,通过在A站点不能清除掉B站点下的cookie;

第二种实践引入iframe,引入一个隐藏的B站点iframe,然后在iframe内直接操作B站点的cookie;另一种方式是通过postMessage来发送消息,然后在message监听回调内进行cookie操作;因为接口后端直接提供了接口,所以没有直接操作是否能直接删除cookie;直接使用的是调用后台提供的接口来清除cookie; 过程如下

在使用postMessage方式之前先看下postMessage方法的兼容性

pc端

image

移动端

image

从can i use上看pc端兼容ie8+,移动端兼容ios4.0+、安卓2.1+,所以我们可以放心大胆的使用postMessage方法

1 在A站点引入一个隐藏的iframe,因为postMessage是window对象上的方法,所以需要先通过contentWindow获取iframe内的window对象

// 创建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)
}

2 B站点下设置监听message回调即可,并且在操作成功之后,在利用postMessage发送回去

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类

  1. 获取网页存储信息,如cookie、localstorage,如iframe内的cookie及localstorage
  2. 操作dom,如iframe内的dom
  3. ajax操作,然后会有什么样的反应,能不能携带cookie,需要什么设置
    上面这三种行为需要好好总结下

链接
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]+

基于微信小程序的axios

/*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,所以就记录下来。
下面列一下一些碰到的疑问

  1. 开发的时候webpack怎么不支持index.html页面的热更新
    因为webpack-dev-server只处理与入口文件有依赖的资源,而我们在各个模块内一般都是引入js资源or css资源,img资源,字体资源,视频音频资源,所有热更新无法覆盖到项目目录下的所有文件,只有与入库文件有依赖关系的资源才会被监视,才会在修改的时候去进行热更新(启用了热更新的前提)
    解决方法是:引入html-withimg-loader,在rules内配置该loader,同时在入库文件内引入index.html即可实现index.html的热更新

  2. webpack打包的时候,html页面内img引入的图片路径没有做处理
    这是webpack本身就不提供这个选项,解决方法有两个
    第一个方法,在js文件内引入改图片资源如import imgURL from './1.jpg'然后通过dom来进行操作
    第二个方法是使用html-withimg-loader这个loader,在rules内配置规则即可,需要注意的时这个loader
    只能处理img标签的src属性,不能处理video等标签的资源引入,所以video的标签资源的引入还需要动
    态设置

  3. 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

  4. 环境变量process.env.NODE_ENV怎么去获取到具体的值
    我们在webpack内使用环境变量的作用是帮助我们进行区分开发环境、测试环境及正式环境,那么要在webpack的配置文件内哪到值需要设置两个地方
    第一处是
    new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"development"' // 定义环境变量 } })

    第二处是
    "dev": set NODE_ENV=development&&webpack-dev-server --open

  5. 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等配置属性

  6. 音频视频文件怎么引入
    配置loader,然后使用require or import导入

  7. HtmlWebpackPlugin插件内的template属性与inject属性具体作用是什么
    template: ‘index.html’ //需要参考的模板 注意前面可以带路径‘views/index.html’,注意这里是相当于根路径
    inject: 'body' // 向template或者templateContent中注入所有静态资源,true或者body:所有JavaScript资源插入到body元素的底部;head: 所有JavaScript资源插入到head元素中;false: 所有静态资源css和JavaScript都不会注入到模板文件中

  8. 使用webpack-dev-server搭建本地服务器时,它的contentBase属性与publicPath属性有什么作用
    contentBase: path.join(__dirname, 'dist') // 告诉webpack-dev-server,在 localhost:8080 下建立服务,服务的文件来自dist目录,这里需要注意的时webpack-dev-server默认生成的dist目录是在内存中,不是项目目录内的dist
    publicPath的作用output内的publicPath作用类似

  9. 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值

  10. webpack内置的CommonsChunkPlugin有什么作用
    抽出公共模块,这里我们的代码js代码一般分为三类,1.引入的第三方框架和库;2.自己写的公共代码;3.其它的单独js文件,我们需要将不经常变的第一类js文件or第二类js文件抽离到公共的一个js文件内,保证浏览器的长时间缓存,提高加载效率

  11. 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/

js内获取尺寸的属性or方法汇总

最近在项目中开发自定义滚动条组件,用到了拖拽及滚动,而用到拖拽及滚动的时候就不得不用到一些获取元素尺寸、滚动高度、鼠标位置、元素距离视口的位置等属性,于是写完组件之后抽了个时间总结了下,希望加深理解

image

标准盒模型

  1. clientWidth = width + padding - scrollBarWidth; 因为滚动条会在border内也就是padding所占位置
  2. offsetWidth = width + padding + border = clientWidth + border ,这里不需要另外加上scrollBarWidth的原因就是scrollBarWidth占用的宽度就是元素自身的width or padding
  3. scrollWidth元素可滚动宽度,不包含元素的margin与border,scrollLeft = ( srollWidth - clientWidth )之间的值
    同理clientHeight offsetHeight scrollHeight是一样的
  4. scrollTop 属性可以获取或设置一个元素的内容垂直滚动的像素数。 注意是这个元素的顶部到它的最顶部可见内容(的顶部)的距离的度量,而不是到视口的高度

box-size: border-box;盒模型 width包含了padding与border

  1. offsetWidth = width;
  2. clientWidth = width - border - scrollBarWidth = offsetWidth - border - scrollBarWidth

同理其它属性值是一样的

getBoudingClientRect()获取到的盒模型 兼容ie9+

  1. .width = offsetWidth;
  2. .height = offsetHeight;
  3. .left = 元素左边框到视口的左侧距离;
  4. .top = 元素上边框到视口的上侧距离;
  5. .right = .left + .width;
  6. .bottom = .top + .height

事件对象event获取的位置属性

  1. offsetX offsetY 相对于target元素边框的位置
  2. pageX pageY 相对于当前页面(整个文档)的左上角的位置 当有滚动条时包含了scrollTop and scrollLeft
  3. clientX clientY 相当于当前可视屏幕的左上角位置

offset定位系列位置属性

  1. offsetLeft: 获取当前元素左侧边框到offsetParent的左侧边框内侧距离
  2. offsetTop: 获取当前元素上侧边框到offsetParent的上侧边框内侧距离
  3. offsetParent: 指向最近的包含改元素的定位元素,如果没有定位元素则为最近的table or table cell or 根元素(标准模式下为 html;quirks 模式下为 body),当元素设置为dispaly:none 时 offsetParent为null

常用的一些场景

  1. 判断元素是否出现滚动条
    根据scorllHeight、scrollWidth与clientHeight、clientWidth的大小关系来进行判断,因为二者获取到的宽高是相同的部分,所有没有滚条条时scrollHeight = clientHeight; scrollWidth = clientWidth;
    判断水平方向滚动条 scrollWidth > clinetWidth 允许滚动的水平范围scrollLeft => scrollWidth - clientWidth
    判断垂直方向滚条条 scrollHeight > clientHeight 允许滚动的垂直范围scrollTop => scrollHeight - clientHeight

  2. 拖拽
    在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;

  3. 获取当前屏幕的尺寸
    screenWidth = document.documentElement.clientWidth || document.body.clientWidth;
    screenHeight = document.documentElement.clientHeight || document.body.clientHeight;

  4. 监听window对象上的scroll事件时获取浏览器滚动条的scrollTop
    scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
    scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;

  5. 获取浏览器滚动条的宽度
    第一种思路设置元素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
});

css常用样式及问题小结

  1. display: none;与visibility: hidden;的区别

    1. 联系:它们都能让元素不可见

    2. 区别:

      1. display:none;会让元素完全从渲染树中消失,渲染的时候不占据任何空间;visibility: hidden;不会让元素从渲染树消失,渲染师元素继续占据空间,只是内容不可见
      2. display: none;是非继承属性,子孙节点消失由于元素从渲染树消失造成,通过修改子孙节点属性无法显示;visibility: hidden;是继承属性,子孙节点消失由于继承了hidden,通过设置visibility: visible;可以让子孙节点显式
      3. 修改常规流中元素的display通常会造成文档重排。修改visibility属性只会造成本元素的重绘。
      4. 读屏器不会读取display: none;元素内容;会读取visibility: hidden;元素内容
  2. specified value,computed value,used value计算方法

    1. specified value: 计算方法如下:
      1. 如果样式表设置了一个值,使用这个值
      2. 如果没有设置值,这个属性是继承属性,从父元素继承
      3. 如果没设置,并且不是继承属性,使用css规范指定的初始值
      4. computed value: 以specified value根据规范定义的行为进行计算,通常将相对值计算为绝对值,例如em根据font-size进行计算。一些使用百分数并且需要布局来决定最终值的属性,如width,margin。百分数就直接作为computed value。line-height的无单位值也直接作为computed value。这些值将在计算used value时得到绝对值。computed value的主要作用是用于继承
      5. used value:属性计算后的最终值,对于大多数属性可以通过window.getComputedStyle获得,尺寸值单位为像素。以下属性依赖于布局,
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
  1. link与@import的区别

    1. link是HTML方式, @import是CSS方式
    2. link最大限度支持并行下载,@import过多嵌套导致串行下载,出现FOUC
    3. link可以通过rel="alternate stylesheet"指定候选样式
    4. 浏览器对link支持早于@import,可以使用@import对老浏览器隐藏样式
    5. @import必须在样式规则之前,可以在css文件中引用其他文件
      总体来说:link优于@import
  2. PNG,GIF,JPG的区别及如何选

    1. GIF:

      1. 8位像素,256色
      2. 无损压缩
      3. 支持简单动画
      4. 支持boolean透明
      5. 适合简单动画
    2. JPEG:

      1. 颜色限于256
      2. 有损压缩
      3. 可控制压缩质量
      4. 不支持透明
      5. 适合照片
    3. PNG:

      1. 有PNG8和truecolor PNG
      2. PNG8类似GIF颜色上限为256,文件小,支持alpha透明度,无动画
      3. 适合图标、背景、按钮
  3. 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
  1. 什么是FOUC?如何避免
    Flash Of Unstyled Content:用户定义样式表加载之前浏览器使用默认样式显示文档,用户样式加载渲染之后再从新显示文档,造成页面闪烁。解决方法:把样式表放到文档的head

  2. stacking context,布局规则

    1. z轴上的默认层叠顺序如下(从下到上):

      1. 根元素的边界和背景
      2. 常规流中的元素按照html中顺序
      3. 浮动块
      4. positioned元素按照html中出现顺序
    2. 如何创建stacking context:

      1. 根元素
      2. z-index不为auto的定位元素
      3. a flex item with a z-index value other than 'auto'
      4. opacity小于1的元素
      5. 在移动端webkit和chrome22+,z-index为auto,position: fixed也将创建新的stacking context
  3. 什么是BFC,BFC有什么用?

    1. BFC是block formatting context,也就是块级格式化上下文,是用于布局块级盒子的一块渲染区域,及一种块级盒子的布局方式,至于有什么用,是因为我们常说的文档流其实分为定位流、浮动流和普通流三种。而普通流其实就是指BFC中的FC。FC是formatting context的首字母缩写,直译过来是格式化上下文,它是页面中的一块渲染区域,有一套渲染规则,决定了其子元素如何布局,以及和其他元素之间的关系和作用,而BFC则是FC的一种扩展方式,可以给我们布局的时候提供一些便利

    2. 换个方式也就是说,变成了BFC的块级盒子,具有了一个普通盒子不具有的功能,它成了一个独立的隔离的容器,外面的元素无法影响到BFC盒子内的元素,BFC盒子内的元素也无法影响到外面的元素,如包含浮动的元素,清除浮动等

    3. 触发BFC的方式

      1. html元素(默认BFC)
      2. float属性值不为none的元素
      3. postion属性值为absolute与fixed的元素
      4. overflow属性值不为visible的元素
      5. display的值为inline-block、table-cell、table-caption
    4. 主要作用

      1. 可以阻止元素被浮动元素覆盖
      2. 包含浮动元素,避免高度塌陷
      3. 阻止margin会发生重叠,注意的是属于同一个BFC的两个相邻块级子元素的上下margin会发生重叠,所以当两个相邻块级子元素分属于不同的BFC时可以阻止margin重叠
// 阻止元素被浮动元素覆盖
.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>
  1. 行内元素float:left后orposition:absolute/fiex后是否变为块级元素?
    浮动后or定位后,行内元素会成为类块状元素(即具有块级元素的特征),可以设置宽高与margin,但是需要注意的是由于浮动与position是脱离了文档流,所以变成块元素的宽度不会是独占一行,而是其内容的宽度or自设的宽度来决定。同理块级元素浮动即定位之后的宽度也一样由内容宽度决定or自设宽度决定;

  2. ::before 和 :after中双冒号和单冒号 有什么区别?
    单冒号(:)用于CSS3伪类,双冒号(::)用于CSS3伪元素
    用于区分伪类和伪元素
    伪类表状态
    伪元素是真的有元素

  3. 设置元素水平成功垂直居中的方式有哪些

// 第一种  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>
  1. 实现左侧定宽右侧自适应的方法有哪些,各有什么优缺点
// 第一种方式,定宽的元素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>
  1. 选择器的权重,除去行内样式,及!important,其它的权重如下所示

    1. ID选择器(例如, #example) 0100简单理解为100
    2. 类选择器(class selectors) (例如,.example),属性选择器(attributes selectors)(例如, [type="radio"]),伪类(pseudo-classes)(例如, :hover,:first-of-type,:nth-child(),:not())0010简单理解为10
    3. 类型选择器(type selectors)(例如, h1)和 伪元素(pseudo-elements)(例如, ::before) 简单理解为1
    4. 通用选择器(universal selector)(*), 组合子(combinators) (+, >, ~, ' ') 简单理解为0
    5. 伪元素如::after,::before,伪类如:active,:nth-child():hover:first-of-type等
    6. 这里计算权重的时候需要注意三点
      1.权重计算都是按选择器的个数来算,不管连多长,如.box:nth-of-type(1):nth-last-of-type(1) 权重就是30
      2. 注意:not伪类,:not伪类自身时没有权重的,它的权重是计算:not(select)这个select的权重,如.box:not(#list) 权重就是110 .box:not(.list)权重就是20
      3. 权重一样的时候按书写位置进行覆盖
  2. 注意nth-child与nth-of-type选择器之间的区别

    1. :nth-child(an+b) 这个 CSS 伪类匹配文档树中在其之前具有 an+b-1 个兄弟节点的元素,其中 n 为正值或零值。简单点说就是,这个选择器匹配那些在同系列兄弟节点中的位置与模式 an+b 匹配的元素;如果包含字母n则,n由0开始一直计算
    2. :nth-of-type(an+b) 这个 CSS 伪类 匹配那些在它之前有 an+b-1 个相同类型兄弟节点的元素,其中 n 为正值或零值;如果包含字母n则,n由0开始一直计算
    3. 二者的区别是:nth-child是所以的兄弟元素为数量,而:nth-of-type是同一类的所有兄弟元素为数量都是从1开始;当使用的兄弟元素都是同一类时,二者选择的元素都是一致的;
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 文档里面这么说的,vuex是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化;

我自己的理解就是vuex是一个通信工具,它可以让我们在vue的各个组件之间进行实时的数据通信,同时以又以一种特定的方式来设置状态,改变状态,从而实现可预测的变化;在传统的项目开发中,我们一般有以下几种方式来实现组件or模块or页面之间的通信,如EventBus, 事件广播emit and broadcast,locastorage, cookie、传参等,而这些方式多多少少都有一些缺点,而vuex则是一种更便捷及更高效的实现组件or模块之前通信的一种工具;

个人建议学习vuex的时候,可以从下面3点来进行学习
一、vuex内有哪些东西 -> vuex里面有6个关键属性,如下所示

  1. state状态值存储的地方,所有需要公用or需要经过vuex处理的数据都需要先在state内定义;注意的是为了保证vuex内定义的状态是响应式的,最好先在state内定义该状态,然后在通过mutation来进行改变;

  2. mutation改变state内状态值的方法集合,即state内的状态只有通过mutations内定义的方法才可以去改变,这也是保证状态以一种可预测的方式方式变化的原因;

  3. action也是改变state内状态值的方法集合,与mutation不同的是,action不是直接操作state而是通过操作mutation来改变state,另外一个区别是action内可以包含任意异步操作,而mutation内不能;

  4. getter用来获取state值的方法集合,主要有两个特点,第一个过滤获取到的state值,第二个就是与计算属性类似,会对值进行缓存,当依赖发生改变的时候才会重新获取;

  5. module即用来对state进行模块划分的,这样的好处就是便于管理与维护不同模块内定义的state,内部定义state,mutation,action与全局的一样;

  6. mutation-type就是方便维护mutation,能够在一个文件内保存所以的mutation

二、vuex内怎么去定义state、mutation、action、module、getter

  1. 定义state
    state.js
    const state = { userInfo: {}, showLoading: false, showPageTagsList: [] }; export default state;

  2. 定义mutation
    mutations.js 注意mutation传入的第一个参数是state,第二个参数是载荷也就是提交mutation时传入的参数
    export default { [types.SAVE_USERINFO] (state, userInfo) { state.userInfo = userInfo.data; Storage.setSessionStorage('userInfo', JSON.stringify(userInfo.data)); } };

  3. 定义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); }); }); } };

  4. 定义getter
    export default { userInfo: state => state.userInfo, productMdInfo: state => state.product.productMdInfo, showLoading: state => state.showLoading };

  5. 定义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';

  6. 最后传入到vuex.store方法内
    export default new Vuex.Store({ modules: { statistics, order, product }, state, mutations, getters, actions, strict: debug, plugins: debug ? [createLogger()] : [] });

三、vue组件内怎样去使用vuex

  1. 通过$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'});

  2. 通过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;
    }
  }
}

参考链接
https://vuex.vuejs.org/zh-cn/intro.html

记录一次php、apache、mysql、composer、wampserver安装过程

因为公司项目需求,然后在github上面找了一个headless cms directus,在安装directus的时候需要php、mysql、apache这三样东西,于是上网找了资料进行安装,遂把安装过程中出现的一些问题它记录下来;操作系统是windows。

apache安装

  1. 下载 下载地址http://httpd.apache.org/docs/current/platform/windows.html#down

image

点击去之后选择操作系统的位数,32还是64,然后点击下载;

  1. 按照这个教程进行安装 https://www.cnblogs.com/Ai-heng/p/7289241.html
启动命令
httpd –k start 

停止命令
httpd –k stop
  1. 设置环境变量的目的是,让我们可以在cmd or git bash内直接运行某个服务or软件,而不需要每次都到对应文件的根目录or bin目录下去执行命令;

php的安装

  1. 下载 下载地址http://php.net/downloads.php

image

点击windows download进入里面选择操作系统对应位数的版本,然后点击下载

  1. 按照这个教程进行安装 https://www.cnblogs.com/Ai-heng/p/7289241.html

mysql的安装

  1. 下载 下载地址https://dev.mysql.com/downloads/mysql/

image

操作系统对应位数的版本,然后点击下载

  1. 按照这个教程进行安装http://blog.csdn.net/luomingjun12315/article/details/50863781
启动mysql服务
net start mysql  

退出mysql命令
mysql > \q

暂停mysql服务
net stop mysql  

composer安装

  1. 下载 下载地址https://getcomposer.org/download/

image

  1. 按照这个教程进行安装http://blog.csdn.net/csdn_dengfan/article/details/54912039

wampserver安装

wampserver是一个集成了php、apache、mysql的工具,能够帮助我们快速搭建php开发环境;

  1. 下载 按照此教程进行下载https://www.cnblogs.com/Sabre/p/6728818.html

  2. 按照这个教程进行安装http://blog.csdn.net/wuguandi/article/details/53561253

  3. 安装完成之后,就可以直接在该目录的www目录下进行开了

  4. 修改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
  1. wampserver内的phpMyAdmin的初始登录名为root,密码为空

  2. wampserver内mysql默认开启严格模式,可以直接使用设置选项禁用or自己修改my.ini文件,取消严格模式参考链接:https://www.cnblogs.com/lujs/p/6288806.html,设置严格模式 参考链接:http://blog.csdn.net/fdipzone/article/details/50616247

  3. wampserver开启rewrite_module重写功能启用.htaccess文件,参考链接:http://blog.csdn.net/sgly2005/article/details/50718538

后端这条路上还是任重而道远啊!

基于axios的xhr封装

常规封装

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

对参数及url处理及返回值处理的封装

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

CSS3属性border-radius的正确使用姿势

border-radius也是经常使用的一个CSS3属性,最近也经常在项目中使用,所以总结记录一番;

border-radius CSS3属性,兼容ie9+及现代浏览器,共有2个属性值,如下所示

border-radius: <length-percentage>{1,4} [ / <length-percentage>{1,4} 半径的第一个语法取值可取1~4个值 半径的第二个语法取值也可取1~4个值

常用的几种写法

  1. 一个属性值 border-radius: 20px; => border-radius: 20px 20px 20px 20px / 20px 20px 20px 20px;
    image

  2. 两个属性值 border-radius: 20px 10px; => border-radius: 20px 10px 20px 10px / 20px 10px 20px 10px;
    image

  3. 三个属性值 border-radius: 20px 10px 5px; => border-radius: 20px 10px 5px 10px / 20px 10px 5px 10px;
    image

  4. 四个属性值 border-radius: 20px 10px 5px 15px; => border-radius: 20px 10px 5px 15px / 20px 10px 5px 15px;
    image

  5. 设置两组属性值 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(左下左边半径);第一组值要么是上边要么是下边,第二组值要么是左边要么是右边;
    image

  6. 不使用简写属性
    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;
    image

总结:理解一点一个元素总共可以设置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基本都支持

image

从CanIUse上看移动端兼容安卓4.0+,ios6.1+,其它大部分都是最新的版本才支持

image

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基本都支持

image

从CanIUse上看移动端兼容安卓3.0+,ios6.0+,其它大部分都是最新的版本才支持

image

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种引用类型,二者的主要区别如下

  1. 包含成员基本类型,包括number,string,boolean,undefined,null,引用类型包括obj
  2. 存放位置,基本类型存放于栈区域,引用类型指针存放于栈区域,内容存放于堆区域
  3. 值得可变性,基本类型的值不可变(注意与重新赋值的区别),引用类型的值是可变的;
  4. 比较,基本类型的比较是值得比较(即值相等就可以判断这两个变量相等,建议使用严格等,避免变量进行隐试转换),引用类型的比较是引用的比较(即指针的比较),引用相等即引用的时同一个引用类型的数据
  5. 拷贝,基本数据类型的拷贝即是重新复制给另一个变量,两个变量互不影响,引用类型的拷贝分为三类,第一类引用类型赋值,即指针的赋值,二者指向同一个引用类型,当其中一个改变时,另一个也会跟着改变,第二类浅拷贝,只拷贝一层,没有对对象中的子对象也进行拷贝,两个对象中的基本类型的值改变,不会互相影响,但是引用类型改变时会互相影响,第三类深拷贝,对对象及对象中包含的子对象进行递归拷贝,拷贝完之后的两个对象不管是基本类型的值还是引用类型的值改变都互不影响;

传值与传址的区别,两者都是针对变量在赋值的时候而言的,传值指的时值得传递(即基本类型的赋值),传址指的是引用的赋值(及引用类型的赋值)

浅拷贝

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

angularJs使用小结

  1. angularJs内使用laydate日期插件
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);

                }
            }
        }]);
  1. angularJs内使用指令定义分页器
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);
                        }

                    }


                }
            }
        }]);
  1. angularJs指令内定义全选与单选
//全选
        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'));
                            }
                        });
                    });
                }
            }
        });
  1. angularJs指令内定义右键菜单
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(); //第三种方式
                        });


                    });
                }
            }
        });
  1. angularJs指令内实现按住不放进行持续进行某个操作
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);
                    }

                }
            }
        })
  1. angularJs内创建iframe打印
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();
                            });
                        }
                    });

                }
            }

        }]);
  1. angularJs内封装http服务
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);
                    })
                }

            }
        }]);
  1. angularJs服务中定义loading
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;
                }
            };

        }]);
  1. angularJs内定义EventBus
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过滤器中进行包装之后才能使用;

CSS3属性box-shadow的正确使用姿势

box-shadow虽然一直在使用,但是没有总结过,这几天在项目中频繁的用到,于是总结记录一番。

box-shadow CSS3 的属性,目前兼容ie9+及现代浏览器,共有6个属性值,如下所示

box-shadow: outside|inside offset-x offset-y blur-radius spread-radius color

  1. 第一个参数 outside|inside 阴影位置显示参数,默认为outside(可以省略);
  2. 第二个参数offset-x设置水平方向的阴影,正值表示水平右方向阴影,负值表示水平左方向阴影,0表示水平左右阴影,值越大阴影偏移元素的位置越远;
  3. 第三个参数offset-y设置垂直方向的阴影,正值表示垂直下方向阴影,负值表示垂直上方向阴影,0表示垂直上下阴影,值越大阴影偏移元素的位置越远;
  4. 第四个参数blur-radius设置阴影的模糊面积,只能设正值,值越大阴影越模糊;
  5. 第五个参数spread-radius设置阴影扩大收缩参数,正值表示扩大,负值表示缩小,值越大阴影的面积就越大(即阴影的面积会超过元素本身的面积);
  6. 第六个参数color设置阴影的颜色;

几种常用的场合

  1. 设置4面阴影 box-shadow: 0 0 10px 3px #ccc;
    image

  2. 设置单边阴影如下边阴影,关键在spread-radius扩大收缩参数,这个时候需要设置成负值,不然水平方向会有阴影,其它同理box-shadow: 0 10px 10px -5px #ccc;
    image

  3. 每边设置不同的阴影,需要注意的时候,因该是渲染了四次,只不过每组组侧重的阴影不一样,才能设置不同颜色的阴影,这里的blur-radius不能大,大的话阴影之间会互相渗透
    box-shadow: 3px 0 1px red, -3px 0 10px #ccc, 0 -3px 1px blue, 0 3px 1px #000;
    image

参考链接
https://developer.mozilla.org/zh-CN/docs/Web/CSS/box-shadow

git在实际开发中的应用场景

git总结

开始一个新项目,我们需要使用git来管理我们的代码,于是总结了下,常用的一些场景;

怎样开始一个新项目

  1. git clone 项目链接 克隆下来之后实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin

  2. 所以此时使用git remote查看远程分支显示结果为origin or 使用git remote -v显示更详细的信息,结果会显示抓取(fetch)与推送(push)的origin地址,如果没有推送权限是看不到push地址的

  3. 此时就看怎么开发了,如果已经远程建了分支,那么只需要git branch查看本地分支,git branch -a 查看远程分支;然后使用git checkout 远程分支名(等价于git checkout -b branch-name origin/branch-name)在本地创建和远程分支对应的分支
    建立本地分支与远程某分支的关联git branch --set-upstream branch-name origin/branch-name

  4. 开发完成之后git add / git commit -m 之后,先更新下远程分支git pull origin 远程分支名(注意,这里简写了,原因是本地分支与远程分支名称一致);git push origin 远程分支名
    git push origin master 将本地的master分支推送到远程仓库(主分支)

  5. 一个常规的开发,上传流程就结束了

怎么去关联某个本地仓库与远程仓库,便于使用简写命令

使用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分支是一样的

创建本地分支or远程分支

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分支代码一样

删除本地or远程分支

删除远程dev分支 git push origin :dev
删除远程dev分支 git push origin --delete dev
删除本都dev分支 git branch -d dev

pull命令与push命令

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主机的对应分支

创建分支开发方式

  1. 只创建本地分支,然后合并master开发
git checkout -b dev

修改文件

git add .

git commit -m 'xx'

git checkout master

git merge dev

git pull 

git push 整个流程就结束了,然后继续切换到dev分支上继续开发,继续上述的步骤
  1. 创建本地与对应的远程分支,然后合并master开发
如果远程仓库没有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上分支的冲突

  1. 进入到有冲突的那个项目内

  2. git pull 拉取最新代码,注意是test分支

  3. git checkout 到修改提交代码的分支,然后git pull

  4. git checkout test分支,然后git merge 刚刚提交代码有冲突的分支,然后会提示有代码冲突

  5. 打开有冲突的文件,然后手动解决冲突

  6. 解决完成之后,点击resolve,然后点击ok

  7. git push 就在test分支上,然后git status查看下,整个过程冲突就算解决了

解决release分支上的冲突

  1. 在本地电脑上进入到有冲突的那个项目内

  2. 进入到对应的开发分支,然后把本地最新的master分支合到开发分支

  3. 然后提交代码,让测试重新build

上面这些操作步骤是可以简化的,我们不需要去checkout到开发的分支并且把代码拉下来;直接进入到test or release分支之后,直接git merge origin/branch_name 直接去merge远程分支即可,然后解决冲突在重新上传test or release分支

本地包的修改如@angular_xx

  1. npm login --registry=https://xxx.cn/repository/xxx/ --scope=@xxx登录,输入用户名与密码还有邮箱,云客的用户名是xxx 密码是xxx

  2. 在本地包目录如angular_xx目录内执行yarn link构建软链接,然后在使用仓库的目录内执行yarn link 目标仓库名称 如yarn link @xxxke/angular-xx,这个时候在使用仓库的目录内直接修改
    就会直接在本地包目录来也同步完成修改

  3. 调式完成之后,在本地包内,记录下change.log及在package.json内改下version +1,然后git add / git commit / git pull / git push / yarn publish发布 注意本地包开发都是在master分支

  4. 然后在使用仓库的目录更改package.json文件内,刚刚发布包的最新版本号,然后在yarn-lock文件内也找到对应的包,并更改版本号,然后执行yarn or yarn install 则可以重新yarn start了

  5. 注意直接在node_modules内改本地包,改完需要yarn start重启一次,才会生效

怎么查看gitlab服务器的地址

查看gitlab服务器的服务器地址,就是打开一个项目,然后查看它的ssh链接,前面那部分就是地址,然后可以通过ssh -T 服务器地址来判断当前电脑是否关联了gitlab
[email protected]:xxx/p_xxx_back.git 使用ssh -T [email protected]
ssh -T [email protected] 查看是否关联github成功

查看某个文件的修改历史

第一种方式

  1. 切换到目录 即cd 到需要查看修改记录的文件夹
  2. git log --pretty=oneline 文件名
  3. git show commit_id 则可查看具体的某一次提交修改内容

第二种方式
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配置项

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'

撤销本地merge操作

git merge --abort

使用cherry-pick将a分支上的某个commit合并到b分支上来

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

撤销本次提交,即撤销本次git commit的内容

直接丢弃本次的commit内容
git reset --hard HEAD^

直接将本次的commit的内容还原到暂存区
git reset --soft HEAD^

git tag

添加附注标签
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

node请求豆瓣接口

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

vue项目中怎么引入mockjs

引入mockjs的目的是,提高我们的开发效率,不需要等待后端接口给出之后,才能够进行开发调试;

mockjs两个最大的特点:
1. 够拦截ajax请求,保证我们能够快速开发,只需要在后端给出接口的时候,把接口替换就ok了;
2. 足够多的方法产生随机的不同类型的数据;

1. 安装

npm install --save mockjs

2. 定义请求接口

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

3. 利用mockjs定义返回的接口数据

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

4. 利用mock定义响应接口

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;

参考链接:https://github.com/nuysoft/Mock/wiki

项目中怎么添加eslint代码检查及eslint基本配置

前端开发到目前为止,已经是多团队,跨项目开发,那么在各个团队及各个项目来回切换的时候,怎么去提高我们的开发效率,显然规范化能够帮助我们解决这个问题,那么到目前为止eslint是一个很棒的js代码规范化选择,我们可以根据官方的规则来进行定制,也可以根据一些成型的规则来引入如standard等;每个团队可以根据自己公司的要求来进行选择,下面是如何在项目中引入eslint的步骤。

eslint安装有两种方式

  1. 使用全局安装 npm install -g eslint
  2. 项目文件内安装 npm install --save-dev eslint

eslint使用方式,不配置package.json

  1. 初始化eslintrc.js文件,全局直接在项目目录内使用eslint --init
  2. 项目内安装git 内使用./node_modules/.bin/eslint --init;cmd内使用 .\node_modules.bin\eslint --init
  3. 配置eslintrc.js文件,这里我们使用standard配置文件,然后在上面做些许修改
  4. eslint进行代码检查,git上使用./node_modules/.bin/eslint 需要被检查的.js文件

eslint使用方式,进行package.json设置

  1. 在scripts对象内配置lint命令"lint": "eslint --ext .js,.vue src"
  2. 运行npm run lint即可对src目录下所有.js与.vue后缀的文件进行检查

eslint修复错误代码

  1. 运行代码./node_modules/.bin/eslint --fix src or ./node_modules/.bin/eslint --fix src/utils/index.js
  2. 在scripts内配置命令"fix": "eslint --fix src" or "fix": "eslint --ext .js,.vue --fix src" or "fix": "eslint --fix src/utils/index.js"

参考链接
https://github.com/eslint/eslint
https://github.com/standard/standard
http://eslint.cn/docs/rules/

vue项目中怎么引入tinymce富文本编辑器

因为项目内需要用到富文本编辑器,于是在找了很多富文本编辑器之后,最终找到tinymce更符合我们项目的富文本编辑器;

  1. 安装引入
npm install --save tinymce
  1. 创建基于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>
  1. 其它组件内使用tinymce组件
<tinymce v-model="myContent1" tinymce-id="tinymce1" ></tinymce>
import Tinymce from '@/components/tinymce';

最终效果图
image

参考链接:https://github.com/tinymce/tinymce

flex布局及应用

简介

flex布局简称弹性盒模型布局,是2009年w3c提出的一种可以简洁、快速弹性布局的属性。主要**是给予容器控制内部元素高度和宽度的能力,是目前移动端布局常用的一种方式

flex布局主要包括flex容器与flex项目,而flex项目又可以是一个新的flex容器,依次类推;只要给一个元素设置了display: flex;那么浏览器在渲染的时候就会该元素为一个flex容器,其内的子元素为flex项目;

flex中的有两条轴分别代表水平和垂直方向,常常称为主轴与交叉轴,默认情况下主轴为水平方向(从左至右),交叉轴为垂直方向,因为flex-direction的默认值为row

flexbox

flex容器的属性

  1. flex-direction: row(行,左至右)(默认值) || column(列,上至下) || row-reverse(行,右至左) || column-reverse(列, 下至上); 控制Flex项目沿着主轴(Main Axis)的排列方向

  2. flex-wrap: wrap(当主轴方向无法显示足够的flex项目时,则会换行显示,从主轴的默认排列方向)(默认值) || nowrap(不换行显示,所有的flex项目显示在这一行,当flex容器的宽度超过视窗宽度则出现滚动条) || wrap-reverse(当主轴方向无法显示足够的flex项目时,则会换行显示,从主轴的默认排列方向的反方向开始排列);

  3. flex-flow: flex-direction flex-wrap 是这两个属性的简写属性

  4. justify-content(类似text-align属性)(只对主轴有效): flex-start(让flex项目从主轴默认开始的方向对齐)(默认值) || flex-end(让flex项目从主轴默认结束的方向对齐) || center(延主轴居中对齐) || space-between(让除了第一个和最一个Flex项目的两者间间距相同(两端对齐)) || space-around(让每个Flex项目具有相同的空间,即让每个flex项目的左右有一个相同的margin值)

  5. align-items(类似text-align属性)(只对交叉轴有效): flex-start(让flex项目从交叉轴默认开始的方向对齐) || flex-end(让flex项目从交叉轴默认结束的方向对齐)(默认值) || center(延交叉轴居中对齐) || stretch(让所有的flex项目与flex容器等高 or 等宽根据主轴的方向来,如果是row方向则等高,column方向则等宽)(默认值) || baseline(延基线对齐)

  6. align-content(与align-items类似只是少了baseline属性值,也是设置flex项目的对齐方式): flex-start(让多行flex项目从交叉轴默认开始的方向对齐) || flex-end(让多行flex项目从交叉轴默认结束的方向对齐)(默认值) || center(延交叉轴居中对齐) || stretch(延交叉轴的方向拉伸flex的项目,让flex项目占满flex容器一个合适的高度or宽度)(默认值)

flex-item项目属性

  1. order(定义flex项目在主轴方向的排列顺序):number(所有的flex项目order默认值为0,值越大排列越靠后,值越小排列越靠前,允许正负值);

  2. flex-grow(控制Flex项目在容器有多余的空间如何放大(扩展)): 0(默认值为0) or 正值,0表示Flex项目不会增长,填充Flex容器可用空间,正值表示flex项目会随着flex容器变大

  3. flex-shrink(控制Flex项目在没有额外空间如何缩小): 0 or正值(默认值为1),0表示Flex项目不会变小,填充Flex容器可用空间,正值表示flex项目会随着flex容器缩小

  4. flex-basis(指定Flex项目的初始大小):% || em || rem || px (默认值为auto);注意的是flex-basis: 0px不能写成flex-basis:0;

  5. flex(简写属性): flex-grow flex-shrink flex-basis; flex: 0 1 auto;(默认属性值)

  6. 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项目

应用

  1. flex固定头部or底部
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>

移动端兼容性

image

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Flexible_Box_Layout/Using_CSS_flexible_boxes

background-size与背景图片展示

background-size 设置背景图片大小。图片可以保有其原有的尺寸,或者拉伸到新的尺寸,或者在保持其原有比例的同时缩放到元素的可用空间的尺寸,只考虑单张图片的背景图设置

单张图片的背景大小可以使用以下三种方法中的一种来规定:

使用关键词 contain
使用关键词 cover
设定宽度和高度值
当通过宽度和高度值来设定尺寸时,你可以提供一或者两个数值:

如果仅有一个数值被给定,这个数值将作为宽度值大小,高度值将被设定为auto。
如果有两个数值被给定,第一个将作为宽度值大小,第二个作为高度值大小。
每个值可以是<length>, 是 <percentage>, 或者 auto.

一、图片原尺寸640/480,div尺寸100%/7.5rem(已640px为基准,7.5rem <=> 240px)

iphone5下
backgrund-size: auto/contain/cover/100% auto/100% 100%;是一致的效果,图片高保真并铺满空间

image

iphone6下
background-size: auto; 图片按原尺寸显示并四边留白

image

background-size: contain/cover/100% auto/100% 100%;是一致的效果图片高保真并铺满空间

image

安卓机下
background-size: auto; 图片裁剪

image

background-size: contain/cover/100% auto/100% 100%;是一致的效果图片高保真并铺满空间

image

结论1:背景图片的展示依赖原图尺寸与div容器尺寸,当div容器尺寸大于or等于图片实际尺寸的时候,background-size只为auto的时候,会展示不同的效果,当图片原尺寸小于容器尺寸的时候,四边会留白,当图片尺寸大于原容器尺寸的时候,图片会裁剪;其它几个值都是保持高保证并铺满整个容器;

二、图片原尺寸640/480 div尺寸100%/5rem(已640px为基准,5rem <=> 160px)

iphone5下
backgrund-size: auto/cover/100% auto;宽度沾满空间,高度被裁剪

image

backgrund-size: contain/100% 100%;图片缩放全部展示在容器内,两边留白

image

backgrund-size: 100% 100%;图片变形,但是铺满空间

image

iphone6下
background-size: auto; 宽度按原尺寸展示,高度被裁剪

image

background-size: contain;图片缩放全部展示在容器内,两边留白

image

background-size: cover/100% auto;图片宽度占满空间,高度被裁剪

image

background-size: 100% 100%;图片变形,但是铺满空间

image

安卓机下
background-size: auto; 图片裁剪

image

background-size: contain;图片缩放,宽度留白

image

background-size: cover/100% auto;宽度铺满,高度被裁剪

image

background-size: 100% 100%;图片变形,但是铺满空间

image

结论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小于容器的尺寸都会失真;

三、图片原尺寸640/480 div尺寸100%/9rem(已640px为基准,9rem <=> 288px)

iphone5下
backgrund-size: auto/contain/100% auto;宽度沾满空间,高度两边留白

image

backgrund-size: cover; 图片放大高度全部展示在容器内,宽度被裁剪,高保真

image

backgrund-size: 100% 100%;图片变形,但是铺满空间

image

iphone6下
background-size: auto; 宽度按原尺寸展示,四边留白

image

background-size: contain/100% auto;图片宽度占满空间,高度两边留白

image

background-size: cover;图片高度占满空间,宽度被裁剪,高保真

image

background-size: 100% 100%;图片变形,但是铺满空间

image

安卓机下
background-size: auto; 图片裁剪

image

background-size: contain/100% auto;图片宽度占满,高度两边留白

image

background-size: cover;图片高度铺满,宽度被裁剪,高保证

image

background-size: 100% 100%;图片变形,但是铺满空间

image

结论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小于容器的尺寸都会失真;

四、总结

  1. 背景图片的展示跟原图片尺寸与容器尺寸相关;

  2. background-size: auto; 尽量用图片原尺寸占满空间,就是把原图片的尺寸看成一个矩形,容器的宽高看成一个矩形,居中展示的是两个矩形的交集,所以当图片的原尺寸大于容器的部分会被裁剪掉,小于的部分全部展示并留白,等于的部分刚好铺满;不推荐使用该值;(一句话,取原图片尺寸与容器尺寸的交集并剧中展示);

  3. background-size: contain; 通过缩小or放大,让整张图片都在容器内,所以当图片的原尺寸中的某一项大于容器某一项的尺寸时,为了保证整张图片都能过展示在容器内,会对大于的一边进行缩放,同时为了保证图片不失真,会对另一边同时进行缩放,所以等于or小于的那一边就会出现留白,当图片的原尺寸中的某一项小于容器某一项的尺寸时,宽度会占满容器,高度显示原图片尺寸高度,高度上下超出部分留白;注意放大后的图片高度是不会超过原尺寸高度;(一句话,整张图片始终会全部显示在容器内,超过部分会被裁剪,小于部分会被留白);

  4. background-size: cover; 通过缩小or放大,让整张图片已最好的比例展示在容器内,当原图片尺寸大于容器尺寸时,图片会被缩放,缩放后的高度大于容器高度则被裁剪,小于容器高度则会铺满;当原尺寸小于容器尺寸时,图片会被放大,放大后超出的部分会被裁剪;(一句话,会按图片原尺寸宽or高来放大与缩小得出当大or缩小之后的图片,然后与容器尺寸取交集,超出部分裁剪,注意这里始终会让一边铺满,另一边被裁剪);

  5. background-size: 100% auto; 宽度铺满整个容器,高度根据宽度放大or缩小的比例进行对应的放大or搜小,当放大后的高度大于容器高度,则会被裁剪,小于容器高度则会按原图高度展示,高度两边留白;(一句话概括,宽度始终占满容器,高度随宽度比例计算,最大高度不会超过原尺寸高度);

  6. background-size: 100% 100%; 只有当容器的尺寸与图片原尺寸一致时才不会失真,其它情况都会失真;

  7. 其实就是三个尺寸之间的关系,图片原尺寸,图片被放大or缩小之后的尺寸,容器尺寸;

  8. 当我们对图片的展示要求较高时,最好的图片展示方式就是固定图片原尺寸,然后background-size: 100%; 容器宽高100%/图片原尺寸高度;注意这里的图片原尺寸至少是二倍图;

参考连接:
https://developer.mozilla.org/zh-CN/docs/Web/CSS/background-size

移动端拖拽与PC端拖拽

js实现拖拽的原理,大致可分为三个步骤:

  1. 绑定mousedown事件,获取鼠标点在被拖拽元素上的初始坐标;同时给document绑定mousemove事件与mouseup事件
  2. 在mousemove内去动态赋值被拖拽元素的left or top值;
  3. 在mouseup的事件函数内解绑document上的mousemove事件,并初始化一些值
  4. 需用注意的是mousedown的时候要阻止冒泡及通过onselectstart来禁止选中

PC端与移动端的区别是:

  1. 绑定的事件不一样,pc端被拖拽元素上绑定的是mousedown而移动端是touchstart,pc端document上绑定的时mousemove、mouseup而移动端上绑定的时touchmove、touchend;
  2. 事件对象event中取值不一样,pc端是通过e.clientX/Y、e.pageX/Y而移动端是通过e.targetTouches[0].pageX/Y来获取坐标值

移动端拖拽实例

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

E6小结及常用场景

变量声明

  1. var 声明一个变量,没有块及作用域,存在变量提升,

  2. let 声明一个变量,有块及作用域,存在暂时性死区(即在变量未声明之前使用都会报错),不允许在同一个块及作用域内重复声明

  3. const 声明一个常量(注意声明引用类型的常量时,是可以改变引用类型的值的),有块及作用域, 存在暂时性死区(即在变量未声明之前使用都会报错), 不允许在同一个块及作用域内重复声明

结构赋值

  1. 数组的结构赋值 就是对数组下标进行一个映射,通过映射关系来进行取值,如let [a, b, c] = [4, 5, 5],需要注意的是左右两边的变量与值的个数可以不等,当变量结构不成功时返回的值为undefined

  2. 对象的结构赋值 就是对对象的key进行一个映射,let {foo, bar} = {foo: 'jack', bar: 'rose'} ==> let {foo: foo, bar: bar} = {foo: 'jack', bar: 'rose'},当解构不成时,返回undefined

  3. 使用场景:交换变量的值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)

字符串扩展

  1. 新增了includes方法,该方法用于查找某个字符串内是否包含需要查找的某个字符or字符串,包含返回true,不包含返回false; startsWidth,表示需要查询的字符串or字符是否在头部,是的话返回true,否的话返回false;endWidth是否在尾部,是的话返回true,否的话返回false;另外这个三个方法都允许传入第二个参数,表示从哪个字符的位置开始算起,endWidth相反表示从哪个字符的前面算起,注意这三个方法的index默认为0

  2. string.repeat(count) 字符串重复次数,该方法会对count进行取整,转换为数字;注意该值不能为infinity,传入的话会直接报错,另外也不能传空,传空传0返回空字符串

  3. 模板字符串hello ${world} 注意的是这个${}大括号内就是一个js执行环境,所以里面可以是变量,也可以进行运算,调用函数等

正则的扩展

  1. 允许RegExp构造函数,第一个参数为正则表达式时,第二个参数允许是修饰符,es5之前是不允许的,var regex = new RegExp(/xyz/, 'i');且如果第一个正则后面有修饰符的话,第二个参数的修饰符会覆盖第一个参数正则内的所有修饰符

数值的拓展

  1. 新增Number.isFinite()与Number.isNaN 与传统的isFinite与isNaN相比的区别就是该方法只对Number类型的值有效,对其它类型的值无效,即isFinite与isNaN会对参数进行一个隐式转换如字符创,布尔值会转换成number类型

  2. 新增Number.parseInt与Number.parseFloat方法,这两个方法与全局的parseInt与pareseFloat是同一个方法,这样做的目的是减少全局方法

  3. 新增Number.isInteger判断一个number类型的值是否为整数,注意不会进行隐式转换只对number类型有效;需要注意的是数值的精度超过限制时isInteger会判断不准确,所以我们在精度要求较高的场景下就不能使用该方法

函数的拓展

  1. 允许传入默认参数 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]; }

  2. rest参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中(rest是一个真正的数组) function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; }注意的是注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

  3. es6 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。原因是函数内部的严格模式,同时适用于函数体和函数参数。但是,函数执行的时候,先执行函数参数,然后再执行函数体。这样就有一个不合理的地方,只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数却应该先于函数体执行。

  4. 箭头函数,注意箭头函数的写法就好了,注意的是箭头函数的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. 扩展运算符...写法是(...[1, 2, 2]),它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,常用的场合1.替换apply方法;2.求数组内的最大值与最小值;3.将一个数组添加到另一个数组的前面or后面

  2. Array.from()方法用于将两类对象转为真正的数组:类似数组的对象(array-like object,这里包括自定义的类数组对象,不限于nodeList与arguments)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map),另外第二个参数可以传入一个函数,用于处理数组中的每个item

  3. Array.of()用来创建数组,将一组值转换为数组,弥补Array()或new Array();区别是Array.of(3)生成的时一个包含3的数组[3],new Array(3)生成一个包含三个空值的数组[ , , ,]

  4. find()用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。

  5. entries()返回一个迭代器对象(注意返回的不是一个数组),用于数组key与value的遍历;keys()是对键名的遍历;values()是对键值的遍历;要注意的是不能使用for in 及for循环来进行变量

  6. includes(val, index)与字符串的includes方法类似;ndexOf方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判;includes使用的是不一样的判断算法,就没有这个问题。

对象的拓展

  1. 属性的简洁写法,允许变量与函数作为对象的属性与方法 即{foo} => {foo: foo}

  2. Object.is(val1, val2)比较两个值是否相等,内部使用的时===,需要注意的是,一是+0不等于-0,二是NaN等于自身。正常===情况下NaN是不等于NaN

  3. Object.assign()合并对象类似extend方法;Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)需要注意的是Object.assign方法实行的是浅拷贝,而不是深拷贝;对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加;Object.assign可以用来处理数组,但是会把数组视为对象(会用下标去进行覆盖)不建议用于数组上面。常用的场合1.合并多个对象;2.为属性指定默认值

  4. Object.keys(obj)返回一个包含改对象key值的数组,Object.values(obj)返回包含该对象values的数组(需要注意的时如果属性key是number类型的话,value是按照key的大小排序之后的输出顺序),Object.entries()返回包含该键值对的二维数组(常见的用途是将对象转换成map结构)

Symbol

  1. Symbol是一种新的数据类型,是一种类字符串类型,引入Symbol的目的是保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突,因为每个Symbol它都是独一无二的,通过Symbol函数来获取,不能使用new操作符;需要注意的是Symbol('a') !== Symbol('a');作为对象属性时只能用中括号语法来设置属于与获取属性;Symbol 作为属性名,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值;使用Symbol.for()来获取相同的symbol值,Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会,Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值;Symbol.keyFor方法返回一个已登记的 Symbol 类型值的key

Set与Map结构

  1. set是一种新的数据结构(类数组),属于引用数据类型,typeof的值为object,特点是该结构的内部成员是无重复项的,即每个成员都是唯一的,该数据结构通过new Set()来进行获取,传入的参数可以是空or数组,传入其它类型的参数会报错,一般用于数组去重[...new Set([1, 2, 6, 3, 7, 7, 5])],不能通过下标索引来进行取值

  2. set的实例方法add(value)添加元素(可以添加简单值也可以添加对象),delete(value)删除元素(只能删除简单值,不能删除对象),has(value)判断是否有改元素,clear()清空所以元素

  3. set的遍历操作,keys(), values(), entries(),forEach()与数组的forEach一样(需要注意的是因为set的键值就是键名,所以第一个参数value与第二个参数key永远是相等的),另外Set的遍历顺序就是插入顺序,Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法

  4. map是一种新的数据结构(类对象),属于引用类型,typeif志伟object, 特点是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键,需要注意的时new Map时可以不传入参数,也可以传入参数,传入参数时需要注意格式

  5. map的实例方法与set类似,分别是set(key,value), get(key), delete(key), has(key), clear()

  6. map的遍历方法,keys(), values(), entries(),forEach()与数组的forEach一样,Map 的遍历顺序就是插入顺序

  7. map的常用场景,map转数组[...map]; 数组转map=>new Map([['name', 'jack'], ['title', 'name']]),map转对象及对象转map

Promise

  1. promise就是异步编程的一种解决方案,有两个特点1.对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态;2.一旦状态改变,就不会再变,任何时候都可以得到这个结果;Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段

  2. 基本用法 new promise((resolve, reject) => {})

  3. Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理,需要注意的时只有所有的promise成员都成功了才会resolve,而只要有一个成员失败了,就好reject

  4. Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例,const p = Promise.race([p1, p2, p3]);与promise.all不同的是,只要成员中有一个率先改变状态,不论成功or失败,p就会执行resolve or reject

  5. Promise.resolve(),现有对象转为 Promise 对象,需要注意的是根据参入的参数会有不一样的结果

  6. Promise.reject()与Promise.resolve方法类似

Iterator 和 for...of 循环

  1. Iterator(遍历器),遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员),Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费

  2. ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable),原生具备 Iterator 接口的数据结构如下Array、Map、Set、String、arguments、NodeList

  3. 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来进行遍历

  4. 对象通过Object.keys(someObject)来使用for of遍历for (var key of Object.keys(someObject))

  5. for in与for of的区别,for in 有如下缺点for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键,某些情况下,for...in循环会以任意顺序遍历键名,而for of 不同于forEach方法,它可以与break、continue和return配合使用。提供了遍历所有数据结构的统一操作接口,总之,for...in循环主要是为遍历对象而设计的,不适用于遍历数组

Class构造函数

  1. 定义类的方式 construct内的是实例上的属性or方法,一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加,类不存在变量提升,如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”,父类的静态方法,可以被子类继承。。ES6 明确规定,Class 内部只有静态方法,没有静态属性

  2. 类通过extends关键字类实现继承, 子类的constructor内必须调用super方法

Module

  1. export提供当前模块对外暴露的接口,export的写法

  2. import提供当前模块可以从其它模块引入的接口, import的写法

  3. 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抓包,先查看这里十分钟学会Charles抓包(iOS的http/https请求)

方法一、接口断点调试

比如我要在接口请求or返回时修改请求参数or修改返回参数

image

方法二、接口转发调试

比如我要将本地的某个接口请求转发到测试环境or生产环境

image

方法三、文件本地代理调试

调试线上生产环境代码,将代码下载到本地,然后通过map local功能,进行本地修改调试

image

方法四、线上环境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界面

image

2、选择类型URL
image

3、添加需要转发的规则
image

参考链接:

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

使用eslint+husky+lint-staged+prettier构建统一的代码风格及代码检查工作流

当我们团队比较大,且人数比较多的时候,因为同事之间不同的代码风格及编辑器,会导致项目内的代码,无法形成统一的风格,不利于阅读,也更容易造成bug;为保持统一的代码风格及减少书写上的bug,所以抽时间,去整理通过什么样的工具和方式去实现团队统一的代码风格及代码检查工作流;

一、引入husky,在我们commit之前做eslint的检查,注意这是对lint命令内所有的文件做eslint检查

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

二、husky只是提供了提交时的钩子,然而有时候我们处理的项目并不是新项目,这个时候,可能只想对本次提交的代码,做代码检查,而不是对现有目录内所有的文件做检查,所以我们需要引入lint-staged这个插件,lint-staged插件提供了对本次提交的各种文件检查的检查方式,如js,css,saas等;

使用方式

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": []
}

三、引入prettier插件格式化代码,当我们做完了前面两步之后,还不能保证团队内的代码是一样的,只是保证了团队内的代码检查是符合同一份eslint规则的,如果要让代码不论在哪个同时的编辑器内打开是一样的,则需要继续引入prettier插件

这里说下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

node shell pre-commit eslint prettier

很多事情有因才有果,因为工作项目的关系,我们的目录结构大致如下

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脚本的过程中碰到几个问题;

1 node内怎么去执行命令,如npm run start、git diff、eslint .等

通过查找资料我们可以知道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'])

2 怎样去获取提交到暂存区的文件

通过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转换成字符串,然后在转换成包含各个文件名的数组

3 怎么去处理Eslint的返回信息

如果使用的是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})

image

4 因为使用prettier来格式化代码,而格式化的时候无法自动识别文件并使用不同的引擎来格式化代码,所以分开获取了js文件与vue文件

5 node子进程内通过precess.exit()来终端进程,而shell脚本内,通过返回值0跟1来区分脚本是成功还是失败;0是成功,1是失败;

6 通过node的读写文件功能,让其它同时也能够方便使用

最终如下所示

#!/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、HTTP、WEB综合汇总

HTML5

  1. html5有哪些新特性、移除了那些元素?

    1. HTML5 现在已经不是 SGML 的子集,主要是关于图像,位置,存储,多任务等功能的增加
      1. 绘画 canvas
      2. 用于媒介回放的 video 和 audio 元素
      3. 本地离线存储 localStorage 长期存储数据,浏览器关闭后数据不丢失
      4. sessionStorage 的数据在浏览器关闭后自动删除
      5. 语意化更好的内容元素,比如article、footer、header、nav、section
      6. 表单控件,calendar、date、time、email、url、search
      7. 新的技术webworker, websocket, Geolocation
    2. 移除的元素:
      1. 纯表现的元素:basefont,big,center,font, s,strike,tt,u`
      2. 对可用性产生负面影响的元素:frame,frameset,noframes
    3. 支持HTML5新标签:
      1. IE8/IE7/IE6支持通过document.createElement方法产生的标签
      2. 可以利用这一特性让这些浏览器支持HTML5新标签
      3. 浏览器支持新标签后,还需要添加标签默认的样式
      4. 当然也可以直接使用成熟的框架、比如html5shim
  2. Canvas和SVG有什么区别?

    1. svg绘制出来的每一个图形的元素都是独立的DOM节点,能够方便的绑定事件或用来修改。canvas输出的是一整幅画布
    2. svg输出的图形是矢量图形,后期可以修改参数来自由放大缩小,不会是真和锯齿。而canvas输出标量画布,就像一张图片一样,放大会失真或者锯齿

web综合

  1. 什么是web语义化,有什么好处

    1. web语义化是指通过HTML标记表示页面包含的信息,包含了HTML标签的语义化和css命名的语义化。
      1. HTML标签的语义化是指:通过使用包含语义的标签(如h1-h6)恰当地表示文档结构,如header标签标示页面的头部,section标签标签页面上的一个快or组件,article标签用于跟文章有关的独立块标签,aside侧边栏标签,progress标签来表示进度条
      2. css命名的语义化是指:为html标签添加有意义的class,id补充未表达的语义,如Microformat通过添加符合规则的class描述信息
    2. 为什么需要语义化:
      1. 去掉样式后页面呈现清晰的结构
      2. 盲人使用读屏器更好地阅读
      3. 搜索引擎更好地理解页面,有利于收录
      4. 便团队项目的可持续运作及维护
  2. 从浏览器地址栏输入url到显示页面的步骤(以HTTP为例)

    1. 在浏览器地址栏输入URL
    2. 浏览器查看缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤
      1. 如果资源未缓存,发起新请求
      2. 如果已缓存,检验是否足够新鲜,足够新鲜直接提供给客户端,否则与服务器进行验证。
      1. 检验新鲜通常有两个HTTP头进行控制Expires和Cache-Control:
      1. HTTP1.0提供Expires,值为一个绝对时间表示缓存新鲜日期
      2. HTTP1.1增加了Cache-Control: max-age=,值为以秒为单位的最大新鲜时间
    3. 浏览器解析URL获取协议,主机,端口,path
    4. 浏览器组装一个HTTP(GET)请求报文
    5. 浏览器获取主机ip地址,过程如下:
      1. 浏览器缓存
      2. 本机缓存
      3. hosts文件
      4. 路由器缓存
      5. ISP DNS缓存
      6. DNS递归查询(可能存在负载均衡导致每次IP不一样)
    6. 打开一个socket与目标IP地址,端口建立TCP链接,三次握手如下:
      1. 客户端发送一个TCP的SYN=1,Seq=X的包到服务器端口
      2. 服务器发回SYN=1, ACK=X+1, Seq=Y的响应包
      3. 客户端发送ACK=Y+1, Seq=Z
    7. TCP链接建立后发送HTTP请求
    8. 服务器接受请求并解析,将请求转发到服务程序,如虚拟主机使用HTTP Host头部判断请求的服务程序
    9. 服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码
    10. 处理程序读取完整请求并准备HTTP响应,可能需要查询数据库等操作
    11. 服务器将响应报文通过TCP连接发送回浏览器
    12. 浏览器接收HTTP响应,然后根据情况选择关闭TCP连接或者保留重用,关闭TCP连接的四次握手如下:
      1. 主动方发送Fin=1, Ack=Z, Seq= X报文
      2. 被动方发送ACK=X+1, Seq=Z报文
      3. 被动方发送Fin=1, ACK=X, Seq=Y报文
      4. 主动方发送ACK=Y, Seq=X报文
    13. 浏览器检查响应状态吗:是否为1XX,3XX, 4XX, 5XX,这些情况处理与2XX不同
    14. 如果资源可缓存,进行缓存
    15. 对响应进行解码(例如gzip压缩)
    16. 根据资源类型决定如何处理(假设资源为HTML文档)
    17. 解析HTML文档,构件DOM树,下载资源,构造CSSOM树,执行js脚本,这些操作没有严格的先后顺序,以下分别解释
      1. 构建DOM树:
        1. Tokenizing:根据HTML规范将字符流解析为标记
        2. Lexing:词法分析将标记转换为对象并定义属性和规则
        3. DOM construction:根据HTML标记关系将对象组成DOM树
        解析过程中遇到图片、样式表、js文件,启动下载
      2. 构建CSSOM树:
        1. Tokenizing:字符流转换为标记流
        2. Node:根据标记创建节点
        3. CSSOM:节点创建CSSOM树
      3. 根据DOM树和CSSOM树构建渲染树:
        1. 从DOM树的根节点遍历所有可见节点,不可见节点包括:1)script,meta这样本身不可见的标签。2)被css隐藏的节点,如display: none
        2. 对每一个可见节点,找到恰当的CSSOM规则并应用
        3. 发布可视节点的内容和计算样式
      4. js解析如下:
        1. 浏览器创建Document对象并解析HTML,将解析到的元素和文本节点添加到文档中,此时document.readystate为loading,HTML解析器遇到没有async和defer的script时,将他们添加到文档中,然后执行行内或外部脚本。这些脚本会同步执行,并且在脚本下载和执行时解析器会暂停。这样就可以用document.write()把文本插入到输入流中。同步脚本经常简单定义函数和注册事件处理程序,他们可以遍历和操作script和他们之前的文档内容,当解析器遇到设置了async属性的script时,开始下载脚本并继续解析文档。脚本会在它下载完成后尽快执行,但是解析器不会停下来等它下载。异步脚本禁止使用document.write(),它们可以访问自己script和之前的文档元素
        2. 当文档完成解析,document.readState变成interactive,所有defer脚本会按照在文档出现的顺序执行,延迟脚本能访问完整文档树,禁止使用document.write()
        3. 浏览器在Document对象上触发DOMContentLoaded事件此时文档完全解析完成,浏览器可能还在等待如图片等内容加载,等这些内容完成载入并且所有异步脚本完成载入和执行,document.readState变为complete,window触发load事件
        显示页面(HTML解析过程中会逐步显示页面)
  3. 提高web网站性能

    1. 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

    2. Server方面
      1. 使用CDN
      2. 添加Expires或者Cache-Control响应头
      3. 对组件使用Gzip压缩
      4. 配置ETag
      5. Flush Buffer Early
      6. Ajax使用GET进行请求
      7. 避免空src的img标签

    3. Cookie方面

      1. 减小cookie大小
      2. 引入资源的域名不要包含cookie
    4. css方面

      1. 将样式表放到页面顶部
      2. 不使用CSS表达式
      3. 不使用@import
      4. 不使用IE的Filter
      5. 使用CSS3代码代替JS动画(尽可能避免重绘重排以及回流)
    5. Javascript方面

      1. 将脚本放到页面底部
      2. 将javascript和css从外部引入
      3. 压缩javascript和css
      4. 删除不需要的脚本
      5. 减少DOM访问
      6. 合理设计事件监听器
      7. 用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能
      8. 当需要设置的样式很多时设置className而不是直接操作style
      9. 少用全局变量、缓存DOM节点查找的结果。减少IO读取操作
    6. 图片方面

      1. 优化图片:根据实际颜色需要选择色深、压缩
      2. 优化css精灵
      3. 不要在HTML中拉伸图片
      4. 保证favicon.ico小并且可缓存
      5. 禁止使用gif图片实现loading效果(降低CPU消耗,提升渲染性能)
  4. HTML5的离线储存怎么使用,工作原理能不能解释一下?

    1. 在用户没有与因特网连接时,可以正常访问站点或应用,在用户与因特网连接时,更新用户机器上的缓存文件

    2. 原理:HTML5的离线存储是基于一个新建的.appcache文件的缓存机制(不是存储技术),通过这个文件上的解析清单离线存储资源,这些资源就会像cookie一样被存储了下来。之后当网络在处于离线状态下时,浏览器会通过被离线存储的数据进行页面展示

    3. 如何使用:

      1. 页面头部像下面一样加入一个manifest的属性;
      2. 在cache.manifest文件的编写离线存储的资源
      3. 在离线状态时,操作window.applicationCache进行需求实现
    4. 浏览器是怎么对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

HTTP

  1. HTTP状态码及其含义
    1. 1XX:信息状态码
      1. 100 Continue:客户端应当继续发送请求。这个临时相应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应。服务器必须在请求万仇向客户端发送一个最终响应
      2. 101 Switching Protocols:服务器已经理解力客户端的请求,并将通过Upgrade消息头通知客户端采用不同的协议来完成这个请求。在发送完这个响应最后的空行后,服务器将会切换到Upgrade消息头中定义的那些协议。
    2. 2XX:成功状态码
      1. 200 OK:请求成功,请求所希望的响应头或数据体将随此响应返回
      2. 201 Created:
      3. 202 Accepted:
      4. 203 Non-Authoritative Information:
      5. 204 No Content: 服务器成功处理了请求,但不需要返回任何实体内容,并且希望返回更新了的元信息
      6. 205 Reset Content:
      7. 206 Partial Content:
    3. 3XX:重定向
      1. 300 Multiple Choices:
      2. 301 Moved Permanently:被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一。(永久重定向)
      3. 302 Found:请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。(临时重定向)
      4. 303 See Other:
      5. 304 Not Modified:如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个状态码;(使用缓存内容)
      6. 305 Use Proxy:
      7. 306 (unused):
      8. 307 Temporary Redirect:
    4. 4XX:客户端错误
      1. 400 Bad Request:1、语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。   2、请求参数有误。(错误请求)
      2. 401 Unauthorized:当前请求需要用户验证(权限校验)
      3. 402 Payment Required:
      4. 403 Forbidden:服务器已经理解请求,但是拒绝执行它;(禁止访问)
      5. 404 Not Found:请求失败,请求所希望得到的资源未被在服务器上发现;(服务器上没有找到对应的资源)但在资源以前存在而现在不存在的情况下,有时用来替代404 代码。如果资源已永久删除,应当使用 301 指定资源的新位置。
      6. 405 Method Not Allowed:
      7. 406 Not Acceptable:
      8. 407 Proxy Authentication Required:
      9. 408 Request Timeout:
      10. 409 Conflict:
      11. 410 Gone:被请求的资源在服务器上已经不再可用,而且没有任何已知的转发地址
      12. 411 Length Required:
      13. 412 Precondition Failed:
      14. 413 Request Entity Too Large:
      15. 414 Request-URI Too Long:
      16. 415 Unsupported Media Type:
      17. 416 Requested Range Not Satisfiable:
      18. 417 Expectation Failed:
    5. 5XX: 服务器错误
      500 Internal Server Error:服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器的程序码出错时出现。(服务异常)
      501 Not Implemented:服务器不支持当前请求所需要的某个功能。当服务器无法识别请求的方法,并且无法支持其对任何资源的请求。
      502 Bad Gateway:由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,并且将在一段时间以后恢复。
      503 Service Unavailable:
      504 Gateway Timeout:
      505 HTTP Version Not Supported:

vue响应式原理

vue的响应式指的是data、props内的数据改变时,对应个的视图也会动态改变,那么vue是怎么做到的呢?

开始分析之前看下Vue.js官网介绍响应式原理的这张图
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.definePropertygetter内调用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
}

总结: 大致思路是捋清楚了,但是中间的一些实现细节,还是有待去研究,不过总的来说,不得不感叹这种实现的方式,太精妙了。

vue开发小结

  1. 在main.js里面注册store的时候,s不能大些,大些的话会无效,导致在组件里面使用mutations or actions无效,即注册的时候只能使用store,不能使用Store

  2. 在router配置文件里面,当配置子路由的时候,子路由的path前面是不需要带/的,/值需要在一级路由前面带上,因为在vue-router里面,把/开头的嵌套路径当做根路径;

  3. 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

  4. v-bind:href的简写:href;v-on:click的简写为@click,即自定义事件在父组件可以通过@自定义事件名or v-on自定义事件名来监听

  5. computed内的计算属性只能够动态获取简单类型值得变化,引用类型的值如果第一次是空,可以获取到最新值,如果不是空则获取不到,这个时候需要用watch来监听对象属性的变化

  6. props只能用于父子之间传递数据不能再祖孙组件之间进行传值,如果祖孙之间需要传至需要一层层的传递;

  7. this.$emit子组件向父组件传递值时,只能够通知到父组件,不能通知到祖组件,如果需要传递到祖组件则需要一层一层的传递;

  8. 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>
  1. computed计算属性,data内定义的属性二者之间有什么区别

    1. data内定义的属性将会转换为getter与setter,从而让 data内定义的属性能够响应数据变化;需要注意的是在组件实例之前在data内定义的对象才会被转换为getter与setter属性,才会实现响应式变化,组件内定义的是不会转换为getter与setter;data为什么是一个函数而不是一个对象是因为组件可能被用来创建多个实例(即被不同的父组件引用)。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。
    2. computed计算属性的结果会被缓存,除非依赖的响应式属性(如data内定义的属性,props内传入的属性)变化才会重新计算。注意,如果实例范畴之外的依赖 (比如非响应式的 not reactive) 是不会触发计算属性更新的。它的初衷是帮助我们简化一些操作(与methods比)与节约一些性能(与watch比),计算属性默认只有getter,即获取计算属性值的方法,也可以定义setter方法,用来设置计算属性的值,在计算属性被重新赋值时进行调用,一般用来设置的当这个计算属性进行改变时,需要进行的一些操作所以在使用的时候只要考虑当前变量是否需要依赖某个项记性改变,还是说需要通过外部操作来进行改变,还是不需要改变来进行判断变量是定义成data属性内的变量还是,computed内的属性
  2. v-model详解 vue内v-model的实现方式是:value="txt" @input="txt = $event.target.value"这两段代码实现的

    1. 在父组件内使用child-input并要求给子组件的input内赋初始值,并且子组件内的值变化时,父组件绑定的对应的值也需要改变,这个argument[0]取的就是$emit(input, value)emit上来的这个value值
    2. 所以要想实现上述效果,子组件内还需要添加props:{value: {}}的属性,在子组件定义的value变量txt的set函数钩子内进行$emit('input', val)事件
    3. 注意input事件是h5新增的事件,支持ie9+浏览器,当input的内容改变时就会触发oninput事件,类似于onchange事件
    4. 还需要注意的是,oninput事件只有type="text"及type="textarea"的表单控件才有,所以要实现单选,复选等表单组件还需要使用另外的类似的方式
   基本用法如下所示
   <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>
  1. vue及angular等框架,核心**数用数据来进行驱动,而不是dom来进行驱动,所以我们在做全选与反选时包括类似的操作应该使用数据来进行驱动而不是dom来驱动,这里需要利用data属性的双向绑定与computed属性的依赖变化

  2. 先在data里面定义一个checkList: []

  3. 在计算属性内获取到从父组件传入的v-for的列表数据内对checkList进行赋值操作,使用isChecked属性来绑定每个checkbox

  4. 在计算属性内定义当前被选中的计算属性selectCount,通过遍历,通过isChecked属性来筛选

  5. 计算属性内定义全选属性selectAll 通过get与set方法来进行全选与全不选操作,全选实现的思路是通过被选中数量计算属性与渲染当前列表的数据长度是否相等来进行判断,全不选是通过遍历及isChecked来进行设置

  6. 计算属性内定义获取当前被选中的元素数组checkedGroup,遍历,isChecked来进行判断

  7. nextTick,将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新;这里有两个概念,一个是响应式原理,一个是异步更新队列

    1. 响应式是vue框架的一个特色,它指的时数据变化之后我们可以知道具体哪个数据进行了变化,从而去更新这个数据对应的视图,实现原理就是使用es5中新增的Object.defineProperty方法,改方法可以定义对象属性的getter与setter方法,而vue就是通过setter方法的调用来判断哪个数据进行了变化,而这个监视setter函数变化就就是一个watch对象,vue框架内每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新;需要注意的时Vue 不能检测到对象属性的添加或删除(受方法的限制)。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的;然而可以通过set方法来动态设置响应式属性;
    2. 异步更新队列:就是说vue框架在处理数据变化时,遵照的是当某个响应式数据变化时,会开启一个队列,改队列会缓存统一事件循环中发生的所有数据的改变,在这一个过程中,如果同一个watcher触发多次,也之后被推入到队列一次,这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要;然后在在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作;所以也就有了nextTick钩子函数,帮助我们可以在dom更新之后再去做一些操作。一定要注意的时只有响应式数据才会遵循这种方法;
  8. 制作一个分页器的核心在两个部分,第一个就是分页器页码的生成规则,第二点就是数据总数,每页展示的条数,及当前页码这是三个关键数字;而分页器的难点就在分页器码的生成规则,需要直接好好的去总结下分页器页码的生成规则

  9. 在将 v-bind 用于 class 和 style 时,表达式结果的类型除了字符串之外,还可以是对象或数组。注意的是在组件上也是可以直接使用的,class会被添加到定义组件时的最外层元素上

字符串:class="className"
对象:class="{ active: isActive, 'text-danger': hasError }"
数组:class = "[activeClass, errorClass]"
  1. Vue 主动检测到数组变化的有三种方式,不能检测到变化的有二种方式
能检测到变化的方式
第一种成功添加的方式,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
  1. Vue 不能检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的;即有三种方式对对象的属性可以变成响应式的变化,二种方式不能进行响应式的变化
第一种是在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 }

  1. v-if or v-for使用的时候可以用一个template来包裹需要操作的标签,这里的template不会被渲染出来,只是其它一个方便操作的作用,v-show的时候不能使用template标签

  2. 注意在prop进行传值时,因为HTML 特性是不区分大小写的。所以,当使用的不是字符串模板(如.html后缀的文件,字符串模板指的时.vueor其它后缀结尾的文件)时,camelCase (驼峰式命名) 的 prop 需要转换为相对应的 kebab-case (短横线分隔式命名): 而不是,所以便于将.vue后缀的文件改成.html后缀文件不出现报错的话,建议都写成kebab-case (短横线分隔式命名)

  3. 为了保证数据的单向流即父组件数据更新时,子组件对应的数据也会更新,而子组件内的数据更新时,不会影响到数据内的属性,因为JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。所以为了避免这种情况的
    出现最好的方法就是在子组件的data内声明一个新的变量来进行接收,or在计算属性内得到一个新的值

  4. 注意 prop 会在组件实例创建之前进行校验,所以在 default 或 validator 函数里,诸如 data、computed 或 methods 等实例属性or方法还无法使用

  5. 组件上的.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)
  1. 单元素or组件的过渡即需要使用到动画,需要用到transition,只要给transition添加一个name,然后就可以根据这个name来定义动画了,过渡的类名总共6个,这里的v可以使用transition上的name来替换如fade-enter fade-enter-to

    1. v-enter 插入dom时触发,在下一个帧移除
    2. v-enter-active
    3. v-enter-to 插入dom完成时触发,transition/animation 完成之后移除
    4. v-leave 删除or隐藏dom时触发,在下一个帧移除
    5. v-leave-active
    6. v-leave-to 删除or隐藏dom完成时触发,transition/animation 完成之后移除
  2. 混合也就是提取出公共的数据or方法,然后混合到需要的组件内,or在全局对象上进行混合操作,添加到每个组件上,需要注意的就是一个覆盖原则,如同名钩子函数,混合之后会变成一个数组且混合对象的 钩子将在组件自身钩子 之前 调用;如methods, components 和 directives将被混合为同一个对象。两个对象键名冲突时,取组件对象的键值对;全局混合一旦使用全局混合对象,将会影响到 所有 之后创建的 Vue 实例

var mixin = {
  created: function () {
    console.log('混合对象的钩子被调用')
  }
}

new Vue({
  mixins: [mixin],
  created: function () {
    console.log('组件钩子被调用')
  }
})
  1. 移动端开发时,怎样将px转换为rem,这里使用的是postcss-px2rem-exclude loader,该loader就比postcss-px2rem多了一个可以去除不转为rem的文件目录,实现px转rem两种配置方式
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的不同,生成三套代码。---- 一般字体需用这个
  1. 开发环境代理设置
在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));

xhr的使用姿势

之前在工作中的时候,使用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等处理;于是抽时间来总结一下

首先jquery也好axios也好,其它的类库也好,原理都是使用xhr对象来进行ajax请求;

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',);
    }

app.js

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>

get请求request参考图

axios-get

post请求content-type: application/json request参考图

axios-post-applicationjson

post请求content-type: application/x-www-form-urlencoded request参考图

axios-post-application-x-www-form-urlencoded

post请求content-type: application/form-data request参考图

post-form-data

参考链接:
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);

根据MDN来回顾javascript基础知识

1. 未赋值的变量,初始值未undefined、需要使用 x === undefined来进行判断,需要注意的是,undefined值在布尔环境中会被当做false,在数值环境中会被转化为NaN;另外需要注意的是null在布尔环境中与undefined一样转化为false,但是在数值环境中会被转化为0;

var x;
if (!x) { //为false
}

x + 2 // NaN

var a = null;

if(!a) {
}

a + 2 // 2

2. 单目加法运算符,将字符串数字转化为数字

'1.1' + '1.1' = '1.1.1.1';
+'1.1' + 2 = 3.1;
+'12a' => NaN

3. 对象字面量,需要注意的是对象属性名字可以是任意字符串,包括空串。如果对象属性名字不是合法的javascript标识符,它必须用""包裹。属性的名字不合法,那么便不能用.访问属性值,而是通过类数组标记("[]")访问和赋值;

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!

4. 超长字符串写法,建议使用es6模板字符串写法

// 使用 \符号来进行换行书写,最终的字符串还是单行,实际上斜线和换行都不会出现在字符串的值中
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.

5. 常用的特殊字符

\n | 换行符
\t | Tab (制表符)
\v | 垂直制表符
\' | 单引号
\" | 双引号
\\ | 反斜杠字符(\)

6. 在布尔环境中会被转化为false的6个值,false、0、‘’、undefined、null、NaN;

7.

underscore源码解析(一)

一时技痒,想找点源码来看看,于是找到了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;  
  }

资源链接
https://github.com/jashkenas/underscore/tree/0.1.0

underscore源码解析(二)

接着上一篇,继续分析,显然underscore0.1.0版本,还不是最优的版本,也不能去覆盖更多的使用场景,所以必然会去继续优化。所以从0.2.0开始看,但是当自己去总结的时候如果一个版本一个版本去总结分析的话,也不合理,因为有些小版本只是添加了一个方法,or修改了某个方法的兼容性,索性从0.2.0一直到1.0.0这中间的版本合到一起来分析,学下作者当时的优化思路及新增的方法是为了哪些业务场景;

总结下0.2.0版本到1.0.0版本之间,作者主要新增及优化了哪些内容,如下所示:

  1. 增加了noConflict方法,避免在underscore加载完之后,覆盖之前定义or加载的_符号;
  2. 新增了面向对象OOP的写法,通过改写了之前定义underscore的方式,即支持_.each(),也支持_().map(),同时所有的静态方法定义在underscore函数上
  3. 增加了对服务端的兼容即新增了适配commonJs的方法
  4. 语义化了部分方法的名称,如将all方法改成every方法,any方法改成some方法,向ECMASCRIPT标准靠拢;
  5. 语义化了原生对象上的属性or方法;
  6. 新增了部分方法如isEmpty、isArguments等方法
  7. 优化了部分方法的内部实现如each方法,keys方法
  8. 扩展了部分方法如foldl、methods等
  9. 支持链式调用
  10. 抽出了部分公共属性or方法如breaker,breakLoop、identity方法
  11. 修改了定义underscore的方法,从window._ = {} 变成了自执行函数内root._ = _
  12. each方法在回调函数处理上添加了第三个参数及把传入的集合也传入到了回调函数内,所以只要内部用到each方法的方法,回调函数都增加了第三个参数
  13. 所有之前用if来判断回调函数的值的写法,然后在条件判断内进行相应(单行)操作的写法,都用&操作符来进行了改写,这样可以使代码更简洁

一、基础设置

使用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;

七、OOP设置

定义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

js常见问题总结与分析

  1. 去掉数值的小数部分
console.log(parseInt(10.23));
console.log(~~10.23);
console.log(10.23 >> 0);
console.log(10.23 | 0);
  1. 数组最大值、最小值(不包含引用类型的值)
// 第一种方法
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])//最小值
  1. 获取随机色
var getRandomColor = function (){
	return '#'+Math.random().toString(16).substr(2,6);
}
  1. 深拷贝(简单深拷贝)
var deepCopy = function (obj){
        //注意函数不能使用该方法进行深拷贝,因为stringify方法无法对函数进行转换
	return JSON.parse(JSON.stringify(obj));
}
  1. 类型检测
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;
		 		}
		 }
})();
  1. 去空格
// 去两边空格
function trim(str) {
	return str.replace(/(^\s+)|(\s+$)/g, "");
}

// 去所有空格
function removeAllSpace(str) {
	return str.replace(/\s+/g, "");
}
  1. 替换对象内某个key对应的值
常规方法是遍历整个对象,然后对对象的某个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
  1. 设置日期的最大值与最小值
一般在使用日历插件的时候,需要设置最大日期与最小日期
最小日期一般很好获取,要么是个定值要么是当前值
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();
  1. focus/blur与focusin/focusout的区别与联系

    1. focus/blur不冒泡,focusin/focusout冒泡
    2. focus/blur兼容性好,focusin/focusout在除FireFox外的浏览器下都保持良好兼容性,如需使用事件托管,可考虑在FireFox下使用事件捕获elem.addEventListener('focus', handler, true)
    3. 可获得焦点的元素:
      1. window
      2. 链接被点击或键盘操作
      3. 表单空间被点击或键盘操作
      4. 设置tabindex属性的元素被点击或键盘操作
  2. mouseover/mouseout与mouseenter/mouseleave的区别与联系

    1. mouseover/mouseout是标准事件,所有浏览器都支持;mouseenter/mouseleave是IE5.5引入的特有事件后来被DOM3标准采纳,现代标准浏览器也支持
    2. mouseover/mouseout是冒泡事件;mouseenter/mouseleave不冒泡。需要为多个元素监听鼠标移入/出事件时,推荐mouseover/mouseout托管,提高性能标准事件模型中event.target表示发生移入/出的元素,vent.relatedTarget对应移出/如元素;在老IE中event.srcElement表示发生移入/出的元素,event.toElement表示移出的目标元素,event.fromElement表示移入时的来源元素
  3. cookie及其操作

    1. cookie是web浏览器存储的少量数据,最早设计为服务器端使用,作为HTTP协议的扩展实现。cookie数据会自动在浏览器和服务器之间传输。
    2. 通过读写cookie检测是否支持
      1. cookie属性有名,值,max-age,path, domain,secure;
      2. cookie默认有效期为浏览器会话,一旦用户关闭浏览器,数据就丢失,通过设置max-age=seconds属性告诉浏览器cookie有效期
      3. cookie作用域通过文档源和文档路径来确定,通过path和domain进行配置,web页面同目录或子目录文档都可访问
      4. 通过cookie保存数据的方法为:为document.cookie设置一个符合目标的字符串如下
      5. 读取document.cookie获得'; '分隔的字符串,key=value,解析得到结果
document.cookie = 'name=qiu; max-age=9999; path=/; domain=domain; secure';
document.cookie = 'name=aaa; path=/; domain=domain; secure';
  1. sessionStorage,localStorage,cookie区别

    1. 都会在浏览器端保存,有大小限制,同源限制
    2. cookie会在请求时发送到服务器,作为会话标识,服务器可修改cookie;web storage不会发送到服务器
    3. cookie有path概念,子路径可以访问父路径cookie,父路径不能访问子路径cookie
    4. 有效期:cookie在设置的有效期内有效,默认为浏览器关闭;sessionStorage在窗口关闭前有效,localStorage长期有效,直到用户删除
    5. 共享:sessionStorage不能共享,localStorage在同源文档之间共享,cookie在同源且符合path规则的文档之间共享
    6. localStorage的修改会促发其他文档窗口的update事件
    7. cookie有secure属性要求HTTPS传输
    8. 浏览器不能保存超过300个cookie,单个服务器不能超过20个,每个cookie不能超过4k。web storage大小支持能达到5M
    9. storage与cookie的读写操作方法不一样
  2. javascript跨域通信

    1. 同源:两个文档同源需满足
      1. 协议相同
      2. 域名相同
      3. 端口相同
    2. 跨域通信:js进行DOM操作、通信时如果目标与当前窗口不满足同源条件,浏览器为了安全会阻止跨域操作。
    3. 跨域通信通常有以下方法
      1. 如果是log之类的简单单项通信,新建,<script>,,<iframe>元素,通过src,href属性设
        置为目标url。实现跨域请求
      2. 如果请求json数据,使用<script>进行jsonp请求
      3. 现代浏览器中多窗口通信使用HTML5规范的targetWindow.postMessage(data, origin);其中data是需要发送的对象,origin是目标窗口的origin。window.addEventListener('message', handler, false);handler的event.data是postMessage发送来的数据,event.origin是发送窗口的origin,event.source是发送消息的窗口引用
      4. 内部服务器代理请求跨域url,然后返回数据(nginx代理跨域、nodejs中间件代理跨域)
      5. 跨域请求数据,现代浏览器可使用HTML5规范的CORS功能,只要目标服务器返回HTTP头部**Access-Control-Allow-Origin: ***即可像普通ajax一样访问跨域资源
通过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);
  1. 什么闭包,闭包有什么用

    1. 闭包就是函数,一个能够读取其他函数内部变量的函数。
    2. 闭包作用域链通常包括三个部分:
      1. 函数本身作用域。
      2. 闭包定义时的作用域。
      3. 全局作用域。
    3. 闭包的特性
      1. 函数内再嵌套函数
      2. 内部函数可以引用外层的参数和变量
      3. 参数和变量不会被垃圾回收机制回收
    4. 闭包的理解
      1. 使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念;闭包 的最大用处有两个,一个是可以读取函数内部的变量,让这些变量始终保持在内存中,另一个用处,是封装对象的私有属性和私有方法,好处:能够实现封装和缓存等;坏处:就是消耗内存、不正当使用会造成内存溢出的问题
      2. 使用闭包的注意点,由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露,解决方法是,在退出函数之前,将不使用的局部变量全部删除
  2. ===运算符判断相等的流程是怎样的

    1. 如果两个值不是相同类型,它们不相等
    2. 如果两个值都是null或者都是undefined,它们相等
    3. 如果两个值都是布尔类型true或者都是false,它们相等
    4. 如果其中有一个是NaN,它们不相等
    5. 如果都是数值型并且数值相等,他们相等, -0等于0
    6. 如果他们都是字符串并且在相同位置包含相同的16位值,那它们相等;
    7. 如果在长度或者内容上不等,它们不相等;两个字符串显示结果相同但是编码不同==和===都认为他们不相等
    8. 如果他们指向相同对象、数组、函数,它们相等;如果指向不同对象,他们不相等
  3. ==运算符判断相等的流程是怎样的

    1. 如果两个值类型相同,按照===比较方法进行比较
    2. 如果类型不同,使用如下规则进行比较
    3. 如果其中一个值是null,另一个是undefined,它们相等
    4. 如果一个值是数字另一个是字符串,将字符串转换为数字进行比较
    5. 如果有布尔类型,将true转换为1,false转换为0,然后用==规则继续比较
    6. 如果一个值是对象,另一个是数字或字符串,将对象转换为原始值然后用==规则继续比较
      其他所有情况都认为不相等
  4. <,>,<=,>=的比较规则
    所有比较运算符都支持任意类型,但是比较只支持数字和字符串,所以需要执行必要的转换然后进行比较,转换规则如下:

    1. 如果操作数是对象,转换为原始值:如果valueOf方法返回原始值,则使用这个值,否则使用toString方法的结果,如果转换失败则报错
      经过必要的对象到原始值的转换后,如果两个操作数都是字符串,按照字母顺序进行比较(他们的16位unicode值的大小)
      否则,如果有一个操作数不是字符串,将两个操作数转换为数字进行比较
  5. 函数内部arguments变量有哪些特性,有哪些属性,如何将它转换为数组

    1. arguments所有函数中都包含的一个局部变量,是一个类数组对象,对应函数调用时的实参。如果函数定义同名参数会在调用时覆盖默认对象
    2. arguments[index]分别对应函数调用时的实参,并且通过arguments修改实参时会同时修改实参
    3. arguments.length为实参的个数(Function.length表示形参长度)
    4. arguments.callee为当前正在执行的函数本身,使用这个属性进行递归调用时需注意this的变化
    5. arguments.caller为调用当前函数的函数(已被遗弃)
    6. 转换为数组:var args = Array.prototype.slice.call(arguments, 0);
  6. iframe有那些缺点?

    1. iframe会阻塞主页面的Onload事件
    2. 搜索引擎的检索程序无法解读这种页面,不利于SEO
    3. iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载
    4. 使用iframe之前需要考虑这两个缺点。如果需要使用iframe,最好是通过javascript动态给iframe添加src属性值,这样可以绕开以上两个问题
  7. This指向的理解

    1. 事件函数内的this
    2. 对象方法内的this
    3. new构造函数时构造函数内的this
    4. 普通函数调用时,函数内的this
    5. call,apply调用时,函数内的this
    6. 箭头函数内的this
  8. 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);
            }
        }
    }
  1. 异步加载JS的方式有哪些?

    1. defer,只支持IE
    2. async:
    3. 创建script,插入到DOM中,加载完毕后callBack
  2. defer和async的区别

    1. defer并行加载js文件,会按照页面上script标签的顺序执行
    2. async并行加载js文件,下载完成立即执行,不会按照页面上script标签的顺序执行
  3. 说说你对AMD和CommonJs的理解
    CommonJS是服务器端模块的规范,Node.js采用了这个规范。CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数
    AMD推荐的风格通过返回一个对象做为模块对象,CommonJS的风格通过对module.exports或exports的属性赋值来达到暴露模块对象的目的

  4. 严格模式常见的限制

    1. 变量必须声明后再使用
    2. 函数的参数不能有同名属性,否则报错
    3. 不能使用with语句
    4. 禁止this指向全局对象
  5. addEventListener 兼容ie9+,所以如果项目是在ie9+以上浏览器内运行,直接就可以用,不用考虑兼容性

  6. 判断js内某个字符串全是数字

    1. 使用isNaN;
    2. 使用正则/\d+/g
  7. 如何获取浏览器滚动条高度

    1. 在ie、firefox、chrome下都是使用document.documentElement.scrollTop 而不是document.body.scrollTop,jquery下是使用$(window).scrollTop
  8. 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];
  1. Object.assign

    1. 合并or拷贝对象,并不是深拷贝,是披着深拷贝外衣的浅拷贝。最多也是Object.assign会深拷贝第一层的值,对于第一层的值都是深拷贝,而到第二层的时候就是复制引用。类似的情况还有,slice方法和concat方法等。
  2. 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);
    }
};
  1. appendChild方法插入的元素怎么样去使用transition动画

  2. requestAnimationFrame方法可以在什么样的场景下使用

  3. elementsFromPoint方法的作用是什么

  4. apply 与 call

    1. 非常相似,不同之处在于提供参数的方式。apply 使用参数数组而不是一组参数列表。apply 可以使用数组字面量(array literal),如 fun.apply(this, ['eat', 'bananas']),或数组对象, 如 fun.apply(this, new Array('eat', 'bananas'))。
    2. 需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。
  5. 为什么ES5及以下的JS无法完美继承数组,最优的方式是什么

  6. throw语句

    1. throw语句用来抛出一个用户自定义的异常。当前函数的执行将被停止(throw之后的语句将不会执行),并且控制将被传递到调用堆栈中的第一个catch块。如果调用者函数中没有catch块,程序将会终止,语法为throw expression; expression可以是字符串,数字,布尔值
    2. 一般可用来捕获异常or中断循环,如forEach循环也是可以被中断的,条件是forEach遍历是放在try catch内的,因为不放在try catch语句内,直接throw是会抛出一个异常终止程序执行的
try {
   throw '__break__';
} catch (e) {
   这里的e就是throw后面的内容
}
  1. propertyIsEnumerable

    1. propertyIsEnumerable方法返回一个布尔值,表示指定的属性是否可枚举。
  2. forEach方法

    1. forEach方法对数组的每个元素执行一次提供的函数。返回值为undefined;即forEach() 为每个数组元素执行callback函数;不像map() 或者reduce() ,它总是返回 undefined值,并且不可链式调用,没有办法中止或者跳出 forEach 循环,除了抛出一个异常。
  3. undefined与void 运算符

    1. undefined并不是保留词(reserved word),它只是全局对象的一个属性,在低版本 IE 中能被重写,undefined 在 ES5 中已经是全局对象的一个只读(read-only)属性了,它不能被重写。但是在局部作用域中,还是可以被重写的
    2. void 运算符 对给定的表达式进行求值,然后返回 undefined。void 运算符通常只用于获取 undefined的原始值,一般使用void(0)(等同于void 0)
  4. for...in语句

    1. for...in语句以任意顺序遍历一个对象的可枚举属性(包括原型上继承的)。对于每个不同的属性,语句都会被执行,另外for in遍历不是按顺序来进行遍历的,它跟每个浏览器自身的实现有关,for in出问题会包含两个条件其一是在 IE < 9 浏览器中(又是万恶的 IE!!),其二是被枚举的对象重写了某些键,如重写了toString属性,chrome下能够正常返回,ie8则不会弹出,因为IE 8 将 toString "内定" 成了不可枚举属性(尽管已经被重写)
  5. 0.1 +0.2===0.30000000000000004的原因

    1. EcmaScrpt规范定义Number的类型遵循了IEEE754-2008中的64位浮点数规则定义的小数后的有效位数至多为52位导致计算出现精度丢失问题!所以可以使用toFixed() 方法来解决这个问题,兼容ie9+
      numObj.toFixed(digits) 方法使用定点表示法来格式化一个数,返回值为所给数值的定点数表示法的字符串形式。 digits小数点后数字的个数;介于 0 到 20 (包括)之间,实现环境可能支持更大范围。如果忽略该参数,则默认为 0。digits具体值取决于传入参数)位数字。该数值在必要时进行四舍五入,另外在必要时会用 0 来填充小数部分,以便小数部分有指定的位数。 如果数值大于 1e+21,该方法会简单调用 Number.prototype.toString()并返回一个指数记数法格式的字符串。
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"
  1. Babel的相关知识

    1. Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。举例来说,ES6在Array对象上新增了Array.from方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片

    2. babel-preset-env 的工作方式类似 babel-preset-latest,唯一不同的就是 babel-preset-env 会根据配置的 env 只编译那些还不支持的特性。即babel-preset-env包含了babel-preset-2015,babel-preset-2016等,但不包括babel-polyfill及babel-preset-stag

  2. babel-polyfill的正确使用方式

  3. beforeunload事件

    1. beforeunload事件用于页面关闭与刷新前,阻止页面是否进行关闭or刷新,在ie10下a也标签会触发beforeunload,解决办法是1.去掉a标签上的href属性,2.把a标签换成其它span标签替代;另外在解决改问题的时候在a标签的click事件里面进行阻止默认行为没有用;
  4. encodeURIComponent

  5. JSbarcode二维码生成插件

    1. JSbarcode生成的图片是基于base64的,生成的图片质量较差在ie下打印非常模糊,所以换成jquery-barcode插件,这个插件主要是以css也就是在一个div内生成数个div用样式来生成条码,所以不存在各个浏览器打印样式的差异,所以如果不考虑ie打印的话建议用JSbarcode,否则用jquery-barcode
  6. base64的图片

    1. base64的图片不能通过js转换成链接的图片,要转换成链接的图片的话要与后端交互,需要后端返回对应的链接,js只能报图片转换成base64显示,转换成Base64的好处就是可以减少请求次数,坏处就是不能换成,另外ie8及以上的浏览器都已经较好的支持base64位图片展示
  7. input file属性提交本地文件

    1. input file属性提交本地文件在ie下会存在双击才能弹出选择文件框而不是单机进行选择,解决办法是让含有file属性的input文本框足够的打,能够充斥整个父容器
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;
}
  1. 生成表格单元格的方法

    1. 生成表格单元格的方法传了传统的通过createElement方法来创建td与tr之外还可以通过insertRow(index)生成tr,index当前行的行号,表示在第几行后插入行,insertCell生成单元格,注意一定要是填充单元格到tr内,不然tr不能正常显示deleteRow(index)删除行,index为行的下标,行数index从0开始,并且随着行数的变化而动态变化
  2. jquery removeClass

    1. 该方法删除某个元素的class类,如果插入类名表示删除该类,如果不传参表示删除该元素上的所有类样式
  3. jquery end

    1. 该方法返回最近一次破坏性操作之前的元素,破坏性操作值获取了新的元素集合or获取了某组数据,如find,child,parent,next,nextAll,offset等方法
  4. jquery closest("选择器")

    1. 获取当前元素的含有传入选择器的最近一个父元素
  5. jquery data

    1. 该方法无法获取动态赋值之后的data值,要获取动态赋值之后的data值需要用attr方法获取,另外data方法也不能动态设置data属性,如果要动态设置的话要使用attr方法来进行动态设置
  6. font-family设置字体类型

    1. font-family设置字体类型时,值可以是加双引,与单引,不加效果都是一样的,但是用jquery的css()方法取值的时候,在chrome下不管你加不加引号取出来的值都是加引号的字符串,而ie浏览器下取出来的值根据设置font-family的值时是否带引号有关,如果带引号那么会将引号也算进去并且进行转义("'ArvoBold'"),所以为了兼容性设置font-family的时候值都不要加引号
  7. 关于iframe引入页面进行打印的小结

    1. 当在父页面调用iframe页面内的打印方法时,如果iframe页面没有进行focus操作,那么打印就是打印父页面的内容,也包括ifame的内容,如果iframe页面内进行了fouse操作,那么只会打印iframe里面的内容,注意了这里打印ifame里面的内容宽高,与打印机的标签纸张大小有关,与iframe及iframe内的元素宽高无关,当iframe内的内容宽高小于打印纸张的宽高时,内容会全部打印出来,当内容的宽高大于纸张的宽高时只会打印出纸张视图内的内容,所以打印最好的方式就是显示一个元素,打印一个元素,然后删除一个元素,注意这里用setTimeout而不是setInterval,原因是setInterval会根据执行的快慢来调整执行的时机,即不一定按时间间隔来准确执行,而setTimeout会按准确的时间间隔来执行
  8. 原生js及jquery,父页面获取iframe页面的window对象,document对象,及元素的方式,iframe页面获取父页面window对象,document对象,及元素的方法

    1. 关键是先获取window对象(注意通过window点出来的直接就是iframe的window对象了,而通过先获取iframe元素的话,就需要通过元素的contentWindow属性来获取window对象),然后再通过window对象获取到document对象,然后通过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");
  1. 当动态创建多个iframe时,怎么去保证当前iframe里面的值就是创建当前iframe时主页面中的值

    1. 试了数组的方式,也试了在iframe元素上加属性的方式,效果都是不特别好,可能会出现iframe内的值,不是创建时需要取的值,解决方法就是在主页面创建一个key-value的对象,然后在iframe内通过key来获取值。这样可以保证每个iframe内取值时,取得就是当前iframe创建时候的值,不会出现错误,另外需要注意的是,iframe内是无法获取到当前iframe元素上的属性值的,唯一的方法就是在父页面内定义一个方法,然后在iframe页面内进行调用
  2. 浏览器输出的对象的key值不是按后台返回的key值顺序来显示

    1. js里面在定义对象时,对象的key是没有顺序的,但是当输出的时候,浏览器就好根据key的ascii来进行从小到大的排序,所以如果输出的内容不需要进行排序的话,可以使用对象直接输出,如果需要对key进行排序的话,就需要使用数组包对象来进行输出,然后利用sort方法来进行排序
  3. 同名的函数与变量函数的提示在变量之后,即先找变量,找到a,然后将a赋值为undefined,然后找函数,发现函数有同名变量,然后会去覆盖同名变量a

jquery-barcode/JsBarcode条码生成插件

jquery-barcode生成的条码插件

image

// 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 //没什么用
});

JsBarcode生成的条码插件

jsbarcode

$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格式的条码是不会出现失真及漏针的情况!

vue项目中引入echart.js来制作图表

echart.js是一款超级强大的图表制作插件,可以满足我们展示业务数据,如柱形图,饼状图,折线图,仪表图等,因为这次项目内大量地方用到数据展示,所以引入了echart.js插件用于制作图表,先总结下开发的时候遇到的小问题

  1. 首先引入echarts.js,使用npm install --save echarts

  2. 定制组件,因为复用的地方比较多,所以抽出来成了组件,共封装了四个组件,折线图,柱状图,饼状图,仪表图,封装这四个组件的方法类似

  3. 按需引入,因为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 主题

  4. 柱状图通过xAxis,与yAxis的type属性的值来决定哪个是类目轴哪个是数值轴,category类目轴,value表示数值轴
    xAxis: { // type参数决定那个是类目轴哪个是数值轴 type: this.isShowX ? 'category' : 'value', },
    yAxis: { type: this.isShowX ? 'value' : 'category', },

  5. 怎样让echart.js制作出来的图表随着浏览器的窗口大小的变化而变化

    1. 初始化组件的时候,监听window对象的resize事件,回调函数是chart对象的返回值下的resize方法,echart插件自带的方法
      window.addEventListener('resize', this.chart.resize)
    2. 销毁组件的时候在注销掉resize事件
      window.removeEventListener('resize', this.chart.resize);,注意要在this.chart.dispose();之前注销
  6. 怎么去动态设置图表的值,让图表随着后端请求来的值进行实时更新

    1. watch父组件传入的数据
    2. this.chart.setOption(this.opt); 利用setOption方法重新赋值
  7. 柱状图,折线图不显示坐标轴,通过设置xAxis,yAxis下的axisLine,不显示背景色通过设置xAxis,yAxis下的splitLine与splitArea

  8. legend属性用于设置切换索引

开发效果图
chart1

chart3

echart.js的使用不难,难的时配置参数的查找;

封装有该组件的项目地址

参考链接
http://echarts.baidu.com/option.html#series

常用设计模式

单例模式

单例模式故名思议只允许实例化一次的对象类;常用于提供命名空间、模块化开发、及提供私有方法与属性

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小结

最近在项目开发中碰到跨域cookie与跨webview的接口携带cookie问题,于是总结一下cookie及前端的角度去看待cookie

为什么会有cookie,及客户端发起请求的时候,浏览器为什么会主动帮助我们在请求头里面添加cookie?

HTTP Cookie(也叫Web Cookie或浏览器Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie使基于无状态的HTTP协议记录稳定的状态信息成为了可能;这页就是为什么会有cookie及浏览器为什么会主动在请求头里面添加cookie;

  1. cookie一般用于存储小量数据与后端存储验证id之类的值如sesion_id等;主要由以下几个参数构成
参数 是否必填 作用 默认值
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
  1. 什么是会话cookie,什么是持久性cookie

两者通过是否设置expires or max-age的值来判断,如果没有设置则是会话cookie,即只存在当前浏览器打开的时间内,如果浏览器关闭则默认会话结束,会话cookie会被浏览器清除;而永久cookie则是根据设置的值来确定是否过期,如果过期则删除;

这里有个小问题需要注意的是mac电脑上,关闭浏览器之后打开,发现cookie没有被清掉是,因为我们点击浏览器的x按钮mac电脑只是关闭了当前的浏览器窗口,并没有推出浏览器,这里需要使用command + q退出之后再打开就会发现会话cookie被清除了;windows下不会有这个问题

  1. cookie参数的详解

    1. 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;

    2. 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)'

    3. HttpOnly是否允许js去访问该cookie;默认情况设置cookie是不会带上该字段, 即允许js操作该cookie;该作用是只允许服务端来操作该cookie,不允许客户端来操作该cookie;这样限制的目的是保证客户信息的安全,避免被xss攻击时通过一段script脚本内的document.cookie来获取用户信息相关的cookie;最后需要注意的是该字段是只有服务端设置cookie的时候才会生效,客户端通过js设置该字段时是无效的;

    4. secure用来设置cookie只有在安全的协议传输时才会被带上,如https等;默认情况下设置cookie时,是不会被带上的;一般不会设置该字段,应该这样可以保证在http or https的请求中该cookie都能够被带上;还有我们可以通过js来直接设置该字段;

    5. 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”。

  2. 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' 
  1. 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也是一样的,子可以读取父,但是父不能读取子;

  2. cookie的修改

    cookie的修改就是直接覆盖,获取之后,修改其它可配置的字段;

  3. 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]);
    }
}

  1. 怎么解决跨域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;
  1. cookie渐渐被淘汰的原因

    1. 存储空间小;
    2. 由于服务器指定Cookie后,浏览器的每次请求都会携带Cookie数据,会带来额外的性能开销(尤其是在移动环境下)

参考链接:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies
https://segmentfault.com/a/1190000004556040

数组去重、数组去重并寻找最大项、数组排序

简介

数组去重的方法很多,于是把平常自己用到的总结了一下

indexOf 去重

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
}

indexOf + forEach去重

数组的下标去重,原理就是当数组内当前元素的下标与自身的下标相等时,表示该数组里面只有一个当前元素,当下标不相等时,表示不只一个当前元素

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不会改变原数组,会返回一个新的过滤后的数组,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排序

使用数组的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;
}

bash shell pre-commit eslint prettier

You can use Prettier and Eslint with a pre-commit tool. This can re-format your files that are marked as "staged" via git add before you commit.

Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言;Shell 脚本(shell script),是一种为 shell 编写的脚本程序,业界所说的 shell 通常都是指 shell 脚本;

shell环境,Shell 编程跟 java、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。Linux 的 Shell 种类众多,常见的有

  1. Bourne Shell(/usr/bin/sh或/bin/sh)
  2. Bourne Again Shell(/bin/bash)
  3. C Shell(/usr/bin/csh)
  4. K Shell(/usr/bin/ksh)
  5. Shell for Root(/sbin/sh)

这次要学习的是Bash,也就是 Bourne Again Shell,由于易用和免费,Bash 在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的 Shell;在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash

shell语法

#! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序,如#!/bin/sh or #!/bin/bash or #!/bin/node

1 运行 Shell 脚本有两种方法

第一种是作为可执行程序,需要注意的是,一定要写成 ./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

2 shell变量

定义变量时,变量名不加美元符号,变量名和等号之间不能有空格,变量名跟js的变量名一样,不能以数字开头等;name="jack"

使用变量时,需要在变量名前加$,变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界;变量能过被重新赋值;

name="jack" 
echo $name
echo ${name}

只读变量,在变量名前加readonly关键字

name="rose"
readonly name

删除变量,使用unset操作符,不能删除只读变量

name="rose"
unset name
echo $name // 为空

3 shell 字符串

字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号,也可以不用引号。

单引跟双引的区别:单引号字符串中的变量是无效的,双引号中才可以;双引号里可以出现转义字符,单引号不行;建议使用双引

获取字符串的长度${#name}
截取字符串${#name:1:3}

4 shell 数组

bash支持一维数组(不支持多维数组),并且没有限定数组的大小,通过下标来取值,下标从0开始

定义数组:数组名=(值1 值2 ... 值n)
读取数组:${数组名[下标]},使用 @ 符号可以获取数组中的所有元素
获取数组的长度:length=${#array_name[@]} or length=${#array_name[*]}

arr=(1 3 5 7)
echo ${arr[1]} ${#arr[@]}

5 注释

单行注释:#
多行注释::<<EOF EOF or :<<! !

6 shell传递参数

我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……

获取第n个参数$n
获取参数的个数 $#
获取所有参数的字符串 $*
获取最后命令的退出状态 $? 0表示没有错误,其它表示有错误

$* 与 $@ 区别,只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 "1 2 3"(传递了一个参数),而 "@" 等价于 "1" "2" "3"(传递了三个参数)

echo $1 $2 $3 $# $* $?

7 shell运算符

算数运算符 + - * / = == != 针对的是数字
关系运算符 -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

8 echo

echo 重定向到文件 也就是把内容输出到某个位置
echo "it a test" > myfile.txt

echo 显示普通字符串的时候可以省略引号

9 test命令

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

10 流程语句

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

11 输入输出重定向

命令 说明
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

jQuery实现loading

//页面的加载完毕可以使用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;

待总结清单

  1. 怎么判断css动画开始与结束,怎样去优化css动画性能;

  2. 为什么要限制http请求中url的长度;

  3. http请求头内包含哪些字段,各有什么作用;

  4. 详解http

  5. 手写promise

  6. 如果递归调用解析多层嵌套的数组;

  7. 弄清webpack的pluging机制及loader机制,并写一个pluging与loader

  8. 实现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');
66

第{{checkPoint * 1}}关

共10关

开始闯关
  • 错题库

  • PK场

  • 终极考核室

排行榜

<script> import card from '@/components/card'; export default { data() { return { motto: 'Hello World', userInfo: {}, checkPoint: 1, }; }, components: { card, }, computed: { leftRotate() { return this.deg > 180 ? `rotate(${this.deg - 180}deg)` : 0; }, rightRotate() { return this.deg > 180 ? 'rotate(180deg)' : `rotate(${this.deg}deg)`; }, deg() { return (this.checkPoint / 10) * 360; }, }, methods: { bindViewTap() { const url = '../logs/main'; wx.navigateTo({ url }); }, getUserInfo() { // 调用登录接口 wx.login({ success: () => { wx.getUserInfo({ success: (res) => { this.userInfo = res.userInfo; }, }); }, }); }, clickHandle(msg, ev) { console.log('clickHandle:', msg, ev); }, }, created() { // 调用应用实例的方法获取全局数据 this.getUserInfo(); }, }; </script> <style scoped lang="less"> @import (reference) '../../assets/less/index.less'; @import '../../assets/iconfont/iconfont.wxss'; .userinfo { display: flex; width: 100%; flex: 1 0 auto; flex-direction: column; align-items: flex-start; .userinfo-avatar { width: 64px; height: 64px; margin: 10px; border-radius: 50%; } div { font-size: 16px; width: 64px; margin-left: 10px; text-align: center; } i { font-size: 30px; } } .progress-wrap { flex: 1 0 240px; position: relative; width: 100%; .progress-circle { width: 200px; height: 200px; position: absolute; border-radius: 50%; background: @color-progress; top: 0; left: 50%; transform: translate(-50%, 0); .pie_left, .pie_right { width: 200px; height: 200px; position: absolute; top: 0; left: 0; } .left, .right { display: block; width:200px; height:200px; background:@color-shallow-gray; border-radius: 50%; position: absolute; top: 0; left: 0; transform: rotate(0deg); } .pie_right, .right { clip:rect(0,auto,auto,100px); } .pie_left, .left { clip:rect(0,100px,auto,0); } .mask { width: 160px; height: 160px; border-radius: 50%; left: 20px; top: 20px; background: @Color-White; position: absolute; text-align: center; line-height: 160px; font-size: 16px; } } .progress-totle { position: absolute; bottom: 36%; left: 42%; color: @color-font-disabled; } .progress-button { position: absolute; bottom: 0; left: 50%; transform: translate(-50%, 0); font-size: 20px; font-weight: bold; cursor: pointer; } } .column { flex: 1 0 auto; width: 100%; display: flex; padding-top: 30px; li { flex: 1 0 33.3%; flex-direction: column; align-items: center; text-align: center; font-size: 16px; img { width: 100px; height: 100px; } i { font-size: 60px; } } } .rank { flex: 1 0 auto; width: 100%; flex-direction: column; text-align: center; font-size: 16px; padding-bottom: 30px; img { width: 50px; height: 50px; } i { font-size: 40px; } } </style> <script> export default { created() { // 调用API从本地缓存中获取数据 const logs = wx.getStorageSync('logs') || []; logs.unshift(Date.now()); wx.setStorageSync('logs', logs); console.log('app created and cache logs by setStorageSync'); }, }; </script> <style lang="less"> @import (reference) './assets/less/index.less'; /* Blue #20A0FF Success #13CE66 Warning #F7BA2A Danger #FF4949 Black #1F2D3D Light Black #324057 Extra Light Black #475669 Dark White #F9FAFC Extra Light Gray #d1dbe5 */ .container { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: space-between; box-sizing: border-box; background-color: @color-body-bg; color: @color-font1; overflow: hidden; } /* this rule will be remove */ * { transition: width 2s; -moz-transition: width 2s; -webkit-transition: width 2s; -o-transition: width 2s; } page { height: 100%; } </style>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.