JS基础面试题
3640字约12分钟
2024-07-09
一、JS中数据数据类型
分为简单数据类型和复杂数据类型两种 基本数据类型分为
- Number
- String
- Boolean
- Null
- Undefined
- symbol 复杂数据类型分为 Array,Object,Function,Reg Exp 他们的区别是
- 简单数据类型:是存储在栈中,如果涉及到引用就重新开辟一个内存空间,然后将内容拷贝过去
- 复杂数据类型:是存储在堆中的,变量保存的是他的内存地址,如果设计到引用是将内存地址拷贝给其他变量,所以如果内存地址中内容发生改变,他们的引用因为是同一内存就会发生改变
二、数组的常用方法
- 增:push、unshift、splice、content
- 删:pop、shift、splice、slice
- 改:splice、
- 查:indexOf、includes、find
- 排序:reverse,sort
- 转换:join
- 迭代:forEach、map、filter、reduce、some、every
三、字符串常用方法
- 增:concat
- 删:slice、subStr、subString
- 改:splice、trim、toLowerCase、toUpperCase
- 查:charAt、indexOf、includes、startWith
- 转换:split
- 匹配:match、search、replace
四、JS中的转换机制
显式转换:Number、parseInt、String、Boolean 隐式转换:
- 比较运算和算数运算的时候
- 会undefined、null、NaN、false、+0、-0、""会转化为false
五、和=的区别
双等于会有隐式转换的过程,内部会将类型转换成同一类型、然后再进行比较 双等于一般用于比较两者的布尔值、不太严谨 三等于一般是严格比较类型和值,一般都使用三等于
六、浅拷贝和深拷贝
浅拷贝一般指的是拷贝对象的表层,一般是Object.assign、concat、拓展运算符等 深拷贝是对对象的所有内容进行拷贝,使两个对象毫不相关、
- JSON正反序列化
- 递归的方式拷贝
- 浏览器提供了一个API :structuredClone
- 函数库lodash,中有一个cloneDeep方法
七:闭包的理解及使用
函数中返回一个函数,函数运行可以访问到上一个函数作用域中的变量,这就是闭包 闭包的使用场景
- 柯里化函数,将接收多个参数的函数转化为单一参数,将之前的参数缓存起来,等到符合条件就一起执行的方法
- 比如一些变量不想污染全局,函数运行返回函数,执行时访问闭包中的变量,比如防抖和节流中的开关和定时器
八、防抖和节流的理解和使用场景
防抖就是回城被打断了,就需要重新按B回城
function debounce(fn, delay = 200) {
let timer = 0
return function() {
// 如果这个函数已经被触发了
if(timer){
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments); // 透传 this和参数
timer = 0
},delay)
}
}
节流就是技能放过了,正在cd,再摁就是技能没有准备好
// 节流函数
function throttle(fn, delay = 200) {
let timer = 0
return function () {
if(timer){
return
}
timer = setTimeout(() =>{
fn.apply(this, arguments); // 透传 this和参数
timer = 0
},delay)
}
}
或者就是发送请求的时候将开关关掉,等请求回来了,再把按钮打开
九、作用域和作用域链
- 全局作用域:就是全局的
- 函数作用域:函数执行时产生的作用域,这里需要注意的是函数在没有运行的时候是以字符串的方式保存在堆中的引用数据类型,执行之后才会产生函数作用域
- 块级作用:定义let会产生块级作用域 当我们需要访问变量的时候,如果本作用域中没有此变量,就会逐层向上查找,一直找到window,如果没有找到就会报not Fund的错误
十、原型和原型链的理解
原型:prototype 每个函数或者类都有一个属性叫prototype,每个对象都有一个属性叫__proto__,对象的__proto__指向他所属类的prototype 当需要查找对象上的某个属性时,就通过__proto__按照原型链从下至上查找,一直查到Object.prototype
十一、如何实现继承
- 原型链继承 :子类的原型等于父类的实例
- 构造函数继承:在子组件内部使用call执行获得父组件的实例
- 组合继承:二者合并
- Object.create:实现对象浅拷贝,深层次就会出现问题
- 类中使用extends
十二、this指向问题
一般来说,this指向的是调用 . 之前的对象,
- 比如事件对象就是事件对象.fn this指向事件对象
- 执行定时器相当于 window.fn this指向window
- 在实例内部,this就是指向实例 箭头函数有this,注意是有this 他的this是在定义的时候就已经确定了也就是说编译的时候绑定了this而不是运行的时候绑定this,在哪里定义的this就指向哪里。这里有个坑是事件对象的箭头函数this指向window
十三、JS中执行上下文和执行栈
执行上下文就是函数运行过程中创建的作用域,函数内部所创建的变量外部都不能被访问到 执行栈就是相当于一个加工厂,当函数执行的时候,就会创建一个函数执行上下文,然后将其压入到栈中,当函数执行完毕的时候将函数弹出 js执行的流程
- 创建全局上下文,将其压入到执行栈中
- 遇到单行脚本,就执行然后将其压入到栈中,执行完毕之后出栈
- 遇到函数,就创建函数执行上下文,然后将其押入到栈中,执行第一行脚本再将其压入到栈中,执行完毕后出栈
- 函数内所有内容都执行完毕了,将函数执行上下文出栈,继续执行
- 当所有的代码都执行完毕的时候,全年据上下文也会被出栈
十四、事件和事件流
事件就是与dom文件交互发生的各种事件 事件流就是事件会在元素上发生,然后逐级向上传播,最后到达document 阻止事件冒泡 event.preventDefault()
十五、typeof 和 instanceof
typeof只能判断基本数据类型,如果遇到大多数引用数据类型就只能返回object instanceof用于检测实例是否由此构造函数创建,返回true / false 如果需要检测通用数据类型可以使用 Object.prototype.toString,调用此方法会统一返回 [object 类型] 封装函数就能识别数据类型
十六、事件代理的原理和使用场景
就是通过事件冒泡的机制,子元素触发,父元素也能捕获到事件 使用方法为 addListenerEvent 使用场景是给很多子元素创建代理,或者子元素有可能后面才创建
十七、new操作之后做了什么
- 创建一个新的对象,
- 将对象与创建的函数通过原型链联系起来
- 将this绑定在实例中
- 根据构造函数的返回值返回内容,如果没有写就返回实例
十八、bind、call、apply的区别
他们都是能修改this指向的方法
- call是让函数执行,让函数的this指向call的第一个参数,然后将剩余的参数传入到函数中
- apply也是让函数执行,然后this指向apply的第一个参数,不同的是传入的参数是以数组的方式
- bind是返回一个被改变this的函数,传参方式和call相同 call的原理是什么 在传入的第一个参数上面定义一个函数,然后执行他
obj.$fn = this
这样this就能修改为obj了执行的时候将参数传入
十九、事件循环、宏任务和微任务
- 当浏览器执行js脚本的时候,会区分同步任务和异步任务,当遇到同步任务直接执行,当遇到异步任务时就将异步任务放入到异步队列中
- js执行同步任务时,如果遇到微任务,就将其放入到微任务队列中
- 待宏任务执行完毕的时候,就将微任务队列中的每一项拿出开执行,如果期间产生了微任务,就等这次微任务执行完毕之后再执行
- 微任务队列清空之后,执行gui渲染
- 然后循环 常见的宏任务为JS线程、setTimeout、setImmediate等 常见的微任务为promise中的then、catch、finally、nextTick等
二十、async和await是什么,实现原理
async和await实际上是generator的语法糖,他能解决promise的过多then的问题,使代码更具有线性思维,更容易书写和阅读 其原理是使用gennerator状态机,当执行到yield的时候,就返回一个promise对象,等promise改变状态的时候调用其next方法,以此类推
二一、DOM增删改查
- 增:createElement、createNodeText
- 删:parentNode.reoveChild("元素")
- 改:appendChild、insertBefore
- 查:querySelector
二二、什么是递归、应用场景是什么
递归就是自己调用自己,返回经过处理的内容 使用场景比如深拷贝、数据的树状类型转平面
二三、什么是内存泄漏、怎么解决内存泄漏,怎么避免
内存泄漏就是在计算机中,因为疏忽的原因并没有对使用过的内存进行释放,从而造成内存的浪费就是内存泄漏 怎么解决内存泄漏的问题:把不使用的变量使用null,浏览器就会进行垃圾回收 怎么避免:能用循环就少使用递归,就算使用递归也要及时使内存地址与变量断开连接,让浏览器清空内存
二四、强引用、弱引用、的区别
强引用:是常见的赋值只要有内容应用这个内存地址,那么这个内存地址就不会被删除 软引用:通过weakMap和weakSet创建,只要垃圾回收机制运行,就会被移除
二五、本地存储的方式和区别
- cookie默认在关闭浏览器时实现,但如果设置了失效时间,就按照时间,cookie会在发送请求的时候自动设置在请求头中,服务端也可以使用setCookie设置客户端的cookie
- sessionStoreage默认是在关闭浏览器的时候失效,与cookie相比并不能设置过期时间,也不能随着请求被发送到后端
- localStoreage默认是永久不失效,除非手动删除,一般大小为5M 也不能随着请求被发送到后端 还有一个是indexDB 可以满足存储的一切方法
二六、js运算精度丢失的问题,如何解决
就是在计算机的角度,二进制计算后得到无限不循环小数,在转化为十进制就会出现精度丢失 解决方式是使用matchjs,别人封装的包
二七、懒加载的应用和原理
用于优化长列表,加快首次渲染的速度
- 使用offsetTop获取上边框到元素上边框的距离
- 使用scrollTop获取滚动点到上边框距离
- 如果滚动距离快要接近上边框了,就给元素src让他去加载内容
二八、大文件上传和断点续传
应用场景:上传大文件,应用就会卡住,不能进行任何操作,如果关闭就需要重来,所以需要使用大文件上传和断点续传
- 首先需要对文件进行检测,根据内容生成hash值,需要使用speak-md5这个包,需要安装
- 将hash名发送到后端,看看已经上传了多少了,后端需要返回已经上传成功的切片列表
- 将文件切片,如果文件小就固定大小,如果文件过大,就使用固定片数,切片就算使用文件对象的属性叫slice,根据返回的没有成功的列表把文件切割,然后再将文件的切片发送到后端
- 切片成功的列表也可以用于进度条,如果全部成功了,后端将所有的文件切片合并起来,组成完整的文件
二九、上拉刷新和下拉刷新
上拉刷新本质是触底
- 需要在最下面放一个盒子,监听滚动条的位置
- 当页面的总滚动条小于盒子距离上方的距离+滚动条距离,那我们就认为他触底了,需要触发更新
- 需要注意的是监听滚动条需要使用防抖,浏览器虽然有内置的节流机制,但是触发时间过短,还是会导致触发多次
- 触发成功之后开始启动下拉刷新 下拉的本质是到顶
- 需要监听第一个盒子,监听第一个盒子的位置
- 监听滑动事件,当页面滚动减去元素位置大于一定的插值,就认为是下拉刷新
- 发起请求并渲染 这里需要注意的是有可能初次渲染的问题,骨架屏没有加载出来,就导致监听的盒子触发触底事件,所以需要对骨架屏加载出来才监听事件的处理
三十、单点登录
本质就是将多个系统的登录情况都保存在同一个服务器下,建立信任信息,由信任系统保管所有域名下的登录信息,根据域名和标识符保存
三一、JS预编译
JS脚本在执行之前,执行预编译,遇到var就将变量声明但是不赋值,如果遇到function就将函数声明并赋值
三二、new的实现原理
- 创建一个空对象
- 将空对象连接到new 后面的函数的原型对象上
- 将空对象作为构造函数的上下文
- 如果没有return返回this,如果有返回就正常返回
function fn(name){
console.log('开始实例化');
this.name = name
this.age = 25
}
let f1 = new fn('fu')
console.log(f1);
function myNew(constructor){
return function(...args){
let obj = Object.create(constructor.prototype)
let res = constructor.call(obj, ...args)
return res || obj
}
}
let f2 = myNew(fn)('fu')
console.log(f2);