20道JS原理题助你面试一臂之力!

论坛 期权论坛 期权     
前端大学   2019-7-29 01:09   3058   0
[h1]前言[/h1]
本文针对目前常见的面试题,实现了相应方法的核心原理,部分边界细节未处理。后续也会持续更新,希望对你有所帮助。

[h1]1. 实现一个call函数[/h1]
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]// 将要改变this指向的方法挂到目标this上执行并返回
复制代码
  1. Function.prototype.mycall = function (context) {
复制代码
  1.   if (typeof this !== 'function') {
复制代码
  1.     throw new TypeError('not funciton')
复制代码
  1.   }
复制代码
  1.   context = context || window
复制代码
  1.   context.fn = this
复制代码
  1.   let arg = [...arguments].slice(1)
复制代码
  1.   let result = context.fn(...arg)
复制代码
  1.   delete context.fn
复制代码
  1.   return result
复制代码
  1. }
复制代码
[h1]2. 实现一个apply函数[/h1]
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]Function.prototype.myapply = function (context) {
复制代码
  1.   if (typeof this !== 'function') {
复制代码
  1.     throw new TypeError('not funciton')
复制代码
  1.   }
复制代码
  1.   context = context || window
复制代码
  1.   context.fn = this
复制代码
  1.   let result
复制代码
  1.   if (arguments[1]) {
复制代码
  1.     result = context.fn(...arguments[1])
复制代码
  1.   } else {
复制代码
  1.     result = context.fn()
复制代码
  1.   }
复制代码
  1.   delete context.fn
复制代码
  1.   return result
复制代码
  1. }
复制代码
[h1]3. 实现一个bind函数[/h1]
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]Function.prototype.mybind = function (context) {
复制代码
  1.   if (typeof this !== 'function') {
复制代码
  1.     throw new TypeError('Error')
复制代码
  1.   }
复制代码
  1.   let _this = this
复制代码
  1.   let arg = [...arguments].slice(1)
复制代码
  1.   return function F() {
复制代码
  1.     // 处理函数使用new的情况
复制代码
  1.     if (this instanceof F) {
复制代码
  1.       return new _this(...arg, ...arguments)
复制代码
  1.     } else {
复制代码
  1.       return _this.apply(context, arg.concat(...arguments))
复制代码
  1.     }
复制代码
  1.   }
复制代码
  1. }
复制代码
[h1]4. instanceof的原理[/h1]
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]// 右边变量的原型存在于左边变量的原型链上
复制代码
  1. function instanceOf(left, right) {
复制代码
  1.   let leftValue = left.__proto__
复制代码
  1.   let rightValue = right.prototype
复制代码
  1.   while (true) {
复制代码
  1.     if (leftValue === null) {
复制代码
  1.       return false
复制代码
  1.     }
复制代码
  1.     if (leftValue === right) {
复制代码
  1.       return true
复制代码
  1.     }
复制代码
  1.     leftValue = rightValue.__proto__
复制代码
  1.   }
复制代码
  1. }
复制代码
[h1]5. Object.create的基本实现原理[/h1]
  1. [/code][list][*][*][*][*][/list][code]function create(obj) {
复制代码
  1.   function F() {}
复制代码
  1.   F.prototype = obj
复制代码
  1.   return new F()
复制代码
[h1]6. new本质[/h1]
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]function myNew (fun) {
复制代码
  1.   return function () {
复制代码
  1.     // 创建一个新对象且将其隐式原型指向构造函数原型
复制代码
  1.     let obj = {
复制代码
  1.       __proto__ : fun.prototype
复制代码
  1.     }
复制代码
  1.     // 执行构造函数
复制代码
  1.     fun.call(obj, ...arguments)
复制代码
  1.     // 返回该对象
复制代码
  1.     return obj
复制代码
  1.   }
复制代码
  1. }
复制代码
  1. [/code][code]function person(name, age) {
复制代码
  1.   this.name = name
复制代码
  1.   this.age = age
复制代码
  1. }
复制代码
  1. let obj = myNew(person)('chen', 18) // {name: "chen", age: 18}
复制代码
[h1]7. 实现一个基本的Promise[/h1]
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]// ①自动执行函数,②三个状态,③then
复制代码
  1. class Promise {
复制代码
  1.   constructor (fn) {
复制代码
  1.     // 三个状态
复制代码
  1.     this.state = 'pending'
复制代码
  1.     this.value = undefined
复制代码
  1.     this.reason = undefined
复制代码
  1.     let resolve = value => {
复制代码
  1.       if (this.state === 'pending') {
复制代码
  1.         this.state = 'fulfilled'
复制代码
  1.         this.value = value
复制代码
  1.       }
复制代码
  1.     }
复制代码
  1.     let reject = value => {
复制代码
  1.       if (this.state === 'pending') {
复制代码
  1.         this.state = 'rejected'
复制代码
  1.         this.reason = value
复制代码
  1.       }
复制代码
  1.     }
复制代码
  1.     // 自动执行函数
复制代码
  1.     try {
复制代码
  1.       fn(resolve, reject)
复制代码
  1.     } catch (e) {
复制代码
  1.       reject(e)
复制代码
  1.     }
复制代码
  1.   }
复制代码
  1.   // then
复制代码
  1.   then(onFulfilled, onRejected) {
复制代码
  1.     switch (this.state) {
复制代码
  1.       case 'fulfilled':
复制代码
  1.         onFulfilled()
复制代码
  1.         break
复制代码
  1.       case 'rejected':
复制代码
  1.         onRejected()
复制代码
  1.         break
复制代码
  1.       default:
复制代码
  1.     }
复制代码
  1.   }
复制代码
  1. }
复制代码
[h1]8. 实现浅拷贝[/h1]
  1. [/code][list][*][*][*][*][*][*][/list][code]// 1. ...实现
复制代码
  1. let copy1 = {...{x:1}}
复制代码
  1. [/code][code]// 2. Object.assign实现
复制代码
  1. [/code][code]let copy2 = Object.assign({}, {x:1})
复制代码
[h1]9. 实现一个基本的深拷贝[/h1]
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]// 1. JOSN.stringify()/JSON.parse()
复制代码
  1. let obj = {a: 1, b: {x: 3}}
复制代码
  1. JSON.parse(JSON.stringify(obj))
复制代码
  1. [/code][code]// 2. 递归拷贝
复制代码
  1. function deepClone(obj) {
复制代码
  1.   let copy = obj instanceof Array ? [] : {}
复制代码
  1.   for (let i in obj) {
复制代码
  1.     if (obj.hasOwnProperty(i)) {
复制代码
  1.       copy[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
复制代码
  1.     }
复制代码
  1.   }
复制代码
  1.   return copy
复制代码
  1. }
复制代码
[h1]10. 使用setTimeout模拟setInterval[/h1]
  1. [/code][list][*][*][*][*][*][/list][code]// 可避免setInterval因执行时间导致的间隔执行时间不一致
复制代码
  1. setTimeout (function () {
复制代码
  1.   // do something
复制代码
  1.   setTimeout (arguments.callee, 500)
复制代码
  1. }, 500)
复制代码
11. js实现一个继承方法// 借用构造函数继承实例属性
  1. function Child () {
复制代码
  1.   Parent.call(this)
复制代码
  1. }
复制代码
  1. // 寄生继承原型属性
复制代码
  1. (function () {
复制代码
  1.   let Super = function () {}
复制代码
  1.   Super.prototype = Parent.prototype
复制代码
  1.   Child.prototype = new Super()
复制代码
  1. })()
复制代码
[h1][/h1][h1]12. 实现一个基本的Event Bus[/h1]
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]// 组件通信,一个触发与监听的过程
复制代码
  1. class EventEmitter {
复制代码
  1.   constructor () {
复制代码
  1.     // 存储事件
复制代码
  1.     this.events = this.events || new Map()
复制代码
  1.   }
复制代码
  1.   // 监听事件
复制代码
  1.   addListener (type, fn) {
复制代码
  1.     if (!this.events.get(type)) {
复制代码
  1.       this.events.set(type, fn)
复制代码
  1.     }
复制代码
  1.   }
复制代码
  1.   // 触发事件
复制代码
  1.   emit (type) {
复制代码
  1.     let handle = this.events.get(type)
复制代码
  1.     handle.apply(this, [...arguments].slice(1))
复制代码
  1.   }
复制代码
  1. }
复制代码
  1. [/code][code]// 测试
复制代码
  1. let emitter = new EventEmitter()
复制代码
  1. // 监听事件
复制代码
  1. emitter.addListener('ages', age => {
复制代码
  1.   console.log(age)
复制代码
  1. })
复制代码
  1. // 触发事件
复制代码
  1. emitter.emit('ages', 18)  // 18
复制代码
[h1]13. 实现一个双向数据绑定[/h1]
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]let obj = {}
复制代码
  1. let input = document.getElementById('input')
复制代码
  1. let span = document.getElementById('span')
复制代码
  1. Object.defineProperty(obj, 'text', {
复制代码
  1.   configurable: true,
复制代码
  1.   enumerable: true,
复制代码
  1.   get() {
复制代码
  1.     console.log('获取数据了')
复制代码
  1.     return obj.text
复制代码
  1.   },
复制代码
  1.   set(newVal) {
复制代码
  1.     console.log('数据更新了')
复制代码
  1.     input.value = newVal
复制代码
  1.     span.innerHTML = newVal
复制代码
  1.   }
复制代码
  1. })
复制代码
  1. input.addEventListener('keyup', function(e) {
复制代码
  1.   obj.text = e.target.value
复制代码
  1. })
复制代码
完整实现可前往之前写的:这应该是最详细的响应式系统讲解了
[h1][/h1][h1]14. 实现一个简单路由[/h1]
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]class Route{
复制代码
  1.   constructor(){
复制代码
  1.     // 路由存储对象
复制代码
  1.     this.routes = {}
复制代码
  1.     // 当前hash
复制代码
  1.     this.currentHash = ''
复制代码
  1.     // 绑定this,避免监听时this指向改变
复制代码
  1.     this.freshRoute = this.freshRoute.bind(this)
复制代码
  1.     // 监听
复制代码
  1.     window.addEventListener('load', this.freshRoute, false)
复制代码
  1.     window.addEventListener('hashchange', this.freshRoute, false)
复制代码
  1.   }
复制代码
  1.   // 存储
复制代码
  1.   storeRoute (path, cb) {
复制代码
  1.     this.routes[path] = cb || function () {}
复制代码
  1.   }
复制代码
  1.   // 更新
复制代码
  1.   freshRoute () {
复制代码
  1.     this.currentHash = location.hash.slice(1) || '/'
复制代码
  1.     this.routes[this.currentHash]()
复制代码
  1.   }
复制代码
  1. }
复制代码
[h1]15. 实现懒加载[/h1]
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]
复制代码
  1.   
复制代码
  1.   
复制代码
  1.   
复制代码
  1.   
复制代码
  1.   
复制代码
  1.   
复制代码
  1.   
复制代码
  1.   
复制代码
  1.   
复制代码
  1.   
复制代码
  1. [/code][code]
复制代码
  1. let imgs =  document.querySelectorAll('img')
复制代码
  1. // 可视区高度
复制代码
  1. let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
复制代码
  1. function lazyLoad () {
复制代码
  1.   // 滚动卷去的高度
复制代码
  1.   let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
复制代码
  1.   for (let i = 0; i < imgs.length; i ++) {
复制代码
  1.     // 得到图片顶部距离可视区顶部的距离
复制代码
  1.     let x = clientHeight + scrollTop - imgs[i].offsetTop
复制代码
  1.     // 图片在可视区内
复制代码
  1.     if (x > 0 && x < clientHeight+imgs[i].height) {
复制代码
  1.       imgs[i].src = imgs[i].getAttribute('data')
复制代码
  1.     }
复制代码
  1.   }
复制代码
  1. }
复制代码
  1. setInterval(lazyLoad, 1000)
复制代码
[h1]16. rem实现原理[/h1]
  1. [/code][list][*][*][*][*][*][*][*][/list][code]function setRem () {
复制代码
  1.   let doc = document.documentElement
复制代码
  1.   let width = doc.getBoundingClientRect().width
复制代码
  1.   // 假设设计稿为宽750,则rem为10px
复制代码
  1.   let rem = width / 75
复制代码
  1.   doc.style.fontSize = rem + 'px'
复制代码
  1. }
复制代码
[h1]17. 手写实现AJAX[/h1]
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]// 1. 简单实现
复制代码
  1. [/code][code]// 实例化
复制代码
  1. let xhr = new XMLHttpRequest()
复制代码
  1. // 初始化
复制代码
  1. xhr.open(method, url, async)
复制代码
  1. // 发送请求
复制代码
  1. xhr.send(data)
复制代码
  1. // 设置状态变化回调处理请求结果
复制代码
  1. xhr.onreadystatechange = () => {
复制代码
  1.   if (xhr.readyStatus === 4 && xhr.status === 200) {
复制代码
  1.     console.log(xhr.responseText)
复制代码
  1.   }
复制代码
  1. }
复制代码
  1. [/code][code]// 2. 基于promise实现
复制代码
  1. [/code][code]function ajax (options) {
复制代码
  1.   // 请求地址
复制代码
  1.   const url = options.url
复制代码
  1.   // 请求方法
复制代码
  1.   const method = options.method.toLocaleLowerCase() || 'get'
复制代码
  1.   // 默认为异步true
复制代码
  1.   const async = options.async
复制代码
  1.   // 请求参数
复制代码
  1.   const data = options.data
复制代码
  1.   // 实例化
复制代码
  1.   const xhr = new XMLHttpRequest()
复制代码
  1.   // 请求超时
复制代码
  1.   if (options.timeout && options.timeout > 0) {
复制代码
  1.     xhr.timeout = options.timeout
复制代码
  1.   }
复制代码
  1.   // 返回一个Promise实例
复制代码
  1.   return new Promise ((resolve, reject) => {
复制代码
  1.     xhr.ontimeout = () => reject && reject('请求超时')
复制代码
  1.     // 监听状态变化回调
复制代码
  1.     xhr.onreadystatechange = () => {
复制代码
  1.       if (xhr.readyState == 4) {
复制代码
  1.         // 200-300 之间表示请求成功,304资源未变,取缓存
复制代码
  1.         if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
复制代码
  1.           resolve && resolve(xhr.responseText)
复制代码
  1.         } else {
复制代码
  1.           reject && reject()
复制代码
  1.         }
复制代码
  1.       }
复制代码
  1.     }
复制代码
  1.     // 错误回调
复制代码
  1.     xhr.onerror = err => reject && reject(err)
复制代码
  1.     let paramArr = []
复制代码
  1.     let encodeData
复制代码
  1.     // 处理请求参数
复制代码
  1.     if (data instanceof Object) {
复制代码
  1.       for (let key in data) {
复制代码
  1.         // 参数拼接需要通过 encodeURIComponent 进行编码
复制代码
  1.         paramArr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
复制代码
  1.       }
复制代码
  1.       encodeData = paramArr.join('&')
复制代码
  1.     }
复制代码
  1.     // get请求拼接参数
复制代码
  1.     if (method === 'get') {
复制代码
  1.       // 检测url中是否已存在 ? 及其位置
复制代码
  1.       const index = url.indexOf('?')
复制代码
  1.       if (index === -1) url += '?'
复制代码
  1.       else if (index !== url.length -1) url += '&'
复制代码
  1.       // 拼接url
复制代码
  1.       url += encodeData
复制代码
  1.     }
复制代码
  1.     // 初始化
复制代码
  1.     xhr.open(method, url, async)
复制代码
  1.     // 发送请求
复制代码
  1.     if (method === 'get') xhr.send(null)
复制代码
  1.     else {
复制代码
  1.       // post 方式需要设置请求头
复制代码
  1.       xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8')
复制代码
  1.       xhr.send(encodeData)
复制代码
  1.     }
复制代码
  1.   })
复制代码
  1. }
复制代码
[h1]18. 实现拖拽[/h1]
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]window.onload = function () {
复制代码
  1.   // drag处于绝对定位状态
复制代码
  1.   let drag = document.getElementById('box')
复制代码
  1.   drag.onmousedown = function(e) {
复制代码
  1.     var e = e || window.event
复制代码
  1.     // 鼠标与拖拽元素边界的距离 = 鼠标与可视区边界的距离 - 拖拽元素与边界的距离
复制代码
  1.     let diffX = e.clientX - drag.offsetLeft
复制代码
  1.     let diffY = e.clientY - drag.offsetTop
复制代码
  1.     drag.onmousemove = function (e) {
复制代码
  1.       // 拖拽元素移动的距离 = 鼠标与可视区边界的距离 - 鼠标与拖拽元素边界的距离
复制代码
  1.       let left = e.clientX - diffX
复制代码
  1.       let top = e.clientY - diffY
复制代码
  1.       // 避免拖拽出可视区
复制代码
  1.       if (left < 0) {
复制代码
  1.         left = 0
复制代码
  1.       } else if (left > window.innerWidth - drag.offsetWidth) {
复制代码
  1.         left = window.innerWidth - drag.offsetWidth
复制代码
  1.       }
复制代码
  1.       if (top < 0) {
复制代码
  1.         top = 0
复制代码
  1.       } else if (top > window.innerHeight - drag.offsetHeight) {
复制代码
  1.         top = window.innerHeight - drag.offsetHeight
复制代码
  1.       }
复制代码
  1.       drag.style.left = left + 'px'
复制代码
  1.       drag.style.top = top + 'px'
复制代码
  1.     }
复制代码
  1.     drag.onmouseup = function (e) {
复制代码
  1.       this.onmousemove = null
复制代码
  1.       this.onmouseup = null
复制代码
  1.     }
复制代码
  1.   }
复制代码
  1. }
复制代码
[h1]19. 实现一个节流函数[/h1]
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]function throttle (fn, delay) {
复制代码
  1.   // 利用闭包保存时间
复制代码
  1.   let prev = Date.now()
复制代码
  1.   return function () {
复制代码
  1.     let context = this
复制代码
  1.     let arg = arguments
复制代码
  1.     let now = Date.now()
复制代码
  1.     if (now - prev >= delay) {
复制代码
  1.       fn.apply(context, arg)
复制代码
  1.       prev = Date.now()
复制代码
  1.     }
复制代码
  1.   }
复制代码
  1. }
复制代码
  1. [/code][code]function fn () {
复制代码
  1.   console.log('节流')
复制代码
  1. }
复制代码
  1. addEventListener('scroll', throttle(fn, 1000))
复制代码
[h1]20. 实现一个防抖函数[/h1]
  1. [/code][list][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]function debounce (fn, delay) {
复制代码
  1.   // 利用闭包保存定时器
复制代码
  1.   let timer = null
复制代码
  1.   return function () {
复制代码
  1.     let context = this
复制代码
  1.     let arg = arguments
复制代码
  1.     // 在规定时间内再次触发会先清除定时器后再重设定时器
复制代码
  1.     clearTimeout(timer)
复制代码
  1.     timer = setTimeout(function () {
复制代码
  1.       fn.apply(context, arg)
复制代码
  1.     }, delay)
复制代码
  1.   }
复制代码
  1. }
复制代码
  1. [/code][code]function fn () {
复制代码
  1.   console.log('防抖')
复制代码
  1. }
复制代码
  1. addEventListener('scroll', debounce(fn, 1000))
复制代码
[h1]最后[/h1]后续会持续更新,欢迎关注点个赞!

作者:陈煜仑
https://juejin.im/post/5d2ee123e51d4577614761f8
觉得本文对你有帮助?请分享给更多人!
关注「前端大学」,提升前端技能!

▲长按二维码即可获得100G编程视频资料!
好文章,我在看
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:30
帖子:6
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP