文章目录
10个面试高频手写JS源码
1、树结构转DOM树
树结构如下:
let obj = {
tag: 'DIV',
children: [
{ tag: 'SPAN', children: [] },
{
tag: 'UL',
children: [
{ tag: 'LI', children: [] },
{ tag: 'LI', children: [] }
]
}
]
}
实现代码:
function Tree2DOM(vnode){
// 如果是数字类型转化为字符串
if(typeof vnode === 'number'){
vnode = String(vnode)
}
// 字符串类型直接就是文本节点
if(typeof vnode === 'string'){
return document.createTextNode(vnode)
}
// 根据它的节点名创建节点
const dom = document.createElement(vnode.tag)
// 遍历当前节点的子节点,并添加到它的子节点中,appendChild
vnode.children.forEach(child=>{dom.appendChild(Tree2DOM(child))})
return dom
}
结果如图:
2、DOM树转树结构
<div>
<span></span>
<ul>
<li></li>
<li></li>
</ul>
</div>
// 将上方的DOM转化为下面的树结构对象
{
tag: 'DIV',
children: [
{ tag: 'SPAN', children: [] },
{
tag: 'UL',
children: [
{ tag: 'LI', children: [] },
{ tag: 'LI', children: [] }
]
}
]
}
实现代码:
function DOM2tree(dom){
let obj = {}
obj.tag = dom.tagName
obj.children = []
// 使用children 不使用childNodes 因为childNodes会把所有节点,包括空白节点也遍历
for(let child of dom.children){
obj.children.push(DOM2tree(child))
}
return obj
}
3、setTimeout 实现 setInterval
function mySetTimeout(fn,delay){
let timer = null
const interval = ()=>{
fn()
// 利用闭包更新最新的timerID,并且递归调用达到循环的目的
timer = setTimeout(interval,delay)
}
setTimeout(interval,delay)
return {
// 传出去一个最新的清楚定时器id的方法
cancel(){
clearTimeout(timer)
}
}
}
测试代码:
// 使用
const {cancel} = mySetTimeout(()=>{console.log('正在循环');},1000)
setTimeout(()=>{
cancel()
//4秒后停止循环
},4000)
4、setInterval 实现 setTimout
function mySetInterval(fn,delay){
let timer = setInterval(()=>{
fn()
clearInterval(timer) // 执行完后清除掉自身
},delay)
}
mySetInterval(()=>{console.log('定时执行');},1000)
mySetInterval(()=>{console.log('定时执行');},1000)
5、a == 1 && a == 2 && a == 3
//第一种方法
let a = {
i:1,
toString(){
return this.i++
}
}
console.log(a==1&&a==2&&a==3);
// 第二种方法
a = [1,2,3]
a.join = a.shift
console.log(a==1&&a==2&&a==3);
// 第三种方法
let obj = {
val:1
}
Object.defineProperty(obj,'a',{
get(){
return this.val++
}
})
console.log(obj.a==1&&obj.a==2&&obj.a==3);
6、AJAX原生实现
const ajax = {
// get请求,输出路径,跟回调函数
get(url,fn){
const xhr = new XMLHttpRequest()
// 第三个参数设置是否异步
xhr.open('GET',url,true)
// 发送事件
xhr.send()
//事件绑定 处理服务器端返回的结果
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
// 当返回的为成功时,回调函数
fn(xhr.response)
}
}
},
post(url,fn){
const xhr = new XMLHttpRequest()
xhr.open("POST",url,true)
// 设置响应头
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
xhr.send() // 发送请求体
// 事件绑定
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
fn(xhr.response)
}
}
}
}
// 测试代码
ajax.post('http://127.0.0.1:8000/server',function(res){
console.log(res); // HELLO,AJAX! POST
})
ajax.get('http://127.0.0.1:8000/server',function(res){
console.log(res); // {"id":1,"name":"dasda"}
})
// 服务器代码
// app.get('/server',(req,res)=>{
// //设置响应头--设置允许跨域
// res.setHeader('Access-Control-Allow-Origin','*');
// const person = {id:1,name:'dasda'}
// res.send(person)
// })
// app.post('/server',(request,response)=>{
// //设置响应头--设置允许跨域
// response.setHeader('Access-Control-Allow-Origin','*');
// //设置响应
// response.send('HELLO,AJAX! POST');
// });
7、instance of 关键字
function MyInstanceOf(son,father){
// 目标对象的原型对象
let fp = father?.prototype
// 自身的原型链
let sp = son?.__proto__
// 判断自身的原型是否在目标对象的原型链上
while(sp){
if(sp == fp){
return true
}
// 向上找原型
sp = sp.__proto__
}
// 不在原型链上返回false
return false
}
console.log(MyInstanceOf([],Object)); // true
console.log([] instanceof Object); // true
8、JSON.parse
function parse(obj){
// return eval("(" + obj + ")") // 不安全
return (new Function("'use strict'; return " + obj ))() // 推荐使用第二个 安全
// return (function(obj){return obj}(obj)) 函数声明做不到反序列
}
let xxx = JSON.stringify({name:18})
console.log(parse(xxx));
9、LRU缓存
/*
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
*/
var LRUCache = function(capacity) {
this.size = capacity
this.map = new Map()
};
LRUCache.prototype.get = function(key) {
if(this.map.has(key)){
let res = this.map.get(key)
this.map.delete(key)
this.map.set(key,res)
return res
}else{
return -1
}
};
LRUCache.prototype.put = function(key, value) {
if(this.map.has(key)){
this.map.delete(key)
this.map.set(key,value)
}else{
if(this.map.size >= this.size){
this.map.delete(this.map.keys().next().value)
}
this.map.set(key,value)
}
};
10、new 关键字实现
new
关键字会进行如下的操作:
- 创建一个空的简单JavaScript对象(即
{}
);- 为步骤1新创建的对象添加属性
__proto__
,将该属性链接至构造函数的原型对象 ;- 将步骤1新创建的对象作为
this
的上下文 ;- 如果该函数没有返回对象,则返回
this
。
function MyNew(fn,...args){
// 创建一个空对象
const obj = {}
// 将新对象的__proto__指向它构造函数的原型
Object.setPrototypeOf(obj,fn.prototype)
// 调用构造函数,传参数初始化,并将this改为obj
const result = fn.apply(obj,args)
// 判断构造函数有没有返回值,1.返回的是基础类型,则返回实例对象,2.返回函数则返回函数,3,返回对象则返回它指定的对象
return result instanceof Object? result:obj
}
function Person(name,age){
this.name = name
this.age = age
return 22 // 实例对象
// return function(){} // 函数
// return {} // 指定对象
}
let p1 = new Person('小米',18) // Person {name: "小米", age: 18}
let p2 = MyNew(Person,'孙悟空',29) // Person {name: "孙悟空", age: 29}
console.log(p1);
console.log(p2);