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 相关文章推荐
JavaScript使用技巧精萃[代码非常实用]
Nov 21 Javascript
EasyUi tabs的高度与宽度根据IE窗口的变化自适应代码
Oct 26 Javascript
基于jQuery实现图片的前进与后退功能
Apr 24 Javascript
js中window.open()的所有参数详细解析
Jan 09 Javascript
node.js中实现同步操作的3种实现方法
Dec 05 Javascript
Angular.js如何从PHP读取后台数据
Mar 24 Javascript
小白谈谈对JS原型链的理解
May 03 Javascript
原生js和jquery分别实现横向导航菜单效果
May 13 Javascript
原生JS改变透明度实现轮播效果
Mar 24 Javascript
微信小程序网络封装(简单高效)
Aug 06 Javascript
JS实现的类似微信聊天效果示例
Jan 29 Javascript
Vue数据驱动表单渲染,轻松搞定form表单
Jul 19 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新手NOTICE错误常见解决方法
2011/12/07 PHP
PHP语言中global和$GLOBALS[]的分析 之二
2012/02/02 PHP
PHP 第一节 php简介
2012/04/28 PHP
php中使用sftp教程
2015/03/30 PHP
php官方微信接口大全(微信支付、微信红包、微信摇一摇、微信小店)
2015/12/21 PHP
php生成验证码,缩略图及水印图的类分享
2016/04/07 PHP
PHP 实现公历日期与农历日期的互转换
2017/09/13 PHP
用javascript父窗口控制只弹出一个子窗口
2007/04/10 Javascript
常见JS效果之图片减速度滚动实现代码
2011/12/08 Javascript
jquery获取当前日期的方法
2015/01/14 Javascript
JavaScript内存管理介绍
2015/03/13 Javascript
jQuery检测输入的字符串包含的中英文的数量
2015/04/17 Javascript
JavaScript通过Date-Mask将日期转换成字符串的方法
2015/06/04 Javascript
javascript中setAttribute()函数使用方法及兼容性
2015/07/19 Javascript
javascript实现继承的简单实例
2015/07/26 Javascript
jQuery javascript获得网页的高度与宽度的实现代码
2016/04/26 Javascript
js获取上传文件的绝对路径实现方法
2016/08/02 Javascript
JS清除字符串中重复值的实现方法
2016/08/03 Javascript
浅谈使用splice函数对数组中的元素进行删除时的注意事项
2016/12/04 Javascript
javascript、php关键字搜索函数的使用方法
2018/05/29 Javascript
vue2.x 通过后端接口代理,获取qq音乐api的数据示例
2019/10/30 Javascript
[01:16:16]DOTA2-DPC中国联赛定级赛 RNG vs Phoenix BO3第二场 1月8日
2021/03/11 DOTA
python实现SOM算法
2018/02/23 Python
Python实现k-means算法
2018/02/23 Python
对numpy 数组和矩阵的乘法的进一步理解
2018/04/04 Python
python 爬虫 批量获取代理ip的实例代码
2018/05/22 Python
浅谈python3 构造函数和析构函数
2020/03/12 Python
pycharm 关掉syntax检查操作
2020/06/09 Python
解决margin 外边距合并问题
2019/07/03 HTML / CSS
html5 外链式实现加减乘除的代码
2019/09/04 HTML / CSS
Raffaello Network德国:意大利拉斐尔时尚购物网
2019/05/01 全球购物
保密协议书范本
2014/04/22 职场文书
个人授权委托书范文
2014/09/21 职场文书
社会实践活动总结
2015/02/05 职场文书
vue实现在data里引入相对路径
2022/06/05 Vue.js
el-form每行显示两列底部按钮居中效果的实现
2022/08/05 HTML / CSS