共计 12402 个字符,预计需要花费 32 分钟才能阅读完成。
实现 call 办法
call 做了什么:
- 将函数设为对象的属性
- 执行和删除这个函数
- 指定
this
到函数并传入给定参数执行函数 - 如果不传入参数,默认指向为
window
// 模仿 call bar.mycall(null); | |
// 实现一个 call 办法:// 原理:利用 context.xxx = self obj.xx = func-->obj.xx() | |
Function.prototype.myCall = function(context = window, ...args) {if (typeof this !== "function") {throw new Error('type error') | |
} | |
// this-->func context--> obj args--> 传递过去的参数 | |
// 在 context 上加一个惟一值不影响 context 上的属性 | |
let key = Symbol('key') | |
context[key] = this; // context 为调用的上下文,this 此处为函数,将这个函数作为 context 的办法 | |
// let args = [...arguments].slice(1) // 第一个参数为 obj 所以删除, 伪数组转为数组 | |
// 绑定参数 并执行函数 | |
let result = context[key](...args); | |
// 革除定义的 this 不删除会导致 context 属性越来越多 | |
delete context[key]; | |
// 返回后果 | |
return result; | |
}; |
// 用法:f.call(obj,arg1) | |
function f(a,b){console.log(a+b) | |
console.log(this.name) | |
} | |
let obj={name:1} | |
f.myCall(obj,1,2) // 否则 this 指向 window |
实现类数组转化为数组
类数组转换为数组的办法有这样几种:
- 通过 call 调用数组的 slice 办法来实现转换
Array.prototype.slice.call(arrayLike);
- 通过 call 调用数组的 splice 办法来实现转换
Array.prototype.splice.call(arrayLike, 0);
- 通过 apply 调用数组的 concat 办法来实现转换
Array.prototype.concat.apply([], arrayLike);
- 通过 Array.from 办法来实现转换
Array.from(arrayLike);
实现数组的 push 办法
let arr = []; | |
Array.prototype.push = function() {for( let i = 0 ; i < arguments.length ; i++){this[this.length] = arguments[i] ; | |
} | |
return this.length; | |
} |
JSONP
script 标签不遵循同源协定,能够用来进行 跨域申请,长处就是兼容性好但仅限于 GET 申请
const jsonp = ({url, params, callbackName}) => {const generateUrl = () => { | |
let dataSrc = ''; | |
for (let key in params) {if (Object.prototype.hasOwnProperty.call(params, key)) {dataSrc += `${key}=${params[key]}&`; | |
} | |
} | |
dataSrc += `callback=${callbackName}`; | |
return `${url}?${dataSrc}`; | |
} | |
return new Promise((resolve, reject) => {const scriptEle = document.createElement('script'); | |
scriptEle.src = generateUrl(); | |
document.body.appendChild(scriptEle); | |
window[callbackName] = data => {resolve(data); | |
document.removeChild(scriptEle); | |
} | |
}) | |
} |
实现深拷贝
- 浅拷贝: 浅拷贝指的是将一个对象的属性值复制到另一个对象,如果有的属性的值为援用类型的话,那么会将这个援用的地址复制给对象,因而两个对象会有同一个援用类型的援用。浅拷贝能够应用 Object.assign 和开展运算符来实现。
- 深拷贝: 深拷贝绝对浅拷贝而言,如果遇到属性值为援用类型的时候,它新建一个援用类型并将对应的值复制给它,因而对象取得的一个新的援用类型而不是一个原有类型的援用。深拷贝对于一些对象能够应用 JSON 的两个函数来实现,然而因为 JSON 的对象格局比 js 的对象格局更加严格,所以如果属性值里边呈现函数或者 Symbol 类型的值时,会转换失败
(1)JSON.stringify()
JSON.parse(JSON.stringify(obj))
是目前比拟罕用的深拷贝办法之一,它的原理就是利用JSON.stringify
将js
对象序列化(JSON 字符串),再应用JSON.parse
来反序列化 (还原)js
对象。- 这个办法能够简略粗犷的实现深拷贝,然而还存在问题,拷贝的对象中如果有函数,undefined,symbol,当应用过
JSON.stringify()
进行解决之后,都会隐没。
let obj1 = { a: 0, | |
b: {c: 0} | |
}; | |
let obj2 = JSON.parse(JSON.stringify(obj1)); | |
obj1.a = 1; | |
obj1.b.c = 1; | |
console.log(obj1); // {a: 1, b: {c: 1}} | |
console.log(obj2); // {a: 0, b: {c: 0}} |
(2)函数库 lodash 的_.cloneDeep 办法
该函数库也有提供_.cloneDeep 用来做 Deep Copy
var _ = require('lodash'); | |
var obj1 = { | |
a: 1, | |
b: {f: { g: 1} }, | |
c: [1, 2, 3] | |
}; | |
var obj2 = _.cloneDeep(obj1); | |
console.log(obj1.b.f === obj2.b.f);// false |
(3)手写实现深拷贝函数
// 深拷贝的实现 | |
function deepCopy(object) {if (!object || typeof object !== "object") return; | |
let newObject = Array.isArray(object) ? [] : {}; | |
for (let key in object) {if (object.hasOwnProperty(key)) {newObject[key] = | |
typeof object[key] === "object" ? deepCopy(object[key]) : object[key]; | |
} | |
} | |
return newObject; | |
} |
实现公布 - 订阅模式
class EventCenter{ | |
// 1. 定义事件容器,用来装事件数组 | |
let handlers = {} | |
// 2. 增加事件办法,参数:事件名 事件办法 | |
addEventListener(type, handler) { | |
// 创立新数组容器 | |
if (!this.handlers[type]) {this.handlers[type] = []} | |
// 存入事件 | |
this.handlers[type].push(handler) | |
} | |
// 3. 触发事件,参数:事件名 事件参数 | |
dispatchEvent(type, params) { | |
// 若没有注册该事件则抛出谬误 | |
if (!this.handlers[type]) {return new Error('该事件未注册') | |
} | |
// 触发事件 | |
this.handlers[type].forEach(handler => {handler(...params) | |
}) | |
} | |
// 4. 事件移除,参数:事件名 要删除事件,若无第二个参数则删除该事件的订阅和公布 | |
removeEventListener(type, handler) {if (!this.handlers[type]) {return new Error('事件有效') | |
} | |
if (!handler) { | |
// 移除事件 | |
delete this.handlers[type] | |
} else {const index = this.handlers[type].findIndex(el => el === handler) | |
if (index === -1) {return new Error('无该绑定事件') | |
} | |
// 移除事件 | |
this.handlers[type].splice(index, 1) | |
if (this.handlers[type].length === 0) {delete this.handlers[type] | |
} | |
} | |
} | |
} |
参考 前端进阶面试题具体解答
查找文章中呈现频率最高的单词
function findMostWord(article) { | |
// 合法性判断 | |
if (!article) return; | |
// 参数解决 | |
article = article.trim().toLowerCase(); | |
let wordList = article.match(/[a-z]+/g), | |
visited = [], | |
maxNum = 0, | |
maxWord = ""; | |
article = "" + wordList.join(" ") +" "; | |
// 遍历判断单词呈现次数 | |
wordList.forEach(function(item) {if (visited.indexOf(item) < 0) { | |
// 退出 visited | |
visited.push(item); | |
let word = new RegExp("" + item +" ","g"), | |
num = article.match(word).length; | |
if (num > maxNum) { | |
maxNum = num; | |
maxWord = item; | |
} | |
} | |
}); | |
return maxWord + " " + maxNum; | |
} |
Object.is
Object.is
解决的次要是这两个问题:
+0 === -0 // true | |
NaN === NaN // false |
const is= (x, y) => {if (x === y) { | |
// + 0 和 - 0 应该不相等 | |
return x !== 0 || y !== 0 || 1/x === 1/y; | |
} else {return x !== x && y !== y;} | |
} |
实现 instanceOf
// 模仿 instanceof | |
function instance_of(L, R) { | |
//L 示意左表达式,R 示意右表达式 | |
var O = R.prototype; // 取 R 的显示原型 | |
L = L.__proto__; // 取 L 的隐式原型 | |
while (true) {if (L === null) return false; | |
if (O === L) | |
// 这里重点:当 O 严格等于 L 时,返回 true | |
return true; | |
L = L.__proto__; | |
} | |
} |
Array.prototype.filter()
Array.prototype.filter = function(callback, thisArg) {if (this == undefined) {throw new TypeError('this is null or not undefined'); | |
} | |
if (typeof callback !== 'function') {throw new TypeError(callback + 'is not a function'); | |
} | |
const res = []; | |
// 让 O 成为回调函数的对象传递(强制转换对象)const O = Object(this); | |
// >>>0 保障 len 为 number,且为正整数 | |
const len = O.length >>> 0; | |
for (let i = 0; i < len; i++) { | |
// 查看 i 是否在 O 的属性(会查看原型链)if (i in O) { | |
// 回调函数调用传参 | |
if (callback.call(thisArg, O[i], i, O)) {res.push(O[i]); | |
} | |
} | |
} | |
return res; | |
} |
渲染几万条数据不卡住页面
渲染大数据时,正当应用 createDocumentFragment 和requestAnimationFrame,将操作切分为一小段一小段执行。
setTimeout(() => { | |
// 插入十万条数据 | |
const total = 100000; | |
// 一次插入的数据 | |
const once = 20; | |
// 插入数据须要的次数 | |
const loopCount = Math.ceil(total / once); | |
let countOfRender = 0; | |
const ul = document.querySelector('ul'); | |
// 增加数据的办法 | |
function add() {const fragment = document.createDocumentFragment(); | |
for(let i = 0; i < once; i++) {const li = document.createElement('li'); | |
li.innerText = Math.floor(Math.random() * total); | |
fragment.appendChild(li); | |
} | |
ul.appendChild(fragment); | |
countOfRender += 1; | |
loop();} | |
function loop() {if(countOfRender < loopCount) {window.requestAnimationFrame(add); | |
} | |
} | |
loop();}, 0) |
实现 AJAX 申请
AJAX 是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新以后网页的对应局部,而不必刷新整个网页。
创立 AJAX 申请的步骤:
- 创立一个 XMLHttpRequest 对象。
- 在这个对象上 应用 open 办法创立一个 HTTP 申请,open 办法所须要的参数是申请的办法、申请的地址、是否异步和用户的认证信息。
- 在发动申请前,能够为这个对象 增加一些信息和监听函数。比如说能够通过 setRequestHeader 办法来为申请增加头信息。还能够为这个对象增加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变动时会触发 onreadystatechange 事件,能够通过设置监听函数,来解决申请胜利后的后果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接管实现,这个时候能够通过判断申请的状态,如果状态是 2xx 或者 304 的话则代表返回失常。这个时候就能够通过 response 中的数据来对页面进行更新了。
- 当对象的属性和监听函数设置实现后,最初调 用 sent 办法来向服务器发动申请,能够传入参数作为发送的数据体。
const SERVER_URL = "/server"; | |
let xhr = new XMLHttpRequest(); | |
// 创立 Http 申请 | |
xhr.open("GET", SERVER_URL, true); | |
// 设置状态监听函数 | |
xhr.onreadystatechange = function() {if (this.readyState !== 4) return; | |
// 当申请胜利时 | |
if (this.status === 200) {handle(this.response); | |
} else {console.error(this.statusText); | |
} | |
}; | |
// 设置申请失败时的监听函数 | |
xhr.onerror = function() {console.error(this.statusText); | |
}; | |
// 设置申请头信息 | |
xhr.responseType = "json"; | |
xhr.setRequestHeader("Accept", "application/json"); | |
// 发送 Http 申请 | |
xhr.send(null); |
手写节流函数
函数节流是指规定一个单位工夫,在这个单位工夫内,只能有一次触发事件的回调函数执行,如果在同一个单位工夫内某事件被触发屡次,只有一次能失效。节流能够应用在 scroll 函数的事件监听上,通过事件节流来升高事件调用的频率。
// 函数节流的实现; | |
function throttle(fn, delay) {let curTime = Date.now(); | |
return function() { | |
let context = this, | |
args = arguments, | |
nowTime = Date.now(); | |
// 如果两次工夫距离超过了指定工夫,则执行函数。if (nowTime - curTime >= delay) {curTime = Date.now(); | |
return fn.apply(context, args); | |
} | |
}; | |
} |
Promise 并行限度
就是实现有并行限度的 Promise 调度器问题
class Scheduler {constructor() {this.queue = []; | |
this.maxCount = 2; | |
this.runCounts = 0; | |
} | |
add(promiseCreator) {this.queue.push(promiseCreator); | |
} | |
taskStart() {for (let i = 0; i < this.maxCount; i++) {this.request(); | |
} | |
} | |
request() {if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {return;} | |
this.runCounts++; | |
this.queue.shift()().then(() => { | |
this.runCounts--; | |
this.request();}); | |
} | |
} | |
const timeout = time => new Promise(resolve => {setTimeout(resolve, time); | |
}) | |
const scheduler = new Scheduler(); | |
const addTask = (time,order) => {scheduler.add(() => timeout(time).then(()=>console.log(order))) | |
} | |
addTask(1000, '1'); | |
addTask(500, '2'); | |
addTask(300, '3'); | |
addTask(400, '4'); | |
scheduler.taskStart() | |
// 2 | |
// 3 | |
// 1 | |
// 4 |
实现数组的 map 办法
Array.prototype._map = function(fn) {if (typeof fn !== "function") {throw Error('参数必须是一个函数'); | |
} | |
const res = []; | |
for (let i = 0, len = this.length; i < len; i++) {res.push(fn(this[i])); | |
} | |
return res; | |
} |
二叉树深度遍历
// 二叉树深度遍历 | |
class Node {constructor(element, parent) { | |
this.parent = parent // 父节点 | |
this.element = element // 以后存储内容 | |
this.left = null // 左子树 | |
this.right = null // 右子树 | |
} | |
} | |
class BST {constructor(compare) { | |
this.root = null // 树根 | |
this.size = 0 // 树中的节点个数 | |
this.compare = compare || this.compare | |
} | |
compare(a,b) {return a - b} | |
add(element) {if(this.root === null) {this.root = new Node(element, null) | |
this.size++ | |
return | |
} | |
// 获取根节点 用以后增加的进行判断 放右边还是放左边 | |
let currentNode = this.root | |
let compare | |
let parent = null | |
while (currentNode) {compare = this.compare(element, currentNode.element) | |
parent = currentNode // 先将父亲保存起来 | |
// currentNode 要不停的变动 | |
if(compare > 0) {currentNode = currentNode.right} else if(compare < 0) {currentNode = currentNode.left} else {currentNode.element = element // 相等时 先笼罩后续解决} | |
} | |
let newNode = new Node(element, parent) | |
if(compare > 0) {parent.right = newNode} else if(compare < 0) {parent.left = newNode} | |
this.size++ | |
} | |
// 前序遍历 | |
preorderTraversal(visitor) { | |
const traversal = node=>{if(node === null) return | |
visitor.visit(node.element) | |
traversal(node.left) | |
traversal(node.right) | |
} | |
traversal(this.root) | |
} | |
// 中序遍历 | |
inorderTraversal(visitor) { | |
const traversal = node=>{if(node === null) return | |
traversal(node.left) | |
visitor.visit(node.element) | |
traversal(node.right) | |
} | |
traversal(this.root) | |
} | |
// 后序遍历 | |
posterorderTraversal(visitor) { | |
const traversal = node=>{if(node === null) return | |
traversal(node.left) | |
traversal(node.right) | |
visitor.visit(node.element) | |
} | |
traversal(this.root) | |
} | |
// 反转二叉树:无论先序、中序、后序、层级都能够反转 | |
invertTree() { | |
const traversal = node=>{if(node === null) return | |
let temp = node.left | |
node.left = node.right | |
node.right = temp | |
traversal(node.left) | |
traversal(node.right) | |
} | |
traversal(this.root) | |
return this.root | |
} | |
} |
先序遍历
二叉树的遍历形式
// 测试 | |
var bst = new BST((a,b)=>a.age-b.age) // 模仿 sort 办法 | |
bst.add({age: 10}) | |
bst.add({age: 8}) | |
bst.add({age:19}) | |
bst.add({age:6}) | |
bst.add({age: 15}) | |
bst.add({age: 22}) | |
bst.add({age: 20}) | |
// 先序遍历 | |
// console.log(bst.preorderTraversal(),'先序遍历') | |
// console.log(bst.inorderTraversal(),'中序遍历') | |
// ![](http://img-repo.poetries.top/images/20210522214837.png) | |
// console.log(bst.posterorderTraversal(),'后序遍历') | |
// 深度遍历:先序遍历、中序遍历、后续遍历 | |
// 广度遍历:档次遍历(同层级遍历)// 都可拿到树中的节点 | |
// 应用访问者模式 | |
class Visitor {constructor() {this.visit = function (elem) {elem.age = elem.age*2} | |
} | |
} | |
// bst.posterorderTraversal({// visit(elem) { | |
// elem.age = elem.age*10 | |
// } | |
// }) | |
// 不能通过索引操作 拿到节点去操作 | |
// bst.posterorderTraversal(new Visitor()) | |
console.log(bst.invertTree(),'反转二叉树') |
实现一个拖拽
<style> | |
html, body { | |
margin: 0; | |
height: 100%; | |
} | |
#box { | |
width: 100px; | |
height: 100px; | |
background-color: red; | |
position: absolute; | |
top: 100px; | |
left: 100px; | |
} | |
</style> |
<div id="box"></div>
window.onload = function () {var box = document.getElementById('box'); | |
box.onmousedown = function (ev) { | |
var oEvent = ev || window.event; // 兼容火狐, 火狐下没有 window.event | |
var distanceX = oEvent.clientX - box.offsetLeft; // 鼠标到可视区右边的间隔 - box 到页面右边的间隔 | |
var distanceY = oEvent.clientY - box.offsetTop; | |
document.onmousemove = function (ev) { | |
var oEvent = ev || window.event; | |
var left = oEvent.clientX - distanceX; | |
var top = oEvent.clientY - distanceY; | |
if (left <= 0) {left = 0;} else if (left >= document.documentElement.clientWidth - box.offsetWidth) {left = document.documentElement.clientWidth - box.offsetWidth;} | |
if (top <= 0) {top = 0;} else if (top >= document.documentElement.clientHeight - box.offsetHeight) {top = document.documentElement.clientHeight - box.offsetHeight;} | |
box.style.left = left + 'px'; | |
box.style.top = top + 'px'; | |
} | |
box.onmouseup = function () { | |
document.onmousemove = null; | |
box.onmouseup = null; | |
} | |
} | |
} |
二叉树搜寻
// 二叉搜寻树 | |
class Node {constructor(element, parent) { | |
this.parent = parent // 父节点 | |
this.element = element // 以后存储内容 | |
this.left = null // 左子树 | |
this.right = null // 右子树 | |
} | |
} | |
class BST {constructor(compare) { | |
this.root = null // 树根 | |
this.size = 0 // 树中的节点个数 | |
this.compare = compare || this.compare | |
} | |
compare(a,b) {return a - b} | |
add(element) {if(this.root === null) {this.root = new Node(element, null) | |
this.size++ | |
return | |
} | |
// 获取根节点 用以后增加的进行判断 放右边还是放左边 | |
let currentNode = this.root | |
let compare | |
let parent = null | |
while (currentNode) {compare = this.compare(element, currentNode.element) | |
parent = currentNode // 先将父亲保存起来 | |
// currentNode 要不停的变动 | |
if(compare > 0) {currentNode = currentNode.right} else if(compare < 0) {currentNode = currentNode.left} else {currentNode.element = element // 相等时 先笼罩后续解决} | |
} | |
let newNode = new Node(element, parent) | |
if(compare > 0) {parent.right = newNode} else if(compare < 0) {parent.left = newNode} | |
this.size++ | |
} | |
} |
// 测试 | |
var bst = new BST((a,b)=>b.age-a.age) // 模仿 sort 办法 | |
bst.add({age: 10}) | |
bst.add({age: 8}) | |
bst.add({age:19}) | |
bst.add({age:20}) | |
bst.add({age: 5}) | |
console.log(bst) |
实现一个函数判断数据类型
function getType(obj) {if (obj === null) return String(obj); | |
return typeof obj === 'object' | |
? Object.prototype.toString.call(obj).replace('[object', '').replace(']','').toLowerCase() | |
: typeof obj; | |
} | |
// 调用 | |
getType(null); // -> null | |
getType(undefined); // -> undefined | |
getType({}); // -> object | |
getType([]); // -> array | |
getType(123); // -> number | |
getType(true); // -> boolean | |
getType('123'); // -> string | |
getType(/123/); // -> regexp | |
getType(new Date()); // -> date |
实现一个 call
call 做了什么:
- 将函数设为对象的属性
- 执行 & 删除这个函数
- 指定 this 到函数并传入给定参数执行函数
- 如果不传入参数,默认指向为 window
// 模仿 call bar.mycall(null); | |
// 实现一个 call 办法:Function.prototype.myCall = function(context) { | |
// 此处没有思考 context 非 object 状况 | |
context.fn = this; | |
let args = []; | |
for (let i = 1, len = arguments.length; i < len; i++) {args.push(arguments[i]); | |
} | |
context.fn(...args); | |
let result = context.fn(...args); | |
delete context.fn; | |
return result; | |
}; |
正文完
发表至: javascript
2023-02-21