20道JS原理题助你面试一臂之力(必看)


Posted in Javascript onJuly 22, 2019

前言

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

1. 实现一个call函数

// 思路:将要改变this指向的方法挂到目标this上执行并返回
Function.prototype.mycall = function (context) {
 if (typeof this !== 'function') {
 throw new TypeError('not funciton')
 }
 context = context || window
 context.fn = this
 let arg = [...arguments].slice(1)
 let result = context.fn(...arg)
 delete context.fn
 return result
}

2. 实现一个apply函数

// 思路:将要改变this指向的方法挂到目标this上执行并返回
Function.prototype.myapply = function (context) {
 if (typeof this !== 'function') {
 throw new TypeError('not funciton')
 }
 context = context || window
 context.fn = this
 let result
 if (arguments[1]) {
 result = context.fn(...arguments[1])
 } else {
 result = context.fn()
 }
 delete context.fn
 return result
}

3. 实现一个bind函数

// 思路:类似call,但返回的是函数
Function.prototype.mybind = function (context) {
 if (typeof this !== 'function') {
 throw new TypeError('Error')
 }
 let _this = this
 let arg = [...arguments].slice(1)
 return function F() {
 // 处理函数使用new的情况
 if (this instanceof F) {
  return new _this(...arg, ...arguments)
 } else {
  return _this.apply(context, arg.concat(...arguments))
 }
 }
}

4. instanceof的原理

// 思路:右边变量的原型存在于左边变量的原型链上
function instanceOf(left, right) {
 let leftValue = left.__proto__
 let rightValue = right.prototype
 while (true) {
 if (leftValue === null) {
  return false
 }
 if (leftValue === rightValue) {
  return true
 }
 leftValue = leftValue.__proto__
 }
}

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

// 思路:将传入的对象作为原型
function create(obj) {
 function F() {}
 F.prototype = obj
 return new F()

6. new本质

function myNew (fun) {
 return function () {
 // 创建一个新对象且将其隐式原型指向构造函数原型
 let obj = {
  __proto__ : fun.prototype
 }
 // 执行构造函数
 fun.call(obj, ...arguments)
 // 返回该对象
 return obj
 }
}

function person(name, age) {
 this.name = name
 this.age = age
}
let obj = myNew(person)('chen', 18) // {name: "chen", age: 18}

7. 实现一个基本的Promise

// 未添加异步处理等其他边界情况
// ①自动执行函数,②三个状态,③then
class Promise {
 constructor (fn) {
 // 三个状态
 this.state = 'pending'
 this.value = undefined
 this.reason = undefined
 let resolve = value => {
  if (this.state === 'pending') {
  this.state = 'fulfilled'
  this.value = value
  }
 }
 let reject = value => {
  if (this.state === 'pending') {
  this.state = 'rejected'
  this.reason = value
  }
 }
 // 自动执行函数
 try {
  fn(resolve, reject)
 } catch (e) {
  reject(e)
 }
 }
 // then
 then(onFulfilled, onRejected) {
 switch (this.state) {
  case 'fulfilled':
  onFulfilled()
  break
  case 'rejected':
  onRejected()
  break
  default:
 }
 }
}

8. 实现浅拷贝

// 1. ...实现
let copy1 = {...{x:1}}

// 2. Object.assign实现

let copy2 = Object.assign({}, {x:1})

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

// 1. JOSN.stringify()/JSON.parse()
let obj = {a: 1, b: {x: 3}}
JSON.parse(JSON.stringify(obj))

// 2. 递归拷贝
function deepClone(obj) {
 let copy = obj instanceof Array ? [] : {}
 for (let i in obj) {
 if (obj.hasOwnProperty(i)) {
  copy[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
 }
 }
 return copy
}

10. 使用setTimeout模拟setInterval

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

11. js实现一个继承方法

// 借用构造函数继承实例属性
function Child () {
 Parent.call(this)
}
// 寄生继承原型属性
(function () {
 let Super = function () {}
 Super.prototype = Parent.prototype
 Child.prototype = new Super()
})()

12. 实现一个基本的Event Bus

// 组件通信,一个触发与监听的过程
class EventEmitter {
 constructor () {
 // 存储事件
 this.events = this.events || new Map()
 }
 // 监听事件
 addListener (type, fn) {
 if (!this.events.get(type)) {
  this.events.set(type, fn)
 }
 }
 // 触发事件
 emit (type) {
 let handle = this.events.get(type)
 handle.apply(this, [...arguments].slice(1))
 }
}

// 测试
let emitter = new EventEmitter()
// 监听事件
emitter.addListener('ages', age => {
 console.log(age)
})
// 触发事件
emitter.emit('ages', 18) // 18

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

let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 数据劫持
Object.defineProperty(obj, 'text', {
 configurable: true,
 enumerable: true,
 get() {
 console.log('获取数据了')
 return obj['text']
 },
 set(newVal) {
 console.log('数据更新了')
 input.value = newVal
 span.innerHTML = newVal
 }
})
// 输入监听
input.addEventListener('keyup', function(e) {
 obj.text = e.target.value
})

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

14. 实现一个简单路由

// hash路由
class Route{
 constructor(){
 // 路由存储对象
 this.routes = {}
 // 当前hash
 this.currentHash = ''
 // 绑定this,避免监听时this指向改变
 this.freshRoute = this.freshRoute.bind(this)
 // 监听
 window.addEventListener('load', this.freshRoute, false)
 window.addEventListener('hashchange', this.freshRoute, false)
 }
 // 存储
 storeRoute (path, cb) {
 this.routes[path] = cb || function () {}
 }
 // 更新
 freshRoute () {
 this.currentHash = location.hash.slice(1) || '/'
 this.routes[this.currentHash]()
 } 
}

15. 实现懒加载

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

16. rem基本设置

// 原始配置
function setRem () {
 let doc = document.documentElement
 let width = doc.getBoundingClientRect().width
 let rem = width / 75
 doc.style.fontSize = rem + 'px'
}
// 监听窗口变化
addEventListener("resize", setRem)

17. 手写实现AJAX

// 1. 简单流程

// 实例化
let xhr = new XMLHttpRequest()
// 初始化
xhr.open(method, url, async)
// 发送请求
xhr.send(data)
// 设置状态变化回调处理请求结果
xhr.onreadystatechange = () => {
 if (xhr.readyStatus === 4 && xhr.status === 200) {
 console.log(xhr.responseText)
 }
}

// 2. 基于promise实现 

function ajax (options) {
 // 请求地址
 const url = options.url
 // 请求方法
 const method = options.method.toLocaleLowerCase() || 'get'
 // 默认为异步true
 const async = options.async
 // 请求参数
 const data = options.data
 // 实例化
 const xhr = new XMLHttpRequest()
 // 请求超时
 if (options.timeout && options.timeout > 0) {
 xhr.timeout = options.timeout
 }
 // 返回一个Promise实例
 return new Promise ((resolve, reject) => {
 xhr.ontimeout = () => reject && reject('请求超时')
 // 监听状态变化回调
 xhr.onreadystatechange = () => {
  if (xhr.readyState == 4) {
  // 200-300 之间表示请求成功,304资源未变,取缓存
  if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
   resolve && resolve(xhr.responseText)
  } else {
   reject && reject()
  }
  }
 }
 // 错误回调
 xhr.onerror = err => reject && reject(err)
 let paramArr = []
 let encodeData
 // 处理请求参数
 if (data instanceof Object) {
  for (let key in data) {
  // 参数拼接需要通过 encodeURIComponent 进行编码
  paramArr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
  }
  encodeData = paramArr.join('&')
 }
 // get请求拼接参数
 if (method === 'get') {
  // 检测url中是否已存在 ? 及其位置
  const index = url.indexOf('?')
  if (index === -1) url += '?'
  else if (index !== url.length -1) url += '&'
  // 拼接url
  url += encodeData
 }
 // 初始化
 xhr.open(method, url, async)
 // 发送请求
 if (method === 'get') xhr.send(null)
 else {
  // post 方式需要设置请求头
  xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8')
  xhr.send(encodeData)
 }
 })
}

18. 实现拖拽

window.onload = function () {
 // drag处于绝对定位状态
 let drag = document.getElementById('box')
 drag.onmousedown = function(e) {
 var e = e || window.event
 // 鼠标与拖拽元素边界的距离 = 鼠标与可视区边界的距离 - 拖拽元素与边界的距离
 let diffX = e.clientX - drag.offsetLeft
 let diffY = e.clientY - drag.offsetTop
 drag.onmousemove = function (e) {
  // 拖拽元素移动的距离 = 鼠标与可视区边界的距离 - 鼠标与拖拽元素边界的距离
  let left = e.clientX - diffX
  let top = e.clientY - diffY
  // 避免拖拽出可视区
  if (left < 0) {
  left = 0
  } else if (left > window.innerWidth - drag.offsetWidth) {
  left = window.innerWidth - drag.offsetWidth
  }
  if (top < 0) {
  top = 0
  } else if (top > window.innerHeight - drag.offsetHeight) {
  top = window.innerHeight - drag.offsetHeight
  }
  drag.style.left = left + 'px'
  drag.style.top = top + 'px'
 }
 drag.onmouseup = function (e) {
  this.onmousemove = null
  this.onmouseup = null
 }
 }
}

19. 实现一个节流函数

// 思路:在规定时间内只触发一次
function throttle (fn, delay) {
 // 利用闭包保存时间
 let prev = Date.now()
 return function () {
 let context = this
 let arg = arguments
 let now = Date.now()
 if (now - prev >= delay) {
  fn.apply(context, arg)
  prev = Date.now()
 }
 }
}

function fn () {
 console.log('节流')
}
addEventListener('scroll', throttle(fn, 1000))

20. 实现一个防抖函数

// 思路:在规定时间内未触发第二次,则执行
function debounce (fn, delay) {
 // 利用闭包保存定时器
 let timer = null
 return function () {
 let context = this
 let arg = arguments
 // 在规定时间内再次触发会先清除定时器后再重设定时器
 clearTimeout(timer)
 timer = setTimeout(function () {
  fn.apply(context, arg)
 }, delay)
 }
}

function fn () {
 console.log('防抖')
}
addEventListener('scroll', debounce(fn, 1000))

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js中widow.open()方法使用详解
Jul 30 Javascript
JavaScript中使用typeof运算符需要注意的几个坑
Nov 08 Javascript
jQuery实现Select左右复制移动内容
Aug 05 Javascript
基于JavaScript实现购物网站商品放大镜效果
Sep 06 Javascript
JavaScript的兼容性与调试技巧
Nov 22 Javascript
BootStrap中关于Select下拉框选择触发事件及扩展
Nov 22 Javascript
Javascript this 函数深入详解
Dec 13 Javascript
完美解决浏览器跨域的几种方法(汇总)
May 08 Javascript
iframe与主框架跨域相互访问实现方法
Sep 14 Javascript
vue 指令之气泡提示效果的实现代码
Oct 18 Javascript
js canvas实现5张图片合成一张图片
Jul 15 Javascript
多个Vue项目部署到服务器的步骤记录
Oct 22 Javascript
微信小程序webview 脚手架使用详解
Jul 22 #Javascript
koa2 用户注册、登录校验与加盐加密的实现方法
Jul 22 #Javascript
koa2服务端使用jwt进行鉴权及路由权限分发的流程分析
Jul 22 #Javascript
在小程序中推送模板消息的实现方法
Jul 22 #Javascript
javascript自定义日期比较函数用法示例
Jul 22 #Javascript
详解微信小程序自定义组件的实现及数据交互
Jul 22 #Javascript
教你30秒发布一个TypeScript包到NPM的方法步骤
Jul 22 #Javascript
You might like
解决文件名解压后乱码的问题 将文件名进行转码的代码
2012/01/10 PHP
php正则匹配html中带class的div并选取其中内容的方法
2015/01/13 PHP
php实现mysql数据库连接操作及用户管理
2015/11/08 PHP
通过PHP实现获取访问用户IP
2020/05/09 PHP
js不是基础的基础
2006/12/24 Javascript
jquery入门—访问DOM对象方法
2013/01/07 Javascript
js弹出模式对话框,并接收回传值的方法
2013/03/12 Javascript
js验证输入是否为手机号码或电话号码示例
2013/12/30 Javascript
JavaScript实现鼠标点击后层展开效果的方法
2015/05/13 Javascript
jQuery的end()方法使用详解
2015/07/15 Javascript
通过BootStrap-select插件 js jQuery控制select属性变化
2017/01/03 Javascript
Jquery实现跨域异步上传文件总结
2017/02/03 Javascript
webpack独立打包和缓存处理详解
2017/04/03 Javascript
微信小程序五星评分效果实现代码
2017/04/06 Javascript
Jquery中.bind()、.live()、.delegate()和.on()之间的区别详解
2017/08/01 jQuery
vue-cli实现多页面多路由的示例代码
2018/01/30 Javascript
详解VueJs中的V-bind指令
2018/05/03 Javascript
微信小程序 生成携带参数的二维码
2019/10/23 Javascript
vue计算属性+vue中class与style绑定(推荐)
2020/03/30 Javascript
vue项目中使用多选框的实例代码
2020/07/22 Javascript
[04:32]DOTA2著名解说配音敌法师 现场专访海涛怒切假腿
2013/12/20 DOTA
解决python3 urllib 链接中有中文的问题
2018/07/16 Python
Django 多表关联 存储 使用方法详解 ManyToManyField save
2019/08/09 Python
树莓派3 搭建 django 服务器的实例
2019/08/29 Python
python单例模式原理与创建方法实例分析
2019/10/26 Python
python pandas移动窗口函数rolling的用法
2020/02/29 Python
Python3爬虫mitmproxy的安装步骤
2020/07/29 Python
美国在线工具商店:Acme Tools
2018/06/26 全球购物
美术专业个人自我评价
2014/01/18 职场文书
追悼会主持词
2014/03/20 职场文书
党员创先争优承诺书
2014/03/26 职场文书
收款委托书范本
2014/09/11 职场文书
2014年宣传部工作总结
2014/11/12 职场文书
想创业成功,需要掌握这些要点
2019/12/06 职场文书
Java Dubbo框架知识点梳理
2021/06/26 Java/Android
Go语言实现一个简单的并发聊天室的项目实战
2022/03/18 Golang