前端面试题(js篇)

34 篇文章 0 订阅

1.解释一下什么是闭包

什么是闭包:函数使用了不属于自己的局部变量(函数套函数,里面函数使用了外面函数定义的变量)
闭包的作用:避免全局污染
闭包的缺点:使用过多会造成内存泄漏(占用的内存释放不掉)

2.js中的本地存储有哪些,区别是什么

(1).sessionStorage

仅在当前会话下生效,当你关闭页面或浏览器后你存储的sessionStorage数据会被清除。
可存储的数据大小一般在5mb。
不参与和服务器的通信

(2).localStorage

永久有效,关闭浏览器也不会消失的,除非自己主动清除localStorage信息。
可存储的数据大小一般在5mb。
不参与和服务器的通信

(3).cookie

cookie是在设置的过期时间之前一直有效,哪怕关闭浏览器。
有个数限制(各浏览器不同),一般不能超过20个。
与服务器端通信:每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题

3.原型与继承、原型链

只有函数才有原型,原型是用来存放共有属性的,原型是一个属性,它的值是个对象
每一个对象都有一个__proto__(隐式原型)的属性,这个属性指向创建该对象的构造函数的prototype(如果不能确定他是谁的实例,都是Object的实例)
原型链:对象实例的隐士原型指向创建改对象的构造函数的原型对象,这样一层一层的指向关系形成的链叫原型链。如下。
在这里插入图片描述

4.js中的this指向

es5this指向函数运行时所在的对象(谁调用我我指谁,会随着调用者不同而改变)
es6箭头函数中this指向函数定义时所在的对象(我在哪出生我指谁,永远不会改变)

5.跨域问题

由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域。存在跨域的情况:
网络协议不同,如http协议访问https协议。
端口不同,如80端口访问8080端口。
域名不同,如qianduanblog.com访问baidu.com。
子域名不同,如abc.qianduanblog.com访问def.qianduanblog.com。
域名和域名对应ip,如www.a.com访问20.205.28.90.
解决方案
1.proxy代理(vue中常用)
2.jsonp
3.生产环境上用nginx反向代理

6.call和apply的用法

call和appley的作用都是改变this指向,让函数中的this可以自定义为我们传入的对象。
call和apply作用是一样的,只不过call接受的是参数序列当参数,apply接受的是数组当参数
其实call和apply的意思就是让一个对象去调用别人的方法,之前我们大多接触的是对象调用自己的方法。
就比如你朋友买了台车,正常我们接触的都是自己开自己的车,但是现在你想开你朋友的车,就是你这个对象去调用别人的方法

fn.call(obj,arg1,arg2,arg3)
fn.apply(obj,[arg1,arg2,arg3])

语法可以概括为:

开车这个函数.call(开车的这个对象,参数1,参数2) **
开车这个函数.apply(开车的这个对象,[参数1,参数2]) **

call前面是你要调用的方法,括号里是要调用这个方法的对象和参与运算的参数

例如:

function fn(){
   console.log(this.name+"在开车")
 } 
var obj={name:"小明"}
fn.call(obj)

运行结果:
在这里插入图片描述
上面的例子我们是用小明对象调用了window对象中定义的方法。下面换个对象是一样的道理,然后我们把参数传进去

var xh={
 	name:"小红",
    fn:function(car){
       	console.log(this.name+"在开"+car+"车")
      } 
}
 var obj={name:"小明"}
 xh.fn.call(obj,"宝马")

结果如下
在这里插入图片描述
换成apply,用法一样,只是传参换成了数组,注意call方法也支持多个参数,只不过不能写成数组,要写成以逗号分隔

var xh={
    name:"小红",
    fn:function(car1,car2){
       	console.log(this.name+"在开"+car1+car2+"车")
      } 
   }
   var obj={name:"小明"}
   xh.fn.apply(obj,["宝马","奔驰"])

在这里插入图片描述
总结:面试一般问作用和区别,作用是都是用来改变this指向的,区别是call接受参数序列,apply接受的是数组

7.for in 循环和for of循环的区别

for-in是ES5标准,遍历的是key(可遍历对象、数组或字符串的key);
for-of是ES6标准,遍历的是value(可遍历对象、数组或字符串的value)。

8.js中的深拷贝和浅拷贝

深拷贝递归地复制新对象中的所有值或属性,而浅拷贝只复制引用关系。
在深拷贝中,新对象中的更改不会影响原始对象,而在浅拷贝中,新对象中的更改,原始对象中也会跟着改。
在深拷贝中,原始对象不与新对象共享相同的属性,而在浅拷贝中,它们具有相同的属性。
slice方法和concat方法可以实现普通数组的深赋值,但是如果是二维数组就无法深复制
下面这个例子证明slice无法真正实现深复制

var arr1=[1,2,3,['1','2','3']];
var arr2=arr1.slice(0);
 arr1[3][0]=0;
 console.log(arr1);//[1,2,3,['0','2','3']]
 console.log(arr2);//[1,2,3,['0','2','3']]

如何实现深拷贝:

1.JSON.stringfy JSON.parse

var arr1 = ['red','green'];
var arr2 = JSON.parse(JSON.stringify(arr1));//复制
console.log(arr2)//['red','green'];
arr1.push('black') ;//改变color1的值
console.log(arr2)//["red", "green"]
console.log(arr1)//["red", "green", "black"]

2.递归

function deepClone(obj){
    //判断参数是不是一个对象
    let objClone = obj instanceof Object?[]:{};
    if(obj && typeof obj==="object"){
        for(key in obj){
            if(obj.hasOwnProperty(key)){
                //判断ojb子元素是否为对象,如果是,递归复制
                if(obj[key]&&typeof obj[key] ==="object"){
                    objClone[key] = deepClone(obj[key]);
                }else{
                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }
    return objClone;
}    
var a ={
    x:1,
    y:2
};
b=deepClone(a);
a.x=3
console.log(a);
console.log(b);

9.从打开浏览器输入网址,到页面显示出来经历了什么

1- 输入网址:那肯定是输入你要访问的网站网址了,俗称url;
2- 缓存解析:它先去缓存当中看看有没有,从 浏览器缓存-系统缓存-路由器缓存 当中查看,如果有从 缓存当中显示页面,然后没有那就进行步骤三;
缓存就是把你之前访问的web资源,比如一些js,css,图片什么的保存在你本机的内存或者磁盘当中
3- 域名解析:域名到IP地址的转换过程。域名的解析工作由DNS服务器完成。解析后可以获取域名相应的IP地址
4- tcp连接:在域名解析之后,浏览器向服务器发起了http请求,tcp连接,三次握手建立tcp连接。TCP协议是面向连接的,所以在传输数据前必须建立连接
5- 服务器收到请求:服务器收到浏览器发送的请求信息,返回一个响应头和一个响应体。
6-页面渲染:浏览器收到服务器发送的响应头和响应体,进行客户端渲染,生成Dom树、解析css样式、js交互。

10.事件委托/事件代理

利用事件的冒泡传播机制(触发当前元素的某一个行为,它父级所有元素的相关行为都会被触发),如果一个容器中有很多元素都要绑定点击事件,我们没有必要一个个的绑定了,只需要给最外层容器绑定一个点击事件即可

11.线程和进程

  1. 线程是最小的执行单元,进程是最小的资源管理单元
  2. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(一般情况)
  3. 一个进程对应多个线程最为常见,Linux、Windows等是这么实现的.其实理论上这种关系并不是一定的,可以存在多个进程对应一个线程,例如一些分布式操作系统的研究使用过这种方式,让线程能从一个地址空间转移到另一个地址空间,甚至跨机器调用不同的进程入口

12.如何实现数组扁平化

什么是数组扁平化?
[‘a’,‘b’,‘c’] //这是一个拥有3个元素的数组,是一个一维数组(不存在数组嵌套)。[[‘a’,‘b’],[‘c’,‘d’],[‘e’,‘f’]] 从整体上看是一个数组,但是其中的元素又是数组,即数组中嵌套数组,这就是二维数组
以此类推·····
[‘a’,[‘b’,[‘c’]]]//3维数组 [‘a’,[‘b’,[‘c’,[…]]]]//n维数组 数组扁平化就是把多维数组转化成一维数组。

1.通过es5递归实现

let result = [];
 function fn(ary) {
    for(let i = 0; i < ary.length; i++) }{
    let item = ary[i];
    if (Array.isArray(ary[i])){
    fn(item);
    } else {
        result.push(item);
        }
    }
}

2.es6 +reduce+递归实现

let arr=[[1,2,3],4,5,[6,7,[8,9]]];
function bianping(arr){
    return arr.reduce((res,item) =>{
        return res.concat(Array.isArray(item)?bianping(item):item)
    },[])
}
console.log(bianping(arr));

13.防抖和节流

见我的这篇博客
防抖和节流

14.js中的event loop(事件循环)

js作为单线程语言。在执行过程中,会产生执行环境。这些执行环境中的代码被顺序的加入到执行栈中,如果遇到异步代码,会被挂起并加入到任务队列当中,等到主线程任务执行完毕,event loop就会从任务队列取出需要执行的代码放入到执行栈中执行。

异步任务分类为宏任务(macro-task)和微任务(micro-task)。

宏任务:整体的Script setTimeout setInterval
微任务:Promise process.nextTick

例子

// 这是一个同步任务
console.log('1')            --------> 直接被执行
                                      目前打印结果为:1

// 这是一个宏任务
setTimeout(function () {    --------> 整体的setTimeout被放进宏任务列表
  console.log('2')                    目前宏任务列表记为【s2】
});

new Promise(function (resolve) {
  // 这里是同步任务
  console.log('3');         --------> 直接被执行
  resolve();                          目前打印结果为:1、3 
  // then是一个微任务
}).then(function () {       --------> 整体的then[包含里面的setTimeout]被放进微任务列表
  console.log('4')                    目前微任务列表记为【t45】
  setTimeout(function () {
    console.log('5')
  });
});

有微则微,无微则宏
如果微任务列表里面有任务 会执行完毕后在执行宏任务。

浏览器瞅了一眼微任务列表 发现里面有微任务 就开始全部执行
then(function () {
  console.log('4')            --------> 直接被执行
                                        目前打印结果为:1、3、4
  setTimeout(function () {    --------> 被放进宏任务列表了
    console.log('5')                    目前宏任务列表记为【s2、s5】
  });
});


浏览器发现微任务执行完毕了

开始执行宏任务列表

setTimeout(function () {
  console.log('2')   --------> 直接被执行
                               目前打印结果为:1、3、4、2

});

setTimeout(function () {
  console.log('5')   --------> 直接被执行
                               目前打印顺序为: 1、3、4、2、5、5
});

最终结果为: 1、3、4、2、5

15.原型链污染

先看一个案例

> var a = {admin:1}
undefined
> a.admin
1
> a.__proto__.admin = 2
2
> var b = {}
undefined
> b.admin
2

这里看到,b为空的,但是却访问到存在admin属性,并且修改了它的值。这是因为我们通过a.proto.admin = 2修改了a的原型,即Object,而这里b的原型也是Object,所以自然会有一个admin属性。
这些不会的同学建议去学一下原型链的知识。任何一个对象,都有隐式原型也就是__proto__,它的值指向的是原型对象。而a和b都是属于 Object的子类实例,所以他们的原型是一样的。

所以,什么是原型链污染(定义)
那么,在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。

16.async await函数

作用:跟promise的作用一样,都是为了解决异步编程问题,但是写法比promise更优雅
async 是“异步”的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于声明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。

async函数会返回一个promise对象,如果在函数中return一个值,那么该值会通过Promise.resolve()传递出去。
任何一个函数都可以直接改为async函数,只需要在function 前面加上async关键字,如下

 async function fn(){
         alert(1)
 }
console.log(fn()) 
//

输出结果如下,
在这里插入图片描述
跟普通函数默认没有结果不同,async函数默认是有结果的,默认返回一个promise对象,如果函数本身有return,那么return的值就是promise对象中resolve的值,这也就意味着我们可以继续在函数的调用之后写then方法如下:

 async function fn(){
    alert(1)
 }
 fn().then((res)=>{
 //因为函数没有return,所以默认resolve的是undefined
     console.log(res)  //undefined
  })


//如果加了return return的值就是该函数返回的promise对象resolve出来的值
 async function fn(){
   return "ok"
}
fn().then((res)=>{
    console.log(res) //ok
})

一般来说,都认为 await 是在等待一个 async 函数完成。不过按语法说明,,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。
因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。所以下面这个示例完全可以正确运行
在这里插入图片描述
await 等到了它要等的东西,一个 Promise 对象,或者其它值,然后呢?我不得不先说,
await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西。
如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
案例

function fn() {
	var a=new Promise(function(resolve,reject){
	setTimeout(function(){
		console.log(666);
		resolve("haha")
		},2000)	
	})
	return a
}
async function fn2(){
	let b=await fn();
	console.log("执行",b)		
}
	fn2()
//执行结果为 
/*
666
执行 haha
*/
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值