文章目录
- js原生类
- new
- call、apply、bind
- instanceof
- Object.create
- Object.is
- Object.assign
- const
- promise
- promise其他静态属性和原型方法
- promise.allSettled
- Array.prototype.filter(返回满足条件的集合)
- Array.prototype.every(全部满足才为true,some是一个满足为true)
- Array.prototype.map
- Array.prototype.foreach
- Array.prototype.reduce
- Array.prototype.includes
- Array.prototype.indexOf
- JSON.stringify()
- 数组扁平
- 对象扁平
- 功能类
- 效果类
- 函数式编程
- 算法类
- 正则
- 其它
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)
正则
- 截取
let src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA......'
let res = src.match(/^data:image\/([a-zA-Z]+);/)[1]
console.log(res)
其它
- 判断是否回文
var str = 'resiviser';
function reserve(str) {
return str === str.split('').reverse().join('');
}
- 递归阶乘
function factorial(num) {
if(num <= 1) return 1;
return num * factorial(num - 1);
}