个人总结的一些JavaScript基础面试题
JavaScript 数据类型
基本类型:String、Number、Blooean、null、undefined、Symbol(ES6新增)、Bigint(ES2020新增)
引用类型:Object
ES6语法知道哪些? 分别怎么用?
声明命令的有 let 和 const , 箭头函数 , 扩展运算符 和 剩余运算符(...) , 模板字符串 , promise 、class
var , let , const 区别
let (定义变量) 、 const(定义常量) 有块级作用域 ,只能在块级作用域访问。而 var没有,可以跨块访问,不能跨函数访问。 然后 var 可以重复声明。
箭头函数的this指向
箭头函数没有this, 父级的this指向谁,箭头函数里的this就指向谁
模板字符串
用一对反引号 ( ` ) 标识,它可以当作普通字符串使用, 如果是表达式的话 需要用 ${} 来包裹。
promise 用法
promise是构造函数, 是用来回调地狱的
promise构造函数本身是同步执行的,而 then 方法是异步指向的(then的返回值是新的promise实例,能够保证链式调用)
promise 有三种状态, pengding 进行中,resolved 已完成,rejected 已失败
promise.all 和 promise.race 分别怎么用?
promise all 方法是 并行执行异步操作, race和它功能一样, 只不过区别在于: all 是谁执行的慢,就返回谁, race和他相反,谁执行的快就返回谁。
async await
async 是英文“异步”的简写, await 可以认为是 async wait 的简写。明白俩个单词就很好理解, async 是用来 声明一个 function 是异步的, 然后 await 就是用于等待一个异步方法执行的。
async 函数:
async返回值: async函数中默认返回值是一个 Promise 对象, return 一个直接量的话, 那么这个直接量会通过 Promise.reslove() 封装成 Promise 对象。
即
- asyncFunction的返回值是 promise 对象
- promise对象的结果是由 asyncFunction 执行的返回值决定
await 表达式:
- await 右侧的表达式一般为promise对象,但也可以是其他的值
- 如果表达式是promise对象,await返回的是promise成功的值
- 如果表达式是其他值,直接将此值作为await的返回值。
如何捕获异常
用 try 和 catch 捕获异常
async function fn(){
try{
let a = await Promise.reject('error')
}catch(error){
console.log(error)
}
}
注意:
- await必须写在async函数中,但async函数中可以没有await
- 如果await的promise失败了,就会抛出异常,需要通过 try...catch来进行捕获。
宏任务和微任务
- JS中用来存储执行回调函数的队列包含2个不同待定的队列
- 宏队列:用来保存执行的宏任务(回调),比如:定时器回调/DOM 事件回调 / ajax回调
- 微队列: 用来保存执行的微任务(回调),比如: promise的回调 / MutationObserver 的回调
- JS执行时会区别这俩个队列
-
JS引擎首先必须先执行所有的初始化同步任务代码
-
每次准备取出第一个宏任务执行前,都要将所有的微任务一个一个取出来执行。
-
手写函数防抖和节流
函数防抖
把频繁触发的动作放在最后执行一次。
const btn = document.querySelector('#btn')
let timer = null
btn.onclick =function(){
if(timer){
console.log('取消');
window.clearTimeout(timer)
}
timer = setTimeout(()=>{
console.log('打印');
timer = null
},1000)
}
函数节流
我一般把 节流 理解为 游戏中的技能冷却时间
const btn = document.querySelector('#btn')
let cd = false
function fn(){
console.log('释放技能');
}
btn.onclick = function(){
if(cd){
console.log('冷却中');
}else{
cd = true
fn()
setTimeout(()=>{
cd = false
},3000)
}
}
手写AJAX
const request = new XMLHttpRequest()
// 设置请求方式 和 url
request.open('get','/xxxx')
request.onreadystatechange = function(){
// 判断 (服务端返回了所有结果, 也就是状态4时)
if(request.readyState === 4){
console.log('请求完成')
// 判断 响应状态码
if(request.status >= 200 && request.status < 300 ){
console.log('请求成功')
}else{
// 处理错误信息
}
}
}
request.send()
this 指向问题
- 如果是一个函数, this 指向 window
- 如果是一个对象, this 指向这个对象
- 如果调用了 call apply bind , this 就指向调用的那个东西
- 如果是一个构造函数, this 指向新的对象实例
- 如果是箭头函数, this 指向 外面的this (这个外面根据以上几种类型判断this)
闭包/立即执行函数是什么
闭包: 就是函数内部访问外部的变量
立即执行函数:
- 声明一个匿名函数
- 马上调用这个匿名函数
立即执行函数的作用: 创建一个独立的作用域。这个作用域的变量,外面访问不到(即避免变量污染)
什么是跨域,什么是 JSONP,什么是 CORS?
什么是跨域
跨域是指从一个域名的网络去请求另一个域名的资源
跨域问题的出现主要是因为浏览器的同源策略, 即浏览器必须保证只有 协议 + 端口 + 域名 才能 实现ajax请求
只要 协议,域名,端口有任何一个的不同,就被当作是跨域。
什么是JSONP?
JSONP(JSONP with Padding) 是一大堆牛逼的程序员想出来的跨域方法。
当前网站创造一个script标签去请求另一个网站的JS,然后这个JS文件会夹带一些数据,这些数据会在我的网站上调用全局函数运行。
JSONP是怎么实现的?
因为我们当前浏览器或者某些条件不支持CORS来实现跨域,所以我们必须要使用另外一种方式来跨域,于是我们就请求JS文件,这个JS文件会执行一个回调,这个回调里面就有我们的数据。
这个回调名字是可以随机生成的,我们把这个随机数以callback为参数传给后台,后台会把这个函数再次返回给我们并执行
JSONP的优点和缺点
优点:
- 支持IE
- 可以跨域(好像是废话哦。。。)
缺点:
- 由于它是script标签,它不能像AJAX一样,读出(拿到)状态码和响应头,它只知道成功和失败(用onload和onerror来监听)。
- 由于它是script标签,它只能发get请求,不支持POST。
JSONP的原理
- 网页有一些标签天生具有跨域的能力,比如:img link iframe script
- JSONP 就是利用 script标签的跨域能力来发送请求的(这也是jsonp跨域只能用get请求的原因所在)
CORS : (Cross-Origin Resource Sharing) 跨域资源共享 。
它是由一个系列传输的 HTTP 头组成, 这些 HTTP 头决定浏览器是否阻止前端JavaScript 代码获取跨域请求的响应.
浅拷贝与深拷贝
浅拷贝
- 赋值运算符
=实现的是浅拷贝,只拷贝对象的引用值; - JavaScript 中数组和对象自带的拷贝方法都是“首层浅拷贝”;
- 数组的
concat 和 slice - Object.assgn 和
...扩展运算符
- 数组的
深拷贝
- 利用 JSON 对象中的 parse 和 stringify
- 有缺点,有个对象中有函数,则会该函数会消失(原因undefined、function、symbol 会在转换过程中被忽略)
- 利用递归来实现每一层都重新创建对象并赋值
使用正则实现 trim()
function trim(string){
return string.replace(/^\s+|\s+$/g,'')
}
不使用 class 如何实现继承? 用 class 又如何实现?
不使用 class 如何实现继承
function Animal(color){
this.color = color
}
function Dog(color,name){
Animal.call(this,color)
this.name = name
}
function Temp(){}
Temp.prototype = Animal.prototype
Dog.prototype = new Temp()
Dog.prototype.constuctor = Dog
Dog.prototype.say = function(){console.log('汪汪');}
let dog = new Dog('黄色','阿黄')
用 class 又如何实现
class Animal{
constructor(color){
this.color = color
}
move(){}
}
class Dog extends Animal{
constructor(color, name){
super(color)
this.name = name
}
say(){}
}
如何实现数组去重
借鉴计数排序的原理,使用hash
unique = (array) => {
const hash = []
for(let i=0;i<array.length; i++){
hash[array[i]] = true
}
const result = []
for(let k in hash){
result.push(k)
}
return result
}
缺点:只支持数字或者字符串数组,如果数组里面有对象,比如 array = [{number:1}, 2],就会出错。
使用 Set
unique = (array) => {
return [...new Set(array)]
// 或者 return Array.from(new Set(array))
}
缺点:不支持对象去重
Map数据结构实现
支出所有类型去重
unique = (array) => {
let map = new Map();
let result = []
for (let i = 0; i < array.length; i++) {
if(map.has(array[i])) { // 判断 map 中是否已有该 key
continue
} else { // 如果 map 中没有该 key,就加入 result 中
map.set(array[i], true);
result.push(array[i]);
}
}
return result;
}