共计 13797 个字符,预计需要花费 35 分钟才能阅读完成。
Promise 实现
基于 Promise
封装Ajax
- 返回一个新的
Promise
实例 - 创立
HMLHttpRequest
异步对象 - 调用
open
办法,关上url
,与服务器建设链接(发送前的一些解决) - 监听
Ajax
状态信息 -
如果
xhr.readyState == 4
(示意服务器响应实现,能够获取应用服务器的响应了)xhr.status == 200
,返回resolve
状态xhr.status == 404
,返回reject
状态
xhr.readyState !== 4
,把申请主体的信息基于send
发送给服务器
function ajax(url) {return new Promise((resolve, reject) => {let xhr = new XMLHttpRequest()
xhr.open('get', url)
xhr.onreadystatechange = () => {if (xhr.readyState == 4) {if (xhr.status >= 200 && xhr.status <= 300) {resolve(JSON.parse(xhr.responseText))
} else {reject('申请出错')
}
}
}
xhr.send() // 发送 hppt 申请})
}
let url = '/data.json'
ajax(url).then(res => console.log(res))
.catch(reason => console.log(reason))
创立 10 个标签,点击的时候弹出来对应的序号
var a
for(let i=0;i<10;i++){a=document.createElement('a')
a.innerHTML=i+'<br>'
a.addEventListener('click',function(e){console.log(this) //this 为以后点击的 <a>
e.preventDefault() // 如果调用这个办法,默认事件行为将不再触发。// 例如,在执行这个办法后,如果点击一个链接(a 标签),浏览器不会跳转到新的 URL 去了。咱们能够用 event.isDefaultPrevented() 来确定这个办法是否 (在那个事件对象上) 被调用过了。alert(i)
})
const d=document.querySelector('div')
d.appendChild(a) //append 向一个已存在的元素追加该元素。}
前端手写面试题具体解答
递归反转链表
// node 节点
class Node {constructor(element,next) {
this.element = element
this.next = next
}
}
class LinkedList {constructor() {
this.head = null // 默认应该指向第一个节点
this.size = 0 // 通过这个长度能够遍历这个链表
}
// 减少 O(n)
add(index,element) {if(arguments.length === 1) {
// 向开端增加
element = index // 以后元素等于传递的第一项
index = this.size // 索引指向最初一个元素
}
if(index < 0 || index > this.size) {throw new Error('增加的索引不失常')
}
if(index === 0) {
// 间接找到头部 把头部改掉 性能更好
let head = this.head
this.head = new Node(element,head)
} else {
// 获取以后头指针
let current = this.head
// 不停遍历 直到找到最初一项 增加的索引是 1 就找到第 0 个的 next 赋值
for (let i = 0; i < index-1; i++) { // 找到它的前一个
current = current.next
}
// 让创立的元素指向上一个元素的下一个
// 看图了解 next 层级 ![](http://img-repo.poetries.top/images/20210522115056.png)
current.next = new Node(element,current.next) // 让以后元素指向下一个元素的 next
}
this.size++;
}
// 删除 O(n)
remove(index) {if(index < 0 || index >= this.size) {throw new Error('删除的索引不失常')
}
this.size--
if(index === 0) {
let head = this.head
this.head = this.head.next // 挪动指针地位
return head // 返回删除的元素
}else {
let current = this.head
for (let i = 0; i < index-1; i++) { // index- 1 找到它的前一个
current = current.next
}
let returnVal = current.next // 返回删除的元素
// 找到待删除的指针的上一个 current.next.next
// 如删除 200,100=>200=>300 找到 200 的上一个 100 的 next 的 next 为 300,把 300 赋值给 100 的 next 即可
current.next = current.next.next
return returnVal
}
}
// 查找 O(n)
get(index) {if(index < 0 || index >= this.size) {throw new Error('查找的索引不失常')
}
let current = this.head
for (let i = 0; i < index; i++) {current = current.next}
return current
}
reverse() {
const reverse = head=>{if(head == null || head.next == null) {return head}
let newHead = reverse(head.next)
// 从这个链表的最初一个开始反转,让以后下一个元素的 next 指向本人,本人指向 null
// ![](http://img-repo.poetries.top/images/20210522161710.png)
// 刚开始反转的是最初两个
head.next.next = head
head.next = null
return newHead
}
return reverse(this.head)
}
}
let ll = new LinkedList()
ll.add(1)
ll.add(2)
ll.add(3)
ll.add(4)
// console.dir(ll,{depth: 1000})
console.log(ll.reverse())
设计一个办法提取对象中所有 value 大于 2 的键值对并返回最新的对象
实现:
var obj = {a: 1, b: 3, c: 4}
foo(obj) // {b: 3, c: 4}
办法有很多种,这里提供一种比拟简洁的写法,用到了 ES10
的Object.fromEntries()
:
var obj = {a: 1, b: 3, c: 4}
function foo (obj) {
return Object.fromEntries(Object.entries(obj).filter(([key, value]) => value > 2)
)
}
var obj2 = foo(obj) // {b: 3, c: 4}
console.log(obj2)
// ES8 中 Object.entries()的作用:var obj = {a: 1, b: 2}
var entries = Object.entries(obj); // [['a', 1], ['b', 2]]
// ES10 中 Object.fromEntries()的作用:Object.fromEntries(entries); // {a: 1, b: 2}
二分查找
function search(arr, target, start, end) {
let targetIndex = -1;
let mid = Math.floor((start + end) / 2);
if (arr[mid] === target) {
targetIndex = mid;
return targetIndex;
}
if (start >= end) {return targetIndex;}
if (arr[mid] < target) {return search(arr, target, mid + 1, end);
} else {return search(arr, target, start, mid - 1);
}
}
// const dataArr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// const position = search(dataArr, 6, 0, dataArr.length - 1);
// if (position !== -1) {// console.log(` 指标元素在数组中的地位:${position}`);
// } else {// console.log("指标元素不在数组中");
// }
手写 Promise.race
该办法的参数是 Promise 实例数组, 而后其 then 注册的回调办法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态 只能扭转一次, 那么咱们只须要把 Promise.race 中产生的 Promise 对象的 resolve 办法, 注入到数组中的每一个 Promise 实例中的回调函数中即可.
Promise.race = function (args) {return new Promise((resolve, reject) => {for (let i = 0, len = args.length; i < len; i++) {args[i].then(resolve, reject)
}
})
}
应用 reduce 求和
arr = [1,2,3,4,5,6,7,8,9,10],求和
let arr = [1,2,3,4,5,6,7,8,9,10]
arr.reduce((prev, cur) => {return prev + cur}, 0)
arr = [1,2,3,[[4,5],6],7,8,9],求和
let arr = [1,2,3,4,5,6,7,8,9,10]
arr.flat(Infinity).reduce((prev, cur) => {return prev + cur}, 0)
arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}],求和
let arr = [{a:9, b:3, c:4}, {a:1, b:3}, {a:3}]
arr.reduce((prev, cur) => {return prev + cur["a"];
}, 0)
验证是否是邮箱
function isEmail(email) {var regx = /^([a-zA-Z0-9_\-])[email protected]([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/;
return regx.test(email);
}
实现一个治理本地缓存过期的函数
封装一个能够设置过期工夫的
localStorage
存储函数
class Storage{constructor(name){this.name = 'storage';}
// 设置缓存
setItem(params){
let obj = {
name:'', // 存入数据 属性
value:'',// 属性值
expires:"", // 过期工夫
startTime:new Date().getTime()// 记录何时将值存入缓存,毫秒级
}
let options = {};
// 将 obj 和传进来的 params 合并
Object.assign(options,obj,params);
if(options.expires){
// 如果 options.expires 设置了的话
// 以 options.name 为 key,options 为值放进去
localStorage.setItem(options.name,JSON.stringify(options));
}else{
// 如果 options.expires 没有设置,就判断一下 value 的类型
let type = Object.prototype.toString.call(options.value);
// 如果 value 是对象或者数组对象的类型,就先用 JSON.stringify 转一下,再存进去
if(Object.prototype.toString.call(options.value) == '[object Object]'){options.value = JSON.stringify(options.value);
}
if(Object.prototype.toString.call(options.value) == '[object Array]'){options.value = JSON.stringify(options.value);
}
localStorage.setItem(options.name,options.value);
}
}
// 拿到缓存
getItem(name){let item = localStorage.getItem(name);
// 先将拿到的试着进行 json 转为对象的模式
try{item = JSON.parse(item);
}catch(error){
// 如果不行就不是 json 的字符串,就间接返回
item = item;
}
// 如果有 startTime 的值,阐明设置了生效工夫
if(item.startTime){let date = new Date().getTime();
// 何时将值取出减去刚存入的工夫,与 item.expires 比拟,如果大于就是过期了,如果小于或等于就还没过期
if(date - item.startTime > item.expires){
// 缓存过期,革除缓存,返回 false
localStorage.removeItem(name);
return false;
}else{
// 缓存未过期,返回值
return item.value;
}
}else{
// 如果没有设置生效工夫,间接返回值
return item;
}
}
// 移出缓存
removeItem(name){localStorage.removeItem(name);
}
// 移出全副缓存
clear(){localStorage.clear();
}
}
用法
let storage = new Storage();
storage.setItem({
name:"name",
value:"ppp"
})
上面我把值取出来
let value = storage.getItem('name');
console.log('我是 value',value);
设置 5 秒过期
let storage = new Storage();
storage.setItem({
name:"name",
value:"ppp",
expires: 5000
})
// 过期后再取出来会变为 false
let value = storage.getItem('name');
console.log('我是 value',value);
实现 apply 办法
apply 原理与 call 很类似,不多赘述
// 模仿 apply
Function.prototype.myapply = function(context, arr) {var context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {result = context.fn();
} else {var args = [];
for (var i = 0, len = arr.length; i < len; i++) {args.push("arr[" + i + "]");
}
result = eval("context.fn(" + args + ")");
}
delete context.fn;
return result;
};
应用 setTimeout 实现 setInterval
setInterval 的作用是每隔一段指定工夫执行一个函数,然而这个执行不是真的到了工夫立刻执行,它真正的作用是每隔一段时间将事件退出事件队列中去,只有当以后的执行栈为空的时候,能力去从事件队列中取出事件执行。所以可能会呈现这样的状况,就是以后执行栈执行的工夫很长,导致事件队列里边积攒多个定时器退出的事件,当执行栈完结的时候,这些事件会顺次执行,因而就不能到距离一段时间执行的成果。
针对 setInterval 的这个毛病,咱们能够应用 setTimeout 递归调用来模仿 setInterval,这样咱们就确保了只有一个事件完结了,咱们才会触发下一个定时器事件,这样解决了 setInterval 的问题。
实现思路是应用递归函数,一直地去执行 setTimeout 从而达到 setInterval 的成果
function mySetInterval(fn, timeout) {
// 控制器,管制定时器是否继续执行
var timer = {flag: true};
// 设置递归函数,模仿定时器执行。function interval() {if (timer.flag) {fn();
setTimeout(interval, timeout);
}
}
// 启动定时器
setTimeout(interval, timeout);
// 返回控制器
return timer;
}
解析 URL Params 为对象
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 后果{user: 'anonymous', id: [ 123, 456], // 反复呈现的 key 要组装成数组,能被转成数字的就转成数字类型 city: '北京', // 中文需解码 enabled: true, // 未指定值得 key 约定为 true}*/
function parseParam(url) {const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 前面的字符串取出来
const paramsArr = paramsStr.split('&'); // 将字符串以 & 宰割后存到数组中
let paramsObj = {};
// 将 params 存到对象中
paramsArr.forEach(param => {if (/=/.test(param)) { // 解决有 value 的参数
let [key, val] = param.split('='); // 宰割 key 和 value
val = decodeURIComponent(val); // 解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则增加一个值
paramsObj[key] = [].concat(paramsObj[key], val);
} else { // 如果对象没有这个 key,创立 key 并设置值
paramsObj[key] = val;
}
} else { // 解决没有 value 的参数
paramsObj[param] = true;
}
})
return paramsObj;
}
实现 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__;
}
}
实现 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);
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
用 Promise 实现图片的异步加载
let imageAsync=(url)=>{return new Promise((resolve,reject)=>{let img = new Image();
img.src = url;
img.οnlοad=()=>{console.log(` 图片申请胜利,此处进行通用操作 `);
resolve(image);
}
img.οnerrοr=(err)=>{console.log(` 失败,此处进行失败的通用操作 `);
reject(err);
}
})
}
imageAsync("url").then(()=>{console.log("加载胜利");
}).catch((error)=>{console.log("加载失败");
})
实现类的继承
类的继承在几年前是重点内容,有 n 种继承形式各有优劣,es6 遍及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深刻了解的去看红宝书即可,咱们目前只实现一种最现实的继承形式。
function Parent(name) {this.parent = name}
Parent.prototype.say = function() {console.log(`${this.parent}: 你打篮球的样子像 kunkun`)
}
function Child(name, parent) {
// 将父类的构造函数绑定在子类上
Parent.call(this, parent)
this.child = name
}
/** 1. 这一步不必 Child.prototype =Parent.prototype 的起因是怕共享内存,批改父类原型对象就会影响子类 2. 不必 Child.prototype = new Parent()的起因是会调用 2 次父类的构造方法(另一次是 call),会存在一份多余的父类实例属性 3. Object.create 是创立了父类原型的正本,与父类原型齐全隔离 */
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {console.log(`${this.parent}好,我是练习时长两年半的 ${this.child}`);
}
// 留神记得把子类的结构指向子类自身
Child.prototype.constructor = Child;
var parent = new Parent('father');
parent.say() // father: 你打篮球的样子像 kunkun
var child = new Child('cxk', 'father');
child.say() // father 好,我是练习时长两年半的 cxk
手写防抖函数
函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则从新计时。这能够应用在一些点击申请的事件上,防止因为用户的屡次点击向后端发送屡次申请。
// 函数防抖的实现
function debounce(fn, wait) {
let timer = null;
return function() {
let context = this,
args = arguments;
// 如果此时存在定时器的话,则勾销之前的定时器从新记时
if (timer) {clearTimeout(timer);
timer = null;
}
// 设置定时器,使事件间隔指定事件后执行
timer = setTimeout(() => {fn.apply(context, args);
}, wait);
};
}
对象数组列表转成树形构造(解决菜单)
[
{
id: 1,
text: '节点 1',
parentId: 0 // 这里用 0 示意为顶级节点
},
{
id: 2,
text: '节点 1_1',
parentId: 1 // 通过这个字段来确定子父级
}
...
]
转成
[
{
id: 1,
text: '节点 1',
parentId: 0,
children: [
{
id:2,
text: '节点 1_1',
parentId:1
}
]
}
]
实现代码如下:
function listToTree(data) {let temp = {};
let treeData = [];
for (let i = 0; i < data.length; i++) {temp[data[i].id] = data[i];
}
for (let i in temp) {if (+temp[i].parentId != 0) {if (!temp[temp[i].parentId].children) {temp[temp[i].parentId].children = [];}
temp[temp[i].parentId].children.push(temp[i]);
} else {treeData.push(temp[i]);
}
}
return treeData;
}
二叉树深度遍历
// 二叉树深度遍历
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(),'反转二叉树')