# 推荐手写网站:

https://bigfrontend.dev/zh/

# 知识点

# fill 方法填充同一个数据

//const a = new Array (3).fill (new Array ()); 和下面相同
const a = new Array(3).fill([]);
a[0].push(14);
console.log(a); // 每一项都改变

# 判断是否是奇数

if(sum & 1)

# Array (n) 不调用 fill 不会生成属性

  • 当数组是稀疏空位时,map 方法不对其生效
Array(2).map(item=>"test") // 仍然是两个空属性
Array(2).fill().map(item=>"test") // 需要调用 fill 方法

# 注意引用数据类型

例如:下面两种写法看似相同实则不同。

const dp1 = Array(m).fill(Array(n).fill(0));
    const dp2 = Array(m).fill().map(item=>Array(n).fill(0));
    console.log(dp1[0]===dp1[1],dp2[0]===dp2[1]);

# arr.keys () 和 Object.keys ()

arr.keys 是数组原型上的方法,返回一个迭代器,Object.keys 是静态方法传入对象,返回数组。
arr.values 、arr.entires 和 arr.keys 一样,都是返回一个迭代器。

# arguments 和剩余参数

  • 由于 arguments 是类数组对象,其数字属性被 v8 排序后和数组相同,因此可以被 Array.from(args) 使用 (Array.from 接收类数组对象或者可迭代对象,其中类数组对象必须要有 length 属性)
  • 由于 arguments 内置了迭代器,因此可以被 for of 遍历、被用于 使用展开运算符 、并且可以被传入 fn.apply(this,args) 进行使用
  • 剩余参数是数组。

# 可迭代和可枚举

依赖于对象可迭代的 api:

  • for(let item of iterable){}
  • 数组的展开运算符 ...iterable
  • fn.apply(this,iterable)
  • Promise.all (iterable) 和 Promise.race (iterable)
  • Map (iterable) 和 Set (iterable) 构造函数等
  • 数组的解构赋值

依赖于对象可枚举的 api:

  • for(let item in enumerable){}
  • 对象的展开运算符 ...enumerable
  • Object.defineProperty(enumerable,...)
  • Object.keys(enumberable)
  • Object.values(enumberable)
  • Object.assign (enumberable) 等

注意: Array.from(arrayLike) 接收一个可迭代对象或者类数组对象,类数组对象必须要有 length 属性.

# fn.length

通过 fn.length 可以获取到该 fn 函数需要接收几个参数( 默认参数和剩余参数不纳入 fn.length 的计算,箭头函数也可以 )

# 默认参数与闭包变量

可以使用默认参数当作闭包变量,以防止全局污染(注意递归调用时需要传入闭包变量)

function closure(arg1,map=new Map(),num=arg1) {
    // 使用默认参数当作闭包变量,可以避免全局污染
    return function () {
        map.set(num++,num++);
        console.log(map);
        //closure (100,map,num); 注意如果这样当函数递归调用时需要传入闭包变量以免被重新赋值
    }
}
const a = closure(0);
a();
a();

# JS 手写

# LRU 缓存:最近最久未使用

class LRUCache{
  constructor(capacity) {
    this.max = capacity;
    this.cache = new Map();
  }
  get(key) {
    const cache = this.cache;
    // 每次获取时,都要重新设置排序
    if (cache.has(key)) {
      const temp = cache.get(key);
      cache.delete(key);
      cache.set(key, temp);
      return temp;
    }
    return -1;
  }
  put(key,value) {
    const cache = this.cache;
    // 每次重复添加时,都要先删除之前的项
    if (cache.has(key)) cache.delete(key);
    else if (cache.size > this.max) {
      //keys 中第一个就是最久没使用的项
      cache.delete(cache.keys().next().value);
    }
    cache.set(key, value);
  }
}
let lRUCache = new LRUCache(2);
lRUCache.put(1, 0);
console.log(lRUCache.get(1));
lRUCache.put(2, 2);
lRUCache.put(3, 3); 
console.log(lRUCache.get(2));
lRUCache.put(4, 4);
console.log(lRUCache.get(1));
console.log(lRUCache.get(3));
console.log(lRUCache.get(4));

# n 数之和

const nSumTarget = (arr, n, startIndex, target) => {
  const res = [];
  const len = arr.length;
  if (len < n) return [];
  if (n < 2) {
    if (arr.includes(target)) return [target];
    else return [];
  }
  if (n === 2) {
    // 两数之和
    let left = startIndex;
    let right = len - 1;
    while (left < right) {
      const leftValue = arr[left];
      const rightValue = arr[right];
      const sum = leftValue + rightValue;
      if (sum < target) {
        while (arr[left] === leftValue) {
          left++
        }
      } else if (sum > target) {
        while (arr[right] === rightValue) {
          right--;
        }
      } else {
        res.push([leftValue, rightValue]);
        while (left < right && arr[left] === leftValue) {
          left++;
        }
        while (left < right && arr[right] === rightValue) {
          right--;
        }
      }
    }
  } else {
    //n 数之和,遍历从 startIndex 到数组尾,递归调用两数之和
    for (let i = startIndex; i < len; i++) {
      let sub = nSumTarget(arr, n - 1, i + 1, target - arr[i]);
      // 获取到两项之和的数组,进行添加
      for (let itemArr of sub) {
        itemArr.push(arr[i]);
        res.push(itemArr);
      }
      // 去重
      while (i < len - 1 && arr[i] === arr[i+1]) {
        i++;
      }
    }
  }
  return res;
};
let nSum = function (arr, n, target) {
  // 数组排序
  arr.sort((a, b) => a - b);
  // 从 0 开始调用 nSum
  return nSumTarget(arr, n, 0, target);
};
console.log(nSum([1, 6, 3, 8, 3, 5, 1, 4, 2], 4, 13));

# 两个日期中间的有效日期

/**
 * 两个日期中间的有效日期
 */
function rangeDay(day1, day2) {
    const oneDayTime = 24 * 60 * 60 * 1000;
    let dayToPush = Math.min(+day1, +day2) + oneDayTime;
    const res = [];
    while (dayToPush < day2) {
        res.push(new Date(dayToPush).toLocaleDateString().replace(/\//g,'-'));
        dayToPush += oneDayTime;
    }
    return res;
}
console.log(rangeDay(new Date("2015-02-08"), new Date("2015-03-03")));

# 实现函数重载

/**
 * 实现函数重载
 * 利用了闭包和相同函数递归调用的原理
 */
function addMethod(object,name,fn){
    // 给 object 添加 name 方法,根据类型执行 fn
    let old = object[name];
    object[name] = function () {
        if (fn.length === arguments.length) { // 默认参数无论传不传都不参与 length 的计算之中
            // 如果函数传入的长度和当前长度相同就执行
            return fn.apply(this, arguments);
        } else if (typeof old === "function") {
            return old.apply(this, arguments);
        } else {
            throw new Error("请保证参数正确");
        }
    }
};
const searcher = {}; // 将对象中对应属性的重载方法全部存储起来
addMethod(searcher,'getUsers',()=>{
    console.log('查询所有用户');
});
addMethod(searcher,'getUsers',(name)=>{ // 注意默认参数的形参数量不与计数
    console.log('按照姓名查询用户');
});
addMethod(searcher, "getUsers", (name, seatch) => {
    console.log('查多个');
})
searcher.getUsers();
/**
 * 或者使用 map 进行存储
 */
function createOverload(){
    const fnMap = new Map();
    function overload(...args){
        const key = args.map((it) => typeof it).join(',');
        const fn = fnMap.get(key);
        if(!fn){
            throw new TypeError('没有找到对应的实现');
        }
        return fn.apply(this,args);
    }
    overload.addImpl = function(...args){
        const fn = args.pop();
        if(typeof fn !== 'function'){
            throw new TypeError('最后一个参数必须是函数')
        }
        const key = args.join(',');
        fnMap.set(key,fn);
    };
    return overload;
}
const getUsers = createOverload();
getUsers.addImpl(()=>{
    console.log('查询所有用户');
});
getUsers.addImpl('number',(page,size=10)=>{
    console.log('按照页码和数量查询用户');
});
getUsers.addImpl('number','number',(page,size=10)=>{
    console.log('按照页码和数量查询用户');
})
getUsers.addImpl('string',(name)=>{
    console.log('按照姓名查询用户');
});
getUsers.addImpl('string','string',()=>{
    console.log('按照性别查询用户');
});
getUsers('asfsdf');

# 滚动加载

/**
 * 滚动加载
 */
window.addEventListener('scroll',function (e) {
	const clientHeight = document.documentElement.clientHeight; // 视口高度
	const scrollTop = document.documentElement.scrollTop; // 滚动高度
    const scrollHeight = document.documentElement.scrollHeight; //DOM 整体高度
	console.log(clientHeight+scrollTop,scrollHeight);
	if(clientHeight + scrollTop >= scrollHeight){ // 滚动到最底部了
		window.scrollTo(0,0)
	}
})

# 手撕 repeat

function myRepeat(str,n) {
    // 将 str 重复 n 次
    let res = '';
    while (n--) {
        res += str;
    }
    return res;
}
function myRepeat(str, n) {
    return new Array(n+1).join(str)
}
console.log(
    myRepeat('ab',4)
);

# 实现 promisify

/**
 * 实现 promisify
 */
const returnPromiseFn = promisify(() => {
    setTimeout(() => {
        console.log('执行完毕');
    }, 1000);
})
returnPromiseFn('参数').then(res => {
    console.log(res,'执行完毕');
})
function promisify(fn) {
    // 将异步的回调函数写法,变为 promise 式的链式调用的方式
    return (...args) => {
        return new Promise((resolve, reject) => {
            fn(...args, (err, data) => {
                if (err) reject(err);
                else resolve(data);
            })
        })
    }
}

# 实现 timeout

let timerID = setMyTimeout(() => console.log("你好"), 1000);
function setMyTimeout(fn,time) {
    const {port1,port2} = new MessageChannel();
    const timeStamp = performance.now();
    port2.onmessage = (a) => {
        let now = performance.now();
        console.time("执行");
        while (now - timeStamp < time) {
            now = performance.now();
        }
        fn();
        console.timeEnd("执行");
    }
    port1.postMessage('')
}

# 休眠函数

function sleep(time) {
    while (performance.now() < time);
}

# 版本号比对

function sortVersion(list) {
    // 版本号比对
    return list.sort((next,cur) => {
        const nextArr = next.split('.');
        const curArr = cur.split('.');
        // 进行版本比对
        const len = Math.min(nextArr.length, curArr.length);
        for (let i = 0; i < len; i++){
            if (nextArr[i] !== curArr[i]) return nextArr[i] - curArr[i];
        }
        return next - cur;
    })
}
console.log(
sortVersion(["1.0.0", "1.2.3.4.5", "2.12.1", "0.18.1", "3.3.2", "0.18.1"])
);

# express 中间件

const app = express();
app.use((next) => {
  console.log(1);
  next();
  console.log(6);
});
app.use((next) => {
  console.log(2);
  next();
  console.log(5);
});
app.use((next) => {
  console.log(3);
  next();
  console.log(4);
});
app();
function express() {
    const middleWares = [];
    const app = () => {
        let i = 0;
        const next = () => {
            if (!middleWares[i]) return;
            const itemFn = middleWares[i++];
            itemFn(next);
        }
        next();
    }
    app.use = (fn) => {
        middleWares.push(fn);
    }
    return app;
}

# 计算字符串模板

// 计算字符串模板
const sprintf2 = (template, obj) => {
    const fn = new Function("obj", `with(obj){return \`${template}\`}`);
    console.log(fn.toString());
    return fn(obj);
};
console.log(sprintf2("a:${a+b},b:${b}", { a: 1, b: 2 }));

# 数组转树

// 数组转树形
function arrayToTree(arr,parentId) {
    const res = [];
    const buildTree = (arr, parentId, container) => {
        for (let item of arr) {
            if (item.parentId === parentId) {
                item.children = [];
                buildTree(arr, item.id, item.children);
                container.push(item);
            }
        }
    }
    buildTree(arr, null, res);
    return res.length ? (res.length > 1?res:res[0]): {};
}
let input = [
    {
      id: 1,
      val: "学校",
      parentId: null,
    },
    {
      id: 2,
      val: "班级1",
      parentId: 1,
    },
    {
      id: 3,
      val: "班级2",
      parentId: 1,
    },
    {
      id: 4,
      val: "学生1",
      parentId: 2,
    },
    {
      id: 5,
      val: "学生2",
      parentId: 3,
    },
    {
      id: 6,
      val: "学生3",
      parentId: 3,
    },
];
console.log(arrayToTree(input, null));

# 事件委托

function on(type, selector, childSelector, fn) {
    const parent = document.querySelector(selector);
    const child = parent.querySelector(childSelector);
    parent.addEventListener(type, (event) => {
		// 向上冒泡,e.target 可能会是监听事件元素的子元素,导致无法触发,因此要进行冒泡
		let targetDom = event.target;
		while (targetDom) {
		  if (targetDom === child) {
            fn.call(this,event);
			event.stopPropagation();
           }
		   targetDom = targetDom.parentElement;
		}
    });
}
// 使用方式
on("click", "body", "#btn1", (e) => {
    console.log(e);
});

# 求嵌套数组的最大深度

function recursiveMax(arr) {
  // 求嵌套的数组的最大的深度为多少
  if (!Array.isArray(arr)) return 0;
  let res = 1;
  const getLen = (arr,curDepth) => {
    for (let item of arr) {
      if (Array.isArray(item)) {
        res = res > curDepth + 1 ? res : curDepth + 1;
        getLen(item, curDepth + 1);
      }
    }
  }
  getLen(arr, 1);
  return res;
}
console.log(recursiveMax([55, [33, [1], [2, 3], [[3, 5], [8]], [], []]]));

# 嵌套数组反转

function reverse(arr,temp) {
    if (arr[1] === null) return [arr[0], temp];
    temp = temp ? [arr[0], temp] : [arr[0], null];
    
    return reverse(arr[1], temp);
}
console.log(
    reverse([1,[2,[3,[4,null]]]])
);

# 千位分隔符

/**
 * 实现千位分隔符
 */
// 方法一:正则表达式(包括处理小数)
function parseToMoney(num) {
  num = parseFloat(num.toFixed(3));
  let [integer, decimal] = String.prototype.split.call(num, ".");
  integer = integer.replace(/\d(?=(\d{3})+$)/g, "$&,");
  return integer + "." + (decimal ? decimal : "");
}
/**
 *  只处理整数时:
 * (运用了正则的前向声明和反前向声明)
 */
function parseIntToMoney(str) {
  // 匹配非空格处,以 3 的倍数个数字结尾处前面的空字符
  let re = /(?=(?!\b)(\d{3})+$)/g;
  return String(str).replace(re, ",");
}
console.log(
parseIntToMoney(123431324)
);
// 暴力解法
function parseToMoney2(numOrStr) {
  numOrStr = String(numOrStr);
  let index = numOrStr.indexOf('.') - 1; // 获取最后一个的索引
  if (index === -2) index = numOrStr.length - 1;
  let res = ''
  for (let i = 0; i < numOrStr.length; i++){
    if (i < index-2 && (index - i) % 3 === 0) {
      res += numOrStr[i] + ",";
    } else {
      res += numOrStr[i];
    }
  }
  return res;
}
console.log(
  parseToMoney(1234.56) // return '1,234.56'
);
console.log(
  parseToMoney(123456789)  // return '123,456,789'
);
console.log(
  parseToMoney(1087654.321) // return '1,087,654.321'
);

# 管道计算

/**
 * 实现管道计算函数
 */
let pipe = function (value) {
  let stack = [];
  let proxy = new Proxy(
    {},
    {
      get(targetObj, key, thisObj) {
        if (key === "get") {
          // 如果时 get 属性就进行计算
          return stack.reduce((pre, cur) => {
            return cur(pre);
          },value)
        } else {
          stack.push(window[key]) // 注意只有浏览器环境下才能通过字符找到对应的全局函数
          return proxy;
        }
      },
    }
  );
  return proxy;
};
var double = (n) => n * 2;
var pow = (n) => n * n;
var reverseInt = (n) => n.toString().split("").reverse().join("") | 0;
pipe(3).double.pow.reverseInt.get; // 63

# 缓存 ajax 请求

/**
 * 缓存 ajax 请求,保证多次同一 ajax 请求都能获取数据,但实际上只请求了一次
 */
const cache = new Map();
const cacheRequest = (url, option="get") => {
  let key = `${url}:${option.method}`;
  if (cache.has(key)) {
    if (cache.get(key).status === "pending") {
      return cache.get(key).myWait;
    }
    return Promise.resolve(cache.get(key).data);
  } else {
    // 无缓存,发起真实请求
    let requestApi = request(url, option);
    cache.set(key, { status: "pending", myWait: requestApi });
    return requestApi
      .then((res) => {
        // console.log(cache)
        cache.set(key, { status: "success", data: res });
        // console.log(cache)
        return Promise.resolve(res);
      })
      .catch((err) => {
        cache.set(key, { status: "fail", data: err });
        Promise.reject(err);
      });
  }
};
// 测试
const request = (url, option="get") =>
  new Promise((res) => {
    setTimeout(() => {
      res({ data: option });
    }, 2000);
  });
cacheRequest("url1").then((res) => console.log(res));
cacheRequest("url1").then((res) => console.log(res));
setTimeout(() => {
  cacheRequest("url1").then((res) => console.log(res));
}, 4000);

# 字符串转二进制

function toBinanry(str) {
  let res = '';
  for (let char of str) {
    res += (char.charCodeAt(0)).toString(2).padStart(8,0);
  }
  return res;
}
console.log(toBinanry('你好饰家'));

# 巧用隐式类型转换

/**
 * 两等(巧妙使用隐式类型转换)
 */
let a = {
  index:1,
  valueOf:function() {
    return this.index++;
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('获胜');
}
/**
 * 全等(不能使用引用类型了)
 */
// 这个方法在 node 环境中行不通
let b = 1;
Object.defineProperty(window, 'b', {
  get() {
    b++;
  }
})
if (b === 1 && b === 2 && b === 3) {
  console.log('赢了');
}

# 判断同花顺

/**
 * 用 0-51 表示这 52 张牌,按照顺序,♠A-♠K 为 0-12,♥A-♥K 为 13-25,♣A-♣K 为 26-38,◆A-◆K 为 39-51
   一副牌,发出 7 张牌,存于数组 arr 中,实现一个函数返回 true 或 false 判断是否有  同花顺
 */
    // 黑桃且不是后四个
function spade(item) {
  return item >= 0 && item <= 8;
}
    // 红心且不是后四个
function heart(item) {
  return item >= 13 && item <= 21;
}
    // 方片且不是后四个
function club(item) {
  return item >= 26 && item <= 34;
}
    // 梅花且不是后四个
function diamand(item) {
  return item >= 39 && item <= 47;
}
    // 判断逻辑
function judge(arr) {
  // 判断数组是否是同花顺
  let result = Array.from(new Set(arr)).sort((a, b) => a - b);
  // 判断顺子 (利用排序之后位置和大小固定)
  for (let i = 0; i < result.length; i++) {
    if (result[i] + 4 == result[i + 4]) {
      return (spade(result[i]) || heart(result[i]) || club(result[i]) || diamand(result[i]))
    }
  }
  return false
}
let arr = [9, 5, 6, 7, 7, 8, 0, 11];
let arr1 = [0, 4, 6, 7, 8, 9, 9, 11];
let arr2 = [0, 5, 2, 3, 6, 4, 8];
console.log(judge(arr)); //true
console.log(judge(arr1)); //false
console.log(judge(arr2)); //true

# 判断对象循环引用

/**
 * 判断对象循环引用
 */
function isCycleObject(obj,set = new WeakSet()) {
    // 判断对象是否
    if (typeof obj !== "object" || obj === null) throw Error("")
    const keys = Object.keys(obj);
    for (let item in keys) {
        let key = keys[item];
        if (typeof obj[key] !== "object") continue;
        if (set.has(obj[key])) {
            console.log('d');
            return true;
        } else {
            set.add(obj[key]);
        }
        return isCycleObject(obj[key], set);
    }
    return false;
}
const a = new Object({ b: { c: 1 } });
a.b.c = a;
console.log(isCycleObject(a));

# 异步生成函数的持续操作模块

/**
 * 异步生成器函数的持续操作模块(CO 模块,按照执行顺序)
 */
function run(generatorFunc) {
    const iter = generatorFunc();
    const res = [];
    // 迭代,直到最后完毕,依次返回,最后返回 promise
    let iterResult = iter.next();
    return new Promise((resolve, reject) => {
        //iterResult.value 是 promise,当 promise 执行完毕执行下一个
        const runNextPromise = () => {
            if (iterResult.done) {
                return resolve(res);
            }
            iterResult.value.then((value) => {
                res.push(value);
                iterResult = iter.next(value); // 迭代器 next 中的返回值会暴露给 yield 前的变量
                runNextPromise();
            })
            res.push()
        }
        runNextPromise();
    })
  }
  // 模拟请求方法,定时返回结果
  const api = (time) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log("time ", time);
        resolve(time);
      }, time);
    });
};
  // 生成器函数
function* generatorFn() {
    const res1 = yield api(1100);
    yield api(1200);
    yield api(300);
    console.log("yield返回", res1); // {value: Promise, done: false}
}
// 执行生成器函数,输出结果
run(generatorFn).then((value) => {
    console.log(value);
});

# 字符串转函数

/**
 * 字符串转函数
 */
function transformStringToFunction2(fnStr) {
    // 将 fnStr 变为函数,然后执行
    const functionReg = /function.+\((.*)\)\w*\{(.*)\}/; // 尽量不要添加多余的匹配
    const nimingFunReg = /\((.*)\)\=\>\{(.*)\}/;
    if (functionReg.test(fnStr)) {
        let [targetStr, fnArg, fnBody] = functionReg.exec(fnStr); // 正则表达式复杂时的 exec 性能消耗严重
        return new Function(...fnArg.split(','), fnBody);
    } else if (nimingFunReg.test(fnStr)) {
        let [targetStr, fnArg, fnBody] = nimingFunReg.exec(fnStr);
        return new Function(...fnArg.split(','), fnBody);
    } else {
        //fnStr 为 body
        return new Function(fnStr);
    }
}
const funcStr = 'return 4';
const funcStr2 = 'function a(b,c){return b + c + 4}';
const funcStr3 = '(c,d)=>{return c + d + 2}';
const validFunction = transformStringToFunction2(funcStr2);
console.log(validFunction(5, 10));

# promise 并行限制

/**
 * promise 并行限制(构造器版)
 */
class Scheduler{
    constructor(maxCount = 2) {
        this.maxCount = maxCount;
        this.queue = [];
        this.currentRunNumber = 0;
    }
    // 添加一个 promise 生成器到队列中
    add(promiseCreator) {
        this.queue.push(promiseCreator);
    }
    // 当执行并发任务时,取出最大个数的任务进行执行
    staskStart() {
        for (let i = 0; i < this.maxCount; i++){
            this.runItem()
        }
    }
    //runItem 函数会根据最大并发数量进行执行
    runItem() {
        if (!this.queue || !this.queue.length || this.currentRunNumber > this.maxCount) return;
        this.currentRunNumber++;
        this.queue.shift()().then(() => {
            // 当上一个 promise 执行完毕之后,继续调度执行
            this.currentRunNumber--;
            this.runItem();
        })
    }
}
const timeout = (time) => new Promise((resolve, reject) => {
    setTimeout(resolve, time);
})
const scheduler = new Scheduler();
//addTask 函数就是向 scheduler 中添加 promise 生成函数
const addTask = (time, order) => {
  scheduler.add(() => {
    return timeout(time).then(() => {
      console.log(order);
    });
  });
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();
/**
 * 模拟最大异步并行请求
 */
const urls = ['url1', 'url2', 'url3', 'url4'];
const request = (url) => {
	return new Promise((resolve, reject) => {
		console.log(url+"发起请求");
		const time = Math.random() * 5000;
		if (time > 4800) {
			reject(url)
			return;
		};
		setTimeout(() => {
			console.log(url + "请求完成");
			resolve(url);
		}, time);
	})
}
class RequestQueue{
    #maxNum;
    #queue;
    #runNum;
    constructor(max,queue=[]) {
        this.#maxNum = max;
        this.#queue = queue;
        this.#runNum = 0;
    }
    add(url) {
        return new Promise((resolve, reject) => {
            const task = () => {
                    this.#runNum++;
                    request(url).then(res => {
                        resolve(res);
                    }).catch(e => {
                        reject(e);
                    }).finally(() => {
                        this.run();
                        this.#runNum--;
                    })
            }
            this.#queue.push(task);
            this.run();
        })
    }
    run() {
        if (this.#queue.length && this.#runNum < this.#maxNum) {
            const task = this.#queue.shift();
            task();
        }
    }
}
const queue = new RequestQueue(2);
for (let i = 0; i < urls.length; i++){
    queue.add(urls[i]);
}
/**
 * promise 最大并行限制(函数调用版)
 */
function multiRequest(urls, maxNum, callback) {
    const len = urls.length;
    const result = new Array(len).fill(false);
    let runCount = 0;
    return new Promise((resolve, reject) => {
      // 最多同时发送 maxNum 个请求
      while (runCount < maxNum) {
        sendRequest();
      }
      function sendRequest() {
        let curCount = runCount;
        runCount++;
        console.log(`开始发送第 ${curCount} 个请求`);
        if (runCount >= len) {
          callback(result);
          resolve(result);
        }
        urls[curCount]
          .then((value) => {
            console.log(`${curCount} 个请求:${value} 成功了!`);
            result[curCount] = `${value} 成功`;
          })
          .catch((reason) => {
            console.log(`${curCount} 个请求:${reason} 失败了!`);
            result[curCount] = `${reason} 失败`;
          })
          .finally(() => {
            if (runCount < len) {
              sendRequest();
            }
          });
      }
    });
  }
//   测试用例 1
const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('111请求完毕');
    }, 1000);
})
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject("222请求失败")
    }, 600);
})
const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('333请求完毕');
    }, 200);
})
const p4 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('444请求完毕');
    }, 800);
})
multiRequest([p1, p2, p3, p4], 2, (result) => {
    console.log('全部请求执行完毕:',result);
}).then(res => {
    console.log(res);
}).catch(err => {
    console.log(err);
})

# 重复个数字符

function convertString(str) {
    // 重复前面的数字,返回新的字符串
    const reg = /(\d+)\[([^\[\]]+)\]/g;
    const res = str.replace(reg, (match, p1,p2) => p2.repeat(p1));
    return reg.test(res)?convertString(res):res;
}
const res1 = convertString("3[x]2[y0.%&]");
const res2 = convertString("3[x2[y]]");
const res3 = convertString("2[xyz]3[ab]cd");
console.log(res1, res2, res3);

# 深度比较

const deepEqual = (a, b) => {
  // 如果两个参数类型不同,直接 false
    if (typeof a !== typeof b) return false;
   const checkType = (target) => {
      // 获取真实类型
      return Object.prototype.toString.call(target).slice(8, -1).toLowerCase();
    };
   return (
      // 首先两个数据的类型相同,然后数据相同
      checkType(a) === checkType(b) && JSON.stringify(a) === JSON.stringify(b)
    );
  };

# lodash get

const get = function (object, path, defaultValue = "没找到") {
  const paths = Array.isArray(path) ? path : path.replace(/\[(\d+)\]/g, ".$1").split("."); // ["a", "0", "b", "c"]
  let result = object;
  for (const key of paths) {
    result = Object(result)[key];
    if (result === undefined) {
      return defaultValue;
    }
  }
  return result;
};
console.log(get({ a: [{ b: { c: 3 } }] }, "a[0].b.c", "哈哈哈"));

# 解析 url 参数

// 解析 url 参数
function parseParam(url) {
    // 将 url 参数进行解析
    const res = {};
    const paramStr = /.+\?(.+)$/.exec(url)[1];
    const arr = paramStr.split("&");
    for (let item of arr) {
        const itemArr = item.split("=");
        if (itemArr.length == 2) {
            // 说明有 =, 但是要考虑节码和重复
            let resItem;
            if (/^\d+$/.test(itemArr[1])) resItem = Number(itemArr[1])
            else resItem = decodeURIComponent(itemArr[1]);
            if (res.hasOwnProperty(itemArr[0])) {
                console.log(res[itemArr[0]]);
                res[itemArr[0]] = [].concat(res[itemArr[0]],resItem);
            } else {
                res[itemArr[0]] = resItem;
            }
        } else {
            res[itemArr[0]] = true;
        }
    }
    return res;
}
// 测试
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
console.log(
parseParam(url)
);

# lodash merge

// 合并两个对象
function isObject(value) {
    return value !== null && (typeof value === "object" || typeof value === "function");
}
function merge(object, sources) {
    // 将两个对象进行合并
    if (!isObject(object) || !isObject(sources)) {
        // 如果两个其中一个不是对象
        return sources === undefined ? object : sources;
    }
    // 对象之间的合并
    return Object.keys({ ...object, ...sources }).reduce((pre, cur) => {
        pre[cur] = merge(object[cur], sources[cur]);
        return pre;
    }, Array.isArray(object) ? [] : {})
}
// 测试
var object = {
    a: [{ b: 2 }, { d: 4 }],
  };
  
  var other = {
    a: [{ c: 3 }, { e: 5 }],
  };
  
console.log(merge(object, other));
console.log(merge({a:1},[1,2,3]));

# 函数组合

// 函数式编程之组合与管道,是 webpack 中 loader 的语法,也是 unix 中 pipe 管道的概念
function compose() {
  let args = arguments;
  let start = args.length - 1;
  let result;
  return function() {
      result = args[start].apply(this, arguments);
      while(start--) {
          result = args[start].call(this, result);
      }
      return result;
  }
}
// 或者用 reduce 实现
function compose(...funcs) {
  // 没有函数返回参数
  if (funcs.length === 0) {
    return (arg) => arg;
  }
  // 单个函数,直接调用
  if (funcs.length === 1) {
    return funcs[0];
  }
  // 多个函数,使用 reduce 传入参数调用
  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
function composeRight(...funcs) {
  // 没有函数直接返回
  if (funcs.length === 0) {
    return (arg) => arg;
  }
  // 单个函数直接执行
  if (funcs.length === 1) {
    return funcs[0];
  }
  // 使用 reduceRight 从右边开始遍历
  return funcs.reduceRight((a, b) => (...args) => a(b(...args)));
}

# 浅拷贝(拷贝一层,不解决里层对象的拷贝)

拷贝之后会返回新的对象,丢失原型链

function shallowClone(objLike) {
   if(typeof objLike !== 'object') return objLike;
   const res = Array.isArray(objLike)?[]:{};
   for(let item in objLike){
       if(objLike.hasOwnProperty(item)){
           res[item] = objLike[item];
       }
   }
   return res;
}

# 深拷贝

深拷贝主要注意:

  • 解决里层对象的拷贝(使用递归调用)
  • 正则和日期对象如何拷贝(通过 实例的构造函数传入原对象创建新对象 ,注意:只有 Object 构造函数特殊,当 new Object 传入一个对象时,它将返回原对象!)
  • 循环引用对象导致的无限递归问题( 使用WeakMap存储,如果之前拷贝过该对象就复用
// 是否是对象类型
const isObject = (obj) => typeof obj === 'object' || typeof obj === 'function' && obj !== null;
function deepClone(target,map = new WeakMap()) {
   if(map.get(target)) return target; // 避免无限递归,如果 WeakMap 中存在就表明这个对象已经克隆过了,直接返回即可
   let constructor = target.constructor; // 获取到 constructor
   // 如果是日期或者正则,就新创建一个实例
   if(/^(RegExp|Date)$/i.test(constructor.name)){
       return new constructor(target);
   }
   if(isObject(target)){
       map.set(target,true);
       const cloneTarget = Array.isArray(target) ? []:{};
       for(let prop in target){
           if(target.hasOwnProperty(prop)){
               cloneTarget[prop] = deepClone(target[prop],map);
           }
       }
       return cloneTarget;
   }else {
       return target;
   }
}
// > 注意:1、原型链不能形成闭环(报错),2、__proto__的值只能是对象或者 null,3、一个对象只能又一个 [[Prorotype]],4、__proto__是内部 [[Prototype]] 的 getter/setter。

# 判断数据类型

精确 Array RegExp Date 等类型

// 方法一: 使用 toString
function myTypeOf(obj) {
 const typeMap = {
     "[object Object]": "Object",
     "[object Array]": "Array",
     "[object RegExp]": "RegExp",
     "[object Date]": "Date",
     "[object String]": "String",
     "[object Number]": "Number",
     "[object Boolean]": "Boolean",
     "[object Function]": "Function",
     "[object Null]": "Null", // 注意这里是 "Null" 而不是 "null"
     "[object Undefined]": "Undefined", // 注意这里是 "Undefined" 而不是 "undefined"
     "[object Symbol]": "Symbol",
     "[object BigInt]": "BigInt"
 };
 return typeMap[Object.prototype.toString.call(obj)];
}
// 方法二:根据类型进行判断
function myTypeOf2(obj) {
 switch (typeof obj) {
     case "object":
         if(Array.isArray(obj)) return "Array"
         if(obj === "null") return "null";
         if(/^(RegExp)$/i.test(target.constructor.name)) return "RegExp";
         if(/^(Date)$/i.test(target.constructor.name)) return "Date";
         return "Object";
     case "number":
         return "Number";
     case "string":
         return "String";
     case "undefined":
         return "undefined";
     case "bigint":
         return "BigInt";
     case "boolean":
         return "Boolean";
     case "function":
         return "Function";
     case "symbol":
         return "Symbol";
     default:
         return "unknown"
 }
}

# new 关键字

new 关键字实现分为三步:
1、根据构造函数的原型创建新的对象
2、执行构造函数,绑定 this 指向为新对象,传入参数
3、返回执行后的结果

function myNew(){
    const Constructor = [].shift.call(arguments);
    // 创建空对象,空对象的原型为构造函数的 prototype
    //const obj = new Object (); obj.__proto__ = Constructor.prototype; 可以直接写为:
    const obj = Object.create(Constructor.prototype);
    // 执行构造函数,传入创建的对象和参数
    const res = Constructor.apply(obj,arguments); // 注意 apply 方法是函数对象的方法,普通对象不可用
    return (res && typeof res === 'object')?res:obj;
}
function Person (name,age){
    this.name = name;
    this.age = age;
}
const p = myNew(Person,'张三',18)
console.log(p);

# 闭包的简单使用

// 闭包隐藏数据,只提供 API 操作
function createCache() {
  const cache = {};
  return {
    get(key){
      return cache[key]
    },
    set(key,val){
      cache[key] = val;
    }
  }
}

# 函数柯里化

柯里化的核心就是

function curry(fn) {
   // 将 fn 函数进行柯里化
   return function curried(...args) {
       if(args.length >= fn.length){
           return fn.apply(this,args);
       }else{
           return function (...args2) {
               return curried.apply(this,args.concat(args2))
           }
       }
   }
}
const curried = curry((a,b,c,d)=>{console.log(a,b,c,d)})
const curriedFn = curried(1,2);
curriedFn(3,4);

# 函数偏函数化

function partial(fn,...args) {
   return (...arg) => {
       return fn(...args,...arg);
   }
}

# 数组去重

function unique(arr) {
   return new Array(...new Set(arr))
}

# 数组扁平化 (扁平一层)

将数组进行一层的扁平化处理

function arrFlat(arr) {
   if(arr.flat){
     return arr.flat(); //Array.flat 方法默认扁平一层
   }
   // 兼容 es5 版本:
   const res = [];
   arr.forEach(item=>{
       if(Array.isArray(item)) res.push(...item);
       else res.push(item) 
   })
   return res;
}

# 数组扁平化 (全部扁平)

function arrayFlat(arr,resArr=[]) {
   arr.forEach(item=>{
       if(Array.isArray(item)){
          arrayFlat(item,resArr);
       }else{
           resArr.push(item);
       }
   })
   return resArr;
}

# 数组扁平化 (扁平 n 层)

function arrayFlat(arr,num,resArr=[]) {
   arr.forEach(item=>{
       if(Array.isArray(item) && num){
           arrayFlat(item,num-1,resArr)
       }else{
           resArr.push(item);
       }
   })
   return resArr;
}
console.log(
   arrayFlat([1,2,[3,[4,5,[1]]]],-1)
);

# 手写对象的 [Symbol.iterator] 迭代器协议

普通对象默认不内置迭代器,现在要在普通对象上添加迭代协议。

// 内置迭代器的异质对象有:Array,String,Set,Map
Symbol.myIterator = Symbol('myIterator');
Object.prototype[Symbol.myIterator] = function () {
   const iterKeys = Object.keys(this);
   let iterIndex = 0;
   return {
       next: ()=> {
           if (iterIndex < iterKeys.length) {
               return {
                   value: this[iterKeys[iterIndex++]],
                   done: false
               };
           } else {
               return {
                   value: undefined,
                   done: true
               };
           }
       }
   }
}

# 手写 for of 循环

for of 遍历的前提是对象内置了迭代器

function myForOf(obj,fn) {
   if(!obj[Symbol.iterator]) throw new TypeError('obj不存在迭代器')
   const myIterator = obj[Symbol.iterator]();
   let cur = myIterator.next();
   while (!cur.done) {
       fn(cur.value);
       cur = myIterator.next();
   }
}

# 手写 for in 循环

for in 的前提是对象属性可被枚举,我们无法模拟底层实现的枚举过程,但是可以通过 Object.keys 进行模拟,他也是基于枚举实现的
Object.keys 返回数组,包含对象的所有可枚举自有属性,返回的键名为字符串类型
但是缺陷就在于: for in 会枚举到对象原型上的方法,但是 Object.keys 只会枚举到对象的自有属性

function myForIn(obj,fn) {
   const keys = Object.keys(obj);
   keys.forEach(item=>{
       // 注意在对象中 [] 作为属性时会被解释为变量引用
       fn({[item]:obj[item]});
   })
}

# 手写全局事件总线

class EventEmitter {
   #cache = {};
   constructor(){}
   on(name,fn){
       if(this.#cache[name]){
           this.#cache[name].push(fn);
       }else{
           this.#cache[name] = [fn]
       }
   }
   off(name,fn){
       if(!this.#cache[name]) return;
       this.#cache[name] = this.#cache[name].filter(item=>{
          return item !== fn;
       })
   }
   emit(name,...args){
       this.#cache[name].forEach(item=>{
           item.call(this,...args);
       })
   }
}
// 复习 CustomEvent、Event 和 EventTarget(dispatchEvent 和 addEventListener)
const sayHiEvent = new CustomEvent('sayHi',{ //customEvent 和 Event 的区别就在于:customEvnet 能接受自己定义的数据
   detail:{
       name:"sayHi事件"
   }
})
const eventTarget = new EventTarget();
eventTarget.addEventListener('sayHi',(e)=>{
   console.log(e.detail,'e.detail');
})
eventTarget.dispatchEvent(sayHiEvent)
// 深度思考:为什么 eventTarget 使用 dispatchEvent 时,要传入 Event 事件,而不是 type 类型?
// 因为一个 DOM 同一事件不能重复,并且同一事件类型一般要创建很多个事件(用于不同的 DOM,因此不能复用同一事件),此时用类型很难对其标识

# 遍历原型链输出构造类

function consoClass(classFn) {
   let a = Object.getPrototypeOf(classFn);
   while (a !== null) {
       console.log(a.constructor);
       a = Object.getPrototypeOf(a);
   }
}
// HTMLElement--Element--Node--EventTarget--Object--null
// 不能用 new 调用某些封闭的类,如: new HTMLElement 会报错,因为这种封闭的类不允许被用 new 进行实例化。这种封闭类还有:HTMLDivElement、StyleSheet 等,它们需要通过特定的 api 进行操作,例如:document.createElement。
consoClass(HTMLElement)

# 图片懒加载

function imgLazyLoad() {
   if(this.imgList.length === 0) return;
   this.imgList =this.imgList || [...document.querySelectorAll("img")];
       let deleteIndexList = [];
       // 遍历图片列表
       this.imgList.forEach((item,index)=>{
           let rect = item.getBoundingClientRect();
           // 如果 rect.top 小于 window.innerHeight,代表以及进入视野之内
           if(rect.top < window.innerHeight){
             console.log(item.dataset.src);
               item.src = item.dataset.src;
               deleteIndexList.push(index);
           }
       })
       this.imgList = this.imgList.filter((item,index)=>!deleteIndexList.includes(index));
}
document.addEventListener('DOMContentLoaded',imgLazyLoad)
document.addEventListener("scroll",imgLazyLoad);

# 函数节流(固定时间只执行一次)

首先防抖和节流内部都会返回一个函数,因为他们通常被使用在注册事件之中,而不是单独被调用
节流比如王者荣耀点击回城操作

function throttle(fn,time=1000) {
   let timer = null;
   return function () {
       if(timer) return;
       timer = setTimeout(() => {
           fn.apply(this,arguments);
           // 注意不能使用 clearTimeout,因为清除定时器之后,timer 的值仍然不变
           timer = null;
       },time);
   }
}
window.addEventListener('scroll', throttle(function() {
   console.log('滚动事件');
}, 1000));

# 函数防抖(多次执行,重新计时)

防抖比如搜索框延迟请求

function debounce(fn,time=1000) {
   // 防抖会撤销上一次定时器,重新开一个定时器
   let timer = null;
   return function (...args) {
       clearTimeout(timer);
       timer = setTimeout(() => {
           fn.apply(this,args);
       }, time);
   }
}
window.addEventListener('scroll', debounce(function() {
   console.log('滚动事件');
}, 1000));

# 使用 jsonp 进行跨域 get 请求

const jsonp = ({ url, params, callbackName }) => {
   const generateUrl = () => {
       let dataSrc = ''
       for (let key in params) {
           if (params.hasOwnProperty(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.removeChild(scriptEle)
       }
   })
}
// 使用:
jsonp({
 url: 'https://api.example.com/data',
 params: {param:"value1"},
 callbackName: 'jsonpCallback'
}).then(data=>{
 console.log(data);
}).catch(err=>{
 console.error(err);
})
window.jsonpCallback = function(response){
 console.log(response);
}

# AJAX 封装 get 请求 JSON 数据

function getJSON(url,timeout) {
   return new Promise((resolve,reject)=>{
       const xhr = new XMLHttpRequest();
       xhr.open("GET",url,true); //true 表示是否是异步,同步则无法设置 timeout
       xhr.setRequestHeader("Accept","application/json");
       xhr.timeout = timeout;
       xhr.ontimeout = ()=> reject(new Error("请求超时"))
       xhr.onerror = ()=> reject(new Error("请求出错"))
       xhr.send();
       xhr.onreadystatechange = function () {
           if(xhr.readyState !== 4) return;
           if(xhr.status === 200 || xhr.status === 304){
               resolve(xhr.responseText);
           }else{
               reject(new Error(xhr.responseText));
           }
       }
   })
}

# 实现 forEach

//   forEach 利用的是可迭代协议
Array.prototype.myForEach = function (fn,thisArg=this) {
   if(typeof fn !== "function") throw new Error("fn不是函数")
   if(this === null) throw new Error("this is null or not defined");
   // 使用执行生成器获取迭代器,或者使用 arr.[values | keys 等] 返回迭代器
   const iterator = thisArg[Symbol.iterator]();
   let item = iterator.next();
   let index=0;
   while (!item.done) {
       fn.call(thisArg,item.value,index++,thisArg);
       item = iterator.next();
   }
}
//  使用 for/while 循环模拟
Array.prototype.myForEach = function (callback,thisArg) {
   if(this === null) throw new TypeError("this is null or not defined");
   // 如果回调不是函数
   if(typeof  callback !== 'function') throw new TypeError(callback + "is not a function");
   thisArg = Object(thisArg || this); // 确保是对象
   const len = thisArg.length >>> 0; // 确保该函数的参数是正整数或 0
   let k = 0;
   while (k < len) { // 当 k 小于 0 进行遍历
       if(k in this){
           callback.call(thisArg,thisArg[k],k,thisArg);
       }
       k++;
   }
}

# 实现 map

Array.prototype.myMap = function (callback,thisArg) {
   if(this.null) throw new TypeError("this is null or defined");
   if(typeof callback !== "function") throw new TypeError(callback.name + "is not a function");
   const thisObj = Object(this); // 保证 thisObj 不是 null 或 undefined,使用 Object 可以传入数组,使用 Array 需要列出来
   const len = thisObj.length >>> 0; // 无符号右移 0,内部全部流程:valueOf、toString、Number,然后无符号左移(去小数)
   let k =0;res = []
   while (k < len) {
       if(k in thisObj){
           res[k]=callback.call(thisArg,thisObj[k],k,thisObj);
       }
       k++;
   }
   return res;
}

# 实现 fill

//fill 方法会改变原数组
Array.prototype.myFill = function (value, start, end) {
  if (!end) end = this.length;
  if (!start) start = 0;
  if (start > end) [start, end] = [end, start];
  for (let i = start; i < end; i++){
    this[i] = value;
  }
  return this;
}
console.log([1, 2, 3, 4, 5].myFill("插",3));
console.log([1, 2, 3, 4, 5].myFill("插"));
console.log([1, 2, 3, 4, 5].myFill("插",1,3));

# 实现 filter

Array.prototype.myFilter = function (callback,thisArg) {
   if(this == null) return new TypeError("this is null or undefined")
   if(typeof callback !== "function") return new TypeError(callback.name + "is not a function")
   // 处理 filter
   const thisObj = Object(this);
   const len = thisObj.length >>> 0;
   let res = [],k = 0;
   while (k < len) {
       if(k in thisObj){
           if(callback.call(thisArg,thisObj[k],k,thisObj)){
               res.push(thisObj[k]);
           }
       }
       k++;
   }
   return res;
}

# 实现 some

Array.prototype.mySome = function (callback,thisArg) {
   if(this == null) return new TypeError("this is null or undefined");
   if(typeof callback !== "function") return new TypeError(callback.name + "is not a function")
   const thisObj = Object(this);
   let len = thisObj.length,k = 0;    
   while (k < len) {
       if(k in thisObj){
           if(callback.call(thisArg,thisObj[k],k,thisObj)){
               return true;
           }
       }
       k++;
   }
   return false;
}

# 实现 reduce

Array.prototype.myReduce = function (callback,initialValue) {
   if(this == null) return new TypeError("this is null or undefined");
   if(typeof callback !== "function") return TypeError(callback.name + "is not a function");
   const thisObj = Object(this); // 防止 null 或者 undefined
   const len = thisObj.length;
   let res,k=0;
   while (k < len) {
       if(k in thisObj){
           // 进行执行
           initialValue = callback(initialValue,thisObj[k]);
       }
       k++;
   }
   return initialValue;
}

# 手写 call、apply 和 bind

Function.prototype.myCall = function (thisContext=globalThis,...args) {
    const fn = Symbol(thisContext); // 避免传入的对象中本身具有 fn 属性
    thisContext[fn] = this; // 将函数存到 this 对象上,使用对象进行调用
    thisContext[fn](...args);
    delete thisContext[fn]; // 删除该属性,避免影响到外部
};
// 小结:使用 call 绑定 this 的关键就在于:将 this(也就是函数)挂载到 thisContext(需要绑定的 this 上),使用对象 [属性] 的方式进行执行,从而自动绑定 this
Function.prototype.myApply = function (thisContext) {
    const fn = Symbol("myApply");
    // 将 fn 绑定到黄经中
    thisContext[fn] = this;
    // 执行
    const args = [...arguments].slice(1);
    thisContext[fn](args);
    delete thisContext[fn]; // 删除属性之后,垃圾回收机制自动回收 fn
}
Function.prototype.myBind = function (thisContext) {
    // 函数绑定
    // 返会一个函数,绑定 this 指向,到固定的函数上
    const thisFn = this;
    const arg = [...arguments].slice(1); // 保存传入的参数
    return function () {
        const newArg = [...arguments];
        return thisFn.apply(thisContext,[...arg,...newArg])
    }
}

# instanceof

//instanceof 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
function myInstanceOf(left,right) {
    // 判断 left 是不是 right 的子类,遍历 left 的原型链,看是否有 right
    if(typeof left !== "object") throw new TypeError("Left-hand side of myInstanceOf is not an object");
    if(typeof right !== "object") throw new TypeError("Right-hand side of myInstanceOf is not an object");
    while (Object.getPrototypeOf(left)) {
        left = Object.getPrototypeOf(left);
        if(left === Object.getPrototypeOf(right)){
            return true;
        }
    }
    return false;
}

# 实现 Object.create

// 参数一:proto 二:属性对象
Object.myCreate = function (proto,propertyObj) {
    if(typeof proto !== "object" && typeof proto !== "function"){
        throw new TypeError("object prototype may not be an object or null")
    }
    if(propertyObj === null){
        new TypeError("Cannot convert undefined or null to object");
    }
    function F() {}
    F.prototype = proto; // 核心:应该操作内部的构造函数的 prototype 而不是实例(构造函数的 prototype 就是其实例的原型)
    const obj = new F();
    if(propertyObj !== undefined){
        Object.defineProperties(obj,propertyObj)
    }
    // 模拟 Object.create (null)
    if(proto === null){
        obj.__proto__ = null;
    }
    return obj;
}
// 使用:
const person = { test:"test" };
// 传入的属性描述符的属性必须是一个对象
const newObj = Object.myCreate(person,{
    property1:{
        value:"张三",
        writable:true
    },
    property2:{
        value:"李四",
        readable:true
    }
});
console.log(newObj);
console.log(newObj.test);
console.log(newObj instanceof person.constructor);

# 实现 Object.assign

Object.myAssign = function (target,...source) {
    if(target == null){
        throw new TypeError("Cannot convert undefined or null to object")
    }
    // 确保是一个对象类型
    let resObj = Object(target);
    source.forEach(item=>{
        if(item != null){
            // 如果 key 属性是在
            for(let key in item){
                // 如果 key 是原型上的方法
                if(item.hasOwnProperty(key)){
                    resObj[key] = item[key];
                }
            }
        }
    })
    return resObj;
}

# vue3 封装 debounce

// 创建一个防抖的 ref(使用 customRef)
function useDebouncedRef(value,delay=200){
    let timeout;
    return customRef((track,trigger)=>{
      return {
        get(){
          track();
          return value;
        },
        set(newValue){
          clearTimeout(timeout);
          timeout = setTimeout(()=>{
            value = newValue
            trigger();
          },delay)
        }
      }
    })
}
// 使用
const text = useDebouncedRef('hello');

# 简易 useState

const state = []; // 利用闭包,将 state 存在外面,这样每次赋值只会赋值一次
let hookIndex = 0;
function useState(initialState) {
  const thisHookIndex = hookIndex++;
  state = state[thisHookIndex] || initialState;
  const setState = (newState)=> {
    state[thisHookIndex] = newState;
    render();
  }
  return [state, setState];
}

# 手写 class

class 关键字其实就是去设置构造函数 prototype 原型上的 init 属性

// 单个实现:
function Example(name) {
    if(!new.target) throw new TypeError("该函数应该使用new来调用");
    this.name = name
}
Object.defineProperty(Example.prototype,"init",{
    enumerable:false,
    value: function () {
        'use strict';
        if(new.target) throw new Error("init函数不能使用new来调用");
        let fn = function () {
            console.log(this.name);
        }
        fn.call(this);
    }
})
// 实现关键字:
function inherit(target, parent) {
  target.prototype = Object.create(parent.prototype, {
    constructor: {
      enumerable: false,
      configurable: true,
      writable: true,
      value: target,
    },
  });
  Object.setPrototypeOf(target, parent);
}

# Promise 实现

Promise 的核心就是:then 方法,它返回一个 myPromise,根据 status 状态在宏任务中运行传入的函数。

const PENDING = Symbol.for('pending');
const FULFILLED = Symbol.for('fulfilled');
const REJECTED = Symbol.for('rejected');
// 将传入的 Promise 进行 resolve
const handleMyPromise = (thePromiseToHandle, theValueToHandle, resolve, reject) => {
  if (thePromiseToHandle === theValueToHandle) { // 检查循环引用,由于要防止循环引用,所以将自身 resolve 的逻辑提取出来,而不是直接执行 resolve
    return reject(
      new TypeError("Chaining cycle detected for MyPromise #<MyPromise>")
    );
  }
  let called; // 确保 resolve 或 reject 不会被多次调用
  if ((typeof theValueToHandle === "object" && theValueToHandle != null) || typeof theValueToHandle === "function") {
    try {
      let then = theValueToHandle.then;
      if (typeof then === "function") { // 如果待 resolve 的值具有.then 方法,就执行,并传入自定义的 resolve 和 reject 函数。
        then.call(
          theValueToHandle,
          (value) => {
            if (called) return;
            called = true;
            handleMyPromise(thePromiseToHandle, value, resolve, reject);
          },
          (reason) => {
            if (called) return;
            called = true;
            reject(reason);
          }
        );
      } else { // 如果不具备.then 方法,就直接 resolve
        resolve(theValueToHandle);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    resolve(valueOrPromise);
  }
};
class MyPromise {
  #status = PENDING; // 状态
  #value = undefined; // 值
  #reason = undefined; // 原因
  #onResolvedCallbacks = []; // 成功回调数组
  #onRejectedCallbacks = []; // 失败回调数组
  constructor(executor) {
    let resolve = (value) => {
      if (value instanceof MyPromise) {
        return value.then(resolve, reject);
      }
      if (this.#status === PENDING) {
        this.#status = FULFILLED;
        this.#value = value;
        this.#onResolvedCallbacks.forEach((fn) => fn());
      }
    };
    let reject = (reason) => {
      if (this.#status === PENDING) {
        this.#status = REJECTED;
        this.#reason = reason;
        this.#onRejectedCallbacks.forEach((fn) => fn());
      }
    };
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (err) => {
            throw err;
          };
    const thenReturnPromise = new MyPromise((resolve, reject) => {
      if (this.#status === FULFILLED) {
        setTimeout(() => {
          try {
            let fulfillReturn = onFulfilled(this.#value);
            handleMyPromise(thenReturnPromise, fulfillReturn, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }
      if (this.#status === REJECTED) {
        setTimeout(() => {
          try {
            let rejectReturn = onRejected(this.#reason);
            handleMyPromise(thenReturnPromise, rejectReturn, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }
      if (this.#status === PENDING) {
        this.#onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let fulfillReturn = onFulfilled(this.#value);
              handleMyPromise(thenReturnPromise, fulfillReturn, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.#onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let rejectReturn = onRejected(this.#reason);
              handleMyPromise(thenReturnPromise, rejectReturn, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
      }
    });
    return thenReturnPromise;
  }
  catch(errCallback) {
    return this.then(null, errCallback);
  }
  finally(callback) {
    return this.then(
      (value) => {
        return MyPromise.resolve(callback()).then(() => value);
      },
      (reason) => {
        return MyPromise.resolve(callback()).then(() => {
          throw reason;
        });
      }
    );
  }
  static resolve(data) {
    return new MyPromise((resolve, reject) => {
      resolve(data);
    });
  }
  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason);
    });
  }
  static all(values) {
    if (!Array.isArray(values)) {
      const type = typeof values;
      return new TypeError(`TypeError: ${type} ${values} is not iterable`);
    }
    return new MyPromise((resolve, reject) => {
      let resultArr = [];
      let orderIndex = 0;
      const processResultByKey = (value, index) => {
        resultArr[index] = value;
        if (++orderIndex === values.length) {
          resolve(resultArr);
        }
      };
      for (let i = 0; i < values.length; i++) {
        let value = values[i];
        if (value && typeof value.then === "function") {
          value.then((value) => {
            processResultByKey(value, i);
          }, reject);
        } else {
          processResultByKey(value, i);
        }
      }
    });
  }
  static race(MyPromises) {
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < MyPromises.length; i++) {
        let val = MyPromises[i];
        if (val && typeof val.then === "function") {
          val.then(resolve, reject);
        } else {
          resolve(val);
        }
      }
    });
  }
}
MyPromise.defer = MyPromise.deferred = function () {
  let dtd = {};
  dtd.MyPromise = new MyPromise((resolve, reject) => {
    dtd.resolve = resolve;
    dtd.reject = reject;
  });
  return dtd;
};
export default MyPromise;

# 字符串转小驼峰

使用正则

function changeStr(str) {
  // 使用正则
  str=str.replace(/[-_]([a-z]?)/g,function name(match,p1) {
      return p1.toUpperCase();
  })
  return str;
}

变量标记

function changeStr(str) {
  // 不使用正则
  let res="",flag=false;
  console.log(str.length);
  for(let i=0;i<str.length;i++){
      const item = str[i];
      if(item==="-" || item==="_") {
          flag = true;
          continue;
      }
      if(flag) res += item.toUpperCase();
      else res+=item;
      flag = false;
  }
  return res;
}

# 节流函数(限定时间执行)

function throttle(fn,wait=1000) { // 返回一个函数,无论这个函数有多么频繁的被调用,调用的频率也不会超过指定的频率
    let waiting = false,lastArgs = null;
    return function (...args) {
        if(waiting){
            // 如果现在处于等待状态,就不去执行这个函数
            lastArgs = args;
            return;
        }else{
            // 如果现在不是处于等待状态,就要去执行这个函数
            // 定时器中将等待状态复为 false
            waiting = true;
            fn.call(this,...args);
            let timeout = setTimeout(() => {
                waiting = false;
                if(lastArgs){
                    fn.call(this,...lastArgs);
                    waiting = true;
                    lastArgs = null;
                }
                clearTimeout(timeout);
            }, wait);
        }
    }
}

# 8 手写 shuffle(打乱数组)

function shuffle(arr) {
    for(let i = arr.length-1;i>0;i--){
        const randomIndex = Math.floor(Math.random() * (i+1));
        [arr[i],arr[randomIndex]] = [arr[randomIndex],arr[i]];
    }
    return arr;
}

# 9 解密字符串

function decode(message) {
    let i=0,j=0,cols = message[0]?.length,res="",step=1;
    //j 代表横向
    while (j<cols) {
        res += message[i][j];
        if(!message[i+step]){
            step *= -1;
        }
        j++;
        i += step;
    }
    return res;
}

# 10 找出第一个不良版本

function firstBadVersion(isBad) {
    // 版本是否坏掉:isBad (version)
    const check = (v)=>isBad(v)?check(v-1):v+1;
    return (version)=>{
        return isBad(version)?check(version-1):-1;
    }
}

# 四则运算方法

/**
 * 加减乘除
 */
function times(left) {
    return (right) => left * right;
}
function plus(left) {
    return (right) => left + right;
}
function subtract(left) {
    return (right) => left - right;
}
function devide(left) {
    return (right) => left / right;
}
/**
 * 生成组合表达式(传入函数数组)
 */
function pipe(funcs) {
    return (x)=>{
       return funcs.reduce((result,itemFn)=>{
           return itemFn.call(this,result);
        },x)
    }
}
pipe([times(2),plus(3),times(4)])(5) // (5 * 2 + 3) * 4

# Immutability Helper 具体修改对象

function update(data, command) { // 操作 data 对象,使用 command 中的命令进行修改
    for (const [key, value] of Object.entries(command)) { // 遍历命令对象
      switch (key) {
        case '$push':
          return [...data, ...value];
        case '$set':
          return value;
        case '$merge':
          if (!(data instanceof Object)) {
            throw Error("bad merge");
          }
          return {...data, ...value};
        case '$apply':
          return value(data);
        default:
          if (Array.isArray(data)) {
            const res = [...data];
            res[key] = update(data[key], value);
            return res;
          } else {
            return {
              ...data,
              [key]: update(data[key], value)
            }
          }
      }
    }
  }

# 实现 memo

function memo(func, resolver=((...args)=>args.join("_"))) {
    const map = new Map();
    return function (...args) {
        const cacheKey = resolver(...args);
        if(map.has(cacheKey)){
            return map.get(cacheKey);
        }
        const value = func.apply(this,args);
        map.set(cacheKey,value);
        return value;
    }
}
const a = memo((a,b)=>{
    console.log('函数执行');
    return a+b;
})
a(1,2);
a(1,2);
a(1,2);
a(2,3);

# 检测所有数据类型

function detectType(data) {
    return Object.prototype.toString.call(data).slice(8,-1).toLowerCase();
}

# 手写 JSON.stringify

function stringify(data) {
    // 对 data 进行 JSON.stringify
    if(data !== data || data === Infinity || data === -Infinity || data === null || data === undefined) return "null"; // 判断 NaN 等,返回 null
    switch (typeof data) {
        case "bigint":
            throw new Error("不能转换bigint")
        case "function":
            return undefined;
        case "string":
            return `"${data}"`
        case "number":
            return `${data}`
        case "boolean":
            return `${data}`
        case "symbol":
            return undefined
        default:
            if(data instanceof Date) return `"${data.toISOString()}"`;
            if(Array.isArray(data)) {
                const arr = data.map(item=>stringify(item));
                return `[${arr.join(",")}]`
            }
            if(typeof data === "object"){
                const arr = Object.entries(data).reduce((acc,[key,value])=>{
                    if(value !== undefined) {
                        acc.push(`"${key}":${stringify(value)}`);
                    }
                    return acc;
                },[])
                return `{${arr.join(",")}}`
            }
            break;
    }
}

# 手写 JSON.parse

// 方法 1:使用 Function(或者 eval)
// 作为参数时,其类型会根据后续传递的参数类型为准
const parse1 = new Function("json", "return json");
// 当引用了外部的参数时,由于 Function 的主体需要是 js 代码,因此会将字符串进行 js 语法的解析,JSON 字符串会被解析成对象。
const parse2 = (json) => new Function(`console.log(${json});return `+json)();
console.log(
  parse1('{ "age": 20, "name": "jack" }')
);
console.log(
  parse2('{ "age": 20, "name": "jack" }')
);
// 方法 2:进行类型判断
function parse(str) {
    if(str === '') {
      throw Error();
    }
    if(str[0] === "'") {
      throw Error();
    }
    if(str === 'null') {
      return null;
    }
    if(str === '{}') {
      return {};
    }
    if(str === '[]') {
      return [];
    }
    if(str === 'true') {
      return true;
    }
    if(str === 'false') {
      return false;
    }
    if(str[0] === '"') {
      return str.slice(1, -1);
    }
    if(+str === +str) { // 不是 NaN
      return Number(str);
    }
    if(str[0] === '{') {
      return str.slice(1, -1).split(',').reduce((acc, item) => {
        const index = item.indexOf(':');
        const key = item.slice(0, index)
        const value = item.slice(index + 1);
        acc[parse(key)] = parse(value);
        return acc;
      }, {});
    }
    if(str[0] === '[') {
      return str.slice(1, -1).split(',').map((value) => parse(value));
    }
  }

# TS 类型体操

# TS Partial

type Partial<T> = {
  [K in keyof T]?:T[K];
}

# TS Required

type Required<T entends object> = {
  [K in keyof T]-?:T[K];
}

# TS ReadOnly

type Readonly<T extends object> = {
  readonly [K in keyof T]:T[K];
}

# TS Record

type Record <K extends number|string|symbol,V> = {
  [key in K]:V;
}

# TS Pick

type Pick<T,P extends keyof T> = {
  [K in P]:T[K];
}

# TS Omit

type Omit<T, K extends keyof any> = {
  // 使用 as 进行断言,如果不在 K 里面就变为 never
  [key in keyof T as key extends K?never:key]:T[key];
}

# React 手写

# useState

const state = [];
let curIndex = 0;
function useState(initialState) {
    const thisIndex = curIndex++;
    state[thisIndex] = state[thisIndex] || initialState;
    function setState(newState) {
        state[thisIndex] = newState;
        // 改变状态之后重新渲染组件
    }
    return [state[thisIndex],setState];
}

# 计数器

import {useState} from 'react'
export function App() {
  let [count,setCount] = useState(0);
  return (
    <div>
      <button onClick={()=>setCount(count-1)}>-</button>
      <button onClick={()=>setCount(count+1)}>+</button>
      <p>clicked: {count}</p>
    </div>
  )
}

# useTimeout

import {useRef,useEffect} from 'react';
export function useTimeout(callback:()=>void,delay:number){
  // 缓存回调函数
  const callbackRef = useRef(callback);
  callbackRef.current = callback; // 重置 callback
  useEffect(()=>{
    // 使用 callbackRef 使得无论 setTimeout 调用多少次,都始终引用相同的函数引用
    const timer = setTimeout(()=>callbackRef.current(),delay);
    return ()=>clearTimeout(timer);
  },[delay])
}

# useIsFirstRender

import {useRef} from 'react';
export function useIsFirstRender(): boolean {
  // 判断是否是第一次执行
  const isFirstRender = useRef(true);
  if(isFirstRender.current) {
    isFirstRender.current = false;
    return true;
  }
  return false;
}

# useSWR

import {useState,useEffect} from 'react';
export function useSWR<T = any, E = any>(
  _key: string,
  fetcher: () => T | Promise<T>
): {
  data?: T
  error?: E
} {
  const [data,setData] = useState<T>();
  const [error,setError] = useState<E>();
  useEffect(()=>{
    Promise.resolve(fetcher()).then(res=>setData(res)).catch(e=>setError(e))
  },[])
  return {data,error};
}
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

dmq 微信支付

微信支付

dmq 支付宝

支付宝