JS面试题
性能优化
- 是一个综合性问题,没有标准答案,但是要求尽量全面
- 默写细节问题可能会单独提问:手写防抖,节流
- 只关注核心点,针对面试
性能优化原则
- 多使用内存,缓存或其他方法
- 减少cpu计算量,减少网络加载耗时
- (适用于所有编程的性能优化---空间换时间)
从何入手
-
加载更快
- 减少资源体积:压缩代码
- 减少访问次数:合并代码(减少访问次数),SSR服务器端渲染,缓存
- 使用更快的网络:CDN
-
让渲染更快
-
CSS放在head,js放在body最下面
-
尽早开始执行js,用DOMContentLoaded触发
-
懒加载(图片懒加载,上滑加载更多)
-
对DOM查询进行缓存
-
频繁dom操作,合并到一起插入dom结构
-
节流throttle防抖debounce
-
防抖场景
-
监听一个输入框的文字变化后触发change事件
-
直接用keyup事件,则会触发change事件
-
防抖:用户输入结束或暂停时,才会触发change事件
<input type="text" id="input1"> const input1 = document.getElementById('input1') let timer = null input1.addEventListener('keyup', function () { if (timer) { clearTimeout(timer) } timer = setTimeout(() => { // 模拟触发 change 事件 console.log(input1.value); // 清空定时器 timer = null }, 500) }) // 防抖 function debounce(fn, delay = 500) { // timer 是闭包中的 let timer = null; return function () { if (timer) { clearTimeout(timer) } timer = setTimeout(() => { fn.apply(this, arguments); timer = null }, delay) } } input1.addEventListener('keyup', debounce(function (e) { console.log(e.target); console.log(input1.value) }, 600));
-
-
节流
-
拖拽一个元素时,要随时拿到该元素被拖拽的位置
-
直接用drag事件,则会频繁触发,很容易导致卡顿
-
节流:无论拖拽速度多快,都会每隔100ms触发一次
const div1 = document.getElementById('div1') // let timer = null // div1.addEventListener('drag', function (e) { // if (timer) { // return // } // timer = setTimeout(() => { // console.log(e.offsetX, e.offsetY) // timer = null // }, 100) // }) // 节流 function throttle(fn, delay = 100) { let timer = null; return function () { if (timer) { return } timer = setTimeout(() => { fn.apply(this, arguments) timer = null }, delay) } } div1.addEventListener('drag', throttle(function (e) { console.log(e.offsetX, e.offsetY) }));
-
-
-
SSR(server side render)
- 服务器端渲染:将网页和数据一起加载,一起渲染
- 非SSR(前后端分离):先加载网页,在加载数据,在渲染数据
- 早先的JSP,ASP,PHP,现在的vue React SSR
静态资源加hash后缀,根据文件内容计算hash
文件内容不变,则hash不变,则url不变、
url和文件不变,则会自动触发http缓存机制,返回304
先加载一个预览图,在加载实际图
安全
- xss跨站请求攻击
- 一个博客网站,我发表一篇博客,其中嵌入
<script>
脚本(document.cookie) - 脚本内容:获取cookie,发送到我的服务器(服务器配合跨域)
- 发布这篇博客,有人查看它,我轻松获取访问者cookie
- 预防:
- 替换特殊字符,如
<
变成<
>变成>
<script>
变为<script>
,直接显示,而不会作为脚本执行- 前后端都替换
- 替换特殊字符,如
- 一个博客网站,我发表一篇博客,其中嵌入
- xsrf跨站请求伪造
- 你正在购物,看中了某个商品,商品id是100
- 付费接口是xxx.com/pay?id=100,但没有任何验证
- 我是攻击者,看中了一个商品,id是200
- 我向你发送一封电子邮件,邮件标题很吸引人
- 但邮件正文隐藏着
<img src=xxx.com/pay?id=200/>
- 你查看邮件,就帮我购买了id是200的商品
- 预防:
- 使用post接口
- 增加验证,例如密码,短信验证码,指纹等
-
var和let const 的区别
- var是es5语法,let const 是es6语法;var有变量提升
- var和let是变量,可修改;const是常量,不可修改
- let const有块作用域,var没有
-
split()和join()区别
- split(separator) 方法用于把一个字符串分割成字符串数组。
- separator可选。指定要使用的分隔符。如果省略该参数,则使用逗号作为分隔符
- join() 方法用于把数组中的所有元素放入一个字符串。
- split(separator) 方法用于把一个字符串分割成字符串数组。
-
数组的pop push unshift shift 分别是是什么
-
功能是什么
-
返回值是什么
-
是否会对原数组造成影响
const arr = [10, 20, 30, 40] // pop 方法用于删除并返回数组的最后一个元素。 const popRes = arr.pop() console.log(popRes, arr); // shift 方法用于删除并返回数组的第一个元素。 const shiftRes = arr.shift() console.log(shiftRes, arr) // push const pushRes = arr.push(50) // 返回 原数组length console.log(pushRes, arr) // unshift const unshiftRes = arr.unshift(5) // 返回 原数组length console.log(unshiftRes, arr)
-
-
手写深度比较,模拟lodash isEqual
// 判断是否是对象或数组 function isObject(obj) { return typeof obj === 'object' && obj !== null } // 全相等(深度) function isEqual(obj1, obj2) { if (!isObject(obj1) || !isObject(obj2)) { // 值类型(注意,参与 equal 的一般不会是函数) return obj1 === obj2 } if (obj1 === obj2) { return true } // 两个都是对象或数组,而且不相等 // 1. 先取出 obj1 和 obj2 的 keys ,比较个数 const obj1Keys = Object.keys(obj1); const obj2Keys = Object.keys(obj2); if (obj1Keys.length !== obj2Keys.length) { return false } // 2. 以 obj1 为基准,和 obj2 一次递归比较 for (let key in obj1) { // 比较当前 key 的 val —— 递归!!! const res = isEqual(obj1[key], obj2[key]); if (!res) { return false } } // 3. 全相等 return true } // 测试 const obj1 = { a: 100, b: { x: 100, y: 200 } } const obj2 = { a: 100, b: { x: 100, y: 200 } } // console.log( obj1 === obj2 ) console.log( isEqual(obj1, obj2) ) const arr1 = [1, 2, 3] const arr2 = [1, 2, 3, 4]
-
数组的api,有哪些纯函数?
-
不改变原数组(没有副作用)
-
返回一个新数组
-
纯函数
- concat()
- map()
- filter()
- slice()
-
非纯函数
-
push pop shift unshift
-
forEach
-
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。没有返回值undefined
注意: forEach() 对于空数组是不会执行回调函数的。
-
-
some
- some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。
- some() 方法会依次执行数组的每个元素:
- 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
- 如果没有满足条件的元素,则返回false。
注意: some() 不会对空数组进行检测。
-
-
-
- xss跨站请求攻击
-
-
-
-
-
- 注意: some() 不会改变原始数组。
-
every
every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。 every() 方法使用指定函数检测数组中的所有元素: 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。 如果所有元素都满足条件,则返回 true。 注意: every() 不会对空数组进行检测。 注意: every() 不会改变原始数组
-
reduce
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。 reduce() 可以作为一个高阶函数,用于函数的 compose。 注意: reduce() 对于空数组是不会执行回调函数的。 返回计算结果
-
-
-
-
数组slice和splice的区别
-
功能的区别(slice-切片,splice-剪接)
-
参数和返回值
-
是否是纯函数
//slice是纯函数,返回新数组,不改变原数组 const arr = [10, 20, 30, 40, 50] const arr1 = arr.slice()//不传参数直接返回原函数内容 const arr2 = arr.slice(1, 4);//20,30,40 const arr3 = arr.slice(2);//30,40,50 从2截取到末尾 const arr4 = arr.slice(-3);//30,40,50 倒数第3位开始截取到末尾 // splice 非纯函数,修改原数组,返回被删除的元素数组 const spliceRes = arr.splice(1, 2, 'a', 'b', 'c'); //arr: [10, "a", "b", "c", 40, 50], spliceRes:20,30 const spliceRes1 = arr.splice(1, 2) //arr:[10,40,50] spliceRes1:20,30 const spliceRes2 = arr.splice(1, 0, 'a', 'b', 'c') //arr:[10, "a", "b", "c", 20, 30, 40, 50],spliceRes2:[]
-
-
[10,20,30].map(parseInt)
返回的结果是什么?//parseInt(数值,进制); 每一位数值不能大于等于进制数 const res = [10, 20, 30].map(parseInt) console.log(res) // 拆解 [10, 20, 30].map((num, index) => { return parseInt(num, index); }); //10,NaN,NaN
-
ajax请求get和post的区别?
- get请求一般用于查询操作,post一般用于用户提交操作
- get参数拼接在url上,post放在请求体内(数据体积更大)
- 安全性:post易于防止CSRF
-
函数call和apply的区别
fn.call(this,p1,p2,p3); fn.apply(this,arguments);
-
事件代理(委托)是什
-
闭包是什么,有什么特性?有什么负面影响
- 回顾作用域和自由变量
- 回顾闭包应用场景:作为参数被传入,作为返回值被返回
- 回顾:自由变量的查找,要在函数定义的地方(而非函数执行的地方)
- 负面影响
- 变量会常驻内存,得不到释放,闭包不要乱用
-
如何阻止事件冒泡和默认行为?
-
查找,添加,删除,移动DOM节点的方法?
-
如何减少DOM操作?
- 缓存DOM查询结果
- 多次DOM操作,合并到一次插入