「一劳永逸」送你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 操作
核心要点 👇
- 创建一个新对象,这个对象的
__proto__
要指向构造函数的原型对象 - 执行构造函数
- 返回值为 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, "><,"); }