JS手撕代码
typeof
// from MDN
function type(value) {
if (value === null) {
return "null";
}
const baseType = typeof value;
// 基本类型
if (!["object", "function"].includes(baseType)) {
return baseType;
}
// Symbol.toStringTag 通常指定对象类的“display name”
// 它在 Object.prototype.toString() 中使用。
const tag = value[Symbol.toStringTag];
if (typeof tag === "string") {
return tag;
}
// 如果它是一个函数,其源代码以 "class" 关键字开头
if (
baseType === "function" &&
Function.prototype.toString.call(value).startsWith("class")
) {
return "class";
}
// 构造函数的名称;例如 `Array`、`GeneratorFunction`、`Number`、`String`、`Boolean` 或 `MyCustomClass`
const className = value.constructor.name;
if (typeof className === "string" && className !== "") {
return className;
}
// 在这一点上,没有合适的方法来获取值的类型,因此我们使用基本实现。
return baseType;
}
instanceof
function instanceOf(instance, constructor) {
let target = constructor.prototype;
let proto = instance.__proto__;
while (proto !== null) {
if (target === proto) {
return true;
}
proto = proto.__proto__;
}
return false;
}
new
function __new(constructor, ...args: any[]) {
const obj = {};
const result = constructor.apply(obj, args);
// function或非空object将被直接返回
if (
typeof result === "function" ||
(typeof result === "object" && result !== null)
) {
return result;
}
return obj;
}
Function.prototype.call
Function.prototype.__call = function (context) {
// 当前上下文中,this指向__call的调用者
const func = this;
// 判断调用对象
if (typeof func != "function") {
throw new Error("type error");
}
// 获取参数
const parameters = Array.from(arguments);
// 改变this指向
const realContext = context || window;
const uniqueKey = Symbol();
realContext[uniqueKey] = func;
// 传入参数执行函数
const returns = realContext[uniqueKey](...parameters);
// 清理副作用
delete realContext[uniqueKey];
return returns;
};
注:改变 this 指向是借助 “形如obj.foo()的函数调用,foo 函数在运行时的 this 会指向 obj 的特点”,而为了不覆盖原函数的已有属性,通过 Symbol 定义一个不重复的属性名,用完即删;
Function.prototype.apply
Function.prototype.__apply = function (context, parameters) {
// 判断调用对象
if (typeof this != "function") {
throw new Error("type error");
}
// 改变this指向,传入参数执行函数,返回结果;
const realContext = context || window;
const temp = Symbol();
realContext[temp] = this;
const returns = realContext[temp](...parameters);
delete realContext[temp];
return returns;
};
Function.prototype.bind
Function.prototype.__bind = function (context) {
// 判断调用对象
if (typeof this != "function") {
throw new Error("type error");
}
// 获取参数
const parameters = [];
let i = 1;
while (arguments[i] !== undefined) {
parameters.push(arguments[i++]);
}
// 通过__this固定对调用者的引用,__this指向被bind的函数
const __this = this;
// 新函数中固定通过bind传入的参数,并合并新函数自身的实参
return function () {
const realContext = context || window;
const temp = Symbol();
realContext[temp] = __this;
const returns = realContext[temp](
...parameters.concat(Array.from(arguments))
);
delete realContext[temp];
return returns;
};
};
注 1:固定参数还有一种兼容性更好的写法,如下
const argumentsString = "";
for (let i = 0; arguments[i] !== undefined; i++) {
argumentsString = argumentsString + arguments[i] + ",";
}
eval("realContext.fn(" + argumentsString + ")");
Promise
重点:
- 链式调用:then 返回一个新的 Promise 实例;
- Promise 状态:初始为 Pending,转化为 fulfilled 或 rejected 后状态不再改变,且所有通过 then 注册进来的回调即便执行完也会被忽略,甚至抛出异常也将被忽略;
- all、race 实现:
- all:实例化一个 Promise,内部定义一个 values 数组,一个 Fulfilled Promises 的计数器;遍历并执行 Promise 数组的 then 方法,每 resolve 一个,则将其 value 存到数组对应下标中以保证有序,计数器自增;直到通过计数器判断 Promise 都已执行完,此时最外层 Promise 再 resolve 该数组;
- race:实例化一个 Promise,遍历并执行 Promise 数组的 then 方法,第一个 resolve 的 promise 会将外层 Promise 的状态置为 Fulfilled,其他 Promise 后续即便执行完毕也会被忽略;
数组去重
写法很多,不再一一列举,ES6 一般通过 Set 去重,时空复杂度都是线性的;
// 1.先排序,后去重;O(n*logn)
const func1 = (arr) => {
const result = [];
arr.sort();
for (let i = 0; i < arr.length; i++) {
if (result[result.length - 1] !== arr[i]) {
result.push(arr[i]);
}
}
return result;
};
// 2.HashSet去重, O(n)
const func2 = (arr) => {
return Array.from(new Set(arr));
};
// 3.Object模拟HashMap去重, O(n)
const func3 = (arr) => {
const map = {};
const result = [];
for (let i = 0; i < arr.length; i++) {
// 或 Object.hasOwn(map, arr[i])
if (!map.hasOwnProperty(arr[i])) {
Object.defineProperty(map, arr[i], {});
result.push(arr[i]);
}
}
return result;
};
// 4.filter + indexOf, O(n*n)
const func4 = (arr) => {
return arr.filter((item, index) => {
// indexOf一定命中从前往后第一个匹配元素
// 此时存在两种case
// 1. 当前元素是重复的,则filter中的index一定大于indexOf的结果
// 2. 不重复,则index一定等于indexOf结果
return arr.indexOf(item) === index;
});
};
// 5.indexOf, O(n*n)
const func5 = (arr) => {
const result = [];
for (let i = 0; i < arr.length; i++) {
if (arr.indexOf(arr[i]) === i) {
result.push(arr[i]);
}
}
return result;
};
Flat
// 递归
function flat_recursion(nestedArray: any[]) {
const result: any[] = [];
return (function dfs(arr: any[]) {
arr.forEach((item) => {
if (Array.isArray(item)) {
dfs(item);
} else {
result.push(item);
}
});
return result;
})(nestedArray);
}
// Array.prototype.toString
export function flat_toString(nestedArray: any[]) {
const flatArrayString = nestedArray.toString();
const result: number[] = [];
for (const s of flatArrayString) {
if (s === ",") {
continue;
}
result.push(+s);
}
return result;
}
// 递归 Array.prototype.reduce
export function flat_reduce(nestedArray: any[]) {
return nestedArray.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flat_recursion(cur) : cur);
}, []);
}
浅拷贝
// 浅拷贝所有自有非继承属性
function shallowCopy(obj) {
const copy = {};
const props = Object.getOwnPropertyNames(obj);
for (const prop of props) {
copy[prop] = obj[prop];
}
return copy;
}
// 浅拷贝所有自有可枚举属性(含原型链上继承的属性)
function shallowCopy(obj) {
const copy = {};
for (const prop in obj) {
copy[prop] = obj[prop];
}
return copy;
}
注:也可用 Object.assign({}, obj)、对象解构赋值语法实现;
深拷贝
function deepCopy(obj = {}, map = new Map()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
if (map.get(obj)) {
return map.get(obj);
}
let result = {}; // 初始化返回结果
if (
obj instanceof Array ||
// 加 || 的原因是为了防止 Array 的 prototype 被重写,Array.isArray 也是如此
Object.prototype.toString(obj) === "[object Array]"
) {
result = [];
}
// 防止循环引用
map.set(obj, result);
for (const key in obj) {
// 保证 key 不是原型属性
if (obj.hasOwnProperty(key)) {
// 递归调用
result[key] = deepClone(obj[key], map);
}
}
return result;
}
注:记住几种特殊情况:Date、RegExp、Array
Throttle(节流)
指定 ms 时间内的重复执行将被忽略;
// 节流
export function throttle(func, ms) {
let runNow = true;
return function () {
if (!runNow) {
return;
}
runNow = false;
const id = setTimeout(() => {
//@ts-ignore
func.apply(this, arguments);
runNow = true;
clearTimeout(id);
}, ms);
};
}
// 立刻执行一次,再节流
export function throttle(func, ms) {
let isFirstExecuted = true;
let runNow = true;
return function () {
if (isFirstExecuted) {
func.apply(this, arguments);
isFirstExecuted = false;
return;
}
if (!runNow) {
return;
}
runNow = false;
const id = setTimeout(() => {
//@ts-ignore
func.apply(this, arguments);
runNow = true;
clearTimeout(id);
}, ms);
};
}
Debounce(防抖)
指定 ms 时间内,如果再次执行 debounced 函数,上次执行将失效,然后重新按 ms 时间计时;
// 防抖
export function debounce(func: () => any | void, ms) {
let id;
return function () {
clearTimeout(id);
id = setTimeout(() => {
//@ts-ignore
func.apply(this, arguments);
}, ms);
};
}
// 立刻执行一次,再防抖
export function debounce(func: () => any | void, ms) {
let isFirstExecuted = true;
let id;
return function () {
if (isFirstExecuted) {
func.apply(this, arguments);
isFirstExecuted = false;
return;
}
clearTimeout(id);
id = setTimeout(() => {
//@ts-ignore
func.apply(this, arguments);
}, ms);
};
}
Generator Function 处理异步操作
// Thunk函数式的异步操作1
function boo1(seconds) {
return function (next) {
setTimeout(() => {
next("a");
}, seconds);
};
}
// Thunk函数式的异步操作2
function boo2(str, seconds) {
return function (next) {
setTimeout(() => {
next(str + "b");
}, seconds);
};
}
// Generator函数,用同步式的代码组织异步操作
function* foo(seconds = 1000) {
const s1 = yield boo1(seconds);
const s2 = (yield boo2(s1, seconds)) + "c";
console.log(s2); // "abc"
}
// 自动执行Generator函数
function run(genFn) {
// 获得Generator对象(还是一个Iterator)
const gen = genFn();
// 包装Generator对象的next方法,并将其传递给异步操作函数,用于交出执行权,以及待异步操作执行完成后交还执行权
function next(data) {
const result = gen.next(data);
if (result.done) return;
result.value(next);
}
// 开始执行
next();
}
run(foo); // 大约两秒后打印"abc"
Async Function 处理异步操作
与 Generator 处理异步操作中的例子相同,对比之下可以看出 Async Function 的优点;
const boo1 = (seconds) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve("a");
}, seconds);
});
};
const boo2 = (str, seconds) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(str + "b");
}, seconds);
});
};
async function foo(seconds = 1000) {
const s1 = await boo1(seconds);
const s2 = (await boo2(s1, seconds)) + "c";
console.log(s2);
}
foo();