20 道常考手写题(上)

78 阅读3分钟

20 道常考手写题(上)

1. call

function.call(thisArg, arg1, arg2, ...)

thisArg

可选的。在 function 函数运行时使用的 this 值。请注意,this 可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。

arg1, arg2, ...

指定的参数列表。

返回值

使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。

具体实现

Function.prototype._call = function (ctx, ...args) {
  //  如果为严格模式this为 undefined , node 为  global , 浏览器为windows
  const _this = (function () {
    return this;
  })();

  ctx = ctx == null ? _this : Object(ctx); // 原始值会被包装
  // 注: 为防止_fn重名和隐藏该属性可以用Symbol
  const key = Symbol("fn");
  ctx[key] = this;
  const res = ctx[key](...args);
  delete ctx[key];
  return res;
};

Object

// Object 构造函数为给定的参数创建一个包装类对象(object wrapper),具体有以下情况:

// 如果给定值是 null 或 undefined,将会创建并返回一个空对象 // 如果传进去的是一个基本类型的值,则会构造其包装类型的对象 // 如果传进去的是引用类型的值,仍然会返回这个值,经他们复制的变量保有和源对象相同的引用地址

2. apply

apply 和 call 的区别为第二个参数

func.apply(thisArg, [argsArray])

thisArg

必选的。在 func 函数运行时使用的 this 值。请注意,this 可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。

argsArray

可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。浏览器兼容性 请参阅本文底部内容。

返回值

调用有指定 this 值和参数的函数的结果。

// 注意 args = []要设置默认,否则 ...args 会报错
Function.prototype._apply = function (ctx, args = []) {
  //  如果为严格模式this为 undefined , node 为  global , 浏览器为windows
  const _this = (function () {
    return this;
  })();

  ctx = ctx == null ? _this : Object(ctx); // 原始值会被包装
  // 注: 为防止_fn重名和隐藏该属性可以用Symbol
  const key = Symbol("fn");
  ctx[key] = this;
  const res = ctx[key](...args);
  delete ctx[key];
  return res;
};

3. bind

注意点:

  1. 如果使用 new 运算符构造绑定函数,则忽略该值
  2. bind 可以传预设参数
  3. 或者 thisArg 是 null 或 undefined,执行作用域的 this 将被视为新函数的 thisArg。
Function.prototype._bind = function (ctx, ...rest) {
  const fn = this;
  // 获取当前环境的this
  const _this = (function () {
    return this;
  })();
  ctx = ctx == null ? _this : Object(ctx);
  return function (...args) {
    // 如果new 则 bind 失效
    if (this instanceof fn) {
      return new fn(...rest, ...args);
    }
    return fn.apply(ctx, [...rest, ...args]);
  };
};

4. inherit

  1. ES6 extend继承
class Person {}
class Student extends Person {}
  1. 通过函数原型继承
function inherit(Child, Parent) {
  Child.prototype = Object.create(Parent.prototype, {
    constructor: {
      value: Child,
      enumerable: false, // 不可枚举该属性
      writable: true, // 可改写该属性
      configurable: true, // 可用 delete 删除该属性
    },
  });
  // 继承静态方法
  Object.setPrototypeOf(Sub, Super);
}

5. new

注意点:

  1. generator 和 箭头函数不能 new
  2. new 返回值如果为对象则返回该对象,否则返回 new 创建的实例
function _new(Fn, ...args) {
  // 判断参数,必须为函数
  if (typeof Fn !== "function") {
    throw new TypeError("parameter type error");
  }
  //且不能为箭头函数和 generator
  if (!Fn.hasOwnProperty("prototype") || Object.prototype.toString.call(Fn) === "[object GeneratorFunction]") {
    throw new Error(`${Fn.name} is not a constructor`);
  }
  // 创建一个对象隐式原型指向Fn的显示原型
  const obj = Object.create(Fn.prototype);
  // 调用执行
  const res = Fn.apply(obj, args);
  //判断返回值
  return res instanceof Object ? res : obj;
}

6. instanceof

注意点: 左边如果为原始值直接返回 false,左边为 null 也返回 false

function _instanceof(l, R) {
  // 判断是不是对象或者函数(不包括null)
  const isObjectOrFunction = (target) => {
    return (typeof target === "object" && target !== null) || typeof target === "function";
  };
  if (!isObjectOrFunction(l)) {
    //原始值,直接返回false
    return false;
  } else {
    // 创建一个指针,指向l的隐式原型
    let p = l;
    // 构造函数的原型
    const prototype = R.prototype;
    while (p) {
      let proto = p.__proto__; //或者使用 Object.getPrototypeOf
      // 如果隐式原型===显示原型,则返回true
      if (proto === prototype) {
        return true;
      }
      // 沿着原型链执行,最终到null结束
      p = proto;
    }
    // 执行了到原型链终点,没有找到则返回false
    return false;
  }
}
console.log(_instanceof(1, Number)); //false
console.log(_instanceof("1", String)); //false
console.log(_instanceof(null, Object)); //false
console.log(_instanceof({}, Object)); //true
console.log(_instanceof(() => {}, Object)); //true
console.log(_instanceof(new Date(), Date)); //true
console.log(_instanceof(new Date(), Object)); //true
// 三个有意思的
console.log(_instanceof(Function, Function)); //true
console.log(_instanceof(Object, Function)); //true
console.log(_instanceof(Function, Object)); //true

7. deepClone

要求

  1. 支持对象、数组、日期、正则的拷贝。
  2. 处理原始类型(原始类型直接返回,只有引用类型才有深拷贝这个概念)。
  3. 处理 Symbol 作为键名的情况。
  4. 处理函数(函数直接返回,拷贝函数没有意义,两个对象使用内存中同一个地址的函数,没有任何问题)。
  5. 处理 DOM 元素(DOM 元素直接返回,拷贝 DOM 元素没有意义,都是指向页面中同一个)。
  6. 额外开辟一个储存空间 WeakMap,解决循环引用递归爆栈问题(引入 WeakMap 的另一个意义,配合垃圾回收机制,防止内存泄漏)
const a = {
  str: "abc",
  arr: [1, 2, 3, { value: 4 }],
  obj: {
    name: "b",
  },
  date: new Date(),
  reg: new RegExp(),
  null: null,
  [Symbol("name")]: "symbol",
  fn: () => {},
};
a.a = a; //循环引用
const b = deepClone(a);
console.log(a);
console.log(b);
function deepClone(target, hash = new WeakMap()) {
  // 处理原始类型和函数 不需要深拷贝,直接返回
  if (typeof target !== "object" || target == null) return target;
  //处理Date
  if (target instanceof Date) return new Date(target);
  // 处理 RegExp
  if (target instanceof RegExp) return new RegExp(target);
  // 处理 HTMLElement
  if (target instanceof HTMLElement) return target;
  if (hash.has(target)) {
    return hash.get(target);
  }
  const newTarget = new target.constructor(); // 创建一个新的克隆对象或克隆数组
  hash.set(target, newTarget);
  // 处理Symbol为键的情况, Reflect.ownKeys遍历自身所有属性包括symbol
  Reflect.ownKeys(target).forEach((key) => {
    newTarget[key] = deepClone(target[key], hash); //hash 记得要传!!!
  });
  return newTarget;
}

8. compose

function compose(...funcs) {
  if (funcs.length === 0) {
    return (arg) => arg;
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  return funcs.reduce(
    (a, b) =>
      (...args) =>
        a(b(...args))
  );
}

9. currying

currying 函数柯里化

function currying(fn) {
  const _args = [];
  const len = fn.length;
  return function _currying(...args) {
    _args.push(...args);
    if (_args.length >= len) {
      return fn.apply(this, _args);
    } else {
      return _currying;
    }
  };
}

用柯里化更简洁直观的调用

function checkByRegExp(reg, string) {
  return reg.test(string);
}
console.log(checkByRegExp(/^1\d{10}$/, "17622222222")); // 校验电话号码
console.log(checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, "test@163.com")); // 校验邮箱

//柯里化
const checkPhone = currying(checkByRegExp)(/^1\d{10}$/);
const checkEmail = currying(checkByRegExp)(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);
console.log(checkPhone("17622222222"));
console.log(checkPhone("17611111111"));
console.log(checkPhone("test@163.com"));

10. flat

flat:打平数组,比如 [1,[2,3,[4,5],[6]]] 转化为 [ 1, 2, 3, 4, 5, 6 ]

function flat(arr) {
  return arr.reduce((memo, cur) => {
    return Array.isArray(cur) ? [...memo, ...flat(cur)] : [...memo, cur];
  }, []);
}
console.log(flat([1, [2, 3, [4, 5], [6]]])); //[ 1, 2, 3, 4, 5, 6 ]