2020前端面试题(一)

406 阅读10分钟

1、 react生命周期

Mounting 组件被放到页面时
componentWillMount():  在组件即将被挂载在页面的时刻自动执行,只在第一次挂载时执行  
render(): 加载组件  
componentDidMount(): 组件挂载页面之后自动执行
Updating
componentWillReceiveProps(): 当该组件从父组件接收了参数且之前已经存在于父组件中才会执行,如果这个组件第一次存在父组件中不会执行。 shouldComponentUpdate() :  组件被更新之前执行.返回true,返回false将不会执行下面的生命周期函数,组件也不会被更新。  
componentWillUpdate(): 组件被更新之前执行,但在1.之后执行,且1.返回true才执行  
render()   
componentDidUpdate(): 组件被更新之后执行
UnMount componentWillUnmount()
存在于子组件中,子组件即将从页面中移除时执行。在componentDidUpdate()之前执行
  • 每次更新数据都会重新渲染一次render

2、 虚拟dom的渲染

React会先将你的代码转换成一个 Java对象,然后这个Java对象再转换成真实 DOM。这个 Java对象就是所谓的虚拟 DOM。  
当我们需要创建或更新元素时, React首先会让这个 VitrualDom对象进行创建和更改,然后再将 VitrualDom对象渲染成真实 DOM。  
DOM更新react只需要告诉React你想让视图处于什么状态, React则通过 VitrualDom确保 DOM与该状态相匹配。你不必自己去完成属性操作、事件处理、 DOM更新, React会替你完成这一切。 

3、 浏览器线程

一个进程有多个线程,进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位),线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位)。浏览器是多进程的。一个标签页至少对应一个进程,一些空白的标签页指向一个进程。浏览器还有一些进程来辅助支撑标签页的进程(如:brower进程,第三方插件进程,GPU进程用于3D绘制,浏览器渲染进程)
浏览器内核,即我们的渲染进程,又名renderer进程,负责页面的渲染,js的执行,事件的循环。该进程下有多个线程(图形用户界面GUI渲染线程、JS引擎线程、事件触发线程)

图形用户界面GUI渲染线程: 负责渲染浏览器界面,包括解析HTML、CSS、构建DOM树、Render树、布局与绘制等当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。

JS引擎线程: JS内核,也称JS引擎,负责处理执行javascript脚本 等待任务队列的任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS引擎在运行JS程序

事件触发线程: 听起来像JS的执行,但是其实归属于浏览器,而不是JS引擎,用来控制时间循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助) 当JS引擎执行代码块如setTimeout时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理 注意:由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)

定时触发器线程: setInterval与setTimeout所在线程 定时计时器并不是由JS引擎计时的,因为如果JS引擎是单线程的,如果JS引擎处于堵塞状态,那会影响到计时的准确 当计时完成被触发,事件会被添加到事件队列,等待JS引擎空闲了执行 注意:W3C的HTML标准中规定,setTimeout中低与4ms的时间间隔算为4ms

异步HTTP请求线程: 在XMLHttpRequest在连接后新启动的一个线程 线程如果检测到请求的状态变更,如果设置有回调函数,该线程会把回调函数添加到事件队列,同理,等待JS引擎空闲了执行

所有的同步任务都在主线程上执行,形成一个执行栈,主线程之外还存在一个任务队列,只要一步任务有了运行结果,就在“任务队列”之中放置一个事件。

补充JS为啥是单线程:

js单线程就是只在一个线程上执行,也就是说,js同时只能执行一个JS任务,其他的任务则会排队等待执行。js脚本语言主要任务处理用户的交互,而用户的交互是响应DOM的增删改,一次时间循环只处理一个事件响应,使得脚本执行相对连续。若是多线程,那么dom之间必然会引起资源竞争,在客户端跑起来,资源的消耗和性能将会不太乐观造成卡顿。

浏览器的渲染进程:主要以webkit为主

1、浏览器输入url,浏览器主进程接管,开了一个下载线程
2、然后进行HTTP请求(DNS查询、IP寻址等等),等待响应,开始下载响应报文。
3、将下载完的内容转交给Renderer进程管理
4、开始渲染..

渲染概念:
  • DOM Tree: 浏览器将HTML解析成树形的数据结构。
  • CSS Rule Tree:浏览器将CSS解析成树形的数据结构。
  • Render Tree:DOM树和CSS规则树合并后生产Render树。
  • layout:有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置。
  • painting: 按照算出来的规则,通过显卡,把内容画到屏幕上。
  • reflow(回流):当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫 reflow。reflow 会从 这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置。reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 - reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。
  • repaint(重绘):改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。
  • 注意:display:none的节点不会被加入Render Tree,而visibility: hidden则会,所以display:none会触发reflow,visibility: hidden会触发repaint。

浏览器内核拿到响应报文后之后,渲染大概分为以下步骤:
1、解析html生成DOM树
2、解析CSS规则
3、根据DOM Tree 和CSS Tree
4、根据Render树进行layout,负责各个元素节点的尺寸,位置计算。
5、绘制Render树painting,绘制页面像素信息
6、浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上。

4、 算法(二分法、快速排序)

  • 冒泡排序:从第一个元素开始,把当前元素和下一个元素作比较。如果当前元素大,那么交换位置,重负操作直到比较到最后一个位置。结果数组末尾为最大值。
function bubbleSort(arr) {
      if (Array.isArray(arr)) {
        for (let i = arr.length - 1; i > 0; i--) {
          for (let j = 0; j < i; j++) {
            if (arr[j] > arr[j + 1]) {
              [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
            }
          }
        }
        return arr;
      }
    }
  • 插入排序:第一个元素为当前元素与下一个元素比较,若下一个元素小,则交换,并继续向前比较。结果 数组变为从小到大的数组。
function insertSort(arr) {
      if (Array.isArray(arr)) {
        for (let i = 1; i < arr.length; i++) {
          var preIndex = i - 1;
          var current = arr[i]
          while (preIndex >= 0 && arr[preIndex] > current) {
            let temp=current;
            arr[preIndex + 1] = arr[preIndex];
            arr[prexIndex]=temp;
            current=arr[prexIndex]
            preIndex--;
          }
          // arr[preIndex + 1] = current;
        }
        return arr;
      }
    }
  • 选择排序:遍历数组,设置最小值的索引为0,如果取出的值比当前值小,就替换最小值索引,遍历完成后,将第一个元素和最小索引值上的值交换。重复遍历,第二次遍历就从索引1开始,一次累加。结果得到从小到大的数组
function selectSort(arr) {
      if (Array.isArray(arr)) {
        for (var i = 0; i < arr.length - 1; i++) {
          var minIdex = i;
          for (var j = i + 1; j < arr.length; j++) {
            minIdex = arr[j] < arr[minIdex] ? j : minIdex;
          }
          [arr[i], arr[minIdex]] = [arr[minIdex], arr[i]];
        }
        return arr;
      }
    }
  • 快速排序:在数据集之中,找一个基准点,建立两个数组,分别存储左边和右边的数组,利用递归进行下次比较。

5、 跨域:指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制 。

同源策略:"协议+域名+端口"三者相同就是同源,两个不同的域名指向同一个ip地址也非同源。它是浏览器最核心也是基本的安全功能。防止遭到CSRF\XSS等攻击 解决办法:


  • jsonp跨域:只能实现GET请求。代理模式,在html页面中通过相应的标签从不同的域名下加载静态资源文件是被浏览器允许的。‘犯罪漏铜’哈哈哈。一般可以动态创建script标签,再去请求一个带参网址来实现跨域通信。
//原生js
let script=document.createElement('script');
script.src='http://www.adsdc/login?username='wzz'&callback=callback';
document.appendChild(script);
function callback(res){
    return res;
}
//jquery
$.ajax({
    url:'http://www.baidu.com',
    type:'GET',
    dataType:'jsonp',
    jsonCallback:'callback',
    data:{
        "username":"wzz",
        "age":18
    }
    success:res=>{
        console.log(res);
    }
})
  • 跨域资源共享(CORS):
axios.defaults.baseURL = 'http://127.0.0.1:8888';
axios.defaults.withCredentials = true;
//👈重点
axios.defaults.headers['Content-Type']='application/x-www-form-urlencoded';
axios.defaults.transformRequest = function (data) {
 if (!data) return data;
 let result = ``; 
 for (let attr in data) { 
     if (!data.hasOwnProperty(attr)) 
        break;
     result += `&${attr}=${data[attr]}`; 
 }
 return result.substring(1);
};
axios.interceptors.response.use(function onFulfilled(response) {
    return response.data;
}, function onRejected(reason) {
    return Promise.reject(reason);
});
axios.defaults.validateStatus = function (status) { 
   return /^(2|3)\d{2}$/.test(status);
}
//服务器端设置相关的头信息
app.use((req, res, next) => {   
  res.header("Access-Control-Allow-Origin", "http://localhost:8000");  //=>*(就不能在允许携带cookie了) 具体地址
 //局限性  二者选其一  因为CORS的这个缺陷  出现了👇
  http proxy  =>webpack webpack-dev-server    res.header("Access-Control-Allow-Credentials", true);    res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length,Authorization, Accept,X-Requested-With");    res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,HEAD,OPTIONS");
  if (req.method === 'OPTIONS') { 
    res.send('OK!');    
    return;    
  }    
  next();
});
  • http proxy =>webpack webpack-dev-server:proxy相当于node模拟了一个服务请求,服务器请求服务器不存在跨域。proxy请求回来数据解决跨域。
//在webpack.config.js文件中:
devServer: {
        port: 3000,
        progress: true,
        contentBase: './build',
        proxy: {             
'/': {      //‘/’  在请求时不用设置前缀                
target: 'http://127.0.0.1:3001',                
changeOrigin: true //改变源 (当我们设置为true时 devserver会帮我们起一个服务,
//来做中层的代理"与node中间件代理类似"]) 
         }
    }
 }
  • nginx反向代理:前端不需要做什么工作
server {    listen       80;    server_name  www.baidu.com;    location / {        proxy_pass   www.baidu.cn; //反向代理        proxy_cookie_demo www.baidu.cn www.baidu.com;        add_header Access-Control-Allow-Origin www.baidu.cn;        add_header Access-Control-Allow-Credentials true;    }}

6、 0.1+0.2解决小数点问题

0.1+0.2为什么不等于0.3?


0.1和0.2转换成二进制后会无限循环,由于标准位数的限制后面多余的位数会被截掉,因此也造成精度损失,相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成0.300000000000000004


解决办法:

function reckon(str, calculate, str2) {
  var times, m1, m2, num, b1, b2;
  m1 = str.toString().split('.')[1].length;
  m2 = str2 == '' ? calculate.toString().split('.')[1].length : str2.toString().split('.')[1].length;
  times = m1 > m2 ? m1 : m2;
  times = Math.pow(10, times);
  b1 = parseInt(str * times);
  b2 = str2 == '' ? parseInt(calculate * times) : parseInt(str2 * times);
  switch (calculate) {
      case '+':
          num = b1 + b2;
          break;
      case '-':
          num = b1 - b2;
          break;
      case '*':
          num = b1 * b2;
          break;
      case '/':
          num = b1 / b2;
          break;
      case '%':
          num = b1 % b2;
          break;
      default:
          num = b1 + b2;
  }
  return parseFloat(num / times);
}
console.log(reckon(0.1,'+',0.2));

7、 Diff算法

Diff算法的作用事用来计算出虚拟Dom中背改变的部分,然后针对该部分进行原生DOM操作,而不用重新渲染整个页面。  
tree diff\component diff\element diff
感谢大佬们的分享

1.www.cnblogs.com/wuqiuxue/p/… 2.segmentfault.com/a/119000001… 3.juejin.cn/post/684490… 4.www.jianshu.com/p/580157c93…