前端面试题归纳总结

746 阅读32分钟

前端面试题100+

HTTP

  1. http 请求方式
  • GET: GET 方法请求一个指定资源的表示形式,使用 GET 的请求应该只被用于获取数据
  • POST: 向 URL 指定的资源提交数据或附加新的数据
  • PUT: 跟 POST ⽅法很像,也是想服务器提交数据 。但是, 它们之间有不同 。 PUT 指定了资源在服务器上的位置, 而 POST 没有
  • DELETE: 删除服务器上的某资源
  • OPTIONS: OPTIONS 方法用于描述目标资源的通信选项,在 CORS 中,可以使用 OPTIONS 方法发起一个预检请求
  • TRACE: TRACE 方法沿着到目标资源的路径执行一个消息环回测试。
  • HEAD: HEAD 方法请求一个与 GET 请求的响应相同的响应,但没有响应体。
  1. 从输入URL后发生了什么

  2. 缓存

    强缓存:Cache-control/Expires
    协商缓存:Etag/If-None-match, Last-Modified/IF-Modified-Since

    当 ctrl+f5 强制刷新网页时, 直接从服务器加载,跳过强缓存和协商缓存; 当 f5 刷新网页时,跳过强缓存,但是会检查协商缓存;

  3. http1.0、http1.1和http2.0的区别

特性HTTP/1.0HTTP/1.1HTTP/2.0
连接管理单一连接,非持久连接持久连接,可以重复使用连接多路复用,单一连接上并行传输多个请求和响应
请求-响应模型阻塞,每次只能处理一个请求阻塞,但支持管线化(存在队头阻塞问题)多路复用,同时发送多个请求和响应
头部压缩使用HPACK算法进行头部压缩
二进制传输使用二进制帧进行数据传输
分块传输支持分块传输(Chunked Transfer)支持分块传输
缓存控制引入Cache-Control等头部引入更复杂的缓存机制,包括新的Cache-Control和ETag等头部字段
服务器推送支持服务器推送,服务器可以在客户端请求之前主动推送资源
性能提升较差较好显著提升,特别是在高延迟和高丢包率网络环境下
  1. TCP三次握手和四次挥手

  2. 常见的HTTP状态码

    2XX: 200正常返回

    image.png 3XX: 301永久重定向/302临时重定向/304缓存

    image.png

    image.png 4XX: 401无权访问/403服务访问被拒绝/404资源错误

    image.png

    image.png 5XX: 502网关错误/500服务器错误

    image.png

    image.png

  3. html5新特性?如何处理HTML5新标签的浏览器兼容问题?如何区分 HTML 和 HTML5?
    html5新特性

    • 语义特性: header footer
    • 本地存储特性: localStorage, manifest
    • 设备访问特性: Geolocation
    • 连接特性: webSocket
    • 网页多媒体特性: audio、video标签
    • 三维、图形及特效:SVG,Canvas, css3D
    • 性能、集成特性

    解决兼容性的方法:

    • 在IE8以下,通过document.createElement(新标签),并设置css样式。document.createElement('header'), header {display: block};
    • 条件注释的方式
    <!--[if lt IE 9]>  
    <script> src="http://html5shim.googlecode.com/svn/trunk/html5.js"</script>  
    <![endif]-->
    

    HTML和HTML5

    • 文档类型声明上:
      html:
     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
       <html xmlns="http://www.w3.org/1999/xhtml">
    

    html5:

      <!doctype html>  
    
    • 结构语义上 html: 基本没有体现结构语义,一般为

      <div id="header"></div>
      

      html5: 具有结构语义

      <header></header>
      
  4. html5的离线缓存 特性:用户没联网时,正常访问网站。用户联网后,更新缓存 原理:它主要新建一个.appcache文件的缓存机制,通过这个文件上的解析清单离线存储资源。之后当网络在处于离线状态下时, 浏览器会通过被离线存储的数据进行页面展示

CACHE MANIFEST
# 版本号
# Comment line
# ...
/css/styles.css
/images/logo.png
/js/scripts.js

使用:

  • 在 example.appcache 文件的编写离线存储的资源
  • 在 HTML 页面中引用 Cache Manifest 文件
<!DOCTYPE html>
<html manifest="example.appcache">
<!-- 页面内容 -->
</html>

  • 在离线状态时, 操作 window.applicationCache 进行需求实
var appCache = window.applicationCache;

appCache.addEventListener('updateready', function () {
  if (appCache.status === appCache.UPDATEREADY) {
    // 有新的缓存,可以提示用户进行刷新
    appCache.swapCache();
    window.location.reload();
  }
}, false);

  1. token/session/cookie/localStorage/sessionStorage
标题描述
session存储在服务器,大小没限制
cookie存储在浏览器,大小不超过4KB,一个站点最多保留20个cookie
localStorage存储在浏览器,关闭浏览器依然存在,除非手动清除,大小为5MB左右
sessionStorage存储在浏览器,会话期间有效,关闭浏览器后消失,大小为5MB左右
  1. cookie
属性作用
http-only不能通过 JS 访问 Cookie ,减少 XSS 攻击
secure只能在协议为 HTTPS 的请求中携带
same-site规定浏览器不能在跨域请求中携带 Cookie ,减少 CSRF 攻击
  1. src和href区别

src指向外部资源的位置,指向位置会嵌入到当前文档标签所在位置,在请求资源时会将src资源下载并应用文档内,如img、script等。当浏览器进行下载解析时会暂停其它资源的处理。

<script src ="js.js"></script>

href是外部资源所在位置的超链接,用来建立和当前文档和元素的连接。当它进行下载解析时不会阻碍其它元素的处理,属于并行处理。

<link href="common.css" rel="stylesheet"/>

CSS

  1. 盒模型

  2. 页面导入样式时,使用link和@import有什么区别?

    • 加载内容: link是xhtml标签,除了能加载css外,还能加载rss(简单信息聚合——xml文件);@import只能加载css文件

    • 加载顺序: link在页面载入的同时加载;@import的css是在页面加载完毕后被加载。

    • 兼容性问题: link无兼容性问题; @import是css2.1里提出的,低版本不兼容

    • dom控制问题: link样式,在js中操作dom可修改样式;@import不支持修改

      建议使用link

  3. BFC

    BFC即为Block-formatting-context(块级格式化上下文),它是页面的一块渲染区域,这个区域与外部无关。它有自己的渲染规则,决定了子元素的位置以及和其它元素的关系和作用。

    规则

    • 内部的box垂直方向一个一个放置
    • 垂直方向距离由margin决定,相邻两个box的margin会发生重叠。
    • BFC是页面上的独立区域,不受外面元素的影响。
    • 每个元素的margin box左边,与包含块的border box左边相接触
    • BFC区域不会与float box重叠
    • 计算BFC的高度时,浮动元素参与计算

    哪些会生成BFC

    • 根元素
    • float不为none
    • position为absolute或fixed
    • display: inline-block, table-cell, table-caption, flex, inline-flex
    • overflow为 scroll, auto、hidden
  4. 选择器优先级

    内联样式 > id选择器(0100) > 类选择器(属性选择器,伪类)(0010) > 类型选择器(伪元素)(0001)> 通配符(0000)
    !important会覆盖以上所有。

  5. position的理解

    取值描述
    static正常布局,top、bottom、left、right、z-index不起作用
    relatvie会留空白,放在正常位置上
    absolute脱离文档,不留空白,相对于非static的祖先元素定位
    fixed脱离文档,不留空白,相对于屏幕视口的位置定位

6. offsetWidth(offsetHeight) / clientWidth(clientHeight) /scrollWidth(scrollHeight)之间的区别

  • offsetWidthoffsetHeight 包括整个元素(content + padding + border),包括边框和滚动条。
  • clientWidthclientHeight 只包括内容区域(含content + padding),不包括边框和滚动条。
  • scrollWidthscrollHeight 包括由于溢出而被隐藏的内容(content + padding + 溢出内容)。

Javascript

  1. JS事件循环机制(event loop)
    事件主要涉及到进程、线程、宏任务、微任务的执行机制,同时需要知道setTimeout、promise等的执行先后

    进程中可以包含多个线程
    js中一个主线程,主线程上可能会有多个任务,包括宏任务和微任务,先执行宏任务,宏任务下产生的微任务优先执行,执行完成后继续下一个宏任务
    什么是Event Loop 面试题:说说事件循环机制(满分答案来了) # Philip Roberts: Help, I’m stuck in an event-loop.
    演示

macro-task 大概包括:

  • setTimeout
  • setInterval
  • setImmediate
  • script(整体代码)
  • I/O 操作等。

micro-task 大概包括:

  • process.nextTick(与普通微任务有区别,在微任务队列执行之前执行)

  • new Promise().then(回调)等。

  1. Chrome V8 内存回收机制
    a、标记清理(现在常用):当变量进入上下文,会被加上存在于上下文的标记,当变量离开上下文时,会加上离开上下文的标记

    b、引用计数:声明变量并给它赋一个引用值为1,如果同一个值又被赋给另一个变量,那么引用数加1。同理,如果保存该值的引用变量被其他值覆盖了,那么引用数减1。引用数为0时,可以释放内存,该方法容易导致循环引用,如DOM的操作

    内存管理

    c、主要是通过2部分,一部分为from-space,一部分为to-space,在from-space中把活跃对象复制到to-space中,然后非活跃对象删除,其中区分活跃和非活跃的方式是在根节点,递归遍历子节点,如果遍历到了就是活跃的,否则非活跃
    新生代和老生代的方式

  2. script标签中的defer和async

    document.write/标签形式的js获取都是异步获取的,默认是会阻止DOM的绘制,直到js结束后继续绘制。

    但是加入defer后,不会阻止DOM的绘制,defer加入后的执行,是在DOM绘制已经准备好,在DOMContentLoaded之前完成。defer是不按照顺序下载js,可能后面的脚本先下载完成,但按顺序执行脚本。

    async也不会阻止DOM的绘制,async和DOMContentLoaded之前没有执行先后关系,是随机获取js,js的运行顺序是先下载先执行,async异步脚本不应该在加载期间修改DOM。

    defer与async的区别是:defer要等到整个页面在内存中正常渲染结束(DOM结构完全生成,以及其它脚本执行完成)才会执行;async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后再继续渲染。一句话,defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。

    image.png

    image.png

  3. 对象继承

  4. 闭包及应用场景

    闭包是指函数外能访问函数里的内容
    应用场景:模块封装debounce、柯里化函数、私有化方法和变量/缓存结果CRUL

  5. 原型链

原型链.jpeg

原型constructor.jpeg

image.png

实例 .proto === 原型

原型 .constructor === 构造函数

构造函数 .prototype === 原型

  1. new运算符的执行过程

    a. 在内存中创建一个新对象
    b. 这个新对象内部的__proto__特性被赋值为构造函数的prototype属性
    c. 构造函数内部this被赋值为这个新对象(即this指向新对象),执行构造函数内部的代码(给新对象添加属性)
    d. 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象

    function create(Fn, ...args){ 
        //a. 创建一个空对象 这个其实就是实例对象 
        var obj = new Object(); 
    
        //b. 将新对象的原型链指向构造函数的原型
        obj.__proto__ = Fn.prototype; 
    
        // c.调用构造函数,并将新对象作为 this 上下文
       ~~ var result = Fn.apply(obj, args); ~~
    
        return obj 
    }
    
    
  2. 模块化(commonJS/es6 Module)

CommonJS/AMD都只是在运行时才能确定这些, 称为“运行时加载”, CommonJS是运行服务器,如node.js, AMD是运行浏览器。 CommonJS模块输出的是值的缓存,不存在动态更新
CommonJS主要用于服务端(Node.js)实现模块化, 模块定义需要使用require()指定依赖,使用exports对象定义自己公共的API,模块第一次加载后会被缓存,后续加载会取得缓存的模块。
AMD则以浏览器为目标执行环境,它实现核心是用函数包装模块定义,包装模块的函数是全局define的参数
es6 module设计思想是尽量静态化,在编译时就能确定依赖关系,输入和输出变量,称为“编译时加载”或静态加载,它不允许运行时改变值。
es6 import()返回的是Promise,可用于按需加载,import from方式不可用于条件判断,但是import()方法可用于条件语句

CommonJS 模块输出的是值的浅拷贝,ES6模块输出的是值的引用。即ES6 Module 只存只读,不能改变值,也就是指针指向不能变,类似const
CommonJS 是运行时加载,ES6是编译时加载
CommonJS 的require是同步加载模块,ES6模块的import是异步加载,有独立的模块依赖解析阶段
CommonJS 加载的是一个对象(module.exports),该对象只在脚本运行完才生成;而ES6模块不是对象,它对外接口是静态定义,在代码静态解析阶段就会生成。

标题commonJSES6AMD
值输出值的浅拷贝值的引用值的引用
编译运行时加载静态编译运行时加载
同步异步同步加载(阻塞)同步加载(阻塞)/异步加载(非阻塞)异步加载(非阻塞)
浏览器/服务器关注点服务器端(Node.js)通用(在浏览器和服务器端越来越常见)面向浏览器
定义模块的语法module.exportsrequireexportimportdefinerequire
  1. 跨域的问题

跨域问题是浏览器的同源策略的安全机制引起的服务器之间是不存在跨域问题的,这也不是说服务器之间没有安全机制,只是服务器之间的调用无论是通过http访问还是通过rpc调用都是协议层面的机制,并没有限制必须同源
跨域:协议+域名+端口的不一致会导致跨域问题。
跨域问题的解决方式: CORS/设置相同二级域名(iframe+postMessage)/代理(nginx反向代理、node)/标签(script)/jsonp/后端在头部信息里面设置安全域名

http可以发送https内容,反之不可以
options请求

12. xss/crsf

XSS是页面被注入恶意代码,解决方式是转码
XSS 可以分为存储型、反射型和 DOM 型:

存储型指的是恶意脚本会存储在目标服务器上,当浏览器请求数据时,脚本从服务器传回并执行。(主要是表单提交相关,如评论)

反射型指的是攻击者诱导用户访问一个带有恶意代码的 URL 后,服务器端接收数据后处理,然后把带有恶意代码的数据发送到浏览器端,浏览器端解析这段带有 XSS 代码的数据后当做脚本执行,最终完成XSS 攻击。(数据存储在url上,诱导网站跳转)

DOM 型指的通过修改页面的 DOM 节点形成的 XSS。(这个属于前端漏洞,前两个属于后端漏洞)

前端安全系列(一):如何防止XSS攻击?\

crsf攻击指的是跨站请求伪造攻击,攻击者诱导用户进入第三方网站,然后该网站向攻击网站发跨站请求。如果用户在被攻击网站中保存了登陆状态,那么攻击者就可以利用这个登陆状态,绕过后台验证,冒充用户向服务执行操作。

常见的 CSRF 攻击有三种:

GET 类型的 CSRF 攻击,比如在网站中的一个 img 标签里构建一个请求,当用户打开这个网站的时候就会自动发起提交。

POST 类型的 CSRF 攻击,比如构建一个表单,然后隐藏它,当用户进入页面时,自动提交这个表单。

链接类型的 CSRF 攻击,比如在 a 标签的 href 属性里构建一个请求,然后诱导用户去点击。

解决方式:
进行同源检测,服务器根据http请求头中的origin或者referer信息来判断请求是否是允许访问的站点,从而对请求进行过滤。(referer可伪造、搜索引擎可能被屏蔽)
token方式,服务向用户返回一个随机token,当网站再次发起请求时,在请求中加入token,服务端进行token校验。
cookie设置samesite属性

  1. 数据类型检测的方式有哪些

    typeof/instanceof/constructor/Object.prototype.toString.call()

    instanceof原理:用于判断构造函数prototype属性是否出现在对象的原型链中的任何位置

    instanceof 只能正确判断引用数据类型,而不能判断基本数据类型。

image.png

```js
function instanceof(l,r) {
   // 获取左侧原型对象
   let lProto = Object.getPrototypeOf(l)

   // 获取右侧构造函数的原型
   let rPrototype = r.prototype

   // 判断构造函数的prototype对象是否在对象的原型链上
   while(true) {
    if (!lProto) return false;
    if (lProto === rPrototype) return true;
    // 如果没找到,继续原型链上查找
    lProto = Object.getPrototypeOf(lProto)
   }
}
```

15. JS 有哪些数据类型\

基本类型5个:string、number、boolean、undefined、null
混合类型:Object
es6新增基本数据类型Symbol

● 栈:原始数据类型(Undefined、Null、Boolean、Number、String)
● 堆:引用数据类型(对象、数组和函数)

两种类型的区别在于存储位置的不同:

● 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;

● 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

● 堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:

在数据结构中,栈中数据的存取方式为先进后出。

堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。

16. 箭头函数

a、箭头函数没有this对象,它所谓的this是捕获上下文this值作为自己的this值
b、不可当作构造函数,即不能通过new使用,否则会报错
c、不可使用arguments对象,该对象在函数体内不存在,要使用可以使用rest
d、不能用yield对象,因为箭头函数不能作为generator函数\

  1. this

this不是编译时绑定,而是运行时绑定,它依赖函数调用上下文,它和函数声明位置无关,和函数调用位置有关。

this是什么

  1. for of和for in 区别
    for of 遍历获取的是对象键值,for in遍历获取的是对象键名;
    for in 会遍历整个原型链,性能非常差不推荐使用,for of只遍历当前对象不遍历原型链。
    总结:for in 是遍历对象而生,不适用遍历数组;for of 用来遍历数组、类数组、字符串、set、map以及generator对象。for of能遍历是因为有Symbol.iterator

image.png

image.png

  1. map和object区别
  • key类型: map的索引可以是任意类型,object只能是string或Symbol类型

  • key顺序: map添加的是有序的,object是无序的

  • 计算:map的键值可以通过size计算,object需要手动计算(Object.keys(obj).length)

  1. 让对象具有for of功能 for of 主要是【Symbol.iterator】+next方法, 在对象上添加该方法即可

     let a = {
            a1: 1,
            a2: 2,
            a3: 3,
            [Symbol.iterator]: function(e) {
              let idx = 1
              return {
                next: () => {
                  let v = this[`a${idx}`]
                  idx += 1
    
                  if (v) {
                    return {
                      value: v,
                      done: false
                    }
                  }
    
                  return {
                    value: undefined,
                    done: true
                  }
                }
              }
            }
          }
    
  2. forEach、map、filter、some、every区别

    标题主要功能返回值是否修改原来的数组对空数组检测跳出循环
    forEach仅仅是遍历数组没有返回值不对原数组进行修改,除非自己通过索引修改不对空数组检测不可以
    map由原数组调用方法后组成一个新数组返回新数组不修改原数组不对空数组检测不可以
    filter对数组进行过滤返回符合条件的新数组不修改原数组不对空数组检测不可以
    some如果任意一项执行为true即为true, 否则falseboolean
    every如果所有的都为true即为true, 否则falseboolean
  3. var/let/const区别

    标题区别
    var定义的变量可修改,作用域是全局/函数作用域,可以变量提升和重复定义
    let定义的变量可修改,作用域是块级作用域,不可变量提升和重复定义,暂时性死区(声明前不可用)
    const定义的变量不可修改,作用域是块级作用域,不可变量提升和重复定义,暂时性死区(声明前不可用)

变量提升和暂时性死区

  1. meta viewport 是做什么用的,怎么写?
    meta表示不能被HTML的其它元素(link,script,base, style, title)之一表示的任何元素信息。
    viewpoint让web开发者控制视口的尺寸及比例,移动设备的viewpoint指设备屏幕上用来展示网页的那一块区域,也就是浏览器上用来展示网页的那部分,可能比浏览器的可视区大,也可能比浏览器可视区域小,一般情况,比浏览器可视区域大。属性包括width、height、initial-scale、maximum-scale、minimum-scale,使用方式是

    <meta name="viewpoint" content="width=device-width, initial-scale=1, maximum-scale=1">
    
  2. null和undefined的区别?如何判断为NaN数据?

    null是一个表示“无”的对象,转为数值时为0, typeof null输出“object”
    (1) 作为函数的参数,表示该函数的参数是对象
    (2) 作为对象原型链的终点


    undefined是一个表示“无”的原始值,转化数值为NaN, typeof undefined输出"undefined"
    (1) 变量声明了,但没有赋值,等于undefined
    (2) 调用函数时,应该提供的参数没有提供,该参数等于undeined;
    (3) 对象没有赋值的属性,该属性的值为undefined
    (4) 函数没有返回值时,默认返回undefined

    当算术运算返回一个未定义或无法表示的值时,NaN就产生了。NaN的值表示不是一个数字(Not a number),typeof NaN 输出“number”,判断NaN方法
    (1) Number.isNaN()或者isNaN
    (2) 如果isNaN函数的参数不是Number类型,isNaN会先尝试将参数转换为数值再判断

     var isNaN = function(value) {
                var n = parseInt(value);
                return n !== n
              }
    

    (3) 和全局isNaN相比,Number.isNaN不会进行强制转换

     Number.isNaN = Number.isNaN || function(value) {
              return typeof value === "number" && isNaN(value)
            }
    
  3. 伪数组定义以及如何转数组?
    定义

    • 伪数组是一个对象
    • 伪数组必须含有length属性
    • 如果length不为0,这数据结构必须按照下标存储

    判断是否为真数组

    • arr instanceof Array
    • Object.prototype.toString.call(arr) === '[object Array]'

    转真数组

    • Array.prototype.slice.call(arr)
    • Array.from(arr)
    • [...arr]
    • Array.prototype.concat.apply([], arr)
  4. 数组降维

    var arr = [1, [[2, 3], 4], [5, 6]];
    
    // es 2019
    arr.flat(Infinity)
    
    // 迭代器方式
    var flat = function* (a) {
      var length = a.length;
      for (var i = 0; i < length; i++) {
        var item = a[i];
        if (typeof item !== 'number') {
          yield* flat(item);
        } else {
          yield item;
        }
      }
    };
    
    for (var f of flat(arr)) {
      console.log(f);
    }
    
    // 递归方式
    var commonArr = []
    var dimensionReduction = function (arr) {
      if (!arr.length) return
      arr.map(a => {
        if (Array.isArray(a)) {
          commonArr.concat(dimensionReduction(a))
        } else {
          commonArr.push(a)
        }
      })
      return commonArr
    }
    
    // reduce
    const flatten = arr => {
      return arr.reduce((pre, cur) => {
        return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
      }, [])
    }
    flatten(arr);
    
    // concat方式
    var commonArr = []
    var dimensionReduction = function(arr) {
      for (let i = 0; i < arr.length ; i++) {
        if (Array.isArray(a)) {
          commonArr.concat(dimensionReduction(a))
        } else {
          commonArr.push(a)
        }
      }
      return commonArr
    }
    
    // 正则的方式
    JSON.stringify(arr).replace(/\[|\]/g, '').split(',')
    JSON.parse(`[${JSON.stringify(arr).replace(/\[|\]/g, '')}]`)
    
    // other
    arr.toString().split(',')
    eval('['+arr+']')
    
    
    
function A() {

  }

  function B(a) {
    this.a = a
  }

  A.prototype.a = 1;
  B.prototype.a = 1;
  console.info(new A().a) // 输出1
  console.info(new B().a) // 输出 undefined

考察的点: new对象时,构造函数初始化是否传值;原型链查找值,先从自身找起。
因为首先查找自身属性是否含有a,有就取值自己的(B含有,且为undefined),否则,通过原型链查找原型链上的。

  1. 下面的代码打印什么内容,为什么?

    var b = 10;
    (function b(){
       b = 20;
       console.log(b);
    })();
    
    答:
    
    ƒ b(){
        b = 20;
        console.log(b);
    }
    

    首先函数声明比变量要高,其次 b = 20 没有 var 获取其他,说明是 window 最外层定义的变量。js 作用域中,先找最近的 那就是 b fn ,直接打印了,如果 b = 20 有 var 那就是打印 20

27.下面代码输出什么?

```js
var a = 10;
(function () {
    console.log(a);
    a = 5;
    console.log(window.a);
    var a = 20;
    console.log(a)
 })()
```

分别为 undefined 10 20,原因是作用域问题,在内部声明 var a = 20;相当于先声明 var a;然后再执行赋值操作,这是在IIFE内形成的独立作用域,如果把 var a=20 注释掉,那么 a 只有在外部有声明,显示的就是外部的A变量的值了。结果A会是 10 5 5

  1. 下面代码输出

    var result = [];
    var a = 3;
    var total = 0;
    
    function foo(a) {
      var i = 0;
      for (; i<3;i++) {
          result[i] = function() {
              total += i * a;
              console.log(total)
          }
      }
    
    }
    
    foo(1)
    result[0](); // i = 3, a = 1, total = 3
    result[1](); // i = 3, a = 1, totoal = 3 + 3 = 6
    result[2](); // i = 3, a = 1, total = 3 + 6 = 9
    // 
    
  2. 如何实现数组去重?
    答:

    • 第一种原始方法:对原数组进行遍历,并在新数组中遍历是否含有,含有就不添加不含有就添加
        function unique(arr) {
          var res = [];
          for (var i = 0; i < arr.length; i++) {
            for (var j = 0; j < res.length; j++) {
              if (arr[i] === res[j]) {
                break;
              }
            }
            if (j === res.length) {
              <!-- 说明res还没有arr[i] -->
              res.push(arr[i])
            }
          }
          return res
        }
    
    • indexOf方法
      function unique(arr) {
        var res = [];
        for (var i = 0; i < arr.length; i++) {
          if (res.indexOf(arr[i]) === -1) {
            res.push(arr[i]);
            continue;
          }
        }
        return res
      }
    
    • 排序后去重
      function unique(arr) {
        var sortArr = arr.sort();
        var res = [];
        var monitor = null
        for (var i = 0; i < sortArr.length -1; i++) {
          if (!monitor || monitor !== sortArr[i]) {
            res.push(sortArr[i]);
          }
          monitor = sortArr[i]
        }
        return res
      }
    
    • filter方法(推荐)
      function unique(arr) {
        return arr.filter((item, index, array) => array.indexOf(item) === index)
      }
    
    • es6方法
      es6数据结构Set类似于数组,但成员唯一,没有重复值。
      function unique(arr) {
        var res = new Set(arr);
        return [...res]
      }
    
      Array.from方法可以将 Set 结构转为数组。
      
      function unique(arr) {
        var res = new Set(arr);
        return Array.from(new Set(array));
      }
    

    为数组设计一个distinct方法
    如:[1,2,3,1,'2'].distinct() = [1,2,3,'2'],请提供几种思路,至少写一个

    Array.prototype.distinct = function() { //这里不适合用箭头函数,不然this指向window
      let arr = this;
      return arr.filter((item, index, arr) => arr.indexOf(item) === index)
    }
    
  3. requestAnimationFrame

  4. html语义化的理解

    语义化就是根据内容结构采用合适的标签标识,让页面具有良好的结构与含义。便于SEO和开发维护。

  5. src和href区别

    src指向外部资源的位置,指向位置会嵌入到当前文档标签所在位置,在请求资源时会将src资源下载并应用文档内,如img、script等。当浏览器进行下载解析时会暂停其它资源的处理。

    <script src ="js.js"></script>
    

    href是外部资源所在位置的超链接,用来建立和当前文档和元素的连接。当它进行下载解析时不会阻碍其它元素的处理,属于并行处理。

    <link href="common.css" rel="stylesheet"/>
    
  6. DOM事件模型和事件流
    a: EventTarget接口的三个实例方法:

    • addEventListener: 绑定事件的监听函数,可以添加多个监听函数,能指定是在捕获还是冒泡阶段触发监听函数
    • removeEventListener: 移除事件的监听函数
    • dispatchEvent: 触发事件 b: HTML的on-属性(只在冒泡阶段触发,违反了HTML与javascript代码分离的原则,写在一起不利于代码分工)
      c: 元素节点的事件属性,如div.onclick;也只在冒泡阶段触发,同一事件只能定义一个监听函数,定义多个时,后一个会覆盖前一个; div.onclick > on-属性
      d: 事件的传播:
    • 第一阶段: 从window对象传导到目标节点(上层传到底层), 称为"捕获阶段"
    • 第二阶段: 在目标节点上触发,称为"目标阶段"
    • 第三阶段:从目标节点传导回window对象(底层传回到上层),称为"冒泡阶段"
  7. 事件委托是什么?有什么好处?
    答:事件委托就是把一个元素相应事件的函数委托到另一个元素;一般来讲,把一个或一组元素的事件绑定到它的父层或更外层上面,真正绑定事件是在外层,当事件触发该元素上,通过冒泡触发它的外层元素,从而执行函数。
    好处: 减少内存消耗,减少重复工作

  8. 重排和重绘, 什么情况下会触发?
    重排relayout(回流reflow):DOM变化影响了元素的几何信息(DOM的位置和尺寸大小),浏览器需要重新计算元素的几何属性,并放置合适的位置的过程。(DOM宽高的修改等)
    重绘(repaint):元素(节点)外观发生变化,但没有改变布局,重新把元素绘制出来的过程.(颜色、阴影等的修改)
    重绘不一定导致重排,但重排一定会导致重绘

  9. 浏览器内多个标签的通信

    主要有websocket协议、localStorage存储、postmessage方式

  10. html解析
    HTML通过tokenization和树的构造,解析和处理HTML标签,建立DOM。当解析遇到非阻塞资源(图片/css),解析器会继续进行;当解析遇到阻塞资源js(没有defer/async属性),会阻塞渲染并停止HTML解析。

    a. 将HTML转换成DOM树
    b. 将css转换成CSSOM树
    c. 将DOM和CSSOM合并为渲染树
    d. 根据渲染树来布局,计算每个节点的几何信息
    e. 当文档的各个部分以不同层绘制,需要重叠时,进行合成(compositing), 最后绘制到屏幕上展示 图1

  11. scss中的include和extend区别
    打包产物extend相同的会合并,include单独写

  12. 盒模型

  13. Doctype作用?标准模式与兼容模式各有什么区别?

    Doctype是document type(文档类型),告诉浏览器解析器采用哪种规范(html、xhtml)来解析页面,Doctype不存在或格式错误的情况下,采用兼容模式。
    标准模式(严格模式)展示的支持最新标准的网页。兼容模式(松散模式或怪异模式)展示的是兼顾传统浏览器的网页,向后兼容老式浏览器。
    具体区别:

    类别标准模式兼容模式
    盒模型width=元素内容宽度(content-box)width=width + padding + border(box-sizing: border-box)
    百分比/行内高度给span设置宽高不生效;块级元素的父元素没有高度,子元素的百分比高度无效有效
    margin: auto水平居中有效无效,可用text-align解决
  14. 居中问题
    水平居中

    • 容器上定义一个width,然后设置margin: auto
    • 父容器的text-align: center,子容器的display:inline-block
    • 绝对定位,left: 50%; (margin-left: -宽度/2 或者css3 transform: translate(-宽度/2, 0),其中宽度不确定情况可以使用translate(-50%, 0))
    • flex方式, justify-content: center

    垂直居中

    • 单行文本line-height
    • 行内块级元素 display:inline-block; vertical-align: middle;
    • 元素高度不定,vertical-align只在父层为td或th时生效,并且父元素display: table,其它不起作用, 子元素display: table-cell; vertical-align:middle;
    • 绝对定位,top: 50%; (margin-top: -高度/2 或者css3 transform: translate(0, -高度/2),其中高度不确定情况可以使用translate(0,-50%))
    • flex方式,align-items: center;
  15. CSS3哪些新特性? 新增伪类有哪些?\
    css3新特性:圆角(border-radius)、阴影(box-shadow\text-shadow)、渐变(gradients)、过度与动画(transition and animations),新的布局方式:多列布局(column)、flex、grid。 新增伪类:

    新增伪类 | 作用 -------- | ------- p:first-of-type | 选择该父节点下的首个p元素 p:last-of-type | 选择该父节点下的最后p元素 p:only-of-type | 选择该父节点下,含有一个p元素的p节点,p可以有兄弟节点 p:only-child | 选择该父节点下,含有唯一一个元素且为p,不含有兄弟节点 p:nth-child | 选择该父节点下的第n个p节点 p:nth-last-child | 选择该父节点下的倒数第n个p节点 p:last-child | 选择该父节点下的最后一个p节点 p: empty | 选择没有子节点的p :not(p) | 选择非p的每个元素

  16. 行内元素和块级元素的区别?行内块元素的兼容性使用?

    • 布局上: 行内元素在一行展示,水平排列;块级元素占据一行,垂直排列

    • 结构上: 行内元素不可以插入块级元素,块级元素可以插入行内元素

    • 属性上: 行内元素设置width、height无效,margin和padding上下无效。比较常用的行内元素: a、b、em、i、img、input、label、span、strong、sub、sup、textarea \

      行内块元素的兼容性使用:

        div {
          *display: inline;
          *zoom: 1; // 触发haslayout
          // display: inline-block; 添加后兼容所有浏览器
        } 
      
  17. 父容器width和height分别为200 * 100, 子元素设置margin:50%时
    (margin百分比都是针对宽度,内联的上下都不起作用)
    a. 子元素的margin的百分比针对于父元素的Width
    b. 子元素margin:50%时,左右margin为100px,加自身的width超出了父元素宽度的范围,此时后面的元素会换行。因此,图中的'2222'虽然是inline-block方式,也在下一行。除非前一个margin的左右百分比之和小于50%,才会展示在一排
    c. 图中可以看出,子元素margin-top也是50%,所以父元素没有全包子元素,如果希望父元素全包子元素,可以通过在父元素添加overflow: scroll破坏BFC

GitHub

  1. AB里面的容器不等高,如何做到使两个背景等高?
    a. 通过flex方式,让父元素display: flex;\ GitHub
    b. 通过position方式,例如:左侧高度固定,父元素position: relatvie; 右侧position: absolute; top: 0; bottom;\ GitHub
    c. 父元素display: table; 两个子元素display: table-cell.\ GitHub
    d. 父元素使用display: grid; grid-template-colums: 1fr 200px;分为两列,子元素通脱grid-colum: 1/2占左边,另个占右边。\ GitHub

  2. px、em、rem
    px是相对于显示器屏幕分辨率而言的。
    em是相对于当前对象的文本的字体尺寸,如果当前对行内文本的字体尺寸未被设置,则依次向上查找直到相对于浏览器的默认字体
    rem是相对于html根元素(比较推荐)
    最后浏览器默认字体是16px,要设置一对一的对应关系,需要设置font-size:62.5%

            1rem 默认 16px, 如果想让计算变成 10px; x/1 = 10/16
    
            function fontAdapt(baseWidth = 7.5, maxWidth = Infinity) {
                  const docEl = document.documentElement;
                  docEl.style.fontSize = `${Math.min(docEl.clientWidth, maxWidth) / baseWidth}px`;
            }
    
         // 使用 1rem = 100px计算,clientWidth 对应 100px , 100vw 对应的font-size 对应x,100vw / docEl.clientWidth  =  x / 100
    
        font-size: calc(100vw / docEl.clientWidth * 100);
    
$designWidth = 375
html {
  margin: 0 auto;
  font-size: calc(100vw / $designWidth * 100);
  @media screen and (max-width: 320px) {
    font-size: calc(320px / $designWidth * 100);
  }
  @media screen and (min-width: 768px) {
    font-size: calc(768px / $designWidth * 100);
    max-width: 768px;
  }
}
   
  1. 简述react diff

    • 对比不同类型的元素: 当根节点为不同类型的元素时,会拆卸原有的树并建立新的树, React销毁原组件,建立新组件
    • 对比同一类型的元素: 当对比同类型的元素时,React会保留DOM节点,仅对比更新有改变的属性,处理完当前节点后,React继续对子节点进行递归
    • 对比同类型的组件元素:当组件更新时,组件实例保持不变,React调用相关方法更新props,下一步,调用render方法,diff算法将在之前的结果及新的结果中进行递归
    • 对子节点进行递归:在默认条件下,递归DOM节点的子元素时,React会遍历两个子元素列表,当产生差异时会生成一个mutation.。但是React没有意识是否该保留原有的子元素
    • key: 为了解决上面问题,采用key属性,当子元素拥有key时,React使用key来匹配原有树上的子元素和最新树上子元素。key可以和判断该节点是新增还是移动等效果
    • key不要采用index, 全局可以有相同的key,兄弟节点不要有相同的可以,否则,操作数据可能会异常,不更新的问题,例:删除第一条数据
  2. react 懒加载的实现原理

    webpack: github.com/webpack/web…
    webpack分割
    webpack分割:webpack遇到import后,会根据路径自动进行代码分割,将其编译成require.ensure, 模块内容会添加到一个分开的chunk中, 该方法通过传入的chunkId找到对应的js, 通过jsonp的方式加载js,并返回promise;
    import(): import()不同于import, 它类似require.ensure, 这个方法主要是用于插入script脚本并返回promise对象
    React.Lazy: 它返回的是一个LazyComponent对象,会根据status状态不同,返回不同,如果转发是Resolved,则直接返回模块默认导出(类似缓存?);否则,会通过throw的方式将thenable抛在外层
    React.Suspense: 它是可以根据状态指示对应内容是children还是fallback,在这里类似异常捕获组件, 如果thenable的状态是pending则渲染fallback的值,一旦状态resolved后重新渲染子组件

  3. 错误边界是什么?它有什么用?
    错误边界解决 js 错误导致整个应用崩溃的问题,它是 React 组件,可以捕获并打印发生在其子组件树任何位置的 js 错误,并渲染出备用 UI,而不是渲染那些崩溃的子组件树,它无法处理一下场景:事件处理/异步代码/服务端渲染/它自身的错误

  4. 聊聊 react的生命周期(新旧)
    v16.3前 v16.3后

image.png

  1. React16废弃的生命周期 componentWillMount/componentWillReceiveProps/componentWillUpdate
    废弃的原因是,在React16的fiber架构中,高优先级任务的出现打断现有任务导致被执行多次,调和过程会多次调用will周期,而不是一次执行。多次执行的话,如果周期有setState或dom操作,会触发多次重绘,影响性能,也会导致数据错乱。fiber中任务中断,willMount可能被执行多次,用constructor代替willMount.

  2. useMemo/useCallback具体区别
    useCallback返回的是memorized的回调函数,useMemo返回的是memorized的值 useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。

  3. setState 是异步还是同步, 以及如何实现多个state合并处理的

    this.setState({
      count: 2,
    });
    this.setState({
      count: 3,
    });
    console.log(this.state.count, "before setTimeout");
    setTimeout(() => {
      this.setState({
        count: 4,
      });
      console.log(this.state.count, "after setTimeout");
    });
    

    初始state为0,上面代码的执行结果before setTimeout是0,after setTimeout是3。
    过程是第一个setState/第二个setState会进行合并(但里面的setState排队等候),此时执行before setTimeout的console时还没完成setState的异步流程,因此结果为0;
    执行完成后进行fiber协调和commitwork提交,渲染count为3,根据scheduler策略进入setTimeout方法的执行,执行setState的fiber和commitwork。

    转存失败,建议直接上传图片文件 discreteUpdates 方法会修改 executionContext 的值,同步与异步的区别是在 ScheduleUpdateOnFiber 函数中,executionContext 是否为 0,为 0 的时候执行 flushSyncCallbackQueue,此时是进行 fiber 协调和 commitWork 等,完成后才完成 ScheduleUpdateOnFiber, 也就完成 setState 工作。 否则是先完成 setState,进行往下执行 console 和 setTimeout,click 方法完成后通过 scheduler 进入到 flushSyncCallbackQueue 流程,然后进行 fiber 和 commitWork.

    setState 同步情况:

    setTimeout/原生事件(window.addEventListener)

    setState 异步情况:

    合成事件/钩子函数, 异步的处理是为了多个 state 进行合并,优化性能。它所谓的异步,只是执行顺序的问题

    多个 setState 合并问题:

    执行setState,都是进行组件Component.prototype.setState方法的执行,它先进行enqueueUpdate,将所有更新的payload的update添加到当前的updateQuene里面,执行scheduleWork的时候,其实就是执行scheduleCallbackForRoot,它会根据当前root节点的root.callbackExpirationTime和expirationTime进行对比,只存一次runRootCallback方法到syncQueue中,之后进行一次renderRoot,先进行fiber处理,其中会执行processUpdateQueue方法,它就是将update链表的state进行object.assign的合并,之后进行commitWork渲染

    是否每次 setState 都会合并

    答案是否, 1, 2 在事件内所以是异步的,二者只会触发一次 render 操作,3, 4 是同步的,3,4 分别都会触发一次 render。

    function onClick(event) {
      setState({a: 1}); // 1
      setState({a: 2}); // 2
      setTimeout(() => {
          setState({a: 3}); // 3
          setState({a: 4}); // 4
      }, 0);
    }
    
    
    转存失败,建议直接上传图片文件
  4. 调用 setState 之后发生了什么

    a.加入更新队列:调用 setState 时,其实就是调用的Component.prototype.updateState, 将更新的 update 对象加入到 updateQueue,
    b.fiber 协调:调用 scheduleWork,进行 fiber 节点的协调,执行 reconcileChild,生成新的 fiber 数。
    c.执行 scheduler 相关方法,根据优先级高低具体调用相关方法,在 fiber 协调的过程中会对比新旧节点,并打上增删改查的相关 tag
    d.协调方法执行完成后,执行 performUnitOfWork,会对新节点生成对应的 dom, 同时回溯执行上面的方法
    e.但 performUnitOfWork 执行完毕后,进入 commitRoot 阶段,根据前面的 tag 对 DOM 进行更新操作。

    总结:react将传入的对象和组件状态合并,并通过调和计算新的DOM树,通过Diff算法对比更新, 从而进行最小化的重渲染。

  5. useEffect(fn, []) 和 componentDidMount 有什么差异

    useEffect(fn, [])实现了 componentDidMount 的功能,但是与 componentDidMount 不同的是,componentDidMount 在第一次执行 commitLayoutEffects 时就执行了 componentDidMount,此时是在浏览器完成布局和绘制前进行的。 useEffect(fn, [])是在浏览器完成布局与绘制后,通过 scheduler 调度执行的。这样 useEffect 比较适合用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因此在 useEffect 不应该执行阻塞浏览器更新屏幕的操作。

    转存失败,建议直接上传图片文件 转存失败,建议直接上传图片文件

    执行顺序大概是:ComponentDidMount -> 浏览器的挂载 -> useEffect(fn, [])

  6. useEffect和useLayoutEffect的差异

    useLayoutEffect 早于 useEffect 执行。
    执行顺序:useLayoutEffect -> 浏览器布局和绘制 -> useEffect 它们调用时机不同, useEffect 是在浏览器渲染(layout)和绘制(paint)之后才执行,但 useLayoutEffect 和 componentDidMount/componentDidUpdate 等一样,在第一次执行 commitLayoutEffects 的时候执行,是在浏览器渲染和绘制之前执行的。
    在执行 commitMutationEffects 的时候,DOM 变更完成,此时同步调用 effect 读取 DOM 布局并同步触发重渲染,尽可能使用标准的 useLayoutEffect 以避免阻塞视觉更新。最好把关于DOM的操作放在useLayoutEffect里避免闪速。
    useEffect 会在浏览器绘制后延迟执行,在组件更新前刷新上一轮渲染 effect。useEffect会阻塞渲染。

DOMContentLoaded 事件触发。

React 的组件更新时,useLayoutEffect 先执行。

浏览器渲染 DOM。

useEffect 执行。

所有资源加载完毕后,onLoad 事件触发。

  1. hooks 为什么不能放在条件判断里
    在 React 内部,hooks 是以链表的形式存在 memoizeState 属性中的,update 阶段,每次执行 setXX 方法,链表会执行 next 向后移动,如果 setXX 写在条件判断中,条件判断不成立时,没有执行 setXX 方法,会导致取值出现偏移

  2. fiber 是什么, 原理和作用
    Fiber是React 16中新的协调引擎,它主要目的是使Virtual DOM 可以进行增量式渲染。
    fiber是一个链表数据结构,能解决以前diff时间过长导致的卡顿问题,它用类似requestIdleCallback的机制做异步diff算法,方便做中断和恢复操作

  3. 为什么虚拟dom 会提高性能?
    a.真实 DOM 挂载的属性较多,比较比较耗时,虚拟 DOM 的属性较少
    b.虚拟 DOM 相当于在 js 和真实 DOM 中添加一个缓存,利用 diff 算法避免没必要的 dom 操作,从而提高性能
    另外采用虚拟DOM,不是因为单独的操作DOM比原生的操作DOM快,主要是它可移植性比较高,方便维护。

  4. 什么是 Portals?
    Portal 提供一种将子节点渲染到存在于父组件以外的 DOM 节点

    ReactDOM.createPortal(child, container);
    
  5. 什么是 suspense 组件?
    Suspense 是一个机制,让组件“等待”某个异步操作,直到该异步操作结束即可渲染。也可以用于懒加载

  6. React 组件间有哪些通信方式?
    a.props (父组件向子组件通信)
    b.回调(子组件向父组件通信)
    c.context (跨层级通信)
    d.react-redux/mobx 等
    e.发布订阅模式

  7. React 父组件如何调用子组件中的方法?
    通过 ref 获取到子组件对象

  8. hook和class对比
    a. hook 避免 class 的额外开支,像创建类实例和构造函数中绑定事件处理器的成本
    b. hook 不需要很深的组件树嵌套,组件树小了,React 的工作量随之减少 传统上认为,react 使用內联函数对性能的影响,每次渲染传递新的回调会破坏子组件 shouldComponentUpdate 优化,hook 从三个方面解决了这个问题
    c. useCallback, 允许你在重新渲染之间保持对相同回调引用,
    d. useMemo 使得控制具体的子节点何时更新变得更容易,减少对纯组件的需要,
    e. useReducer 减少对深层传递回调的依赖

  9. React 有哪些优化性能的手段?
    类组件

    a.React.memo,props 进行浅比较
    b.PureComponent
    c.shouldComponentUpdate 自定义渲染逻辑

    hook

    a.useCallback/useMemo 通过【记住】上一次计算结果的方式在多次渲染的之间缓存计算结果

    其它

    a. 使用 id 作为 key
    b. 通过 css 隐藏显示组件,而不是通过条件隐藏组件
    c. 懒加载

  10. 为什么 React 元素有一个 $$typeof 属性?
    目的是为了防止 XSS 攻击,因为 Symbol 无法被序列化,

  11. React 如何区分 Class组件 和 Function组件?
    isSimpleFunctionComponent类组件都继承自React.Component image.png

  12. HTML 和 React 事件处理有什么区别?
    a.HTML 是原生事件,React 是复合事件
    b.HTML 事件名小写,React 事件名驼峰
    c.HTML 事件挂在当前节点,React 事件都是挂在 Document 上(v17之后挂在root上),React 17 之前是将合成事件委托在 document 元素上的,17 之后将合成事件委托在 root 元素上了。这样做的好处是页面上可以共存多个 react 版本了,以后做迁移的时候也可以是渐进式的了。
    d. HTML 事件可以返回false阻止默认操作,React必须调用preventDefault才可以
    f. HTML 事件可以通过e.stopPropogation阻碍 React合成事件的执行,反之,不可以。
    g. HTML 事件比React事件先执行

  13. React合成事件的好处
    a.兼容浏览器监听写法,不同浏览器事件属性不一样,合成事件将其统一处理,磨平差异
    b.避免大量节点绑定事件占用内存,将事件委托到 document 上,有统一的事件处理函数,等事件冒泡到 document 上,React 从 target 节点往上遍历父元素,判断有没有元素绑定对应事件,有则触发
    c.合成事件没法阻止原生事件的执行

    react合成事件的好处

    执行过程:
    事件初始化:将所有的事件click/blur等初始化;
    事件注册:在workLoop对fiber进行协调的时候进行事件注册,将onClick这些props属性挂载fiber的memorize上,在document上注册一个dispatchDiscreteEvent方法,
    点击:点击获取到当前节点的fiber结构,通过parent依次往上查询到document,将当前节点到document的保存在数组中,执行document的事件,它会依次正序和逆序遍历数组,相当于事件的捕获和冒泡。

  14. 为什么 JSX 中的组件名要以大写字母开头?
    判断渲染的是组件还是 HTML 元素

  15. render Props
    render prop 是一个用于告知组件需要渲染什么内容的函数 prop

    优点:数据共享、代码复用,将组件内的state作为props传递给调用者,将渲染逻辑交给调用者
    缺点:无法在return语句外访问数据、嵌套写法不优雅

  16. hoc

    高阶组件是React复用组件的一种高级技巧,它是基于React的组合设计模式,高阶组件是参数为组件,返回值是新组件的函数。

    优点:逻辑复用、不影响被包裹组件内部逻辑
    缺点:hoc传递给被包裹组件的props容易和被包裹组件重名,进而被覆盖;同时嵌套写法不够优雅

  17. hooks思想
    组件复用问题:class组件一般通过hoc或render Props的方式,会产生嵌套地狱的问题。hooks使你无需在修改组件结构的同时复用状态逻辑

    组件理解问题:class组件有一些生命周期,很多函数被拆分到各个生命周期中,比较分散不易理解。hooks能够将相关联的函数拆分放在一起便于阅读管理。

    优点:
    解决hoc的prop重名问题
    解决render props因数据共享而出现嵌套问题
    能在return后使用数据的问题

    总结∶

    Hoc、render props 和 hook 都是为了解决代码复用的问题,但是 hoc和 render props 都有特定的使用场景和明显的缺点。hook 是react16.8 更新的新的 API,让组件逻辑复用更简洁明了,同时也解决了 hoc 和 render props的一些缺点。

  18. react-router原理

    客户端路由实现的思想:
    基于 hash 的路由:通过监听 hashchange 事件,感知 hash 的变化,改变 hash 可以直接通过 location.hash=xxx

    基于 H5 history 路由:改变 url 可以通过 history.pushState 和 resplaceState 等,监听PopStateEvent方法,会将 URL 压入堆栈,同时能够应用 history.go() 等 API监听 url 的变化可以通过自定义事件触发实现

    react-router 实现的思想:

    基于 history 库来实现上述不同的客户端路由实现思想,并且能够保存历史记录等,磨平浏览器差异,上层无感知通过维护的列表,在每次 URL 发生变化的回收,通过配置的路由路径,匹配到对应的 Component,并且 render。

    当地址url发生改变时,history通过popState事件监听到状态改变,通过setState更新新的location对象,然后router组件通过context传递location信息,switch匹配出对应的组件,然后组件取出context内容给渲染页面

  19. 什么是webpack

    webpack是用于js应用程序静态模块打包工具。其流程主要是:
    参数合并:通过配置文件webpack.config.js和shell命令进行参数合并;
    开始编译:用上一步获取到的参数,初始化Compiler对象,并加载各类插件,执行run方法进行编译;
    确定入口:从入口entry获取到文件入口,并生成AST语法树,根据依赖关系递归执行;
    编译模块:递归中,根据文件类型和对应的loader对文件进行编译处理;
    完成模块编译:递归完成后,获取编译内容和依赖关系,生成对应的chunk模块;
    输出完成:将chunk模块根据对应依赖关系写到文件系统

  20. webpack如何区分环境打包/webpack优化

    ⽤webpack 优化前端性能是指优化 webpack 的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。

    压缩代码:删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤webpack的 UglifyJsPlugin 和ParallelUglifyPlugin 来压缩JS⽂件, 利⽤ cssnano (css-loader?minimize)来压缩 css

    利⽤CDN 加速: 在构建过程中,将引⽤的静态资源路径修改为 CDN 上对应的路径。可以利⽤webpack 对于 output 参数和各 loader 的publicPath 参数来修改资源路径

    Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动 webpack 时追加参数 --optimize-minimize 来实现

    Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存

    提取公共第三⽅库: SplitChunksPlugin 插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码

  21. 如何提高webpack构建速度?

    a.多⼊⼝情况下,使⽤ CommonsChunkPlugin 来提取公共代码

    b.通过 externals 配置来提取常⽤库

    c.利⽤ DllPlugin 和 DllReferencePlugin 预编译资源模块 通过DllPlugin 来对那些我们引⽤但是绝对不会修改的 npm 包来进⾏预编译,再通过 DllReferencePlugin 将预编译的模块加载进来。

    d.使⽤ Happypack 实现多线程加速编译

    e.使⽤ webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。原理上 webpack-uglify-parallel 采⽤了多核并⾏压缩来提升压缩速度

    f.使⽤ Tree-shaking 和 Scope Hoisting 来剔除多余代码

  22. bundle、chunk和module区别

    bundle:webpack打包后的文件
    chunk: 代码块,一个chunk由多个模块组合而成,用于代码分割和合并
    module:模块,一个文件对应一个模块

  23. plugin和loader区别
    loader:实现对不同文件的处理,如JSX转化为js;主要在module.rules规则下进行配置(如file-loader)
    plugin:plugin是在webpack整个生命周期构建流程中执行,通过监听事件修改输出结果;主要在plugins中单独配置(如new HtmlWebpackPlugin())

  24. url-loader和file-loader, raw-loader的区别?

    url-loader是处理图片,如果低于limit限制,就采用base64的,否则采用file-loader的方式; file-loader是文件处理,复制一份文件,以hash命名保存文件系统下; raw-loader是为了将文件格式问题进行处理,webpack会将图片以UTF-8的处理,但图片是二进制的方式。

  25. less-loader、css-loader、style-loader区别
    less-loader:编译less为css
    css-loader: 将css转化为js模块, 并处理@import/url内容
    style-loader: 通过js创建样式节点style插入\

  26. 热更新实现原理

    热更新(HMR)是可以让浏览器不刷新而变更模块。

    ⾸先要知道 server 端和 client 端都做了处理⼯作:

    image.png

    第⼀步,在 webpack 的 watch 模式下,⽂件系统中某⼀个⽂件发⽣修改,webpack 监听到⽂件变化,根据配置⽂件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript对象保存在内存中。

    第⼆步是 webpack-dev-server 和 webpack 之间的接⼝交互,⽽在这⼀步,主要是 dev-server 的中间件 webpack- dev-middleware和 webpack 之间的交互,webpack-dev-middleware 调⽤ webpack暴露的 API 对代码变化进⾏监 控,并且告诉 webpack,将代码打包到内存中。

    第三步是 webpack-dev-server 对⽂件变化的⼀个监控,这⼀步不同于第⼀步,并不是监控代码变化重新打包。当我们在配置⽂件中配置了 devServer.watchContentBase 为 true 的时候,Server 会监听这些配置⽂件夹中静态⽂件的变化,变化后会通知浏览器端对应⽤进⾏ live reload。注意,这⼉是浏览器刷新,和 HMR 是两个概念。

    第四步也是 webpack-dev-server 代码的⼯作,该步骤主要是通过sockjs(webpack-dev-server 的依赖)在浏览器端和服务端之间建⽴⼀个 websocket ⻓连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,同时也包括第三步中 Server 监听静态⽂件变化的信息。浏览器端根据这些 socket 消息进⾏不同的操作。当然服务端传递的最主要信息还是新模块的 hash 值,后⾯的步骤根据这⼀hash 值来进⾏模块热替换。

    webpack-dev-server/client 端并不能够请求更新的代码,也不会执⾏ 热 更 模 块 操 作 , ⽽ 把 这 些 ⼯ 作 ⼜ 交 回 给 了 webpack ,webpack/hot/dev-server 的 ⼯ 作 就 是 根 据webpack-dev-server/client 传给它的信息以及 dev-server 的配置决定是刷新浏览器呢还是进⾏模块热更新。当然如果仅仅是刷新浏览器,也就没有后⾯那些步骤了。

    HotModuleReplacement.runtime 是客户端 HMR 的中枢,它接收到上⼀ 步 传 递 给 他 的 新 模 块 的 hash 值 , 它 通 过JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回⼀个 json,该 json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。这就是上图中 7、8、9 步骤。

    ⽽第10步是决定 HMR 成功与否的关键步骤,在该步骤中,HotModulePlugin 将会对新旧模块进⾏对⽐,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引⽤。

    最后⼀步,当 HMR 失败后,回退到 live reload 操作,也就是进⾏浏览器刷新来获取最新打包代码。

  27. Babel的原理 babel 的转译过程也分为三个阶段,这三步具体是:

    解析 Parse: 将代码解析⽣成抽象语法树(AST),即词法分析与语法分析的过程;

    转换 Transform: 对于 AST 进⾏变换⼀系列的操作,babel 接受得到 AST 并通过 babel-traverse 对其进⾏遍历,在此过程中进⾏添加、更新及移除等操作;

    ⽣成 Generate: 将变换后的 AST 再转换为 JS 代码, 使⽤到的模块是 babel-generator。

    可参考the-super-tiny-compiler编译分析

  28. webpack Compiler和Compilation区别
    compiler 对象代表的是构建过程中不变的 webpack 环境,整个 webpack 从启动到关闭的生命周期。针对的是webpack。
    compilation 对象只代表一次新的编译,只要项目文件有改动,compilation 就会被重新创建。针对的是随时可变的项目文件。

  29. 网站优化手段

    a.减少http请求(图片懒加载/文件合并)
    b.减少回流/重绘
    c.动画优化(position:fixed脱离文档,其实同b相似)
    d.文件压缩(js/css/gzip)
    e.css优化 F.缓存(CDN)

  30. 算法:快排、斐波拉契、递归、冒泡、红二叉树、堆栈
    参考:www.runoob.com/w3cnote/ten…

  31. 手写call、apply、bind函数
    call与apply是立即执行,bind可以后面执行
    call传入参数是依次传入,apply传入参数是数组

    
        // 使用 func.call(context, arg1, arg2, ...)
        Function.prototype.myCall = function(context, ...args) {
    
          // 获取上下文
          context = context || window
    
          // 将调用函数设置为对象
          context.fn = this
    
          // 调用函数
          let result = context.fn(...args)
    
          // 删除属性
          delete context.fn
    
          return result
        }
    
        // 示例用法 
        function greet(message) { 
            console.log(`${message}, ${this.name}!`); 
        } 
        const person = { name: 'Alice' }; 
        greet.myCall(person, 'Hello'); // 输出:Hello, Alice!
    
      // 使用func.apply(context, [arg1, arg2,...args])
      Function.prototype.myApply = function (context, argsArray) {
        context = context || window;
        context.fn = this;
        var result = context.fn(...argsArray)
        delete context.fn
        return result
      }
    
         // 示例用法 
        function greet(message) { 
            console.log(`${message}, ${this.name}!`); 
        } 
        const person = { name: 'Alice' }; 
        greet.myCall(person, ['Hello']); // 输出:Hello, Alice!
    
    
      // 使用func.bind(context, args)
     Function.prototype.myBind = function (context, ...args) {
        context = context || window;
        context.fn = this;
        return function (...innerArgs) {
           return context.fn.apply(context, args.concat(innerArgs))
        }
     }
    
        // 示例用法 
        function greet(message) { 
            console.log(`${message}, ${this.name}!`); 
        } 
        const person = { name: 'Alice' }; 
        const boundGreet = greet.myBind(person, 'Hello'); 
        boundGreet();
    
  32. 手写debounce、throttle函数

    防抖debounce:是指事件在触发n秒后执行,如果n秒内又被触发,时间重新计算。可以避免多次点击的问题(如按钮提交、scroll事件)
    节流throttle:指一个时间内只执行一次,如果一个时间内,事件被触发多次,也只执行一次(拖拽、缩放、动画)

        // 防抖使用debounce(func, wait, immediate)
        const debounce = function(func, wait, immediate){
            let timeId = null
    
            function debounced(...args){
                const context = this;
                if (immediate) {
                   func.apply(context, args)
                }
                if (timeId) {
                  clearTimeout(timeId)
                }
               timeId = setTimeout(() => {
                   func.apply(context, args)
               }, wait)
            }
    
            function cancel() {
              if (timeId) clearTimeout(timeId);
              timeId=null;
            }
    
            debounced.cancel = cancel;
            return debounced
        }
        // 节流(时间控制)
       const throttle = function(func, wait){
           let previous = 0;
           function throttled(...args) {
              const context = this
              let now = new Date().getTime();
              if (now - previous > wait) {
                 previous = now;
                 func.apply(context, args)    
              }
           }
           return throttled
       }
    
       // 节流(定时器)
       const throttle = function(func, wait) {
          let timeId = null;
          function throttled(...args) {
            const context = this;
            if (timeId) return;
            timeId = setTimeout(() => {
              func.apply(context, args)
              timeId = null
            }, wait)
          }
          return throttled
       }
    
  33. 手写promise、async/await、generator函数

       // 使用 new Promise(function(resolve, reject)).then(function(resolve,reject){
       })
      function Promise(fn) {
        // 状态/值/错误等绑定在对象上
        const self = this;
        // 状态
        self.state = 'pending';
        // 成功的值
        self.value = undefined;
        // 异常提示
        self.reason = '';
    
        // 储存多个promise回调
        self.onResolveCallbacks = [];
        self.onRejectCallbacks = [];
    
        const resolve = function(value) {
            self.state = 'fulfilled';
            self.value = value;
            self.onResolveCallbacks.forEach(fn => fn())
        }
        const reject=function(reason){
            self.state='rejected';
            selft.reason = reason;
            self.onRejectCallbacks.forEach(fn => fn())
        }
    
        try {
            fn(resolve, reject)
        } catch(error) {
            reject(error)
        }
      }
    
      const resolvePromise = function(promise, value, resolve, reject) {
       if ((typeof value === 'object' && value !== null ) || typeof value === 'function') {
        if (typeof value.then === 'function') {
          // 如果value是函数的话,执行then继续,
          value.then.call(value, (success) => {
            resolvePromise(promise, success, resolve, reject)
          }, (fail) => {
            reject(fail)
          })
        } else {
          resolve(value)
        }
      } else {
        resolve(value)
      }
      }
    
      Promise.prototype.then = function(resolve, reject) {
      const self = this;
      // 这里的resolve/ reject可能没值,需要包装下
      resolve = typeof resolve === 'function' ? resolve : noop;
      reject = typeof reject === 'function' ? reject : function(err){throw(err)}
      let promise2 = new Promise((_resolve, _reject) => {
        if (self.state === 'fulfilled') {
          // 这里的res可能是then执行后返回的Promise,因此需要循环继续调用
         setTimeout(() => {
          try {
            let res = resolve(self.value);
            resolvePromise(promise2, res, _resolve, _reject)
          } catch (error) {
            reject(error)
          }
         }, 0)
        }
    
        if (self.state === 'rejected') {
          setTimeout(() => {
            try {
              let res = reject(self.reason);
              resolvePromise(promise2, res, _resolve, _reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
    
        }
    
        if (self.state === 'pending') {
          self.onResolvedCallbacks.push(() => {
            setTimeout(() => {
              try {
                let res = resolve(self.value);
                resolvePromise(promise2, res, _resolve, _reject)
              } catch (error) {
                reject(error)
              }
             }, 0)
          })
    
          self.onRejectedCallbacks.push(() => {
            setTimeout(() => {
              try {
                let res = reject(self.reason);
                resolvePromise(promise2, res, _resolve, _reject)
              } catch (error) {
                reject(error)
              }
            }, 0)
          })
        }
      });
      return promise2;
    }
    
     Promise.prototype.finally = function(cb) {
       return this.then((value) => {
        return Promise.resolve(cb()).then(() => value)
       }, (err) => {
        return Promise.resolve(cb()).then(() => {
          throw err
        })
       })
     }
    
    
    
    
  34. AJAX 手写一下

    
    const ajax = (method='get',url, async = true) => {
        // 实例化对象
        const xhr = new XMLHttpRequest()
        xhr.onreadystatechange = function(){
            if(this.readyState !== 4) return;
             if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
                  // success
              } else {
                  // error
              }
        }
    
       // 指定请求
       xhr.open(method, url, async)
    
       // 发送请求
       xhr.send()
    }
    
    
    // promise版本
    
    const promiseAjax = (method='get', url, async = true) => {
       return new Promise(function(resolve, reject){
            const xhr = new XMLHttpRequest()
            xhr.onreadystatechange = function () {
                if (this.readyState !== 4) return 
                if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
                  // success
                    resolve(this.response)
              } else {
                  // error
                   reject(new Error(this.statusText))
              }
            }
    
           xhr.open(method, url, async);
           xhr.send
        }) 
    }
    
    function generator(list) {
       let index = 0;
       let len = list.length;
       return {
          next: function() {
             let done = index >= len;
             let value = done ? undefined : list[index++];
             return {
                done,
                value
             }
          }
       }
    }
    
    funtion asyncToGenerator(generatorFunc) {
       // 返回一个新函数
       return function() {
          // 先调用generatorFunc生成迭代器
          const gen = generatorFunc.apply(this, arguments);
          return new Promise((resolve, reject) => {
              function step(key, arg) {
                 let generatorResult;
                 try {
                     generatorResult = gen[key](arg)
                 } catch(error) {
                     reject(error)
                 }
    
                 const { done, value } = generatorResult;
                 if (done) {
                    resolve(value)
                 } else {
                    return Promise.resolve(value).then(function onResolve(val) {step('next', val)}, function onReject(err) {
                    step('throw', err)
                    })
                 }
              }
              step('next');
          })
    
       }
    }
    
    
  35. ajax/fetch/axios区别

    AJAX

    Ajax 即“AsynchronousJavascriptAndXML”(异步 JavaScript 和XML),是指一种创建交互式网页应用的网页开发技术。它是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。 这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。其缺点如下: \

    本身是针对 MVC 编程,不符合前端 MVVM 的浪潮
    基于原生 XHR 开发,XHR 本身的架构不清晰
    不符合关注分离(Separation of Concerns)的原则
    配置和调用方式非常混乱,而且基于事件的异步模型不友好。

    Fetch

    fetch 号称是 AJAX 的替代品,是在 ES6 出现的,使用了 ES6 中的promise 对象。Fetch 是基于 promise 设计的。Fetch 的代码结构比18起 ajax 简单多。fetch 不是 ajax 的进一步封装,而是原生 js,没有使用 XMLHttpRequest 对象。

    fetch 的优点:
    语法简洁,更加语义化
    基于标准 Promise 实现,支持 async/await
    更加底层,提供的 API 丰富(request, response)
    脱离了 XHR,是 ES 规范里新的实现方式

    fetch 的缺点:
    fetch 只对网络请求报错,对 400,500 都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。 fetch 默 认 不 会 带 cookie , 需 要 添 加 配 置 项 : fetch(url,{credentials: 'include'})
    fetch 不 支 持 abort , 不 支 持 超 时 控 制 , 使 用 setTimeout 及Promise.reject 的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
    fetch 没有办法原生监测请求的进度,而 XHR 可以

    Axios

    Axios 是一种基于 Promise 封装的 HTTP 客户端,其特点如下: 浏览器端发起 XMLHttpRequests 请求
    node 端发起 http 请求
    支持 Promise API
    监听请求和返回
    对请求和返回进行转化
    取消请求
    自动转换 json 数据
    客户端支持抵御 XSRF 攻击

  36. 常见设计模式

    • 观察者模式:主体和观察者是相互感知的
    • 发布订阅模式:借助第三方来调度,发布者和订阅者相互感知不到。
    • 单例模式:
    • 工厂模式:
    • 代理模式:
    • 策略模式:
    • 装饰器模式:
  37. 如何实现浅拷贝和深拷贝?

    答:浅拷贝是拷贝原对象的引用;浅拷贝只是将对象的各个属性一次进行拷贝,不会进行递归拷贝

      function shallowCopy(source) {
        var targetObj = source.constructor === Array ? [] : {}
        for(var key in source) {
          targetObj[key] = source[key]
        }
        return targetObj
      }
    
      深拷贝是拷贝出一个新的实例,新实例和之前的实例互不影响。深拷贝不仅将各个对象的属性拷贝出来,而且会递归拷贝各个属性所包含的对象。  
    
      function deepCopy(source) {
        var targetObj = source.constructor === Array ? [] : {}
        for (var key in source) {
          if (source[key] && typeof source[key] === 'object') {
            <!--targetObj[key] = source[key].constructor === Array ? [] : {}-->
            targetObj[key] = deepCopy(source[key])
          } else {
            targetObj[key] = source[key]
          }
        }
        return targetObj
      }
    
      <!-- 利用JSON序列化实现的深拷贝 -->
      function deepCopy(source) {
        return JSON.parse(JSON.stringify(source))
      }
    
      <!-- es6 -->
      object.assign(target, ...sources)
    
  38. 深度遍历和广度遍历
    深度遍历:从根节点依次往下遍历,并从左到右
    广度遍历:从根节点依次找子节点,从左往右,找到后继续子节点循环

    // 深度遍历 递归方式
      function DFS (node) {
          let res = []
          if (!node) return null
          res.push(node);
          const children = node.children;
          for (let i = 0; i < children.length; i++) {
            DFS(children[i])
          }
          return res
     }
    
    // 深度遍历 栈数据结构
    function DFS(node) {
      let res = [];
      if (!node) return null;
      let stack = [];
      stack.push(node);
      while(stack.lenght) {
        const pop = stack.pop();
        res.push(pop)
        const children = pop.children;
        for (let i = children.length - 1; i >= 0; i--) {
           stack.push(children[i])
        }
      }
      return res
    }
    
    
    //广度遍历 
    
    function BFS(node) {
      if (!node) return null;
      let res = [];
      let i = 0;
      while(node) {
        res.push(node);
        node = res[i++];
        let children = node.children;
        for (let j = 0; j < children.length; j++) {
           res.push(children[j])
       }
      }
    
    }
    
  39. lazyman

    //prototype版本
    function _Lazyman(name) {
      this.name = name;
      this.tasks = [];
    
      this.talk = () => {
        this.tasks.push(() => {
          console.log(`Hi! this is ${name}`);
          this.next();
        })
      }
    
      this.talk();
       // 下一个事件循环执行
      setTimeout(this.next.bind(this), 0);
    }
    
    _Lazyman.prototype.next = function(){
      const task = this.tasks.shift();
      task && task();
    }
    
    _Lazyman.prototype.sleep = function (time) {
      const that = this;
      this.tasks.push(function() {
        setTimeout(() => {
          console.log(`Wake up after ${time}`)
          that.next()
        }, time * 1000)
      })
      // 链式调用
      return this
    }
     _Lazyman.prototype.eat = function (food) {
      const that = this;
      this.tasks.push(function () {
          console.log(`Eat ${food}~`)
          that.next()
        })
      return this
    }
    
    _Lazyman.prototype.sleepFirst = function(time) {
      const that = this
      this.tasks.unshift(function () {
        setTimeout(function () {
          console.log(`Wake up after ${time}`)
          that.next()
        }, time * 1000)
      })
      return this
    }
    
    // 使用
    var lazyman = function(name) {
      return new _Lazyman(name)
    }
     lazyman('Hank').sleep(2).eat('dinner').sleepFirst(3)
    
    
    //es6 版本
    class _Lazyman {
      constructor(name) {
        this.tasks = [];
        const task = () => {
          console.log(`hi! this is ${name}`);
          this.next()
        }
        this.tasks.push(task);
        setTimeout(() => {
          this.next()
        }, 0)
      }
    
      next() {
        const task = this.tasks.shift();
        task && task()
      }
    
      sleepWrapper(time, first) {
        const task = () => {
          setTimeout(() => {
            console.log(`wake up after ${time}`)
            this.next();
          }, time * 1000)
        }
        if (first) {
          this.tasks.unshift(task)
        } else {
          this.tasks.push(task)
        }
      }
    
      sleep(time) {
        this.sleepWrapper(time, false)
        return this;
      }
    
      sleepFirst(time){
         this.sleepWrapper(time, true);
         return this;
      }
    
      eat(name) {
        const task = () => {
          console.log(`eat ${name}`);
          this.next();
        }
        this.tasks.push(task);
        return this;
      }
    }
    
    // 使用
    var lazyman = function(name) {
      return new _Lazyman(name)
    }
     lazyman('Hank').sleep(2).eat('dinner').sleepFirst(3)
    
    //promise 版本
    function lazyman(name) {
      const tasks: any = [];
    
      const methods = {
        talk(name) {
          tasks.push(() => console.log(`Hi! This is ${name}`))
          return this
        },
        eat(name) {
          tasks.push(()=> console.log(`Eat ${name}`))
          return this;
        },
        sleepFirst(time) {
          tasks.unshift(() => new Promise(resolve => setTimeout((arg) => {
            console.log(`wake up after ${time}`)
            resolve(arg)
          }, time * 10)))
          return this
        },
        sleep(time) {
          tasks.push(() => new Promise(resolve => setTimeout((arg) => {
            console.log(`wake up after ${time}`)
            resolve(arg)
          }, time * 10)))
          return this
        }
      }
    
      setTimeout(function run() {
        if(!tasks.length) return
        Promise.resolve(tasks.shift()()).then(run)
      }, 0)
      methods.talk(name)
      return methods;
    }
    
    // 使用
     lazyman('Hank').sleep(2).eat('dinner').sleepFirst(3)
    
  40. 什么是CDN

    CDN依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取内容,降低网络延迟,提高访问速度。简单来讲,CDN是用来进行加速的,它可以让用户更快获得所需的数据。

    CDN工作流程:
    a.CDN请求当地local DNS;
    b.当地local DNS查询服务器的gslb;
    c.服务器根据最佳节点返回对应的ip;
    d.用户根据ip找到最佳节点,如果没内容就通过内部路由访问上一个节点,直到找到文件或源为止。
    e.CDN节点缓存该数据,下次直接请求返回

  41. redux流程图

  42. redux与mobx区别 类别 | redux | mobx
    ----|----|---- store | 单一 | 多个
    管理数据 | 干净 | 复杂、凌乱
    数据对象 | plain javascript | observable对plain javascript进行包装的

  43. redux是什么?

    Redux是Javascript的状态容器,提供可预测的状态管理。优点:解决跨层级的数据交互,缺点是内容复杂不容易上手

  44. react-redux的实现原理?
    react-redux主要api是Provider/connect, 其中Provide是结合context来实现的,使用发布订阅的方式来监听store的变化;
    a. Subscription类实现了发布订阅逻辑;
    b. Provider传入store作为Context, 便于下面组件获取,并订阅store的变化subscription.onStatechange
    c.connect高阶组件:获取要注入的组件中的值,并将它注入到Props,订阅Props更新组件
    d.selector,负责获取store中的state

  45. css动画库及原理

  46. 拖拽

    针对源对象
    onDragStart: 对源对象开始拖拽
    onDrag: 对源对象拖拽中
    onDragEnd: 对源对象拖拽结束\

    针对拖拽进入的容器
    onDragEnter: 拖拽对象进入到容器
    onDragover: 拖拽对象一直在容器中拖动
    onDragLeave: 拖动对象离开容器
    onDrop: 鼠标放开

  47. 学完React源码后的感受及如何应用平时开发

    1. 事件委托:Ul.li
    2. 递归: 异步,算法思想
    3. requestIdleCallback: 切片的问题
    4. 代码封装,设计模式React.createElement
  48. vite与webpack区别

    首先,底层语言实现方式的问题,vite采用go语言,webpack是使用nodejs 其次,实现逻辑,webpack需要先分析各个模块的依赖关系,然后启动服务;vite先启动服务,然后采用module形式,动态引入

  49. 白屏与首屏

    白屏是浏览器输入url到显示内容 首屏是浏览器输入url到内容渲染完成(定义可能有些不同)

  50. https协商算法

  51. JS实现一个带并发限制的异步调度器Scheduler

    
    class Scheduler {
      constructor(limit) {
        this.limit = limit;
        this.list = [];
        this.curNum = 0;
      }
      run = async () => {
        console.log(this.list, "----this.list", this.curNum, this.list.length);
        while(this.curNum < this.limit && this.list.length) {
          let shift = this.list.shift();
          this.curNum += 1;
          await shift()
          this.curNum -= 1;
        }
      };
    
      add = (fn) => {
        this.list.push(fn);
        return this.run();
      };
    }
    
    //异步任务函数
    const fetchUser = (name, delay) => {
      return () => new Promise((resolve) => {
        setTimeout(resolve, delay)
      }).then(() => console.log(name))
    }
    
    let scheduler = new Scheduler(2);
    scheduler.add(fetchUser('A', 2000));
    scheduler.add(fetchUser('B', 1000));
    scheduler.add(fetchUser('C', 800));
    scheduler.add(fetchUser('D', 500))
    
    
  52. redux与useReducer的区别
    相同点:
    统一store管理数据
    通过action修改数据
    差异:
    hooks没有middleware解决方案
    hooks的reducer来源于useReduser

  53. React版本区别

    React16.8引入hooks

    React17 将事件挂在document上改到root上

  54. 柯里化函数实现add(1)(2)(3),参数有限和无限的计算

118.单页和多页区别

标题单页面应用(singlePage web application)多页面应用(multiPage web application)
组成一个外壳页面(root)和多个页面片段多个完整的页面
资源共用(js\css)共用,最外层加载不共用,每个独立加载
刷新方式局部刷新或更改整页刷新
url一般没有.html后缀以.html为后缀
数据传递方便(redux, props)url、storage、cookie
seo不方便seo,需要结合ssrseo容易
开发成本较高、专业框架较低,重复代码多
维护成本较低较高
  1. 图片类型

png/jpeg/gif/svg

Webp:加快图片加载速度的图片格式,图片压缩体积大约只有 JPEG 的 2/3

Apng,如:im7.ezgif.com/tmp/ezgif-7… 是PNG的位图动画扩展, 可以实现png格式的动态图片效果

image.png

image.png

  1. MVC 和 MVVM 及在前端的应用 M: Model(状态state或外部API数据) V: View(试图界面UI) Controller: 负责接收用户输入,处理业务逻辑,更新Model和View VM: 双向数据绑定和试图,处理业务逻辑,自动更新试图
  2. 数据绑定方式:
  • MVC:单项数据绑定,需要手动处理数据更新
  • MVVM:强调双向数据绑定,试图和数据可以同步
  1. 关注点不同:
  • MVC:更强调分离业务逻辑、数据和UI,控制器扮演了主要角色,直接影响视图
  • MVVM:强调数据和UI的联动,ViewModel介于视图和模型之间,负责数据同步和业务逻辑
  1. 前端场景:
  • MVC: 多用于传统后端渲染框架,没有双向数据绑定的前端
  • MVVM:现代框架,如Vue
  1. 在构造函数中,super(props)的作用是什么?
  • super: 在子类的构造函数中,必须先调用super()继承父类, 否则this关键字在构造函数中是未定义的,无法使用

  • 使用super(props)是需要将props传递给父类的构造函数

  1. react-router中的link标签和a标签的区别

link标签在单页面中进行路由导航,它利用虚拟DOM,只需要更新必要的部分,不需要刷新整个页面。a标签会整个页面进行刷新

  1. window.onLoad和window.DomContentLoad执行顺序
  • DomContentLoad: 在初始化html被完全加载和解析时触发,而无需等待样式表、图像和子框架的完成加载。意味着在DOM构建完成后,立即可以访问和操作DOM元素
  • window.onload事件在页面的所有资源(包括样式表和图片及子框架)都加载完成后触发。意味着页面完全加载完成后才执行。通常发生在DomContentLoad后执行

参考:
1.react原理问题
2.React问题
3.how browsers work