前端js面试题
1、原型、原型链
原型:js中一切皆对象,对象都有一个隐式的属性_proto_,它指向该对象的原型-prototype(原型对象)
原型链:我们调用对象的某个属性,如果该对象上没有该属性,则会向上在该对象的原型对象上找,有则返回,如果没有的话继续向原型的原型上找,直到Object的原型,Object的prototype为null,如果没有找到该属性则返回undefind。我们把整个访问的过程称为原型链
tips: 通过Object.create(null) 创建的对象没有原型对象
varA=function(){}
A.prototype.n=1
var b=new A()
A.prototype={n:2,m:3}
varc=newA()
console.log(b.n,b.m,c.n,c.m) //1 undefind 2 3
varF=function(){};
Object.prototype.a=function(){
console.log('a()')
};
Function.prototype.b=function(){
console.log('b()')
}
var f=newF();
f.a()//? a()f.b()//? errF.a()//? a()F.b()//? b()
2、闭包、闭包的优缺点、闭包的应用场景
闭包:js的作用域中内部函数可以访问外部函数的变量,反之则不行,但是我们为能够达到这一目的,就使用内部函数应用外部函数的变量,把该内部函数通过外部函数return到外部去执行,就形成了闭包。
缺点:长期占用内存、容易造成内存泄漏
优点:私有话属性或变量、避免外部环境污染
应用场景:
A、防抖(清除旧定时器,开始新的定时器)、节流(上一个定时器没有结束,则return 不执行新的定时器)
B、函数柯里化
C、<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
给li循环绑定点击事件,弹出 i
3、字符串的常用方法
indexOf() 查找某字符串对应的下标,如果找不到返回-1
split(sep) 将字符串按照指定的字符切割成数组元素,sep表示指定的字符
slice(start,end) 截取字符串,start开始的下标,end结束的下标,不包含end本身;如果end为空截取到最后,如果为负数表示倒数。
substr(start,count) 截取字符串,start开始的下标,count截取的长度,如果count为空截取到最后,如果start为负值表示倒数
substring(start,end) 截取字符串,start开始的下标,end结束的下标,不包含end本身,如果end为空截取到最后;如果下标为负数,自动转为0
4、Set、Map、和Array的异同、用法
Set:创建类似数组的数据结构,但成员是唯一(基本数据类型无重复,应用数据类型可以重复)
常用方法:size()、add()、delete()、clear()、has()可以使用该方法求交集/差集、keys()、values()、entries()、forEach()
Map:以键值对的行书存储数据,key可以是任何数据类型
常用方法:size()、set(key,value)、get(key)、delete()、clear()、has()、keys()、values()、entries()、forEach()
Set/Map转Array:
Array.from(set)
const arr = [ ...set ]
const arr = [ ...map.values ]
5、类数组转成数组的方法
const arr = Array.from(args)
const arr = Array.prototype.slice.call(args)或者[].splice.call(args)
const arr = [ ...args ]
6、宏任务微任务
js是单线程,但是它有个事件队列机制来处理异步操作,事件队列中有包含宏任务和微任务。像常用的setTimeout,setInterval就是宏任务,Promise()的.then(()=>{})/.catch(()=>{})/.finally(()=>{}) 回调都是微任务。js先执行主线程任务,然后看如果有可以执行的微任务就会被执行,再看如果有宏任务就被执行,执行完继续主线程任务-然后就一遍一遍这循环执行,这也就是它的事件循环机制
7、深拷贝、浅拷贝
js的数据类型分为基本数据类型(Number/String/Boolean/Null/undefind)和引用数据类型Object(包含Function
/Array/Date/Regex)。浅拷贝对基本数据类型来说拷贝的是值,但对引用数据类型来说拷贝的只是引用, 深拷贝针对的是引用数据类型,拷贝的是值。
我们常用的拷贝方法有:
1、Object.assgin() 仅第一层是深拷贝
2、ES6的扩展运算符(...)仅第一层是深拷贝
3、JSON.parse( JSON.stringify(target) ) 会丢失函数、会把时间对象变成字符串、会把正则对象变成空对象
4、利用递归手写深拷贝方法
function deepClone1(obj) {
var objClone = Array.isArray(obj) ? [] : {};
if (obj && typeof obj === "object") {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === "object") {
objClone[key] = deepClone1(obj[key]);
} else {
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
8、数组去重
1、利用对象访问属性的方法,判断对象中是否存在key
2、利用reduce方法遍历数组,reduce第一个参数是遍历需要执行的函数,第二个参数是item的初始值
varobj={};
vararr=arr.reduce(function(item,next) {
obj[next.key]?'':obj[next.key]=true&&item.push(next);
returnitem;
}, []);
3、利用new Set() 去重,去重之后可以通过Array.from(set)或者 [...set] 把set转回数组
9、cookie sessionstorage localstorage 的异同
相同点:都可以存储数据
不同点:
存储大小:cookie 4KB
sessionstorage和localstorage 5MB
生命周期:
cookie 可以手动设置过期时间
sessionstorage 当前会话,关闭窗口或浏览器就被删除
localstorage 永久保存 除非手动删除
使用api:
cookie 操作的是字符串 比较麻烦需要手动封装方法
sessionstorage和localstorang的话有自己的getItem()和setItem()、clear()方法,使用起来比较方便
10、跨域
因为浏览器存在同源策略,协议+域名+端口 必须完全一致不然就会受到浏览器同源策略限制造成跨域。
跨域有如下常用方法:
1、proxy
2、jsonp 原理是利用<script src="https://xxx/a.html?callback=fnName"></script>不是同源策略限制
3、跨域资源贡献(CORS)服务端设置Access-Control-Allow-Origin即可
4、window.name + iframe 原理是window.name保存的字符串数据可以跨不同页面和不同的域
5、h5的postMessage() + window.onmessage() 再不同页面(或iframe)之间发送消息和接受消息
6、nginx代理 原理是因为跨域是存在与浏览器端,所以通过反向代理通过服务端获取数据
11、apply()、call()、bind() 之间的异同
相同点:三个都是改变this指向的。
区 别:call()和apply()第一个参数是指定的对象,call()之后的参数是传入该函数的值
apply()第二个参数是数组,数组中是函数执行需要的参数
bind()和call()的参数相同,不同的是bind()改变this的指向后不会立即执行,其他两个是立即执行的
tips :使用bind()的时候最好不要直接绑定在Dom上,避免当Dom变化时需要重新绑定
12、防抖、节流函数
防抖:代码实现重在清零clearTimeout
functiondebounce(f,wait) {
lettimer
return(...args)=>{
clearTimeout(timer)
timer=setTimeout(()=>{
f(...args)
},wait)
}
}
节流:代码实现重在开锁关锁timer=timeout; timer=null
functionthrottle(f,wait) {
lettimer
return(...args)=>{
if(timer) {return}
timer=setTimeout(()=>{
f(...args)
timer=null
},wait)
}
}
13、数组降维
Array.prototype.concat.apply([],targetArr);
14、判断数据类型
function toType(obj) {
return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
}
15、箭头函数和普通function的区别
1、写法不同
2、this指向不同
function中的this执行环境的不同而不同
箭头函数的this是它自己执行环境最近非箭头函数的this
3、function可以被new 实例化,箭头函数不可以
4、function可以被声明提前,箭头函数不可以,必须先声明后使用不然会报错
16、数组的常用方法
改变原数组的:push()、pop()、unshift()、shift()、splice() 、sort()、reverse()
不改变原数组:join()、slice()、map()、filter()、forEach()、some()、every()、find()、reduce()
17、new 操作符具体做了什么
1、创建了一个空对象 let obj = {}
2、继承了该函数的原型 obj._ protp_= Fn.prototype
3、改变了该函数的this指向 let result = Fn.call(obj,...args)
4、判断该函数的返回值,如果该返回值是基本数据类型则 return obj ,否则 return result
18、Promise
它用于异步操作,它是一个构造函数,接收一个回调函数,用于异步计算,可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果而且可以在对象之间传递和操作promise,帮助我们处理队列。
有三个状态:
1、pending[待定]初始状态
2、fulfilled[实现]操作成功
3、rejected[被否决]操作失败
常用方法:
1、then()、catch()、finally()
2、all([])接收一个数组,当所有的异步请求完成之后才完成,一旦有某个异步请求报错,则直接catch
3、race([])也接收一个数组,但它是抢占执行,一旦有一个异步请求完成就算完成了
19、函数柯里化
概念:把一个接收多个参数的函数变成接收单一参数 并且返回能够接收新参数的函数
add(1)(2)(3)(4)=10;
function add(num){
var sum=num;
var fn=function(v){
sum+=v;
return fn
};
fn.toString=function(){
return sum
};
return fn
}
console.log(add(1)(2)(3)(4)) // 10
20、斐波那契数列
eg:1、1、2、3、5、8、13...
function fibonacci(n) {
if(n == 1 || n == 2) {
return1
};
returnfibonacci(n - 2) + fibonacci(n - 1);
}
fibonacci(30)
21、缓存策略
1、强缓存:
强缓存两个相关字段:【Expires】-过期时间,【Cache-Control】-过期时长。
强缓存分为两种情况,一种是发送HTTP请求,一种不需要发送。
首先检查强缓存,这个阶段不需要发送HTTP请求,通过查找不同的字段来进行,不同的HTTP版本不同
http1.0版本,使用的是expires,http1.1使用的是cache-control
expires即过期时间,时间是相对于服务器的时间而言的,存在于服务端返回的响应头中,在这个过期时间之· 前可以直接从缓存里面获取数据,无需再次请求。
cache-control,http1.1版本中,使用的是这字段,这个字段采用的时间是过期时长,对应的是max-age
注意点:当expires和cache-control同时存在时,优先考虑cache-control。
当缓存资源失效了,也就是没有命中强缓存,接下来就进入协商缓存
2、协商缓存
强缓存失效后,浏览器在请求头中携带响应的缓存etag来向服务器发送请求,服务器根据对应的tag,来决定是否使用缓存。
分为两种,【last-modified】和【etag】。两者个有优势
last-modified:这个字段表示的是【最后修改时间】,在浏览器第一次个服务器发送请求后,服务器会在响应头中加上这个字段。浏览器接收到后,【如果再次请求】,会在请求头中携带 if-modified-since 这个字段,这个字段的值也就是服务器传来的最后修改时间。服务器拿到请求头中的 if-modified-since 的字段后,其实会和这个服务器中 该资源的最后修改时间 做对比:
如果请求头中的这个值小于最后修改时间,说明要更新了,返回新的资源,跟常规的http请求响应的流程一样,否则返回304,告诉浏览器直接使用缓存。
etag:etag是服务器根据当前文件的内容,对文件生成唯一的标识,比如md5算法,只要里面的内容有改动,这个值就会修改,服务器通过响应头把这个字段给浏览器。浏览器接收到etag值,会在下次请求的时候,将这个值作为【if-none-match】这个字段的内容,发送给服务器。服务器接收到这个【if-none-match】字段后,会跟服务器上该资源的【etag】进行比较。
如果两者一样的话,直接返回304,告诉浏览器直接使用缓存,如果不一样的话,说明内容更新了,返回新的资源,跟常规的http请求响应的流程一样。
22、301、302、303、304状态码有什么区别
301表示永久重定向,请求的资源分配了新的url
302表示临时重定向,请求的资源分配了新的url,本次暂且使用新的url,下次请求可能会改变
303表示请求的资源路径发生改变,使用GET方法请求新url。她与302的功能一样,但是明确指出使用GET方法请求新url。
304表示请求的资源未更新。该状态码不应该认为是一种错误,而是对客户端有缓存情况下服务端的一种响应。
tips:新url指的是,第一次请求返回的location
23、深度优先、广度优先
深度优先遍历:对每一个可能的分支路径深入到不能再深入为止,而且每个结点只能访问一次。递归
要特别注意的是,二叉树的深度优先遍历比较特殊,可以细分为先序遍历、中序遍历、后序遍历(我们前面使用的是先序遍历)。具体说明如下:
先序遍历:对任一子树,先访问根,然后遍历其左子树,最后遍历其右子树。
中序遍历:对任一子树,先遍历其左子树,然后访问根,最后遍历其右子树。
后序遍历:对任一子树,先遍历其左子树,然后遍历其右子树,最后访问根。
广度优先遍历:又叫层次遍历,从上往下对每一层依次访问,在每一层中,从左往右(也可以从右往左)访问结点,访问完一层就进入下一层,直到没有结点可以访问为止 遍历
24、介绍一下webpack
webpack是模块打包机(自动化构建工具),我们常用webpack构建我们的项目框架。在webpack中,包含entry,output,mode,module,plugin,module中我们一般会引入各种loader帮助我们翻译编译代码,(loader是从右往左执行)plugin会引入各种插件,优化构建出来的代码,mode是模式,一般分为development和production。entry是入口,output是出口。