Object.create
function create(obj) {
function F() {}
F.prototype = obj;
return new F();
}
实现 new
function myNew() {
const obj = {}; // 1
const constructor = Array.prototype.shift.call(arguments);
obj.__proto__ = constructor.prototype;
const result = constructor.apply(obj, arguments);
return typeof result === 'object' ? result : obj;
}
function myNew() {
const constructor = Array.prototype.shift.call(arguments);
const obj = Object.create(constructor.prototype);
const result = constructor.apply(obj, arguments);
return typeof result === 'object' ? result : obj;
}
实现柯里化
function myCurry(fn, args) {
var len = fn.length;
var args = args || [];
return function () {
var _args_ = args.concat(Array.prototype.slice.call(arguments));
if (_args.length < len) {
return myCurry.call(this, fn, _args);
}
return fn.apply(this, fn, _args);
};
}
实现 add(1)(2)(3)
function add(...args){
let func = function (..._args) {
return add(...args, ..._args);
}
func.toString() = function () {
return args.reduce((a,b)=>a+b)
}
return func;
}
防抖和节流
函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
function debounce(fn, wait) {
let timer = null;
return function () {
const context = this;
const params = [...arguments];
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, params);
timer = null;
})
}
}
function debounce(fn, wait) {
let timer = null;
return function (...args) {
const context = this;
if (timer) {
clearTimeout(timer);
timer = null;
}
// 设置定时器,使事件间隔指定事件后执行
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
// 定时器版本
function throttle(fun, wait) {
let timeout = null
return function (...args) {
let context = this;
if (!timeout) {
timeout = setTimeout(() => {
fun.apply(context, args)
timeout = null;
}, wait)
}
};
}
// 时间戳版
function throttle(fn, wait) {
const oldTime = Date.now();
return function (...args) {
const context = this,
const newTime = Data.now();
if (newTime - oldTime >= delay) {
oldTime = Data.now();
return fn.apply(context, args);
}
};
}
为何在此处需要声明一个中间变量 `context` 来确定 `this` 的指向呢?
一般情况下是没有问题的,但在一些复杂的情况下,比如将防抖/节流函数作为回调函数传递,this 指向可能会发生变化
const myObject = {
value: 0,
increment() {
this.value++;
console.log(this.value);
},
};
myObject.debouncedIncrement = debounce(myObject.increment, 300);
setTimeout(myObject.debouncedIncrement, 1000); // 这里的 this 不再指向 myObject
apply、call、bind
Function.prototype.myCall = function (context) {
if (typeof this !== 'function') {
console.error('type error');
}
const args = [...arguments].slice(1);
context = context || window;
context.fn = this;
const result = (result = context.fn(...args));
delete context.fn;
return result;
};
// apply 函数实现
Function.prototype.myApply = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error');
}
context = context || window;
context.fn = this;
let result = null;
if (arguments[1]) {
result = context.fn();
} else {
result = context.fn(arguments[1]);
}
delete context.fn;
return result;
};
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error');
}
const args = [...arguments].slice(1);
const self = this;
return function F() {
return self.apply(
this instanceof F ? this : context,
args.concat(...arguments)
);
};
};
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error');
}
// this 指向调用者
var self = this;
// 实现第2点,因为第1个参数是指定的this,所以只截取第1个之后的参数
var args = Array.prototype.slice.call(arguments, 1);
// 创建一个空对象
var fNOP = function () {};
// 实现第3点,返回一个函数
var fBound = function () {
// 实现第4点,获取 bind 返回函数的参数
var bindArgs = Array.prototype.slice.call(arguments);
// 然后同传入参数合并成一个参数数组,并作为 self.apply() 的第二个参数
return self.apply(
this instanceof fNOP ? this : context,
args.concat(bindArgs)
);
// 注释1
};
// 注释2
// 空对象的原型指向绑定函数的原型
fNOP.prototype = this.prototype;
// 空对象的实例赋值给 fBound.prototype
fBound.prototype = new fNOP();
return fBound;
};
赋值 context.fn,是为了定义 this 绑定(函数作为对象的方法调用时,this 指向调用该方法的对象)。
异步任务调度
function asyncRetry(fn, times, timeout = 0) {
// fn必需
// times必需,且为大于0的正整数
times = parseInt(times);
if (times <= 0 || isNaN(times)) {
throw new TypeError('times必需是大于0的正整数');
}
// Number.isInteger()
return Promise.race([
// 错误后重复
new Promise(async (resolve, reject) => {
while (times--) {
try {
const res = await fn();
resolve(res);
break;
} catch (error) {
if (times <= 0) {
reject('retry error');
}
}
}
}),
// 超时任务
new Promise((resolve, reject) => {
setTimeout(() => {
reject('retry error');
}, timeout);
}),
]);
}
正则
下划线转驼峰
function underscoreToCamelCase(str) {
return str.replace(/_([a-z])/g, function (match, group) {
return group.toUpperCase();
});
}
驼峰转下划线
function camelCaseToUnderscore(str) {
return str.replace(/([A-Z])/g, function (match, group) {
return '_' + group.toLowerCase();
});
}
模板渲染
let str = '我是,年龄,性别';
let person = {
name: '张三',
age: 18,
sex: '男',
};
function compile(template, data) {
return template.replace(//g, (match, key) => {
return key.trim() in data ? data[key.trim()] : match;
});
}
let result = compile(str, person);
console.log(result); // 输出: 我是张三,年龄18,性别男
实现 flat 方法
function flat(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
const cur = arr[i];
if (Array.isArray(cur)) {
result = result.concat(flat(cur));
} else {
result.push(cur);
}
}
return result;
}
指定扁平化的层次
function customFlat(arr, depth = 1) {
let result = [];
function flatten(arr, currDepth) {
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i]) && currDepth < depth) {
flatten(arr[i], currDepth + 1); // 递归调用 flatten 方法,并增加当前深度
} else {
result.push(arr[i]);
}
}
}
flatten(arr, 0);
return result;
}
洋葱模型
async function mid1(ctx, next) {
console.log(1);
await next();
console.log(4);
}
async function mid2(ctx, next) {
console.log(2);
await next();
console.log(5);
}
async function mid3(ctx, next) {
console.log(3);
await next();
console.log(6);
}
function compose(middleware) {
return function (ctx) {
async function dispatch(i) {
if (i === middleware.length) {
// 所有中间件函数执行完毕
return;
}
try {
const fn = middleware[i];
await fn(ctx, () => dispatch(i + 1));
} catch (err) {
console.error(err);
}
}
return dispatch(0);
};
}
const middleware = [mid1, mid2, mid3];
compose(middleware)({});
API 中间件模式
// 异步API执行器
const asyncApiExecutor = async (...args) => {
console.log('Async API called with args:', ...args);
return new Promise(resolve => {
setTimeout(() => {
console.log('Async API execution completed');
resolve('API Result');
}, 1000);
});
};
// 中间件机制
function applyMiddlewares(fn, middlewares) {
return function (...args) {
let index = -1;
function dispatch(i) {
if (i <= index) {
throw new Error('next() called multiple times');
}
index = i;
if (i === middlewares.length) {
return fn(...args);
}
const middleware = middlewares[i];
if (!middleware) return;
return middleware(dispatch.bind(null, i + 1))(...args);
}
return dispatch(0);
};
}
// 中间件定义
const loggingMiddleware =
next =>
async (...args) => {
console.log('Logging Middleware before');
const result = await next(...args);
console.log('Logging Middleware after');
return result;
};
const timingMiddleware =
next =>
async (...args) => {
console.log('Timing Middleware before');
const start = Date.now();
const result = await next(...args);
const end = Date.now();
console.log('Timing Middleware after');
console.log(`Execution time: ${end - start}ms`);
return result;
};
const errorHandlingMiddleware =
next =>
async (...args) => {
try {
console.log('Error Handling Middleware before');
const result = await next(...args);
console.log('Error Handling Middleware after');
return result;
} catch (error) {
console.error('Error caught in Middleware:', error);
}
};
// 使用中间件包装异步API
const enhancedApiExecutor = applyMiddlewares(asyncApiExecutor, [
errorHandlingMiddleware,
loggingMiddleware,
timingMiddleware,
]);
// 调用增强后的API
enhancedApiExecutor('arg1', 'arg2')
.then(result => {
console.log('Final result:', result);
})
.catch(error => {
console.error('Unhandled error:', error);
});
深拷贝
考虑内置对象 Date、RegExp 和循环引用的问题
const isObject = target =>
(typeof target === 'object' || typeof target === 'function') &&
target !== null;
function deepClone(target, map = new WeakMap()) {
if (map.get(target)) {
return target;
}
// 获取当前值的构造函数:获取它的类型
let constructor = target.constructor;
// 检测当前对象target是否与正则、日期格式对象匹配
if (/^(RegExp|Date)$/i.test(constructor.name)) {
// 创建一个新的特殊对象(正则类/日期类)的实例
return new constructor(target);
}
if (isObject(target)) {
map.set(target, true); // 为循环引用的对象做标记
const cloneTarget = Array.isArray(target) ? [] : {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop], map);
}
}
return cloneTarget;
} else {
return target;
}
}
并发异步调度器
class Scheduler {
constructor(maxConcurrency) {
this.maxConcurrency = maxConcurrency;
this.currentConcurrency = 0;
this.taskQueue = [];
}
addTask(delay, value) {
const task = () =>
new Promise(resolve => {
setTimeout(() => {
console.log(value);
resolve();
}, delay);
});
if (this.currentConcurrency < this.maxConcurrency) {
this.runTask(task);
} else {
this.taskQueue.push(task);
}
}
runTask(task) {
this.currentConcurrency++;
task().then(() => {
this.currentConcurrency--;
if (this.taskQueue.length > 0) {
const nextTask = this.taskQueue.shift();
this.runTask(nextTask);
}
});
}
}
const scheduler = new Scheduler(2);
scheduler.addTask(1000, '1');
scheduler.addTask(500, '2');
scheduler.addTask(300, '3');
scheduler.addTask(400, '4');
实现一个简易的 Vue
class Vue {
constructor(options) {
this.$options = options;
this.$data = options.data;
this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
this._proxyData(this.$data)
new Observer(this.$data)
new Compiler(this)
},
_proxyData(data) {
Object.keys(data).forEach((key) => {
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get() {
return data[key]
},
set(newVal) {
if (newVal !== data[key]) {
data[key] = newVal
}
}
})
})
}
}
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
class Observer {
constructor(data) {
this.observe(data);
}
observe(data) {
if (!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach(key => this.defineReactive(data, key, data[key]));
}
defineReactive(obj, key, val) {
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
if (Dep.target) {
dep.addSub(Dep.target);
}
return val;
},
set(newVal) {
if (val !== newVal) {
val = newVal;
this.observe(newVal);
dep.notify();
}
}
});
}
}
class Watcher {
constructor(vm, key, callback) {
this.vm = vm;
this.key = key;
this.callback = callback;
Dep.target = this;
this.oldValue = this.vm[key];
Dep.target = null;
}
update() {
const value = this.vm[this.key];
if (value !== this.oldValue) {
this.callback(value);
}
}
}
class Compiler {
constructor(vm) {
this.el = vm.$el;
this.vm = vm;
this.compile(this.el);
}
compile(el) {
let childNodes = el.childNodes;
Array.from(childNodes).forEach((node) => {
// 处理文本节点
if (this.isTextNode(node)) {
this.compileText(node);
} else if (this.isElementNode(node)) {
// 处理元素节点
this.compileElement(node);
}
// 判断node节点,是否有子节点,如果有子节点,要递归调用compile
if (node.childNodes && node.childNodes.length) {
this.compile(node);
}
});
}
// 编译元素节点,处理指令
compileElement(node) {
// console.log(node.attributes)
// 遍历所有的属性节点
Array.from(node.attributes).forEach((attr) => {
// 判断是否是指令
let attrName = attr.name;
if (this.isDirective(attrName)) {
// v-text --> text
attrName = attrName.substr(2);
let key = attr.value;
this.update(node, key, attrName);
}
});
}
update(node, key, attrName) {
let updateFn = this[attrName + "Updater"];
updateFn && updateFn.call(this, node, this.vm[key], key);
}
// 处理 v-text 指令
textUpdater(node, value, key) {
node.textContent = value;
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue;
});
}
// v-model
modelUpdater(node, value, key) {
node.value = value;
new Watcher(this.vm, key, (newValue) => {
node.value = newValue;
});
// 双向绑定
node.addEventListener("input", () => {
this.vm[key] = node.value;
});
}
// 编译文本节点,处理差值表达式
compileText(node) {
// console.dir(node)
//
let reg = /\{\{(.+?)\}\}/;
let value = node.textContent;
if (reg.test(value)) {
let key = RegExp.$1.trim();
node.textContent = value.replace(reg, this.vm[key]);
// 创建watcher对象,当数据改变更新视图
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue;
});
}
}
// 判断元素属性是否是指令
isDirective(attrName) {
return attrName.startsWith("v-");
}
// 判断节点是否是文本节点
isTextNode(node) {
return node.nodeType === 3;
}
isElementNode(node) {
return node.nodeType === 1;
}
}
实现 lodash 的 get
方法
function get(object, path, defaultValue = 'undefined') {
const paths = Array.isArray(path)
? path
: path.replace(/\[/g, '.').replace(/\]/, '').split('.');
return (
paths.reduce((obj, key) => {
console.log(obj, key);
return (obj || {})[key];
}, object) || defaultValue
);
}
限制异步操作的并发个数,并尽可能快的完成全部
有 8 个图片资源的 url,已经存储在数组 urls 中。
urls 类似于[‘https://image1.png’, ‘https://image2.png’, ….]
而且已经有一个函数 function loadImg,输入一个 url 链接,返回一个 Promise,该 Promise 在图片下载完成的时候 resolve,下载失败则 reject。
但有一个要求,任何时刻同时下载的链接数量不可以超过 3 个。
请写一段代码实现这个需求,要求尽可能快速地将所有图片下载完成。
var urls = [
'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png',
'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png',
'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png',
'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png',
'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png',
'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png',
'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png',
'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png',
];
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function () {
console.log('一张图片加载完成');
resolve(img);
};
img.onerror = function () {
reject(new Error('Could not load image at' + url));
};
img.src = url;
});
}
function limitLoad(urls, handler, limit) {
let sequence = [].concat(urls); // 复制urls
// 这一步是为了初始化 promises 这个"容器"
let promises = sequence.splice(0, limit).map((url, index) => {
return handler(url).then(() => {
// 返回下标是为了知道数组中是哪一项最先完成
return index;
});
});
// 注意这里要将整个变量过程返回,这样得到的就是一个Promise,可以在外面链式调用
return sequence
.reduce((pCollect, url) => {
return pCollect
.then(() => {
return Promise.race(promises); // 返回已经完成的下标
})
.then(fastestIndex => {
// 获取到已经完成的下标
// 将"容器"内已经完成的那一项替换
promises[fastestIndex] = handler(url).then(() => {
return fastestIndex; // 要继续将这个下标返回,以便下一次变量
});
})
.catch(err => {
console.error(err);
});
}, Promise.resolve()) // 初始化传入
.then(() => {
// 最后三个用.all来调用
return Promise.all(promises);
});
}
limitLoad(urls, loadImg, 3)
.then(res => {
console.log('图片全部加载完毕');
console.log(res);
})
.catch(err => {
console.error(err);
});
实现一个串行队列
实现一个串行请求队列 serial 函数,接收包含异步请求的数组,按顺序依次执行。
function serial(arr) {
return arr.reduce((pre, cur) => pre.then(() => cur()), Promise.resolve());
}
// forEach 版本
function serial(arr) {
var sequence = Promise.resolve();
arr.forEach(function (item) {
sequence = sequence.then(item);
});
return sequence;
}
实现 mergePromise 函数
实现 mergePromise 函数,把传进去的数组按顺序先后执行,并且把返回的数据先后放到数组 data 中。
const time = timer => {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, timer);
});
};
const ajax1 = () =>
time(2000).then(() => {
console.log(1);
return 1;
});
const ajax2 = () =>
time(1000).then(() => {
console.log(2);
return 2;
});
const ajax3 = () =>
time(1000).then(() => {
console.log(3);
return 3;
});
function mergePromise() {
// 在这里写代码
}
mergePromise([ajax1, ajax2, ajax3]).then(data => {
console.log('done');
console.log(data); // data 为 [1, 2, 3]
});
// 要求分别输出
// 1
// 2
// 3
// done
// [1, 2, 3]
function mergePromise(arr) {
return arr.reduce((pre, cur) => {
return pre.then(data => {
return cur().then(res => [...data, res]);
});
}, Promise.resolve([]));
}
将数字转换成汉语
function trans(number) {
const integral = number + '';
const digits = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
const radices = ['', '十', '百', '千'];
const bigRadices = ['', '万', '亿'];
let result = '';
let zeroCount = 0;
for (let i = 0; i < integral.length; i++) {
const digit = +integral[i];
// 尾数个数
const p = integral.length - 1 - i;
// 有几个 4 位
const quotient = Math.floor(p / 4);
// 单位
const module = p % 4;
if (digit === 0) {
zeroCount++;
} else {
if (zeroCount > 0) {
result += digits[0];
zeroCount = 0;
}
// 10 要简写为十
if (digit === 1 && module === 1) {
result += radices[module];
} else {
result += digits[digit] + radices[module];
}
}
// 刚好是 4 位的时候加上 万 或者 亿
if (module === 0 && zeroCount < 4) {
result += bigRadices[quotient];
}
}
return result;
}
实现 reduce
Array.prototype.myReduce = function (fn, initialValue) {
let result = initialValue ? initialValue : this[0];
for (let i = initialValue ? 1 : 0; i < this.length; i++) {
result = fn(result, this[i], i, this);
}
return result;
}