【前端面试题】JavaScript篇

467 阅读4分钟

变量和运算

typeof运算符可以识别哪些类型?

  1. 值类型;
  2. 函数(function);
  3. 引用类型(object);
    • 注意:typeof null === 'object'

Map和Object的区别

  • Map可以以任意类型为key,Object只能以字符串为key;
  • Map是有序结构,Object是无序结构;

手写一下JavaScript深拷贝

function deepClone(obj) {
  if(typeof obj !== 'object' || obj == null) {
    return obj;
  }
  let cloneObj;
  if(obj instanceof Array) {
    cloneObj = []
  } else if(obj instanceof Object) {
    cloneObj = {}
  } else {
    throw new Error("Undefined Type!");    
  }
  Object.keys(obj).map(key => cloneObj[key] = deepClone(obj[key]));
  return cloneObj;
}

手写一个函数isEqual,比较两个对象是否全相等

function isObject(obj) {
  return typeof obj === "object" && obj !== null
}

function isEqual(obj1, obj2) {
  if(obj1 === obj2) return true
  if(!isObject(obj1) || !isObject(obj2)) return false
  const obj1Keys = Object.keys(obj1)
  const obj2Keys = Object.keys(obj2)
  if(obj1Keys.length !== obj2Keys.length) return false
  for(const key in obj1) {
    if(!isEqual(obj1[key], obj2[key])) return false
  }
  return true
}

常见的隐式类型转换都有哪些?

  • 字符串拼接:10 + '10' = '1010'
  • ==运算符:null == undefined,可用== null 代替 === null && === undefined
  • if语句:if中判断的依据是turelyfalsely,即!!(x)的真值。注意:!!('') === false;

for in与for of有什么区别?

  • for in仅适用于可枚举对象(key值);
  • for of仅适用于可迭代对象(value值);
    • 可迭代对象由一个内建的Symbol.iterator方法控制,循环启动时必须返回一个迭代器,此后通过迭代器中返回的next()方法获取下一个值。
  • for await of仅适用于遍历Promise(功能上相当于Promise.all);

原型和原型链

如何理解JavaScript原型?

  • 每个class都有显式原型prototype
  • 每个实例都有隐式原型__proto__
  • 实例的隐式原型指向对应类的显示原型;

实例获取属性或方法时,遵循的顺序是什么?

  1. 先在自身属性和方法中寻找;
  2. 当(1)寻找失败时,从隐式原型中寻找需要的属性和方法;
  3. 重复上述步骤,直到隐式原型的值为null

作用域和闭包

什么是闭包?说说你对闭包的理解?

【名词解释】闭包 - 掘金 (juejin.cn)

所有自由变量的查找,均从函数定义的地方开始,向上级作用域查找。

this如何取值?

this的取值是在运行时确定的,不是在定义时确定的

  • 箭头函数没有this,箭头函数中的this指向父级作用域;
  • 通过call可以改变自身的this指向,通过bind可以改变复制的this指向;

哪里不能使用箭头函数?

  • 箭头函数的缺陷:
    • 没有arguments
    • 无法通过call, bind, apply改变this(箭头函数的this为父级元素);
  • 不适用场景:
    • 对象方法;
    • 原型方法;
    • 构造函数;
    • 动态上下文中的回调函数;

异步

说说你对Promise的理解?

理解Promise - 掘金 (juejin.cn)

说说你对事件循环的认识?

消息队列与事件循环 - 掘金 (juejin.cn)

手写一下Promise

Promise类实现

class MyPromise{
  state = "pending"
  value
  reason
  resolveCallBacks = []
  rejectCallBacks = []
  constructor(fn) {
    const resolveHandler = (value) => {
      if(this.state === "pending") {
        this.state = "fulfilled"
        this.value = value
        this.resolveCallBacks.forEach(fn => fn(this.value))
      }
    }
    const rejectHandler = (reason) => {
      if(this.state === "pending") {
        this.state = "rejected"
        this.reason = reason
        this.rejectCallBacks.forEach(fn => fn(this.reason))
      }
    }
    try {
      fn(resolveHandler, rejectHandler)
    } catch (error) {
      rejectHandler(error)
    }
  }

  then(fn1, fn2) {
    fn1 = typeof fn1 === "function" ? fn1 : (val) => val
    fn2 = typeof fn2 === "function" ? fn2 : (err) => err
    if(this.state === "pending") {
      return new MyPromise((resolve, reject) => {
        this.resolveCallBacks.push(() => {
          try {
            const newValue = fn1(this.value)
            resolve(newValue)
          } catch (e) {
            reject(e)
          }
        })
        this.rejectCallBacks.push(() => {
          try {
            const newReason = fn2(this.reason)
            resolve(newReason)
          } catch (e) {
            reject(e)
          }
        })
      })
    }
    if(this.state === "fulfilled") {
      return new MyPromise((resolve, reject) => {
        try {
          const newValue = fn1(this.value)
          resolve(newValue)
        } catch (error) {
          reject(error)
        }
      })
    }
    if(this.state === "rejected") {
      return new MyPromise((resolve, reject) => {
        try {
          const newReason = fn2(this.reason)
          resolve(newReason)
        } catch (error) {
          reject(newReason)
        }
      })
    } 
  }
  catch(fn) {
    return this.then(null, fn) 
  }
}

Promise静态方法实现

MyPromise.resolve = function(value) {
  return new MyPromise((resolve, reject) => resolve(value))
}

MyPromise.reject = function(reason) {
  return new MyPromise((resolve, reject) => reject(reason))
}

MyPromise.all = function(promiseList = []) {
  return new MyPromise((resolve, reject) => {
    const length = promiseList.length
    const result = []
    let resolveCount = 0
    promiseList.forEach((p, i) => {
      p.then(data => {
        result[i] = data
        resolveCount += 1
        if(resolveCount === length) {
          resolve(result)
        }
      }).catch(err => reject(err))
    })
  })
}

MyPromise.any = function(promiseList = []) {
  return new MyPromise((resolve, reject) => {
    let resolved = false
    promiseList.forEach(p => {
      p.then(data => {
        if(!resolved) {
          resolved = true
          resolve(data)
        }
      }).catch(err => reject(err))
    })
  })
}

DOM

说说你对DOM的理解?

理解DOM - 掘金 (juejin.cn)

手写一下防抖

用户输入结束或暂停时,再触发事件,使用装饰器模式。

function debounce(fn, delay = 500) {
  let timer = null
  return function() {
    if(timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments)
      timer = null
    }, delay)
  }
}

手写一下节流

将用户的高频输入等时间间隔触发,使用装饰器模式。

function throttle(fn, delay = 100) {
  let timer = null
  return function() {
    if(timer) {
      return
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments)
      timer = null
    }, delay)
  }
}

Ajax

什么是同源策略?

Ajax请求时,浏览器要求当前页面与服务端必须同源,即协议,域名,端口必须一致。

  • 图片,CSS,JavaScript可以不同源;
  • 所有的跨域,都需要获得服务端的允许和配合。

跨域的常见方式有什么?

  • JSONP:服务端动态拼接数据,利用<script>绕过跨域限制;
  • CORS:服务端设置http-header;

GET和POST的区别有什么?

  • GET:
    • 查询操作,拼接在url上;
    • 有缓存;
    • 受url长度限制;
  • POST:
    • 提交操作,放在请求体内;
    • 无缓存;
    • 无长度限制;

存储

localStorage sessionStorage和cookie的区别?

  • cookie:最多存4KB,请求时会被发送到服务端;
  • localStorage:最多存5MB,不会随请求发送到服务端,永久存储;
  • sessionStorage:最多存5MB,不会随请求发送到服务端,关闭页面即清空;