常见前端手写面试题(一)

常见前端手写面试题 ## (一)

1.二分法查找(非递归)

function serch(arr,key){
 let min = 0;
 let max = arr.length-1;
 while(min<=max){
    let mid = Math.floor((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
    }
 } 
}

2.二分法查找(递归)

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

3.实现instanceof操作符的效果

function isInstanceOf(left,right){
       let prototype = right.prototype
       let _proto = left.__proto__
       while(_proto){
           if(_proto==prototype){
               return true
           }else{
               _proto=_proto.__proto__
           }
       }
       return false
}

4.实现new操作符的效果

function MyNew() {
    let obj = {}

    let Constructor = [].shift.call(arguments)

    obj.__proto__ = Constructor.prototype

    let result = Constructor.call(obj, arguments)

    return typeof result === 'object'? result : obj
}

5.深拷贝

/*  封装函数deepCopy, 实现深拷贝, 支持的类型有:基本数据类型、对象、数组、日期、正则   */

function deepCopy(obj, cache = new WeakMap()) {
    // 判断是否为引用类型
    if(!obj instanceof Object) return obj;

    // 避免循环引用
    if(cache.get(obj)) return cache.get(obj)

    // 支持函数
    if(obj instanceof Function) {
        return function() {
            obj.call(this, ...arguments)
        }
    }

    // 支持日期
    if(obj instanceof Date) return new Date(obj)

    // 支持正则
    if(obj instanceof RegExp) return new RegExp(obj.source, obj.flags)

    /*
        还可以继续添加其他支持的数据类型
    */

    // 支持数组和对象
    const res = Array.isArray(obj)? [] : {}

    // 缓存遍历过的对象
    cache.set(obj, res)

    // 遍历对象或数组
    Object.keys(obj).forEach(key => {
        if(obj[key] instanceof Object) {
            res[key] = deepCopy(obj[key], cache)
        } else {
            res[key] = obj[key]
        }
    })

    // 返回最终结果
    return res
}

6.节流

/*  封装函数throttle, 实现节流,动作稀释   */

function throttle(func, delay=500) {
    let timer = null
    let status = false//重在加开关锁
        return function (...args) {
        if(status) return;
        status = true//改变开关的状态
        timer = setTimeout(() => {
            func.apply(this, args)
            status = false
        }, delay)
    }
}

7.防抖

/*  封装函数debounce, 实现防抖,防止误触   */
function debounce(func, delay=500) {
    let timer = null//重在消除时间间隔
    return function(...args) {
        if(timer) clearTimeout(timer);
        timer = setTimeout(() => {
            func.apply(this, args)
        }, delay)
    }
}

8.实现apply函数

/*   在函数原型上封装myApply函数 , 实现和原生apply函数一样的效果   */

Function.prototype.myApply = function(context) {
    // 存储要转移的目标对象
    _this = context? Object(context) : window
    // 在转移this的对象上设定一个独一无二的属性 , 并将函数赋值给它
    let key = Symbol('key')
    _this[key] = this
    // 将数组里存储的参数拆分开,作为参数调用函数
    let res = arguments[1]? _this[key](...arguments[1]) : _this[key]()
    // 删除
    delete _this[key]
    // 返回函数返回值
    return res
}
// 测试代码
let obj = {
    'name': '张三'
}
function showName(first, second, third) {
    console.log(first, second, third);
    console.log(this.name);
}
showName.myApply(obj, [7, 8, 9])

9.实现call函数

/*   在函数原型上封装myCall函数 , 实现和原生call函数一样的效果   */

Function.prototype.myCall = function(context) {

    // 存储要转移的目标对象
    let _this = context? Object(context) : window

    // 在转移this的对象上设定一个独一无二的属性 , 并将函数赋值给它
    let key = Symbol('key')
    _this[key] = this
    
    // 创建空数组,存储多个传入参数
    let args = []
    
    // 将所有传入的参数添加到新数组中
    for(let i = 1; i < arguments.length; i++) {
        args.push(arguments[i])
    }

    // 将新数组拆开作为多个参数传入,并调用函数
    let res = _this[key](...args)

    // 删除
    delete _this[key]

    // 返回函数返回值
    return res
}



// 测试代码
let obj = {
    'name': '张三'
}

function showName(first, second, third) {
    console.log(first, second, third);
    console.log(this.name);
}

showName.myCall(obj, 7, 8, 9)

9.实现bind函数

/*   在函数原型上封装myBind函数 , 实现和原生bind函数一样的效果   */

Function.prototype.myBind = function(context) {

    // 存储要转移的目标对象
    let _this = context? Object(context) : window

    // 在转移this的对象上设定一个独一无二的属性 , 并将函数赋值给它
    let key = Symbol('key')
    _this[key] = this

    // 创建函数闭包
    return function() {

        // 将所有参数先拆分开,再添加到新数组中,以此来支持多参数传入以及数组参数传入的需求
        let args = [].concat(...arguments)

        // 调用函数
        let res = _this[key](...args)

        // 删除
        delete _this[key]

        // 返回函数返回值
        return res
    }
}


// 测试代码
let obj = {
    'name': '张三'
}

function showName(first, second, third) {
    console.log(first, second, third);
    console.log(this.name);
}

showName.myBind(obj)([7,8,9])

10.斐波那契数列(递归)

/*   递归实现斐波那契数列   */

function fibonacci1(n) {
    if(n === 1 | n === 2) return 1;

    return fibonacci1(n - 1) + fibonacci1(n - 2)
}

/*   高效地实现斐波那契数列   */
function fibonacci2(n) {
    let arr = [1, 1]

    for(let i = 2; i < n; i++) {
        arr[i] = arr[i - 1] + arr[i - 2]
    }

    return arr[n - 1]
    
}


// 测试两种函数的效率

let start1 = Date.now()
console.log(fibonacci1(40))
let end1 = Date.now()

let start2 = Date.now()
console.log(fibonacci2(40));
let end2 = Date.now()

console.log(`
低效率所用时间:${end1 - start1} ms
高效率所用时间:${end2 - start2} ms
`);

10.发布订阅者模式

/*   简单实现一下发布订阅者模式   */

class Subect {
    constructor(name) {
        this.name = name                // 被观察者的名字
        this.message = '今天是晴天'      // 存放一个值
        this.observers = []             // 存放所有观察者
    }
    on(observer) {
        this.observers.push(observer)
    }
    triggle(data) {
        this.message = data
        this.observers.forEach(o => o.update(data))
    }
}

class Observer{
    constructor(name) {
        this.name = name
    }
    update(newDate) {
        console.log(`我是观察者${this.name}${newDate}`);
    }
}

// 测试代码
let subject = new Subect('message')

let o1 = new Observer('小红')
let o2 = new Observer('小明')

subject.on(o1)
subject.on(o2)

subject.triggle('明天会下雨')

11.数组扁平化(四种方式)

/*  数组扁平化就是将多维数组转成一维数组   */

// 多维数组
let arr = [1, 2, [3, 4, [6, 7]]]

// 第一种方法:利用 flat() 函数
function flatArr1(arr) {
    return arr.flat(Infinity)
}

// 第二种方法: 正则匹配
function flatArr2(arr) {
    return JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']')
}

// 第三种方法:利用 reduce() 遍历所有的元素
function flatArr3(arr) {
    return arr.reduce((i, j) => {
        return i.concat(Array.isArray(j)? flatArr3(j) : j)
    }, [])
}

// 第四种方法:直接使用递归函数
function flatArr4(arr) {
    let new_arr = []

    function innerArr(v) {
        for(let i in v) {
            let item = v[i]

            if(Array.isArray(item)) {
                innerArr(item)
            } else {
                new_arr.push(item)
            }
        }
    }

    innerArr(arr)

    return new_arr
}

// 方法测试
console.log(flatArr1(arr));
console.log(flatArr2(arr));
console.log(flatArr3(arr));
console.log(flatArr4(arr));

12.数组去重(六种方式)

/*   数组去重:让数组所有元素都独一无二,没有重复元素   */

// 创建一个含有重复元素的数组
let arr = [1, 1, 2, 3, 3, 6, 7, 2, 9, 9]

// 第一种方法:利用 Set数据结构 + Array.from() 函数
function removeRepeat1(arr) {
    return Array.from(new Set(arr))
}

// 第二种方法: 利用 Set数据结构 + ...扩展运算符
function removeRepeat2(arr) {
    return [...new Set(arr)]
}

// 第三种方法: 利用 indexOf 函数
function removeRepeat3(arr) {
    let new_arr = []

    for(let i in arr) {

        let item = arr[i]

        if(new_arr.indexOf(item) === -1) {
            new_arr.push(item)
        }
    }

    return new_arr
}

// 第四种方法: 利用 includes 函数
function removeRepeat4(arr) {
    let new_arr = []

    for(let i in arr) {

        let item = arr[i]

        if(!new_arr.includes(item)) {
            new_arr.push(item)
        }
    }

    return new_arr
}

// 第五种方法: 利用 filter 函数
function removeRepeat5(arr) {
    return arr.filter((value, index) => {
        return arr.indexOf(value) === index
    })
}

// 第六种方法: 利用 Map 数据结构
function removeRepeat6(arr) {

    let map = new Map()
    let new_arr = []

    for(let i in arr) {
        
        let item = arr[i]

        if(!map.has(item)) {
            map.set(item, true)
            new_arr.push(item)
        }
    }

    return new_arr
}

// 测试方法
console.log(removeRepeat1(arr));
console.log(removeRepeat2(arr));
console.log(removeRepeat3(arr));
console.log(removeRepeat4(arr));
console.log(removeRepeat5(arr));
console.log(removeRepeat6(arr));

13.获取对象指定层次的所有键值

/*  返回对象obj中第level层次的所有键值  */

let obj = {
    name: {
        a: 1,
        b: 2,
        c: {
            o: 9,
            p: 10,
            q: 11
        }
    },
    age: {
        m: 3,
        n: 4
    }

}

function  key(o,level){
    let arr = []
    function from(ob,l){
       Object.keys(ob).forEach(key=>{
           if(arr[l]) arr[l].push(key);
           else{
               arr[l] = [key]
           }
           if(l!==level-1){
               from(ob[key],l+1)
           }
       })

    }
    from(o,0)
    return arr[level-1]
}
// 测试代码

// 第一层:['name', 'age']
// 第二层:['a', 'b', 'c', 'm', 'n']
// 第三层:['o', 'p', 'q']
console.log(key(obj, 1));
console.log(key(obj, 2));
console.log(key(obj, 3));

14.函数柯里化(三种方式)

/*  函数柯里化:将一个接收多个参数的函数变为接收任意参数返回一个函数的形式,便于之后继续调用,直到最后一次调用,才返回结果值  
    例子:有一个add函数,用于返回所有参数的和,add(1, 2, 3, 4, 5) 返回的是15
          现在要将其变为类似 add(1)(2)(3)(4)(5) 或者 add(1)(2, 3, 4)(5) 的形式,并且功能相同
*/

// 普通的 add() 函数
function add() {
    let sum = 0
    let args = [...arguments]
    for(let i in args) {
        sum += args[i]
    }
    return sum
}

/* 第一种add()函数柯里化方式
   缺点:最后返回的结果是函数类型,但会被隐式转化为字符串,调用toString方法
*/
function add1() {
    // 创建数组,用于存放之后接收的所有参数
    let args = [...arguments]
    
    function getArgs() {
        args.push(...arguments)
        return getArgs
    }

    getArgs.toString = function() {
        return args.reduce((a, b) => {
            return a + b
        })
    }

    return getArgs
}


/* 第二种add()函数柯里化方式
   缺点:需要在最后再自调用一次,即不传参调用表示已没有参数了
*/
function add2() {
    let args = [...arguments]

    return function() {
        if(arguments.length == 0) {
            return args.reduce((a, b) => {
                return a + b
            })
        } else {
            let _args = [...arguments]
            for(let i = 0; i < _args.length; i++) {
                args.push(_args[i])
            }
            return arguments.callee
        }
    }
}

/* 第三种add()函数柯里化方式
   缺点:在刚开始传参之前,设定总共需要传入参数的个数
*/
function curry(length) {

    let args = [...arguments].slice(1)

    return function() {
        args = args.concat([...arguments])
        if(arguments.length < length) {
            return curry.apply(this, [length - arguments.length].concat(args))
        } else {
            return args.reduce((a, b) => a + b)
        }
    }
}

// 测试代码
let res = add(1, 2, 3, 4, 5)
let res1 = add1(1)(2)(3)(4)(5)
let res2 = add2(1)(2, 3, 4)(5)()
let res3 = curry(5)

console.log(res);
console.log(res1);
console.log(res2);
console.log(res3(1)(2, 3)(4)(5));

15.排序

/*冒泡排序 */
//存在循环嵌套,时间复杂度为O(n^2)
Array.prototype.bubbleSort = function(){
     for(let i = 0;i<this.length-1;i++){
        for(let j=0;j<=this.length-1-i;j++){
            if(this[j]<this[j+1]){
                const temp = this[j+1]
                this[j] = this[j+1]
                this[j+1] = temp
            }
        }
    }
}

/*
插入排序:从数组第二系那个开始选取,与前面的值进行比较,
若选取的元素比前面某一个元素小,则查到这个元素前面,较大的
元素放到后面去
*/
Array.prototype.insertionSort = function(){
    for(let i = 0;i<this.length;i++){
        const temp = this[i]
        let j = i
        while(j>0){
          if(this[j-1]>temp){
              this[j] = this[j-1]
          }else{
              break;
          }
          j-=1
        }
        this[j] = temp
    }
}
//时间复杂度同样是O(n^2)


/*
归并排序的思路:分:将数组分成两半,在递归对子数组进行“分”操作
直到分成一个个单独的数
合:把两个数合并成有序数组,再对有序数组进行合并
直到全部子数组合并为一个完整数组
*/
Array.prototype.mergeSort = function(){
     const rec = (arr) =>{
        if(arr.length==1){return arr;}
         const mid = Math.floor(arr.length/2)
         const left = arr.slice(0,mid);
         const right = arr.slice(mid,arr.length)
         const orderLeft =  rec(left)
         const orderRight = rec(right)

        const res= []
        while(orderLeft.length||orderRight.length){
            if(orderLeft.length&&orderRight.length){
                res.push(orderLeft[0]<orderRight[0]?orderLeft.shift():orderRight.shift())
            }else if(orderLeft.length){
                  res.push(orderLeft.shift())
            }else if(orderRight){
                 res.push(orderRight.shift())
        }
     }
      return res
};
    const res = rec(this)
    res.forEach((n,i) => {
         this[i] = n
    });
}
//分的时间复杂度为O(logN)
//合的时间复杂度为O(n)
//因此总时间复杂度为O(n*logN)


/*
  快排思路:分区:从数组中任意选择一个基准,所有比基准小的放在基准前面
  比基准大的放在基准后面
  递归:递归地对基准前后的子数组进行分区
*/
Array.prototype.quickSort = function(){
   const rec = (arr)=>{
       if(arr.length===1) {return arr;}
          const left = []
          const right = []
          const mid = arr[0]
          for(let i = 0;i<this.length;i++){
              if(arr[i]<mid){
                  left.push(arr[i])
              }else{
                  right.push(arr[i])
              }
          }
          return [...rec(left),mid,...rec(right)]
   }
        const res = rec(this)
        //将排好序的结果数组按下标放回原数组,相当于复制
        res.forEach((n,i) => {
             this[i] = n
        });
}
//递归时间复杂度为O(logN)
//分区操作时间复杂度为O(n)
//整体时间复杂度为O(n*logN)


/*
选择排序的思路:找到数组中最小值,选中并将其放置在第一位
接着找到第二小的值放在第二位,以此类推,执行n-1轮
*/
Array.prototype.selectSort = function(){
    for(let i =0;i<this.length-1;i++){
        let indexMin = i
        for(let j = i;j<this.length;j++){
          if(this[j]<this[indexMin]){
              indexMin = j
          }
      }
      if(indexMin!==i){
        const temp = this[0]
        this[0] = this[indexMin]
        this[indexMin] = temp
      }
    }
}
//时间复杂度和冒泡排序一样,性能欠佳

15.数组的map方法

Array.prototype.myMap = function(fn, thisValue) {
     let res = []
     thisValue = thisValue||[]
     let arr = this
     for(let i in arr) {
        res.push(fn(arr[i]))
     }
     return res
 }

16.手写ajax

**步骤

创建 XMLHttpRequest 实例

发出 HTTP 请求

服务器返回 XML 格式的字符串

JS 解析 XML,并更新局部页面

不过随着历史进程的推进,XML 已经被淘汰,取而代之的是 JSON。

了解了属性和方法之后,根据 AJAX 的步骤,手写最简单的 GET 请求。**

function ajax() {
  let xhr = new XMLHttpRequest() //实例化,以调用方法
  xhr.open('get', 'https://www.google.com')  //参数2,url。参数三:异步
  xhr.onreadystatechange = () => {  //每当 readyState 属性改变时,就会调用该函数。
    if (xhr.readyState === 4) {  //XMLHttpRequest 代理当前所处状态。
      if (xhr.status >= 200 && xhr.status < 300) {  //200-300请求成功
        let string = request.responseText
        //JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象
        let object = JSON.parse(string)
      }
    }
  }
  request.send() //用于实际发出 HTTP 请求。不带参数为GET请求
}


promise 版本:
 
 
function ajax(url) {
  const p = new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest()
    xhr.open('get', url)
    xhr.onreadystatechange = () => {
      if (xhr.readyState == 4) {
        if (xhr.status >= 200 && xhr.status <= 300) {
          resolve(JSON.parse(xhr.responseText))
        } else {
          reject('请求出错')
        }
      }
    }
    xhr.send()  //发送hppt请求
  })
  return p
}
let url = '/data.json'
ajax(url).then(res => console.log(res))
  .catch(reason => console.log(reason))
 
 
 

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值