Giter VIP home page Giter VIP logo

blog's People

Contributors

54leibo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

blog's Issues

保持子元素高度与父元素一直(未设置高度)

  • 背景:有时候我们想设置父元素min-height属性,而并不想设置其height属性,此时需要如何保持子元素和父元素高度一致呢
  • 方法1:
.parent {
 display: flex;
}
  • 方法2:
.parent {
 position: relative;
}
.child {
 position: absolute;
 top: 0;
 bottom: 0; // 或者 height: 100%;
}

全屏截图实现原理

  • Electron中创建无边框透明窗口(nw 也可)
  • 获取屏幕视频流 navigator.getUserMedia
  • video 接收视频流
  • canvas 绘制 video,转成图片

利用stream为node-fetch实现文件上传下载进度条功能

  • 背景:Electron环境;接口请求node实现;对于上传需要将完整的文件buffer转化为流实现上传进度提示功能
  • 依赖
    • bufferhelper
    • progress-stream:生成流的处理进度
    {
        percentage: 9.05, // 下载百分比
        transferred: 949624, // 已传输数据(单位byte)
        length: 10485760, // 数据总长度
        remaining: 9536136, // 剩余未下载
        eta: 42, // 预计剩余时间
        runtime: 3, // 已经消耗时间
        delta: 295396, // 未知
        speed: 949624 // 处理速度
    }
    
    • stream:node 原生模块
  • stream to buffer 实现代码
const BufferHelper = require('bufferhelper');
const progress = require('progress-stream');
const { Duplex } = require('stream');

function _streamToBuffer(stream) {
  return new Promise((resolve, reject) => {
    const bufferHelper = new BufferHelper();
    stream.on('error', reject);
    stream.on('data', (chunk) => bufferHelper.concat(chunk));
    stream.on('end', () => resolve(bufferHelper.toBuffer()));
  });
}

const bufferStream = // buffer stream
const bufferSize = // buffer total size
    
const processGenerator = progress({
      length: buffer.byteLength,
      time: 100,
    }, progressInfo => {
      // process progessInfo
    });

_streamToBuffer(bufferStream.pipe(processGenerator))
  .then(() => {
      // success process
  })
  .catch((e) => {
    // error process
  });
  • buffer to stream 实现代码
const BufferHelper = require('bufferhelper');
const progress = require('progress-stream');
const { Duplex } = require('stream');

function _bufferToStream(buffer, chunkCount = 100) {
  if (!(buffer instanceof Buffer)) {
    throw new Error(`buffer must be an instance of Buffer, get ${typeof buffer}`)
  }

  if (typeof chunkCount !== 'number') {
    throw new Error(`chunkCount must be a number, get ${typeof chunkCount}`)
  }

  const size = buffer.byteLength;
  let index = 0;
  const chunkSize = Math.ceil(size / chunkCount);

  const stream = new Duplex();
  stream._read = () => {
    if (index < chunkCount) {
      stream.push(buffer.slice(index * chunkSize, (index + 1) * chunkSize));
      // eslint-disable-next-line no-plusplus
      index++
    } else if(index === chunkCount) {
      stream.push(buffer.slice(index * chunkCount, size + 1));
    } else {
      stream.push(null);
    }
  };

  return stream;
}

const buffer = // buffer
const processGenerator = progress({
    length: buffer.byteLength,
    time: 100, /* ms */
  }, progressInfo => {
    options.onProgress(progressInfo);
  });

  try {
    const bufferStream = _bufferToStream(buffer, 100).pipe(processGenerator);
  } catch (e) {
    // error process
  }

数组、对象相互嵌套递归查找

/**
 * 深度递归搜索
 * @param {Array} arr 你要搜索的数组
 * @param {Function} condition 回调函数,必须返回谓词,判断是否找到了。会传入item参数
 * @param {String} children 子数组的key
 */
export function deepFind(arr, condition, children) {
  // 即将返回的数组
  const main = []

  // 开始轮询
  ;(function poll(arr) {
    // 如果传入非数组
    if (!Array.isArray(arr)) return

    // 遍历数组
    for (let i = 0; i < arr.length; i++) {
      // 获取当前项
      const item = arr[i]

      // 检验是否已经找到了
      const isFind = condition && condition(item) || false

      // 如果已经找到了
      if (isFind) {
        main.push(item)

        // 如果存在children,那么深入递归
      } else if (children && item[children] && item[children].length) {
        poll(item[children])
      }
    }
  })(arr, 0)

  // 返回最终数组
  return main
}

// example
    const fuck = [
      {
        'label': '占用道路问题',
        'value': 31,
        'children': [
          {
            'label': '经营占道',
            'value': 35,
            'children': [
              { 'label': '店外经营占道', 'value': 40, 'children': null },
              {
                'label': '流动摊贩占道',
                'value': 41,
                'children': null
              }
            ]
          },
          {
            'label': '垃圾占道',
            'value': 36,
            'children': [
              { 'label': '生活垃圾', 'value': 42, 'children': null },
              {
                'label': '建筑垃圾',
                'value': 43,
                'children': null
              },
              { 'label': '工业垃圾', 'value': 44, 'children': null }
            ]
          },
          {
            'label': '车辆占道',
            'value': 37,
            'children': [
              { 'label': '机动车占道', 'value': 45, 'children': null },
              {
                'label': '非机动车占道',
                'value': 46,
                'children': null
              }
            ]
          },
          { 'label': '霸占车位', 'value': 38, 'children': [] },
          { 'label': '其他占道', 'value': 39, 'children': [] }
        ]
      },
      {
        'label': '“两违”问题',
        'value': 32,
        'children': [
          {
            'label': '违法建筑',
            'value': 58,
            'children': [
              { 'label': '房屋违建', 'value': 61, 'children': null },
              {
                'label': '小区违建',
                'value': 62,
                'children': null
              },
              { 'label': '违建棚架', 'value': 63, 'children': null }
            ]
          },
          { 'label': '违法用地', 'value': 59, 'children': [] },
          { 'label': '其他违建', 'value': 60, 'children': [] }
        ]
      },
      {
        'label': '市容设施管理问题',
        'value': 33,
        'children': [
          { 'label': '道路损坏', 'value': 47, 'children': [] },
          {
            'label': '垃圾桶损坏',
            'value': 48,
            'children': []
          },
          { 'label': '下水道堵塞', 'value': 49, 'children': [] },
          {
            'label': '井盖损坏',
            'value': 50,
            'children': []
          },
          { 'label': '路灯损坏', 'value': 51, 'children': [] },
          {
            'label': '树木修剪',
            'value': 52,
            'children': []
          },
          { 'label': '水电气', 'value': 63, 'children': [] },
          {
            'label': '户外广告牌',
            'value': 54,
            'children': []
          },
          { 'label': '隔音屏损坏', 'value': 55, 'children': [] },
          {
            'label': '洒水车问题',
            'value': 56,
            'children': []
          },
          { 'label': '其他', 'value': 57, 'children': [] }
        ]
      },
      { 'label': '其他问题', 'value': 34, 'children': [] }
    ]

    const arr = deepFind(fuck, item => item.value === 63, 'children')
    console.log(20181115092957, arr) // [{"label":"违建棚架","value":63,"children":null},{"label":"水电气","value":63,"children":[]}]

Backbone源码解读 - Event模块

  • 需要实现的API列表
    • on object.on(event, callback, [context])别名: bind,绑定事件,参数依次为:绑定的事件名(字符串,多个事件使用空格隔开)、事件触发回调、回调中this指向(可选)
    • off object.off([event], [callback], [context])别名: unbind,事件解除绑定,参数依次为:解除绑定的事件名(可选,字符串,多个事件使用空格隔开)、解除绑定的回调函数(可选)、回调中this指向(可选),参数为解除的限定条件,无参数解除所有事件
    • once object.once(event, callback, [context]),同on,回调执行一次后自动解除绑定
    • trigger object.trigger(event, [*args]),触发绑定的事件,参数依次为:要触发的事件名、传递绑定回调的参数(可以传递多个)
    • listenTo A.listenTo(B, event, callback) A对象为B对象绑定事件,参数依次为:A对象(操控绑定和解绑)、B对象(触发事件)、事件名称(字符串,多个事件名使用空格)、事件触发回调
    • stopListening A.stopListening([B], [event], [callback]) A对象为B对象解除事件绑定,参数依次为:A对象、B对象(可选)、事件名(可选)、回调(可选),从第二个参数开始为限定条件,没有参数是意为解除A控制的所有事件绑定
    • listenToOnce A.listenToOnce(B, event, callback) 参考once 和 listenTo

Promise关键步骤实现


const isFunction = val => typeof val === 'function'

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

class MyPromise {
  constructor(handle) {
    // 保存Promise的状态和值
    this._status = PENDING
    this._value = undefined

    // 保存单个Promise多次调用then的回调
    this._fulfilledQueues = []
    this._rejectedQueues = []

    try {
      handle(this._resolve.bind(this), this._reject.bind(this))
    } catch (e) {
      this._reject(e)
    }
  }
  _resolve(val) {
    if(this._status !== PENDING) return

    const run = () => {
      const runFulfilled = (value) => {
        let cb
        this._status = FULFILLED
        this._value = val
        // 依次执行成功队列回调
        while (cb = this._fulfilledQueues.shift()) {
          cb(value)
        }
      }
      const runRejected = (value) => {
        let cb
        this._status = REJECTED
        this._value = val
        // 依次执行失败队列回调
        while (cb = this._rejectedQueues.shift()) {
          cb(value)
        }
      }

      // 传入Promise则由传入Promise的结果决定_status和_value
      if(val instanceof MyPromise) {
        val.then(runFulfilled, runRejected)
        return
      }

      runFulfilled(val)
    }

    // 模拟Promise在本次事件循环末执行
    setTimeout(run, 0)
  }
  _reject(val) {
    if(this._status !== PENDING) return

    // 不同于_resolve,即时是传入Promise也直接作为值传递给回调
    const run = () => {
      this._status = REJECTED
      this._value = val
      let cb
      // 依次执行失败队列回调
      while (cb = this._rejectedQueues.shift()) {
        cb()
      }
    }
    // 模拟Promise在本次事件循环末执行
    setTimeout(run, 0)
  }
  then(onFulFilled, onRejected) {
    const { _status, _value } = this
    return new MyPromise((onFulFilledNext, onRejectedNext) => {
      const fulFilled = (val) => {
        try {
          // onFulFilled 不是函数
          if(!isFunction(onFulFilled)) {
            onFulFilledNext(val)
            return
          }

          // onFulFilled 返回了Promise,则该Promise决定了then返回Promise的状态
          const res = onFulFilled(val)
          if(res instanceof MyPromise) {
            res.then(onFulFilledNext, onRejectedNext)
            return
          }

          // onFulFilled 返回普通值
          onFulFilledNext(res)
        } catch (err) {
          // onFulFilled 内执行报错
          onRejectedNext(err)
        }
      }
      const rejected = (val) => {
        try {
          // onRejected 不是函数
          if(!isFunction(onFulFilled)) {
            onRejectedNext(val)
            return
          }

          // onRejected 返回了Promise,则该Promise决定了then返回Promise的状态
          const res = onRejected(val)
          if(res instanceof MyPromise) {
            res.then(onFulFilledNext, onRejectedNext)
            return
          }

          // onRejected 返回普通值
          onRejectedNext(res)
        } catch (err) {
          // onRejected 内执行报错
          onRejectedNext(err)
        }
      }

      switch (_status) {
        // 当状态为pending时,将then方法回调函数加入执行队列等待执行
        case PENDING:
          this._fulfilledQueues.push(fulFilled)
          this._rejectedQueues.push(rejected)
          break
        // 当前状态已改变是,立即执行回调函数
        case FULFILLED:
          fulFilled(_value)
          break
        case REJECTED:
          rejected(_value)
          break
      }
    })
  }
  catch(onRejected) {
    return this.then(undefined, onRejected)
  }
  finally(onFinally) {
    // onFinally 不是函数
    if(!isFunction(onFinally)) {
      onFinally = () => {}
    }

    // 返回Promise且保持原Promise结果,onFinally返回结果并不重要
    // 暂时不对onFinally中报错进行处理,根据浏览器原生Promise处理,
    // 如果finally回调函数中产生报错,则该错误会被返回
    return this.then(
      value => MyPromise.resolve(onFinally()).then(() => value),
      error => MyPromise.resolve(onFinally()).then(() => {throw error})
        )
  }
  static resolve(val) {
    // 如果是Promise则直接返回
    if(val instanceof MyPromise) return val

    // 其它值则返回一个返回该值的已完成Promise
    return new MyPromise(resolve => resolve(val))
  }
  static reject(val) {
    // 不同于resolve,reject返回一个rejected的Promise,并把值直接返回
    return new MyPromise((undefined, rejected) => rejected(val))
  }
  static all(list) {
    if((list instanceof Array)) return
    // 当全部为fulfilled或有一个为rejected时结束,返回一个Promise
    // 若全部为fulfilled,则值为promiseArr中promise返回结果的按次序排列的数组
    // 若有一个为rejected,则值为发生rejected的参数
    const results = []
    return MyPromise((resolve, reject) => {
      for(let [i, v] of list.entries()) {
        // 如果值不是Promise则构建一个
        this.resolve(v).then((val) => {
          results[i] = val
          if(results.length === list.length) {
            resolve(results)
          }
        }, err => reject(err))
      }
    })
  }
  static race(list) {
    // 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
    return new MyPromise((resolve, reject) => {
      for (let p of list) {
        this.resolve(p).then(res => {
          resolve(res)
        }, err => {
          reject(err)
        })
      }
    })
  }
}

引用(建议先看这篇文章,本实现只是增加了一些小改动)

require.context: 创建模块上下文

  • 语法:const req= require.context(directory, useSubdirectories = false, regExp = /^.//);

    • directory 加载的目录
    • useSubdirectories 是否加载子目录
    • regExp 文件名匹配正则
    • 返回值:req为一个require函数,且包含三个属性(resolve, keys, id)
      • resolve:是一个函数,它返回 request 被解析后得到的模块 id
      • keys:也是一个函数,它返回一个数组,由所有可能被此 context module 处理的请求组成。
      • id:是 context module 里面所包含的模块 id.
  • 例子1:多文件便捷载入(例子2有展示)

  • 例子2:多文件便捷载入之SVG图标解决方案(elementUI使用的该方法,除此之外还可以使用Unicode、font-class详见参考资料)

    • 说明:除了利用require.context还利用了svg-sprite-loader及svg sprite相关知识
目录说明:
svg文件夹存放svg图标并与index.js同目录存放

index.js:
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg component

// register globally
Vue.component('svg-icon', SvgIcon)
const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)

SvgIcon组件:
<template>
  <svg :class="svgClass" aria-hidden="true">
    <use :href="iconName" />
  </svg>
</template>

<script>
export default {
  name: 'SvgIcon',
  props: {
    iconClass: {
      type: String,
      required: true
    },
    className: {
      type: String,
      default: ''
    }
  },
  computed: {
    iconName() {
      return `#icon-${this.iconClass}`
    },
    svgClass() {
      if (this.className) {
        return 'svg-icon ' + this.className
      } else {
        return 'svg-icon'
      }
    }
  }
}
</script>

<style scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}
</style>

获取中文首字母(常用于通讯录)

/**
 * Get chinese first spell letter 
 *
 * @params {String} c: chinese
 * @return {String} letter
*
function getChineseFirstSpellLetter(c) {
  const classify = 'ABCDEFGHJKLMNOPQRSTWXYZ';
  const boundaryChar = '驁簿錯鵽樲鰒餜靃攟鬠纙鞪黁漚曝裠鶸蜶籜鶩鑂韻糳';
  let idx = -1;
  if(!String.prototype.localeCompare) {
    throw Error('String.prototype.localeCompare not supported.');
  }
  if(/[^\u4e00-\u9fa5]/.test(c)) {
    return c;
  }
  for (let i = 0; i < boundaryChar.length; i++) {
    if(boundaryChar[i].localeCompare(c, 'zh-CN-u-co-pinyin') >= 0) {
      idx = i;
      break;
    }
  }
  return classify[idx];
}

数组项前移后移(用于排序)

/**
 * @arr: {Array} 操作数组
 * @startPos: {Number} 需要移动数据开始项
 * @length: {Number} 需要移动数据项数
 * @moveSteps: {Number} 需要移动位数, positive number move right, minus number move left
 * @return {Array}
 * */
      const moveArrayItem = (arr, startPos, length, moveSteps) => {
        if (!(arr instanceof Array)) {
          throw new Error(`arr can only be: Array, got:${Object.prototype.toString.call(arr)}`)
        }
        if (typeof startPos !== 'number') {
          if (startPos < 0) {
            throw new Error(`startPos can only be: positive integer, got:${startPos}`)
          }
          if (startPos.toString().indexOf('.') !== -1) {
            throw new Error(`startPos can only be: positive integer, got:${startPos}`)
          }

          throw new Error(`startPos can only be: Number, got:${startPos}`)
        }
        if (typeof length !== 'number') {
          if (length < 0) {
            throw new Error(`length can only be: positive integer, got:${length}`)
          }
          if (length.toString().indexOf('.') !== -1) {
            throw new Error(`startPos can only be: positive integer, got:${length}`)
          }

          throw new Error(`length can only be: Number, got:${length}`)
        }
        if (typeof moveSteps !== 'number') {
          throw new Error(`moveSteps can only be: Number, got:${length}`)
        }
        const originArrLen = arr.length
        const moveItems = arr.splice(startPos, length) || []

        if (moveSteps < 0) {
          moveSteps = Math.abs(moveSteps)
          arr.splice(startPos - moveSteps >= 0 ? startPos - moveSteps : 0, 0, ...moveItems)
        } else {
          arr.splice(startPos + moveSteps <= originArrLen - 1 ? startPos + moveSteps : originArrLen - 1, 0, ...moveItems)
        }

        return arr
      }

     // example
      const arr = [1, 2, 3, 4, 5, 6, 7]
      console.log(`move: ${arr}`, moveArrayItem(arr, 1, 2, 1)) // [1, 4, 2, 3, 5, 6, 7]

JavaScript实现一个简单队列,顺序执行异步任务

功能描述

  • QueueJob构造函数实例存在一个queue方法,该方法接收一个Promise;能够保证按照调用的顺序依次执行传入的callback,即使其中包含异步代码
class QueueJob {
  queue(callback) {
     const previous = this.pending || Promise.resolve()
     this.pending = previous.then(callback).catch(callback)
     // 利用闭包保存当前pending
     const current = this.pending
     current.then(() => {
             // this.pending 完成时,this.pending没有被覆盖
            if(this.pending === current) {
                delete this.pending
            }
      })
  }
}

var queueJob = new QueueJob()

queueJob.queue(() => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log('first')
			resolve()
		}, 2000)
	})
})

queueJob.queue(() => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log('second')
			resolve()
		}, 1000)
	})
})

queueJob.queue(() => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log('three')
			resolve()
		}, 500)
	})
})

// first
// second
// three

扩展

web中的文件上传和下载

文件上传

  • 传统形式上传(form 表单)
    • 表现:上传成功后跳转到 action属性 指定的地址
<form id="upload-form" action="upload.php" method="post" enctype="multipart/form-data" >
    <input type="file" id="upload" name="upload" /> <br />
    <input type="submit" value="Upload" />
</form>
  • iframe + form 表单:成功上传后可以避免页面刷新
html:
<form id="upload-form" action="upload.php" method="post" enctype="multipart/form-data" >
    <input type="file" id="upload" name="upload" /> <br />
    <input type="submit" value="Upload" />
</form>

js:
var form = $("#upload-form");

form.on('submit',function() {
  var seed = Math.floor(Math.random() * 1000);
  var id = "uploader-frame-" + seed;
  var callback = "uploader-cb-" + seed;
  var iframe = $('<iframe id="'+id+'" name="'+id+'" style="display:none;">');
  var url = form.attr('action');
  /*
  首先,它为表单添加target属性,指向动态插入的iframe窗口,这使得上传结束后,服务器将结果返回iframe窗口,所以当前页面就不会跳转了。其次,它在action属性指定的上传网址的后面,添加了一个参数,使得服务器知道回调函数的名称。这样就能将服务器返回的信息,从iframe窗口传到上层页面。
  */
  form.attr('target', id).append(iframe).attr('action', url + '?iframe=' + callback);
});


window[callback] = function(data){
    console.log('received callback:', data);
    iframe.remove(); //removing iframe
    form.removeAttr('target');
    form.attr('action', url);
    window[callback] = undefined; //removing callback
};

php(upload.php):
<script type="text/javascript">
    window.top.window['callback'](data);
</script>
  • ajax上传(借助 FormData对象)
if (window.FormData) {
  var formData = new FormData()
  // 建立一个upload表单项,值为上传的文件
  formData.append('upload', document.getElementById('upload').files[0])
  var xhr = new XMLHttpRequest()
  xhr.open('POST', $(this).attr('action'))
  // 定义上传完成后的回调函数
  xhr.onload = function () {
    if (xhr.status === 200) {
      console.log('上传成功')
    } else {
      console.log('出错了')
    }
  }
  xhr.send(formData)
}
  • ajax + 拖放上传
css:
#holder {
    border: 10px dashed #ccc;
    width: 300px;
    min-height: 300px;
    margin: 20px auto;
}
#holder.hover {
    border: 10px dashed #0c0;
}

html:
<div id="holder"></div>

js:
// 检查浏览器是否支持拖放上传。
if ('draggable' in document.createElement('span')) {
  var holder = document.getElementById('holder')
  holder.ondragover = function () {
    this.className = 'hover'
    return false
  }
  holder.ondragend = function () {
    this.className = ''
    return false
  }
  holder.ondrop = function (event) {
    event.preventDefault()
    this.className = ''
    var files = event.dataTransfer.files
    // do something with files
  }
}

文件下载

  • 设置 Http Header:Content-Disposition 消息头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。
  • Using a form element (Other than GET methods): form 元素,除了get方法
function downloadFile(actoinURL,filePath,fileName){
	var form = $("<form>");   
	$('body').append(form);
        form.attr('style','display:none');
        form.attr('target','');
        form.attr('method','post');
        form.attr('action',actoinURL);//下载文件的请求路径
        
        var input1 = $('<input>'); 
        input1.attr('type','hidden'); 
        input1.attr('name','filePath'); 
        input1.attr('value',filePath);
        form.append(input1);  
        var input2 = $('<input>'); 
        input2.attr('type','hidden'); 
        input2.attr('name','fileName'); 
        input2.attr('value',fileName);
        form.append(input2);
        
        form.submit();
  • a[download]: 带有 download 属性的a标签
    • 带有该属性的话浏览器将会下载它而不是跳转;如果download带有一个值它将作为默认文件名;对于文件名没有特别的要求/、\这两个符号会被转为下划线,同时每个系统会对文件命名有要求,浏览器会根据系统调整非法命名
    • 和 blob:URLs、data:URLs 一起使用,下载有 JavaScript 产生的数据,例如图片编辑软件产生的图片
    • 如果 HTTP header Content-Disposition 给了一个不同的 fileName, 优先使用 HTTP header Content-Disposition 指定的文件名
    • Content-Disposition 设置为了inline。谷歌浏览器依然会下载该文件,firefox则会采用 Content-Disposition 指定的方式处理(浏览器能展示则直接展示,无法展示则下载)
html:
<a id="download" href="">download</a>

js:
function downloadFile(fileName, content){
  var aLink = document.createElement('a');
  var blob = new Blob([content]);

  aLink.download = fileName;
  aLink.href = URL.createObjectURL(blob);
  aLink.click();
}
downloadFile('aa.txt', '123')
  • ajax 和 库FileSaver.js (原理同a[download])
    • 可以使用a标签的download来实现的
    • 特殊浏览器支持FileReader的,使用其构建URL,使用window.open或location.href来实现
    • 除了特殊的浏览器,使用URL.createObjectURL来构建对象URL,使用window.open或location.href来
  • ajax 和 库StreamSaver.js(利用 Service Workers 的 FetchEvent.respondWith(),构建一个 Content-Disposition返回)
  • 参考

接口请求失败重试机制

function _ajax() {
    return new Promise((resolve, reject) => {
    ...
    })
 }
/**
 * HTTP Error retry
 * 
 * @params {Object} options: ajax params
 * @params {Number | undefined} limitCount: retry max count
 * @params {Number | undefined} providedCount: current retry count
*/
  function _retry(options, limitCount, providedCount) {
    // set default value
    if(!limitCount) {
      limitCount = 3
    }
    if(!providedCount) {
      providedCount = 0
    }
    return _ajax(options)
      .catch(err => {
      // retry condition
        if(err.name === 'HTTPError' && providedCount < limitCount) {
          return new Promise(resolve => {
          // retry after 1 second
            setTimeout(() => {
              resolve(_retry(options, limitCount, providedCount++))
            }, 1000)
          })
        }
        throw err
      })
  }

二维码扫码登录实现原理

  • 连接websocket,接收uuid
  • 本地生成publicKey,结合uuid生成二维码
  • 扫码,手机确认
  • websocket接收登录凭据消息(账号、验证码),通过privateKey解密
  • 断开websocket,调用ajax发送登录凭据给后台,同时可接收后台返回信息

body滚动监听问题

document.body.onscroll = () => console.log('body onscroll') // 正常, 和window.onscroll相互覆盖
document.body.addEventListener('scroll', () => console.log('body addEventListener')) // 无效
document.onscroll = () => console.log('document onscroll') // 正常
document.addEventListener('scroll', () => console.log('document addEventListener')) // 正常
document.documentElement.onscroll = () => console.log('documentElement onscroll') // 无效
document.documentElement.addEventListener('scroll', () => console.log('documentElement addEventListener')) // 无效
window.onscroll = () => console.log('window onscroll') // 正常,和document.body.onscroll 相互覆盖
window.addEventListener('scroll', () => console.log('window addEventListener')) // 正常

说明: 
- 在滚动body时,document对scroll的监听也会被触发
- 这些感觉有点凌乱的API应该是浏览器历史遗留问题,平常的话监听document即可
- 测试环境(userAgenet):Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36

利用border制作三角形以及箭头

// 宽度和高度设置为0,再把另外三个border背景设置为transparent
#box{
 position: relative;
 width:0;
 height:0;
 border: 20px solid transparent;
 border-top-color:red;
 border-bottom-width:0;
}

// 如果需要空心三角形(箭头),利用before再叠加一个,正确设置其背景色,border宽度小一个像素,调整left和top值即可
#box: before {
 content: ' ';
 position: absolute;
 left: -19px;
 top: -21px;
 width:0;
 height:0;
 border: 19px solid transparent;
 border-top-color:#fff;
 border-bottom-width:0;
}

Transfer: ArrayBuffer、Base64、Hex、String

  • ArrayBuffer <=> String:
const _arrayBufferToString = arrayBuffer => 
    String.fromCharCode(...new Uint16Array(arrayBuffer))

const _stringToArrayBuffer = str => 
    Uint16Array.from(str, c => c.charCodeAt(0)).buffer

// example
const str = '123'
const buf = _stringToArrayBuffer(str)
console.log(str, _arrayBufferToString(buf))
  • ArrayBuffer <=> Base64
const _arrayBufferToBase64 = arrayBuffer => 
    btoa(String.fromCharCode(...new Uint16Array(arrayBuffer)))

const _base64ToArrayBuffer = base64 => 
    Uint16Array.from(atob(base64), c => c.charCodeAt(0)).buffer
    
// example
const base64 = window.btoa('123')
const buf = _base64ToArrayBuffer(base64)
console.log(base64, _arrayBufferToBase64(buf))
  • ArrayBuffer <=> Hex
const _arrayBufferToHex = (arrayBuffer) => 
    Array.prototype.map.call(new Uint16Array(arrayBuffer), x => ('00' + x.toString(16)).slice(-2)).join('')

const _hexToArrayBuffer = (str) => {
    const len = str.length
    const arrayBuffer = new ArrayBuffer(len)
    const uint16 = new Uint16Array(arrayBuffer)
    let b;
    
    for (let i=0, j=0; i<len; i+=2) {
        b = parseInt(str.substring(i, i+2), 16)
        uint16[j++] = b
    }
    
    return arrayBuffer;
}

// example
const hex = "39b2abc192db9bc9175de7e5f9e7ef08"
const buffer = _hexToArrayBuffer(hex);
console.log(hex, _arrayBufferToHex(buffer), hex === _arrayBufferToHex(buffer))

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.