前端面试常见问题

175 阅读7分钟

HTML

CSS

JS

类型转换

转Boolean类型

在条件判断中,除了 undefined, null, false, NaN, ‘’, 0, -0,其他所有值都转为 true,包括所有对象。 只是在条件判断中 undefined, null, false, NaN, ‘’, 0, -0可以当falses使用,并不意味着*==false为true(下面会详细讲解)

对象转基本类型

  • 对象在转基本类型的时候,首先会调用valueOf方法。

原型与原型链

写一个原型链继承的例子

function Elem (id) {
  this.elem = document.getElementById('id')
}

Elem.prototype.html = function (val) {
  var elem = this.elem
  if (val) {
    elem.html = val
    return this
  } else {
    return elem.html
  }
}

Elem.propotype.on = function (type, fn) {
  var elem = this.elem
  elem.addEventListener(type, fn)
  return this
}

var div1 = new Elem('div1') // div1._proto_ = Elem.prototype

div1.html('插入文本')

div1.on('click', function () {
  console.log('绑定事件')
})

// 由于每一个原型方法中都返回了this 故可进行原型操作如下:
div1.html('插入文本').on('click', funtion() {
	console.log('绑定事件')
}).html() // 插入文本

描述new一个对象的过程

  • 创建了一个新对象
  • 把obj._proto_指向 Obj.prototype实现继承
  • 执行构造函数、传递参数、改变this指向
  • 返回obj给新变量

example:

// 原生 new
function Dog () {
  this.name = name
}
Dog.prototype.bark = function () {
  console.log('wangwang')
}
Dog.prototype.sayName = function () {
  console.log('my name is' + this.name)
}

var dog = new Dog('dog')
dog.bark() // wangwang
dog.sayName() // my name is dog

//  自行编写一个简单的new
var _new = function () {
  let constructor = Array.prototype.shift.call(arguments) // 取第一个参数及构造函数
  let obj = new Object()
  let args = arguments
  obj._proto_ = constructor.prototype
  constructor.apply(obj, args)
  return obj
}

var cat = _new(Dog, 'cat')
cat.bark()
cat.sayName()

原型的规则

  • 所有的引用类型(数组、对象、函数),都具有对象特性,且具有自由可扩展属性
  • 所有的引用类型(数组、对象、函数),都有一个_proto_(隐式原型)属性,属性值是一个普通的对象(浏览器自己创建)
  • 所有的函数,都有一个prototype(显示原型)属性,属性值也是一个普通的对象(浏览器自己创建)
  • 所有的引用类型(数组、对象、函数),_proto_属性值指向它的构造函数“prototype”属性值
  • 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去他的——proto_(即它的构造函数的prototype)中寻找

instanceof

instanceof用于判断属于哪个构造函数的方法

let arr = []
arr instanceof Array // true
arr instanceof Object // true 
// 由于所有的引用类型均有隐式原型且指向构造函数的显示原型,隐式原型和显示原型又均为对象,原型链可以一直指向Object.prototype._proto_ => null

作用域与闭包

闭包在实际开发中主要应用于收敛权限、封装变量 在了解这部分之前先了解一下 this、执行上下文、执行站(内存堆、调用栈)

防抖与节流

防抖和节流的作用都是防止函数多次调用。 区别在于:假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况只会调用一次,而节流的情况每个一定时间(参数wait)调用函数

Eventloop

不同的任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务(microtask) 和 宏任务(macrotask)。在 ES6 规范中,microtask 称为 jobs,macrotask 称为 task。

console.log('script start')

setTimeout(function() {
  console.log('setTimeout')
}, 0)

new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
  })

console.log('script end')
// script start => Promise => script end => promise1 => promise2 => setTimeout
  • 微任务包括 process.nextTick ,promise ,Object.observe ,MutationObserver
  • 宏任务包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering

很多人有个误区,认为微任务快于宏任务,其实是错误的。因为宏任务中包括了 script ,浏览器会先执行一个宏任务,接下来有异步代码的话就先执行微任务。

所以正确的一次 Event loop 顺序是这样的

  • (1) 执行同步代码,这属于宏任务
  • (2) 执行栈为空,查询是否有微任务需要执行
  • (3) 执行所有微任务
  • (4) 必要的话渲染 UI
  • (5) 然后开始下一轮 Event loop,执行宏任务中的异步代码

websocket

websocket原理

AJAX

Ajax的全称是Asynchronous JavaScript and XML (异步的JavaScript 和 XML),有别于传统web开发中采用的同步方式。

关于同步和异步

  • 异步传输是面向字符的传输,单位是字符。
  • 同步传输是面向比特的传输,单位是帧,传输时客户端和服务器端的时间一致。

ajax所包含的技术

  • 使用CSS和XHTML来表示
  • 使用DOM模型来交互和动态显示
  • 使用XMLHttpRequest来和服务器进行一步通信
  • 使用javascript来绑定和调用

ajax相当于在用户和服务器之间加了一个中间层,使用户操作与服务器响应异步化。并不是所有的用户请求都提交给服务器,类似于一些数据验证和数据处理等都交给Ajax引擎自己来做,只有确定需要从服务器读取新数据时再由Ajax引擎代为向服务器提交请求。

Ajax的原理简单来说通过XmlHttpRequest对象来向服务器发送异步请求,从服务器活的数据,然后用js来操作DOM进行页面更新。XMLHttpRequest是ajax的核心机制。

Example:

// 1、创建XMLHttpRequest对象
var xhr = new XMLHttpRquest()
// 2、设置请求方式
xhr.open("GET", "/api", true) // 异步
// 3、调用回调函数
xhr.onreadystatechange = function () {
  if (xhr.readyState == 4) {
    if (xhr.status == 200) {
      console.log(xhr.reponseText)
    }
  }
}
// 4、设置发送请求的内容和发送报文,紧接着发送请求
xhr.send() // POST 请求数据是放在send里面
  • 状态码说明
  • (1)readyState 0: (尚未初始化)还没有调用send()方法 1: (载入)已调用send()方法,正在发送请求 2: (载入完成)send()方法执行完成,已经接收到全部响应内容 3: (交互)正在解析响应内容 4: (完成) 响应内容解析完成,可以在客户端调用
  • (2)status 2XX、3XX、4XX、5XX

ajax的优缺点

  • 优点:
  • (1)无刷新更新数据: AJAX最大的有点就是能在不刷新整个页面的前提下与服务器通信并维护数据。使Web应用程序更为迅捷的响应用户交互,并避免在网上发送没有用的信息,减少用户等待时间,增强用户体验。
  • (2)异步与服务器通信
  • (3)前端和后端负载平衡: AJAX可以把以前一些服务器负担的工作转嫁到客户端,利用客户端闲置的能力来处理,减轻服务器和贷款的负担,节约空间和带宽租用成本。**AJAX的原则是“按需取数据”
  • (4)基于标准被广泛支持
  • (5)界面与应用分离 缺点:
  • (1)AJAX干掉了Back和History功能,即对浏览器机制的破坏:在动态更新页面的情况下,用户无法回到前一个页面状态,因为浏览器仅能计息历史记录中的静态页面。一个完整读入的页面与一个一杯动态修改过的页面之间的差别非常微妙。
  • (2)AJAX安全问题
  • (3)破坏程序的异常处理机制

Promise(Promise 是ES6新增的语法,解决了回调地狱的问题)

可以把Promise看成一个状态机。初始值是pending, 可以通过函数resolve 和 reject, 将状态转变为resolved或者rejected状态,状态一旦改变不能再次变化。

then 函数会返回一个Promise实例,并且该返回值是一个新的实例而不是之前的实例。因为Promise规范规定除了pending状态,其他状态是不可改变的,如果返回的是一个相同实例的话,多个then调用就是去意义了。

一个简易版手写Promise:

const PENDING = "pending"
const RESOLVED = "resolved"
const REJECTED = "rejected"
function MyPromise (fn) {
  let that = this
  // promise 的初始值是pending
  that.curentState = PENDING
  // 用于保存resolve 或者 reject 中传入的值
  that.value = ''
  // 用于保存then中的回调
  that.resolvedCallbacks = []
  that.rejectedCallbacks = []

  that.resolve = function (value) {
    if (value instanceof MyPromise) {
      // 如果value 是个Promise, 递归执行
      return value.then(that.resolve, that.reject)
    }
    // 异步执行 保证执行顺序
    setTimeout(() => {
      if (that.currentState  === PENDING) {
        that.currentState = REJECTED
        that.value = value
        that.resolvedCallbacks.forEach(fn => fn())
      }
    })
  }

  that.reject = function (reson) {
    setTimeout(() => {
      if (that.currentState === PENDING) {
        that.currentState = REJECTED
        that.value = reason
        that.rejectedCallbacks.forEach(fn => fn())
      }
    })
  }

  // 用于解决new Promise(() => throw Error('error'))
  try {
    fn(that.resolve, that.reject)
  } catch (e) {
    that.reject(e)
  }
}

MyPromoise.prototype.then = function (onFulfilled, onRejected) {
  var self = this
  // 必须返回一个新的Promise
  var promiseN
  // onFulfilled onRejected都为可选参数
  // 如果类型不是函数需要忽略, 同是也实现了透传
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
  onRejected = typeof onRejected === 'function' ? onRejected : r => throw r

  if (self.currentState === RESOLVED || self.currentState === REJECTED) {
    return (promiseN = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        try {
          resolve(self.value)
        } catch (reason) {
          reject(reason)
        }
      })
    }))
  }

  if (self.currentState === PENDING) {
    return (promiseN = new MyPromise((resolve, reject) => {
      self.resolvedCallbacks.push(() => {
        try {
          resolve(self.value)
        } catch (r) {
          reject(r)
        }
      })

      self.rejectedCallbacks.push(() => {
        try {
          resolve(self.value)
        } catch (r) {
          reject(r)
        }
      })
    }))
  }
}

// flatten

const flatten = array => array.reduce((acc, cur) => (Array.isArray(cur) ? [...acc, ...flatten(cur)] : [...acc, cur]), [])

const flatten = function (arr) {
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr)
    }
    return arr
}

// 递归实现
function flatten(arr) {
  let arrs = [];
  arr.map(item => {
    if(Array.isArray(item)){
      arrs.push(... flatten(item))
    } else {
      arrs.push(item)
    }
  })
  return arrs
}
// 迭代实现
function flatten(arr) {
  let arrs =[...arr]
  let newArr = [];
  while (arrs.length){
    let item = arrs.shift()
    if(Array.isArray(item)){
      arrs.unshift(...item)
    }else {
      newArr.push(item)
    }
  }
  return newArr
}
// 字符串实现
arr.join(',').split(',').map(item => Number(item)))