前端高薪每日进步

0 阅读4分钟

1、使用代理实现单例模式

class myVideo{
  constructor(){
    
  }
}

//代理函数
function singleton(className){
  let ins = null
  const proxy = new Proxy(className,{
    construct(target,args){
      if(!ins){
        ins = Reflect.construct(target,args)
      }
      return ins
    }
  })
  className.prototype.constructor = proxy
}

const myVideoSingleton = singleton(myVideo)
export default myVideoSingleton

// 用法
import MyVideo from './xxx'
const v1 = new MyVideo()
const v2 = new MyVideo()
console.log(v1 === v2) // true 为同一个实例

2、用发布订阅模式解耦

// eventEmitter.ts 文件
const eventNames = ['API:UN_AUTH','API:VALIDATE_ERROR'] as const 

type EventNames = (typeof eventNames)[number];

class EventEmitter{
  private listeners:Record<EventNames,Function[]> = {
    'API:UN_AUTH':new Set(),
    'API:VALIDATE_ERROR':new Set()
  }

  // 监听事件
  on(eventName:EventNames,listener:Function){
    this.listeners[eventName].add(listener)
  }

  // 触发事件
  emit(eventName:EventNames,...args:any[]){
    this.listeners[eventName].forEach((listener)=>listener(...args))
  }
}
export default new EventEmitter()

// request.ts 文件
import axios,{AxiosResponse} from 'axios';
import eventEmitter from './eventEmitter'

// 创建axios实例
const ins = axios.create({
  baseUrl:'http://localhost:300'
})

const successHandler = (res:AxiosResponse):any =>{
  // 成功操作
}

const errorHandler = (error:any):any =>{
  if(error.response.status === 401){
    // 使用订阅器
    eventEmitter.emit('API:UN_AUTH')
  }
}

ins.interceptors.response.use(successHandler,errorHandler)

3、浏览器中的跨标签页的通信

常见方案:BroadCast Channel、SerVice Worker、LocalStorage window.onstorage 监听、Websocket、window.open window.postMessage

const channel = new BroadCastChannel('sync-update') // 创建通信频道实例

// 发送消息
export function sendMsg(type,msg){
  channel.postMessage({
    type,
    msg
  })
}

// 监听消息
export function listenMsg(callback){
  const handelr = (e)=>{
    callback && callback(e.data)
  }
  channel.addEventListener('message',handelr)
  // 取消监听
  return ()=>{
    channel.removeEventListener('message',handelr)
  }
}

// 使用
const addData = {a:1}
sendMsg('add',{...addData})
listenMsg(info=>{
  console.log(info.type) // 'add'
  console.log(info.data) // {a:1}
})

4、手写promise

const PRNDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise{
  // 定义私有属性
  #state = PRNDING;
  #result = undefined;
  #handler = []
  // 构造器
  constructor(executor){
    const resolve = (data)=>{
      this.#changeState(FULFILLED,data);
    };
    const reject = (reason)=>{
      this.#changeState(REJECTED,reason);
    }
    try{
      executor(resolve,reject)
    }catch(err){
      reject(err)
    }
  }

  #changeState(state,result){
    if(this.#state != PRNDING) return;
    this.#state = state
    this.#result = result
    this.#run()
  }

  #run(){
    if(this.#state == PENDING) return
    while(this.#handler.length){
      const {onFulfilled,onRejected,resolve,reject} = this.#handler.shift()
      if(this.#state == FULFILLED){
        if(typeof onFulfilled === 'function'){
          onFulfilled(this.#result)
        }
      }
      else{
         if(typeof onRejected === 'function'){
          onRejected(this.#result)
        }
      }
    }
  }

  then(onFulfilled,onRejected){
    return new MyPromise((resolve,reject)=>{
      // 记录--为了处理pending挂起状态而定义
      this.#handler.push({
        onFulfilled,
        onRejected,
        resolve,
        reject
      })
      this.#run()
    })
  }
}

const p = new MyPromise((resolve,reject)=>{
  resolve(13)
})
p.then((res)=>{
  console.log('promise 完成',res)
},(err)=>{
  console.log('promise 失败',err)
})

5、手写promise.all

Promise.myAll = function(proms){
  let res, rej
  const p = new Promise((resolve,reject)=>{
    res = resolve
    rej = reject
  })
  let i = 0
  let fulfilled = 0
  const result = []
  for(const prom of proms){
    const index = i; // 记录位置
    i++;
    // 转为promise,防止有些不是promise的参数
    Promise.resolve(prom).then((data)=>{
      // 1.完成的数据汇总到最终结果
      result[index] = data
      fulfilled++
      // 2.判断是否全部完成
      if(fulfilled === i){
        res(result)
      }
    },rej)
  }
  if(i == 0){
    res([])
  }
}

6、大量任务执行的调度

/**
1.运行一个耗时任务
2.如果要异步执行,请返回promise
3.要尽快完成任务,同时不能让页面产生卡顿
*/
function runTask(task){
  return new Promise((resolve)=>{
    _runTask(task,resolve)
  })
}

function _runTask(task,callback){
  reqestIdleCallback((deadline)=>{
    if(deadline.timeRemaining() >  0){
      task();
      callback()
    }else{
       _runTask(task,callback)
    }
  })
}

7、并发请求


// urls 请求url 数组
// maxNum 最大并发数
function concurRequest(urls,maxNum){
  if(urls.length === 0) return 	Promise.resolve([])
  return new Promise((resolve)=>{
    
    let index = 0; // 指向下一次请求的url对应的下标
    const result  = [] // 保存的结果
    const count  = 0 // 当前请求完成的数量
    
    async function _request(){
      const i = index // 当前请求对应的原来的位置
      const url = urls[index]
      index++;
      try{
        const resp = await fetch(url);
        result[i] = resp;
      }catch(err){
        result[i] = err
      }finally{
        count++
        if(count === urls.length){
          resolve(result)
        }
        if(index < urls.length){
          _request()
        }
      }
    }

    for(let i = 0;i<Math.min(urls.length,maxNum);i++){
      _request()
    }
  })
}

8、深拷贝

function deepClone(target,map = new Map()){
  // null或者是基础数据类型则直接返回
  if(target == null || typeof target !== "object") return target;
  // 如果是日期或者是正则
  if(target instanceof Date) return new Date(target)
  if(target instanceof RegExp) return new Date(RegExp)

  // 从map中获取有没有已经初始化的对象
  if(map.has(target)) return map.get(target)

  // 是否是数组
  const cloneTarget = Array.isArray(target) ? [] : {}

  // 存储到map中
  map.set(target,cloneTarget)

  for(const key of Reflect.ownKeys(target)){
    cloneTarget[key] = deepClone(target[key],map)
  }
  return cloneTarget
}

9、图片资源优化

  1. 资源选择:logo类的图片首选svg格式的;其它的首选webp 现代格式,如不支持的话可以用picture标签来兼容自上而下的选择图片资源类型
  2. 加载策略:按需加载(响应式图片和懒加载)。响应式:可以采用image标签的srcset(提供图片资源清单)和sizes(图片大小)属性,懒加载则是延迟加载非视口内的图片,可以采用image标签的loading为lazy,自定义加载动画等。监听一个元素是否进入视图可以采用:intersectionOberver api 来监听从而触发图片懒加载
  3. 分发链路:从架构上分析可以采用cdn来存放图片资源

10、html上的js资源加载失败如何重试

<html>
  <head>
    <meta/>
    <meta/>
    <title>Document</title>
    // 1、什么时候重试
    //因为js加载是同步执行的,所以要使用htmlplugin插件在这里插入一段script脚本
    <script>
      // 备用域名映射
      const domains=['http://aa','http://b']
      // 重试次数
      const retry = {}
      window.addEEventlistener('error',(e)=>{
        if(e instanceof ErrorEvent || e.target.tagName !== 'SCRIPT') return
        const src = e.target.src

        const url = new URL(src)
        const key = url.pathname
        if(!(key in retry)){
          retry[key] = 0;
        }
        const index = retry[key]
        if(index >= domains.length){
          return // 超出重试次数
        }
        const host = retry[index]
        retry[key]++
        url.host = host
        // 创建一个新的执行脚本script,而且要同步执行用document.write
        document.write(`\<script src="${url}"><\/script>`)
        e.target.remove()
      },true)
    </script>
  </head>
  <body>
    <script src="http://static.com/js/1.js"></script>
    <script src="http://static.com/js/2.js"></script>
    <script src="http://static.com/js/3.js"></script>
  </body>
</html>

11、页面滚动加载更多性能更好方案(intersectionOberver api)

// 创建重叠度观察器
const ob = new IntersectionOberver(
  // entries 为数组,因为可以观察多个元素
  async(entries)=>{
    for(const entry of entries){
      // 判断是否是进入状态
      if(entry.isIntersecting){
        if(!isloading){
          isloading = true
          await more() // 加载更多
          isloading = false
        }
      }
    }
    
  },{
  // root:null // 默认重叠元素视口
  threshold:0, // 重叠阈值(loadinng元素和视口的重叠度(0-1))
})

// 加载元素
const dom = doucment.querySelector('.loading')
// 观察元素
ob.observe(dom)

12、静态资源的动态访问--vue3+vite

vite打包的依赖分析:打包的时候由于是编译状态,自动依赖可以发现

  1. 多媒体元素的静态连接
  2. 样式中的静态连接
  3. 动态导入语句中的静态或者半静态连接
  4. URL构造器中的静态或者半静态连接

方案:
1、静态资源从src下的assets移动到public,弊端丢失了文件指纹
2、动态导入语句中的静态或者半静态连接

3、URL构造器中的静态或者半静态连接

// 动态导入语句中的静态或者半静态连接
function handleChange(val) {
  import(`./assets/${val}.jpg`).then((m)=>{
    path.value = m.default
  })
}

// URL构造器中的静态或者半静态连接 --- 推荐做法
function handleChange(val) {
  const url = new URL(`./assets/${val}.jpg`,import.meta.url)
  path.value = url.pathname
}