《手写代码》前端面试题

js写一个下划线驼峰格式互转

// 下划线转驼峰
function underlineToCamel(str) {
  // 初始化结果字符串
  let result = '';
  // 标记下一个字符是否应该转为大写
  let nextIsUpper = false;

  // 遍历字符串的每个字符
  for (let i = 0; i < str.length; i++) {
    // 如果当前字符是下划线,则标记下一个字符需要转为大写
    if (str[i] === '_') {
      nextIsUpper = true;
    } else {
      // 如果当前字符不是下划线
      // 如果下一个字符需要转为大写,则将当前字符转为大写
      // 否则保持不变
      result += nextIsUpper ? str[i].toUpperCase() : str[i];
      // 重置下一个字符的转换标记
      nextIsUpper = false;
    }
  }

  // 返回转换后的结果字符串
  return result;
}

// 驼峰转下划线
function camelToUnderline(str) {
  // 初始化结果字符串
  let result = '';

  // 遍历字符串的每个字符
  for (let i = 0; i < str.length; i++) {
    // 获取当前字符
    const char = str[i];
    // 判断当前字符是否为大写字母
    const isUpperCase = char !== char.toLowerCase();

    // 如果当前字符是大写字母且不是第一个字符,则在其前面添加下划线
    if (isUpperCase && i > 0) {
      result += '_';
    }

    // 将当前字符转换为小写并拼接到结果字符串中
    result += char.toLowerCase();
  }

  // 返回转换后的结果字符串
  return result;
}

// 示例
const underscoreStr = "my_variable_name";
const camelStr = "myVariableName";

console.log(underlineToCamel(underscoreStr)); // 输出:myVariableName
console.log(camelToUnderline(camelStr)); // 输出:my_variable_name

二分查找

非递归

       function search(arr,key) {
         let min = 0
         let max = arr.length-1
         while (min<=max) {
           let  mid = parseInt((min+max)/2)
           if(arr[mid]==key){
             return mid
           }else if(arr[mid]>key){
             max = mid-1
           }else if(arr[mid]<key){
             min = mid+1
           }else{
             return -1
           }           
         }
       }

递归

       function search(min,max,key,arr) {
         let mid = parseInt((min+max)/2)
         if(arr[mid]=key){
           returnn mid
         }else if(arr[mid]>key){
           max = mid-1
           search(min,max,key,arr)
         }else if(arr[mid]<key){
           min = mid+1
           search(min,max,key,arr)
         }else{
           return -1
         }
       }

时间复杂度

总共有n个元素,每次查找的区间大小就是n,n/2,n/4,…,n/2^k(接下来操作元素的剩余个数),其中k就是循环的次数。
最差的情况就是 n/2^k=1, 即区间大小为1, 所以 k=log2n, 时间复杂度为 O(logn)

call

Function.prototype.myCall = function(context) {
 context = context||window
 context.fn = this
 let args = [...arguments].splice(1)
 let result = context.fn(...args)
 delete context.fn
 return result 
}

apply

Function.prototype.myApply = function (context) {
  context = context||window
  context.fn = this
  let args = arguments[1]
  let result
  if(args){
    result = context.fn(..args)
  }else{
    result = context.fn()
  }
  delete context.fn
  return result
}

bind

Function.prototype.myBind = function (context) {
  if(typeof this!=='function'){
    return TypeError('error')
  }
  let _this = this
  let args = [...arguments].splice(1)
  return function F() {
    if(this instanceof F){
      return _this.apply(this,args.concat([...arguments]))
    }
    return _this.apply(context,args.concat([...arguments]))
  }
}

扁平化数组(递归)

Array.prototype.flat2 =function(){
  let reslut=[]
  for(let i=0;i<this.length;i++){
      if(Array.isArray(this[i])){
          reslut=reslut.concat(this[i].flat2())
      }else{
          reslut.push(this[i])
      }
  } 
  return reslut
}
let arr=[1,[2,[3,[4],[5],[6]]]]
arr.flat2()

扁平化数组(迭代)

    function *flat(arr) {
       for (const item of arr) {
           if (Array.isArray(item)) {
               yield *flat(item);
           } else {
               yield item;
           }
       }
   }
   const result = [...flat([1,2,[3,[4]]])]
   console.log(result) //  [1, 2, 3, 4]

定义一个生成器函数:迭代当前数组,如果值也是数组则生成扁平化的值,否则直接生成值。然后在我们的扁平化函数里调用这个生成器函数得到我们的一维数组。

这里有两点需要注意:

  1. 嵌套 yield 需要再加一个星号,这被称为生成器委托。
  2. 不能使用 forEach 代替 for...of 但可以用 for 循环,因为 for 循环和for...of 可以中断迭代去执行 yield,forEach 不行

归并排序

function mergeSort(arr) {
    // 基本情况:数组长度为1时已经是有序的
    if (arr.length <= 1) {
        return arr;  // 如果数组长度小于等于1,直接返回该数组,因为长度为1的数组已经是有序的
    }
 
    // 二分数组并递归排序
    var middle = Math.floor(arr.length / 2),  // 找到数组的中间位置
        left = arr.slice(0, middle),  // 将数组分成左右两部分
        right = arr.slice(middle);
 
    return merge(mergeSort(left), mergeSort(right));  // 递归调用 mergeSort 对左右两部分数组进行排序,并调用 merge 函数合并
}
 
function merge(left, right) {
    // 合并两个已排序的数组
    var result = [];  // 用于存放合并后的结果
 
    while (left.length && right.length) {
        if (left[0] <= right[0]) {  // 比较左右两个数组的第一个元素,将较小的元素推入 result 数组
            result.push(left.shift());
        } else {
            result.push(right.shift());
        }
    }
 
    // 将剩余的元素直接加到结果数组中
    while (left.length)
        result.push(left.shift());  // 当其中一个数组为空时,将另一个数组中剩余的元素直接加入到 result 数组中
 
    while (right.length)
        result.push(right.shift());  // 当其中一个数组为空时,将另一个数组中剩余的元素直接加入到 result 数组中
 
    return result;  // 返回合并后的有序数组 result
}
 
// 使用示例
var unsortedArray = [4, 3, 2, 10, 12, 1, 5, 6];
var sortedArray = mergeSort(unsortedArray);  // 调用 mergeSort 函数对未排序数组进行排序
console.log(sortedArray); // 输出: [1, 2, 3, 4, 5, 6, 10, 12] // 打印排序后的数组

冒泡排序

function bubbleSort(arr) {
  let len = arr.length
  for (let i = 0; i < len-1; i++) {
   for (let j = 0; j < len-i-1; j++) {
     if(arr[j]>arr[j+1])
     let temp = arr[j]
     arr[j] = arr[j+1]
     arr[j+1] = temp
   }    
  }
}

快速排序

function quickSort(arr) {
  let len = arr.length
  let mid = Math.floor(len/2)
  let midValue = arr.splice(mid,1)[0]
  let leftArr=[]
  let rigthArr=[]
  for (let i = 0; i < len; i++) {
    if(arr[i]>midValue){
      rigthArr.push(arr[i])
    }else{
      leftArr.push(arr[i])
    }   
  }
  return quickSort(leftArr).concat([midValue,quickSort(rigthArr)])
}

排序算法的时间复杂度

数组去重

let arr=[1,'1',1,2,3,4,4,5]
[...new Set(arr)]
//也可用Array.from(new Set(arr) 


let newArr=[]
for(let i=0,i>arr.lengeh,i++){
  if(newArr.indexOf(arr[i])==-1){
    newArr.push(arr[i]
}

柯里化函数

function currying(fn) {
  let args_arr = []
  max_length = fn.length
  let closure = function (...args) {
    // 先把参数加进去
    args_arr = args_arr.concat(args)
    // 如果参数没满,返回闭包等待下一次调用
    if (args_arr.length < max_length) return closure
    // 传递完成,执行
    return fn(...args_arr)
  }
  return closure
}
function add(x, y, z) {
  return x + y + z
}
curriedAdd = currying(add)
console.log(curriedAdd(1, 2)(3))

防抖

  function debounce(fn) {
            let timeout = null; // 创建一个标记用来存放定时器的返回值
            return function(...args) {
                clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
                timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
                    fn(...args);
                }, 500);
            };
        }

节流

function throttle(fn) {
    let canRun = true; // 通过闭包保存一个标记
    return function (...args) {
        if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
        canRun = false; // 立即设置为false
        setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
            fn(...args);
            // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
            canRun = true;
        }, 500);
    };
}

promise.all

 Promise.all= function (promiseList) {
    return new Promise((resolve,reject)=>{  // 返回一个promise实例
      if(!Array.isArray(promiseList)){  // 判断参数是否是数组
        return reject(new TypeError('error'))
      }
       // 数组为空,直接resolve
       if (promiseList.length == 0) {
         resolve(promiseList);
      }
      let cont=0 // 用于计数,当等于cont=len时就resolve
      let len=promiseList.length
      let values=new Array(len) //创建一个和传入数组长度相同的数组,用于存放结果
      for (let i = 0; i < len; i++) {
       Promise.resolve(promiseList[i]) //如果元素不是 Promise 对象,则使用 Promise.resolve 转成 Promise 对象
       .then((res)=>{
          cont++  //每次成功后+1
          values[i]=res //将成功后的值放入数组
          if(cont===len){ // 数据接收完成
            resolve(values) 
          }
       })
       .catch(err=>{ //只要有一个失败,抛出异常,状态变为 rejected
         reject(err)
       })
        
      }
    })
  }

Promise.race

Promise.race=function(promiseList){
 return new Promise((resolve,reject)=>{
   if(!Array.isArray(promiseList)){
     return reject (new TypeError('error'))
   }
   promiseList.forEach(item => {
     Promise.resolve(item)
     .then(res=>{
        resolve(res)
     })
     .catch(err=>{
        reject(err)
     })
   });
 }) 
}

简单promise

function Promise(executor){ //executor执行器
    let self = this;
    self.status = 'pending'; //等待态
    self.value  = undefined; // 表示当前成功的值
    self.reason = undefined; // 表示是失败的值
    function resolve(value){ // 成功的方法
        if(self.status === 'pending'){
            self.status = 'resolved';
            self.value = value;
        }
    }
    function reject(reason){ //失败的方法
        if(self.status === 'pending'){
            self.status = 'rejected';
            self.reason = reason;
        }
    }
    executor(resolve,reject);
}
 
Promise.prototype.then = function(onFufiled,onRejected){
    let self = this;
    if(self.status === 'resolved'){
        onFufiled(self.value);
    }
    if(self.status === 'rejected'){
        onRejected(self.reason);
    }
}
module.exports = Promise;

promise实现,红色三秒打印一次、绿色两秒打印一次、黄色一秒打印一次

        function red() {
            console.log("red");
        }
        function green() {
            console.log("green");
        }
        function yellow() {
            console.log("yellow");
        }

        function light(time,name){
            return new Promise(resolve=>{
                setTimeout(()=>{
                        name()
                        resolve()
                    },time)
            })
        }
        
        function step(){
            Promise.resolve().then(()=>{
                return light(3000,red)
            }).then(()=>{
                return light(2000,green)
            }).then(()=>{
                return light(1000,yellow)
            }).then(()=>{
                // 黄色循环完毕再调用一次step,实现不断循环打印
                return step()
            })
        }
        step()

简单数据劫持

function definedRective(data,key,val){
                Object.defineProperty(data,key,{
                    get:function(){
                        return val;
                    },
                    set:function(newVal){
                        val = newVal;
                        update();
                    }
                })
            }

简单发布订阅

class EventEmitter {
  constructor() {
    // 初始化一个空对象用于存储事件名和对应的回调函数列表
    this.cache = {}
  }

  // 订阅事件的方法
  on(name, fn) {
    // 如果已经有其他回调函数订阅过相同的事件名,则将该回调函数添加到已存在的回调函数列表中
    if (this.cache[name]) {
      this.cache[name].push(fn)
    } else {
      // 否则,创建一个新的回调函数列表,并将该回调函数添加到列表中
      this.cache[name] = [fn]
    }
  }

  // 取消订阅事件的方法
  off(name, fn) {
    // 获取该事件名对应的回调函数列表
    const tasks = this.cache[name]
    if (tasks) {
      // 找到要取消订阅的回调函数在列表中的索引,并从列表中删除
      const index = tasks.findIndex((f) => f === fn || f.callback === fn)
      if (index >= 0) {
        tasks.splice(index, 1)
      }
    }
  }

  // 触发事件的方法
  emit(name, once = false) {
    // 获取该事件名对应的回调函数列表
    if (this.cache[name]) {
      // 创建副本,避免在执行回调函数时修改原始列表,因为可能会导致意外行为
      const tasks = this.cache[name].slice()
      // 依次执行每个回调函数
      for (let fn of tasks) {
        fn();
      }
      // 如果需要只触发一次,则在执行完所有回调函数后,将该事件名从缓存中删除
      if (once) {
        delete this.cache[name]
      }
    }
  }
}



// 测试
const eventBus = new EventEmitter()
const task1 = () => { console.log('task1'); }
const task2 = () => { console.log('task2'); }
eventBus.on('task', task1)
eventBus.on('task', task2)

setTimeout(() => {
  eventBus.emit('task')
}, 1000)

发布者核心功能: 增加/删除订阅者 ,通知订阅者更新

观察者核心功能: 在发布者通知更新后,及时更新,绑定订阅者

简单深拷贝,递归

// 定义一个深拷贝函数  接收目标target参数
function deepClone(target) {
    // 定义一个变量
    let result;
    // 如果当前需要深拷贝的是一个对象的话
    if (typeof target === 'object') {
    // 如果是一个数组的话
        if (Array.isArray(target)) {
            result = []; // 将result赋值为一个数组,并且执行遍历
            for (let i in target) {
                // 递归克隆数组中的每一项
                result.push(deepClone(target[i]))
            }
         // 判断如果当前的值是null的话;直接赋值为null
        } else if(target===null) {
            result = null;
         // 判断如果当前的值是一个RegExp对象的话,直接赋值    
        } else if(target.constructor===RegExp){
            result = target;
        }else {
         // 否则是普通对象,直接for in循环,递归赋值对象的所有值
            result = {};
            for (let i in target) {
                result[i] = deepClone(target[i]);
            }
        }
     // 如果不是对象的话,就是基本数据类型,那么直接赋值
    } else {
        result = target;
    }
     // 返回最终结果
    return result;
}

考虑循环引用、symbol类型的深拷贝

需要解决拷贝对象内的循环引用问题(使用数组记录拷贝过的对象,记录一个拷贝对象的数据结构,{source:原拷贝对象,target:拷贝后的对象})

//存放已拷贝的对象用于循环引用检测
let objArr = [];
 
function deepCopy(obj) {
  //判断循环引用检测
  for (let ele of objArr) {
    if (obj === ele.source) {
      return ele.target;
    }
  }
  //拷贝容器
  let newObj = {};
 
  //将拷贝的对象放入数组中用于循环引用检测
  objArr.push({
    source: obj, //被拷贝对象上的原引用对象,用于循环检测比对
    target: newObj
  })
 
  //使用Reflect可以检测到Symbol类型的属性
  Reflect.ownKeys(obj).forEach(key => {
    if (obj.hasOwnProperty(key)) {
      if (typeof obj[key] === 'object') {
        //使用Array.from对拷贝的数组进行处理
        newObj[key] = Object.prototype.toString.call(obj[key]) === '[object Array]' ? Array.from(deepCopy(obj[key], key)) : deepCopy(obj[key], key);
      } else {
        //属性值为原始类型的值
        newObj[key] = obj[key];
      }
    }
  })
  return newObj;
}

考虑递归爆栈的深拷贝,迭代

function cloneForce(x) {
  //拷贝对象记录
  const uniqueList = []; 
 
  let root = {};
 
  // 循环数组
  const loopList = [{
    parent: root,
    key: undefined,
    data: x,
  }];
 
  while (loopList.length) {
    //深拷贝,元素出栈
    const node = loopList.pop();
    const parent = node.parent;
    const key = node.key;
    const data = node.data;
 
    let res = parent;
    if (typeof key !== 'undefined') {
      res = parent[key] = {};
    }
 
    // 判断数据是否存在
    let uniqueData = find(uniqueList, data);、
    //数据存在
    if (uniqueData) {
      parent[key] = uniqueData.target;
      break; // 中断本次循环
    }
 
    //数据不存在,将其放入数组
    uniqueList.push({
      source: data,
      target: res,
    });
 
    for (let k in data) {
      if (data.hasOwnProperty(k)) {
        if (typeof data[k] === 'object') {
          // 下一次循环
          loopList.push({
            parent: res,
            key: k,
            data: data[k],
          });
        } else {
          res[k] = data[k];
        }
      }
    }
  }
 
  return root;
}
 
function find(arr, item) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i].source === item) {
      return arr[i];
    }
  }
 
  return null;
}

给ul下的所有li绑定点击事件,弹出下标和内容

  var itemli = document.getElementsByTagName("li");
 
   for(var i = 0; i<itemli.length; i++){
 
    itemli[i].index = i; //给每个li定义一个属性索引值
 
    itemli[i].onclick = function(){
 
      alert(this.index+this.innerHTML); 
 
    }
 
   }

 var ul = document.querySelector("ul");
    ulItem.onclick = function (e) {
        e = e || window.event; //这一行及下一行是为兼容IE8及以下版本
        var target = e.target || e.srcElement;
        if (target.tagName.toLowerCase() === "li") {
            var li = this.querySelectorAll("li");
            index = Array.prototype.indexOf.call(li, target);
            alert(target.innerHTML + index);
        }
    }

杨辉三角

 
      function compute(m, n) {
        if (n == 0) {
          return 1; //每行第一个数为1
        } else if (m == n) {
          return 1; //最后一个数为1
        } else {
          //其余都是相加而来
          return compute(m - 1, n - 1) + compute(m - 1, n);
        }
      }
      function yanghui(n) {
        //杨辉三角,N为行数
        for (var i = 0; i < n; i++) {
          //一共N行
          for (var j = 0; j <= i; j++) {
            //每行数字的个数即为行号、例如第1行1个数、第2行2个数
            document.write(compute(i, j) + " ");
          }
          document.write("<br>");
        }
      }
      yanghui(15)
    
 

实现接口失败重新请求,并限制并发请求Retry

retry = function(fn, times, delay) {
  var err = null;
  return new Promise(function(resolve, reject) {
 
    var attempt = function() {
      fn().then(resolve).catch(function(err) {
        console.log(`Attempt #${times} failed`);
        if (0 == times) {
          reject(err);
        } else {
          times--;
          setTimeout(function(){
            attempt()
          }, delay);
        }
      });
    }; 
    attempt(); 
  });
};

instanceof

function instanceOf(left, right) {
    let leftValue = left.__proto__;
    let rightValue = right.prototype;
    while(leftValue ){ 
        if(leftValue === rightValue ){
        return true
        }else{
          leftValue = leftValue.__proto__
        }         
   }
   return false   
}

闭包解决settimeout打印同一个值的问题

for (var i = 0; i < 5; i++) {
  (function (i) {
      setTimeout(function() {
     console.log(i);
  }, 1000);
  })(i)
}

交集、并集、补集、差集

//交集
var c = a.filter(function(v){ return b.indexOf(v) > -1 })
//差集
var d = a.filter(function(v){ return b.indexOf(v) == -1 })
//补集
var e = a.filter(function(v){ return !(b.indexOf(v) > -1) })
 .concat(b.filter(function(v){ return !(a.indexOf(v) > -1)}))
//并集
var f = a.concat(b.filter(function(v){ return !(a.indexOf(v) > -1)}));

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1. 实现一个数组去重的函数 思路:使用对象来存储数组中的元素,遍历数组,若元素在对象中不存在,则存储到对象中,并将其推入新数组中。 2. 实现一个函数,判断一个字符串是否是回文字符串 思路:将字符串翻转,与原字符串比较是否相等。 3. 实现一个函数,可以将多维数组转化为一维数组 思路:使用递归来遍历多维数组,将每个元素推入新数组中,直到遍历完所有元素。 4. 实现一个函数,统计一个字符串中出现次数最多的字符 思路:使用对象来存储每个字符出现的次数,遍历字符串,将每个字符存储到对象中,找到出现次数最多的字符。 5. 实现一个函数,实现数组的冒泡排序 思路:使用双重循环遍历数组,比较相邻两个元素的大小,如果前者大于后者,则交换两个元素的位置,直到遍历完数组。 6. 实现一个函数,实现数组的快速排序 思路:选择数组中的一个元素作为基准点,将数组分为两个部分,一部分大于基准点,一部分小于基准点,递归处理两个部分。 7. 实现一个函数,实现数组的深拷贝 思路:使用递归遍历数组中的每个元素,判断元素类型,如果是对象或数组,则进行深拷贝,如果是基本类型,则直接复制。 8. 实现一个函数,实现函数的柯里化 思路:使用闭包保存参数,当参数个数达到预设值,执行函数。 9. 实现一个函数,实现函数的节流 思路:使用定器来控制函数执行的频率,每次函数执行,清除定器并重新设置一个定器。 10. 实现一个函数,实现函数的防抖 思路:使用定器来延迟函数执行,每次函数执行,清除定器并重新设置一个定器。如果在定器延迟间内再次触发函数,则重新计

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值