前言

本文针对目前常见的面试题,仅提供了相应的核心原理及思路,部分边界细节未处理。后续会持续更新,希望对你有所帮助。

1. 实现一个call函数

  1. // 思路:将要改变this指向的方法挂到目标this上执行并返回
  2. Function.prototype.mycall = function (context) {
  3. if (typeof this !== 'function') {
  4. throw new TypeError('not funciton')
  5. }
  6. context = context || window
  7. context.fn = this
  8. let arg = [...arguments].slice(1)
  9. let result = context.fn(...arg)
  10. delete context.fn
  11. return result
  12. }
  13. 复制代码

2. 实现一个apply函数

  1. // 思路:将要改变this指向的方法挂到目标this上执行并返回
  2. Function.prototype.myapply = function (context) {
  3. if (typeof this !== 'function') {
  4. throw new TypeError('not funciton')
  5. }
  6. context = context || window
  7. context.fn = this
  8. let result
  9. if (arguments[1]) {
  10. result = context.fn(...arguments[1])
  11. } else {
  12. result = context.fn()
  13. }
  14. delete context.fn
  15. return result
  16. }
  17. 复制代码

3. 实现一个bind函数

  1. // 思路:类似call,但返回的是函数
  2. Function.prototype.mybind = function (context) {
  3. if (typeof this !== 'function') {
  4. throw new TypeError('Error')
  5. }
  6. let _this = this
  7. let arg = [...arguments].slice(1)
  8. return function F() {
  9. // 处理函数使用new的情况
  10. if (this instanceof F) {
  11. return new _this(...arg, ...arguments)
  12. } else {
  13. return _this.apply(context, arg.concat(...arguments))
  14. }
  15. }
  16. }
  17. 复制代码

4. instanceof的原理

  1. // 思路:右边变量的原型存在于左边变量的原型链上
  2. function instanceOf(left, right) {
  3. let leftValue = left.__proto__
  4. let rightValue = right.prototype
  5. while (true) {
  6. if (leftValue === null) {
  7. return false
  8. }
  9. if (leftValue === rightValue) {
  10. return true
  11. }
  12. leftValue = leftValue.__proto__
  13. }
  14. }
  15. 复制代码

5. Object.create的基本实现原理

  1. // 思路:将传入的对象作为原型
  2. function create(obj) {
  3. function F() {}
  4. F.prototype = obj
  5. return new F()
  6. }
  7. 复制代码

6. new本质

  1. function myNew (fun) {
  2. return function () {
  3. // 创建一个新对象且将其隐式原型指向构造函数原型
  4. let obj = {
  5. __proto__ : fun.prototype
  6. }
  7. // 执行构造函数
  8. fun.call(obj, ...arguments)
  9. // 返回该对象
  10. return obj
  11. }
  12. }
  13. function person(name, age) {
  14. this.name = name
  15. this.age = age
  16. }
  17. let obj = myNew(person)('chen', 18) // {name: "chen", age: 18}
  18. 复制代码

7. 实现一个基本的Promise

  1. // 未添加异步处理等其他边界情况
  2. // ①自动执行函数,②三个状态,③then
  3. class Promise {
  4. constructor (fn) {
  5. // 三个状态
  6. this.state = 'pending'
  7. this.value = undefined
  8. this.reason = undefined
  9. let resolve = value => {
  10. if (this.state === 'pending') {
  11. this.state = 'fulfilled'
  12. this.value = value
  13. }
  14. }
  15. let reject = value => {
  16. if (this.state === 'pending') {
  17. this.state = 'rejected'
  18. this.reason = value
  19. }
  20. }
  21. // 自动执行函数
  22. try {
  23. fn(resolve, reject)
  24. } catch (e) {
  25. reject(e)
  26. }
  27. }
  28. // then
  29. then(onFulfilled, onRejected) {
  30. switch (this.state) {
  31. case 'fulfilled':
  32. onFulfilled()
  33. break
  34. case 'rejected':
  35. onRejected()
  36. break
  37. default:
  38. }
  39. }
  40. }

8. 实现浅拷贝

  1. // 1. ...实现
  2. let copy1 = {...{x:1}}
  3. // 2. Object.assign实现
  4. let copy2 = Object.assign({}, {x:1})

9. 实现一个基本的深拷贝

  1. // 1. JOSN.stringify()/JSON.parse()
  2. let obj = {a: 1, b: {x: 3}}
  3. JSON.parse(JSON.stringify(obj))
  4. // 2. 递归拷贝
  5. function deepClone(obj) {
  6. let copy = obj instanceof Array ? [] : {}
  7. for (let i in obj) {
  8. if (obj.hasOwnProperty(i)) {
  9. copy[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
  10. }
  11. }
  12. return copy
  13. }

10. 使用setTimeout模拟setInterval

  1. // 可避免setInterval因执行时间导致的间隔执行时间不一致
  2. setTimeout (function () {
  3. // do something
  4. setTimeout (arguments.callee, 500)
  5. }, 500)

11. js实现一个继承方法

  1. // 借用构造函数继承实例属性
  2. function Child () {
  3. Parent.call(this)
  4. }
  5. // 寄生继承原型属性
  6. (function () {
  7. let Super = function () {}
  8. Super.prototype = Parent.prototype
  9. Child.prototype = new Super()
  10. })()

12. 实现一个基本的Event Bus

  1. // 组件通信,一个触发与监听的过程
  2. class EventEmitter {
  3. constructor () {
  4. // 存储事件
  5. this.events = this.events || new Map()
  6. }
  7. // 监听事件
  8. addListener (type, fn) {
  9. if (!this.events.get(type)) {
  10. this.events.set(type, fn)
  11. }
  12. }
  13. // 触发事件
  14. emit (type) {
  15. let handle = this.events.get(type)
  16. handle.apply(this, [...arguments].slice(1))
  17. }
  18. }
  19. // 测试
  20. let emitter = new EventEmitter()
  21. // 监听事件
  22. emitter.addListener('ages', age => {
  23. console.log(age)
  24. })
  25. // 触发事件
  26. emitter.emit('ages', 18) // 18
  27. 复制代码

13. 实现一个双向数据绑定

  1. let obj = {}
  2. let input = document.getElementById('input')
  3. let span = document.getElementById('span')
  4. // 数据劫持
  5. Object.defineProperty(obj, 'text', {
  6. configurable: true,
  7. enumerable: true,
  8. get() {
  9. console.log('获取数据了')
  10. },
  11. set(newVal) {
  12. console.log('数据更新了')
  13. input.value = newVal
  14. span.innerHTML = newVal
  15. }
  16. })
  17. // 输入监听
  18. input.addEventListener('keyup', function(e) {
  19. obj.text = e.target.value
  20. })
  21. 复制代码

完整实现可前往之前写的:这应该是最详细的响应式系统讲解了

14. 实现一个简单路由

  1. // hash路由
  2. class Route{
  3. constructor(){
  4. // 路由存储对象
  5. this.routes = {}
  6. // 当前hash
  7. this.currentHash = ''
  8. // 绑定this,避免监听时this指向改变
  9. this.freshRoute = this.freshRoute.bind(this)
  10. // 监听
  11. window.addEventListener('load', this.freshRoute, false)
  12. window.addEventListener('hashchange', this.freshRoute, false)
  13. }
  14. // 存储
  15. storeRoute (path, cb) {
  16. this.routes[path] = cb || function () {}
  17. }
  18. // 更新
  19. freshRoute () {
  20. this.currentHash = location.hash.slice(1) || '/'
  21. this.routes[this.currentHash]()
  22. }
  23. }
  24. 复制代码

15. 实现懒加载

  1. <ul>
  2. <li><img src="./imgs/default.png" data="./imgs/1.png" alt=""></li>
  3. <li><img src="./imgs/default.png" data="./imgs/2.png" alt=""></li>
  4. <li><img src="./imgs/default.png" data="./imgs/3.png" alt=""></li>
  5. <li><img src="./imgs/default.png" data="./imgs/4.png" alt=""></li>
  6. <li><img src="./imgs/default.png" data="./imgs/5.png" alt=""></li>
  7. <li><img src="./imgs/default.png" data="./imgs/6.png" alt=""></li>
  8. <li><img src="./imgs/default.png" data="./imgs/7.png" alt=""></li>
  9. <li><img src="./imgs/default.png" data="./imgs/8.png" alt=""></li>
  10. <li><img src="./imgs/default.png" data="./imgs/9.png" alt=""></li>
  11. <li><img src="./imgs/default.png" data="./imgs/10.png" alt=""></li>
  12. </ul>
  13. 复制代码
  1. let imgs = document.querySelectorAll('img')
  2. // 可视区高度
  3. let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
  4. function lazyLoad () {
  5. // 滚动卷去的高度
  6. let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
  7. for (let i = 0; i < imgs.length; i ++) {
  8. // 图片在可视区冒出的高度
  9. let x = clientHeight + scrollTop - imgs[i].offsetTop
  10. // 图片在可视区内
  11. if (x > 0 && x < clientHeight+imgs[i].height) {
  12. imgs[i].src = imgs[i].getAttribute('data')
  13. }
  14. }
  15. }
  16. // addEventListener('scroll', lazyLoad) or setInterval(lazyLoad, 1000)
  17. 复制代码

16. rem基本设置

  1. // 原始配置
  2. function setRem () {
  3. let doc = document.documentElement
  4. let width = doc.getBoundingClientRect().width
  5. let rem = width / 75
  6. doc.style.fontSize = rem + 'px'
  7. }
  8. // 监听窗口变化
  9. addEventListener("resize", setRem)
  10. 复制代码

17. 手写实现AJAX

  1. // 1. 简单流程
  2. // 实例化
  3. let xhr = new XMLHttpRequest()
  4. // 初始化
  5. xhr.open(method, url, async)
  6. // 发送请求
  7. xhr.send(data)
  8. // 设置状态变化回调处理请求结果
  9. xhr.onreadystatechange = () => {
  10. if (xhr.readyStatus === 4 && xhr.status === 200) {
  11. console.log(xhr.responseText)
  12. }
  13. }
  14. // 2. 基于promise实现
  15. function ajax (options) {
  16. // 请求地址
  17. const url = options.url
  18. // 请求方法
  19. const method = options.method.toLocaleLowerCase() || 'get'
  20. // 默认为异步true
  21. const async = options.async
  22. // 请求参数
  23. const data = options.data
  24. // 实例化
  25. const xhr = new XMLHttpRequest()
  26. // 请求超时
  27. if (options.timeout && options.timeout > 0) {
  28. xhr.timeout = options.timeout
  29. }
  30. // 返回一个Promise实例
  31. return new Promise ((resolve, reject) => {
  32. xhr.ontimeout = () => reject && reject('请求超时')
  33. // 监听状态变化回调
  34. xhr.onreadystatechange = () => {
  35. if (xhr.readyState == 4) {
  36. // 200-300 之间表示请求成功,304资源未变,取缓存
  37. if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
  38. resolve && resolve(xhr.responseText)
  39. } else {
  40. reject && reject()
  41. }
  42. }
  43. }
  44. // 错误回调
  45. xhr.onerror = err => reject && reject(err)
  46. let paramArr = []
  47. let encodeData
  48. // 处理请求参数
  49. if (data instanceof Object) {
  50. for (let key in data) {
  51. // 参数拼接需要通过 encodeURIComponent 进行编码
  52. paramArr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
  53. }
  54. encodeData = paramArr.join('&')
  55. }
  56. // get请求拼接参数
  57. if (method === 'get') {
  58. // 检测url中是否已存在 ? 及其位置
  59. const index = url.indexOf('?')
  60. if (index === -1) url += '?'
  61. else if (index !== url.length -1) url += '&'
  62. // 拼接url
  63. url += encodeData
  64. }
  65. // 初始化
  66. xhr.open(method, url, async)
  67. // 发送请求
  68. if (method === 'get') xhr.send(null)
  69. else {
  70. // post 方式需要设置请求头
  71. xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8')
  72. xhr.send(encodeData)
  73. }
  74. })
  75. }
  76. 复制代码

18. 实现拖拽

  1. window.onload = function () {
  2. // drag处于绝对定位状态
  3. let drag = document.getElementById('box')
  4. drag.onmousedown = function(e) {
  5. var e = e || window.event
  6. // 鼠标与拖拽元素边界的距离 = 鼠标与可视区边界的距离 - 拖拽元素与边界的距离
  7. let diffX = e.clientX - drag.offsetLeft
  8. let diffY = e.clientY - drag.offsetTop
  9. drag.onmousemove = function (e) {
  10. // 拖拽元素移动的距离 = 鼠标与可视区边界的距离 - 鼠标与拖拽元素边界的距离
  11. let left = e.clientX - diffX
  12. let top = e.clientY - diffY
  13. // 避免拖拽出可视区
  14. if (left < 0) {
  15. left = 0
  16. } else if (left > window.innerWidth - drag.offsetWidth) {
  17. left = window.innerWidth - drag.offsetWidth
  18. }
  19. if (top < 0) {
  20. top = 0
  21. } else if (top > window.innerHeight - drag.offsetHeight) {
  22. top = window.innerHeight - drag.offsetHeight
  23. }
  24. drag.style.left = left + 'px'
  25. drag.style.top = top + 'px'
  26. }
  27. drag.onmouseup = function (e) {
  28. this.onmousemove = null
  29. this.onmouseup = null
  30. }
  31. }
  32. }
  33. 复制代码

19. 实现一个节流函数

  1. // 思路:在规定时间内只触发一次
  2. function throttle (fn, delay) {
  3. // 利用闭包保存时间
  4. let prev = Date.now()
  5. return function () {
  6. let context = this
  7. let arg = arguments
  8. let now = Date.now()
  9. if (now - prev >= delay) {
  10. fn.apply(context, arg)
  11. prev = Date.now()
  12. }
  13. }
  14. }
  15. function fn () {
  16. console.log('节流')
  17. }
  18. addEventListener('scroll', throttle(fn, 1000))
  19. 复制代码

20. 实现一个防抖函数

  1. // 思路:在规定时间内未触发第二次,则执行
  2. function debounce (fn, delay) {
  3. // 利用闭包保存定时器
  4. let timer = null
  5. return function () {
  6. let context = this
  7. let arg = arguments
  8. // 在规定时间内再次触发会先清除定时器后再重设定时器
  9. clearTimeout(timer)
  10. timer = setTimeout(function () {
  11. fn.apply(context, arg)
  12. }, delay)
  13. }
  14. }
  15. function fn () {
  16. console.log('防抖')
  17. }
  18. addEventListener('scroll', debounce(fn, 1000))
  19. 复制代码

原文