JavaScript———原理题
@
目录
前言
本文针对目前常见的面试题,实现了相应方法的核心原理,部分边界细节未处理。
实现一个call函数
// 通过this获取指定方法,然后挂载在传入的上下文
Function.prototype.myCall = function (context) {
// 判断指定方法是否为函数
if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
// 检测上下文是否为空
context = context || window
// 上下文保存指定方法
context.myFun = this
// 获取其余参数
let args = [...arguments].slice(1)
let result = context.myFun(args)
// 删除该属性
delete context.myFun
return result
}
实现一个apply函数
Function.prototype.myApply = function (context) {
// 判断指定方法是否为函数
if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
// 检测上下文是否为空
context = context || window
// 上下文保存指定方法
context.myFun = this
let result
// 参数判断 其余参数是否为一个数组
if (arguments[1] && Array instanceof arguments[1]) {
result = context.myFun(...arguments[1])
} else {
result = context.myFun()
}
// 删除该属性
delete context.myFun
return result
}
实现一个bind函数
Function.prototype.myBind = function (context) {
// 判断指定方法是否为函数
if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
// 保存指定方法
let _this = this
// 获取初始参数
let arg = [...arguments].slice(1)
// 返回绑定了上下文的函数
return function F(...args) {
// 处理函数被使用new的情况
if (this instanceof F) {
return new _this(...arg, ...args)
} else {
return _this.apply(context, arg.concat(...args))
}
}
}
instanceof的原理
// 可通过constructor属性比较,但是constructor属性具有被改写的风险
// 用原型和原型对象进行比较
function myInstanceof(obj, cons) {
let leftVal = obj.__proto__
let rigthVal = cons.prototype
while (true) {
if (leftVal) {
return false
}
if (leftVal === rigthVal) {
return true
}
leftVal = leftVal.__proto__
}
}
Object.create的基本实现原理
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}
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}
实现一个基本的Promise
// 判断变量否为function
const isFunction = variable => typeof variable === 'function'
// 定义Promise的三种状态常量
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class MyPromise {
constructor(handle) {
if (!isFunction(handle)) {
throw new Error('MyPromise must accept a function as a parameter')
}
// 添加状态
this._status = PENDING
// 添加状态
this._value = undefined
// 添加成功回调函数队列
this._fulfilledQueues = []
// 添加失败回调函数队列
this._rejectedQueues = []
// 执行handle
try {
handle(this._resolve.bind(this), this._reject.bind(this))
} catch (err) {
this._reject(err)
}
}
// 添加resovle时执行的函数
_resolve(val) {
const run = () => {
if (this._status !== PENDING) return
this._status = FULFILLED
// 依次执行成功队列中的函数,并清空队列
const runFulfilled = (value) => {
let cb;
while (cb = this._fulfilledQueues.shift()) {
cb(value)
}
}
// 依次执行失败队列中的函数,并清空队列
const runRejected = (error) => {
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(error)
}
}
/* 如果resolve的参数为Promise对象,则必须等待该Promise对象状态改变后,
当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
*/
if (val instanceof MyPromise) {
val.then(value => {
this._value = value
runFulfilled(value)
}, err => {
this._value = err
runRejected(err)
})
} else {
this._value = val
runFulfilled(val)
}
}
// 为了支持同步的Promise,这里采用异步调用
setTimeout(run, 0)
}
// 添加reject时执行的函数
_reject(err) {
if (this._status !== PENDING) return
// 依次执行失败队列中的函数,并清空队列
const run = () => {
this._status = REJECTED
this._value = err
let cb;
while (cb = this._rejectedQueues.shift()) {
cb(err)
}
}
// 为了支持同步的Promise,这里采用异步调用
setTimeout(run, 0)
}
// 添加then方法
then(onFulfilled, onRejected) {
const {
_value,
_status
} = this
// 返回一个新的Promise对象
return new MyPromise((onFulfilledNext, onRejectedNext) => {
// 封装一个成功时执行的函数
let fulfilled = value => {
try {
if (!isFunction(onFulfilled)) {
onFulfilledNext(value)
} else {
let res = onFulfilled(value);
if (res instanceof MyPromise) {
// 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
res.then(onFulfilledNext, onRejectedNext)
} else {
//否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
onFulfilledNext(res)
}
}
} catch (err) {
// 如果函数执行出错,新的Promise对象的状态为失败
onRejectedNext(err)
}
}
// 封装一个失败时执行的函数
let rejected = error => {
try {
if (!isFunction(onRejected)) {
onRejectedNext(error)
} else {
let res = onRejected(error);
if (res instanceof MyPromise) {
// 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
res.then(onFulfilledNext, onRejectedNext)
} else {
//否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
onFulfilledNext(res)
}
}
} catch (err) {
// 如果函数执行出错,新的Promise对象的状态为失败
onRejectedNext(err)
}
}
switch (_status) {
// 当状态为pending时,将then方法回调函数加入执行队列等待执行
case PENDING:
this._fulfilledQueues.push(fulfilled)
this._rejectedQueues.push(rejected)
break
// 当状态已经改变时,立即执行对应的回调函数
case FULFILLED:
fulfilled(_value)
break
case REJECTED:
rejected(_value)
break
}
})
}
// 添加catch方法
catch (onRejected) {
return this.then(undefined, onRejected)
}
// 添加静态resolve方法
static resolve(value) {
// 如果参数是MyPromise实例,直接返回这个实例
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
// 添加静态reject方法
static reject(value) {
return new MyPromise((resolve, reject) => reject(value))
}
// 添加静态all方法
static all(list) {
return new MyPromise((resolve, reject) => {
/**
* 返回值的集合
*/
let values = []
let count = 0
for (let [i, p] of list.entries()) {
// 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
this.resolve(p).then(res => {
values[i] = res
count++
// 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
if (count === list.length) resolve(values)
}, err => {
// 有一个被rejected时返回的MyPromise状态就变成rejected
reject(err)
})
}
})
}
// 添加静态race方法
static race(list) {
return new MyPromise((resolve, reject) => {
for (let p of list) {
// 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
this.resolve(p).then(res => {
resolve(res)
}, err => {
reject(err)
})
}
})
}
finally(cb) {
return this.then(
value => MyPromise.resolve(cb()).then(() => value),
reason => MyPromise.resolve(cb()).then(() => {
throw reason
})
);
}
}
参考:
Promise实现原理(附源码)
Promise的运行原理以及重写Promise内置类
实现浅拷贝
// 1. ...实现
let copy1 = {...{x:1}}
// 2. Object.assign实现
let copy2 = Object.assign({}, {x:1})
实现一个基本的深拷贝
// 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
}
使用setTimeout模拟setInterval
// 可避免setInterval因执行时间导致的间隔执行时间不一致
setTimeout (function () {
// do something
setTimeout (arguments.callee, 500)
}, 500)
实现一个基本的Event Bus
class EventEmitter {
constructor() {
this._event = this._event || new Map() // 储存事件
this._maxListeners = this._maxListeners || 10 // 设置监听上限
}
}
EventEmitter.prototype.emit = function(type, ...args) {
let handler = null
handler = this._event.get(type)
if (Array.isArray(handler)) {
// 有多个监听者,需要依次触发
for (let i = 0; i < handler.length; i++) {
if (args.length > 0) {
handler[i].apply(this, args)
} else {
handler[i].call(this)
}
}
} else if (handler && typeof handler === 'function') {
if (args.length > 0) {
handler.apply(this, args)
} else {
handler.call(this)
}
}
return true
}
EventEmitter.prototype.addListener = function(type, fn) {
let handler = this._event.get(type)
if (!handler) {
this._event.set(type, fn)
} else if (handler && typeof handler === 'function') {
// 如果handler是函数,说明目前已经存在一个监听者
this._event.set(type, [handler, fn])
} else {
// 已经有多个监听者,直接push
handler.push(fn)
}
}
EventEmitter.prototype.removeListener = function(type, fn) {
let handler = this._event.get(type)
if (handler && typeof handler === 'function') {
// 只有一个监听者,直接删除
this._event.delete(type, fn)
} else if (Array.isArray(handler)) {
// 是数组,说明被监听多次,要找到对应的函数
let position = -1
for (let i = 0; i < handler.length; i++) {
if (handler[i] === fn) {
position = i
}
}
// 如果匹配,从数组中移除
if (position !== -1) {
handler.splice(position, 1)
// 移除后,如果监听只剩一个,那么取消数组,以函数形式保存
if (handler.length === 1) {
this._event.set(type, handler[0])
}
} else {
return this
}
}
}
实现一个双向数据绑定
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
})
实现一个简单路由
class Route {
constructor() {
// 保存所有路由
this.routes = {}
// 当前hash
this.currentHash = ''
// 绑定this,避免监听时this指向改变
this.freshRoute = this.freshRoute.bind(this)
// 初始化监听函数
window.addEventListener('load', this.freshRoute, false)
window.addEventListener('hash', this.freshRoute, false)
}
// 注册并存储路由
storeRoute(path, fn) {
let callback = fn || function() {}
this.routes[path] = callback
}
// hash值改变
freshRoute() {
this.currentHash = location.hash.slice(1) || '/'
this.routes[this.currentHash]()
}
}
实现懒加载
// html:
// <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
// 获取滚动条滚动的高度
let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
// 懒加载函数
function lazyLoad() {
// 获取元素到文档顶部的距离
function getTop(e) {
let h = e.offsetTop
while (e = e.offsetParent) {
h += e.offsetTop
}
return h
}
imgs.forEach(ele => {
// 元素顶部到视窗底部的距离差
let x = scrollTop + clientHeight - getTop(ele)
if (x > 0 && x < clientHeight + ele.height) {
ele.src = ele.getAttribute('data')
}
})
}
// 也可直接通过 e.getBoundingClientRect 获取元素相对视窗的距离做判断
// function lazyLoad() {
// function isIn(e) {
// // 返回元素的大小及其相对于视口的位置。
// let bound = e.getBoundingClientRect();
// let is = (bound.top <= clientHeight) && (bound.top > 0)
// return is
// }
// imgs.forEach(ele => {
// if (is) {
// ele.src = ele.getAttribute('data')
// }
// })
// }
// 定时操作
setInterval(lazyLoad, 1000)
rem实现原理
function setRem () {
let doc = document.documentElement
let width = doc.getBoundingClientRect().width
// 假设设计稿为宽750,则rem为10px
let rem = width / 75
doc.style.fontSize = rem + 'px'
}
手写实现AJAX
// 手写实现AJAX
// xhr 简单实现
let xhr = new XMLHttpRequest()
// 初始化
xhr.open(method, url, async)
// 状态检测
// readyState 属性,该属性表示请求/响应过程的当前活动阶段。
// 0:未初始化。尚未调用 open()方法。
// 1:启动。已经调用 open()方法,但尚未调用 send()方法。
// 2:发送。已经调用 send()方法,但尚未接收到响应。
// 3:接收。已经接收到部分响应数据。
// 4:完成。已经接收到全部响应数据,而且已经可以在客户端使用了。
// 只要 readyState 属性的值由一个值变成另一个值,都会触发一次 readystatechange 事件。
// status:响应的 HTTP 状态。
xhr.onreadystatechange = function () {
if (xhr.readyStatus === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}
// 发送
xhr.send(data)
// 基于promise
function ajax(options) {
// 初始化参数
let url = options.url
let method = options.method.toLocaleLowerCase() || 'get'
let async = options.async
let data = options.data
let xhr = new XMLHttpRequest()
if (options.timeout && options.timeout > 0) {
xhr.timeout = options.timeout
}
// 返回Promise结果
return new Promise((resolve, reject) => {
// 必须在调用 open()之前指定 onreadystatechange事件处理程序才能确保跨浏览器兼容性。
xhr.onreadystatechange = function () {
if (xhr.readyStatus === 4) {
try {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
alert(xhr.responseText)
// 决议成功
resolve && resolve(xhr.responseText)
} else {
// alert('Request was unsuccessful: ' + xhr.status)
// reject && reject(e)
resolve && resolve()
}
} catch (e) {
// 决议失败
reject && reject(e)
console.log('异常处理')
}
}
}
// XMLHttpRequest事件
// loadstart:在接收到响应数据的第一个字节时触发。
// progress:在接收响应期间持续不断地触发。
// error:在请求发生错误时触发。
// abort:在因为调用 abort()方法而终止连接时触发。
// load:在接收到完整的响应数据时触发。
// loadend:在通信完成或者触发 error、 abort 或 load 事件后触发。
// 每个请求都从触发 loadstart 事件开始,接下来是一或多个 progress 事件,
// 然后触发 error、abort 或 load 事件中的一个,最后以触发 loadend 事件结束。
// 请求发生错误监听
xhr.onerror = (err) => reject && reject(err)
// 超时监听
xhr.ontimeout = (err) => reject && reject(err)
// 判断请求方法
// get方法 拼接查询字符串
if (method === 'get') {
let paramArray = []
let encodeParam
if (typeof data === 'object') {
for (let key in data) {
paramArray.push(
encodeURIComponent(key) + '=' + encodeURIComponent(data[key])
)
}
encodeParam = paramArray.join('&')
}
// 判断是否已有查询字符串 没有添加?号 有则添加&
url += url.indexOf('?') == '-1' ? '?' : '&'
// 拼接查询字符串
url += encodeParam
xhr.open(method, url, async)
}
if (method === 'post') {
let dataForm = new FormData(data)
xhr.send(dataForm)
} else {
xhr.send(null)
}
})
}
参考:
XMLHttpRequest—必知必会
你不知道的 XMLHttpRequest
实现一个节流(throttle)函数
函数频繁操作,在规定时间内只执行一次,在大于等于执行周期时才执行,周期内调用不执行。
// 时间戳版本 比较时间
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 throttle(fn, delay) {
let time
return function () {
let context = this
let arg = arguments
if (!time) {
time = setTimeout(() => {
time = null
fn.apply(context, args)
}, delay)
}
}
}
// 精简版
function throttle(fn, delay) {
let bool = true
return function () {
let context = this
let arg = arguments
if (bool) {
fn.apply(context, args)
bool = false
setTimeout(() => {
bool = true
}, 2000)
}
}
}
实现一个防抖(debounce)函数
在函数需要频繁触发时,只有当有足够空闲的时间时,才执行一次。
和节流函数的区别:在发生持续触发事件时,防抖设置事件延迟并在空闲时间去触发事件,而节流则是隔一定的时间触发一次。
function debounce(fn, time) {
// 利用闭包保存定时器
let delay
return function () {
let context = this
let arg = arguments
// 清除定时器
clearTimeout(delay)
// 重新指定定时器
delay = setTimeout(() => {
fn.apply(context, arg)
}, time)
}
}