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

前言

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

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))

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

(0)

相关推荐

  • 一些可能会用到的Node.js面试题

    前言 从ECMAScript标准,Node.js语法以及NPM模块角度来看,Node.js的发展让人目不暇接,那么面试题也得与时俱进. 问题 什么是错误优先的回调函数? 如何避免回调地狱? 什么是Promise? 用什么工具保证一致的代码风格?为什么要这样? 什么是Stub?举例说明 什么是测试金字塔?举例说明 最喜欢哪个HTTP框架?为什么? Cookies如何防范XSS攻击? 如何保证依赖的安全性? 答案 1. 什么是错误优先的回调函数? 错误优先的回调函数(Error-First Call

  • 送你43道JS面试题(收藏)

    导读 这两天的GitHub Trending repositories被一个名叫 javascript-questions的项目霸榜了,项目中记录了一些JavaScript题目. 我大概从头到尾看了一遍,都是一些基础的题目,我大概花了半个小时(有些题很简单,可以一扫而过)把这些题做完了,虽然题目很简单,但是每道题都对应一个知识点,如果这个知识点你没有接触过,那肯定会做错,如果你接触过这些知识点,那么这些题对你来说就很容易. 建议大家也花半个小时来做一做,以便查漏补缺. 为方便大家能够更快的做题,

  • 详解JS中的this、apply、call、bind(经典面试题)

    这又是一个面试经典问题~/(ㄒoㄒ)/~~也是 ES5中众多坑中的一个,在 ES6 中可能会极大避免 this 产生的错误,但是为了一些老代码的维护,最好还是了解一下 this 的指向和 call.apply.bind 三者的区别. this 的指向 在 ES5 中,其实 this 的指向,始终坚持一个原理:this 永远指向最后调用它的那个对象,来,跟着我朗读三遍:this 永远指向最后调用它的那个对象,this 永远指向最后调用它的那个对象,this 永远指向最后调用它的那个对象.记住这句话

  • JS搜狐面试题分析

    本文实例讲述了几道JS搜狐面试题.分享给大家供大家参考,具体如下: 一.实现一个遍历数组或对象里所有成员的迭代器. var each = function(obj, fn){ //+++++++++++答题区域+++++++++++ //+++++++++++答题结束+++++++++++ }; try{ var data1 = [4,5,6,7,8,9,10,11,12]; var data2 = { "a": 4, "b": 5, "c":

  • 13道关于JavaScript正则表达式的面试题

    1.要想在正则表达式中按照直接量匹配反斜线本身,必须使用反斜线将其转义.() A.正确  B.错误 2.2.WINDOWS下,要摘取绝对路径字符串最前端的磁盘驱动器部分(即A:/B:/C:/...),用正则表达式实现是/^[A-Za-z]:/() A.正确  B.错误 3.3.\W这个字符类等价于字符类[a-zA-Z0-8].() A.正确  B.错误 4.4.0到999间的任意整数对应正则表达式是/[^0-9]{1,3}$/.() A.正确  B.错误 5.在JS正则表达式中,当n=m时,{n

  • JS面试题大坑之隐式类型转换实例代码

    1.1-隐式转换介绍 在js中,当运算符在运算时,如果两边数据不统一,CPU就无法计算,这时我们编译器会自动将运算符两边的数据做一个数据类型转换,转成一样的数据类型再计算 这种无需程序员手动转换,而由编译器自动转换的方式就称为隐式转换 例如1 > "0"这行代码在js中并不会报错,编译器在运算符时会先把右边的"0"转成数字0`然后在比较大小 1.2-隐式转换规则 转成string类型: +(字符串连接符) 2..转成number类型:++/--(自增自减运算符

  • 关于javascript作用域的常见面试题分享

    本文主要给大家分享了关于javascript作用域面试题的相关内容,分享出来供大家参考学习,下面来一起看看吧. 一.作用域: 在了解作用域之前,首先需要明白一些基础概念: 每一个变量.函数都有其作用的范围,超出作用不得使用,这个叫做作用域. 二.全局变量.局部变量: 1.全局变量: (1)在全局范围内声明的变量,如var a=1; (2)只有赋值没有声明的值,如a=2; (注:如果a=2在函数环境中,也是全局变量) 2.局部变量: 写入函数中的变量,叫做局部变量. 3.作用: (1)程序的安全.

  • 20个必会的JavaScript面试题(小结)

    问题1:JavaScript 中 undefined 和 not defined 的区别 JavaScript 未声明变量直接使用会抛出异常:var name is not defined,如果没有处理异常,代码就停止运行了. 但是,使用typeof undeclared_variable并不会产生异常,会直接返回 undefined. var x; // 声明 x console.log(x); //output: undefined console.log(typeof y); //outp

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

    前言 本文针对目前常见的面试题,仅提供了相应的核心原理及思路,部分边界细节未处理.后续会持续更新,希望对你有所帮助. 1. 实现一个call函数 // 思路:将要改变this指向的方法挂到目标this上执行并返回 Function.prototype.mycall = function (context) { if (typeof this !== 'function') { throw new TypeError('not funciton') } context = context || w

  • JS操作时间 - UNIX时间戳的简单介绍(必看篇)

    准备知识 GMT(Greenwich Mean Time) - 格林尼治时间.UTC(Universal Time Code) - 国际协调时间.PST(Pacific Standard Time,太平洋标准时间). UTC出现的比GMT时间晚,可以认为UTC时间更加精确,不过它们之间只相差几秒钟. UTC开始时间为:1970-01-01T00:00:00.000Z(这种时间设置可以一直精确到毫秒,字母T和Z仅仅是一种格式) UNIX时间戳指的是从1970-01-01T00:00:00.000Z

  • react.js 获取真实的DOM节点实例(必看)

    为了获取真实的dom节点,文本输入框必须有一个 ref 属性,然后 this.refs.[refName] 就会返回这个真实的 DOM 节点. var MyComponent = React.createClass({ handleClick: function() { this.refs.myTextInput.focus(); }, render: function() { return ( <div> <input type="text" ref="m

  • js字符串引用的两种方式(必看)

    如下所示: function setName(obj) { obj.ok = "ccccccc"; } function aa() { var name = new String("hechangmin"); name.ok = "sdf"; //第一种方式 // String.prototype.ok = "aaaaa"; //第二种方式 alert(name.ok); // aaaaa setName(name); ale

  • Java8中的默认方法(面试者必看)

    背景 在Java8之前,定义在接口中的所有方法都需要在接口实现类中提供一个实现,如果接口的提供者需要升级接口,添加新的方法,那么所有的实现类都需要把这个新增的方法实现一遍,如果说所有的实现类能够自己控制的话,那么还能接受,但是现实情况是实现类可能不受自己控制.比如说Java中的集合框架中的List接口添加一个方法,那么Apache Commons这种框架就会很难受,必须修改所有实现了List的实现类 现在的接口有哪些不便 向已经发布的接口中添加新的方法是问题的根源,一旦接口发生变化,接口的实现者

  • 经典的20道AJAX面试题(必知必会)

    1.什么是AJAX,为什么要使用Ajax(请谈一下你对Ajax的认识) 什么是ajax: AJAX是"Asynchronous JavaScript and XML"的缩写.他是指一种创建交互式网页应用的网页开发技术. Ajax包含下列技术: 基于web标准(standards-basedpresentation)XHTML+CSS的表示: 使用 DOM(Document ObjectModel)进行动态显示及交互: 使用 XML 和 XSLT 进行数据交换及相关操作: 使用 XMLH

  • Java面试题及答案集锦(基础题122道,代码题19道)

    Java基础面试题及答案集锦(基础题122道,代码题19道),具体详情如下所示: 1.面向对象的特征有哪些方面 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面.抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节.抽象包括两个方面,一是过程抽象,二是数据抽象. 2.继承: 继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法.对象的一个新类可以从现有的类中派生,这个过程称为类继承.新类继承了原始类的特性

  • 20行JS代码实现网页刮刮乐效果

    分享一段用canvas和JS制作刮刮乐的代码,JS部分去掉注释不到20行代码 效果如下 盖伦.jpg 刮刮乐.gif HTML部分 <body> ![](img/gailun.jpg) <canvas id="canvas" width="400" height="300"></canvas> </body> 没什么要特别注意的 为了效果加了些CSS样式 CSS部分 <style type=&

  • 20行JS代码实现粘贴板复制功能

    使用剪贴板是一项基本技能.作为码农都应知道, Tab , Ctrl/Cmd + A , Ctrl / Cmd + C 以及 Ctrl / Cmd + V 分别是自动聚焦.复制.粘贴的快捷键. 而对普通用户可能就不太容易了.即使用户知道剪贴板是什么,(除了)那些眼神极好或反应很快的人,其他情况下很难以突出显示他们想要的确切文字.若用户不知道键盘快捷键,也看不到隐藏的编辑菜单,或从未使用右键菜单或不知道长按触屏弹出选项菜单,那么他很可能无法察觉到复制功能. 那么我们是否应该提供一个"复制到剪贴板&q

  • Vue.js原理分析之nextTick实现详解

    前言 tips:第一次发技术文章,篇幅比较简短,主要采取文字和关键代码表现的形式,希望帮助到大家.(若有不正确还请多多指正) nextTick作用和用法 用法:nextTick接收一个回调函数作为参数,它的作用是将回调延迟到下一次DOM更新之后执行,如果没有提供回调函数参数且在支持Promise的环境中,nextTick将返回一个Promise. 适用场景:开发过程中,开发者需要在更新完数据之后,需要对新DOM做一些操作,其实我们当时无法对新DOM进行操作,因为这时候还没有重新渲染,这时候nex

随机推荐