前言
本文针对目前常见的面试题,仅提供了相应的核心原理及思路,部分边界细节未处理。后续会持续更新,希望对你有所帮助。
1. 实现一个call函数
// 思路:将要改变this指向的方法挂到目标this上执行并返回Function.prototype.mycall = function (context) {if (typeof this !== 'function') {throw new TypeError('not funciton')}context = context || windowcontext.fn = thislet arg = [...arguments].slice(1)let result = context.fn(...arg)delete context.fnreturn result}复制代码
2. 实现一个apply函数
// 思路:将要改变this指向的方法挂到目标this上执行并返回Function.prototype.myapply = function (context) {if (typeof this !== 'function') {throw new TypeError('not funciton')}context = context || windowcontext.fn = thislet resultif (arguments[1]) {result = context.fn(...arguments[1])} else {result = context.fn()}delete context.fnreturn result}复制代码
3. 实现一个bind函数
// 思路:类似call,但返回的是函数Function.prototype.mybind = function (context) {if (typeof this !== 'function') {throw new TypeError('Error')}let _this = thislet 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.prototypewhile (true) {if (leftValue === null) {return false}if (leftValue === rightValue) {return true}leftValue = leftValue.__proto__}}复制代码
5. Object.create的基本实现原理
// 思路:将传入的对象作为原型function create(obj) {function F() {}F.prototype = objreturn 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 = namethis.age = age}let obj = myNew(person)('chen', 18) // {name: "chen", age: 18}复制代码
7. 实现一个基本的Promise
// 未添加异步处理等其他边界情况// ①自动执行函数,②三个状态,③thenclass Promise {constructor (fn) {// 三个状态this.state = 'pending'this.value = undefinedthis.reason = undefinedlet 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)}}// thenthen(onFulfilled, onRejected) {switch (this.state) {case 'fulfilled':onFulfilled()breakcase 'rejected':onRejected()breakdefault:}}}
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 somethingsetTimeout (arguments.callee, 500)}, 500)
11. js实现一个继承方法
// 借用构造函数继承实例属性function Child () {Parent.call(this)}// 寄生继承原型属性(function () {let Super = function () {}Super.prototype = Parent.prototypeChild.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('获取数据了')},set(newVal) {console.log('数据更新了')input.value = newValspan.innerHTML = newVal}})// 输入监听input.addEventListener('keyup', function(e) {obj.text = e.target.value})复制代码
完整实现可前往之前写的:这应该是最详细的响应式系统讲解了
14. 实现一个简单路由
// hash路由class Route{constructor(){// 路由存储对象this.routes = {}// 当前hashthis.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.clientHeightfunction lazyLoad () {// 滚动卷去的高度let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTopfor (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.documentElementlet width = doc.getBoundingClientRect().widthlet rem = width / 75doc.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'// 默认为异步trueconst 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 += '&'// 拼接urlurl += 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.offsetLeftlet diffY = e.clientY - drag.offsetTopdrag.onmousemove = function (e) {// 拖拽元素移动的距离 = 鼠标与可视区边界的距离 - 鼠标与拖拽元素边界的距离let left = e.clientX - diffXlet 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 = nullthis.onmouseup = null}}}复制代码
19. 实现一个节流函数
// 思路:在规定时间内只触发一次function throttle (fn, delay) {// 利用闭包保存时间let prev = Date.now()return function () {let context = thislet arg = argumentslet 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 = nullreturn function () {let context = thislet arg = arguments// 在规定时间内再次触发会先清除定时器后再重设定时器clearTimeout(timer)timer = setTimeout(function () {fn.apply(context, arg)}, delay)}}function fn () {console.log('防抖')}addEventListener('scroll', debounce(fn, 1000))复制代码
