手撕
Object.create()
在该函数中传入一个对象,从而创建一个继承自该对象的对象
js
function create(obj,propertiesObject) {
function F() {} // 1. 创建一个空的构造函数 F
F.prototype = obj // 2. 将 F 的原型指向传入的 obj
const instance = new F() // 3. 返回 F 的实例对象,该实例对象的原型是 obj
// 处理第二个参数:属性描述符对象
if (propertiesObject !== undefined) {
Object.defineProperties(instance, propertiesObject);
}
return instance;
}
instanceOf
A instanceOf B,用于判断A是否是B的实例对象
js
function myInstanceof(left, right) {
// 获取 left 对象的原型,即__proto__属性
let proto = Object.getPrototypeOf(left);
// 获取 right 构造函数的 prototype 对象
let prototype = right.prototype;
// 在原型链上查找
while (true) {
if (!proto) return false; // 到达原型链末端
if (proto === prototype) return true; // 找到匹配的原型
// 继续向上查找原型链
proto = Object.getPrototypeOf(proto);
}
}
new操作符
js
function objectFactory() {
let newObject = null;
let constructor = Array.prototype.shift.call(arguments); // 用于取出arguments的第一个值,同时保留剩下的
let result = null;
// 判断参数是否是一个函数
if (typeof constructor !== "function") {
console.error("type error");
return;
}
// 新建一个空对象,对象的原型为构造函数的 prototype 对象
newObject = Object.create(constructor.prototype);
// 将 this 指向新建对象,并在对象内执行构造函数,如果构造函数有返回值,result会接受返回值
result = constructor.apply(newObject, arguments);
// 判断result的返回类型
let flag = result && (typeof result === "object" || typeof result === "function");
// 判断返回结果。如果是对象或者函数类型,则直接返回,如果不是,就需要返回object包装的类型
return flag ? result : newObject;
}
// 使用方法
objectFactory(构造函数, 初始化参数);
Promise
js
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
this.state = PENDING;
this.value = undefined;
this.resolvedCallbacks = [];
this.rejectedCallbacks = [];
// 使用箭头函数自动绑定 this
const resolve = (value) => {
// 可写可不写,用于当resolve一个promise的时候,会等待该promise执行完毕后再执行
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}
// 为了实现优先执行Promise后的同步任务,在Promise内部设置宏任务
setTimeout(() => {
if (this.state === PENDING) {
this.state = RESOLVED;
this.value = value;
this.resolvedCallbacks.forEach(cb => cb(value));
}
});
};
const reject = (reason) => {
setTimeout(() => {
if (this.state === PENDING) {
this.state = REJECTED;
this.value = reason;
this.rejectedCallbacks.forEach(cb => cb(reason));
}
});
};
try {
executor(resolve, reject); // 执行传入Promise的回调函数,向外暴露resolve和reject
} catch (err) {
reject(err);
}
}
then(onResolved, onRejected) {
// 值穿透处理
onResolved = typeof onResolved === 'function' ? onResolved : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
// Promise未执行时逻辑
if (this.state === PENDING) {
// 返回新 Promise 支持链式调用
return new MyPromise((resolve, reject) => {
// 传入回调函数,当Promise状态发生变化的时候,会取出执行
this.resolvedCallbacks.push((value) => {
try {
const result = onResolved(value);
resolve(result);
} catch (err) {
reject(err);
}
});
this.rejectedCallbacks.push((reason) => {
try {
const result = onRejected(reason);
resolve(result);
} catch (err) {
reject(err);
}
});
});
}
// 状态已确定时直接执行
return new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
const callback = this.state === RESOLVED ? onResolved : onRejected;
const result = callback(this.value);
resolve(result);
} catch (err) {
reject(err);
}
});
});
}
// catch实则是语法糖
catch(onRejected) {
return this.then(null, onRejected);
}
}
防抖
js
function debounce(fn, wait) {
let timer = null;
return function(...args) { // ⭐️ 使用 ...args 替代 arguments
const context = this; // 保存下调用环境下的this(谁调用,this就指向谁)
if (timer) clearTimeout(timer);
timer = setTimeout(() => { // setTimeout会返回一个计时器id,用于取消计时器
fn.apply(context, args); // 传递保存的参数
}, wait);
};
}
let callDebounce = doubounce(hello,300);
callDebounce('hello'); // hello 就是传入的arguments
节流
js
// 函数节流的实现;
function throttle(fn, delay) {
let curTime = Date.now();
return function(...args) {
let context = this,
nowTime = Date.now();
// 如果两次时间间隔超过了指定时间,则执行函数。
if (nowTime - curTime >= delay) {
curTime = Date.now();
return fn.apply(context, args);
}
};
}
类型判断
typeof的痛点:typeof null 会返回object,对部分引用类型,返回类型均为object
js
function getType(value) {
if (value === null) return "null";
const type = typeof value;
if (type !== "object") return type;
// 引用类型统一处理
return Object.prototype.toString.call(value) // 该函数会返回[object xxx]
.slice(8, -1) // 直接截取 "Array"、"Date" 等
.toLowerCase();
}
map
js
// array.map(value,index,array) map参数
Array.prototype.myMap = function(callbackFn, thisArg) {
// this 指向调用 myMap 的数组实例
const arr = this;
const result = [];
for (let i = 0; i < arr.length; i++) {
// 检查数组中是否存在该索引(处理稀疏数组,如 [1, , 3]),这样写可以跳过空值
if (i in arr) {
result[i] = callbackFn.call(thisArg, arr[i], i, arr);
}
}
return result;
};
// 使用reduce
Array.prototype.myMap = function(callbackFn, thisArg) {
return this.reduce((mappedArray, currentValue, index, array) => {
// 仅当原数组中该索引存在时才添加映射结果,空槽无法通过array.hasOwnProperty(index)
if (array.hasOwnProperty(index)) {
mappedArray[index] = callbackFn.call(thisArg, currentValue, index, array);
}
return mappedArray;
}, []);
}
call
js
// call函数实现
Function.prototype.myCall = function(context, ...args) {
// 类型检查:确保调用 myCall 的是一个函数(this指向)
if (typeof this !== 'function') {
throw new TypeError('Not a function');
}
// 处理 context:如果为 null/undefined,则指向全局对象(浏览器中是 window)。??操作符:当左侧为null或undefined时返回右侧
context = context ?? window;
// 使用唯一的 Symbol 作为属性名,避免冲突
const fnSymbol = Symbol('fn');
context[fnSymbol] = this; // 将原函数挂载为 context 的临时方法
// 执行函数并传递参数,保存结果
const result = context[fnSymbol](...args);
// 删除临时属性,避免污染 context 对象
delete context[fnSymbol];
return result;
};
apply
js
Function.prototype.myApply = function(context) {
// 确保调用 myApply 的是一个函数
if (typeof this !== 'function') {
throw new TypeError('当前对象不是函数');
}
// 处理 context,若为 null/undefined 则指向全局对象
context = context ?? window;
// 使用 Symbol 避免属性名冲突
const fnSymbol = Symbol('fn');
context[fnSymbol] = this;
// 获取参数数组(第二个参数为数组或 undefined)
const args = arguments[1]; // 不传参为undefined
// 执行函数并传递参数
let result;
if (args === undefined || args === null) {
// 无参数或参数为 null/undefined 时直接调用
result = context[fnSymbol]();
} else {
// 确保参数是数组类型
if (!Array.isArray(args)) {
throw new TypeError('第二个参数必须是数组或类数组对象');
}
// 使用扩展运算符展开参数数组
result = context[fnSymbol](...args);
}
// 删除临时属性,避免污染 context
delete context[fnSymbol];
return result;
};
bind
js
Function.prototype.myBind = function(context, ...presetArgs) {
const originalFunc = this;
const boundFunc = function(...callArgs) {
const allArgs = [...presetArgs, ...callArgs];
// 使用 Reflect.construct 确保实例继承自 boundFunc.prototype
if (this instanceof boundFunc) {
return Reflect.construct(originalFunc, allArgs, boundFunc);
}
// 普通调用时绑定 context
return originalFunc.apply(context, allArgs);
};
// 使用 Object.setPrototypeOf 确保 boundFunc 继承 originalFunc 的静态属性
Object.setPrototypeOf(boundFunc, Object.getPrototypeOf(originalFunc));
return boundFunc;
};
sleep
需要通过sleep函数来阻塞进程
js
function sleep(delay) {
return new Promise(resolve => {
setTimeout(resolve, delay);
});
}
// 使用示例(配合async/await)
async function main() {
console.log('开始');
await sleep(1000); // 等待1秒
console.log('延迟1秒后执行');
await sleep(500); // 再等待0.5秒
console.log('结束');
}
main();
函数柯里化
将一个需要传入多个参数的函数,分成可以接受任意数量参数的函数,直到参数数量符合原函数要求后才执行原函数
js
function curry(fn, args) {
// 获取函数需要的参数长度
let length = fn.length;
args = args || [];
return function() {
// 浅拷贝参数,从而不影响原先参数
let subArgs = args.slice();
// 拼接得到现有的所有参数,arguments是return的函数传入的参数
for (let i = 0; i < arguments.length; i++) {
subArgs.push(arguments[i]);
}
// 判断参数的长度是否已经满足函数所需参数的长度
if (subArgs.length >= length) {
// 如果满足,执行函数
return fn.apply(this, subArgs);
} else {
// 如果不满足,递归返回科里化的函数,等待参数的传入
// 每次调用该方法都会重走上面的逻辑
return curry.call(this, fn, subArgs);
}
};
}
// es6 实现
function curry(fn, ...args) {
return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}
实现浅拷贝
- Object.assign(target,param1,param2,...)
- 扩展运算符(...)
- 数组浅拷贝,两个方法不传入任何参数就是实现数组的浅拷贝
- .slice()
- .concat()
js
// 手撕浅拷贝
function shallowCopy(object) {
// 只拷贝对象
if (!object || typeof object !== "object") return;
// 根据 object 的类型判断是新建一个数组还是对象
let newObject = Array.isArray(object) ? [] : {};
// 遍历 object,并且判断是 object 的属性才拷贝,如果是object[key],会访问原型链上的属性
for (let key in object) {
// 也可以使用obj.hasOwnProperty(key),前提是obj的该方法没有被重写
if (Object.hasOwnProperty.call(obj, key)) {
newObject[key] = object[key];
}
}
return newObject;
}
实现深拷贝
JSON.parse(JSON.stringify(obj))
,但是存在问题,拷贝的对象中如果有函数,undefined,symbol,当使用过JSON.stringify()
进行处理之后,都会消失。lodash的_.cloneDeep方法
- js
// 深拷贝的实现 function deepCopy(object) { if (!object || typeof object !== "object") return; let newObject = Array.isArray(object) ? [] : {}; for (let key in object) { if (Object.hasOwnProperty.call(obj, key)))) { newObject[key] = typeof object[key] === "object" ? deepCopy(object[key]) : object[key]; // 如果是对象的话,递归调用 } } return newObject; }
object.assign()
js
// sources为多个对象
Object.myAssign = function(target, ...sources) {
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
// 使用Object包装target,避免target是String或者Number
const result = Object(target);
// 遍历source,并将属性添加到target上
sources.forEach(obj => {
if (obj != null) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = obj[key];
}
}
}
});
return result;
};
数组扁平化
相当于实现es6中的flat功能
js
// 递归实现
let arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
let result = [];
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i])); // 递归子数组
} else {
result.push(arr[i]);
}
}
return result;
}
flatten(arr); // [1, 2, 3, 4,5]
js
// 扩展运算符实现
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) { // 查看每次遍历的数组是否还有子数组,...操作符可以解构子数组
arr = [].concat(...arr);
}
return arr;
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
js
// toString会将数组递归的形成字符串,然后再通过,分割
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.toString().split(',');
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
js
// 使用reduce
const flat = arr => {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flat(cur) : cur); // 递归扩展数组
}, []);
};
js
// 带deepth,即拍平的层数
// reduce + 递归
function flat(arr, num = 1) {
return num > 0
? arr.reduce(
(pre, cur) =>
pre.concat(Array.isArray(cur) ? flat(cur, num - 1) : cur),
[]
)
: arr.slice();
}
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5]
flat(arr, Infinity);
// [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5 ];
数组去重
js
// es6,使用set和Array.from
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
js
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
uniqueArray(array); // [1, 2, 3, 5, 9, 8]\
// 通过map来唯一
function uniqueArray(array) {
let map = {};
let res = [];
for(var i = 0; i < array.length; i++) {
if(!map.hasOwnProperty([array[i]])) {
map[array[i]] = 1;
res.push(array[i]);
}
}
return res;
}
字符串repeat
js
function repeat(s, n) {
return (new Array(n + 1)).join(s); // new Array(n+1)会构造一个[,,...,]的数组,可容纳n+1个元素,.join(s)方法可以让数组中的元素以s连接起来,最后构成n个s相连
}
将对象数组转换成树形结构
js
const deptList = [
{ id: 1, name: "总公司", parentId: null },
{ id: 2, name: "技术部", parentId: 1 },
{ id: 3, name: "市场部", parentId: 1 },
{ id: 4, name: "前端组", parentId: 2 },
{ id: 5, name: "后端组", parentId: 2 },
{ id: 6, name: "销售组", parentId: 3 },
];
function arrayToTree(arr) {
// 辅助函数:查找某个父节点的所有子节点
function findChildren(parentId) {
return arr.filter(item => item.parentId === parentId)
.map(item => {
const children = findChildren(item.id);
return { ...item, children }; // 保留item内的属性,同时添加一个children属性,值为子节点
});
}
// 找到所有根节点(parentId 为 null 或 0)
return findChildren(null);
}
// 使用示例
const tree = arrayToTree(deptList)
[
{
id: 1,
name: "总公司",
parentId: null,
children: [
{
id: 2,
name: "技术部",
parentId: 1,
children: [
{ id: 4, name: "前端组", parentId: 2, children: [] },
{ id: 5, name: "后端组", parentId: 2, children: [] }
]
},
{
id: 3,
name: "市场部",
parentId: 1,
children: [
{ id: 6, name: "销售组", parentId: 3, children: [] }
]
}
]
}
]
红绿灯
js
// 定义Promise
const task = (timer, light) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (light === 'red') {
red()
}
else if (light === 'green') {
green()
}
else if (light === 'yellow') {
yellow()
}
resolve()
}, timer)
})
// 使用.then递归实现
const step = () => {
task(3000, 'red')
.then(() => task(2000, 'green'))
.then(() => task(1000, 'yellow'))
.then(step) // 循环调用
}
step()
// 使用async/await
const taskRunner = async () => {
await task(3000, 'red')
await task(1000, 'green')
await task(2000, 'yellow')
taskRunner() // 循环调用
}
taskRunner()
约瑟夫环
js
function josephus(n, m) {
// 常用生成数组方法,第一个参数是生成了一个类数组,第二个参数是mapFn,箭头函数的参数是(值,索引)
const people = Array.from({ length: n }, (_, i) => i + 1); // 初始化编号1~n
let index = 0; // 当前报数位置
for (let i = n; i > 1; i--) { // 直到剩下1人
index = (index + m - 1) % i; // 计算下一个淘汰者的索引(从0开始)
people.splice(index, 1); // 淘汰该人
}
return people[0]; // 返回幸存者
}
console.log(josephus(5, 3)); // 输出: 4
规定位置写入元素
js
<ul class="ulBox">
<!-- first -->
<li><a>链接1</a></li>
<li><a>链接2</a></li>
<li><a>链接3</a></li>
<!-- second -->
</ul>
<script>
// 获取所有注释节点
const comments = document.querySelector('.ulBox');
const place1 = comments.firstChild; // 可以获取到注释(comment节点),nodeType的值为8,nodeName的值为“#comment”,nodeValue的值是注释的内容
const place2 = comments.lastChild; // 同理
// 创建新的li元素
const newLi1 = document.createElement('li');
const newLi2 = document.createElement('li');
// 创建a元素并设置文本内容
const newA1 = document.createElement('a');
newA1.textContent = '新链接1';
const newA2 = document.createElement('a');
newA2.textContent = '新链接2';
// 将a元素添加到li元素中
newLi1.appendChild(newA1);
newLi2.appendChild(newA2);
comments.insertBefore(newLi1, place1);
comments.insertBefore(newLi2, place2);
</script>
随机打乱数组
js
// 洗牌算法
function shuffleArray(array){
let arr = [...array];
for(let i=arr.length-1;i>0;--i){
let j = Math.floor(Math.random()*(i+1))
[arr[i],arr[j]] = [arr[j],arr[i]]
}
return arr
}