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 相关文章推荐
jquery中常用的SET和GET
Jan 13 Javascript
自动完成JS类(纯JS, Ajax模式)
Mar 12 Javascript
删除select中所有option选项jquery代码
Aug 12 Javascript
js中arguments的用法(实例讲解)
Nov 30 Javascript
js通过元素class名字获取元素集合的具体实现
Jan 06 Javascript
原生js获取宽高与jquery获取宽高的方法关系对比
Apr 04 Javascript
JavaScript省市区三级联动菜单效果
Sep 21 Javascript
[原创]JavaScript语法高亮插件highlight.js用法详解【附highlight.js本站下载】
Nov 01 Javascript
Servlet实现文件上传,可多文件上传示例
Dec 05 Javascript
jQuery Validate 校验多个相同name的方法
May 18 jQuery
详解从零搭建 vue2 vue-router2 webpack3 工程
Nov 22 Javascript
微信小程序实现多选删除列表数据功能示例
Jan 15 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
全国FM电台频率大全 - 21 海南省
2020/03/11 无线电
frename PHP 灵活文件命名函数 frename
2009/09/09 PHP
hadoop中一些常用的命令介绍
2013/06/19 PHP
php按百分比生成缩略图的代码分享
2014/05/10 PHP
php获取网页请求状态程序示例
2014/06/17 PHP
php实现插入数组但不影响原有顺序的方法
2015/03/27 PHP
PHP配置ZendOpcache插件加速
2019/02/14 PHP
PHP析构函数destruct与垃圾回收机制的讲解
2019/03/22 PHP
PHP实现数组向任意位置插入,删除,替换数据操作示例
2019/04/05 PHP
myeclipse安装jQuery插件的方法
2011/03/29 Javascript
JS trim去空格的最佳实践
2011/10/30 Javascript
js滚动条回到顶部的代码
2011/12/06 Javascript
javascript创建和存储cookie示例
2014/01/07 Javascript
jquery 字符串切割函数substring的用法说明
2014/02/11 Javascript
从零学jquery之如何使用回调函数
2014/05/16 Javascript
Angularjs中$http以post请求通过消息体传递参数的实现方法
2016/08/05 Javascript
JS实现表单验证功能(验证手机号是否存在,验证码倒计时)
2016/10/11 Javascript
angular十大常见问题
2017/03/07 Javascript
AngularJs1.x自定义指令独立作用域的函数传入参数方法
2018/10/09 Javascript
[02:44]DOTA2英雄基础教程 魅惑魔女
2014/01/07 DOTA
[56:48]FNATIC vs EG 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/16 DOTA
Python中dictionary items()系列函数的用法实例
2014/08/21 Python
基于scrapy实现的简单蜘蛛采集程序
2015/04/17 Python
Python图像处理实现两幅图像合成一幅图像的方法【测试可用】
2019/01/04 Python
tensorflow2.0保存和恢复模型3种方法
2020/02/03 Python
python 的topk算法实例
2020/04/02 Python
无需JS和jQuery代码实现CSS3鼠标浮动放大图片
2016/11/21 HTML / CSS
大学毕业生的自我鉴定
2013/11/30 职场文书
开业庆典策划方案
2014/02/18 职场文书
汉语言文学专业自荐信
2014/06/11 职场文书
供用电专业求职信
2014/07/07 职场文书
人民调解协议书范本
2014/10/11 职场文书
红领巾广播站广播稿
2015/08/19 职场文书
导游词之苏州盘门景区
2019/11/12 职场文书
PHP中多字节字符串操作实例详解
2021/08/23 PHP
AudioContext 实现音频可视化(web技术分享)
2022/02/24 Javascript