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、图片资源优化
- 资源选择:logo类的图片首选svg格式的;其它的首选webp 现代格式,如不支持的话可以用picture标签来兼容自上而下的选择图片资源类型
- 加载策略:按需加载(响应式图片和懒加载)。响应式:可以采用image标签的srcset(提供图片资源清单)和sizes(图片大小)属性,懒加载则是延迟加载非视口内的图片,可以采用image标签的loading为lazy,自定义加载动画等。监听一个元素是否进入视图可以采用:intersectionOberver api 来监听从而触发图片懒加载
- 分发链路:从架构上分析可以采用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打包的依赖分析:打包的时候由于是编译状态,自动依赖可以发现
- 多媒体元素的静态连接
- 样式中的静态连接
- 动态导入语句中的静态或者半静态连接
- 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
}