前言
在前端面试的时候,我们经常会遇到手撕代码题,但是如果我们没有用过手撕的功能,或者没有接触过此类的算法,写的时候可能会抓耳挠腮但下不了笔。这里按照专题整理了一些前端常见的手撕代码考题,希望大家找工作都大顺大利。
在看具体的题目前,我们需要看一下笔试或面试时可能会用到的输入输出格式:
acm格式输入输出
js
// 读取输入的ACM格式数据
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.on('line', (line) => {
// 处理输入的每一行
const input = line.split(' ').map(Number);
// 假设我们要计算两个数的和
const sum = input[0] + input[1];
// 输出结果
console.log(sum);
});
// 需要时可以关闭readline接口
// rl.close();
力扣模式输入输出
js
/**
* @param {Array<Function>} functions
* @return {Promise<any>}
*/
var promiseAll = function(functions) {
};
ts
type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
type Fn = (...args: JSONValue[]) => void
function cancellable(fn: Fn, args: JSONValue[], t: number): Function {
};
上下文和原型链
手撕instanceof
instanceof的用法是: 实例A instanceof 原型B, 如果B在A的原型链上,则表达式为true,否则为false。
实例的原型是指定在实例的_proto属性上的,可以用A._proto进行访问。
原型则是绑定在原型对象的prototype属性上的,可以用B.prototye进行访问。
js
const myInstanceOf=(Left,Right)=>{
if(!Left){
return false
}
while(Left){
if(Left.__proto__===Right.prototype){
return true
}else{
Left=Left.__proto__
}
}
return false
}
//验证
console.log(myInstanceOf({},Array)); //false
手撕call, bind, apply
call :fn.call(context, ...args) 支持将函数的上下文绑定到context,并以参数形式 传入函数运行所需的参数,且立即执行函数。
js
function foo(x,y){
console.log(this.a,x+y);
}
const obj={
a:1
}
Function.prototype.myCall=function(context,...args){
if(typeof this !== 'function') return new TypeError('is not a function')
const fn = Symbol('fn') //使用Symbol尽可能降低myCall对其他的影响
context[fn] = this //this指向foo
const res = context[fn](...args) //解构,调用fn
delete context[fn] //不要忘了删除obj上的工具函数fn
return res //将结果返回
}
//验证
foo.myCall(obj,1,2) //1,3
apply :fn.apply(context, args) 支持将函数的上下文绑定到context,并以参数数组 形式传入函数运行所需的参数,且立即执行函数。
js
function foo(x,y){
console.log(this.a,x+y);
}
const obj={
a:1
}
Function.prototype.myApply=function(context,args){
if(typeof this !== 'function') return new TypeError('is not a function')
const fn = Symbol('fn') //使用Symbol尽可能降低myCall对其他的影响
context[fn] = this //this指向foo
const res = context[fn](...args) //解构,调用fn
delete context[fn] //不要忘了删除obj上的工具函数fn
return res //将结果返回
}
//验证
foo.myApply(obj,1,2) //1,3
bind :bind和call,apply的区别是会返回一个新的函数
,接收零散
的参数
需要注意的是,官方bind
的操作是这样的:
- 当new了bind返回的函数时,相当于new了foo,且new的
参数
需作为实参
传给foo - foo的this.a
访问不到
obj中的a
js
function foo(x,y,z){
this.name='zt'
console.log(this.a,x+y+z);
}
const obj={
a:1
}
Function.prototype.myBind=function(context,...args){
if(typeof this !== 'function') return new TypeError('It is not a function');
context = context || window; // 上下文环境
const _this = this; // 当前的函数的上下文this
return function F(...arg) {
//判断返回出去的F有没有被new,有就要把foo给到new出来的对象
if (this instanceof F) {
return new _this(...args, ...arg);
} else {
_this.call(this, ...args, ...arg);
}
}
}
//验证
const bar=foo.myBind(obj,1,2)
console.log(new bar(3)); //undefined 6 foo { name: 'zt' }
对象
实现浅拷贝
浅拷贝:只拷贝最外面一层数据;更深层次的对象,只拷贝引用; 这个链接讲的更加清楚哟
实现深拷贝
深拷贝:只拷贝最外面一层数据;拷贝多层数据;每一层级别的数据都会拷贝;这里展示一个层层拷贝的写法。
js
//utils.js
//...
const deepCopy= (obj = {}) => {
//变量先置空
let newobj = null;
//判断是否需要继续进行递归
if (typeof (obj) == 'object' && obj !== null) {
newobj = obj instanceof Array ? [] : {};
//进行下一层递归克隆
for (var i in obj) {
newobj[i] = deepCopy(obj[i])
}
//如果不是对象直接赋值
} else newobj = obj;
console.log(newobj)
return newobj;
}
判断两个值是否相符
判断两个值是否相等,值可能是基本数据类型,也可能是对象或者数组,值相等视为===相等
js
function compare(data1, data2) {
if (typeof data1 != typeof data2) {
return false;
}
if (typeof data1 != "object" && typeof data1 == typeof data2) {
return data1 === data2;
}
if ((!data1 instanceof Array && data2 instanceof Array) || (data1 instanceof Array && !data2 instanceof Array)) {
return false;
} else if (data1 instanceof Array && data2 instanceof Array) {
if (data1.length == data2.length) {
for (let i = 0; i < data1.length; i++) {
if (!compare(data1[i], data2[i])) {
return false;
}
}
} else {
return false;
}
} else {
if (Object.keys(data1).length == Object.keys(data2).length) {
for (let key in data1) {
if (!data2[key] || !compare(data1[key], data2[key])) {
return false;
}
}
} else {
return false;
}
}
return true;
}
实现new方法
js
function myNew(Fn, ...args){
let newObj = {};
if(Fn.prototype){
newObj.__proto__ = Fn.prototype;
}
let result = Fn.apply(newObj, args);
// 如果new的结果是对象则返回这个对象,否则直接返回这个新创建的对象
if((typeof result === 'object' && result !== null) || typeof result === 'function'){
return result;
}
return newObj;
}
实现Object.create()
js
// proto 可以是object 或者function
Object.myCreate = function (proto, defineProperties){
if((typeof proto === 'object' && proto !== null) || typeof proto === 'function'){
let obj = {};
// obj.__proto__ = proto;
Object.setPrototypeOf(obj, proto);
Object.defineProperty(obj, defineProperties);
return obj;
}else {
throw new TypeError('类型错误');
}
}
实现Object.assign()
js
Object.myAssign = function (newObj, oldObj){
if((typeof proto === 'object' && proto !== null) || typeof proto === 'function'){
let obj = {};
// obj.__proto__ = proto;
Object.setPrototypeOf(obj, proto);
Object.defineProperty(obj, defineProperties);
return obj;
}else {
throw new TypeError('类型错误');
}
}
数组
数组去重
js
// 方法一
function unique(arr) {
for(let i = 0; i < arr.length; i++) {
for(let j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1);
}
}
}
return arr;
}
// 方法二
function unique(arr) {
let myset = new Set();
arr.forEach(item => {
myset.add(item);
})
return Array.from(myset);
}
// 方法三
function unique(arr) {
return arr.filter(function(item, index, array) {
return arr.indexOf(item) === index
})
}
const arr = [1,1,'true','true',true,true,15,15,false,false];
console.log(unique(arr)) // [ 1, 'true', true, 15, false ]
数组快速排序
js
function quickSort(arr) {
if (arr.length <= 1) {
return arr;
}
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
};
let arr = [1,3,4,2,6,3,5];
console.log(quickSort(arr));
实现Array.prototype.reduce
js
if (!Array.prototype.myReduce) {
Array.prototype.myReduce = function(callback, initialValue) {
const array = this;
let accumulator = initialValue !== undefined ? initialValue : array[0];
let index = initialValue !== undefined ? 0 : 1;
while (index < array.length) {
accumulator = callback(accumulator, array[index], index, array);
index++;
}
return accumulator;
};
}
// 示例使用:
const array1 = [1, 2, 3, 4];
const sum = array1.myReduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(sum); // 输出:10
实现Array.prototype.flat(数组扁平化)
js
Array.prototype.myFlat = function(arr){
let res = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
res = res.concat(flatten(arr[i]));
} else {
res.push(arr[i]);
}
}
return res;
};
let arr = ['a',['b',['c']]];
console.log(arr.myFlat()); // [ 'a', 'b', 'c' ]
实现Array.prototype.forEach
js
Array.prototype.myForEach = function (fn) {
for (let i = 0; i < this.length; i++) {
fn(this[i], i, this);
}
};
const arr = [1, 2, 3];
arr.forEach((item, index, arr) => {
console.log(item, index, arr);
});
实现Array.prototype.map
js
Array.prototype.myMap = function (fn, thisArg) {
const res = [];
for (let i = 0; i < this.length; i++) {
const mappedValue = fn.call(thisArg, this[i], i, this);
res.push(mappedValue);
}
return res;
};
const arr = [1, 2, 3];
const douleArr = arr.myMap((value,index,array) => value * 2);
console.log(douleArr);
实现Array.prototype.filter
js
const arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.myFilter = function(item,start) {
if (start < 0) { start += this.length}
for (let i = start; i < this.length; i++) {
if (this[i] === item){
return true;
}
}
return false;
}
//验证
const flag = arr.myFilter('c',3) //查找的元素,从哪个下标开始查找
console.log(flag); //false
实现Array.prototype.includes
js
const arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.myIncludes = function(item,start) {
if (start < 0) { start += this.length}
for (let i = start; i < this.length; i++) {
if (this[i] === item){
return true;
}
}
return false;
}
//验证
const flag = arr.myIncludes('c',3) //查找的元素,从哪个下标开始查找
console.log(flag); //false
实现Array.prototype.fill
js
Array.prototype.myFill = function (value,start,end) {
if(!start && start!==0){
start=0
}
end = end || this.length
for(let i = start; i < end; i++){
this[i] = value
}
return this
}
//验证
const arr=new Array(7).myFill('hh',null,3) //往数组的某个位置开始填充到哪个位置,左闭右开
console.log(arr); //[ 'hh', 'hh', 'hh', <4 empty items> ]
实现Array.prototype.join
js
Array.prototype.myJoin = function (s = ',') {
let str = '';
for(let i = 0; i < this.length; i++) {
str += this[i];
str += ',';
}
}
实现Array.prototype.find
js
Array.prototype.my_find = function (callback) {
for (let i = 0; i < this.length; i++) {
if(callback(this[i], i, this)){
return this[i]
}
}
return undefined
}
//验证
let j = arr.myFind((item, index, arr) => { return item.age > 19 }) console.log(j); //{ name: 'cc', age: 21 }
实现Array.prototype.findIndex
js
const arr = [
{ name: 'zt', age: 18 },
{ name: 'aa', age: 19 },
{ name: 'bb', age: 18 },
{ name: 'cc', age: 21 },
]
Array.prototype.myFindIndex = function (callback) {
for (let i = 0; i < this.length; i++) {
if(callback(this[i], i, this)){
return i
}
}
return -1
}
let j = arr.myFindIndex((item, index, arr) => {
return item.age > 19
})
console.log(j); //3
实现Array.prototype.some
js
const arr = [
{ name: 'zt', age: 18 },
{ name: 'aa', age: 19 },
{ name: 'bb', age: 18 },
{ name: 'cc', age: 21 },
]
Array.prototype.mySome = function (callback) {
for (let i = 0; i < this.length; i++) {
if(callback(this[i], i, this)){
return true
}
}
return false
}
//验证
const flag = arr.mySome((item, index, arr) => {
return item.age > 20
})
console.log(flag); //true
实现Array.prototype.every
js
const arr = [
{ name: 'zt', age: 18 },
{ name: 'aa', age: 19 },
{ name: 'bb', age: 18 },
{ name: 'cc', age: 21 },
]
Array.prototype.myEvery = function (callback) {
for (let i = 0; i < this.length; i++) {
if(!callback(this[i], i, this)){
return false
}
}
return true
}
//验证
const flag = arr.my_every((item, index, arr) => {
return item.age > 16
})
console.log(flag); //true
字符串
字符串下划线和驼峰相互转换
js
//方式一:操作字符串数组
function transformStr2Hump1(str) {
if(str == null) {
return "";
}
var strArr = str.split('-');
for(var i = 1; i < strArr.length; i++) {
strArr[i] = strArr[i].charAt(0).toUpperCase() + strArr[i].substring(1);
}
return strArr.join('');
}
//方式二:操作字符数组
function transformStr2Hump2(str) {
if(str == null) {
return "";
}
var strArr =str.split('');
for(var i = 0; i < strArr.length; i++) {
if(strArr[i] == "-"){
//删除-
strArr.splice(i, 1);
//将该处改为大写
if(i < strArr.length) {
strArr[i] = strArr[i].toUpperCase();
}
}
}
return strArr.join("");
}
//方式三:利用正则
function transformStr2Hump3(str) {
if(str == null) {
return "";
}
var reg = /-(\w)/g;//匹配字母或数字或下划线或汉字
return str.replace(reg, function($0, $1) {
return $1.toUpperCase();
})
}
函数
函数柯里化
js
const my_curry = (fn, ...args) =>
args.length >= fn.length
? fn(...args)
: (...args1) => curry(fn, ...args, ...args1);
function adder(x, y, z) {
return x + y + z;
}
const add = my_curry(adder);
console.log(add(1, 2, 3)); //6
console.log(add(1)(2)(3)); //6
console.log(add(1, 2)(3)); //6
console.log(add(1)(2, 3)); //6
同步异步
实现Promise类
js
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
/**
* Promise构造函数
* excutor: 内部同步执行的函数
*/
class Promise {
constructor(excutor) {
const self = this;
self.status = PENDING;
self.onFulfilled = [];// 成功的回调
self.onRejected = [];// 失败的回调
// 异步处理成功调用的函数
// PromiseA+ 2.1 状态只能由Pending转为fulfilled或rejected;fulfilled状态必须有一个value值;rejected状态必须有一个reason值。
function resolve(value) {
if (self.status === PENDING) {
self.status = FULFILLED;
self.value = value;
// PromiseA+ 2.2.6.1 相同promise的then可以被调用多次,当promise变为fulfilled状态,全部的onFulfilled回调按照原始调用then的顺序执行
self.onFulfilled.forEach(fn => fn());
}
}
function reject(reason) {
if (self.status === PENDING) {
self.status = REJECTED;
self.reason = reason;
// PromiseA+ 2.2.6.2 相同promise的then可以被调用多次,当promise变为rejected状态,全部的onRejected回调按照原始调用then的顺序执行
self.onRejected.forEach(fn => fn());
}
}
try {
excutor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
// PromiseA+ 2.2.1 onFulfilled和onRejected是可选参数
// PromiseA+ 2.2.5 onFulfilled和onRejected必须被作为函数调用
// PromiseA+ 2.2.7.3 如果onFulfilled不是函数且promise1状态是fulfilled,则promise2有相同的值且也是fulfilled状态
// PromiseA+ 2.2.7.4 如果onRejected不是函数且promise1状态是rejected,则promise2有相同的值且也是rejected状态
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
const self = this;
const promise = new Promise((resolve, reject) => {
const handle = (callback, data) => {
// PromiseA+ 2.2.4 onFulfilled或者onRejected需要在自己的执行上下文栈里被调用,所以此处用setTimeout
setTimeout(() => {
try {
// PromiseA+ 2.2.2 如果onFulfilled是函数,则在fulfilled状态之后调用,第一个参数为value
// PromiseA+ 2.2.3 如果onRejected是函数,则在rejected状态之后调用,第一个参数为reason
const x = callback(data);
// PromiseA+ 2.2.7.1 如果onFulfilled或onRejected返回一个x值,运行这[[Resolve]](promise2, x)
resolvePromise(promise, x, resolve, reject);
} catch (e) {
// PromiseA+ 2.2.7.2 onFulfilled或onRejected抛出一个异常e,promise2必须以e的理由失败
reject(e);
}
})
}
if (self.status === PENDING) {
self.onFulfilled.push(() => {
handle(onFulfilled, self.value);
});
self.onRejected.push(() => {
handle(onRejected, self.reason);
})
} else if (self.status === FULFILLED) {
setTimeout(() => {
handle(onFulfilled, self.value);
})
} else if (self.status === REJECTED) {
setTimeout(() => {
handle(onRejected, self.reason);
})
}
})
return promise;
}
}
function resolvePromise(promise, x, resolve, reject) {
// PromiseA+ 2.3.1 如果promise和x引用同一对象,会以TypeError错误reject promise
if (promise === x) {
reject(new TypeError('Chaining Cycle'));
}
if (x && typeof x === 'object' || typeof x === 'function') {
// PromiseA+ 2.3.3.3.3 如果resolvePromise和rejectPromise都被调用,或者对同一个参数进行多次调用,那么第一次调用优先,以后的调用都会被忽略。
let used;
try {
// PromiseA+ 2.3.3.1 let then be x.then
// PromiseA+ 2.3.2 调用then方法已经包含了该条(该条是x是promise的处理)。
let then = x.then;
if (typeof then === 'function') {
// PromiseA+ 2.3.3.3如果then是一个函数,用x作为this调用它。第一个参数是resolvePromise,第二个参数是rejectPromise
// PromiseA+ 2.3.3.3.1 如果resolvePromise用一个值y调用,运行[[Resolve]](promise, y)
// PromiseA+ 2.3.3.3.2 如果rejectPromise用一个原因r调用,用r拒绝promise。
then.call(x, (y) => {
if (used) return;
used = true;
resolvePromise(promise, y, resolve, reject)
}, (r) => {
if (used) return;
used = true;
reject(r);
})
} else {
// PromiseA+ 如果then不是一个函数,变为fulfilled状态并传值为x
if (used) return;
used = true;
resolve(x);
}
} catch (e) {
// PromiseA+ 2.3.3.2 如果检索属性x.then抛出异常e,则以e为原因拒绝promise
// PromiseA+ 2.3.3.4 如果调用then抛出异常,但是resolvePromise或rejectPromise已经执行,则忽略它
if (used) return;
used = true;
reject(e);
}
} else {
// PromiseA+ 2.3.4 如果x不是一个对象或函数,状态变为fulfilled并传值x
resolve(x);
}
}
实现Promise.resolve
js
class Promise {
// ...
// 将现有对象转为 Promise 对象
static resolve(value) {
// 如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
if (value instanceof Promise) return value;
// 参数是一个thenable对象(具有then方法的对象),Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
if (typeof value === 'object' || typeof value === 'function') {
try {
let then = value.then;
if (typeof then === 'function') {
return new Promise(then.bind(value));
}
} catch (e) {
return new Promise((resolve, reject) => {
reject(e);
})
}
}
// 参数不是具有then方法的对象,或根本就不是对象,Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
return new Promise((resolve, reject) => {
resolve(value);
})
}
}
实现Promise.reject
js
class Promise {
// ...
// 返回一个新的 Promise 实例,该实例的状态为rejected。
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason);
})
}
}
实现Promise.all
js
class Promise {
// ...
// 用于将多个 Promise 实例,包装成一个新的 Promise 实例。只有所有状态都变为fulfilled,p的状态才会是fulfilled
static all(promises) {
const values = [];
let resolvedCount = 0;
return new Promise((resolve, reject) => {
promises.forEach((p, index) => {
Promise.resolve(p).then(value => {
resolvedCount++;
values[index] = value;
if (resolvedCount === promises.length) {
resolve(values);
}
}, reason => {
reject(reason);
})
})
})
}
}
实现Promise.race
js
class Promise {
// ...
// 只要有一个实例率先改变状态,状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给回调函数。
static race(promises) {
return new Promise((resolve, reject) => {
promises.forEach((p, index) => {
Promise.resolve(p).then(value => {
resolve(value);
}, reason => {
reject(reason);
})
})
})
}
}
实现Promise.catch
js
class Promise {
// ...
// 是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
catch(onRejected) {
return this.then(undefined, onRejected);
}
}
实现Promise.any
如果有promise成功则返回,都没有成功返回error
js
Promise.any = async function(promises) {
if (promises.length === 0) {
throw new AggregateError([], 'All promises were rejected');
}
for (const promise of promises) {
try {
return await promise;
} catch {
// 忽略错误并继续等待下一个promise
}
}
throw new AggregateError([], 'All promises were rejected');
};
实现Promise.finally
js
class Promise {
// ...
// 用于指定不管 Promise 对象最后状态如何,都会执行的操作。
finally(callback) {
return this.then(
value => Promise.resolve(callback()).then(() => value),
reason => Promise.resolve(callback()).then(() => { throw reason })
)
}
}
async await原理
js
function run(genF) {
// 返回值是Promise
return new Promise((resolve, reject) => {
const gen = genF();
function step(nextF) {
let next;
try {
// 执行该函数,获取一个有着value和done两个属性的对象
next = nextF();
} catch (e) {
// 出现异常则将该Promise变为rejected状态
reject(e);
}
// 判断是否到达末尾,Generator函数到达末尾则将该Promise变为fulfilled状态
if (next.done) {
return resolve(next.value);
}
// 没到达末尾,则利用Promise封装该value,直到执行完毕,反复调用step函数,实现自动执行
Promise.resolve(next.value).then((v) => {
step(() => gen.next(v))
}, (e) => {
step(() => gen.throw(e))
})
}
step(() => gen.next(undefined));
})
}
用async await实现一个中间件,计算函数执行时间
js
function createTimingMiddleware() {
return async (ctx, next) => {
const start = Date.now();
console.log('Function execution started at:', start);
// 调用下一个中间件或操作
await next();
const end = Date.now();
console.log('Function execution completed at:', end);
console.log(`Function execution took: ${end - start}ms`);
};
}
// 使用示例
const middleware = createTimingMiddleware();
// 假设你有一个需要计算执行时间的函数
const myFunction = async () => {
// 一些异步操作
await new Promise(resolve => setTimeout(resolve, 1000));
};
// 应用中间件
const timedFunction = middleware(myFunction);
// 执行被计算执行时间的函数
(async () => {
await timedFunction();
})();
实现sleep函数
js
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('sleep...')
resolve()
}, ms);
})
}
async function test(){
console.log('1');
await sleep(400)
console.log('2')
}
test();
Node内置对象
实现Event类
js
class Event {
constructor(){}
handlers = {};
addEventListener(type, handler) {
if (!(type in this.handlers)) {
this.handlers[type] = [];
}
this.handlers[type].push(handler);
}
dispatchEvent(type, ...args) {
if (!(type in this.handlers)) {
return new Error('not registered!!!')
}
this.handlers[type].forEach(handler => {
handler(...args);
});
}
removeEventListener(type, handler) {
if (!(type in this.handlers)) {
return new Error('invalid event!!!')
}
if (!handler) {
delete this.handlers[type]
} else {
const idx = this.handlers[type].findIndex(ele => ele === handler);
if (idx === -1) {
return new Error('invalid event!!!')
}
this.handlers[type].splice(idx, 1);
if (this.handlers[type].length === 0) {
delete this.handlers[type];
}
}
}
}
var event = new Event();
function load(){
console.log('load: ',...arguments);
}
function load2(){
console.log('load2: ',...arguments);
}
event.addEventListener('load', load)
event.addEventListener('load', load2)
event.dispatchEvent('load', 'hello, world');
event.removeEventListener('load', load);
event.dispatchEvent('load', 'hello, world');
请求响应
ajax请求过程
优化性能
实现节流函数
函数节流 方法是一个函数,它在
t
毫秒内只能执行一次,如果在这个时间窗口内再次调用它,它将不会再次执行。例如,假设
t = 50ms
,函数分别在30ms
、60ms
和100ms
时调用。第一个函数会在80ms
执行,第二个函数将不被执行,第三个函数将在150ms
执行。如果改为t = 75ms
,则第一个调用将在105ms
执行,第二、三个调用将被取消。
js
/**
* @param {Function} fn
* @param {number} t milliseconds
* @return {Function}
*/
var debounce = function(fn, t) {
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(()=>{
fn.apply(this, args)
}, t);
}
}
};
ts
type F = (...args: number[]) => void
function debounce(fn: F, t: number): F {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(()=>{
fn.apply(this, args);
},t)
}
};
实现防抖函数
函数防抖 方法是一个函数,它的执行被延迟了
t
毫秒,如果在这个时间窗口内再次调用它,它的执行将被取消。你编写的防抖函数也应该接收传递的参数。例如,假设
t = 50ms
,函数分别在30ms
、60ms
和100ms
时调用。前两个函数调用将被取消,第三个函数调用将在150ms
执行。如果改为t = 35ms
,则第一个调用将被取消,第二个调用将在95ms
执行,第三个调用将在135ms
执行。
js
/**
* @param {Function} fn
* @param {number} t milliseconds
* @return {Function}
*/
var debounce = function(fn, t) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(()=>{
fn.apply(this, args)
}, t);
}
};
ts
type F = (...args: number[]) => void
function debounce(fn: F, t: number): F {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(()=>{
fn.apply(this, args);
},t)
}
};
实现一个限流器
js
// https://blog.csdn.net/zz_jesse/article/details/107293743?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-5-107293743.nonecase&utm_term=js%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E5%B8%A6%E5%B9%B6%E5%8F%91%E9%99%90%E5%88%B6%E7%9A%84%E5%BC%82%E6%AD%A5%E8%B0%83%E5%BA%A6%E5%99%A8
class Scheduler {
// TODO
list = [];
constructor(){}
add(promiseCreator) {
this.list.push(promiseCreator)
}
max = 2;
startIndex = 0;
enter(){
for(var i = 0; i < this.max; i++){
this.run();
}
}
run(){
if (this.list.length == 0 || this.startIndex >= this.max) return;
this.startIndex++;
this.list.shift()().then(() => {
this.startIndex--;
this.enter();
})
}
}
const scheduler = new Scheduler();
const timeout = time => {
return new Promise(resolve => setTimeout(resolve, time))
}
const addTask = (time, order) => {
scheduler.add(() => timeout(time).then(() => console.log(order)))
}
addTask(1000, 1)
addTask(500, 2)
addTask(300, 3)
addTask(400, 4)
scheduler.enter();
// 2 3 1 4
前端打包
实现类似rollup的打包功能
实现tree-shaking
python
import ast
def is_node_side_effect_free(node):
# 判断节点是否为副作用免的表达式
return isinstance(node, ast.Expr) and isinstance(node.value, ast.Constant)
def perform_tree_shaking(tree):
# 遍历AST,移除副作用免的节点
for node in tree.body:
if is_node_side_effect_free(node):
tree.body.remove(node)
# 示例代码
code = """
console.log('Hello, World!');
var x = 1 + 2;
console.log(x);
"""
# 使用ast模块解析代码
tree = ast.parse(code)
# 执行tree-shaking
perform_tree_shaking(tree)
# 将修改后的AST编译回代码
compiled_code = astunparse.unparse(tree)
print(compiled_code)
DOM
虚拟DOM的渲染
详情请参考:# 手写简易前端框架:vdom 渲染和 jsx 编译
js
const render = (vdom, parent = null) => {
const mount = parent ? (el => parent.appendChild(el)) : (el => el);
if (isTextVdom(vdom)) {
return mount(document.createTextNode(vdom));
} else if (isElementVdom(vdom)) {
const dom = mount(document.createElement(vdom.type));
for (const child of vdom.children) {
render(child, dom);
}
for (const prop in vdom.props) {
setAttribute(dom, prop, vdom.props[prop]);
}
return dom;
}
};
模版渲染
Diff算法
Diff算法是一种用来比较两个数据集合差异的算法。在JavaScript中,我们可以使用diff算法来比较两个数组或者两棵树的差异,并记录下这些差异,以便我们可以高效地更新DOM。 以下是一个简单的实现:
js
function diff(oldArr, newArr) {
let result = [];
for (let i = 0; i < newArr.length; i++) {
if (!oldArr.includes(newArr[i])) {
result.push({ type: 'add', item: newArr[i] });
}
}
for (let i = 0; i < oldArr.length; i++) {
if (!newArr.includes(oldArr[i])) {
result.push({ type: 'remove', item: oldArr[i] });
}
}
return result;
}
// 使用示例
let oldList = [1, 2, 3, 4, 5];
let newList = [1, 2, 3, 6, 7];
let changes = diff(oldList, newList);
console.log(changes); // [{ type: 'add', item: 6 }, { type: 'add', item: 7 }]
输出DOM结构
js
// 输出DOM结构
var str = '';
var el = document.documentElement;
var empty;
var level = 0;
function output(el, level) {
if (el) {
if (level > 0) {
empty = new Array(level).fill(' ');
str += empty.join('');
}
str += el.tagName;
str += '\n'; // 换行
}
if (el.children) {
for(let i = 0; i < el.children.length; i++) {
output(el.children[i], level+1);
}
}
}
output(el, level);
console.log(str)
实现红绿灯效果
js
const timeout = (time) => {
return new Promise(resolve => setTimeout(resolve, time))
}
var el = document.querySelector('body')
function change(){
timeout(2000).then(() => {
el.style.backgroundColor = 'red';
return timeout(3000)
}).then(() => {
el.style.backgroundColor = 'yellow';
return timeout(1000)
}).then(() => {
el.style.backgroundColor = 'green';
change()
})
}
change();
其他
url解析
js
function parseURL(url) {
var result = {};
var match = url.match(/^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/);
if (match) {
result.protocol = match[1];
result.slashes = match[2];
result.host = match[3];
result.port = match[4];
result.path = match[5];
result.query = match[6];
result.hash = match[7];
}
return result;
}
// 示例使用
var myURL = "https://www.example.com:80/pathname/?search=test#hash";
console.log(parseURL(myURL));
懒加载
1)首先,不要将图片地址放到src属性中,而是放到其它属性(data-original)中。
2)页面加载完成后,根据scrollTop判断图片是否在用户的视野内,如果在,则将data-original属性中的值取出存放到src属性中。
3)在滚动事件中重复判断图片是否进入视野,如果进入,则将data-original属性中的值取出存放到src属性中。
elementNode.getAttribute(name):方法通过名称获取属性的值。
elementNode.setAttribute(name, value):方法创建或改变某个新属性。
elementNode.removeAttribute(name):方法通过名称删除属性的值。
js
//懒加载代码实现
var viewHeight = document.documentElement.clientHeight;//可视化区域的高度
function lazyload () {
//获取所有要进行懒加载的图片
let eles = document.querySelectorAll('img[data-original][lazyload]');//获取属性名中有data-original的
Array.prototype.forEach.call(eles, function(item, index) {
let rect;
if(item.dataset.original === '') {
return;
}
rect = item.getBoundingClientRect();
//图片一进入可视区,动态加载
if(rect.bottom >= 0 && rect.top < viewHeight) {
!function () {
let img = new Image();
img.src = item.dataset.original;
img.onload = function () {
item.src = img.src;
}
item.removeAttribute('data-original');
item.removeAttribute('lazyload');
}();
}
})
}
lazyload();
document.addEventListener('scroll', lazyload);
实现事件发射器EventEmitter(leetcode有)
设计一个
EventEmitter
类。这个接口与 Node.js 或 DOM 的 Event Target 接口相似,但有一些差异。EventEmitter
应该允许订阅事件和触发事件。你的
EventEmitter
类应该有以下两个方法:
- subscribe - 这个方法接收两个参数:一个作为字符串的事件名和一个回调函数。当事件被触发时,这个回调函数将被调用。 一个事件应该能够有多个监听器。当触发带有多个回调函数的事件时,应按照订阅的顺序依次调用每个回调函数。应返回一个结果数组。你可以假设传递给
subscribe
的回调函数都不是引用相同的。subscribe
方法还应返回一个对象,其中包含一个unsubscribe
方法,使用户可以取消订阅。当调用unsubscribe
方法时,回调函数应该从订阅列表中删除,并返回 undefined。- emit - 这个方法接收两个参数:一个作为字符串的事件名和一个可选的参数数组,这些参数将传递给回调函数。如果没有订阅给定事件的回调函数,则返回一个空数组。否则,按照它们被订阅的顺序返回所有回调函数调用的结果数组。
js
class EventEmitter {
constructor() {
this.events = {};
}
/**
* @param {string} eventName
* @param {Function} callback
* @return {Object}
*/
subscribe(eventName, callback) {
// this.events对象中的键值对,键为事件名称,值为其回调函数数组
this.events[eventName] = this.events[eventName] ?? [];
this.events[eventName].push(callback);
return {
// 取消某个事件的某个callback
unsubscribe: () => {
this.events[eventName] = this.events[eventName].filter(f => f !== callback);
// 为避免内存泄漏,添加清理条件
if (this.events[eventName].length === 0) { delete this.events[event] }
}
};
}
/**
* @param {string} eventName
* @param {Array} args
* @return {Array}
*/
emit(eventName, args = []) {
// 如果事件不被监听,则返回[]
if (!(eventName in this.events)) return [];
// 否则返回事件回掉函数的所有处理结果
return this.events[eventName].map(f => f(...args));
}
}
/**
* const emitter = new EventEmitter();
*
* // Subscribe to the onClick event with onClickCallback
* function onClickCallback() { return 99 }
* const sub = emitter.subscribe('onClick', onClickCallback);
*
* emitter.emit('onClick'); // [99]
* sub.unsubscribe(); // undefined
* emitter.emit('onClick'); // []
*/
发布订阅模式
ts
// 发布订阅(TypeScript版)
interface Publish {
registerObserver(eventType : string, subscribe : Subscribe) : void;
remove(eventType : string, subscribe ?: Subscribe) : void;
notifyObservers(eventType : string) : void;
}
interface SubscribesObject{
[key : string] : Array<Subscribe>
}
class ConcretePublish implements Publish {
private subscribes : SubscribesObject;
constructor() {
this.subscribes = {};
}
registerObserver(eventType : string, subscribe : Subscribe) : void {
if (!this.subscribes[eventType]) {
this.subscribes[eventType] = [];
}
this.subscribes[eventType].push(subscribe);
}
remove(eventType : string, subscribe ?: Subscribe) : void {
const subscribeArray = this.subscribes[eventType];
if (subscribeArray) {
if (!subscribe) {
delete this.subscribes[eventType];
} else {
for (let i = 0; i < subscribeArray.length; i++) {
if (subscribe === subscribeArray[i]) {
subscribeArray.splice(i, 1);
}
}
}
}
}
notifyObservers(eventType : string, ...args : any[]) : void {
const subscribes = this.subscribes[eventType];
if (subscribes) {
subscribes.forEach(subscribe => subscribe.update(...args))
}
}
}
interface Subscribe {
update(...value : any[]) : void;
}
class ConcreteSubscribe1 implements Subscribe {
public update(...value : any[]) : void {
console.log('已经执行更新操作1,值为', ...value);
}
}
class ConcreteSubscribe2 implements Subscribe {
public update(...value : any[]) : void {
console.log('已经执行更新操作2,值为', ...value);
}
}
function main() {
const publish = new ConcretePublish();
const subscribe1 = new ConcreteSubscribe1();
const subscribe2 = new ConcreteSubscribe2();
publish.registerObserver('1', subscribe1);
publish.registerObserver('2', subscribe2);
publish.notifyObservers('2', '22222');
}
main();
实现eventBus
js
class EventBus{
constructor() {
this.listeners = {};
}
on(event, listener){
if(!this.listeners[event]){
this.listeners[event] = [];
}
this.listeners[event].push(listener);
}
off(event, listener){
if(this.listeners[event]){
const index = this.listeners[event].indexOf(listener);
if(index > -1){
this.listeners[event].splice(index, 1);
}
}
}
emit(event, ...args){
if(this.listeners[event]){
this.listeners[event].forEach(listener => listener(...args));
}
}
}
const bus = new EventBus();
bus.on('login', function(name){
console.log('12313', name);
})
bus.on('login', function(name){
console.log('1231223', name);
})
bus.emit('login', 1222313);
FileReader使用
js
function uploadMulFile(uploadFile) {
return new Promise((resolve, reject) => {
let fileLength = 0;
let reader = new FileReader();
reader.readAsText(uploadFile[fileLength]);
reader.onabort = function(e) {
console.log("文件读取异常");
}
reader.onerror = function(e) {
console.log("文件读取错误");
}
reader.onload = function(e){
if(e.target.result) {
fileLength++;
if(fileLength < uploadFile.length) {
reader.readAsText(uploadFile[fileLength]);
}else{
resolve({
carArr,
crossArr,
roadArr
})
}
}
}
})
}