手撕代码

吴龙淼 写于 2020-10-25

promise 方法实现

// all实现
function myPromiseAll(httpArr){
let result = []
let count = 0
return new Promise((resolve,reject)=>{
  for(let i=0;i<httpArr.length;i++){
    Promise.resolve(httpArr[i]).then((res)=>{
      result[i]=res
      count++
      if(count===httpArr.length){
        resolve(result)
      }
    }).catch((err)=>{
      return reject(err)
    })
  }
})
}


// allSettled实现
function myPromiseAllSettled(httpArr){
let result = []
let count = 0
return new Promise((resolve,reject)=>{
  for(let i=0;i<httpArr.length;i++){
    Promise.resolve(httpArr[i]).then((res)=>{
      result[i]=res
      count++
      if(count===httpArr.length){
        resolve(result)
      }
    }).catch((err)=>{
      count++
      result[i]= {
          status: 'rejected',
          reason: err
        }
      if(count===httpArr.length){
        resolve(result)
      }
    })
  }
})
}


// race实现
function myPromiseRace(httpArr){
return new Promise((resolve,reject)=>{
  for(let i=0;i<httpArr.length;i++){
    Promise.resolve(httpArr[i]).then((res)=>{
      return resolve(res)
    }).catch((err)=>{
      return reject(err)
    })
  }
})
}


// resolve实现
function myPromiseResolve(httpArr){
  return new Promise((resolve) => {
    resolve(httpArr)
  })
}


// reject实现
function myPromiseReject(httpArr){
  return new Promise((resolve,reject) => {
    reject(httpArr)
  })
}


// promise实现
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function myPromise(executor) {
    let self = this;
    self.status = PENDING;
    self.onFulfilled = [];//成功的回调
    self.onRejected = []; //失败的回调

    function resolve(value) {
        if (self.status === PENDING) {
            self.status = FULFILLED;
            self.value = value;
            self.onFulfilled.forEach(fn => fn());
        }
    }

    function reject(reason) {
        if (self.status === PENDING) {
            self.status = REJECTED;
            self.reason = reason;
            self.onRejected.forEach(fn => fn());
        }
    }

    try {
        executor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

并发控制

function limitRequest(urls = [], limit = 3) {
  return new Promise((resolve, reject) => {
    const len = urls.length
    let count = 0

    // 同时启动limit个任务
    while (limit > 0) {
      start()
      limit -= 1
    }

    function start() {
      const url = urls.shift() // 从数组中拿取第一个任务
      if (url) {
        axios.post(url).then(res => {
          // todo
        }).catch(err => {
          // todo
        }).finally(() => {
          if (count == len - 1) {
            // 最后一个任务完成
            resolve()
          } else {
            // 完成之后,启动下一个任务
            count++
            start()
          }
        })
      }
    }

  })
}

防抖

// 非立即执行
function debounce(fun,time) {
    let timeout; // 存放定时器
    return function () {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            fun()
        }, time);
    };
}
// 触发立即执行一次
function debounce(func,wait) {
    let timeout;
    return function () {
        if (!timeout) func()
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            timeout = null;
        }, wait)
    }
}

节流

// 非立即执行
function throttle(fun, time) {
    let timeout=null;
    return function() {
        if (timeout===null) {
            timeout = setTimeout(() => {
                timeout = null;
                fun()
            }, time);
        }

    };
}
// 立即执行
function throttle(fun, time) {
    let timeout=null;
    let doNow = !timeout
    return function() {
        if (doNow) {
                doNow = null
                fun()
        }
        if (timeout===null) {
            timeout = setTimeout(() => {
                timeout = null;
                fun()
            }, time);
        }
    };
}

setTimeout 实现 setInterval

function mySetInterval(fn, millisec){
  function my_interval(){
    fn();
    setTimeout(my_interval, millisec);
  }
  setTimeout(my_interval, millisec)
}

jsonp 实现

var newscript = document.createElement('script');
newscript.src = 'https://www.adb.com?callback=fn'
document.body.appendChild(newscript);
//要跨域的脚本
function fn(data) {
  console.log(data);
}

JSON.stringify&&parse

JSON.parse('{"p": 5}', function (k, v) {
    if(k === '') return v;     // 如果到了最顶层,则直接返回属性值,
    return v * 2;              // 否则将属性值变为原来的 2 倍。
});

JSON.stringify({p: 5,a:4,rg:'fd'},['p','a'] || null || function (k, v) {
    if(!k) return v
    return v * 2+"test";
});

快速排序

// 快排 小到大
function quickSort(arr,left,right) {
if(left>right) return
let l=left,r=right,baseValue=arr[l]
while (l<r) {
    while (l<r && arr[r]>=baseValue) {
        r--
    }
    while (l<r && arr[l]<=baseValue) {
        l++
    }
    if(l<r){
        let temp = arr[r]
        arr[r] = arr[l]
        arr[l] = temp
    }
}
arr[left] = arr[l]
arr[l] = baseValue
quickSort(arr,l+1,right)
quickSort(arr,left,l-1)
return arr
}

归并排序

function quickSort(arr) {

if (arr.length < 2) return arr

let baseIndex = Math.floor(arr.length / 2);

let base = arr.splice(baseIndex, 1)[0];

let left = [];

let right = [];

for (let i = 0; i < arr.length; i++){

  if (arr[i] < base) {

    left.push(arr[i]);

  } else {

    right.push(arr[i]);

  }

}

return quickSort(left).concat([base], quickSort(right));

}

常用正则

数字格式化(三位添加,)
str.replace(/\B(?=(\d{3})+(?!\d))/g, ',')

首尾去空字符
str.replace(/^\s+|\s+$/g,'')

url参数格式化
str.match(/(?<=[?]).*/)[0].match(/.*(?=[&])/g)
str.match(/(?<=[?]).*/)[0].match(/(?<=[&]).*/g)

首字母大写
str.replace(/\w+/g,(str)=>str.slice(0,1).toUpperCase()+str.slice(1))

相邻字符去重
str.replace(/(\w{1})(\1)+/g, '$1');

多余字符省略 20
str.replace(/(?<=.{20}).*$/,'...')

深拷贝

function deepClone (obj){
    if (obj===undefined || obj===null) return obj;
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (typeof obj !== 'object') return obj;
    let cloneObj = new obj.constructor;
    for (const key in obj) {
        cloneObj[key] = deepClone(obj[key]);
    }
    return cloneObj;
}

数组展平

arr.flat(Infinity);

const flatten = (arr) => {
  let result = [];
  arr.forEach((item, i, arr) => {
    // 若为数组,递归调用 faltten,并将结果与result合并
    if (Array.isArray(item)) {
      result = result.concat(flatten(item));
    } else {
      result.push(arr[i])
    }
  })
  return result;
};

原生 ajax

let xhr = new XMLHttpRequest()
xhr.open('GET', url, false);
xhr.setRequestHeader('Accept', 'application/json');
xhr.onreadystatechange = function() {
    if (xhr.readyState !== 4) return;
    if (xhr.status === 200 || xhr.status === 304) {
        resolve(xhr.responseText);
    } else {
        reject(new Error(xhr.responseText));
    }
 }
xhr.send();

函数柯里化

function sum(...args1) {
  // 求和
  let x = args1.reduce((sum, next) =>sum+next)
  return function(...args2) {
    if (args2.length == 0) return x;
    // 求和
    let y = args2.reduce((sum, next) =>sum+next)
    return sum(x+y)
  }
}

懒加载

// 假设img元素是body的直接子元素
function lazyload(){
  var imgs = document.querySelectorAll('img');
  var flag= 0;//避免每次都从第一个元素开始遍历
	function lazyload(imgs){
		var h = window.innerHeight; // 可视区域
		var s = document.body.scrollTop; // 滚动条宽高
		for(let i=flag;i<imgs.length;i++){
      //滚动高度 > 相对父元素顶部的高度
			if ((h+s)>imgs[i].offsetTop && imgs[i].getAttribute("src") == "") {
          imgs[i].src = imgs[i].getAttribute("data-src");
          flag=i+1;
			}
		}
	}

window.addEventListener('scroll',throttle(lazyload(imgs),5*1000),{captrue:false})
}

虚拟列表

定高
<template>
  <div ref="list" class="infinite-list-container" @scroll="scrollEvent($event)">
    <div class="infinite-list-phantom" :style="{ height: listHeight + 'px' }">
    </div>
    <div class="infinite-list" :style="{ transform: getTransform }">
      <div ref="items"
        class="infinite-list-item"
        v-for="item in visibleData"
        :key="item.id"
        :style="{ height: itemSize + 'px',lineHeight: itemSize + 'px' }"
      ></div>
    </div>
  </div>
</template>

<script>
export default {
  name:'VirtualList',
  props: {
    //所有列表数据
    listData:{
      type:Array,
      default:()=>[]
    },
    //每项高度
    itemSize: {
      type: Number,
      default:200
    }
  },
  computed:{
    //列表总高度
    listHeight(){
      return this.listData.length * this.itemSize;
    },
    //可显示的列表项数
    visibleCount(){
      return Math.ceil(this.screenHeight / this.itemSize)
    },
    //偏移量对应的style
    getTransform(){
      return `translate3d(0,${this.startOffset}px,0)`;
    },
    //获取真实显示列表数据
    visibleData(){
      return this.listData.slice(this.start, Math.min(this.end,this.listData.length));
    }


  // 定义缓冲区
    <!-- aboveCount(){
      return Math.min(this.start,this.bufferScale * this.visibleCount)
    },
    belowCount(){
      return Math.min(this.listData.length - this.end,this.bufferScale * this.visibleCount);
    },
    visibleData(){
      let start = this.start - this.aboveCount;
      let end = this.end + this.belowCount;
      return this.listData.slice(start, end);
    } -->
  },
  mounted() {
    this.screenHeight = this.$el.clientHeight;
    this.start = 0;
    this.end = this.start + this.visibleCount;
  },
  data() {
    return {
      //可视区域高度
      screenHeight:0,
      //偏移量
      startOffset:0,
      //起始索引
      start:0,
      //结束索引
      end:null,
    };
  },
  methods: {
    scrollEvent() {
      //当前滚动位置
      let scrollTop = this.$refs.list.scrollTop;
      //此时的开始索引
      this.start = Math.floor(scrollTop / this.itemSize);
      //此时的结束索引
      this.end = this.start + this.visibleCount;
      // 实际应用场景结合懒加载,下拉到底部加载更多
      if(this.end===this.listData.at(-1)) fetch().then((data)=>{
        this.listData.push(data)
        })
      //此时的偏移量
      this.startOffset =this.start*this.itemSize   Math.floor(scrollTop)
    }
  }
};
</script>
<style scoped>
.infinite-list-container {
  height: 100%;
  overflow: auto;
  position: relative;
  -webkit-overflow-scrolling: touch;
}

.infinite-list-phantom {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  z-index: -1;
}

.infinite-list {
  left: 0;
  right: 0;
  top: 0;
  position: absolute;
  text-align: center;
}

.infinite-list-item {
  padding: 10px;
  color: #555;
  box-sizing: border-box;
  border-bottom: 1px solid #999;
}
</style>
不定高
// 预设高度,先渲染再根据真实高度替换
<template>
  <div ref="list" :style="{height}" class="infinite-list-container" @scroll="scrollEvent($event)">
    <div ref="phantom" class="infinite-list-phantom"></div>
    <div ref="content" class="infinite-list">
      <div
        class="infinite-list-item"
        ref="items"
        :id="item._index"
        :key="item._index"
        v-for="item in visibleData"
      >
        <slot ref="slot" :item="item.item"></slot>
      </div>
    </div>
  </div>
</template>


<script>
export default {
  props: {
    //所有列表数据
    listData: {
      type: Array,
      default: () => []
    },
    //预估高度
    estimatedItemSize: {
      type: Number,
      required: true
    },
    //容器高度 100px or 50vh
    height: {
      type: String,
      default: "100%"
    }
  },
  computed: {
    _listData() {
      return this.listData.map((item, index) => {
        return {
          _index: `_${index}`,
          item
        };
      });
    },
    visibleCount() {
      return Math.ceil(this.screenHeight / this.estimatedItemSize);
    },
    visibleData() {
      return this._listData.slice(this.start, this.end);
    }
  },
  created() {
    this.initPositions();
  },
  mounted() {
    this.screenHeight = this.$el.clientHeight;
    this.start = 0;
    this.end = this.start + this.visibleCount;
  },
  updated() {
    this.$nextTick(function() {
      if (!this.$refs.items || !this.$refs.items.length) {
        return;
      }
      //获取真实元素大小,修改对应的尺寸缓存
      this.updateItemsSize();
      //更新列表总高度
      let height = this.positions[this.positions.length - 1].bottom;
      this.$refs.phantom.style.height = height + "px";
      //更新真实偏移量
      this.setStartOffset();
    });
  },
  data() {
    return {
      //可视区域高度
      screenHeight: 0,
      //起始索引
      start: 0,
      //结束索引
      end: 0
    };
  },
  methods: {
    initPositions() {
      this.positions = this.listData.map((d, index) => ({
        index,
        height: this.estimatedItemSize,
        top: index * this.estimatedItemSize,
        bottom: (index + 1) * this.estimatedItemSize
      }));
    },
    //获取列表起始索引
    getStartIndex(scrollTop = 0) {
      //二分法查找
      return this.binarySearch(this.positions, scrollTop);
    },
    //二分法查找
    binarySearch(list, value) {
      let start = 0;
      let end = list.length - 1;
      let tempIndex = null;
      while (start <= end) {
        let midIndex = parseInt((start + end) / 2);
        let midValue = list[midIndex].bottom;
        if (midValue === value) {
          return midIndex + 1;
        } else if (midValue < value) {
          start = midIndex + 1;
        } else if (midValue > value) {
          if (tempIndex === null || tempIndex > midIndex) {
            tempIndex = midIndex;
          }
          end = end - 1;
        }
      }
      return tempIndex;
    },
    //获取列表项的当前尺寸
    updateItemsSize() {
      let nodes = this.$refs.items;
      nodes.forEach(node => {
        let rect = node.getBoundingClientRect();
        let height = rect.height;
        let index = +node.id.slice(1);
        let oldHeight = this.positions[index].height;
        let dValue = oldHeight - height;
        //存在差值
        if (dValue) {
          this.positions[index].bottom = this.positions[index].bottom - dValue;
          this.positions[index].height = height;

          for (let k = index + 1; k < this.positions.length; k++) {
            this.positions[k].top = this.positions[k - 1].bottom;
            this.positions[k].bottom = this.positions[k].bottom - dValue;
          }
        }
      });
    },
    //获取当前的偏移量
    setStartOffset() {
      let startOffset =
        this.start >= 1 ? this.positions[this.start - 1].bottom : 0;
      this.$refs.content.style.transform = `translate3d(0,${startOffset}px,0)`;
    },
    //滚动事件
    scrollEvent() {
      //当前滚动位置
      let scrollTop = this.$refs.list.scrollTop;
      //此时的开始索引
      this.start = this.getStartIndex(scrollTop);
      //此时的结束索引
      this.end = this.start + this.visibleCount;
      //此时的偏移量
      this.setStartOffset();
    }
  }
};
</script>
<style scoped>
.infinite-list-container {
  overflow: auto;
  position: relative;
  -webkit-overflow-scrolling: touch;
}

.infinite-list-phantom {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  z-index: -1;
}

.infinite-list {
  left: 0;
  right: 0;
  top: 0;
  position: absolute;
}

.infinite-list-item {
  padding: 5px;
  color: #555;
  box-sizing: border-box;
  border-bottom: 1px solid #999;
  /* height:200px; */
}
</style>

storage 过期

Storage.prototype.setStorageWithAge = (key, value, age) => {
    if (isNaN(age) || age < 1) throw new Error("age must be a number");
    const obj = {
        data: value, //存储值
        time: Date.now(), //存值时间戳
        maxAge: age, //过期时间
    };
    localStorage.setItem(key, JSON.stringify(obj));
};

Storage.prototype.getStorageWithAge = key => {
    const { data, time, maxAge } = JSON.parse(localStorage.getItem(key));
    if (time + maxAge < Date.now()) {
        localStorage.removeItem(key);
        return undefined;
    }
    return data;
};

对象池

function objectPools() {
    let objPool = [];
    return {
        create: function() {
            if (objPool.length === 0) {
                let newDiv = document.createElement("div");
                document.body.appendChild(newDiv);
                console.log('div created')
                return newDiv;
            } else {
                return objPool.shift();
            }
        },
        recover: function(obj) {
            return objPool.push(obj);
        }
    }
}
let objPool = objectPools()
// 创建两个div
let arr = [];
for (let i = 0, str=['A', 'B'];i<str.length; i++) {
    let toolTip = objPool.create();
    toolTip.innerHTML = str;
    arr.push(toolTip);
}
// 回收进对象池
arr.forEach(function(toolTip) {
    objPool.recover(toolTip);
});
// 再创建6个div,先从对象池中取出对象,再创建
    ['A', 'B', 'C', 'D', 'E', 'F'].forEach((str)=> {
        let toolTip = objPool.create();
        toolTip.innerHTML = str;
    });

2 div created
4 div created