JS篇
前言该系列详细整理前端面试题和讲解
一、JavaScript有哪些数据类型?
javascript
1. 基本数据类型
Number、 String、 Boolean、 Null、 Undefined、 Symbom(ES6新增)
2. 引用类型
Object、 Array、 Function
二、JavaScript中字符串(String)有哪些常用方法?
scss
let str = "Hello,World" // 原字符串都是这个str
1. split()
作用:字符串分割
使用:arr.split(",")
解析:按照传入的参数进行字符串分割, 分割后的字符串会放入一个数组中
结果:["Hello","World"]
结论:该方法不会改变原字符串!!! 方法的返回值是分割后的数组
2. slice()
作用:字符串截取
使用:arr.slice(0,5)
解析:按照传入的参数进行字符串截取, 从索引0开始,截取长度是5
结果:'Hello'
结论:该方法不会改变原字符串!!! 方法的返回值是截取后的字符串
3. concat()
作用:字符串拼接
使用:arr.concat("!!!")
解析:按照传入的参数进行字符串拼接,拼接在原字符串之后,也可以使用 + 运算符进行拼接
结果:"Hello,World!!!"
结论:该方法不会改变原字符串!!! 方法的返回值是拼接的字符串
4. indexOf()
作用:查找字符串中是否包含某字符串
使用:arr.indexOf("Hello")
结果:存在返回 0 / 不存在返回 -1
结论:该方法不会改变原字符串!!!
5. trim()
作用:去除字符串头尾的空格
使用:arr.trim()
结论:该方法不会改变原字符串!!!
6. replace()
作用:替换字符串
使用:arr.slice("," , "--")
解析:第一个参数是被替换的字符串,第二个参数是替换的字符串
结果:"Hello--World"
结论:该方法不会改变原数组!!!
7. substr()
作用:字符串截取
使用:arr.substr(0,5)
结果:'Hello'
结论:和slice基本一样
8.toLowerCase()
作用:将英文字符都小写
使用:arr.toLowerCase()
结果:'hello,world'
9.toUpperCase()
作用:将英文字符都大写
使用:arr.toUpperCase()
结果:'HELLO,WORLD'
三、JavaScript中数组(Array)有哪些常用方法?
scss
let arr = [1,2,3] // 原数组都是这个arr
1. push()
作用:在数组末尾插入一个元素
使用:arr.push("last")
结果:[1,2,3,"last"]
结论:该方法会改变原数组, 方法的返回值是数组的长度
2. pop()
作用:删除数组的最后一个元素
使用:arr.pop()
结果:[1,2]
结论:该方法会改变原数组, 方法的返回值是删除的元素
3. unshift()
作用:在数组的第一位插入一个元素
使用:arr.unshift(0)
结果:[0,1,2,3]
结论:该方法会改变原数组, 方法的返回值是数组的长度
4. shift()
作用:删除数组的第一个元素
使用:arr.shift()
结果:[2,3]
结论:该方法会改变原数组, 方法的返回值是删除的元素
5. splice()
作用:方法可以用于删除元素或者替换元素
使用:arr.splice(index,num) / arr.splice(index,num,value)
解释:可以有两个或者三个参数, 两个是删除,三个是替换,
第一个参数是开始的索引, 第二个是个数, 第三个是要替换的内容(选填)
结论:该方法会改变原数组, 方法的返回值是被删除的元素或被替换的元素
// 删除
使用:arr.splice(0,1)
结果:[2,3]
结论:删除从第0个索引开始,一共1个元素
//替换
使用:arr.splice(0,1,0)
结果:[0,2,3]
结论:替换从第0个索引开始,一共1个元素,替换的值是0
6. slice()
作用:截取数组中的某一段
使用:arr.slice(0,2)
结果:[1,2]
结论:该方法不会改变原数组!!! 方法的返回值是截取的数组,
获得的是[1,2],但是arr还是[1,2,3]
7. concat()
作用:数组拼接 -- 可以直接拼接一个元素或者拼接一个数组
使用:arr.concat(8) / arr.concat([8,9])
结果:[1,2,3,8] / [1,2,3,8,9]
结论:该方法不会改变原数组!!! 方法的返回值是拼接之后的数组,
获得的是[1,2,3,8]/[1,2,3,8,9],但是arr还是[1,2,3]
四、说说JavaScript中的节流和防抖?
-
节流
解析 :在指定的时间间隔内只执行一次函数调用。如果在这段时间内多次触发事件,只有第一次触发会立即执行函数,后续触发会被忽略。
场景 :一般为了防止一个按钮被快速重复点击, 同时发送多次请求
方案 :定义一个变量,在请求发出后将值改变,等响应之后再将值改回来
代码:inilet isStop = false // 定义一个变量来控制请求 // 假设这是按钮点击函数 function saveInfo(){ if(isStop) return // 如果请求已经发送,这里拦截返回 isStop = true //如果false-可以发送,然后把值改为true-正在发送 // 这是请求的接口 - 封装的promise - 可以进行链式调用 saveInfo().then(res =>{ isStop = false //得到响应把值改为false-可以发送 ... //代码块 }).catch(err =>{ isStop = false //响应失败也要把值改为false-可以发送 }) }
-
防抖
解析 :在指定的时间间隔内,如果连续触发事件,只有最后一次触发会延迟执行函数。如果在延迟期间再次触发事件,延迟会被重新计算。
场景 :一般的搜索框可能会做输入之后自动搜索, 这里为了防止输入一个字符就请求一次 的问题,就会有很多无用的请求发出
方案 :使用一个定时器,在规定时间内没有改变之后,发送请求代码:
scsslet timer = null; // 声明一个定时器 //这是输入框Change时调用的函数 function handleInputChange(){ // 加一个判断防止timer未初始化报错 if(timer != null){ // 每次调用这个函数时都将定时器清除 // 然后往下走就会重新创建一个新的定时器 clearTimeout(timer); } // 设置一个定时器 在规定时间之后调用搜索接口 timer = setTimeOut(()=>{ search().then(res =>{ ... }).catch(err =>{ ... }) }, 2000) } //如果两秒内handleInputChange函数没有再被触发,就会执行搜索的接口, //如果一直在被触发,定时器就会一直被清除,接口也就不会发出
五、说说Promise? 有没有手写过Promise?
含义 :Promise是一种处理异步操作的机制
作用:用于解决回调地狱问题,并使异步代码更具可读性和可维护性。
问题 : 那么什么是回调地域呢?
说到回调地域就要先了解两个概念:异步任务和回调函数
异步任务 :
任务通常分为同步任务和异步任务,同步任务就是一条一条代码往下执行,不能插队的叫做同步任务,而异步任务就是单独到一边去执行,不会影响代码往下走, 不需要等待任务结束的叫做异步任务
javascript
let num1 = 10;
let num2 = 20;
console.log(num1); //先执行
setTimeout(()=>{ //异步
console.log(num1 + num2); //最后执行
}, 5000)
console.log(num2); //第二个执行
// 结果 10 20 30
//说明异步任务是自己单独去一边执行的,不会影响代码往下走
回调函数 :
当一个函数作为参数传入另一个函数,并且他不会立即执行, 而是达到某一个条件时才会执行, 这样的函数就叫做回调函数
javascript
//1.创建异步对象
var xhr=new XMLHttpRequest();
//2.绑定监听事件(接收请求)
xhr.onreadystatechange=function(){
//此方法会被调用4次
//最后一次,readyState==4
//并且响应状态码为200时,才是我们要的响应结果 xhr.status==200
if(xhr.readyState==4 && xhr.status==200){
//把响应数据存储到变量result中
var result=xhr.responseText;
console.log(result);
}
}
//3.打开链接(创建请求)
xhr.open("get","/demo/ajaxDemo",true);
//4.发送请求
xhr.send();
//ajax请求这里的onreadystatechange就是回调函数,
//hr.send()得到响应后,才会触发onreadystatechange函数
回调地域 :
了解了这两个概念之后, 来举一个例子:当如果遇到多个异步任务,又需要按顺序输出时,
scss
//假设以下三个函数都是异步函数, 但是要按顺序输出1、2、3句,
//你不知道异步函数他具体什么时候可以返回, 这时候你要这么做呢?
//是不是根本就没有办法, 甚至没办法动态化
function func1(){
setTimeout(function () {
console.log('这是第一句)
}, 3000)
}
function func2(){
setTimeout(function () {
console.log('这是第二句)
}, 2000)
}
function func3(){
setTimeout(function () {
console.log('这是第三句)
}, 1000)
}
//能做到的就是无限嵌套下去,这就是回调地域,每个函数都要等待上一个函数返回
//但却又不知道什么时候返回,就会形成回调地域
setTimeout(function () {
console.log('这是第一句)
setTimeout(function () {
console.log('这是第二句)
setTimeout(function () {
console.log('这是第三句)
}, 1000)
}, 2000)
}, 3000)
使用promise :
为了解决回调地域, 所以推出了promise来解决这个问题,先来看看promise如何使用
javascript
function func1(){
return new Promise((resolve,reject) =>{
setTimeout(function () {
console.log('这是第一句')
resolve(1);
}, 3000)
})
}
function func2(){
return new Promise((resolve,reject) =>{
setTimeout(function () {
console.log('这是第二句')
resolve(1);
}, 2000)
})
}
function func3(){
return new Promise((resolve,reject) =>{
setTimeout(function () {
console.log('这是第三句')
resolve(1);
}, 1000)
})
}
//把上面三个函数使用promise封装一遍,
//因为promise是写好的, 我们只需要将函数用promise封装一遍就可以使用.then来进行操作了
func1().then(res =>{
func2().then(res =>{
func3()
})
})
//这样就可以实现既可以等待上一个函数执行完成,又可以做到解耦的需求了
手写promise:
markdown
/**
* 首先创建一个Promise类
* 确定Promise类的属性和方法
* 1.当前状态
* 2.成功回调
* 3.失败回调
* 4.then方法
* 5.catch方法
*/
function Promise(){
}
六、说说Promise.all的作用?
作用 :等多个异步任务都完成时,一起返回
场景 :当一个函数同时需要多个异步函数中的数据时,可以使用
代码:
javascript
//设置两个异步函数 -- 分别返回两个值, 你需要把这两个值相加
//也可以试试不用promise去实现看看能不能实现
func1(){
return new Promise((resolve,reject) =>{
setTimeout(function () {
console.log('这是第一句')
resolve(10)
}, 3000)
})
},
func2(){
return new Promise((resolve,reject) =>{
setTimeout(function () {
console.log('这是第2句')
resolve(20)
}, 2000)
})
},
//调用all方法, 参数是所有异步函数的数组
Promise.all([func1(),func2()]).then(res =>{
//这里会将所有的promise按参数的顺序返回promise返回的值
console.log(res) //[10,20]
...执行其他代码
})
七、谈谈深拷贝和浅拷贝?
要了解深浅拷贝,首先需要熟悉js的数据类型,分为:基本数据类型和引用数据类型
对于基本数据类型在内存中直接存储的就是值,所以通过拷贝, 一定会开辟新的内存空间进行存储。
而拷贝引用数据类型时,引用类型在内存中是单独进行存储的,所以会出现深浅拷贝的区分
css
//基本数据类型
let a = 10;
let b = a;
a = 1
console.log(a,b) // 1, 10
修改a的值不会影响b的值, 所以属于深拷贝, 也证实基本数据类型在内存中存储的是值
浅拷贝:当赋值时修改其中一个值, 另一个值也发生改变,就是浅拷贝
深拷贝:当赋值时修改其中一个值, 另一个值不发生改变,就是深拷贝(存在多层嵌套时必须每一层都不会发生改变)
js
//引用数据类型 -- 单层
//赋值运算符
let obj = {
name:"orange"
}
let obj_copy = obj
obj.name = "orange_2"
console.log(obj,obj_copy) //{name:"orange_2"}, {name:"orange_2"}
当改变obj的值时,obj_copy的值也发生了改变,这就是浅拷贝(对于引用类型用赋值运算符,一定是浅拷贝)
//Object.assign()
let obj = {
name:"orange",
}
let obj_copy = Object.assign({},a);
obj.name = "orange_2"
console.log(obj,obj_copy) //{name:"orange_2"}, {name:"orange"}
当改变obj的name时,obj_copy的name 没有发生变化, 此时就是深拷贝
js
//引用数据类型 -- 多层
let obj = {
name:"orange",
like:{
foot:"orange",
color:"orange"
},
}
let obj_copy = Object.assign({},obj);
obj.like.foot = "apple"
console.log(obj,obj_copy) //可以自行打印一下哦,动动手印象更深
打印会发现obj的like.foot变了,obj_copy的like.foot也发生了变化,
为什么在这里Object.assign又变成浅拷贝了呢?
这是相对引用类型而言, 要把引用类型中的所有值都拷贝出来才算是深拷贝,
只有引用类型中还有引用类型没有被拷贝, 就属于浅拷贝
为什么会出现Object.assign有时是深拷贝有时是浅拷贝的原因究竟是什么呢?可以前往从内存的角度分析深浅拷贝, 了解了内存就很好理解深浅拷贝了
js
//如何做到无论多少层都是深拷贝呢?
//JSON.stringify / JSON.parse
let obj = {
name:"orange",
like:{
foot:"orange",
color:"orange"
},
}
let obj_copy = JSON.parse(JSON.stringify(obj));
obj.like.foot = "apple"
console.log(obj,obj_copy)
这时候你会发现无论是外层还是内层都不会被影响了,相当于把每一层的引用类型都单独找了一个新的内存地址存放
如果看完还是云里雾里的小伙伴一定要去看一下从内存的角度分析深浅拷贝,一定会有收获的。
八、JavaScript阻止事件冒泡的方法?
先来看一段代码,来了解一下什么是事件冒泡
typescript
<div class="parent" @click="clickParent">
<div class="children" @click="clickChildren"> children</div>
<div class="children2" @click="clickChildren2">children2</div>
<div class="children3" @click="clickChildren3">children3</div>
</div>
//script
clickParent(){
console.log('clickParent');
},
clickChildren(){
console.log('clickChildren');
},
clickChildren2(){
console.log('clickChildren2');
},
clickChildren3(){
console.log('clickChildren3');
},
这里以vue代码为例, 当点击子元素时, 会触发子元素的点击事件,相对应的,点击子元素也相当于点击父元素, 所以父元素的点击事件也会被触发, 如果还是多层嵌套的话, 会一层一层往外触发事件, 这么一个过程就叫做事件冒泡, 像泡泡一样不断往上
当然很多情况我们不希望父级,或者再上级的事件被触发, 所以这时候有一个方法可以用来阻止事件冒泡。
- e.stopPropagation()
javascript
clickChildren(e){
e.stopPropagation()
console.log('clickChildren');
},
每个事件都包含一个默认的参数event(e),包含一些事件信息啥的,在需要阻止事件冒泡的函数中添加e.stopPropagation()这段语句,就可以阻止事件冒泡了。
- @click.stop (特指vue语法)
arduino
<div class="parent" @click="clickParent">
<div class="children" @click.stop="clickChildren"> children</div>
<div class="children2" @click="clickChildren2">children2</div>
<div class="children3" @click="clickChildren3">children3</div>
</div>
这个使用vue框架时的一种方式,在@click后面点上一个stop就可以实现阻止事件冒泡了。
九、JavaScript阻止默认事件的方法?
ini
<div class="parent">
<div class="children" @click.right="clickChildrenRight"> children</div>
</div>
当元素绑定一个右键事件时, 浏览器右键的话会默认有一个弹窗
假设如果想自己做一个像文件夹一样的功能,使用右键打开弹窗,那么这个默认弹窗就需要禁止掉
javascript
clickChildrenRight(e){
e.preventDefault()
console.log('clickChildrenRight');
},
只需要加上 e.preventDefault() 就可以阻止默认的事件了。
十、简述JavaScript异步线程、轮询机制(事件循环)?
异步线程
- 什么是异步
js分为同步和异步两种模式
同步是代码按顺序执行
异步是代码单独到一边执行,不影响后面代码执行
- 为什么要异步
js是单线程异步可以提高cpu的利用率
- 为什么js是单线程
js为了防止多个线程同时操作dom,例如一个线程创建dom,另一个线程删除dom,js无法判断该如何执行
在HTML5中Web Worker标准允许脚本创建多个线程
- js多个线程
本质上js还是单线程, js 拥有一个主线程, 其他线程都为子线程,而子线程不可以操作dom,提高效率的同时不影响js的运行
轮训机制
js在执行时,会按顺序执行代码,同步任务会进入主线程,排队执行,而异步任务会进入Event Table ,相当于异步任务会统一被带到另一个空间去执行,然后进入Event Queue (事件队列 )等待, 等待主线程的同步任务全部执行完,然后js引擎会到事件队列 中查看是否有异步任务, 如果有就拿第一个到主线程中执行,等这个任务执行完,再次到事件队列中查找排队的异步任务, 一个一个将异步任务拿到主线程中执行,直到事件队列中的异步任务全部执行完毕,这个重复的过程被称为事件循环,整个过程就是js的轮训机制了。
总结
有疏漏的欢迎大家评论提提意见, 期待下一期的前端面试题, 创作中...