1. 数组去重
// 方法一 局限性:不支持对象方法
function unique(ary){
return Array.from(new Set(ary))
}
// 方法一
function unique(ary){
let newAry = [];
for (let i = 0; i < ary.length; i++) {
if(newAry.indexOf(ary[i]) === -1){
newAry.push(ary[i])
}
}
return newAry;
}
// 方法三
function unique(ary){
let newAry = [];
newAry = ary.filter((item,index,ary) => {
return ary.indexOf(item) === index
})
return newAry;
}
2. 找到某个字符串每个字符出现的次数
function getNeedObj(str){
let arr = str.split("");
let needArr = [];
let needArrPlan = [];
for (let i = 0; i < arr.length; i++){
if (needArrPlan.indexOf(arr[i]) !== -1) {
for (let j = 0; j < needArr.length; j++) {
if(needArr[j].name === arr[i]){
needArr[j].num += 1;
}
}
} else {
needArrPlan.push(arr[i])
needArr.push({
name: arr[i],
num: 1
})
}
}
needArr.sort(function(a,b){
return a.num-b.num
})
return needArr
}
3. 数组扁平化
let arr=[1,2,[5,6,[7,[9,10]]],3,[4]];
// 方法一
function flat(arr) {
// 以下代码还可以简化,不过为了可读性,还是....
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flat(cur) : cur);
}, []);
}
// 方法二
function flat(arr){
let res = [];
while(arr.some(item => Array.isArray(item))){
arr = [].concat(...arr);
}
return arr;
}
// 方法三
function flat(arr){
const res = arr.map(item => {
if(Array.isArray(item)){
return flat(item)
}
return [item]
});
return [].concat(...res);
}
4. 递归——list to tree
// 数组转为tree
const oldData = [
{id:1,name:'boss',parentId:0},
{id:2,name:'lily',parentId:1},
{id:3,name:'jack',parentId:1},
{id:4,name:'john',parentId:2},
{id:5,name:'boss2',parentId:0},
]
function listToTree(oldArr){
oldArr.forEach(element => {
let parentId = element.parentId;
if(parentId !== 0){
oldArr.forEach(ele => {
if(ele.id == parentId){ //当内层循环的ID==外层循环的parendId时,(说明有children),需要往该内层id里建个children并push对应的数组;
if(!ele.children){
ele.children = [];
}
ele.children.push(element);
}
});
}
});
console.log(oldArr) //此时的数组是在原基础上补充了children;
oldArr = oldArr.filter(ele => ele.parentId === 0); //这一步是过滤,按树展开,将多余的数组剔除;
console.log(oldArr)
return oldArr;
}
const res = listToTree(oldData);
console.log(res);
5. 深拷贝和浅拷贝
// 1,对象深拷贝
// JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串
// JSON.parse() 方法将数据转换为 JavaScript 对象
JSON.parse(JSON.stringify())
// 2,循环和递归
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== "object") return obj;
if (map.has(obj)) return map.get(obj); // 处理循环引用
let clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone); // 存储已拷贝对象
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], map);
}
}
return clone;
}
6. 防抖和节流
防抖: 在事件被触发后,等待一定的时间再执行事件处理函数,如果在等待时间内事件再次被触发,则重新计时。节流: 在规定的时间间隔内,只允许事件触发一次,无论期间事件被触发多少次都不响应,只有到达时间间隔后才能触发下一次事件
// 防抖
function be(fn, delay) {
let timer = null
return function(...args) {
const context = this;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
}
// 节流
function th(fn, delay) {
let timer = null
return function(...args) {
const context = this;
if (timer) return
timer = setTimeout(() => {
fn.apply(context, args)
timer = null
}, delay)
}
}
7. 手写call,apply ,bind函数
// 手写call
Function.prototype.myCall = function(){
// 获取原始this
const oldSelf = this;
// 获取接受的参数,将伪数组变为数组
let getArr = [];
for(let i=0;i<arguments.length;i++){
getArr.push(arguments[i])
}
// 获取新的this,和新的参数
const newSelf = getArr.shift();
newSelf.fn = oldSelf;
const res = newSelf.fn(...getArr);
delete newSelf.fn;
return res;
}
function fn(a, b){
console.log(a,b,this)
return 123
};
const res = fn.myCall({name:"lx"},"dhusfh","hfdhgf");
console.log(res)
// 手写bind
Function.prototype.myBind = function(){
// 拿到原始this
const oldSelf = this;
// 拿到传进来的参数,将伪数组变为数组
let argus = [];
for (let i=0;i<arguments.length;i++) {
argus.push(arguments[i]);
}
// 拿到第一个参数,且参数数组同时把第一个数给移除
const newSelf = argus.shift();
return function(...argus2){
console.log(new.target)
if (new.target === undefined){
//执行时改变原始this为newSelf
newSelf.fn = oldSelf;
const res = newSelf.fn(...argus);
delete newSelf.fn;
return res;
} else {
// new调用了bind
return oldSelf(...argus,...argus2)
}
}
}
function fn(a,b){
console.log(a,b,this)
return 123
}
const p = fn.myBind({name:"lx"},["aaa","bbb"],'ccc')
p()
new p()
8. 图片懒加载
const images = document.querySelector("img");
const callback = entries => {
entries.forEach(item => {
if(item.isIntersecting){
const image = item.target;
const data_src = image.getAttribute('data-src');
image.setAttribute('src',data_src);
observer.unobserve(image);
}
});
}
const observer = new IntersectionObserver(callback);
images.forEach(item => {
observer.observe(item);
})
9. 柯里化函数
柯里化:返回一个从接受多个参数,变为只接受单一参数的函数 -》 柯里化之后的函数不会立即执行,会把传入的参数通过闭包保存起来,直到检测到最后一个参数时,之前的参数都会被一次性用于求值
function curry(fn) {
console.log(fn)
return function curried(...args) {
console.log(111, args, fn)
// 如果传入的参数数量足够,调用原函数
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
// 否则,返回一个新的函数,继续接受参数
return function(...moreArgs) {
console.log(222, moreArgs)
return curried.apply(this, args.concat(moreArgs));
};
}
};
}
function add(a,b,c){
return a + b + c
}
const curryAdd = curry(add);
console.log(curryAdd)
const res = curryAdd(10)(20)(30);
console.log(res)
10. 手写一个promise
const p = new Promise((resolve,reject) => {
resolve(123)
})
p.then(res => {
console.log(res)
})
class MyPromise {
static PEDING = '待定';
static REJECT = '拒绝';
static FULFILLED = '成功';
constructor (func){
this.status = MyPromise.PEDING;
this.result = null;
this.resolveCallbacks = [];
this.rejectCallbacks = [];
try{
func(this.resolve.bind(this),this.reject.bind(this));
}catch(error){
this.reject(error)
}
}
resolve(result){
setTimeout(()=>{
if(this.status === MyPromise.PEDING){
this.status = MyPromise.FULFILLED;
this.result = result;
this.resolveCallbacks.forEach(callback => {
callback(result);
})
}
})
}
reject(result){
setTimeout(() => {
if(this.status === MyPromise.PEDING){
this.status = MyPromise.REJECT;
this.result = result;
this.rejectCallbacks.forEach(callback => {
callback(result)
})
}
})
}
then(onFULFILLED,onREJECTED){
return new MyPromise((resolve,reject) => {
onFULFILLED = typeof onFULFILLED === 'Function' ? onFULFILLED : () => {};
onREJECTED = typeof onREJECTED === 'Function' ? onFULFILLED : () => {};
if(this.status === MyPromise.PEDING){
this.resolveCallbacks.push(onFULFILLED);
this.rejectCallbacks.push(onREJECTED);
}
if(this.status === MyPromise.FULFILLED){
setTimeout(() => {
onFULFILLED(this.result);
})
}
if(this.status === MyPromise.REJECT){
setTimeout(() => {
onREJECTED(this.result);
})
}
})
}
}
let myP = new MyPromise((resolve,reject) => {
resolve("123");
})
myP.then(result => {
console.log(result);
})
11. 手写一个promise.all
// 基础版本:理解核心原理
Promise.myAll = function(promises) {
return new Promise((resolve, reject) => {
// 参数校验:如果不是可迭代对象,直接reject
if (!promises || typeof promises[Symbol.iterator] !== 'function') {
return reject(new TypeError('Argument is not iterable'))
}
const results = [] // 存储所有Promise的结果
let count = 0 // 记录已完成的Promise数量
// 处理空数组的情况
if (promises.length === 0) {
return resolve(results)
}
promises.forEach((promise, index) => {
// 统一处理:将非Promise值转换为Promise
Promise.resolve(promise).then(
value => {
// 保持结果顺序:按索引存储
results[index] = value
count++
// 所有Promise都完成时resolve
if (count === promises.length) {
resolve(results)
}
},
// 任何一个Promise reject,整个all就reject
reject
)
})
})
}
12. 并发限制版本
// Promise.all + 并发限制
Promise.allWithLimit = function(promises, limit) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Promises must be an array'))
}
if (promises.length === 0) {
return resolve([])
}
const results = new Array(promises.length)
let completedCount = 0
let currentIndex = 0
let isRejected = false
// 执行下一个Promise
const runNext = () => {
if (currentIndex >= promises.length || isRejected) {
return
}
const index = currentIndex++
Promise.resolve(promises[index]).then(
value => {
if (isRejected) return
results[index] = value
completedCount++
if (completedCount === promises.length) {
resolve(results)
} else {
runNext() // 继续执行下一个
}
},
reason => {
if (!isRejected) {
isRejected = true
reject(reason)
}
}
)
}
// 启动初始的并发任务
const initialTasks = Math.min(limit, promises.length)
for (let i = 0; i < initialTasks; i++) {
runNext()
}
})
}
13. 宏任务/微任务
// 写出下面代码运行结果
// 题目1
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function () {
console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
});
console.log('script end');
// 题目2
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
//async2做出如下更改:
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
});
}
console.log('script start');
setTimeout(function () {
console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise3');
resolve();
}).then(function () {
console.log('promise4');
});
console.log('script end');
// 题目3
async function async1() {
console.log('async1 start');
await async2();
//更改如下:
setTimeout(function () {
console.log('setTimeout1')
}, 0)
}
async function async2() {
//更改如下:
setTimeout(function () {
console.log('setTimeout2')
}, 0)
}
console.log('script start');
setTimeout(function () {
console.log('setTimeout3');
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
});
console.log('script end');
// 题目4
async function a1() {
console.log('a1 start')
await a2()
console.log('a1 end')
}
async function a2() {
console.log('a2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
Promise.resolve().then(() => {
console.log('promise1')
})
a1()
let promise2 = new Promise((resolve) => {
resolve('promise2.then')
console.log('promise2')
})
promise2.then((res) => {
console.log(res)
Promise.resolve().then(() => {
console.log('promise3')
})
})
14. 三数之和,给定一个数组nums,判断nums中是否存在三个元素a,b,c,是的a+b+c=target,找到满足条件且不重复的三元组合
两数之和
function toSum(arr, target) {
const indexNums = arr.map((num, index) => ({num, index}))
let left = 0;
let right = arr.length - 1;
indexNums.sort((a, b) => a.num - b.num);
console.log(indexNums)
while(left < right) {
const sum = indexNums[left].num + indexNums[right].num;
if (sum === target) {
return [indexNums[left].num, indexNums[right].num]
} else if (sum < target) {
left++
} else {
right--
}
}
return []
}
三数之和:排序 + 双指针(最优解)
function threeSum(nums, target) {
const result = [];
const n = nums.length;
// 1. 先排序(方便去重和双指针移动)
nums.sort((a, b) => a - b);
// 2. 遍历数组,固定第一个数
for (let i = 0; i < n - 2; i++) {
// 跳过重复的第一个数
if (i > 0 && nums[i] === nums[i - 1]) continue;
let left = i + 1;
let right = n - 1;
// 3. 双指针寻找另外两个数
while (left < right) {
const sum = nums[i] + nums[left] + nums[right];
if (sum === target) {
result.push([nums[i], nums[left], nums[right]]);
// 跳过重复的第二个数
while (left < right && nums[left] === nums[left + 1]) left++;
// 跳过重复的第三个数
while (left < right && nums[right] === nums[right - 1]) right--;
// 移动指针继续寻找
left++;
right--;
} else if (sum < target) {
left++; // 和太小,左指针右移
} else {
right--; // 和太大,右指针左移
}
}
}
return result;
}
15. 给定一个字符串,找出其中无重复自负的最长子串的长度
滑动窗口 + Set(最优解)
function lengthOfLongestSubstring(s) {
const set = new Set();
let maxLength = 0;
let left = 0; // 左指针
let right = 0; // 右指针
while (right < s.length) {
const char = s[right];
// 如果字符不在Set中,加入Set并更新最大长度
if (!set.has(char)) {
set.add(char);
maxLength = Math.max(maxLength, right - left + 1);
right++;
} else {
// 如果字符已存在,移动左指针直到删除重复字符
set.delete(s[left]);
left++;
}
}
return maxLength;
}
// 时间复杂度:O(n)
// 空间复杂度:O(min(n, m)),m是字符集大小
变种1:找到最长子串本身
function longestSubstring(s) {
const charIndex = new Map();
let maxLength = 0;
let left = 0;
let start = 0; // 最长子串的起始位置
for (let right = 0; right < s.length; right++) {
const char = s[right];
if (charIndex.has(char) && charIndex.get(char) >= left) {
left = charIndex.get(char) + 1;
}
charIndex.set(char, right);
const currentLength = right - left + 1;
if (currentLength > maxLength) {
maxLength = currentLength;
start = left;
}
}
return s.substring(start, start + maxLength);
}
20. vue的响应式
vue2的响应式
// vue响应式原理
let data = {
name: "lianxiao",
age: 18,
friend: {
origin: "lx",
},
colors: ["red","origin","balana"]
}
// 处理数组
const oldArrayProto = Array.prototype; // 拿到Array原型
const newArrayProto = Object.create(oldArrayProto); // 把现有对象的属性,挂到新建对象的原型上,新建对象为空对象
["push","unshift","pop","splice"].forEach(item => {
newArrayProto[item] = function(){
console.log("视图更新");
oldArrayProto[item].call(this,...arguments)
}
})
// 绑定响应数据
observer(data);
function observer(data){
if (typeof data !== "object" || data === null){
return data;
}
if (Array.isArray(data)){
data.__proto__ = newArrayProto; // 数组原型替换
}
for(let key in data){
console.log(key,data[key])
defineReactive(data,key,data[key])
}
}
function defineReactive(data,key,value){
observer(value);
Object.defineProperty(data,key,{
get(){
return value
},
set(newValue){
observer(newValue);
if (newValue !== value) {
value = newValue;
console.log("视图更新")
}
}
})
}
vue3的响应式
// 创建响应式
function reactive(target = {}) {
if (typeof target !== 'object' || target == null) {
// 不是对象或数组,则返回
return target
}
// 代理配置
const proxyConf = {
get(target,key,receiver){
// 只处理本身(非原型的)属性
const ownKeys = Reflect.ownKeys(target);
if(ownKeys.includes(key)){
console.log('get',key) // 监听
}
const result = Reflect.get(target,key,receiver);
return reactive(result) // 返回结果
},
set(target,key,val,receiver){
// 重复的数据,不处理
if(val===target[key]){
return true
}
const ownKeys = Reflect.ownKeys(target);
if(ownKeys.includes(key)){
console.log('已有的key',key) // 监听
}else{
console.log('新增的key',key) // 监听
}
const result = Reflect.set(target,key,val,receiver);
return result // 是否设置成功
},
deleteProperty(target,key){
const result = Reflect.deleteProperty(target,key);
return result // 是否删除成功
}
};
// 生成代理对象
const observed = new Proxy(target,proxyConf);
return observed;
}