「一劳永逸」送你21道高频JavaScript手写面试题(下)

简介: 「一劳永逸」送你21道高频JavaScript手写面试题

「一劳永逸」送你21道高频JavaScript手写面试题(上):https://developer.aliyun.com/article/1483400


手写 call 和 apply


改变 this 指向,唯一区别就是传递参数不同 👇

// 实现call
Function.prototype.mycall = function () {
  let [thisArg, ...args] = [...arguments];
  thisArg = Object(thisArg) || window;
  let fn = Symbol();
  thisArg[fn] = this;
  let result = thisArg[fn](...args);
  delete thisArg[fn];
  return result;
};
// 实现apply
Function.prototype.myapply = function () {
  let [thisArg, args] = [...arguments];
  thisArg = Object(thisArg);
  let fn = Symbol();
  thisArg[fn] = this;
  let result = thisArg[fn](...args);
  delete thisArg.fn;
  return result;
};
//测试用例
let cc = {
  a: 1,
};
function demo(x1, x2) {
  console.log(typeof this, this.a, this);
  console.log(x1, x2);
}
demo.apply(cc, [2, 3]);
demo.myapply(cc, [2, 3]);
demo.call(cc, 33, 44);
demo.mycall(cc, 33, 44);

手写 bind


bind 它并不是立马执行函数,而是有一个延迟执行的操作,就是生成了一个新的函数,需要你去执行它 👇

// 实现bind
Function.prototype.mybind = function (context, ...args) {
  return (...newArgs) => {
    return this.call(context, ...args, ...newArgs);
  };
};
// 测试用例
let cc = {
  name: "TianTian",
};
function say(something, other) {
  console.log(`I want to tell ${this.name} ${something}`);
  console.log("This is some" + other);
}
let tmp = say.mybind(cc, "happy", "you are kute");
let tmp1 = say.bind(cc, "happy", "you are kute");
tmp();
tmp1();

实现 new 操作


核心要点 👇


  1. 创建一个新对象,这个对象的__proto__要指向构造函数的原型对象
  2. 执行构造函数
  3. 返回值为 object 类型则作为 new 方法的返回值返回,否则返回上述全新对象


代码如下 👇

function _new() {
  let obj = {};
  let [constructor, ...args] = [...arguments];
  obj.__proto__ = constructor.prototype;
  let result = constructor.apply(obj, args);
  if ((result && typeof result === "function") || typeof result === "object") {
    return result;
  }
  return obj;
}

实现 instanceof


「instanceof」 「运算符」用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。


语法 👇

object instanceof constructor
object 某个实例对象
construtor 某个构造函数

原型链的向上找,找到原型的最顶端,也就是 Object.prototype,代码 👇

function my_instance_of(leftVaule, rightVaule) {
  if (typeof leftVaule !== "object" || leftVaule === null) return false;
  let rightProto = rightVaule.prototype,
    leftProto = leftVaule.__proto__;
  while (true) {
    if (leftProto === null) {
      return false;
    }
    if (leftProto === rightProto) {
      return true;
    }
    leftProto = leftProto.__proto__;
  }
}

实现 sleep


某个时间后就去执行某个函数,使用 Promise 封装 👇

function sleep(fn, time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(fn);
    }, time);
  });
}
let saySomething = (name) => console.log(`hello,${name}`);
async function autoPlay() {
  let demo = await sleep(saySomething("TianTian"), 1000);
  let demo2 = await sleep(saySomething("李磊"), 1000);
  let demo3 = await sleep(saySomething("掘金的好友们"), 1000);
}
autoPlay();

实现数组 reduce


更多的手写实现数组方法,看我之前这篇 👉 「数组方法」从详细操作 js 数组到浅析 v8 中 array.js


直接给出简易版 👇

Array.prototype.myreduce = function (fn, initVal) {
  let result = initVal,
    i = 0;
  if (typeof initVal === "undefined") {
    result = this[i];
    i++;
  }
  while (i < this.length) {
    result = fn(result, this[i]);
  }
  return result;
};

实现 Promise.all 和 race

不清楚两者用法的话,异步 MDN👉Promise.race() Promise.all()

// 实现Promise.all 以及 race
Promise.myall = function (arr) {
  return new Promise((resolve, reject) => {
    if (arr.length === 0) {
      return resolve([]);
    } else {
      let res = [],
        count = 0;
      for (let i = 0; i < arr.length; i++) {
        // 同时也能处理arr数组中非Promise对象
        if (!(arr[i] instanceof Promise)) {
          res[i] = arr[i];
          if (++count === arr.length) resolve(res);
        } else {
          arr[i].then(
            (data) => {
              res[i] = data;
              if (++count === arr.length) resolve(res);
            },
            (err) => {
              reject(err);
            }
          );
        }
      }
    }
  });
};
Promise.myrace = function (arr) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < arr.length; i++) {
      // 同时也能处理arr数组中非Promise对象
      if (!(arr[i] instanceof Promise)) {
        Promise.resolve(arr[i]).then(resolve, reject);
      } else {
        arr[i].then(resolve, reject);
      }
    }
  });
};

测试用例 👇

// 测试用例
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(11);
  }, 2000);
});
let p2 = new Promise((resolve, reject) => {
  reject("asfs");
});
let p3 = new Promise((resolve) => {
  setTimeout(() => {
    resolve(33);
  }, 4);
});
Promise.myall([p3, p1, 3, 4]).then(
  (data) => {
    // 按传入数组的顺序打印
    console.log(data); // [3, 1, 2]
  },
  (err) => {
    console.log(err);
  }
);
Promise.myrace([p1, p2, p3]).then(
  (data) => {
    // 谁快就是谁
    console.log(data); // 2
  },
  (err) => {
    console.log("失败跑的最快");
  }
);

手写继承


继承有很多方式,这里不过多追溯了,可以看看这篇 JS 原型链与继承别再被问倒了


主要梳理的是 寄生组合式继承 和 Class 继承怎么使用


「寄生组合式继承」

function inheritPrototype(subType, superType) {
  // 创建对象,创建父类原型的一个副本
  var prototype = Object.create(superType.prototype);
  // 增强对象,弥补因重写原型而失去的默认的constructor 属性
  prototype.constructor = subType;
  // 指定对象,将新创建的对象赋值给子类的原型
  subType.prototype = prototype;
}

测试用例 👇

// 父类初始化实例属性和原型属性
function Father(name) {
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
Father.prototype.sayName = function () {
  alert(this.name);
};
// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function Son(name, age) {
  Father.call(this, name);
  this.age = age;
}
// 将父类原型指向子类
inheritPrototype(Son, Father);
// 新增子类原型属性
Son.prototype.sayAge = function () {
  alert(this.age);
};
var demo1 = new Son("TianTian", 21);
var demo2 = new Son("TianTianUp", 20);
demo1.colors.push("2"); // ["red", "blue", "green", "2"]
demo2.colors.push("3"); // ["red", "blue", "green", "3"]

Class 实现继承 👇

class Rectangle {
  // constructor
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
  // Getter
  get area() {
    return this.calcArea();
  }
  // Method
  calcArea() {
    return this.height * this.width;
  }
}
const rectangle = new Rectangle(40, 20);
console.log(rectangle.area);
// 输出 800
// 继承
class Square extends Rectangle {
  constructor(len) {
    // 子类没有this,必须先调用super
    super(len, len);
    // 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
    this.name = "SquareIng";
  }
  get area() {
    return this.height * this.width;
  }
}
const square = new Square(20);
console.log(square.area);
// 输出 400

extends继承的核心代码如下,其实和上述的寄生组合式继承方式一样 👇

function _inherits(subType, superType) {
  // 创建对象,创建父类原型的一个副本
  // 增强对象,弥补因重写原型而失去的默认的constructor 属性
  // 指定对象,将新创建的对象赋值给子类的原型
  subType.prototype = Object.create(superType && superType.prototype, {
    constructor: {
      value: subType,
      enumerable: false,
      writable: true,
      configurable: true,
    },
  });
  if (superType) {
    Object.setPrototypeOf
      ? Object.setPrototypeOf(subType, superType)
      : (subType.__proto__ = superType);
  }
}

把实现原理跟面试官扯一扯,这小子基础还行。


手写一下 AJAX


写的初略版的,详细版的就不梳理了,面试的时候,跟面试官好好探讨一下吧 🐂🐂🐂

var request = new XMLHttpRequest();
request.open("GET", "index/a/b/c?name=TianTian", true);
request.onreadystatechange = function () {
  if (request.readyState === 4 && request.status === 200) {
    console.log(request.responseText);
  }
};
request.send();

用正则实现 trim()


去掉首位多余的空格 👇

String.prototype.trim = function () {
  return this.replace(/^\s+|\s+$/g, "");
};
//或者
function trim(string) {
  return string.replace(/^\s+|\s+$/g, "");
}

实现 Object.create 方法

//实现Object.create方法
function create(proto) {
  function Fn() {}
  Fn.prototype = proto;
  Fn.prototype.constructor = Fn;
  return new Fn();
}
let demo = {
  c: "123",
};
let cc = Object.create(demo);

实现一个同时允许任务数量最大为 n 的函数


使用 Promise 封装,给你一个数组,数组的每一项是一个 Promise 对象

function limitRunTask(tasks, n) {
  return new Promise((resolve, reject) => {
    let index = 0,
      finish = 0,
      start = 0,
      res = [];
    function run() {
      if (finish == tasks.length) {
        resolve(res);
        return;
      }
      while (start < n && index < tasks.length) {
        // 每一阶段的任务数量++
        start++;
        let cur = index;
        tasks[index++]().then((v) => {
          start--;
          finish++;
          res[cur] = v;
          run();
        });
      }
    }
    run();
  });
  // 大概解释一下:首先如何限制最大数量n
  // while 循环start < n,然后就是then的回调
}

10 进制转换


给定 10 进制数,转换成[2~16]进制区间数,就是简单模拟一下。

function Conver(number, base = 2) {
  let rem,
    res = "",
    digits = "0123456789ABCDEF",
    stack = [];
  while (number) {
    rem = number % base;
    stack.push(rem);
    number = Math.floor(number / base);
  }
  while (stack.length) {
    res += digits[stack.pop()].toString();
  }
  return res;
}

数字转字符串千分位


写出这个就很逼格满满 🐂🐂🐂

function thousandth(str) {
  return str.replace(/\d(?=(?:\d{3})+(?:\.\d+|$))/g, "><,");
}
相关文章
|
4天前
|
JavaScript
分享经典面试题:JS数组去重的多种方法
分享经典面试题:JS数组去重的多种方法
|
16天前
|
前端开发 JavaScript
JavaScript新科技:PostCSS的安装和使用,2024年最新2024网易Web前端高级面试题总结
JavaScript新科技:PostCSS的安装和使用,2024年最新2024网易Web前端高级面试题总结
|
16天前
|
前端开发 JavaScript
JavaScript:this-关键字,2024中级前端开发面试解答
JavaScript:this-关键字,2024中级前端开发面试解答
|
17天前
|
JSON JavaScript 前端开发
web前端入门到实战:32道常见的js面试题,2024年最新秋招是直接面试吗
web前端入门到实战:32道常见的js面试题,2024年最新秋招是直接面试吗
|
17天前
|
JavaScript 前端开发 程序员
web前端入门到实战:32道常见的js面试题(1),面试哪些
web前端入门到实战:32道常见的js面试题(1),面试哪些
|
17天前
|
前端开发 JavaScript
web前端JS高阶面试题,2024我的前端大厂面试之旅
web前端JS高阶面试题,2024我的前端大厂面试之旅
|
17天前
|
JavaScript 前端开发
web前端JS高阶面试题(1),高级开发工程师面试
web前端JS高阶面试题(1),高级开发工程师面试
|
17天前
|
JSON 前端开发 JavaScript
【JavaScript】面试手撕深拷贝(2),2024年最新nacos面试题及答案
【JavaScript】面试手撕深拷贝(2),2024年最新nacos面试题及答案
【JavaScript】面试手撕深拷贝(2),2024年最新nacos面试题及答案
|
17天前
|
JSON JavaScript 前端开发
【JavaScript】面试手撕深拷贝(1),面试前必看的一本书书籍是什么
【JavaScript】面试手撕深拷贝(1),面试前必看的一本书书籍是什么
|
17天前
|
缓存 前端开发 JavaScript
Javascript模块化开发基础,最新美团点评前端团队面试题
Javascript模块化开发基础,最新美团点评前端团队面试题