JavaScript———原理题
Posted 、妤
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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" ></li>
// <li><img src="./imgs/default.png" data="./imgs/2.png" ></li>
// <li><img src="./imgs/default.png" data="./imgs/3.png" ></li>
// <li><img src="./imgs/default.png" data="./imgs/4.png" ></li>
// <li><img src="./imgs/default.png" data="./imgs/5.png" ></li>
// <li><img src="./imgs/default.png" data="./imgs/6.png" ></li>
// <li><img src="./imgs/default.png" data="./imgs/7.png" ></li>
// <li><img src="./imgs/default.png" data="./imgs/8.png" ></li>
// <li><img src="./imgs/default.png" data="./imgs/9.png" ></li>
// <li><img src="./imgs/default.png" data="./imgs/10.png" ></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)
JavaScript高级学习笔记目录(持续更新)
【JavaScript高级】this绑定、绑定优先级、相关面试题与箭头函数
【JavaScript高级】浏览器原理:渲染引擎解析页面步骤、回流和重绘、composite合成、defer与async
【JavaScript高级】JavaScript的运行原理:V8引擎,JS代码执行原理,作用域和作用域链面试题
【JavaScript高级】内存管理与闭包:垃圾回收GC、闭包定义、访问和执行过程、内存泄漏
【JavaScript高级】函数相关知识:函数、纯函数、柯里化、严格模式
【JavaScript高级】对象相关:对象属性、属性描述符(数据属性,存取属性)
【JavaScript高级】原型和继承相关:原型对象、函数原型、原型链和继承、继承的优化、对象判断相关方法
【JavaScript高级】class类、ES6实现继承、ES6对象增强
【JavaScript高级】手写apply()、call()、bind()
【JavaScript高级】ES6常见新特性:词法环境、let、const、模板字符串、函数增强、Symbol、Set、Map
【JavaScript高级】ES7-ES13常用新特性
【JavaScript高级】Proxy和Reflect
以上是关于JavaScript———原理题的主要内容,如果未能解决你的问题,请参考以下文章