前端手写系列

js原生类

new

1)创建一个新对象
2)新对象__proto__指向构造函数原型对象(将构造函数的作用域赋给新对象,因此this就指向了这个新对象)
3)执行构造函数中的代码,为这个新对象添加属性
4)返回this指向的新对象

如果构造函数return了‘新’对象,那么新对象会取代整个new出来的结果,否则还是返回上面所创建的对象

function myNew() {
    // 1、创建空对象
    const obj={}
    const constructor = [].shift.call(arguments) // 转化成数组,并返回第一项
    // 2、原型链接,obj隐式原型链指向构造函数的原型对象上 
    obj.__proto__ = constructor.prototype
    // 3、将函数的属性和方法添加到空对象上
    const ret = constructor.apply(obj, arguments)
    // 4、ret是执行构造函数返回的值,如果返回的是新对象,那上面创建的空对象将作废
    return typeof ret === 'object'? ret : obj
}

call、apply、bind

  • call
    1)把this赋值给context的fn
    2)传入参数调用fn函数,并返回结果

说白了就是把它的方法挂载在我身上,我执行完该方法后返回结果,并删除这个方法

Function.prototype.myCall=function(context = globalThis){ // 默认参数为当前环境的全局对象
     // 判断调用对象this,谁调用call,this指向谁,所以this是sayName函数
	if(typeof this !== 'function') console.log('type error')
	// 截取下标1后面的参数
	const args = [...arguments].slice(1) 
	// 把this赋值给当前上下文的fn函数
	context.fn = this
	// 传入参数并调用函数
	const result = context.fn(...args)
	// 将属性删除
	delete context.fn
	return result 
}

// 测试
const obj1={
	name: 'xiaoming'
}
const obj2={
	sayName(val){ 
		console.log(this.name + ' and '+ val)
	}
}
obj2.sayName.myCall(obj1, 'lili', 'lilei')  // 让obj1执行sayName方法,此时this指向obj1
  • apply(不用处理参数,直接那arguments[1]就可以获取第二个参数)
Function.prototype.myApply=function(context = globalThis){
    // 判断调用对象
	if(typeof this !== 'function') console.log('type error')
	let  result 
	// 把this赋值给当前上下文的fn函数
	context.fn = this
	// 传入参数并调用函数
	if(arguments[1]){ // ["lili", "lilei"]
		result = context.fn(...arguments[1])
	}else{
		result = context.fn()
	}
	// 将属性删除
	delete context.fn
	return result
}
const obj1={
	name: 'xiaoming'
}
const obj2={
	sayName(...val){  // val => ['lili', 'lilei']
		console.log(this.name + ' and '+ val) // xiaoming and lili,lilei
	}
}
obj2.sayName.myApply(obj1, ['lili', 'lilei']) // 让obj1执行sayName方法,此时this指向obj1
  • bind(只是传入参数,不用执行,所以return fn(){})
Function.prototype.myBind = function (context) { 
    // 判断调用对象
    if (typeof this !== 'function') {
        console.log('type error')
    }
    // 从下标1开始截取参数
    let args = [...arguments].slice(1),   
        fn = this  // foo(){}
    return function Fn() {
        // this instanceof Fn 表示this是否由Fn实例化出来,如果是,this指向new出来的对象
        // 否则this指向window,用context(即{a:1})
        return fn.apply(this instanceof Fn ? this : context, args.concat(...arguments))
    }
}

// 测试
function foo(){
    this.b=100
    return this.a
}
var func=foo.myBind({a:1}) // Fn(){}
console.log(func()) //1   Fn()里的this指向window
console.log(new func()) // {b:100}    this指向new出来的对象

instanceof

判断左边对象的原型链上是否存在右边对象的prototype属性()

  • 隐式原型链指向所属类的原型对象
  • Object.getPrototypeOf()可以找到隐式原型对象
function myInstanceof(left, right) {
    // 基本数据类型直接返回false
    if(typeof left !== 'object' || left === null) return false;
    // getProtypeOf是Object对象自带的一个方法,能够拿到参数的原型对象
    let LProto = Object.getPrototypeOf(left);  // 相当于 left.__proto__ 隐式原型对象
    while(true) {
        // 查找到尽头,还没找到
        if(LProto == null) return false;
        // 找到相同的原型对象
        if(LProto == right.prototype) return true;
        // 没找到继续向上一层原型链查找
        LProto = Object.getPrototypeOf(LProto); // LProto.__proto__
    }
}
console.log(myInstanceof(new String("111"), String)); // true

Object.create

用现有的对象去创建新对象,并把现有对象挂载到新对象的隐式原型链上

/*
Object.create = function(obj){
    let nObj={}
    nObj.__proto__=obj   // __proto__不兼容ie
    return nObj
}
*/

Object.prototype.create = function(obj, propertiesObject={}){
    function Fn(){} 
    Fn.prototype = obj 
    const res = new Fn() // new完之后, res.__proto__ = obj
    Object.defineProperties(res, propertiesObject)
    return res 
}

// 测试
const parent = {a:'1111'}
const obj = Object.create(parent)
Object.defineProperties(obj, {
    name: {
        value: '张三',
        configurable: false,
        writable: true,
        enumerable: true
    },
    age: {
        value: 18,
        configurable: true
    }
})
console.log(obj)

Object.is

// 主要用来解决 +0 === 0 // true    NaN === NaN // false
Object.prototype._is = function(x, y){
	if(x===y){
		return x !==0 || y!==0 ||1/x===1/y
	}else{
		return x!==x && y!==y
	}
}
console.log(Object._is(+0, -0))
console.log(Object._is(NaN, NaN))

Object.assign

Object.defineProperty(Object, '_assign', {
	value:function(target, ...args){
		if(target==null) return new TypeError('Cannot convert undefined or null to object')
		// 统一是引用数据类型
		const to = Object(target)
		for(let i=0;i<args.length;i++){
			const nextSource = args[i]
			if(nextSource!==null){
				for(const nextKey in nextSource){
					if(Object.prototype.hasOwnProperty.call(nextSource, nextKey)){
						to[nextKey] = nextSource[nextKey]
					}
				}
			}
		}
		return to
	},
	enumerable: false,
	writable:true,
	configuerable:true
})

const

function __const(data, value) {
    window.data = value // 把要定义的data挂载到window下,并赋值value
	let c=1
    Object.defineProperty(window, data, { 
        enumerable: true, // 可枚举
        configurable: false, // 可配置
        get: function () {
			return value
        },
        set: function (newVal) {
			if(c>=1) throw new TypeError('Assignment to constant variable')
           c++
		   value = newVal
        }
    })
}
__const('a', 10)
a = 10 // 报错
console.log(a)

promise

把then里的参数都分别push到数组里,待改变状态再遍历执行出来

class myPromise{
	constructor(executor){
		this.status = 'pending'
		this.value = null
		this.reason = null
		this.onFulfilledArray = []
		this.onRejectedArray = []

		const resolve =(value)=>{
			if(value instanceof myPromise){
				return value.then(resolve, reject)
			}
		
			setTimeout(()=>{
				if(this.status === 'pending'){
					this.status = 'fulfilled'
					this.value = value
					this.onFulfilledArray.forEach(func=>{
						func(value)
					})
				}
			})
		}
		const reject =(reason)=>{
			setTimeout(()=>{
				if(this.status === 'pending'){
					this.status = 'rejected'
					this.reason = reason
					this.onRejectedArray.forEach(func=>{
						func(reason)
					})
				}
			})
		}
		try{
			executor(resolve, reject)	
		}catch(e){
			reject(e)
		}
		
	}
	then(onFulfilled, onRejected){
		onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data=>data
		onRejected = typeof onRejected === 'function'? onRejected : (err)=> { throw err }

		const _this = this
		return new Promise((resolve, reject)=>{
			if(_this.status === 'pending'){
				_this.onFulfilledArray.push(()=>{
					try{
						setTimeout(()=>{
							const result = onFulfilled(_this.value)
							 /* result instanceof Promise的情况 
							 	const p = new myPromise((resolve, reject)=>{...})
                 			   p.then(res=>{ return p.then(res=>{})})
                			 */
							result instanceof Promise ? result.then(resolve, reject): resolve(result)
						})
					}catch(e){
						reject(e)
					}
				})
				_this.onRejectedArray.push(()=>{
					try{
						const result = onRejected(_this.reason)
						result instanceof Promise? result.then(resol, reject) : resolve(result)
					}catch(e){
						reject(e)
					}
				})
			}else if(this.status === 'fulfilled'){
				try{
					setTimeout(()=>{
						const result = onFulfilled(_this.value)
						result instanceof Promise? result.then(resolve, reject): resolve(result)
					})
				}catch(e){
					reject(e)
				}
			}else if(this.status === 'rejected'){
				try{
					setTimeout(()=>{
						const result = onRejected(_this.reason)
						result instanceof Promise? result.then(resolve, reject): resolve(result)
					})
				}catch(e){
					reject(e)
				}
			}
		})
	}
}

const p = new myPromise((resolve, reject)=>{
	setTimeout(()=>{
		reject(1)
	}, 2000)
})

p.then(res=>{
	console.log(res)
}, err=>{
	console.log(err)
})

promise其他静态属性和原型方法

catch(onRejected){
	return this.then(null, onRejected) 
}
// finally 参数是一个回调函数,执行完当前的then、catch最后都会执行到这里
finally(callback){ 
	return this.then(
		value => Promise.resolve(callback()).then(()=>value),
		reason => Promise.resolve(callback()).then(()=>{ throw reason})
	)
}
static resolve(value){
	if(value instanceof Promise){ // 如果是promise实例,直接返回
		return value 
	}else{ // 否则,返回新promise对象
		return new Promise((resolve, reject)=>resolve(value))
	}
}
static reject(reason){
	return new Promise((resolve, reject)=>{
		reject(reason)
	})
}
static all(promiseArr){
	return new Promise((resolve, reject)=>{
		let resultArray = []
		promiseArr.forEach(p=>{
			Promise.resolve(p).then(value=>{
				resultArray.push(value)
				if(resultArray.length == promiseArr.length) resolve(resultArray)
			},reason=>{
				reject(reason)
			})
		})
	})
}
static race(promiseArr){
	return new Promise((resolve, reject)=>{
		promiseArr.forEach(p=>{
			Promise.resolve(p).then(value=>{
				resolve(value)
			},reason=>{
				reject(reason)
			})
		})
	})
}

promise.allSettled

接收promise数组,返回promise对象,返回结果永远是成功状态,值是每个promise他们的状态和结果

static allSettled = (promiseArr) => {
  return new Promise((resolve) => {
    let count= 0
    let result = []
    for(let index = 0;index<promiseArr.length;index++){
      promiseArr[index].then(res => { 
        result[index] = {
          status: 'fulfilled',
          value: res
        }
      })
      .catch(err => { 
        result[index] = {
          status: 'rejected',
          reason: err
        }
      })
      .finally(() => { ++count === promiseArr.length && resolve(result) })
    }
  })
}

Array.prototype.filter(返回满足条件的集合)

过滤掉为true的数组,找不到返回空数组

Array.prototype.filter = function(callback, thisArg){ // 谁调用filter,this指向谁
	if(this===undefined){
		throw new TypeError('this is null or undefined')
	}
	if(typeof callback !== 'function'){
		throw new TypeError(callback + 'is not a function')
	}
	const res=[]
	const obj = Object(this) // 让O成为回调函数的对象传递 
	const len = obj.length >>> 0 // 转为number,且为正整数
	for(let i=0; i<len; i++){
		// 检查i是否在0的属性(原型链)上
		if(i in obj){
			if(callback.call(thisArg, obj[i], i, obj)){ // 如果callback回调执行后为true
				res.push(obj[i])
			}
		}
	}
	return res
}

// array.filter(function(currentValue,index,arr), thisValue)
var arr=[function(val){console.log(val); return 1},function(val){console.log(val); return 2},function(val){console.log(val); return 3}]
var res = arr.filter(function(item, index, arrr){
    console.log(this) // String {'ggg'},没有第二个参数this为window
    var res = item() 
    return res> 2
},'ggg')

Array.prototype.every(全部满足才为true,some是一个满足为true)

Array.prototype.every = function(callback, thisArg){ // 谁调用,this指向谁
	if(this===undefined){
		throw new TypeError('this is null or undefined')
	}
	if(typeof callback !== 'function'){
		throw new TypeError(callback + 'is not a function')
	}
	const obj = Object(this) // 让O成为回调函数的对象传递
	const len = obj.length >>> 0 // 转为number,且为正整数
	for(let i=0; i<len; i++){
		if(!callback.call(thisArg, obj[i], i, obj)){ // 如果callback回调执行后为true
			return false
		}
	}
	return true
}

Array.prototype.map

对每个元素进行处理,返回新数组

Array.prototype.map = function(callback,thisArg){
	if(this === undefined){
		throw new TypeError('this is null or undefined')
	}
	if(typeof callback !== 'function'){
		throw new TypeError(callback + 'is not a function')
	}
	const res = []
	const obj = Object(this)
	const len = obj.length >>> 0
	for(let i=0;i<len;i++){
		if(i in obj){
			res[i] = callback.call(thisArg, obj[i], i, obj)
		}
	}
	return res
}

Array.prototype.foreach

和map类似,但是没有返回值

Array.prototype.forEach = function(callback, thisArg){
	if(this === undefined){
		throw new TypeError('this is null or undefined')
	}
	if(typeof callback !== 'function'){
		throw new TypeError(callback + 'is not a function')
	}
	const obj = Object(this)
	const len = obj.length >>> 0
	let k = 0
	while(k<len){
		if(k in obj){
			callback.call(thisArg, obj[k], k, obj)
		}
		k++
	} 
}

Array.prototype.reduce

reduce(callback(accumulator, currentValue, index, array), initialValue)
参数比其他的多了累计器accumulator,初始值initialValue

Array.prototype.myReduce = function(callback, initialValue){
	if(this === undefined){
		throw new TypeError('this is null or undefined')
	}
	if(typeof callback !== 'function'){
		throw new TypeError(callback + 'is not a function')
	}
	const obj = Object(this)
	const len = obj.length >>> 0
	let accumulator = initialValue
	let k = 0
	if(accumulator === undefined){
		while(k<len && !(k in obj)){
			k++
		}
		if(k>=len){
			throw new TypeError('Reduce of empty array whith no initial value')
		}
		accumulator = obj[k++]
	}
	while(k < len){
		if(k in obj){
			accumulator = callback.call(undefined, accumulator, obj[k], k, obj)
		}
		k++
	}
	return accumulator
}

Array.prototype.includes

includes(searchElement, formIndex),fromIndex为开始搜索的位置

Array.prototype.includes = function(searchElement, formIndex = 0){
	const obj = Object(this)
	const len = obj.length >>> 0
	if(formIndex >= len || !len) return false
	for(let i=formIndex; i<len; i++){
		if(obj[i] === searchElement) return true
	}
	return false
}
var arr = ['a', 'b', 'c']
console.log(arr.includes('c', 0))

Array.prototype.indexOf

indexOf(item, start) start为开始检索位置,找到返回下标值,没找到返回-1

Array.prototype.indexOf = function(item, start = 0){
	const obj = Object(this)
	const len = obj.length >>> 0
	for(let i=start; i<len; i++){
		if(obj[i] === item) return i
	}
	return -1
}

JSON.stringify()

function jsonStringify (obj) {
  let type = typeof obj;
  if (type !== "object" || type === null) {
    if (/string|undefined|function/.test(type)) {
      obj = '"' + obj + '"';
    }
    return String(obj);
  } else {
    let json = []
    arr = (obj && obj.constructor === Array);
    for (let k in obj) {
      let v = obj[k];
      let type = typeof v;
      if (/string|undefined|function/.test(type)) {
        v = '"' + v + '"';
      } else if (type === "object") {
        v = jsonStringify(v);
      }
      json.push((arr ? "" : '"' + k + '":') + String(v));
    }
    return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
  }
}
jsonStringify({ x: 5 })
// "{"x":5}"
jsonStringify([1, "false", false])
// "[1,"false",false]"
jsonStringify({ b: undefined })
// "{"b":"undefined"}"

数组扁平

// 1. 递归
function recursionFlat(arr=[]){
    const res=[]
    arr.forEach(item=>{ // 或for of
        if(Array.isArray(item)){ // 或item.constructor === Array
            res.push(...recursionFlat(item))   // 或res = res.concat(item.flat()) 
        }else{
            res.push(item)
        }
    })
    return res
}

// 2. reduce+递归
function reduceFlat(arr=[]){
    return arr.reduce((pre, cur)=>{
      return pre.concat(Array.isArray(cur)? reduceFlat(cur) :cur)  
    }, [])
}

// 测试
const source = [1, 2, [3, 4, [5, 6]], '7']
console.log(recursionFlat(source))
console.log(reduceFlat(source))

// 3. arr.flat()
console.log(source.flat(Infinity))

// 4. 正则
const res = JSON.parse('['+JSON.stringify(source).replace(/\[|\]/g,'')+']')
console.log(res)

// 5. 转字符串再转数组
arr.prototype.flat = function() {
    this.toString().split(',').map(item=> +item )
}

对象扁平

function objectFlat(obj = {}) {
    const res = {}
    function flat(item, preKey=''){
        Object.entries(item).forEach(([key, val]) => {
            const newKey = preKey? `${preKey}.${key}`:key
            if(val && typeof val === 'object'){
                flat(val, newKey)
            }else{
                res[newKey] = val
            }
        })
    }
    flat(obj)
    return res
}

// 测试
const source = { a: { b: { c: 1, d: 2 }, e: 3 }, f: { g: 2 } }
console.log(objectFlat(source)) // test.html:28 {a.b.c: 1, a.b.d: 2, a.e: 3, f.g: 2}

功能类

deepCopy深拷贝

function deepClone(obj){
	let target
	if(typeof obj === 'object'){
		target = Array.isArray(obj)?[]:{}
		for(let key in obj){
			// 获取自身属性
			if(obj.hasOwnProperty(key)){
				if(typeof obj[key] !== 'object'){
					target[key] = obj[key]
				}else{
					target[key] = deepClone(obj[key])
				}
			}
		}
	}else{
		target = obj
	}
	return target
}

const obj={a:'111', b:'222'}
const nObj = deepClone(obj)
console.log(nObj)

防抖

防抖:当短期内有大量的事件触发时,只会执行最后一次事件关联的任务。
把短时间内连续触发合并成一次,在最后一次触发且超过规定时间才去执行回调函数(某一段时间内只执行一次回调函数)(实时搜索、拖拽、滚动)
节流:将短时间的函数调用以一个固定的频率间隔执行(间隔时间执行)(上拉加载、窗口调整、监听滚动、鼠标频繁点击、拖拽)
如果想在最后一次触发时执行回调就用防抖,如果只想稀释触发频率就用节流

debounce函数返回了一个匿名函数,这个匿名函数被用来作为窗口“move”和“resize”事件的监听器。此处,这个匿名函数内部代码有访问timeout和fn的能力,即使debounce函数已经退出了,这个能力依然存在,这就是JavaScript语言的闭包特性

function debounce(func, wait = 2000, immediate = false){
    let timeout, res
    let db = function(){
        const callnow = immediate && !timeout
        timeout && clearTimeout(timeout)
        timeout = setTimeout(()=>{
            timeout = null
            if(!immediate) res = func.apply(this, arguments)
        }, wait)
        if(callnow) res = func.apply(this, arguments)

        return res
    }
    db.cancel = () =>{
        clearTimeout(timeout)
    }
    return db
}
const action = debounce(function(){
    console.log('scroll~~~~~~~~')
}, 1500, flase)
const button = document.getElementById('cancel')
window.addEventListener('scroll', action, true)
button.addEventListener('click', action.cancel, true)

节流

// 定时器:比防抖少了clearTimeout的操作;判断是否存在timeout,没存在才执行
const throttle=(func, wait=3000)=>{
    let timeout 
    return function(){
        if(!timeout){
            timeout = setTimeout(()=>{
                func.apply(this, arguments)
                timeout = null
            }, wait)
        }
    }
}


// 时间戳
const throttle=(func, wait=3000)=>{
    let startTime = 0
    return function(){
        let handleTime = +new Date()
        if(handleTime-startTime>=wait){
            func.apply(this, arguments)  // 执行回调函数
            startTime = handleTime // 更新开始时间
        }
    }
}


/*  定时器 + 时间戳 = 执行两次(互补优化)
    定时器:让函数在最后一次事件触发后执行,wait毫秒后执行
    时间戳:让函数在时间段开始时执行 
*/
function throttle(func, wait=3000) {
  let startTime, timeout;
  return function() {
    let nowTime = +new Date();
    if (startTime && nowTime < startTime + wait) {
      timeout && clearTimeout(timeout);
      timeout = setTimeout(()=>{
        startTime = nowTime;
        func.apply(this, arguments);
      }, wait);
    } else {
      startTime = nowTime;
      func.apply(this, arguments);
    }
  }
}

function sayHi(){
    console.log('hhhhhhh')
}
button.addEventListener('click', throttle(sayHi))
// 对象.addEventListener(事件名称,事件处理函数,布尔值) ①监听后执行函数 ②this指向当前元素

event事件总线、发布订阅

/* cache = {'event1': [_on, fun2], 'event2': [fun1, fun2]}
    注册:往某个属性,添加函数
    注册一次:把_on函数push到某个属性中,_on只要被emit一次,就在数组中删除
    移除:delete某个属性,或者某个属性对应value值的某个元素
    发布emit:找出某个属性对应的value值,把它们都遍历出来执行
*/
class Event{
  constructor(){
    this.cache = {}
  }
  // 注册监听
  on(eventType, func){
    // 判断是否已存在某个属性,存在就直接插入监听函数,否则先创建空数组,再插入函数
    (this.cache[eventType]||(this.cache[eventType]=[])).push(func)
  }
  /* 监听一次,把之前注册的先移除掉,再注册
    往数组中push的是_on函数,当_on函数被执行,就移除这个函数
  */
  once(eventType, func){
    function _on(){
      func.apply(this, arguments)
      this.off(eventType, _on)
    }
    this.on(eventType, _on)
  }
  // 移除监听,移除某个数组或者某个数组中的一个元素
  off(eventType, func){
    // 根据传入参数,判断是整个移除,还是移除某属性的一个
    if(func){
      const stack = this.cache[eventType]
      if(stack && stack.length>0){
      /*  for(let i=0; i<stack.length;i++){
          if(stack[i]==func){
            console.log(stack[j])
            stack.splice(i, 1)
            break
          }
        }*/
        const index = stack.findIndex((f) => f === fn )
        if (index >= 0) stack.splice(index, 1)
      }
    }else{
      delete this.cache[eventType]
    }
  }
  // 发布订阅通知
  emit(eventType, ...args){
    // 可以考虑创建副本,避免回调函数内继续注册相同事件,会造成死循环this.cache[eventType].slice() 
    const stack = this.cache[eventType] 
    if(stack && stack.length>0){
      stack.forEach(item=> item.apply(this, args) )
    }
  }
}

const ge = new Event()
function fun1(a, b){
    console.log(a, b, a + b)
    return a + b
}
ge.once('event1', fun1)
ge.emit('event1', 1, 2)
// ge.off('event1', fun1)
ge.emit('event1', 1, 2)

sleep

sleep()可以将一个线程睡眠,参数可以指定一个时间
wait()可以将一个线程挂起,直到超时或者该线程被唤醒

const sleep = function(time){
    const startTime = new Date().getTime() + parseInt(time, 10)
    // 不停的while循环,制造阻塞
    while(new Date().getTime() < startTime){}
}
// 测试
function fn(){
    sleep(3000)
    console.log('2222222')
}
fn()
  • 用promise实现
function sleep(s){
	return new Promise(resolve=>{
		setTimeout(resolve,s)
	})
}
// 测试
sleep(1000).then(()=>alert(1))

!async function run() {
	await sleep(1500);
	console.log('start await');
}()

lazyMan

class _LazyMan{
    constructor(name){
        this.runTimer = null
        this.queue = [] 
        this.sayName(name)
    }
    run(){
        if(this.runTimer){
            clearTimeout(this.runTimer)
        }
        // 变成异步队列
        this.runTimer = setTimeout(async()=>{
            for(let asyncFun of this.queue){
                await asyncFun()
            }
            this.queue.length = 0
            this.runTimer = null
        })
        return this // 用来做链式调用
    }
    sleep(second){
        this.queue.push(async()=>{
            console.log(`Sleep ${second} s`)
            return this._timeout(second)
        })
        return this.run() // 返回run()函数,相当于返回this,所以可以链式调用
    }
    sleepFirst(second){
        this.queue.unshift(async()=>{
            console.log(`Sleep first ${second} s`)
            return  this._timeout(second)
        })
        return this.run()
    }
    sayName(name){
        this.queue.push(async()=> console.log(`Hi, This is ${name}!`))
        return this.run()
    }
    eat(food){
        this.queue.push(async()=> console.log(`Eat ${food} ~`))
        return this.run()
    }
    async _timeout(second){
        await new Promise((resolve)=>{
            setTimeout(resolve, second * 1e3)
        })
    }
}

function lazyMan(name){
   return new _LazyMan(name)
}

lazyMan('Hank').sleep(10).eat('dinner').sleepFirst(5)

异步并发量的控制

  • 串行:一次只能做一件事,挨个按顺序执行 (reduce实现)
  • 并发:控制每次只能同时执行n次任务(promise.all出来)
function requestLimit(arr, limit, callback){
   let taskList = [], // 存储并发limit的promise数组
       count = 0

   function  toFetch() {
        // 所有的都处理完了,返回一个resolve(没处理完的继续递归)
        if(count === arr.length) return Promise.resolve() 
		// 取出第count个url, 放入callback里面 , 每取一次count++
        let one = callback(arr[count++]) 
        // 当promise执行完毕后,从数组删除
        one.then(res=>{
            console.log(res)
            taskList.splice(taskList.indexOf(one), 1) 
        })
        asyncList.push(one) //将当前的promise存入并发数组中

        let p = Promise.resolve()
        // 当并行数量达到最大后, 用race比较 第一个完成的, 然后再调用一下函数自身
        if(taskList.length >= limit) p = Promise.race(taskList)
        return p.then(()=> toFetch())
   }

   return toFetch().then(()=> Promise.all(taskList))
}

requestLimit(['url1', 'url2', 'url3', 'url4', 'url5', 'url6', 'url7', 'url8'], 3, fetch).then(res => {
     console.log('fetch end', res) 
})

另一种写法

const mapLimit = (list, limit, callback) => {
       /* 每次递归,从队列头部取出一个执行callback回调函数(这个回调是promise,可以用then,为微任务),
       等到执行微任务的时候,上一个事件循环setTimeout已经执行完成(即调用limit次回调函数)
       执行完判断剩余arr是否为空,为空返回'finish',否则继续递归(继续执行limit次)
       */
        const recursion = (arr) => {
            return callback(arr.shift()).then((res) => {
                if (arr.length > 0) {  // 每执行完limit次,后回来递归limit次
                    return recursion(arr)
                }
                return 'finish' 
            })
        }
        let asyncList = [] // 存放异步任务
        let listCopy = [].concat(list)
        // 递减遍历limit:同时调用了limit次recursion函数,并把结果push到数组里  
        while (limit--) {
            asyncList.push(recursion(listCopy)) 
            console.log(asyncList)
        }
        return Promise.all(asyncList) // 全部执行完毕返回,只要有一个失败就返回
    }
 
    var dataLists = [1,2,3,4,5,6,7,8,9,10] // 任务
    var count = 0 // 计数
    // 每次并发执行3个任务
    mapLimit(dataLists, 3, (url)=>{ // 模拟接口请求
        return new Promise(resolve => {
            count++
            setTimeout(()=>{
                console.log(url, '当前并发量:', count--)
                resolve()
            }, 1000*Math.random())
        });
    }).then(response => {
        console.log('finish', response)
    })

实现并发限制的异步调度器

class Scheduler {
    constructor() {
        this.taskList = []
        this.maxCount = 2
        this.temp = 0
    }
    add(promiseCreator) {
        this.taskList.push(promiseCreator) // fn1 fn2 fn3 fn4
    }
    run() {
        // 先for循环执行两个函数
        for (var i = 0; i < this.maxCount; i++) {
            this.request()
        }
    }
    request() {
        // 处理边界问题
        if (!this.taskList || !this.taskList.length || this.temp >= this.maxCount) {
            return
        }
        this.temp++
        /*
            this.taskList.shift()()相当于拿出数组中第一个执行 timeout().then()
            执行完后再链式调用去调用递归,用变量控制数量
        */
        this.taskList.shift()().then(() => {
            this.temp--
            this.request()
        })
    }
}
const timeout = (time) => new Promise(resolve => {
    setTimeout(resolve, time)
})
const scheduler = new Scheduler()
const addTask = (time, order) => {
    scheduler.add(() => timeout(time).then(() => console.log(order)))
}
console.time()
addTask(1000, 1)
addTask(500, 2)
addTask(300, 3)
addTask(400, 4)
scheduler.run()
console.timeEnd()

串行和并行

function asyncAdd(a, b, callback) {
	setTimeout(function () {
		callback(null, a + b)
	}, 500);
}

// 包装为promise
const promiseAdd = (a, b) => new Promise((resolve, reject) => {
	asyncAdd(a, b, (err, res) => {
		if (err) {
			reject(err)
		} else {
			resolve(res)
		}
	})
})

// 1. 串行处理:使用reduce
async function serialSum(...args) {
	return args.reduce((task, now) => task.then(res => promiseAdd(res, now)), Promise.resolve(0))
}

// 2. 并行处理:递归并行2个数字的累加,最后Promise.all处理
async function parallelSum(...args) {
	if (args.length === 1) return args[0]
	const tasks = []
	for (let i = 0; i < args.length; i += 2) {
		tasks.push(promiseAdd(args[i], args[i + 1] || 0))
	}
	const results = await Promise.all(tasks)
	return parallelSum(...results)  // [3, 7, 13, 19, 23]  => [10, 32, 23] => [42, 23] => [65]
}

// 测试
(async () => {
	console.log('Running...')
	const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
	console.log(res1)
	const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
	console.log(res2)
	console.log('Done')
})()

实现串行

实现一个方法function execute(tasks: Task[]): Promise,该方法将 tasks 内的任务 依次 执行,并返回一个结果为数组的 Promise ,该数组包含任务执行结果(以执行顺序排序) 要求: 忽略异常任务,并在结果数组中用null占位
注意点:无论成功失败,都不能阻断下一个then执行;最后返回每个任务的结果

const Task = (result, isSuccess = true) => {
    return () => new Promise((resolve, reject) => {
        setTimeout(() => {
            if (isSuccess) {
                console.log(`success: ${result}`);
                resolve(result);
            } else {
                console.log(`error: ${result}`);
                reject(result);
            }
        }, 1000);
    });
}
execute([Task('A'), Task('B'), Task('X', false), Task('C'),]).then(resultList => { // 因为有then,所以要返回一个promise
    // 这里期望打印 ["A", "B", null, "C"]
    console.log(resultList)
})

// 串行可以想到reduce,一个个执行;怎么把执行的结果往下传递?使用.then拿到结果,往下concat拼接
function execute(arr) { // 每个arr都是promise
    return arr.reduce((allPromise, curPromise)=>{ 
        // allPromise是被Promise.resolve([])包含的数组,所以可以用.then()
        return allPromise.then((resultList)=>{ // 通过.then拿到存储结果
            return new Promise(resolve=>{ // 最后返回出的必须是promise,execute函数才能用then
                curPromise().then(res=>{
                    resolve(resultList.concat(res)) 
                }).catch(()=>{
                    resolve(resultList.concat(null))
                })
            })
           
       }) 
    }, Promise.resolve([])) // 用来存储执行的结果
}

长列表渲染- 时间分片

requestAnimationFrame + DocumentFragment 每次加载多少条

<ul id="container"></ul>

const ul = document.getElementById('container')
const total = 1000
const pageSize = 20
function loop(curTotal, curIndex){ // 剩余总数,已加载条数
	if(curTotal <= 0) return false
	// 判断当页要加载多少条,可能只需加载5条,所以取最小
	const pageCount = Math.min(curTotal, pageSize) 
	window.requestAnimationFrame(function(){
		let fragment = document.createDocumentFragment()
		for(let i=0; i<pageCount;i++){
			const li = document.createElement('li')
			li.innerText = curIndex+i+':'+ ~(Math.random()*total)
			fragment.appendChild(li)
		}
		ul.appendChild(fragment)
		loop(curTotal-pageCount, curIndex+pageCount)
	})
}
loop(total, 0)

长列表渲染- 虚拟列表

以前用到是懒加载,但是随着加载数据越来越多,浏览器的回流和重绘的开销越来越大,整个滑动会造成卡顿
虚拟列表就是按需显示,只对可见区域进行渲染,对非可见区域中的数据不渲染或部分渲染的技术,从而达到极高的渲染性能

假设有1000条数据,每个列表高度50px
可视区域:视觉可见区域
可滚动区域(滚动容器的内部区别)高度: 1000*50

1)先计算当前可视区域起始数据startIndex和结束数据的endIndex
startIndex = scrollTop/itemHeight(即滚动条高度/列表高度50)
endIndex = startIndex+(clientHeight/itemHeight)
2)再根据startIndex和endIndex取相应范围的数据,渲染到可视区域
3)然后再计算startOffset和endOffset,是一个隐藏区域,会撑开容器元素的内容高度,起到缓冲作用,使其保持滚动的平滑性,还能使滚动条处于正确位置

效果:不论怎么滚动,只改变滚动条高度位置和元素内容,并没有增加多余元素![在

如果列表高度不固定?
方法1:给每个元素绑定获取高度的方法,当结点加载渲染后调用此方法来动态获取元素正式高度,并且将此位置的index和height信息缓存起来
方法2:添加一个获取列表项高度的方法,给这个方法传入item和index,返回对应列表项的高度,每次计算页会将index和height信息缓存起来(每次滑动都需要遍历所有节点找到当前scrollTop对应元素位置,重新计算startIndex和endIndex)
startIndex = getStartIndex(scrollTop) ,getStartIndex就是根据当前滚动位置对应的index(即获取开始索引)(可以用二分查找)

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

优化:使用IntersectionObserver代替scroll滚动事件(避免频繁触发),IntersectionObserver可以监听目标元素是否出现在可视区域内

参考

ajax

const getJson = function(url){
	return new Promise((resolve, reject)=>{
		let xhr = null
		try{
			xhr=new XMLHttpRequest(); 
		}catch(e){
			xhr=new ActiveXObject('Microsoft.XMLHTTP');
		}
		xhr.open('GET', url, false)
		xhr.setRequestHeader('Accept', 'application/json')
		xhr.send()
		xhr.onreadystatechange = function(){
			if(xhr.readyState === 4){ // 响应内容解析完成
				if(xhr.status === 200 || xhr.status === 304){
					resolve(xhr.responseText)
				}else{
					reject(new Error(xhr.responseText))
				}
			}
		}
	})
}

jsonp

function fn(data){
	console.log(data)
}
<script src="https://matchweb.sports.qq.com/kbs/calendar?columnId=10000&callback=fn"></script>

// 后端返回
var callback = `fn$(data)`;  // 返回一个函数调用的js代码 
res.end(callback);  
const jsonp = ({ url, params, callbackName }) => {
  const generateUrl = () => {
    let dataSrc = '';
    for (let key in params) {
      if (Object.prototype.hasOwnProperty.call(params, key)) {
        dataSrc += `${key}=${params[key]}&`;
      }
    }
    dataSrc += `callback=${callbackName}`;
    return `${url}?${dataSrc}`;
  }
  return new Promise((resolve, reject) => {
    const scriptEle = document.createElement('script');
	scriptEle.src = generateUrl()
    document.body.appendChild(scriptEle);
    window[callbackName] = data => {
		resolve(data)
		document.body.removeChild(scriptEle)
    }
  })
}
jsonp({
	url:'https://matchweb.sports.qq.com/kbs/calendar',
	params:{columnId:10000},
	callbackName:'fn'
})

事件代理

var ul = document.getElementsByTagName('ul')[0]
ul.addEventListener('click', show) // 或  ul.onclick = e => {}
function show(e){
	e = e || window.event
	var target = e.target || e.srcElement
	// tagName只能用在元素节点上,而nodeName可以用在任何节点上,可以说nodeName涵盖了tagName
	if(target && target.nodeName.toLowerCase() === 'li'){
		console.log(target.innerHTML)
	}
}

  • vue
<ul @click="change($event)">
  <li v-for="item in menus" class="li" >{{item}}</li>
</ul>

change(e){
  if(e.target.className === 'li'){
    ...
  }
}

img埋点

const imgReport = (type = '', code, detail = {}, extra = {}) => {
	const logInfo = {
		type,
		code,
		detail: detailAdapter(code, detail),
		extra: extra,
		common: {...}
	}
	// 图片打点
	const img = new window.Image()
	img.onload = img.onerror = img.onabort = function(){
      img = null;
    }
	img.src = `${url}?d=${encodeURIComponent(JSON.stringify(logInfo))}`
}

// 使用
window.onload = () => {
  const performance = window.performance
  if (!performance) {
    // 当前浏览器不支持
    console.log('你的浏览器不支持 performance 接口')
    return
  }
  const times = performance.timing.toJSON()

  imgReport('perf', 20001, {
    ...times,
    url: `${window.location.host}${window.location.pathname}`
  })
}

url太长,可以使用HTTP/2 头部压缩,否则采用sendBeacon的方式发送请求,如果sendBeacon方法不兼容,则发送AJAX post同步请求

const reportData = url => {
    // ...
    if (urlLength < 2083) {
        imgReport(url, times)
    } else if (navigator.sendBeacon) {
        sendBeacon(url, times)
    } else {
        xmlLoadData(url, times)
    }
}

网页访问量很大,可以给上报设置一个采集率

// 设置采集率,采集30%,从而减缓服务器的压力
const reportData = url => {
    // 只采集 30%
    if (Math.random() < 0.3) {
        send(data) // 上报错误信息
    }
}

vnode渲染成真实dom

function render(vnode, container) {
	container.appendChild(_render(vnode))
}

function _render(vnode) {
	const dom = document.createElement(vnode.tag)
	if (vnode.attrs) {
		// 遍历属性
		Object.keys(vnode.attrs).forEach(key => {
			const value = vnode.attrs[key]
			dom.setAttribute(key, value)
		})
	}
	if(Array.isArray(vnode.children)){
		// 子数组进行递归操作
		vnode.children.forEach(child => {
			render(child, dom)
		})
	}else{
	  	dom.appendChild(document.createTextNode(vnode.children))
	}
	return dom
}

class Vnode {
	constructor(tag, attrs, children) {
		this.tag = tag 
		this.attrs = attrs
		this.children = children
	}
}

function createElement(tag, attrs, children){
	return new Vnode(tag, attrs, children)
}

// createElement('div', {id:'app'}, '哈哈哈')
// createElement('div', {id:'app'}, [createElement('span', {}, 'hahaha')])
render(createElement('div'), document.getElementById('container'))

把object变成可枚举

const newObj={a:'1111', b:'2222'}
newObj[Symbol.iterator]=function*(){
	let keys = Object.keys(this)
	for(let i=0;i<keys.length;i++){
		yield {
			key: keys[i],
			value: this[keys[i]] 
		}
	}
}

for(let v of newObj){
	console.log( v );
}


// 在class扩展
class Obj{
    constructor(name, gender){
        this.name = name;
        this.gender = gender;
    }
    *[Symbol.iterator](){
        let keys = Object.keys( this ) 
        for(let i = 0, l = keys.length; i < l; i++){
            yield {
                key: keys[i]
                , value: this[keys[i]]
            };
        }
    }
}

把扁平数据转成树形数据

function treeData(source, id, parentId, children){   
    let cloneData = JSON.parse(JSON.stringify(source))
    return cloneData.filter(father=>{
        let branchArr = cloneData.filter(child => father[id] == child[parentId]);
        branchArr.length>0 ? father[children] = branchArr : ''
        return father[parentId] == 0        // 如果第一层不是parentId=0,请自行修改
    })
}

 data : [
    {id:1,parentId:0,name:"一级菜单A",rank:1},
    {id:2,parentId:0,name:"一级菜单B",rank:1},
    {id:3,parentId:0,name:"一级菜单C",rank:1},
    {id:4,parentId:1,name:"二级菜单A-A",rank:2},
]

效果类

图片懒加载

懒加载原理:
1)给所有img标签src设置占位图,把真正图片地址设置到img标签的自定义属性上
2)当监听该图片进入可视区域内,把自定义属性的地址赋值给src属性

<img src='https://hbimg.huabanimg.com/675a9b3ccdb7bde620c0a43daf7c7178a1c4552ea09e-9Q9ZHk_fw658' data-src="https://hbimg.huabanimg.com/a2a2798dbb577e858c62a850a6c03550fe097c69102185-H5iRia_fw658"/>
<img src='https://hbimg.huabanimg.com/675a9b3ccdb7bde620c0a43daf7c7178a1c4552ea09e-9Q9ZHk_fw658' data-src="https://hbimg.huabanimg.com/e30c18da9f84b5aa3e3f2661345a7942202d2aad1909bf-dmnIrk_fw658"/>
<img src='https://hbimg.huabanimg.com/675a9b3ccdb7bde620c0a43daf7c7178a1c4552ea09e-9Q9ZHk_fw658' data-src="https://hbimg.huabanimg.com/dcd00752fe974f8280b8297e59983c9d8797589413bf0b-gZ3byJ_fw658"/>

可视区域判断:

  • 方法1:图片到顶部的距离 - 可视区域 <= 窗口滚动距离
    offsetTop < clientHeight + scrollTop
function lazyload() {
  const imgs = document.getElementsByTagName('img')
  const len = imgs.length
  const clientHeight = document.documentElement.clientHeight // 视口的高度
  // 滚动高度 
  const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop  
  for (let i = 0; i < len; i++) {
 	// 图片到顶部的距离,或img[i].getBoundingClientReact().top
    const offsetHeight = imgs[i].offsetTop 
    if (offsetHeight < clientHeight + scrollHeight) {
      const src = imgs[i].dataset.src
      imgs[i].src = src
    }
  }
}

// 可以使用节流优化一下
window.addEventListener('scroll', lazyload);
  • 方法2:element.getBoundingClientRect().top < clientHeight
    Element.getBoundingClientRect()指距离视图的而高度,offsetTop包括滚动条卷起的部分(有很多浏览器兼容性问题,慎用)

  • 方法3:IntersectionObserver(交叉观察器)作为构造函数,传入一个回调函数作为参数,生成一个实例observer,用这个observer观察img图片,判断它是否进入窗口 代码参考

  • 方法4

function isVisible(el) {
  const position = el.getBoundingClientRect()
  console.log(position)
  const windowHeight = document.documentElement.clientHeight
  // 顶部边缘可见
  const topVisible = position.top > 0 && position.top < windowHeight;
  // 底部边缘可见
  const bottomVisible = position.bottom < windowHeight && position.bottom > 0;
  return topVisible || bottomVisible;
}

function imageLazyLoad() {
  const images = document.querySelectorAll('img')
  // 获取自定义属性上的真实图片地址,等进入视图时,赋值到src属性上
  for (let img of images) {
    const realSrc = img.dataset.src
    if (!realSrc) continue
    console.log(isVisible(img))
    if (isVisible(img)) {
      img.src = realSrc
      img.dataset.src = ''
    }
  }
}

// 测试
window.addEventListener('load', imageLazyLoad)
window.addEventListener('scroll', imageLazyLoad)
// window.addEventListener('scroll', throttle(imageLazyLoad, 1000)) 

首屏loading加载

var loadingProcess = document.getElementById("loading")
var picArr = ['https://img.alicdn.com/i3/2787846918/O1CN01iCFjzy20yUZ3BJCpy_!!2787846918.jpg',
	'https://img.alicdn.com/imgextra/i2/2819532551/O1CN01E10NIY1UiOzB9m6iC_!!2819532551.jpg',
	'https://img.alicdn.com/i2/2200552342840/O1CN01acTsrT1WqlSVkcB5n_!!2200552342840.jpg',
	'https://img.alicdn.com/bao/uploaded/TB1dys9UVzqK1RjSZFCXXbbxVXa.png',
	'https://img.alicdn.com/imgextra/i1/2672045966/O1CN01YHKBO51twTXhAbijN_!!2672045966.jpg',
	'https://img.alicdn.com/imgextra/i1/2741904141/O1CN01rrz5Yy1gSck7hAkQp_!!2741904141.jpg'
]

function preLoad(){
	var img =  new Image()
	var sum = picArr.length
	var now = 0

	loadImg()
	function loadImg(){
		img.src = picArr[now]
		function go () {
			now ++ 
			loadingProcess.innerHTML = parseInt( now/sum *100 ) + "%"
			if(now < picArr.length){
				loadImg()
			}else{
				// console.log("全部加载完成")
				// 其他操作
				// box.style.opacity = "1"
				// loadingProcess.style.display = "none"
			}
		}
		img.onerror = go
		img.onload = go
	}
}

preLoad()

写一个方法生成随机色值,例如#c1c1c1

function bg1(){
   return '#'+Math.floor(Math.random()*0xffffff).toString(16)
}

function bg2(){
   var colorStr="";
   var randomArr=['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f']; //字符串的每一字符的范围
   //产生一个六位的字符串
   for(var i=0;i<6;i++){
     //15是范围上限,0是范围下限,两个函数保证产生出来的随机数是整数
     colorStr+=randomArr[Math.ceil(Math.random()*(15-0)+0)];
   }
   console.log( `#${colorStr}`)
   return `#${colorStr}`
}

函数式编程

柯里化curry

只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数

var currying = function(fn) {
    // args 获取第一个方法内的全部参数
    var args = Array.prototype.slice.call(arguments, 1)
    return function() {
        // 将后面方法里的全部参数和args进行合并
        var newArgs = args.concat(Array.prototype.slice.call(arguments))
        // 把合并后的参数通过apply作为fn的参数并执行
        return fn.apply(this, newArgs)
    }
}

无限累加函数(边收集参数,边累加)

add(1)(2)(3)

function add(a) {
    function _add(b) { // 使用闭包
        a = b ? a + b : a // 累加 有没有b,有就相加,没有就返回a,最后赋值给a
        return add // 返回函数自己,所以可以链式调用,
    }
    // 先执行这步,往函数传入参数
    // console.log调用,返回函数toString方法
    _add.toString = function() { 
        return a   
    }
    // 调用_add之后,返回的函数就通过闭包的方式记住了add的第一个参数
    return _add; // 返回一个add函数处理剩下的参数
}

console.log(add(1))              // 1
console.log(add(1)(2))          // 3
console.log(add(1)(2)(3))        // 6
console.log(add(1)(2)(3)(4))     // 10 

无限累加函数II (先把参数收集起来,最后再累加)

add(2, 3)(2)

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments)  //  [].slice.call()、Array.fomr()
    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    //  相当于return function(){} ,第二个执行括号()的时候开始执行里面的函数
    var _add = function() {
        _args.push(...arguments)
        return _adder
    }

    // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    // console.log()访问的时候就会走.toString
    _add.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b
        })
    }
    return _add
}
add(1)(2)(3)
// 简洁写法
// 使用递归add来收集所有累加项;使用reduce相加
function add (...args) {
  const f = (...rest) => {
    return add(...[...args, ...rest])
  }
  f.toString = () => {
    return args.reduce((x, y) => x + y, 0)
  }
  return f
}
add(1, 2, 3) // 6
add(2, 3)(2)  // 7
add(2)(4, 1)(2) // 9

无限累加函数III(把curry和sum拆离)

function curry(fn) {
    return function(){ // 再返回一个函数,避免产生闭包
        var arr = [...arguments]
       /*  function _add() {
            if (arguments.length > 0) {
                arr=arr.concat([...arguments])
                return arguments.callee
            } else {
                return fn.apply(null,arr)
            }
            return _add
        }
        _add.toString = function(){
            return _add()
        } */

     // 简写
        function _add() {
            arr = arr.concat([...arguments])
            return _add
        }
        _add.toString = () => fn(...arr)

        return _add
    }
}

function sum(){
    let arr=Array.from(arguments)
    return arr.reduce((pre, cur)=> pre+cur, 0)
}

let fn=curry(sum)

console.log(fn(1)(2)(3))
console.log(fn(1, 2)(3)(4))

Add(1)(2)(3).sumOf()

// 先收集参数到一个数组,等调用sumof时,才调用累加函数返回结果
// 链式调用要自己返回自己
function Add(...args){
   if(!Add.arr) Add.arr = []
    Add.arr.push(...args)
    return Add
}
Add.sumOf = function(){
    console.log(Add.arr)
    return Add.arr.reduce((pre, cur)=> pre+cur, 0)
}
console.log(Add(1)(2)(3).sumOf())

链式调用

var obj = {
    a(val) {
        console.log(val);
        return this;
    },
    b(val) {
        console.log(val);
        return this;
    },
};
obj.a(1).b(2);

代码组合compose

function add(x){
	return x+'a'
}
function minus(x){
	return x+'b'
}
function mult(x){
	return x+'c'
}
const compose = (...funs)=>{
	// return funs.reduce((a,b)=>(...args)=>b(a(...args)));   // 从左到右 2abc
	// return funs.reduce((a,b)=>(...args)=>a(b(...args))); // 从右到左 2cba  
	// return funs.reduceRight((a,b)=>(...args)=>a(b(...args)));   从左到右 2abc
	// return arg => funs.reduce((a,b) => b(a), arg) // 从左到右 2abc
	return arg => funs.reduceRight((a,b) => b(a), arg) // 从右到左 2cba
}
console.log(compose(add, minus, mult)(2)) 

算法类

LRU算法

Least Recently Used 最近最久未使用,应用场景:缓存(有些数据经常被访问,把数据放到内存中缓存下来,下次取得时候从缓存取数据,这样就不用每次都从数据库里查。但是缓存容量有效,容量不足的时候要清除久的数据,LRU算法就是经量剔除很久没访问过的内存数据,达到保证缓存数据又有效性的效果)

  • 一个大容量,两个api(put、get)
  • 保证都是O(1)时间复杂度(访问快)
  • 上一次访问的元素在第一位
/*
   假设最近使用在队尾,优先删除第一个
   Map{0:{key:1, value:{value: 1, time: 1606811673670}}}
 */
class LRUCache {
	constructor(capacity, expirationTime) {
		this.cache = new Map() // 一般用双向链表+散列表,map的key有序
		this.capacity = capacity // 最大容量
		this.expirationTime = expirationTime // 过期时间
	}
	get(key) {
        if (!this.cache.has(key)) return -1 // 查不到对应的k-value
		const tempValue = this.cache.get(key)
        this.cache.delete(key)
		if (Date.now() - tempValue.time > this.expirationTime) { // 超过缓存时间,查询不到
			return -2
		}
		this.cache.set(key, {
			value: tempValue.value,
			time: Date.now()
		})
		return tempValue.value
	}
	put(key, value) { 
		if (this.cache.has(key)) this.cache.delete(key) // 如果已存在就删掉
		if (this.cache.size >= this.capacity) { // 内存满了,就删掉第一个创建的
			const keys = this.cache.keys()
			this.cache.delete(keys.next().value) //  {value: "1", done: false} value为key的名字
		}
		this.cache.set(key, {
			value,
			time: Date.now()
		})
	}
}

const cache = new LRUCache(2, 1606813636435)
cache.put(1, 1)
cache.put(2, 2222)
cache.put('3', '3333')
console.log(cache.get(1)) // -1
console.log(cache.get(2)) // -1
console.log(cache.get('3')) // -1
console.log(cache)

参考1

参考2

正则

  1. 截取
let src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA......'
let res = src.match(/^data:image\/([a-zA-Z]+);/)[1]
console.log(res)

其它

  1. 判断是否回文
var str = 'resiviser';
function reserve(str) {
  return str === str.split('').reverse().join('');
}
  1. 递归阶乘
function factorial(num) {
  if(num <= 1) return 1;
  return num * factorial(num - 1);
}
  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值