# 工具方法

# load

function loadCss(url, callback) {
  return new Promise(resolve => {
    var node = document.createElement('link')
    node.type = 'text/css'
    node.rel = 'stylesheet'
    node.href = url
    node.onerror = node.onload = function() {
      resolve()
      isFunction(callback) && callback()
    }
    document.head.appendChild(node)
  })
}

function loadJs(url, callback, attr) {
  if (!isFunction(callback)) {
    attr = callback
    callback = null
  }
  return new Promise((resolve, reject) => {
    var script = document.createElement('script')
    script.type = 'text/javascript'
    if (isObject(attr)) {
      Object.keys(attr).forEach(key => {
        if (attr.hasOwnProperty(key)) {
          script.setAttribute(key, attr[key])
        }
      })
    }
    if (script.readyState) {
      script.onreadystatechange = function() {
        if (script.readyState == 'loaded' || script.readyState == 'complete') {
          script.onreadystatechange = null
          isFunction(callback) && callback()
          resolve()
        }
      }
    } else {
      script.onload = function() {
        isFunction(callback) && callback()
        resolve()
      }
    }
    script.onerror = function() {
      reject()
    }
    script.src = url
    document.head.appendChild(script)
  })
}

# 加减乘除

/**
 * 加法
 * @param  {...any} n
 */
function add(...n) {
  return n.reduce((ji, item) => {
    let l1 = (ji.toString().split('.')[1] || '').length
    let l2 = (item.toString().split('.')[1] || '').length
    let l = Math.pow(10, Math.max(l1, l2))
    return (ji * l + item * l) / l
  })
}

/**
 * 乘法
 * @param  {...any} n
 */
function mul(...n) {
  return n.reduce((ji, item) => {
    let n1 = (ji.toString().split('.')[1] || '').length
    let n2 = (item.toString().split('.')[1] || '').length
    return (ji * Math.pow(10, n1) * item * Math.pow(10, n2)) / Math.pow(10, n1 + n2)
  })
}

/**
 * 除法
 * @param  {...any} n
 */
function div(...n) {
  return n.reduce((ji, item) => {
    let n1 = (ji.toString().split('.')[1] || '').length
    let n2 = (item.toString().split('.')[1] || '').length
    return (ji * Math.pow(10, n1) * item * Math.pow(10, n2)) / Math.pow(10, n1 + n2)
  })
}

/**
 * 减法
 * @param  {...any} n
 */
function sub(...n) {
  return n.reduce((ji, item) => {
    let l1 = (ji.toString().split('.')[1] || '').length
    let l2 = (item.toString().split('.')[1] || '').length
    let n = Math.max(l1, l2)
    let l = Math.pow(10, n)
    return ((ji * l - item * l) / l).toFixed(n)
  })
}

# 深拷贝

// 拷贝一个数组
let nums = [10, 1, 2, 3, 5, 7, 13]

console.log(Array.from(nums).sort((a, b) => a - b))
console.log([...nums].sort((a, b) => a - b))
console.log(nums.slice().sort((a, b) => a - b))
console.log(nums.map(v => v).sort((a, b) => a - b))
console.log(nums.filter(v => true).sort((a, b) => a - b))
console.log(Object.assign([], nums).sort((a, b) => a - b))

console.log('nums', nums) // [10, 1, 2, 3, 5, 7, 13]
const deepClone = (o, cached) => {
  if (o instanceof Object) {
    let cache = new Map()
    let result

    if (o instanceof Function) {
      if (o.prototype) {
        result = function() {
          return o.apply(this, arguments)
        }
      } else {
        result = (...args) => {
          return o.call(undefined, ...args)
        }
      }
    } else if (o instanceof Array) {
      result = []
    } else if (o instanceof Date) {
      return +new Date(o)
    } else if (o instanceof RegExp) {
      result = new RegExp(o.source, o.flags)
    } else {
      result = {}
    }
    for (const key in o) {
      if (Object.hasOwnProperty.call(o, key)) {
        if (cached && cached.has(o)) {
          result[key] = cached.get(key)
        } else {
          let val = deepClone(o[key], cache)
          cache.set(key, val)
          result[key] = val
        }
      }
    }
    return result
  } else {
    return o
  }
}

const a = {
  date: new Date(2020, 0, 1, 20, 30, 0),
  reg: /\s/g,
  number: 1,
  str: 'h1',
  empty1: undefined,
  empty2: null,
  array: [
    { name: 'yue', arge: 18 },
    { name: 'heizi', arge: 18 }
  ],
  obj: {
    name: 'yue',
    arge: 18
  },
  f1: (a, b) => a + b,
  f2: function(a, b) {
    return a + b
  }
}
a.self = a

const a2 = deepClone(a)

//   console.log(a2);
//   console.log(a2.f2(1, 2));
//   console.log(a2.f1(1, 2));

# 对象全等 looseEqual

function isObject(value) {
  var type = typeof value
  return value != null && (type == 'object' || type == 'function')
}
/**
 * form vue
 * Check if two values are loosely equal - that is,
 * if they are plain objects, do they have the same shape?
 */
function looseEqual(a, b) {
  if (a === b) return true
  const isObjectA = isObject(a)
  const isObjectB = isObject(b)
  if (isObjectA && isObjectB) {
    try {
      const isArrayA = Array.isArray(a)
      const isArrayB = Array.isArray(b)
      if (isArrayA && isArrayB) {
        return (
          a.length === b.length &&
          a.every((e, i) => {
            return looseEqual(e, b[i])
          })
        )
      } else if (a instanceof Date && b instanceof Date) {
        return a.getTime() === b.getTime()
      } else if (!isArrayA && !isArrayB) {
        const keysA = Object.keys(a)
        const keysB = Object.keys(b)
        return (
          keysA.length === keysB.length &&
          keysA.every(key => {
            return looseEqual(a[key], b[key])
          })
        )
      } else {
        return false
      }
    } catch (e) {
      return false
    }
  } else if (!isObjectA && !isObjectB) {
    // 判断基本类型 number、string、boolean、null、undefined、Symbol、BigInt
    return String(a) === String(b)
  } else {
    return false
  }
}

const a = {
  weight: 8,
  fields: [
    {
      name: '全额缴费',
      value: 0
    },
    {
      name: '按月缴费',
      value: 1
    }
  ]
}
const b = {
  weight: '8',
  fields: [
    {
      name: '全额缴费',
      value: 0
    },
    {
      name: '按月缴费',
      value: 1
    }
  ]
}

console.log(looseEqual(a, b))

# 对象取键 get

const obj = { a: [{ b: { c: 3 } }] }
function get(obj, path, def) {
  let chain = Array.isArray(path) ? path : path.split(/[\.\[\]]+/)
  let val = chain.reduce((prev, curr) => {
    if (prev) {
      return (prev = prev[curr])
    } else {
      return prev
    }
  }, obj)
  return val === undefined ? def : val
}
console.log(get(obj, 'a.b', false)) // false
console.log(get(obj, 'pop_act.pic.aaaaaaaaa')) // undefined
console.log(get(obj, 'pop_act.pic.aaaaaaaaa', false)) // false
console.log(get(obj, ['a', 'b', 'c'])) // undefined
console.log(get(obj, ['a', '0', 'b', 'c'])) // 3
console.log(get(obj, 'a[0].b.c')) // 3

# merge

let base = {
  name: 'n20210033',
  version: '1.0.0',
  description: 'nprd-n20210033',
  author: 'itw_lilei01',
  private: true,
  config: {
    nuxt: {
      host: '0.0.0.0',
      port: '7711'
    }
  },
  scripts: {
    getCmsData: 'node build.js @LOCAL=true',
    serve: 'npm run getCmsData && cross-env PATH_TYPE=development nuxt',
    build: 'npm run getCmsData && cross-env nuxt build -a',
    generate: 'nuxt generate'
  },
  dependencies: {
    axios: '^0.21.1',
    'regenerator-runtime': '^0.13.7',
    'crypto-js': '^4.0.0',
    'node-rsa': '^1.1.1',
    nuxt: '2.11.0',
    qs: '^6.9.4',
    vant: '^2.9.1',
    'vuex-persistedstate': '^2.7.0'
  },
  devDependencies: {
    '@babel/preset-env': '^7.12.17',
    '@nuxtjs/proxy': '^1.3.3',
    '@nuxtjs/style-resources': '^1.2.1',
    'babel-plugin-import': '^1.13.3',
    'babel-plugin-lodash': '^3.3.4',
    'cross-env': '^5.2.1',
    less: '^4.1.1',
    'less-loader': '^7.2.1',
    'postcss-pxtorem': '^5.1.1',
    sass: '^1.29.0',
    'sass-loader': '^7.0.1',
    'webpack-spritesmith': '^1.1.0'
  }
}

let append = {
  config: {
    commitizen: {
      path: 'node_modules/cz-customizable'
    }
  },
  scripts: {
    prepare: 'husky install',
    lint: 'eslint --ext .vue pages/ --ext .vue components/prd --ext .js store/',
    prettier: 'prettier pages/** store/* components/prd/*  --write',
    'lint:fix': 'npm run lint -- --fix'
  },
  'lint-staged': {
    'pages/*.vue': ['eslint --fix', 'prettier --write', 'git add'],
    'store/*.js': ['eslint --fix', 'prettier --write', 'git add']
  }
}

function merge() {
  let isObj = s => '[object Object]' == Object.prototype.toString.call(s)
  let r = {}
  for (let i = 0, e = arguments.length; i < e; i++) {
    let m = arguments[i]
    if (isObj(m)) {
      for (let k in m) {
        let n = m[k]
        if (isObj(n)) {
          r[k] = merge(r[k] || {}, n)
        } else {
          r[k] = n
        }
      }
    } else {
      throw new Error('arguments must be pure object.')
    }
  }
  return r
}

let o = merge(base, append)

console.log(JSON.stringify(o, null, 2))

# 枚举

/**
 * 枚举类型
 */
class Enum {
  constructor(array) {
    Object.defineProperty(this, '_map', {
      value: [...array],
      enumerable: false
    })
    for (const item of array) {
      this[item.key] = item.value
      Object.defineProperty(this, item.value, {
        value: item.key,
        enumerable: false
      })
    }
  }

  /**
   * 返回键值对数组
   * 形如[{key1: value1}, {key2: value2}]
   */
  all() {
    return this._map
  }
}

//证件类型
const CARD_TYPE = new Enum([
  {
    key: '01',
    value: '居民身份证'
  },
  {
    key: '02',
    value: '护照'
  },
  {
    key: '03',
    value: '军人证'
  },
  {
    key: '05',
    value: '港台同胞证'
  }
])

console.log(CARD_TYPE)

# 队列

/**
 * 队列类型 先入先出
 */
class Queue {
  constructor() {
    this.items = []
  }
  all() {
    return this.items
  }
  push(v) {
    this.items.push(v)
  }
  pop() {
    return this.items.shift()
  }
  size() {
    return this.items.length
  }
  clear() {
    this.items = []
  }
}

const doSth = new Queue()

doSth.push({
  key: '01',
  value: '吃饭'
})
doSth.push({
  key: '02',
  value: '睡觉'
})
doSth.push({
  key: '03',
  value: '打豆豆'
})

console.log(doSth.pop())
console.log(doSth.pop())
console.log(doSth.pop())

#

/**
 * 栈类型 先入后出
 */
class Stack {
  constructor() {
    this.items = []
  }
  all() {
    return this.items
  }
  push(v) {
    this.items.push(v)
  }
  pop() {
    return this.items.pop()
  }
  size() {
    return this.items.length
  }
  clear() {
    this.items = []
  }
}

const doSth = new Stack()

doSth.push({
  key: '01',
  value: '吃饭'
})
doSth.push({
  key: '02',
  value: '睡觉'
})
doSth.push({
  key: '03',
  value: '打豆豆'
})

console.log(doSth.pop())
console.log(doSth.pop())
console.log(doSth.pop())

# 函数式

# 函数记忆

/**
 * 缓存静态方法的value
 * @param {*} fn  fn 的参数中不能使用 引用类型
 */
function cacheStaticFn(fn) {
  const cacheMap = new Map()
  return (...args) => {
    let cacheKey = args.join('-')
    if (!cacheMap.has(cacheKey)) {
      cacheMap.set(cacheKey, fn(...args))
      console.log('存储: ', cacheMap)
    }
    console.log('已有结果')
    return cacheMap.get(cacheKey)
  }
}

function a() {
  console.log('do a =>')
  return 'a'
}
function b() {
  console.log('do d =>')
  return 'b'
}
const cacheA = cacheStaticFn(a)
const cacheB = cacheStaticFn(b)

cacheA() // log: do a => 存储:  Map(1) {'' => 'a'} => 已有结果
cacheA() // 已有结果
cacheA() // 已有结果

cacheB() // log: do b => 存储:  Map(1) {'' => 'b'} => 已有结果
cacheB() // 已有结果
cacheB() // 已有结果

升级版

/**
 * 缓存函数
 * @param {function} func
 * @returns 一个会缓存结果的函数,只执行一次计算逻辑
 */
function cacheStaticFn(fn) {
  const cacheMap = new Map()
  return (...args) => {
    // ! 注: 真实项目使用 object-hash 生成参数为对象类型的唯一 key
    const cacheKey = args.length > 0 ? JSON.stringify(args) : 'value'
    if (!cacheMap.has(cacheKey)) {
      cacheMap.set(cacheKey, fn(...args))
      console.log('set =>', cacheMap)
    }
    console.log('get =>', cacheMap)

    return cacheMap.get(cacheKey)
  }
}
const getEntityApi = () => {
  return new Promise((resolve, reject) => {
    console.log('fetch getEntityApi...')
    setTimeout(() => {
      resolve({
        code: '0',
        data: {
          name: 'yanyue404'
        }
      })
    }, 3000)
  })
}
// 不带缓存的
const getEntity = () => {
  return new Promise(async (resolve, reject) => {
    const { code, data } = await getEntityApi()
    if (code === '0' && data) {
      resolve(data)
    }
  })
}

// 简写
const getEntityThen = () => {
  return getEntityApi().then(({ code, data }) => {
    if (code === '0' && data) {
      return data
    }
  })
}

// 带缓存的(无参)
const getEntity__cache = cacheStaticFn(() => {
  return new Promise(async (resolve, reject) => {
    const { code, data } = await getEntityApi()
    if (code === '0' && data) {
      resolve(data)
    }
  })
})

// 带缓存的(基础类型)
const getEntity__cache2 = cacheStaticFn(getEntityApi)

async function main() {
  // 缓存无参
  console.log(await getEntity__cache())
  // 缓存有参
  // params: 1
  // params: {id: 1}
  /*    getEntity__cache2({ id: 1 }).then(({ code, data }) => {
          if (code === "0" && data) {
            console.log("data", data);
          }
        }); */
}
main()

setTimeout(() => {
  main()
}, 3000)

# 函数柯里化

const curry = function(fn) {
  const arity = fn.length // 获取参数个数 3
  console.log('arity', arity)
  return function $curry(...args) {
    console.log('参数的个数', arity)
    console.log('args', args)
    if (args.length === arity) {
      return fn.apply(this, args)
    } else {
      return function(...reset) {
        console.log('reset', reset)
        console.log('all args', args.concat(reset))
        return $curry.apply(this, args.concat(reset))
      }
    }
  }
}
/*     const curry = (fn, ...args) => {
        // 函数的参数个数可以直接通过函数数的.length属性来访问
        if (fn.length === args.length) {
          // 传入的参数大于等于原始函数fn的参数个数,则直接执行该函数
          return fn.call(fn, ...args);
        }

        // 传入的参数小于原始函数fn的参数个数时
        // 则继续对当前函数进行柯里化,返回一个接受所有参数(当前参数和剩余参数)的函数
        return (...rest) => curry(fn, ...args, ...rest);
      }; */
const add = function(a, b, c) {
  return a + b + c
}

const curriedAdd = curry(add)
console.log(curriedAdd)

const result = curriedAdd(1)(2)

console.log('result:', result(3)) // 6

// const result2 = curriedAdd(1, 2)(3);

// console.log("result2:", result2); // 6

// const result3 = curriedAdd(1, 2, 3);

// console.log("result3:", result3); // 6
const add = (x, y, z) => x + y + z
const curry = fn => {
  const fnLength = fn.length
  return function curried(...args) {
    if (args.length === fnLength) {
      return fn.apply(null, args)
    } else {
      return function(...reset) {
        return curried.apply(null, args.concat(reset))
      }
    }
  }
}
const curriedAdd = curry(add)

const result = curriedAdd(1)(2)(3)
const result2 = curriedAdd(1, 2, 3)
const result3 = curriedAdd(1, 2)(3)
console.log('result', result)
console.log('result2', result2)
console.log('result3', result3)

# 函数管道 compose、pipe

function pipe(...funcs) {
  return function(result) {
    let list = funcs.slice()
    while (list.length > 0) {
      // 从列表中取第一个函数并执行
      result = list.shift()(result)
    }
    return result
  }
}
// function compose(...funcs) {
//   return function (result) {
//     let list = funcs.slice();
//     while (list.length > 0) {
//       // 从列表中取第一个函数并执行
//       result = list.pop()(result);
//     }
//     return result;
//   };
// }

function compose(...funcs) {
  return function(value) {
    return funcs.reduceRight((reducer, reducer) => reducer(state), value)
  }
}
/*  function pipe(...funcs) {
        return function (value) {
          return funcs.reduce((state, reducer) => reducer(state), value);
        };
      } */
const join = (x, y) => `${x}${y}`
const firstUpperCase = str => {
  return str.slice(0, 1).toLocaleUpperCase() + str.slice(1)
}

const camelize = str => {
  return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ''))
}

const componentName = pipe(firstUpperCase, camelize)
const componentName2 = compose(camelize, firstUpperCase)
console.log(componentName('insure-step-by-step')) // InsureStepByStep
console.log(componentName2('rainbow-insurance-mall')) // TkInsuranceMall

# 算法

# 找出现最多次数的字母

let str = 'configureyourdevicetousewhistleasitsHTTPandHTTPSproxyonIP'
let str2 = 'aabbcbc'

// o、e 出现了 5 次

function getMaxString(string) {
  const map = {}
  let max = 1
  let maxKeys = []
  for (let i = 0; i < string.length; i++) {
    let key = string[i]
    map[key] ? map[key]++ : (map[key] = 1)
    if (map[key] > max) {
      max = map[key]
      maxKeys = [key]
    } else if (map[key] === max) {
      maxKeys.push(key)
    }
  }

  console.log('最大值存在多个', maxKeys.join('、') + '出现了 ' + max + '次')
  return [max, maxKeys]
}

getMaxString(str) // 5, ['e','o']
getMaxString(str2) // 3, ['b']
function randomStr(length) {
  let array = []
  const strPool = 'AAABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
  for (let i = 0; i < length; i++) {
    array[i] = strPool[Math.floor(Math.random() * strPool.length)]
  }
  return array.join('')
}

var str = randomStr(1000000)

console.log(`目标字符串长度为${str.length}`)

function findByRegex() {
  console.time('findByRegex')
  let orderStr = str
    .split('')
    .sort()
    .join('')
  // let orderStr = str;
  let letter = ''
  let max = 0
  orderStr.replace(/([a-zA-Z])\1*/g, (subStr, char) => {
    if (subStr.length > max) {
      letter = char
      max = subStr.length
    }
  })
  console.log(`出现次数最多的字母是${letter},出现了${max}`)
  console.timeEnd('findByRegex')
}

function findByLoop() {
  console.time('findByLoop')
  let letter = ''
  let max = 0
  let result = []
  let datum = 'A'.charCodeAt()
  for (let i = 0; i < str.length; i++) {
    let index = str.charCodeAt(i) - datum
    result[index] = (result[index] || 0) + 1
  }
  for (let i = 0; i < result.length; i++) {
    if (result[i] > max) {
      max = result[i]
      letter = String.fromCharCode(datum + i)
    }
  }
  console.log(`出现次数最多的字母是${letter},出现了${max}`)
  console.timeEnd('findByLoop')
}
function getMaxString(string) {
  console.time('getMaxString')
  const map = {}
  let max = 1
  let maxKeys = []
  for (let i = 0; i < string.length; i++) {
    let key = string[i]
    map[key] ? map[key]++ : (map[key] = 1)
    if (map[key] > max) {
      max = map[key]
      maxKeys = [key]
    } else if (map[key] === max) {
      maxKeys.push(key)
    }
  }

  console.log('最大值存在多个', maxKeys.join('、') + '出现了 ' + max + '次')
  console.timeEnd('getMaxString')
  return [max, maxKeys]
}
findByRegex()

findByLoop()

getMaxString(str)

# 二分查找

// 先递增后递减数组求减求最大值
const arr1 = [0, 1, 4, 7, 5]
const arr2 = [1, 6, 5, 3, 2]
const arr3 = [1, 2, 3, 4, 5, 6, 7, 9, 3, 2]

function getMax(params) {
  let begin = 0
  let end = params.length - 1
  while (begin <= end) {
    let mid = Math.floor(begin + (end - begin) / 2)
    console.log(mid)

    let element = params[mid]
    if (element > params[mid - 1] && element > params[mid + 1]) {
      console.log('第' + (mid + 1) + '个' + element + '最大')
      return element
      // 当前比右边的数小,向右走
    } else if (element < params[mid + 1]) {
      begin = mid + 1
      // 当前比左边的数小,相左走
    } else if (element < params[mid - 1]) {
      end = mid - 1
    }
  }
  return -1
}
//  console.log(getMax(arr1));
// console.log(getMax(arr2));
console.log(getMax(arr3))

# 大数相加

function add(a, b) {
  let maxLength = Math.max(a.length, b.length)
  //用0去补齐长度
  a = a.padStart(maxLength, 0) //"0009007199254740991"
  b = b.padStart(maxLength, 0) //"1234567899999999999"
  //定义加法过程中需要用到的变量
  let t = 0
  let f = 0 //"进位"
  let sum = ''
  for (let i = maxLength - 1; i >= 0; i--) {
    t = parseInt(a[i]) + parseInt(b[i]) + f
    f = Math.floor(t / 10)
    sum = (t % 10) + sum
  }
  if (f == 1) {
    sum = '1' + sum
  }
  return sum
}

const add = (a, b) => {
  const maxLength = Math.max(a.length, b.length)
  let overflow = false
  let sum = ''
  for (let i = 1; i <= maxLength; i++) {
    const ai = a[a.length - i] || '0'
    const bi = b[b.length - i] || '0'
    let ci = parseInt(ai) + parseInt(bi) + (overflow ? 1 : 0)
    overflow = ci >= 10
    ci = overflow ? ci - 10 : ci
    sum = ci + sum
  }
  sum = overflow ? '1' + sum : sum
  return sum
}
console.log(add('111', '99'))
console.log(add('9007199254740991', '1234567899999999999'))

# 玩转异步

# 基础模拟 Promise Class

class Promise__fake {
  constructor(executor) {
    this.status = 'pending' // 默认状态为 PENGING
    this.value = undefined // 存放成功状态得值
    this.reason = undefined // 存放失败状态得值
    this.handleFulfilled = [] // 存储成功后的回调
    this.handleRejection = [] // 存储失败后的回调
    // ! resolve 形参的实际参数在这儿
    const resolve = data => {
      // 状态变更只有一次
      if (this.status === 'pending') {
        this.status = 'fulfilled'
        this.value = data
        this.handleFulfilled.forEach(fn => fn(data))
      }
    }
    const reject = reason => {
      if (this.status === 'pending') {
        this.status = 'rejected'
        this.reason = reason
        this.handleRejection.forEach(fn => fn(reason))
      }
    }
    try {
      executor(resolve, reject)
    } catch (e) {
      // 遇到错误时,捕获错误,执行 reject 函数
      reject(e)
    }
  }
  then(fulfilledFn, rejectedFn) {
    if (this.status === 'fulfilled') {
      fulfilledFn(this.value)
    }
    if (this.status === 'rejected') {
      fulfilledFn(this.reason)
    }
    if (this.status === 'pending') {
      this.handleFulfilled.push(fulfilledFn)
      this.handleRejection.push(rejectedFn)
    }
    return this
  }
  all() {}
}

//1. 链式测试
/*   var p1 = new Promise__fake(function (resolve, reject) {
        console.log("init Promise");
        if (Math.random() > 0.5) {
          resolve("大");
        } else {
          reject("小");
        }
      });
      p1.then(
        (data) => console.log("success", data),
        (reason) => console.log("error", reason)
      ).then(
        () => console.log("success 2"),
        () => console.log("error 2")
      ); */

// 2. 延时测试
/*  var sleep = (time, data) =>
        new Promise__fake(function (resolve, reject) {
          setTimeout(resolve, time, data);
        });
      sleep(3000, "时间到!").then((val) => {
        console.log(val);
      }); */

// 3. 状态变更后不可变
/*  const p2 = new Promise__fake(function (resolve, reject) {
        resolve("失败了!");
        reject("还会成功吗!");
      });
      p2.then(
        (data) => console.log(data),
        (reason) => console.log(reason)
      ); */

// 4. 替换 setTimeOut 模拟得异步,使用状态控制
const promise = new Promise__fake(resolve => {
  setTimeout(() => {
    resolve('成功!')
  }, 1000)
}).then(
  data => {
    console.log('success', data)
  },
  err => {
    console.log('faild', err)
  }
)

# await 错误捕获

const data = {
  code: 200,
  data: {
    num: 100
  }
}
const p = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(data)
    }, 500)
    //   throw new Error('出错了');
  })
}
// promise 链 .catch 捕获
/* async function main() {
        const res = await p().catch((err) => {
          console.log("err", err);
        });
        console.log("res", res);
      } */

// try catch 捕获
/*  async function main() {
        try {
          const res = await p();
          console.log("res", res);
        } catch (err) {
          console.log("err", err);
        }
      }
      main(); */

// 自执行函数
/*  (async () => {
        try {
          const res = await p();
          console.log("res", res);
        } catch (err) {
          console.log("err", err);
        }
      })(); */

function promiseWrapper(p) {
  return (...args) => {
    return p(...args)
      .then(res => [null, res])
      .catch(err => [err, null])
  }
}
// promiseWrapper
;(async () => {
  const func = promiseWrapper(p)
  const [err, res] = await func()
  console.log('res', res)
  console.log('err', err)
})()

# Promise 同步与异步

const p1 = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('3000')
      resolve('3s')
    }, 3000)
  })
const p2 = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('5000')
      resolve('5s')
    }, 5000)
  })

// 同步反应
console.time('promiseTask')
await p1()
await p2()
console.timeEnd('promiseTask') //  promiseTask: 8017.93017578125ms

// 异步反应
console.time('promiseTask')
let task1 = p1()
let task2 = p2()
await task1
await task2
console.timeEnd('promiseTask') // promiseTask: 5003.4599609375ms

# 调试

function takeLongTime(n) {
  return new Promise(resolve => {
    setTimeout(() => resolve(n + 200), n)
  })
}
function step1(n) {
  console.log(`step1 with ${n}`)
  return takeLongTime(n)
}
function step2(n) {
  console.log(`step2 with ${n}`)
  return takeLongTime(n)
}
function step3(n) {
  console.log(`step3 with ${n}`)
  return takeLongTime(n)
}
function doIt() {
  console.time('doIt')
  const time1 = 300
  step1(time1)
    .then(time2 => step2(time2))
    .then(time3 => step3(time3))
    .then(result => {
      console.log(`result is ${result}`)
      console.timeEnd('doIt')
    })
}
// 调试上两者无差异,都可以步进后续的下一步,方便调试 (可能是 chrome 的支持更好了)
/*  async function doIt() {
        console.time("doIt");
        const time1 = 300;
        const time2 = await step1(time1);
        const time3 = await step2(time2);
        const result = await step3(time3);
        console.log(`result is ${result}`);
        console.timeEnd("doIt");
      } */

doIt()

# 异步重试

按时间重试

// all 最多等多久
// delay 延迟等待重试时间
// asyncFn 异步方法
// shouldFn 校验异步执行结果是否有效
function tryPromise(all = 3000, delay = 500, asyncFn = undefined, shouldFn = function() {}) {
  let cur_retry_num = 0 // 重试次数
  return new Promise(resolve => {
    ;(async function main() {
      // 异步反应
      let task = asyncFn()
      let res = await task
      if (shouldFn(res) || cur_retry_num++ >= all / delay) {
        resolve()
      } else {
        setTimeout(main, delay)
      }
    })()
  })
}
const p = () =>
  new Promise(resolve =>
    setTimeout(() => {
      let a = Math.random()
      let flag = a > 0.1 ? 0 : 1
      console.log('=====', flag)
      resolve(flag)
    }, 1000)
  )
tryPromise(3000, 500, p, res => res === 1).then(() => {
  console.log('okkk')
})

按次数重试

实现 Promie.retry ,成功后 resolve 结果,失败后重试,尝试超过一定次数才真正 reject

const p = () =>
  new Promise((resolve, reject) =>
    setTimeout(() => {
      let a = Math.random()
      let flag = a > 0.1 ? 0 : 1
      console.log(flag)
      flag === 1 ? resolve(flag) : reject(flag)
    }, 1000)
  )

/*   Promise.retry = function (promiseFn, times = 3) {
        return new Promise(async (resolve, reject) => {
          while (times--) {
            try {
              let ret = await promiseFn();
              resolve(ret);
              break;
            } catch (error) {
              if (!times) reject(error);
            }
          }
        });
      }; */

/*  Promise.retry = function (promiseFn, times = 3) {
        return new Promise((resolve, reject) => {
          let count = 0;
          let action = function () {
            promiseFn()
              .then(resolve)
              .catch((err) => {
                count++;
                if (count >= times) {
                  reject(err);
                } else {
                  action();
                }
              });
          };
          action();
        });
      }; */

Promise.retry = function(asyncFn, times = 3) {
  let count = 0
  function executeFn() {
    return new Promise((resolve, reject) => {
      resolve(asyncFn())
    })
      .then(res => {
        return Promise.resolve(res)
      })
      .catch(err => {
        count++
        if (count >= times) {
          return Promise.reject(err)
        } else {
          return executeFn()
        }
      })
  }
  return executeFn()
}

Promise.retry(p, 3).then(() => {
  console.log('okkk')
})

# 任务中断

批量执行异步任务,有任务返回 false 就中断执行并返回结果。

let asyncFn = val => {
  return new Promise(resolve => {
    setInterval(() => {
      resolve(val)
    }, 1000)
  })
}

let tasks = [true, false, false].map(v => () => asyncFn(v))
// 基础 for 循环
/*       async function run() {
           for (let i = 0; i < tasks.length; i++) {
             const task = tasks[i];
             let res = await task();
             if (!res) {
               return false;
             }
           }
           return true;
         } */
// for of
async function run() {
  for (const task of tasks) {
    let res = await task()
    if (!res) {
      return false
    }
  }
  return true
}

async function main() {
  let result = await run()
  console.log('result', result)
}
main() // result false

#

# Git

# git stash 命令的理解?应用场景?

一、是什么

stash,译为存放,在 git 中,可以理解为保存当前工作进度,会把暂存区和工作区的改动进行保存,这些修改会保存在一个栈上

后续你可以在任何时候任何分支重新将某次的修改推出来,重新应用这些更改的代码

默认情况下,git stash会缓存下列状态的文件:

  • 添加到暂存区的修改(staged changes)
  • Git 跟踪的但并未添加到暂存区的修改(unstaged changes)

但以下状态的文件不会缓存:

  • 在工作目录中新的文件(untracked files)
  • 被忽略的文件(ignored files)

如果想要上述的文件都被缓存,可以使用-u或者--include-untracked可以工作目录新的文件,使用-a或者--all命令可以当前目录下的所有修改

二、如何使用

关于git stash常见的命令如下:

  • git stash

  • git stash save

  • git stash list

  • git stash pop

  • git stash apply

  • git stash show

  • git stash drop

  • git stash clear

(1)git stash

保存当前工作进度,会把暂存区和工作区的改动保存起来

(2) git stash save

git stash save可以用于存储修改.并且将git的工作状态切回到HEAD也就是上一次合法提交上

如果给定具体的文件路径,git stash只会处理路径下的文件.其他的文件不会被存储,其存在一些参数:

  • --keep-index 或者 -k 只会存储为加入 git 管理的文件

  • --include-untracked 为追踪的文件也会被缓存,当前的工作空间会被恢复为完全清空的状态

  • -a 或者 --all 命令可以当前目录下的所有修改,包括被 git 忽略的文件

(3) git stash list

显示保存进度的列表。也就意味着,git stash命令可以多次执行,当多次使用git stash命令后,栈里会充满未提交的代码,如下:

其中,stash@{0}stash@{1}就是当前stash的名称

(4)git stash pop

git stash pop 从栈中读取最近一次保存的内容,也就是栈顶的stash会恢复到工作区

也可以通过 git stash pop + stash名字执行恢复哪个stash恢复到当前目录

如果从stash中恢复的内容和当前目录中的内容发生了冲突,则需要手动修复冲突或者创建新的分支来解决冲突

(5) git stash apply

将堆栈中的内容应用到当前目录,不同于git stash pop,该命令不会将内容从堆栈中删除

也就说该命令能够将堆栈的内容多次应用到工作目录中,适应于多个分支的情况

同样,可以通过git stash apply + stash名字执行恢复哪个stash恢复到当前目录

(6)git stash show

查看堆栈中最新保存的stash和当前目录的差异

通过使用git stash show -p查看详细的不同

通过使用git stash show stash@{1}查看指定的stash和当前目录差异

(7) git stash drop

git stash drop + stash名称表示从堆栈中移除某个指定的 stash

(8) git stash clear

删除所有存储的进度

三、应用场景

当你在项目的一部分上已经工作一段时间后,所有东西都进入了混乱的状态, 而这时你想要切换到另一个分支或者拉下远端的代码去做一点别的事情

但是你创建一次未完成的代码的commit提交,这时候就可以使用git stash

例如以下场景:

当你的开发进行到一半,但是代码还不想进行提交 ,然后需要同步去关联远端代码时.如果你本地的代码和远端代码没有冲突时,可以直接通过git pull解决

但是如果可能发生冲突怎么办.直接git pull会拒绝覆盖当前的修改,这时候就可以依次使用下述的命令:

  • git stash
  • git pull
  • git stash pop

或者当你开发到一半,现在要修改别的分支问题的时候,你也可以使用git stash缓存当前区域的代码

  • git stash:保存开发到一半的代码
  • git commit -m '修改问题'
  • git stash pop:将代码追加到最新的提交之后

# 其他

# reduce 经典用法

  1. 参数合并
const buildParamsTxt = `@tag=next-last@env=dev `
const buildParams = buildParamsTxt.split('@').reduce((o, item) => {
  let [key, value] = item.replace(/\s/g, '').split('=')
  key && (o[key] = value)
  return o
}, {}) // {tag: "next-last", env: "dev"}

# padStart 方法重写

String.prototype.padStart__fake = function(allLen, str) {
  let len = this.length
  let base = this[0]
  while (str.length < allLen) {
    str += str
  }
  return str.substring(0, allLen - base.length) + base
}

console.log('1'.padStart__fake(4, '0'))
console.log('1'.padStart__fake(4, '12345'))
console.log('11'.padStart__fake(4, '0'))

# Proxy api

// Object.defineProperty
function observer(obj) {
  if (typeof obj === 'object') {
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        defineReactive(obj, key, obj[key])
      }
    }
  }
}

function defineReactive(obj, key, value) {
  //针对value是对象,递归检测
  observer(value)
  //劫持对象的key
  Object.defineProperty(obj, key, {
    get() {
      console.log('获取:' + key)
      return value
    },
    set(val) {
      //针对所设置的val是对象
      observer(val)
      console.log(key + '-数据改变了')
      value = val
    }
  })
}

let obj = {
  name: '守候',
  flag: {
    book: {
      name: 'js',
      page: 325
    },
    like: ['吃饭']
  }
}

observer(obj)

// 1. 新增一个属性,由于在 执行 observer(obj) 的时候没有这个属性,所以无法监听到,删除的属性页无法监听到。
// 2. 数组的变化无法监听到 (! 数组属性实际修改成功, push, splice,pop)
// 3. 递归遍历对象,使用 Object.defineProperty 劫持对象属性,如果遍历的对象很深,花费的时间比较久,甚至性能问题
function observerProxy(obj) {
  const handler = {
    get(target, prop, receiver) {
      console.log('正在读取', prop)
      if (typeof target[prop] === 'object' && target[prop] !== null) {
        return new Proxy(target[prop], handler)
      }
      return Reflect.get(...arguments) // 将操作转发给对象
    },
    set(target, prop, val) {
      console.log('正在写入', prop, val)
      return Reflect.set(...arguments) // 将操作转发给对象
    },
    deleteProperty(target, prop) {
      console.log('正在删除', prop)
      delete target[prop]
    }
  }
  return new Proxy(obj, handler)
}
let obj = {
  name: '守候',
  flag: {
    book: {
      name: 'js',
      page: 325
    },
    like: ['吃饭']
  }
}

let obj2 = observerProxy(obj)
// 拦截方式除了 get、set、deleteProperty 还有很多监听方法,
// 也可以兼容 Object.defineProperty 监听不到的操作,如 监听数组(对数组进行push shift 等操作,会触发对应的方法名称和 length 变化),监听对象属性的新增、删除等

// 使用场景
// 1. 负索引数组
// 2. 表单校验
// 3. 增加附加属性
// 4. 数据格式化
let target = [1, 2, 3]
let proxy = new Proxy(target, {
  get(target, prop, receiver) {
    console.log('正在读取', prop)
    if (target[prop]) {
      return Reflect.set(...arguments)
    } else if (prop < 0) {
      return target[target.length - -prop]
    } else {
      throw new ReferenceError(`Property doesn't exist: "${prop}"`)
    }
  },
  set(target, prop, val) {
    console.log('正在写入', prop, val)
    return Reflect.set(...arguments) // 将操作转发给对象
  }
})

console.log(proxy[-1])

# 动态导入

const components = {}
// display目录下代表需要弹窗展示的组件
const files = require.context('@/components/display', false, /\.vue$/)
files.keys().forEach(item => {
  // 命名仅包括字母或数字或下划线或短横线
  const fileName = item.replace(/^\.\/([\w-]+)\.vue$/, '$1')
  const component = () => import(`@/components/display/${fileName}.vue`)
  components[fileName] = options => {
    return {
      render(h) {
        return h(component, options)
      }
    }
  }
})
// iframe组件,用以加载pdf链接或者第三方链接
components['iframe-page'] = link => {
  const component = () => import('rainbow-components/iframe-page.vue')
  return {
    render(h) {
      return h(component, { props: { link } })
    }
  }
}
// html-wrap组件,用以展示富文本
components['html-wrap'] = html => {
  const component = () => import('rainbow-components/html-wrap.vue')
  return {
    render(h) {
      return h(component, { props: { html } })
    }
  }
}

export default components
Vue.prototype.$displayComponents = function(fileName, options) {
  return async () => {
    const com = await import(/* webpackChunkName: "display" */ `@/components/display/index.js`)
    return com.default[fileName](options)
  }
}