一、JavaScript基础
1. 手写instanceOf
instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置
具体实现:
function _instanceOf (obj, constructor) {
// constructor 的prototype在obj对象的原型链上则返回true
while (obj.__proto__) {
if (obj.__proto__ === constructor.prototype) {
return true;
}
obj.__proto__ = obj.__proto__.__proto__;
}
return false;
}
2. 手写Object.create
创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
function create(proto) {
const F = new Function();
F.prototype = proto;
return new F();
}
3. 手写new操作符
function new(fn, ...args) {
const obj = Object.create(fn.prototype);
const res = fn.apply(obj, args);
return typeof res === 'object' ? res : obj;
}
4. 手写apply
Function.prototype.apply(context, ...args) {
if (!context) {
context = typeof window === 'undefined' ? global : window;
}
context.fn = this;
const arguments = ...args;
const result = context.fn(...arguments);
delete context.fn;
return result;
}
5. 手写call
// 与apply类似,只是参数传递形式不一样
Function.prototype.apply(context, ...args) {
if (!context) {
context = typeof window === 'undefined' ? global : window;
}
context.fn = this;
const arguments = ...args;
const result = context.fn(...args);
delete context.fn;
return result;
}
6. 手写bind
// 与apply和bind不同,不执行函数。返回改变了this指向的函数
Function.prototype.bind(context, ...args) {
if (!context) {
context = typeof window === 'undefined' ? global : window;
}
const self = this;
return function() {
self.apply(context, [...args, ...arguments]);
}
}
7. 手写Promise
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;
function MyPromise(executor) {
const self = this;
self.status = 0;
self.data = undefined;
self.onResolvedcallback = [];
self.onRejectedCallback = [];
function resolve (value) {
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}
if (self.status === 0) {
self.status = 1;
self.data = value;
for (let i = 0; i < self.onResolvedcallback.length; i++) {
self.onResolvedcallback[i](value);
}
}
}
function reject(reason) {
if (self.status === 0) {
self.status = 2;
self.data = reason;
for (let i = 0; i < self.onResolvedcallback.length; i++) {
self.onRejectedCallback[i](reason);
}
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e);
}
}
8. 手写Promise.then
MyPromise.prototype.then = function (onResolved, onRejected) {
const self = this;
if (self.status === 0) {
return new Promise(function(resolve, reject) {
self.onResolvedcallback.push(function(value) {
try {
const x = onResolved(value);
if (x instanceof MyPromise) {
x.then(resolve, reject)
}
resolve(x);
} catch(e) {
reject(e);
}
})
self.onRejectedCallback.push(function(reason) {
try {
const x = onRejected(reason);
if (x instanceof MyPromise) {
x.then(resolve, reject)
}
resolve(x);
} catch(e) {
reject(e);
}
})
})
}
if (self.status === 1) {
return new Promise(function(resolve, reject) {
try {
const x = onResolved(self.data);
if (x instanceof MyPromise) {
x.then(resolve, reject)
}
resolve(x);
} catch(e) {
reject(e);
}
})
}
if (self.status === 2) {
return new Promise(function(resolve, reject) {
try {
const x = onRejected(self.data);
if (x instanceof MyPromise) {
x.then(resolve, reject)
}
reject(x);
} catch(e) {
reject(e);
}
})
}
}
9. 手写Promise.all
function promiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
for (let i = 0 ; i < promises.length; i++) {
Promise.resolve(promises[i]).then((res) => {
results[i] = res;
if (results.length === promises.length) {
resolve(results)
}
})
.catch((err) => {
reject(err);
})
}
});
}
10. 手写Promise.race
function promiseRace(promises) {
return new Promise((resolve, reject) => {
for (let i = 0 ; i < promises.length; i++) {
promises[i].then(resolve, reject);
}
})
}
11. 手写Promise.prototype.catch
Promise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
}
12. 手写Promise.prototype.finally
Promise.prototype.finally = function(callback) {
return this.then((value) => {
Promise.resolve(callback()).then(() => value)
}, (error) => {
Promise.resolve(callback()).then(() => throw error)
});
}
13. 手写节流函数
// 节流: 指定时间间隔内只会执行一次任务
// 指连续触发事件但是在 n 秒中只执行一次函数。 节流会稀释函数的执行频率
function throttle(fn, delay) {
let last = + new date();
return function(...args) {
const now = +new date();
if (now - last >= delay) {
last = now;
fn.apply(this, args)
}
}
}
14. 手写防抖
// 防抖: 就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) { clearTimeout(timer)}
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
}
}
15. 手写Array.prototype.reduce()
语法:arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
Array.prototype.reduce = function(callback, initvalue) {
// 如果提供了`initialValue`,则起始索引号为0,否则从索引1起始。
let value = initvalue || this[0];
const start = initvalue ? 0 : 1;
for (let i = start; i < this.length; i++) {
const item = this[i];
value = callback(value, item, i, this);
}
return value;
}
实现对象for of遍历
对于普通的对象,for...of结构不能直接使用,必须部署了Iterator接口后才能使用。
// 方法一:使用Object.keys方法将对象的键名生成一个数组,然后遍历这个数组
for (let key of Object.keys(obj)) {
console.log(`${key}: ${obj[key]}`);
}
// 方法二:使用Generator函数将对象包装一下
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]]
}
}
for (let [key value] of entries(obj)) {
console.log(`${key}: ${value}`);
}
二、数据处理
1. 数组扁平化
2. 对象扁平化
/* 题目*/
// 输入:
const obj = {
a: {
b: {
c: {
dd: "abcdd",
},
},
d: {
ee: "adee",
},
e: "ae",
},
f: "f",
g: [{ h: 1 }, 2, 3],
};
// 输出
{'a.b.c.dd': 'abcdd', 'a.d.ee': 'adee', 'a.e': 'ae', f: 'f', 'g.0.h': 1, 'g.1': 2, 'g.2': 3}
function flattenObj () {
const res = {};
const process = (obj, prevKey) => {
Object.keys(obj).forEach((key) => {
if (typeof obj[key] !== 'object') {
res[`${prevKey || ''}${key}`] = obj[key];
} else {
process(obj[key], `${prevKey || ''}${key}.`);
}
});
}
process(obj);
return res;
}
3. js将扁平结构数据转换为树形结构
/* 题目*/
const data = [{id: 1, parent: 4}, {id: 2, parent: 4}, {id: 3}, {id: 4}];
转换后的树形结构为:[
{ id: 3 },
{ id: 4,
children: [ { id: 1, parent: 4 }, { id: 2, parent: 4 }, ],
}
];
function jsonToTree(data, options={}) {
const {
key = 'id',
child = 'children',
parent = 'parent',
} = options;
const tree = [];
const record = {}; // // 用来记录扁平化数据,key为id, value为子元素
for (let i = 0; i < data.length; i++) {
const item = data[i];
const id = item[key];
const pid = item[parent];
if (!id) continue;
if (record[id]) {
item[child] = record[id];
}
if (pid) {
if (!record[pid]) {
record[pid] = [];
}
record[pid].push(item);
} else {
tree.push(item);
}
}
return tree;
}
4. 实现一个add函数,满足以下功能
add(1); // 1
add(1)(2); // 3
add(1)(2)(3);// 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(1, 2, 3); // 6
// 隐式调用
function add(...a) {
let sum = a.reduce((p, n) => p + n);
function next(...b) {
let _sum = b.reduce((p, n) => p + n);
sum = sum + _sum;
return next;
}
next.tostring = function() {
return sum;
}
return next;
}
三、场景处理
1. 报数问题
题目: 13个人围坐一圈报数(123的报数),凡是报到3的人出队,最后剩下的人的序号是什么?
function count(num) {
const arr = new Array(num).fill(true);
let leftCount = num; // 剩余人数
let countNum = 0; // 报数记录,为3时,出队
let index = 0; // 记录报数人的序号,会超过num,超过后需要取余
while (leftCount > 1) {
if (arr[index % num]) {
// 还在队列中,报数
countNum++;
if (countNum === 3) {
// 报数为3的人出队
countNum = 0;
arr[index % num] = false;
leftCount--;
}
}
index++;
}
return arr.findIndex(item => item);
}
count(13);
2. 实现sleep函数
// Promise
function sleep(time) {
return new Promise((resolve) => {
setTimeout(resolve, time)
});
}
sleep(3000);
3. 打乱一个数组
原理:循环遍历该数组,在每次遍历中产生一个(0 ~ length - 1)之间的随机下标的数,该数代表本次循环要随机交换的位置。 将本次循环当前位置的数和随机位置的数进行交换。
// 循环随机位交换法
let arr = [1, 2, 3, 4, 5];
function randFun(arr) {
for (let i = 0; i < arr.length; i++) {
let idx = parseInt(Math.random() * arr.length);
let temp = arr[i];
arr[i] = arr[idx];
arr[idx] = temp;
}
return arr;
}
4. 每隔一秒输出数组中的一个元素
function output(arr) {
let i = 0;
return function timer() {
setTimeout(() => {
console.log(arr[i++]);
i < arr.length && timer();
}, 1000);
}
}
// 使用promise实现
function output(arr) {
arr.reduce((prev, item) => {
return prev.then(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(console.log(item))
}, 1000)
})
})
}, Promise.resolve())
}
5. 使用Promise实现红绿灯交替重复
// 红灯3秒亮一次,黄灯2秒亮一次,绿灯1秒亮一次;如何让三个灯不断交替重复亮灯?
function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
const light = function(timer, cb) {
return new Promise((resolve) => {
setTimeout(() => {
cb();
resolve();
}, timer);
});
}
function step() {
Promise.resolve()
.then(() => {
return light(3000, red)
})
.then(() => {
return yellow(2000, yellow)
})
.then(() => {
return green(1000, green)
})
}
step();
6. 异步加载图片
function loadImage(url) {
return new Promise((resolve) => {
const img = new Image();
img.onload =function() {
resolve(img);
}
img.onerror = function() { reject(new Error('Could not load image at' + url)); };
img.src = url;
})
}
7. 限制异步操作的并发个数并尽可能快的完成全部
题目:
有8个图片资源的url,已经存储在数组urls中。
urls类似于['https://image1.png', 'https://image2.png', ....]
而且已经有一个函数function loadImg,输入一个url链接,返回一个Promise,该Promise在图片下载完成的时候resolve,下载失败则reject。
但有一个要求,任何时刻同时下载的链接数量不可以超过3个。
请写一段代码实现这个需求,要求尽可能快速地将所有图片下载完成。
function limitLoad(urls, handler, limit) {
const sequence = [...urls];
const promises = sequence.splice(0, limit).map((url, index) => {
return handler(url).then(() => index);
});
return promises.reduce((prev, url) => {
return prev.then(() => {
return Promise.race(promises)
})
.then((fastIndex) => {
promises[fastIndex] = handler(url).then(() => fastIndex)
})
}, Promise.resolve())
.then(() => {
Promise.all(promises);
})
}
四、面试题
1. 微软一面【WIP】
答案有空整理
主要考察模板字符串语法
this指向等
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
</head>
<body>
<h1>Case1</h1>
<div id="case1"></div>
<hr />
<h1>Case2</h1>
<div id="case2"></div>
<script src="src/index.js"></script>
</body>
</html>
function render(root, template, data) {
const _data = Object.assign(data, { rerender });
function rerender() {
root.innerHTML = template(_data);
}
rerender();
}
const template1 = html`
<div>
<h1>${(x) => x.title}</h1>
<p>${(x) => x.description}</p>
</div>
`;
const template2 = html`
<div>
<h1>${(x) => x.title}</h1>
<button ${(x) => x.update}>${(x) => x.number}</button>
<button ${(x) => x.reset}>reset</button>
</div>
`;
render(document.getElementById("case1"), template1, {
title: "Paragraph",
description: "Hi, template!"
});
render(document.getElementById("case2"), template2, {
title: "Counter",
number: 0,
update: {
type: "click",
runner() {
this.number = this.number + 1;
}
},
reset: {
type: "click",
runner() {
this.number = 0;
}
}
});
function html(strings, ...args) {
return (x) => {
const res = strings.reduce((prev, next, i) => {
let dyncticString = '';
if (args[i - 1]) {
let result = args[i - 1](x);
if (typeof result === "object") {
const { type, runner } = result;
result = `on${type} = ${runner}`
} else {
dyncticString = result;
}
}
prev = `${prev}${dyncticString}${next}`;
return prev;
});
return res;
};
}