前端面试之逻辑(js)

//

setTimeout(() => {

  const np = new Promise((resolve, reject) => {

    console.log(1);

    resolve()

  })

  np.then(_ => {

    console.log(2);

  })

  console.log(3);

}, 1000);

const pro = new Promise((resolve, reject) => {

  console.log(5);

  resolve()

})

  .then(_ => {

    setTimeout(() => {

      console.log(6);

    }, 0);

    return 7

  })

  .then(n => {

    console.log(n);

  })

console.log(4);


 

// 深拷贝

function deepCopy(object) {

  // 判断是否为对象或数组

  if (typeof object !== 'object' || object === null) {

    return object;

  }

  // 创建一个空对象或数组用于存储深拷贝后的值

  const copy = Array.isArray(object) ? [] : {};

  // 遍历原始对象的属性或元素,并进行递归拷贝

  for (let key in object) {

    copy[key] = deepCopy(object[key]);

  }

  return copy;

}

const obj = { a: 1, b: [1, 2, 3] };

const copy = deepCopy(obj);

console.log(copy.b.c, 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(() => {

  console.log('setTimeout');

}, 0);

async1()

new Promise(function (resolve) {

  console.log('promise1');

  resolve()

}).then(function () {

  console.log('promise2');

})

console.log('script end');


 

// add

function add(x) {

  const fun = y => {

    if (y) {

      return add(x + y)

    } else {

      return x

    }

  }

  fun.valueOf = () => x

  return fun

}

console.log(add(1)(2)(3).toString());  // 输出 6

console.log(add(1)(2)(3)(4).toString());  // 输出 6



 

// 这个add函数采用了闭包和递归的方式。每次调用add函数时,它返回一个内部函数fun。当传入参数y

// 时,fun会返回add(x + y),即返回一个新的函数,继续等待下一个参数的传入。若不传入参数y,则直接

// 返回当前结果X。

// 为了实现输出结果的展示,我们重写了fun函数的toString方法,使其返回当前结果x的字符串表示。

function add() {

  const args = Array.from(arguments);

  function fun() {

    // 因为参数个数不定 所以把每次累加的值 都push到一个数组内

    // 最后使用 reduce 求和

    args.push(...arguments);

    return fun;

  }

  fun.toString = function () {

    return args.reduce((total, num) => total + num, 0);

  };

  return fun;

}

console.log(add(1, 2)(3, 4, 5)(4).toString());  // 输出 19


 

// 发布订阅

class PubSub {

  constructor() {

    this.subscribers = {};

  }

  on(eventType, callback) {

    if (!this.subscribers[eventType]) {

      this.subscribers[eventType] = [];

    }

    this.subscribers[eventType].push(callback);

  }

  off(eventType, callback) {

    if (this.subscribers[eventType]) {

      this.subscribers[eventType] = this.subscribers[eventType].filter(cb => cb !== callback);

    }

  }

  emit(eventType) {

    if (this.subscribers[eventType]) {

      this.subscribers[eventType].forEach(callback => callback());

    }

  }

  once(eventName, callback) {

    const onceCallback = (...args) => {

      callback(...args);

      this.off(eventName, callback);

    };

    this.on(eventName, onceCallback);

  }

}

// 示例用法

const eventBus = new PubSub();

function handleEvent1() {

  console.log('测试1');

}

function handleEvent2() {

  console.log('测试2');

}

eventBus.on('event1', handleEvent1);

eventBus.on('event2', handleEvent2);

eventBus.emit('event1', { message: 'Hello from Event 1!' });

eventBus.emit('event2', { message: 'Greetings from Event 2!' });

eventBus.off('event1', handleEvent1);

eventBus.emit('event1');


 

// 深拷贝

function deepCopy(opt) {

  // 如果原始对象是数组,则创建一个空数组

  var newObject = Array.isArray(opt) ? [] : {};

  for (var key in opt) {

    // 判断原始对象是否有该属性的自身属性(不包括原型链上的属性)

    if (opt.hasOwnProperty(key)) {

      // 判断该属性的值是否为对象,如果是则递归调用深拷贝函数

      if (typeof opt[key] === 'object' && opt[key] !== null) {

        newObject[key] = deepCopy(opt[key]);

      } else {

        // 否则直接复制属性值

        newObject[key] = opt[key];

      }

    }

  }

  return newObject;

}

const data = {

  name: 'John',

  age: 30,

  address: {

    user: '小花',

    sex: '男'

  },

};

const xxx = deepCopy(data)


 

//

let a = "  what is your name  ? fine thank you and you ?  ";

const b = a.replace(/\s+/g, " ");//左右两边多个空格弄成一个,中间一个空格不变

console.log(b, 'b');


 

// data 数据

// parentId 传入的 id

function createTree(data, parentId = null) {

  const tree = [];

  data.forEach(item => {

    // 如果 pid === 父节点id

    if (item.pid === parentId) {

      // 递归

      const children = createTree(data, item.id);

      // 如果 children 有值 存入 item

      if (children.length > 0) {

        item.children = children;

      }

      tree.push(item);

    }

  });

  return tree;

}

const data = [

  {

    id: 1,

    pid: null,

  },

  {

    id: 2,

    pid: 1,

  },

  {

    id: 3,

    pid: 1,

  },

  {

    id: 4,

    pid: 2,

  },

  {

    id: 5,

    pid: 4,

  },

  {

    id: 6,

    pid: 5,

  },

];

const result = createTree(data);

console.log(result);




 

// 1万个接口 每次执行2个

// 方法一

async function fun() {

  await Promise.all([apis.shift(), apis.shift()])

  fun()

}

fun()


 

// 方法二

// 模拟执行接口的函数

function executeAPI(api) {

  return new Promise((resolve, reject) => {

    // 模拟异步执行接口

    setTimeout(() => {

      console.log(`执行接口 ${api}`);

      resolve();

    }, 1000);

  });

}

// 执行接口的函数

async function executeAPIs(apis) {

  for (let i = 0; i < apis.length; i += 2) {

    const api1 = apis[i];

    const api2 = apis[i + 1];

    // 并行执行两个接口

    await Promise.all([executeAPI(api1), executeAPI(api2)]);

  }

}

// 定义接口数组

const apis = [

  '接口1',

  '接口2',

  '接口3',

  '接口4',

  // ... 其他接口

];

// 执行接口

executeAPIs(apis);


 

/*

  要判断 JavaScript 中的闭合括号,可以使用栈的数据结构来实现

  算法步骤:

    1. 创建一个空栈

    2. 遍历字符串中的每个字符

    3. 如果遇到开括号(如 (、{ 或 [),则将其压入栈中

    4. 如果遇到闭括号(如 )、} 或 ]),则判断栈顶的元素是否与当前闭括号匹配

        - 如果栈为空或栈顶元素与当前闭括号不匹配,则说明闭括号没有正确闭合,返回 false

        - 如果匹配成功,则弹出栈顶元素

    5. 遍历完所有字符后,如果栈为空,则说明所有闭括号都正确闭合,返回 true

      否则,说明还有未闭合的括号,返回 false

*/

function isValidate(str) {

  const inArr = [];

  const arr = str.split('');

  for (let s of arr) {

    if (s === '{' || s === '[' || s === '(') {

      // 入栈

      inArr.push(s);

    }

    if (s === '}' || s === ']' || s === ')') {

      let temp;

      switch (s) {

        case '}':

          temp = '{';

          break;

        case ']':

          temp = '[';

          break;

        case ')':

          temp = '(';

          break;

      }

      // 出栈

      const out = inArr.pop();

      if (temp !== out) {

        return false;

      }

    }

  }

  return true

}

// 示例用法

// console.log(isValidate('(){}'));     // true

// console.log(isValidate('(())'));     // true

// console.log(isValidate('({[]})'));   // true

// console.log(isValidate('({[}])'));   // false

// console.log(isValidate('(()}'));     // false


 

// 发布订阅

const sub = {

  list: {},

  publish(key, ...args) {

    if (!this.list[key]) return false

    this.list[key].forEach(dt => {

      // 如果是外包 2参数

      // dt(...args)

      dt(key, ...args)

    })

  },

  listen(key, fun) {

    if (!this.list[key]) {

      this.list[key] = []

    }

    this.list[key].push(fun)

  }

}

// 订阅

sub.listen('123', (a, b) => {

  console.log(a, b);

})

// 发布

sub.publish('123', 'zzzz')


 

// sleep

async function sleep(millis) {

  return new Promise(resolve => setTimeout(resolve, millis));

}

let t = Date.now();

sleep(100).then(() => {

  console.log(Date.now() - t); // 100

});


 

// last

Array.prototype.last = function () {

  if (!this.length) return false

  return this[this.length - 1]

}

const arr = [1, 2, 3]

console.log(arr.last())


 

/*

  javascript;输入 intervals = [[1,3], [2, 6], [8,10], [15, 18]]

  输出 [[1,6], [8,10],[15, 18]]

  区间 [1,3]和[2, 6]重叠,合并为[1,6]

*/

function fun(arg) {

  if (arg.length <= 1) {

    return arg;

  }

  const merged = [arg[0]];

  for (let i = 1; i < arg.length; i++) {

    const current = arg[i];

    const last = merged[merged.length - 1];

    if (current[0] <= last[1]) {

      last[1] = Math.max(current[1], last[1]);

    } else {

      merged.push(current);

    }

  }

  return merged;

}

const arr = [[1, 3], [2, 6], [8, 10], [15, 18]];

console.log(fun(arr));





 

function sum() {

  var total = 0;

  for (var i = 0; i < arguments.length; i++) {

    total += arguments[i];

  }

  return total;

}

console.log(sum(1, 2, 3).valueOf());  // 输出:6



 

// Promise.all 模拟实现

let promise1 = new Promise((resolve, reject) => {

  setTimeout(() => resolve("Promise 1 完成"), 1000);

});

let promise2 = new Promise((resolve, reject) => {

  setTimeout(() => resolve("Promise 2 完成"), 2000);

});

let promise3 = new Promise((resolve, reject) => {

  setTimeout(() => resolve("Promise 3 完成"), 3000);

});

Promise.all = function (promises) {

  // 返回一个新的 Promise  

  return new Promise((resolve, reject) => {

    // 用于存放所有 Promise 的结果的数组  

    let results = [];

    // 计数器,记录已完成的 Promise 数量  

    let count = 0;

    // 遍历传入的 Promise 数组  

    for (let i = 0; i < promises.length; i++) {

      // 将当前 Promise 的结果存入 results,并更新计数器  

      Promise.resolve(promises[i]).then(res => {

        results[i] = res;

        count++;

        // 如果所有 Promise 都已完成,调用 resolve 并传入结果数组  

        if (count === promises.length) {

          resolve(results);

        }

      }, err => {

        // 如果任何一个 Promise 失败,立即调用 reject 并传入错误信息  

        reject(err);

      });

    }

  });

}

// 使用 Promise.all 来等待所有 Promise 都完成  

Promise.all([promise1, promise2, promise3])

  .then(results => {

    console.log(results);  // ["Promise 1 完成", "Promise 2 完成", "Promise 3 完成"]  

  }).catch(error => {

    console.error("发生错误:", error);

  });



 

/*

  在这个实现中,curry 函数接受一个函数 fn 作为参数,并返回一个新的函数 curried。curried 函数会检查其当前参数的数量。

  如果参数数量小于 fn 的参数数量,它会返回一个新的函数,等待接收更多的参数。如果参数数量等于 fn 的参数数量,它就会调用

  fn 并返回结果。这样,我们就可以通过连续调用 sum 来逐步增加参数,并在最后调用 valueOf 来获取最终的结果。

*/

function curry(fn) {

  return function curried(...args) {

    if (args.length >= fn.length) {

      return fn.apply(this, args);

    } else {

      return function (...args2) {

        return curried.apply(this, args.concat(args2));

      }

    }

  };

}

const sum = curry((a, b) => a + b);

console.log(sum(1)(2)(3)(4).valueOf());  // 输出:10  

console.log(sum(1, 2, 3).valueOf());  // 输出:6  

console.log(sum(2)(4, 1)(2).valueOf());  // 输出:9  

console.log(sum(2, 3).valueOf());  // 输出:7





 

function arrayToTree(arr) {

  const roots = [];

  const map = new Map();

  // 将数组中的对象转换为节点,并将节点保存到map中

  for (const item of arr) {

    map.set(item.id, item);

  }

  // 遍历数组中的每一个对象,将其添加到对应的父节点的子节点数组中

  for (const item of arr) {

    item.children = [];

    // 去集合中找对应的子节点

    const node = map.get(item.id);

    if (item.parentId) {

      // 去集合中找对应的父节点

      const parent = map.get(item.parentId);

      // 如果父节点存在,把子节点放入到父节点的children数组中

      if (parent) parent.children.push(node);

      // 如果父节点有值,但是集合中不存在,如上述数据 parentId: 11 ,找不到对应id为11的,也设置为子节点

      if (!parent) roots.push(node);

    } else {

      roots.push(node);

    }

  }

  return roots;

}

const arr = [

  { id: 1, name: "A", parentId: null },

  { id: 2, name: "B", parentId: 1 },

  { id: 3, name: "C", parentId: 1 },

  { id: 4, name: "D", parentId: 2 },

  { id: 5, name: "E", parentId: 2 },

  { id: 6, name: "F", parentId: null },

  { id: 7, name: "G", parentId: 6 },

  { id: 8, name: "H", parentId: 7 },

  { id: 9, name: "I", parentId: 7 },

  { id: 9, name: "J", parentId: 7 },

  { id: 10, name: "K", parentId: 11 },

];

const roots = arrayToTree(arr);

console.log(roots, 1);




 

// 定义一个函数compareVersion,用于比较两个版本号字符串

function compareVersion (v1, v2) {

  // 将版本号字符串按点(.)和短横线(-)分割成数组

  const p1 = v1.split(/[.-]/);

  const p2 = v2.split(/[.-]/);

  // 遍历每个部分,直到找到不相等的部分或其中之一结束

  for (let i = 0; i < Math.max(p1.length, p2.length); i++) {

    // 获取对应部分的数值,如果该部分不存在则默认为0

    const n1 = parseInt(p1[i] || "0", 10);

    const n2 = parseInt(p2[i] || "0", 10);

    // 比较当前部分的数值,若不相等则返回结果

    if (n1 !== n2) {

      return n1 > n2 ? 1 : -1;

    }

  }

  // 如果所有部分都相等,则认为版本号相同,返回0

  return 0;

}

// 测试用例

const res1 = compareVersion('1.0.0', '1.0.1'); // 输出 -1,因为1.0.0 < 1.0.1

const res2 = compareVersion('10.0', '10.0.1'); // 输出 -1,因为10.0 < 10.0.1

const res3 = compareVersion('1.10.0', '1.10'); // 输出 0,因为1.10.0 == 1.10

const res4 = compareVersion('1.0.0', '1.0.1'); // 输出 -1,因为1.0.0 < 1.0.1

const res5 = compareVersion('1.100.0', '1.100'); // 输出 0,因为1.100.0 == 1.100

const res6 = compareVersion('1.1.0', '1.10'); // 输出 1,因为1.10 > 1.1.0(这里假设1.10实际上等于1.1.0而非1.10.0)

const res7 = compareVersion('1.1.0-beta.1', '1.1.0-beta.2'); // 输出 -1,因为1.1.0-beta.1 < 1.1.0-beta.2

const res8 = compareVersion('1.1.0-beta.1', '1.1.0'); // 输出 -1,因为1.1.0-beta.1 < 1.1.0

const res9 = compareVersion('1.1.1', '1.1.0-beta'); // 输出 1,因为1.1.1 > 1.1.0-beta(这里假设1.1.0-beta实际上等于1.1.0-beta.0)

console.log(res1);

console.log(res2);

console.log(res3);

console.log(res4);

console.log(res5);

console.log(res6);

console.log(res7);

console.log(res8);

console.log(res9);


 

// 防抖

function debounce (fn, delay){

  let timer = null;

  return function(){

    clearTimeout(timer);

    timer = setTimeout(()=> {

      fn.apply(this, arguments);

    }, delay)

  }

}

// 节流

function throttle (fn, delay){

  let valid = true;

  return function () {

    if(valid) {

      setTimeout(()=> {

        fn.apply(this, arguments);

        valid = true;

      }, delay)

      valid = false;

    }

  }

}


 

/*

  nextTick 原理: Vue.nextTick()的实现原理主要是将回调函数推入到一个队列中, 在下一个事件循环周期中执行这个队列中的所有回调函数

  1. 首先 Vue.js 会将回调函数推入到一个队列中。这个队列称为“异步更新队列” 它是 Vue.js 用于收集在同一事件循环周期内需要执行的

     所有异步任务的容器

  2. Vue.js 会判断当前是否存在一个微任务队列。如果存在, 则将异步更新队列合并到微任务队列中; 否则, 创建一个新的微任务队列, 并将

  异步更新队列添加到其中, nextTick 会通过 Promise.resolve()来创建一个成功的 Promise, 然后再通过 Promise.then()来将回调函

  数添加入微任务队列, 不支持 Promise 的, vue 会选择将 nextTick 加入到宏任务队列, 加入宏任务队列的方式有很多种, 按照优先级来依

  次为 setImmediate MessageChannel setTimeout

  3. Vue.js 会将当前执行上下文捕获并保存下来。这个上下文包含了当前执行 Vue.nextTick()方法的组件实例、数据变化等信息

  4. Vue.js 会将一个微任务添加到微任务队列中。这个微任务的作用是在下一个事件循环周期中执行异步更新队列中的所有回调函数, 并且在

    执行之前恢复上下文, 确保回调函数能够正确地访问到相关数据

*/

   // 存放回调函数的数组

   const callbacks = [];

   

   // 判断当前事件循环当中是否存在nextTick

   let padding = false;

   

   // 在事件循环当中执行回调函数

   function flushCallbacks() {

     // 本轮事件循环当中的nextTick已经执行完成,将pedding的状态变成false

     pedding = false;

   

     // 拷贝回调函数的数组

     const copies = callbacks.slice(0);

   

     // 将存放回调函数的数组清空

     callbacks.length = 0;

   

     // 执行回调函数

     for (let i = 0; i < copies.length; i++) {

       copies[i]();

     }

   }

   

   // 微任务

   let microTimerFunc;

   // 宏任务

   let macroTimerFunc;

   // 判断当前是否使用宏任务

   let useMacroTask = false;

   

   // 进行环境验证

   //如果当前环境支持setImmediate,则使用setImmediate将回调函数添加到宏任务队列

   if (typeof setImmediate !== "undefined" && isNative(setImmediate)) {

     macroTimerFunc = () => {

       setImmediate(flushCallbacks);

     };

   }

   // 如果当前环境支持MessageChannel,则使用MessageChannel将回调函数添加到宏任务队列

   else if (

     typeof MessageChannel !== "undefined" &&

     (isNative(MessageChannel) ||

       MessageChannel.toString() === "[object MessageChannelConstructor]")

   ) {

     const channel = new MessageChannel();

     const port = channel.port2;

     channel.port1.onmessage = flushCallbacks;

     macroTimerFunc = () => {

       port.postMessage(1);

     };

   } else {

     macroTimerFunc = () => {

       setTimeout(flushCallbacks, 0);

     };

   }

   

   // 判断当前环境是否支持Promise

   if (typeof Promise !== "undefined" && isNative(Promise)) {

     //创建一个Promise对象

     const p = Promise.resolve();

   

     // 将flushCallbacks添加到微任务

     microTimerFunc = () => {

       p.then(flushCallbacks);

     };

   } else {

     //当前环境不支持Promise,则直接添加到宏任务当中

     microTimerFunc = macroTimerFunc;

   }

   

   export function withMacroTask(fn) {

     return (

       fn._withTask ||

       (fn.withTask = function () {

         useMacroTask = true;

         const res = fn.apply(null, arguments);

         useMacroTask = false;

         return res;

       })

     );

   }

   /**

    *

    * @param {*} cb 下一次Dom更新后执行的回调函数

    * @param {*} ctx 回调函数的上下文对象

    */

   export function nextTick(cb, ctx) {

     let _resolve;

     //将回调函数缓存到callbacks当中

     callbacks.push(() => {

       //判断当前是否有回调函数

       if (cb) {

         // 让当前回调函数的this指向ctx上下文对象

         cb.call(ctx);

       } else if (_resolve) {

         // 将Promise返回出去

         return _resolve(ctx);

       }

     });

   

     //判断当前事件循环当中是否存在nextTick

     if (!pedding) {

       //将当前状态更改为true,表示事件队列当中已经有nextTick了

       pedding = true;

       if (useMacroTask) {

         macroTimerFunc();

       } else {

         microTimerFunc();

       }

     }

   

     //如果回调函数不存在,并且环境支持Promise,则nextTick会返回一个Promise

     if (!cb && typeof Promise !== "undefined") {

       //返回一个成功状态的回调函数

       return new Promise((resolve) => {

         _resolve = resolve;

       });

     }

   }



 

// # 快排

var originalArr = [12,23,16,14,12,21,7];

function quickSort(arr) {

    // 只有一个元素的时候就不用排了,直接返回,需要一直拆解到只有一个元素

    if(arr.length<=1){

        return arr;

    }

    // 拿到中间元素

    const middleIndex = Math.floor(arr.length/2);

    const middleValue  = arr.splice(middleIndex,1)[0]; // 把计划做中间数据的元素拿出来

    let left = [];

    let right = [];

    // 跟别跟中间元素进行大小比较

    for(let i=0;i<arr.length;i++){

        if(arr[i]<=middleValue){

            left.push(arr[i]);

        }else{

            right.push(arr[i]);

        }

    }

    // 这一步是关键,需要递归的处理

    return quickSort(left).concat(middleValue,quickSort(right));

}

let newArr = quickSort( [...originalArr] );

console.log(`quickSort end, newArr = ${JSON.stringify(newArr)}`);

// 结果: quickSort end, newArr = [7,12,12,14,16,21,23]



 

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值