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 相关文章推荐
Track Image Loading效果代码分析
Aug 13 Javascript
单独使用CKFinder选择图片的方法
Aug 21 Javascript
jquery实现点击TreeView文本父节点展开/折叠子节点
Jan 10 Javascript
JS 加入收藏夹的代码(主流浏览器通用)
May 13 Javascript
JavaScript移除数组元素减少长度的方法
Sep 05 Javascript
javascript中的__defineGetter__和__defineSetter__介绍
Aug 15 Javascript
javascript框架设计读书笔记之字符串的扩展和修复
Dec 02 Javascript
JS加载iFrame出现空白问题的解决办法
May 13 Javascript
9个让JavaScript调试更简单的Console命令
Nov 14 Javascript
ES6中javascript实现函数绑定及类的事件绑定功能详解
Nov 08 Javascript
提升node.js中使用redis的性能遇到的问题及解决方法
Oct 30 Javascript
react 路由Link配置详解
Nov 11 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
在php中使用sockets:从新闻组中获取文章
2006/10/09 PHP
Mysql数据库操作类( 1127版,提供源码下载 )
2010/12/02 PHP
在php中判断一个请求是ajax请求还是普通请求的方法
2011/06/28 PHP
详解php中的implements 使用
2017/06/13 PHP
javascript offsetX与layerX区别
2010/03/12 Javascript
javascript中length属性的探索
2011/07/31 Javascript
jquery实现点击TreeView文本父节点展开/折叠子节点
2013/01/10 Javascript
js输入框邮箱自动提示功能代码实现
2013/12/10 Javascript
js验证输入是否为手机号码或电话号码示例
2013/12/30 Javascript
node.js中的http.get方法使用说明
2014/12/14 Javascript
javascript框架设计之浏览器的嗅探和特征侦测
2015/06/23 Javascript
AngularJS国际化详解及示例代码
2016/08/18 Javascript
Bootstrap基本样式学习笔记之标签(5)
2016/12/07 Javascript
详解Windows下安装Nodejs步骤
2017/05/18 NodeJs
jsonp跨域请求详解
2017/07/13 Javascript
vue实现移动端图片裁剪上传功能
2020/08/18 Javascript
vue中element组件样式修改无效的解决方法
2018/02/03 Javascript
关于redux-saga中take使用方法详解
2018/02/27 Javascript
详解Node使用Puppeteer完成一次复杂的爬虫
2018/04/18 Javascript
详解angular分页插件tm.pagination二次触发问题解决方案
2018/07/20 Javascript
在Angular项目使用socket.io实现通信的方法
2021/01/05 Javascript
在Python的Flask框架下使用sqlalchemy库的简单教程
2015/04/09 Python
Python实现二分查找算法实例
2015/05/26 Python
Python 模块EasyGui详细介绍
2017/02/19 Python
在python 不同时区之间的差值与转换方法
2019/01/14 Python
django celery redis使用具体实践
2019/04/08 Python
python获取地震信息 微信实时推送
2019/06/18 Python
TensorFlow 读取CSV数据的实例
2020/02/05 Python
最新Python idle下载、安装与使用教程图文详解
2020/11/28 Python
德国运动鞋网上商店:Afew Store
2018/01/05 全球购物
澳大利亚波西米亚风连衣裙在线商店:Fortunate One
2019/04/01 全球购物
Eclipse面试题
2014/03/22 面试题
大学生农村教师实习自我鉴定
2013/09/21 职场文书
物业公司的岗位任命书
2014/06/06 职场文书
三下乡活动心得体会
2016/01/23 职场文书
幼儿园六一儿童节开幕词
2016/03/04 职场文书