Neil.

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

重点:

  1. 链式调用:then 返回一个新的 Promise 实例;
  2. Promise 状态:初始为 Pending,转化为 fulfilled 或 rejected 后状态不再改变,且所有通过 then 注册进来的回调即便执行完也会被忽略,甚至抛出异常也将被忽略;
  3. all、race 实现:
    1. all:实例化一个 Promise,内部定义一个 values 数组,一个 Fulfilled Promises 的计数器;遍历并执行 Promise 数组的 then 方法,每 resolve 一个,则将其 value 存到数组对应下标中以保证有序,计数器自增;直到通过计数器判断 Promise 都已执行完,此时最外层 Promise 再 resolve 该数组;
    2. race:实例化一个 Promise,遍历并执行 Promise 数组的 then 方法,第一个 resolve 的 promise 会将外层 Promise 的状态置为 Fulfilled,其他 Promise 后续即便执行完毕也会被忽略;

Promise Mock

数组去重

写法很多,不再一一列举,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;
}

by 2023 前端面试系列— JS 篇

注:记住几种特殊情况: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();