本文内容转载自32个手写JS,巩固你的JS基础(面试高频)
1. 数组扁平化
数组扁平化是指将一个多维数组变为一个一维数组。
const arr = [1, [2, [3, [4, 5]]], 6];
// => [1, 2, 3, 4, 5, 6]
方法一:利用flat方法
const res = arr.flat(Infinite);
方法二:利用正则
const res2 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');
方法三:使用reduce
const flatten = arr => {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, [])
}
console.log(flatten(arr));
方法四:使用递归
const flatten = arr => {
const res = [];
const fn = arr => {
for (const num of arr) {
if (Array.isArray(num)) {
fn(num);
} else {
res.push(num);
}
}
}
return res;
}
2. 数组去重
const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];
// => [1, '1', 17, true, false, 'true', 'a', {}, {}]
方法一:利用Set
const res = Array.from(new Set(arr));
方法二:两层for循环 + splice
const unique = arr => {
let len = arr.length;
for (let i = 0; i < len; i++) {
for (let j = i + 1; j < len; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
j--;
len--;
}
}
}
return arr;
}
方法三:利用indexOf
const unique = arr => {
const res = [];
for (let i = 0; i < arr.length; i++) {
if (res.indexOf(arr[i]) === -1) {
res.push(arr[i])
}
}
return res;
}
方法四:利用includes
const unique = arr => {
const res = [];
for (const num of arr) {
if (!res.includes(num)) {
res.push(num);
}
}
return res;
}
方法五:利用filter
const unique = arr => {
return arr.filter((item, index) => {
return arr.indexOf(item) === index;
})
}
方法六:利用Map
const unique = arr => {
const map = new Map();
const res = [];
for (const num of arr) {
if (!map.has(num)) {
map.set(num, true);
res.push(num);
}
}
return res;
}
3. 类数组转数组
类数组是具有length属性,但不具有数组原型上的方法。常见的类数组有arguments
、DOM操作方法返回的结果
。
方法一:Array.from
Array.from(document.querySelectorAll('div'))
方法二:扩展运算符
[...document.querySelectorAll('div')]
方法三:Array.prototype.slice.call
Array.prototype.slice.call(document.querySelectorAll('div'))
方法四:利用concat
Array.prototype.concat.apply([], document.querySelectorAll('div'));
4. debounce(防抖)
触发高频时间后n秒内函数只会执行一次,如果n秒内高频时间再次触发,则重新计算时间。
const debounce = (fn, time) => {
let timeout = null;
return function() {
clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(this, arguments);
}, time)
}
}
防抖常应用于用户进行搜索输入节约请求资源
,window触发resize
事件时进行防抖只触发一次。
5. throttle(节流)
高频时间触发,但n秒内只会执行一次,所以节流会稀释函数的执行频率。
const throttle = (fn, time) => {
let timeout = null;
return function() {
if (!timeout) {
timeout = setTimeout(() => {
fn.apply(this, [...arguments]);
clearTimeout(timeout);
timeout = null;
}, time);
}
}
}
6. 函数柯里化
指的是将一个接受多个参数的函数 变为 接受一个参数返回一个函数的固定形式,这样便于再次调用。
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
const _args = [...arguments];
function fn() {
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
_args.push(...arguments);
return fn;
}
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
fn.toString = function() {
return _args.reduce((sum, cur) => sum + cur);
}
return fn;
}
var a = add(1)(2)(3)(4);
var b = add(1)(2,3,4)(5);
console.log(a + a); // 10
console.log(b + b); // 20
valueOf偏向于运算,toString偏向于显示。上边的fn.toString可以换成fn.valueOf,对a进行运算时会优先执行valueOf,对a进行展示时会执行toString。
7. new操作符
function newOperator(ctor) {
if (typeof ctor !== 'function') {
throw new TypeError(ctor + 'is not a function.');
}
newOperator.target = ctor;
var newObj = Object.create(ctor.prototype);
var argsArr = [].slice.call(arguments, 1);
var ctorReturnRes = ctor.apply(newObj, argsArr);
var isObject = typeof ctorReturnRes === 'object' && ctorReturnRes !== null;
var isFunction = typeof ctorReturnRes === 'function';
if (isObject || isFunction) return ctorReturnRes;
return newObj;
}
8. instanceof操作符
function instanceofFn(A, B) {
if (typeof A !== 'object' || !A) return false;
let p = A.__proto;
while (p) {
if (p === B.prototype) return true;
p = p.__proto;
}
return false;
}
9. Object.is
Object.is 和 === 的区别体现在
+0 === -0; // true
NaN === NaN; // false
Object.is(+0, -0); // false
Object.is(NaN, NaN); // true
const is = (x, y) => {
if (x === y) {
return x !== 0 || y !== 0 || 1/x === 1/y;
} else {
return x !== x || y !== y;
}
}
10. 深拷贝
考虑Symbol
和Set
,Map
数据结构的深拷贝。
const deepClone = (target, hash = new WeakMap()) {
if (typeof target !== 'object' || target === null) {
return target;
}
if (hash.has(target)) {
return hash.get(target);
}
const cloneTarget = Array.isArray(target) ? [] : {};
hash.set(target, cloneTarget);
if (Object.prototype.toString.call(target) === '[object Set]') {
target.forEach((value) => {
cloneTarget.add(deepClone(value, hash));
});
return cloneTarget;
}
if (Object.prototype.toString.call(target) === '[object Map]') {
target.forEach((item, key) => {
cloneTarget.set(key, deepClone(item, hash));
})
return cloneTarget;
}
const symKeys = Object.getOwnPropertySymbols(target);
if (symKeys.length) {
symKeys.forEach((key) => {
if (typeof target[key] === 'object' && target[key] !== null) {
cloneTarget[key] = deepClone(target[key]);
} else {
cloneTarget[key] = target[key];
}
});
}
for (const key in target) {
if (Object.prototype.hasOwnPerperty.call(target, key)) {
if (typeof target[key] === 'object' && target[key] !== null) {
cloneTarget[key] = deepClone(target[key], hash);
} else {
cloneTarget[key] = target[key];
}
}
}
return cloneTarget;
}
11. Promise
const PENDING = 'PENDING'; // 进行中
const FULFILLED = 'FULFILLED'; // 已成功
const REJECTED = 'REJECTED'; // 已失败
class Promise {
constructor(exector) {
// 初始化状态
this.status = PENDING;
this.value = undefined; // 成功结果
this.reason = undefined; // 失败结果
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = value => {
// 只有进行中状态才能改变
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn(this.value));
}
}
const reject = reason => {
// 只有进行中状态才能改变
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn(this.reason));
}
}
try {
exector(resolve, reject);
} catch(e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function'
? onFulfilled : value => value;
onRejected = typeof onRejected === 'function'
? onRejected : reason => {
throw new Error(reason instanceof Error ? reason.message : reason)
}
const self = this;
return new Promise((resolve, reject) => {
if (self.status === PENDING) {
self.onFulfilledCallbacks.push(() => {
try {
// 模拟微任务
setTimeout(() => {
const result = onFulfilled(self.value);
result instanceof Promise
? result.then(resolve, reject) : resolve(result);
})
} catch(e) {
reject(e);
}
})
}
});
}
catch(onRejected) {
return this.then(null, onRejected)
}
static resolve(value) {
if (value instanceof Promise) {
// 如果是Promise实例,直接返回
return value;
} else {
// 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED
return new Promise((resolve, reject) => resolve(value));
}
}
static reject(reason) {
return new Promise((resolve, reject) => {
reject(rason);
});
}
static all(PromiseArr) {
const len = PromiseArr.length;
const values = new Array(len);
// 记录已经成功执行的promise个数
let count = 0;
return new Promise((resolve, reject) => {
for (let i = 0; i < len; i++) {
Promise.resolve(promiseArr[i]).then(
val => {
values[i] = val;
count++;
// 如果全部执行完,返回promise的状态就可以改变了
if (count === len) resolve(values);
},
err => reject(err)
)
}
});
}
static race(promiseArr) {
return new Promise((resolve, reject) => {
promiseArr.forEach(p => {
Promise.resolve(p).then(
val => resolve(val),
err => reject(err)
)
})
});
}
}
12. Promise并行限制调度器
class Scheduler {
constructor() {
this.queue = [];
this.maxCount = 2;
this.runCount = 0;
}
add(promiseCreator) {
this.queue.push(promiseCreator);
}
taskStart() {
for (let i = 0; i < this.maxCount; i++) {
this.request();
}
}
request() {
if (!this.queue || !this.queue.length || this.runCount >= this.maxCount) {
return;
}
this.runCount++;
this.queue.shift()().then(() => {
this.runCount--;
this.request();
})
}
addTask(time, order) {
this.add(() => {
return new Promise(resolve => {
setTimeout(resolve, time);
}).then(() => {
console.log(order);
})
})
}
}
const scheduler = new Scheduler();
scheduler.addTask(1000, '1');
scheduler.addTask(500, '2');
scheduler.addTask(300, '3');
scheduler.addTask(400, '4');
scheduler.taskStart();
13. Ajax
const getJSON = function (url) {
return new Promise((resolve, reject) => {
const xhr = XMLHttpRequest
? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
xhr.open('GET', url, false);
xhr.setRequestHeader('Accept', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4) return;
if (xhr.status === 200 || xhr.status === 304) {
resolve(xhr.responseText);
} else {
reject(new Error(xhr.responseText));
}
};
xhr.send();
});
}
14. 实现event
function EventEmitter() {
this.events = new Map();
}
const wrapCallback = (fn, once = false) => ({ callback: fn, once });
EventEmitter.prototype.addListener = function(type, fn, once = false) {
const handler = this.events.get(type);
if (!handler) {
this.events.set(type, wrapCallback(fn, once));
} else if (handler && handler.callback === 'function') {
this.events.set(type, [handler, wrapCallback(fn, once)]);
} else {
handler.push(wrapCallback(fn, once));
}
}
EventEmitter.prototype.removeListener = function(type, listener) {
const handler = this.events.get(type);
if (!handler) return;
if (!Array.isArray(this.events)) {
if (handler.callback === listener.callback) this.events.delete(type);
else return;
}
for (let i = 0; i < handler.length; i++) {
const item = handler[i];
if (item.callback === listener.callback) {
handler.splice(i, 1);
i--;
if (handler.length === 1) {
this.events.set(type, handler[0]);
}
}
}
}
EventEmitter.prototype.once = function (type, listener) {
this.addListener(type, listener, true);
}
EventEmitter.prototype.emit = function (type, ...args) {
const handler = this.events.get(type);
if (!handler) return;
if (Array.isArray(handler)) {
handler.forEach(item => {
item.callback.apply(this, args);
if (item.once) {
this.removeListener(type, item);
}
});
} else {
handler.callback.apply(this, args);
if (handler.once) {
this.events.delete(type);
}
}
return true;
}
EventEmitter.prototype.removeAllListeners = function (type) {
const handler = this.events.get(type);
if (!handler) return;
this.events.delete(type);
}
15. 图片懒加载
可以给图片img标签统一自定义属性data-src='default.png'
,当检测到图片出现在窗口之后再补充src属性,此时才会进行图片资源加载。
function lazyload() {
const imgs = document.getElementsByTagName('img');
const len = imgs.length;
// 视口的高度
const viewHeight = document.documentElement.clientHeight;
// 滚动条的高度
const scrollHeight =
document.documentElement.scrollTop || document.body.scrollTop;
for (let i = 0; i < len; i++) {
const offsetHeight = imgs[i].offsetTop;
if (offsetHeight < viewHeight + scrollHeight) {
const src = imgs[i].dataset.src;
imgs[i].src = src;
}
}
}
document.addEventListener('scroll', lazyload);
16. 高性能渲染数据
合理使用createDocumentFragment和requestAnimationFrame,将操作切分为一小段一小段执行。
setTimeout(() => {
// 插入十万条数据
const total = 100000;
// 一次插入的数据
const once = 20;
// 插入数据需要的次数
const loopCount = Math.ceil(total / once);
let countOfRender = 0;
const ul = document.querySelector('ul');
// 添加数据的方法
function add() {
const fragment = document.createDocumentFragment();
for (let i = 0; i < once; i++) {
const li = document.createElement('li');
li.innerText = Math.floor(Math.random() * total);
fragment.appendChild(li);
}
ul.appendChild(fragment);
countOfRender += 1;
loop();
}
function loop() {
if (countOfRender < loopCount) {
window.requestAnimationFrame(add);
}
}
loop();
}, 0)
let ul = document.getElementById('container');
// 十万条数据
let total = 100000;
// 一次加载20条
let once = 20;
// 总共有多少页数据
let page = Math.ceil(total / once);
// 索引的起始值
let index = 0;
function loop(curTotal, curIndex) {
if (curTotal <= 0) {
return false;
}
let pageCount = Math.min(curTotal, once);
window.requestAnimationFrame(function() {
let fragment = document.createDocumentFragment();
for (let i = 0; i < pageCount; i++) {
let li = document.createElement('li');
li.innerText = curIndex + i + ':' + Math.random() * total;
fragment.appendChild(li);
}
ul.appendChild(fragment);
loop(total - pageCount, curIndex + pageCount);
});
}
loop(total, index);
17. 浅比较和深比较
本段代码来自对 React Hooks 与 Immutable 数据流实战 课程的学习。
PureComponent 浅比较部分的核心源码:
function shallowEqual (objA: mixed, objB: mixed): boolean {
// 下面的 is 相当于 === 的功能,只是对 + 0 和 - 0,以及 NaN 和 NaN 的情况进行了特殊处理
// 第一关:基础数据类型直接比较出结果
if (is(objA, objB)) {
return true;
}
// 第二关:只要有一个不是对象数据类型就返回 false
if (
typeof objA !== 'object' || objA === null ||
typoef objB !== 'object' || objB === null
) {
return false;
}
// 第三关:在这里已经可以保证两个都是对象数据类型,比较两者的属性数量
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keys.length !== keysB.length) {
return false;
}
// 第四关:比较两者的属性是否相等,值是否相等
for (let i = 0; i < keys.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keys[i]], objB[keys[i]])
) {
return false;
} else {
// 如果是深比较,加入这个else条件下的代码,递归比较子节点下的对象属性
if (!deepEqual(objA[keys[i]], objB[keys[i]])) {
return false;
}
}
}
return true;
}
浅比较的情况下,属性值为引用类型就无法比较出正确结果了。深比较情况下,如果数据量很大,那么比较的性能会很差。因此,可以引入immutable来解决浅比较下引用类型比对失效的问题。immutable 数据一种利用结构共享形成的持久化数据结构,一旦有部分被修改,那么将会返回一个全新的对象,并且原来相同的节点会直接共享。