天津做网站印标,专门培训seo的网站,假链接制作网站,国内精美网站界面网址1、手撕防抖与节流、树与对象的转换、递归调用#xff0c;链表头插法
1.1、防抖
防抖函数用于延迟执行某个函数#xff0c;直到过了一定的间隔时间#xff08;例如等待用户停止输入#xff09;后再执行。
即后一次点击事件发生时间距离一次点击事件至少间隔一定时间。
…1、手撕防抖与节流、树与对象的转换、递归调用链表头插法
1.1、防抖
防抖函数用于延迟执行某个函数直到过了一定的间隔时间例如等待用户停止输入后再执行。
即后一次点击事件发生时间距离一次点击事件至少间隔一定时间。
function debounce(fn, wait) {let timer nullreturn function () {if (timer) {clearTimeout(timer)timer null}timer setTimeout(() {fn.call(this, arguments)}, wait)}
}1.2、节流
节流函数用于限制函数的执行频率确保一定时间内只执行一次。
//时间戳版
function throttle(fn, wait) {let date Date.now()return function () {let now Date.now()if (now - date wait) {fn.call(this, arguments)date now}}
}
//定时器版
function throttle(fn, wait) {let timer nullreturn function () {if (!timer) {timer setTimeout(() {fn.call(this, arguments)timer null}, wait)}}
} 1.3、树与对象的转换
参考作者之前的文章
2、水平垂直居中方法 ①flex布局父元素display:flex; justify-content:center; align-items:center; ②父元素position:relative; 子元素position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); ③父元素position:relative; 子元素position:absolute; left:0; top:0; bottom:0; right:0; margin:auto; ④文字的话 text-align:center; line-height 和 height 相等 3、 手写ajax使用promise封装
function getJSON(url) {let promise new Promise((resolve, reject) {let xhr new XMLHttpRequest()xhr.open(GET, url, true)xhr.onreadystatechange function () {if (this.readyState ! 4) returnif (this.status 200) {resolve(this.response)} else {reject(new Error(this.statusText))}}xhr.onerror function () {reject(new Error(this.statusText))}xhr.responseType jsonxhr.setRequestHeader(Accept, application/json)xhr.send(null)})return promise
}4、扁平数组
const flatten (arr) {return arr.reduce((pre, cur) {return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);}, []);
};arr1 [1, [2, 3], [4, [5, [6, 7], 8]]];
5、setTimeout实现setInterval
function myInterval(func, time) {let ids [];function fn() {let id setTimeout(() {func();fn();}, time);ids.push(id);}fn();return ids;
}let id myInterval(() {console.log(Hello World);
}, 500);function clearMyInterval(idList) {idList.forEach((id) {clearTimeout(id);});
}setTimeout(() {clearMyInterval(id);
}, 3000);6、输入url发生了什么 URL 解析 浏览器解析输入的 URL。 DNS 解析 如果域名需要解析进行 DNS 解析。如果已经有缓存的 DNS 记录可以跳过此步骤。 检查缓存 浏览器检查缓存看是否已经有了之前请求过的资源的副本。这包括检查浏览器缓存和可能存在的代理服务器缓存。 有缓存 如果资源已经存在于缓存中并且没有过期浏览器可以跳过后续的步骤直接使用缓存中的资源渲染页面。 无缓存或缓存过期 如果资源不存在于缓存中或者缓存已经过期浏览器将按照正常的流程发起网络请求 TCP 连接拿到IP地址后三次握手建立TCP连接https的话还需要进行TLS加密协议的握手过程 发送请求获取响应连接建立成功之后浏览器会构建请求行、cookie等数据附加到请求头中发给服务器服务器接受请求并解析如果没有对应的资源就404否则检查HTTP请求头有没有包含协商缓存信息前面命中强缓存且已过期的话就会走这个步骤如果验证缓存没有更新过期的缓存依然可以使用就返回304和空响应体如果没有缓存或者资源更新了就读取完整请求并准备http响应进行查询数据库等操作返回200和查询到的资源 TCP 连接 浏览器接收到响应数据之后如果是http1.1以下则直接关闭连接否则双方都可以根据情况选择关闭TCP连接或者保留重用现在浏览器默认都会保持连接(keep-alive) 浏览器渲染 浏览器使用获取到的资源渲染页面。
缓存是一种重要的性能优化手段可以减少网络请求加快页面加载速度。缓存策略通常由服务器端和浏览器端一起决定可以通过 HTTP 头部信息来进行配置。例如使用 Cache-Control 头部可以控制缓存的行为而 ETag 和 Last-Modified 头部可以用于验证缓存是否过期。
7、加载js和css会不会阻塞页面渲染
css用link和import的情况
link标签引入css资源时在火狐浏览器中是异步加载的在谷歌浏览器中是同步加载的。
但如果是通过style标签引入样式则不论何种浏览器均为同步加载。
import是在网页完全载入后才加载在关键路径上创造了更多的网络请求阻塞渲染时间影响浏览器的并行下载多个import导致下载顺序紊乱。
8、重排重绘 重排当渲染树的一部分必须更新并且节点的尺寸发生了变化浏览器会使渲染树中受到影响的部分失效并重新构造渲染树。 ①添加、删除可见的dom ②元素的位置改变 ③元素的尺寸改变外边距、内边距、边框厚度、宽高等几何属性 ④页面渲染初始化 ⑤浏览器窗口尺寸改变 重绘是在一个元素的外观被改变所触发的浏览器行为浏览器会根据元素的新属性重新绘制使元素呈现新的外观。 如何减少reflow、repaint ①不要一条一条的修改DOM的样式可以先定义好css的class然后修改DOM的className。 ②不要把DOM结点的属性值放在一个循环里当成循环里的变量。 ③为动画的HTML元件适用fixed或absolute的position那么修改他们的css是不会reflow 9、深浅拷贝
浅拷贝基本数据类型、扩展运算符()、slice()、concat()、Object.assign() 深拷贝JSON.parse(JSON.stringify())、手写深拷贝、lodash 手撕深拷贝
let obj {lili: { name: lili, person: [lisan, zhangsan] },arr: [1, 2, 3, 4],fruit: apple,
};
function deepclone(obj) {// 检查是否是基本数据类型如果是则直接返回if (obj null || typeof obj ! object) {return obj;}if (Array.isArray(obj)) {let newArray [];for (let i 0; i obj.length; i) {newArray[i] deepclone(obj[i]);}return newArray;} else {let newObj {};for (const key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] deepclone(obj[key]);}}return newObj;}
}
console.log(deepclone(obj));10、手撕合并函数
function deepMerge(target, source) {// 检查参数类型if (typeof target ! object || typeof source ! object) {throw new Error(Both target and source must be objects);}// 遍历source对象的属性for (const key in source) {if (source.hasOwnProperty(key)) {// 如果属性是对象且存在于target中递归深度合并if (typeof source[key] object source[key] ! null target.hasOwnProperty(key) typeof target[key] object target[key] ! null) {if (Array.isArray(target[key])) {target[key] [].concat(target[key], source[key]);} else {target[key] deepMerge(target[key], source[key]);}} else {// 否则直接赋值// target[key] source[key];target[key] [].concat(target[key], source[key]);}}}return target;
}// 使用例子
const targetObject {name: John,age: 30,address: {city: New York,zip: 10001,people: { class: 1班 },},hobbies: [shopping],
};const sourceObject {age: 31,address: {zip: 10002,},hobbies: [reading, traveling],
};const resultObject deepMerge(targetObject, sourceObject);
console.log(resultObject);11、Object和map
共同点键值对的动态集合支持增删
不同点
①构造方式不同
//map
const map new Map()
const map1 new Map([[a,1],[b,2]])
//obj
const obj new Object()
const obj1 Object.create()
②object键的类型必须是String或者Symbol、map键的类型可以是任意类型
③object中key是无序的map中可以是有序的按照插入的顺序返回
④object只能通过Object.key()方法或for in统计数量map有map.size
⑤object可以通过点或中括号访问属性map用map.get()
⑥object不具备Iterator特性不能for of遍历map的keys()、values()、entries()都具有迭代器
⑦object可以用JSON.stringify()进行序列化map只能转化成JSON不能被parse解析
⑧应用场景object做数据存储需要序列化时使用map频繁更新键值对key类型未知时使用
12、http和https的区别
①https协议需要到CA申请证书一般免费证书较少因而需要一定费用。 ②http是超文本传输协议信息是明文传输https则是具有安全性的ssl/tls加密传输协议。 ③http和https使用的是完全不同的连接方式用的端口也不一样前者是80后者是443。 ④http的连接很简单是无状态的HTTPS协议是由SSL/TLSHTTP协议构建的可进行加密传输、身份认证的网络协议比http协议安全。
13、堆和栈
13.1、区别
①堆栈空间分配区别操作系统
栈由操作系统自动分配释放 存放函数的参数值局部变量的值等。其操作方式类似于数据结构中的栈 堆 一般由程序员分配释放 若程序员不释放程序结束时可能由OS回收分配方式倒是类似于链表。 ②堆栈缓存方式区别 栈使用的是一级缓存 他们通常都是被调用时处于存储空间中调用完毕立即释放 堆是存放在二级缓存中生命周期由虚拟机的垃圾回收算法来决定并不是一旦成为孤儿对象就能被回收。所以调用这些对象的速度要相对来得低一些。 ③堆栈数据结构区别 栈是一种先进后出FILO的数据结构 堆可以被看成是一棵树如堆排序。
13.2、最大堆和最小堆
最大堆根结点的键值是所有堆结点键值中最大者且每个结点的值都比其孩子的值大。 最小堆根结点的键值是所有堆结点键值中最小者且每个结点的值都比其孩子的值小。
13.3、 堆栈溢出
JavaScript 的函数调用栈有一定大小限制当函数调用的嵌套层数过多时会导致栈溢出错误。
function recursive() {// 递归出现栈溢出recursive();
}
recursive();
JavaScript 的堆也有大小限制。堆是用来存储变量和对象等数据的一段内存空间当我们创建了大量数据或者数据太大而超过了堆的容量时就会触发堆溢出错误。
let arr [];
while (true) {// 堆溢出arr.push(a);
}14、进程和线程
进程一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间一个进程可以有多个线程。
线程进程中的一个执行任务控制单元负责当前进程中程序的执行。一个进程至少有一个线程一个进程可以运行多个线程多个线程可共享数据。
进程和线程的区别
①进程是资源分配的最小单位线程是程序执行的最小单位资源调度的最小单位 ②进程有自己的独立地址空间每启动一个进程系统就会为它分配地址空间建立数据表来维护代码段、堆栈段和数据段这种操作非常昂贵。 而线程是共享进程中的数据的使用相同的地址空间因此CPU切换一个线程的花费远比进程要小很多同时创建一个线程的开销也比进程要小很多。 ③线程之间的通信更方便同一进程下的线程共享全局变量、静态变量等数据而进程之间的通信需要以通信的方式IPC) 进行。 ④但是多进程程序更健壮多线程程序只要有一个线程死掉整个进程也死掉了而一个进程死掉并不会对另外一个进程造成影响因为进程有自己独立的地址空间。
15、promise.allpromise.any,promise.race,promise allsettled
Promise.allSettled 不会在其中一个 Promise 失败时立即 reject而是等待所有 Promise 完成后再返回结果。
const promise1 Promise.resolve(42);
const promise2 Promise.reject(Oops!);
const promise3 new Promise((resolve) setTimeout(() resolve(Done!), 1000));Promise.allSettled([promise1, promise2, promise3]).then((results) {console.log(results);// results 包含了每个 Promise 的状态和结果// [{ status: fulfilled, value: 42 }, { status: rejected, reason: Oops! }, { status: fulfilled, value: Done! }]}).catch((error) {console.error(Error:, error);});Promise.all 在所有 Promise 全部成功resolved时才会成功但只要有一个 Promise 失败rejected整个 Promise.all 就会立即失败。这种行为被称为“一败俱败”。
传递给 Promise.all 的数组为空 如果传递给 Promise.all 的 Promise 数组为空返回的 Promise 会立即被解决为一个空数组。
如果在某一个promise中reject后那一个函数捕捉catch就不会报错。不然就会走all的catch
16、场景题100000条数据渲染
const renderList async () {const list await getList()const total list.lengthconst page 0const limit 200const totalPage Math.ceil(total / limit)const render (page) {if (page totalPage) returnsetTimeout(() {for (let i page * limit; i page * limit limit; i) {const item list[i]const div document.createElement(div)div.className sunshinediv.innerHTML img src${item.src} /span${item.text}/spancontainer.appendChild(div)}render(page 1)}, 0)}render(page)17、你做的项目如何部署到服务器 function myNew(constructor, ...args) {// 步骤 1创建一个新的空对象const obj {};// 步骤 2将新对象的 __proto__ 指向构造函数的 prototype 属性obj.__proto__ constructor.prototype;// 步骤 3将构造函数的上下文传递给构造函数并执行构造函数const result constructor.apply(obj, args);// 步骤 4如果构造函数返回一个对象则返回该对象否则返回新创建的对象return result instanceof Object ? result : obj;
}
18、路由鉴权
18.1、请求数据 用户登录 用户在登录时通过用户名和密码等方式向后端发起登录请求。后端验证用户身份并在登录成功后生成一个 Token。 Token 存储 将生成的 Token 存储在前端通常是通过 localStorage。axios请求拦截如果存在token在每次请求时自动附加到请求头。 请求时携带 Token 在每次请求后端受保护资源时将 Token 携带在请求头中通常是通过 Authorization 头或自定义头。 后端验证 Token 后端接收到请求后通过解析 Token 验证用户身份和权限。如果 Token 有效且权限足够则返回相应资源否则返回错误状态。
18.2、路由守卫
根据权限决定跳转 如果用户拥有访问权限正常放行让用户访问受保护页面。如果用户没有权限可以将其重定向到登录页或其他提示页面或者显示相应的提示信息。
// 路由表
const routes [{ path: /, component: Home, meta: { requiresAuth: true } },{ path: /admin, component: Admin, meta: { requiresAuth: true, requiresAdmin: true } },{ path: /login, component: Login }
];// 创建路由实例
const router new VueRouter({routes
});// 路由守卫
router.beforeEach((to, from, next) {const isAuthenticated /* 根据用户身份信息判断用户是否已登录 */;const isAdmin /* 根据用户身份信息判断用户是否是管理员 */;if (to.meta.requiresAuth !isAuthenticated) {// 用户未登录重定向到登录页next(/login);} else if (to.meta.requiresAdmin !isAdmin) {// 用户不是管理员可以根据需要进行处理例如重定向到首页next(/);} else {// 用户有权限放行next();}
});// 在 Vue 实例中使用路由
new Vue({el: #app,router,render: h h(App)
});18.3、不同等级的权限控制 获取用户权限信息 在用户登录成功后获取token与用户的权限信息存储在Vuex或者localStorage 定义菜单权限配置 在前端定义一个菜单权限配置包含不同权限下允许访问的菜单项。这可以是一个简单的 JSON 对象或数组其中每个菜单项都包含一个权限属性表示需要的用户权限。 根据用户权限过滤菜单项 根据用户拥有的权限从菜单权限配置中筛选出符合条件的菜单项。 动态生成菜单 使用框架或库的路由功能根据过滤后的菜单项动态生成菜单。这可以在页面加载时或用户登录成功后执行。
// 菜单权限配置
const menuConfig [{ path: /dashboard, name: Dashboard, permission: view_dashboard },{ path: /users, name: Users, permission: manage_users },// 其他菜单项
];// 获取用户权限信息模拟
const userPermissions [view_dashboard, manage_users]; // 实际中应该根据登录成功后的用户信息获取权限// 根据用户权限过滤菜单项
const userMenu menuConfig.filter(item userPermissions.includes(item.permission));// 在 Vue 实例中使用路由
const router new VueRouter({routes: userMenu,
});// 示例组件中动态生成菜单
Vue.component(Sidebar, {template: divrouter-link v-foritem in userMenu :toitem.path :keyitem.path{{ item.name }}/router-link/div,data() {return {userMenu,};},
});// 在 Vue 实例中使用路由和菜单组件
new Vue({el: #app,router,render: h h(App),
});