前端面试题(js篇)

1.解释一下什么是闭包

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

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

(1).sessionStorage

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

(2).localStorage

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

(3).cookie

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

3.原型与继承、原型链

只有函数才有原型,原型是用来存放共有属性的,原型是一个属性,它的值是个对象
每一个对象都有一个__proto__(隐式原型)的属性,这个属性指向创建该对象的构造函数的prototype(如果不能确定他是谁的实例,都是Object的实例)
原型链:对象实例的隐士原型指向创建改对象的构造函数的原型对象,这样一层一层的指向关系形成的链叫原型链。如下。
前端面试题(js篇)_第1张图片

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();                          目前打印结果为:13 
  // then是一个微任务
}).then(function () {
            --------> 整体的then[包含里面的setTimeout]被放进微任务列表
  console.log('4')                    目前微任务列表记为【t45】
  setTimeout(function () {
     
    console.log('5')
  });
});

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


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


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

开始执行宏任务列表

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

});

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

最终结果为: 13425

你可能感兴趣的:(面试题系列,javascript)