前端面试---JS面试常考(手撕)

前言

解题方法有很多,这里只是参考

1 字符串

1.1 字符串逆序

// 法1
function reverseString1(str) {
  return str.split('').reverse().join('')
}

// 法2
// 对于长字符串不推荐使用这种方法,会比较费内存(字符串不可变)
function reverseString2(str) {
  let res = ''
  for (let i = str.length - 1; i >= 0; i--) {
    res += str.charAt(i)
  }
  return res
}

1.2 统计出现最多字符

function getMaxCount(str) {
  let map = new Map()
  str.split('').forEach((val) => {
    if (map.has(val)) {
      let count = map.get(val)
      map.set(val, count + 1)
    } else {
      map.set(val, 1)
    }
  })
  // 出现次数最多的字符及次数
  let char = null
  let maxCount = 0
  for (let obj of map) {
    // obj其实是个数组,第一个是键(字符),第二个是值(出现次数)
    if (obj[1] > maxCount) {
      char = obj[0]
      maxCount = obj[1]
    }
  }
  return { char, maxCount }
}

1.3 去除字符串中重复的字符

function removeStringChar(str) {
  return [...new Set(str)].join('')
}

1.4 判断一个字符串是否为回文字符串

function isEequStr(str) {
  let temp = str.toLowerCase().split('').reverse().join('')
  return str === temp
}

2 判断为空

2.1 判断变量是否为空对象

function isEmpty(obj) {
    for (let key in obj) {
    // 遍历所有可枚举的属性
      if (obj.hasOwnProperty(key)) {
        return false;
      }
    }
    return true;
}
}

扩展–判断某个属性是否存在于指定对象上

function hasPrototypeProperty(object, name) {
   // 首先判断属性name是否可以在object上可以找到,如果可以找到,那可能是在object对象上,也可能在原型上,然后在判断是否是在object上
   return name in object && object.hasOwnProperty(name)
}

2.2 判断变量是否为空数组

var arr=new Array()
arr instanceof Array && arr.length===0

3 Object类型相关考题

3.1 object.create()函数原理

首先该函数可以创建一个指定原型和指定属性的对象,然后来看下的它的实现原理,

Object.create=function(proto,propertiesObject){
    //省略了其它判断操作
    // 注意这里创建的是一个空的构造函数,而不是空对象(与模拟new操作符的实现要区分开来),创建构造函数这样才能为其添加属性
    function F(){}
    F.prototype=proto;
    if(propertiesObject){ Object.defineProperties(F, propertiesObject)}
    return new F()
}

在该函数中创建了一个空的构造函数,然后将他的prototype属性指向所指定的原型proto,为这个空的构造函数设定属性,最后返回这个空的构造函数创建的一个实例。该实例的__proto__指向它构造函数的原型,而他构造函数的原型又指向了proto,因此完成了create创建对象的功能,用代码表示如下,

function F(){}
F.prototype=proto
var f=new F()
f.__proto__===F.prototype

这里的一个重要应用场景就是继承,具体可以参见继承部分的内容

3.2 模拟new操作符的实现

function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHello = function () {
  console.log('hello')
}
// 模拟new操作符,三件事
function New() {
  var obj = {}
  obj.__proto__ = Person.prototype
  Person.apply(obj, arguments)
  return obj
}

New('zs', 20).sayHello()

4 Array类型相关考题

4.1 判断变量是数组类型还是对象类型

不能使用typeof,因为typeof判断Array是object,没有办法区分变量是数组类型还是对象类型,下面有几种方法可以进行判断:
使用instanceof

  function getType(o) {
    if (o instanceof Array) {
      return "Array";
    } else if (o instanceof Object) {
      return "Object";
    } else {
      return "参数类型不是Array也不是Object";
    }
  }

// 这里注意需要先判断Array,在判断Object,注意顺序,因为数组既是Array,又是object
    var a = [1, 2, 3];
      console.log(a instanceof Array); // true
      console.log(a instanceof Object); // true

使用构造函数

function getType(o) {
  //获取构造函数
  var constructor = o.__proto__.constructor
  if (constructor === Array) {
    return 'Array'
  } else if (constructor === Object) {
    return 'Object'
  } else {
    return '参数类型不是Array也不是Object'
  }
}
var a = [1, 2, 3]
console.log(getType(a))

使用object原型上的toString()方法

  var arr = [1, 2, 3];
      var obj = { userName: "zhangsan" };
      console.log(Object.prototype.toString.call(arr)); //[object Array]
      console.log(Object.prototype.toString.call(obj)); // [object Object]
 console.log(arr.toString()); // 1,2,3,数组上toString()方法有改写

通过Array.isArray()函数来判断

    var arr = [1, 2, 3];
      var obj = { name: "zhangsan" };
      console.log(Array.isArray(1)); //false
      console.log(Array.isArray(arr)); //true
      console.log(Array.isArray(obj)); //false

在这四种方法中,需要注意的地方:

  • 在使用instanceof时,如果一个对象的原型发生了修改,会影响最后的判断
let obj = {}
obj.__proto__ = Array.prototype
console.log(obj instanceof Array)   // true
console.log(Array.isArray(obj))   // false
  • 如果对于一些老的浏览器不支持Array.isArray(),需要自己定义
if(!Array.isArray) {
    Array.isArray = function(args) {
        return Object.prototype.toString.call(args) === '[object Array]'
    }
}
  • Object.prototype.toString方法不能对自己定义的类进行一个精确的判断,可以使用instanceof
function Person(name) {
    this.name = name
}
const p = new Person('zs')
console.log(Object.prototype.toString.call(p))  //[object object]
console.log(p instanceof Person)        // true

4.2 手动实现find()函数

Array.prototype.findTest = function (fn) {
  // 这里的this是隐式绑定,即谁调用就指向谁
  for (var i = 0; i < this.length; i++) {
    if (fn(this[i])) {
      return this[i]
    }
  }
}

// 测试
var arr = [1, 2, 3]
var temp = arr.findTest((item) => {
  return item > 2
})

console.log(temp)

4.3 手动实现filter()函数

Array.prototype.filterTest = function (fn) {
  let newArr = []
  for (let i = 0; i < this.length; i++) {
    let num = this[i]
    if (fn(num)) {
      newArr.push(num)
    }
  }
  return newArr
}

// 测试
var arr = [1, 2, 3]
var temp = arr.filterTest((item) => {
  return item > 1
})

console.log(temp)

4.4 手动实现some()函数

Array.prototype.someTest = function (fn) {
  for (let i = 0; i < this.length; i++) {
    if (fn(this[i])) {
      return true
    }
  }
  return false
}

// 测试
var arr = [1, 2, 3]
var temp = arr.someTest((item) => {
  return item > 1
})

console.log(temp)

4.5 手动实现every()函数

Array.prototype.everyTest = function (fn) {
  for (let i = 0; i < this.length; i++) {
    if (!fn(this[i])) {
      return false
    }
  }
  return true
}

// 测试
var arr = [1, 2, 3]
var temp = arr.everyTest((item) => {
  return item < 5
})

console.log(temp)

4.6 手动实现map()函数

Array.prototype.mapTest = function (fn) {
  var newArr = []
  for (let i = 0; i < this.length; i++) {
    let temp = fn(this[i], i, this)
    newArr.push(temp)
  }
  return newArr
}

// 测试
var arr = [1, 2, 3]
var temp = arr.mapTest((item, index, array) => {
  return item * item
})

console.log(temp)

4.7 手动实现reduce()函数

Array.prototype.reduceTest = function (fn, initValue) {
  // 判断是否传入initValue,若没有传入,accumulator初始值为数组第一个;currentValue值为数组索引为1的数组元素
  let hasInitValue = initValue !== undefined
  var value = hasInitValue ? initValue : this[0]
  for (let i = hasInitValue ? 0 : 1; i < this.length; i++) {
    value = fn(value, this[i], i, this)
  }
  return value
}

let arr = [1, 2, 3]
let temp = arr.reduceTest((accumulator, currentValue) => {
  return accumulator + currentValue
}, 0)
console.log(temp)

4.8 数组去重

// 数组去重
function fn1(arr) {
  let newArr = []
  for (let i = 0; i < arr.length; i++) {
    if (newArr.indexOf(arr[i]) === -1) {
      newArr.push(arr[i])
    }
  }
  return newArr
}

// set数据结构去重
function fn2(arr) {
  return [...new Set(arr)]
}

let arr = [1, 2, 3, 3, 2]
let temp = fn2(arr)
console.log(temp)

4.9 获取数组中最多的元素

function fn(arr) {
  let map = new Map()
  for (let i = 0; i < arr.length; i++) {
    if (map.has(arr[i])) {
      let count = map.get(arr[i])
      map.set(arr[i], count + 1)
    } else {
      map.set(arr[i], 1)
    }
  }
  let maxEle = null
  let maxCount = 0
  for (let item of map) {
    if (item[1] > maxCount) {
      maxEle = item[0]
      maxCount = item[1]
    }
  }
  return '在数组中出现最多的元素是' + maxEle + ',共出现了' + maxCount + '次'
}

let arr = [1, 2, 3, 2, 2]
let temp = fn(arr)
console.log(temp)

4.10 数组方法总结(是否会改变原数组)

  • 会改变原数组:push, shift, unshift, pop, sort, reverse, splice
  • 不会改变原数组:concat, join, reduce, map, forEach, filter, slice, findIndex

5 函数

5.1 手写call()

/**
 * 手写call()函数
 * @param {*} context 让this指向context
 */
Function.prototype.myCall = function (context) {
  // 得到除了context的其他参数
  let args = [...arguments].slice(1)
  // 如果没有传入context,就指向window
  context = context || window
  // 哪个函数调用this就指向那个函数
  context.fn = this
  // 实现函数调用功能,随便改变this指向
  let res = context.fn(...args)
  return res
}

5.2 手写apply()

/**
 * 手写apply()函数
 * @param {*} context context 让this指向context
 */
Function.prototype.myApply = function (context) {
  // 需要考虑如果除了context没有传入其他参数的情况
  let tempArr = arguments[1] || []
  context = context || window
  context.fn = this
  let res = context.fn(...tempArr)
  return res
}

5.3 手写bind()

/**
 * 手写bind()函数
 * @param {*} context context 让this指向context
 */
Function.prototype.myBind = function (context) {
  let args = [...arguments].slice(1)
  let fn = this
  return function () {
    // 返回的函数有可能也有参数,所以要进行处理
    let bindArgs = [...arguments]
    // 拼接的时候也可以用args.concat(bindArgs)
    return fn.apply(context, [...args, ...bindArgs])
  }
}

// 测试
function add(a, b) {
  console.log(this)
  console.log(a + b)
}
function sub(a, b) {
  console.log(a - b)
}

let newFun = add.myBind(sub, 1)
newFun(2)

6 对象

6.1 浅拷贝

法一:创建一个空对象,遍历原始对象,用等于直接赋值即可

var obj = { a: 1, arr: [2, 3], o: { name: 'zs' } }

function shallowCopy(src) {
  let obj = {}
  for (let prop in src) {
    if (src.hasOwnProperty(prop)) {
      obj[prop] = src[prop]
    }
  }
  return obj
}

// 测试
let shallowObj = shallowCopy(obj)

obj.o.name = 'lisi'
console.log(shallowObj.o.name) //lisi,值受到了影响
obj.arr[0] = 20
console.log(shallowObj.arr[0]) //20,值受到了影响
obj.a = 10
console.log(shallowObj.a) // 1,值没有收到影响

法二:通过ES6中的Object.assign()函数来实现

6.2 深拷贝

法一:使用JSON.stringify()JSON.parse()实现
这种方式会有一些问题:第一:无法实现对函数的拷贝;第二:如果对象中存在循环引用,会抛出异常;第三:对象中的构造函数会指向Object,原型链关系被破坏。

法二:递归实现

let map = new Map()
function clone(target) {
  if (typeof target === 'object') {
    // 判断是否为引用数据类型,如果是
    // 考虑是否为数组
    let temp = Array.isArray(target) ? [] : {}
    // 考虑循环引用情况
    if (map.get(target)) {
      return target
    }
    map.set(target, temp)

    for (let prop in target) {
      // 递归赋值
      temp[prop] = clone(target[prop])
    }
    return temp
  } else {
    // 如果不是
    return target
  }
}

// 测试
function Person(name) {
  userName: name
}

let person = new Person('zs')

var obj = {
  userName: 'zhangsan',
  o: person,
  arr: [2, 3],
  sayHi: function () {
    console.log('hi')
  }
}
var res = clone(obj)
// console.log(res)
console.log(res.o.constructor)
console.log(obj.o.constructor)

7 异步编程

7.1 手写promise构造函数

/**
 *
 * @param {*} task是一个函数,它有两个参数,分别是resolve和reject函数
 */
function MyPromise(task) {
  let that = this
  // promise的三个状态: pending(默认)   fulfilled(成功)   rejected(失败)
  that.status = 'Pending'
  that.value = undefined
  // 存放成功和失败的回调函数的数组
  that.onResolvedCallbacks = []
  that.onRejectedCallbacks = []
  function resolve(value) {
    if (that.status === 'Pending') {
      that.status = 'Resolved'
      that.value = value
      that.onResolvedCallbacks.forEach((item) => item(that.value))
    }
  }
  function reject(reason) {
    if (that.status === 'Pending') {
      that.status = 'Rejected'
      that.value = reason
      //状态修改完成后,调用的是then 方法中处理失败的回调函数
      that.onRejectedCallbacks.forEach((item) => item(that.value))
    }
  }
  try {
    task(resolve, reject)
  } catch (err) {
    reject(err)
  }
}

MyPromise.prototype.then = function (onFulfilled, onRejected) {
  let that = this

  // resolve为同步任务,而promise.then为微任务,resolve函数先执行,这时onRejectedCallbacks和onRejectedCallbacks数组中还没有存放回调函数,因此需要在MyPromise.prototype.then中添加判断操作
  // 以下是判断操作
  if (that.status === 'Resolved') {
    onFulfilled(that.value)
  }
  if (that.status === 'Rejected') {
    onRejected(that.value)
  }
  that.onRejectedCallbacks.push(onRejected)
  that.onRejectedCallbacks.push(onFulfilled)
}

// 测试
let myPromise = new MyPromise(function (resolve, reject) {
  // setTimeout是一个异步任务,而promise.then微任务,微任务会先执行,所以当setTimeout执行的时候,onRejectedCallbacks和onRejectedCallbacks数组中已经存放了回调函数
  // setTimeout(function () {
  //   let num = Math.random()
  //   if (num > 0.3) {
  //     resolve('成功了')
  //   } else {
  //     reject('失败了')
  //   }
  // }, 1000)

  // resolve为同步任务,而promise.then为微任务,resolve函数先执行,这时onRejectedCallbacks和onRejectedCallbacks数组中还没有存放回调函数,因此需要在MyPromise.prototype.then中添加判断操作
  resolve('成功了')
})

myPromise.then(
  function (value) {
    console.log(value)
  },
  function (reason) {
    console.log(reason)
  }
)

7.2 模拟实现Promise.all()

function myAll(arr) {
  let res = []
  let index = 0
  return new Promise((resolve, reject) => {
    arr.forEach((cur, i) => {
      // 数组可能有些元素不是promise对象,所以先用Promise.resolve()
      Promise.resolve(cur).then(
        (val) => {
          res[i] = val
          index++
          if (index === arr.length) {
            resolve(res)
          }
        },
        (err) => reject(err)
      )
    })
  })
}

7.3 模拟实现Promise.race()

function myRace(arr) {
  return new Promise((resolve, reject) => {
    for (let item of arr) {
      Promise.resolve(item).then(
        (val) => resolve(val),
        (err) => reject(err)
      )
    }
  })
}

8 排序算法

常用排序算法汇总

9 防抖节流

防抖与节流

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值