前端面试基础题总结 (必会)

回答问题的原则: 能用量词, 尽可能用量词, 注意: 不要一直然后然后, 要有条理
推荐: 总分总结构 / 第一, 第二, 第三, 第四 ...


0. 事件冒泡和捕获?
   事件冒泡:当子元素(事件源)事件触发,事件会沿着包含关系,
      依次往上级传递,每一级都可以感知到事件,直到触发根元素(根源)
     阻止冒泡:在相应的函数中加上event.stopPropagation()不让事件向document上蔓延

  事件捕获:事件从最上一级标签开始往下查找,直到捕获到事件目标(target)
  阻止捕获:stopImmediatePropagation()方法来阻止事件捕获或冒泡


  事件委托:也叫事件代理,“事件代理”即是把原本需要绑定在子元素的响应事件
  (click、keydown…)委托给父元素,让父元素担当事件监听的职务。事件代理的
  原理是DOM元素的事件冒泡
  优点:减少事件注册减少内存 在table上代理所有的td点击事件 在ul上代理所有的li点击事件
  缺点:对不冒泡短的事件不支持 层级过多 冒泡过程可能会被某层阻止 可能会出现事件误判 单击变双击


1. 前端有哪几种本地存储方式?  5种
   (1)cookie (过期时间expires)        
      请求时自动携带, 4k, 原生操作麻烦 (插件: js-cookie)
      生命周期: 默认是会话级别, 关闭浏览器就销毁 => 但是可以设置过期时间
   (2)localStorage
      永久存储, 5M
   (3)sessionStorage

      会话级别, 页面关闭就销毁, 临时暂存数据 5M
   -----------------------------------------------------------------

   (4)websql          已废弃
   (5)indexedDB       基于键值对存储, 可以存大量的数据, 至少可以存几百M


2. JS 的参数是以什么方式进行传递的?考你值类型 和 引用类型的传值
   JS中的数据类型 2种:
   - 简单数据类型(基本数据类型)  string boolean number undefined null (symbol)
     简单数据存储于变量中, 存的是值本身, 赋值之后的修改, 互不影响

   - 引用数据类型(复杂数据类型)
     引用数据类型存储于变量中, 存的是地址, 赋值之后赋值的是地址, 地址相同, 修改就会互相影响


3. JS垃圾回收机制的理解?
   JS中内存的分配 和 回收, 都是自动完成的, 内存在不使用时, 会被垃圾回收机制自动回收

   function fn () {
     let num = 1
     let obj = {}
   }
   fn 函数调用时, 开辟内存空间, 调用完, 内存空间会自动被释放

   注意: 不是所有的空间都会被立刻回收, 回收机制有两种, 引用计数, 标记清除, 
   满足回收策略, 确定是垃圾了, 才会回收

   回收机制的两种: 回收机制会有策略, 判断内存是否是垃圾

   1. 引用计数 (IE) => 引用为 0, 就需要释放
      看一个对象是否有指向它的引用, 如果没有任何变量指向它, 说明对象已经不需要了, 该被释放
      对象空间的引用计数, 引用为 0, 就需要释放
      致命缺点: 引用计数存在 循环引用 的问题, 造成了内存泄露

   2. 标记清除 (主流浏览器, chrome 火狐 ...) => 无法到达的对象要回收
      回收策略: 将不再使用的对象 定义为 无法到达的对象, 无法到达的对象要回收
      1. 从根部(JS中全局对象)出发, 定时扫描内存中的对象
      2. 凡是从根部能到达的对象, 都是还要使用的, 如果无法从根部出发, 触及到, 就会被标记为不再使用!


4 . 作用域链的理解?
   作用域: 全局作用域 局部作用域 块级作用域 3种
   平时开发中, 作用域存在嵌套的情况, 变量访问的链式作用域结构, 就称之为是作用域链
   变量访问的原则: 就近 => 优先访问自己作用域的变量, 如果没有, 就往外部作用找


5. 闭包的理解
   闭包概念: 函数 和 声明该函数的 词法环境的组合
   简单点: 内层函数 引用 外层函数的变量, 就可以形成闭包
   理解: 内层函数被缓存时, 外部作用域所访问到的变量, 也会被缓存下来

   闭包应用(优点): 缓存数据 hooks, 实现数据私有
   闭包的缺点: 滥用闭包, 如果不及时释放, 会造成内存泄露(global vaiiables)
   应用场景: 
          点击事件,需要用到外部的局部变量,有了闭包在数据传递上更为灵活。

          匿名自执行函数
   ----------------------------------------------------------------------

   react中hooks闭包的问题
   const [count, setCount] = useState(0)

   useEffect(function() {
      setInterval(() => {
         console.log(count)  // 永远是0

         // 实时拿最新的值?参数传递
         setCount(count => {
            console.log(count)
            return count
         })

      }, 1000)

   }, []) // 只会在dom解析完成, componentDidMount 之后执行一次
   ----------------------------------------------------------------------

6. 原型 和 原型链
   为什么要有原型 => 如果每个实例都自己构造自己的方法, 太浪费内存, 其实方法是可以共享的
   原型意义: 给实例 "共享方法" , 节约内存的一个对象

   原型的三角关系图: 构造函数  原型   实例 
   原型链: 实例对象, 有着原型, 而原型有着原型的原型, 这样一层一层的链式原型结构, 就是原型链
   属性访问原则: 访问 实例.属性, 先访问自己, 如果自己没有, 往原型链上就近查找 ...

7. 继承
   分类: 三大类, 原型继承, 组合式继承, 寄生组合式继承
   (1) 原型继承: 原型替换, 继承方法
       Student 继承自 Person, Person 有的方法, Student 都应该有
       Student.prototype = new Person()

   (2) 组合式继承: 将构造函数借调 + 原型替换组合
       1 原型替换 => 继承方法   
         子构造函数.prototype = new 父构造函数()

       2 构造函数借调 => 继承实例属性
         父构造函数.call(this, xx, xx)

   (3) 寄生组合式继承: 将构造函数借调 + 原型替换组合 + Object.create 优化
       1 原型替换 => 继承方法   
         子构造函数.prototype = new 父构造函数()

       2 构造函数借调 => 继承实例属性
         父构造函数.call(this, xx, xx)
       
       3 对原型替换做了优化
         Object.create基于传入的对象, 创建一个新对象, 让新对象的__proto__ 直接指向传入的对象
         子构造函数.prototype = Object.create(Person.prototype)
       
       相比于, 原来的原型替换, 替换成父构造函数的实例 => 寄生式好处, 省了一个 new 的过程, 直接形成原型关系

   es6 新增的继承 => extends

8. 判断是否是数组
   Object.prototype.toString.call(数组)    '[object Array]'
   Array.isArray(数组)                     true

   Object.prototype.toString.call(数据)    '[object 类型]'


9. 数组去重
   let arr = [1, 3, 6, 6, 3, 1]
   let newArr = [...new Set(arr)]  // newArr 就是去重完的数组


10. this的理解
    this的5种情况
    1. 默认绑定  fn()            指向window  定时器 / 延时器
    2. 隐式绑定  obj.fn()        指向调用者
    3. new绑定   new Person()    指向实例  (new会让构造函数的this, 指向创建出来的实例)
    4. 显示绑定  call(接受多个参数) apply(接受一个包含多个数组) bind(返回函数) 想让他指向谁, 就指向谁
    5. 箭头函数  () => { ... }   上一层作用域的 this


11. async和await
    async 是一个加在函数前的修饰符,被async定义的函数会默认返回一个Promise对象
    因此对async函数可以直接then,返回值就是then方法传入的函数。
    await 也是一个修饰符,只能放在async定义的函数内。可以理解为等待
    await 修饰的如果是Promise对象:可以获取Promise中返回的参数(resolve或reject)
    取到值后语句才会往下执行


11. Promise
    Promise几个状态? 3个状态  pending等待   fulfilled成功   rejected失败

    new Promise((resolve, reject) => {
       // 一帮封装异步操作 xhr, fs ...
       // 成功的时候, 调用 resolve 将promise(同步执行)的状态修改成成功fulfilled  将来执行 then(异步执行)  
       // 失败的时候, 调用 reject  将promise的状态修改成失败rejected   将来执行 catch
    })

    状态凝固, 一旦promise的状态被修改了, 就会状态凝固, 不能再更新状态了
    resolve(100)  成功了
    reject(200)   再去失败  无意义的
    promise.all(成功失败都可调用)


12. 如何手写实现Promise.all 和 Promise.race?  
    Promise的四个静态方法: 

    1. Promise.resolve(200)  得到一个成功的promise对象
       new Promise((resolve, reject) => {
          resolve(200)
       })

    2. Promise.reject(500)      得到一个失败的promise对象
       new Promise((resolve, reject) => {
          reject(500)
       })

    3. Promise.all([p1, p2, p3]).then(values => { ... })
       等待原则: 会等待所有的promise都满足条件, 才算成功
         Promise.myall = function(arr) {
       let count = 0 // 计数器, 统计已经成功的个数
       let resultsArr = [] // 数组, 用于统计所有的成功的结果

       return new Promise((resolve, reject) => {
          // 等待所有的promise都满足条件
          // 遍历 arr
          arr.forEach((item, index) => {
             item.then(res => {
                resultsArr[index] = res
                count++ // 每成功一个, 就让count++
                // 每成功完一个, 也要判断一下, 是否达到了目标个数
                if (count === arr.length) {
                   resolve(resultsArr)
                }
             }).catch(err => {
                reject(err)
             })
          })
       })
         }

    4. Promise.race([p1, p2, p3]).then(value => { ... })
       竞速原则: 所有的promise中, 第一个满足条件的, 就会触发 race 的 .then
       Promise.myrace = function(arr) {
         // 遍历arr 去.then 只要第一个 .then 了, 直接整个promise就成功
         return new Promise((resolve, reject) => {
            arr.forEach(item => {
               item.then(res => {
                  resolve(res)
               })
            })
         })
       }


13. 深拷贝 浅拷贝
       浅拷贝: 只拷贝一层
      1. for in
      2. ... 展开运算符

   深拷贝: 拷贝多层, 递归拷贝
     1. 递归
     2. JSON.stringify / JSON.parse

   深拷贝如果遇到循环引用怎么办? (了解)
     核心思路: 拷贝时, 准备一个数组, 存储拷贝了的所有的复杂类型的引用地址,
     每一次进行递归拷贝之时, 遍历数组, 判断这个对象, 是否曾经拷贝过, 如果拷贝过, 直接用

     这样创建出来的新的对象, 也是一个存在着循环引用关系的新对象

--------------------------- HTTP 协议 Start ------------------------------

14. HTTP有哪些⽅法?
    get获取  post新增  put修改  patch修改  delete删除


15. HTTP请求报文和响应报文
    请求报文: 请求行  请求头  请求体
    响应报文: 响应行  响应头  响应体


16. http状态码? 
    200 成功         
    201 新建      

    301/302 重定向   
    304 协商缓存

    400 接口传参错误
    401 权限认证未通过
    403 请求资源拒绝
    404 找不到

    5xx 服务器错误


17. https? https是如何保证安全?
    https 相比于 http 更安全
    https 在数据传输时, 进行数据的加密处理, 所以更安全

    如何保证安全?
    (1) https 加密方案: 非对称加密 + 对称加密
        结合了两种加密方式, 实际传输信息过程, 以对称加密为主
        如何保证约定的密钥的传输的安全性? => 利用非对称加密传输密钥
        1. 先使用非对称加密, 传输对称加密的密钥   =>  密钥的传输安全性
        2. 再经过对称加密的密钥, 来进行信息的传输 =>  传输的效率高

    (2) 数字证书: 加密签发公钥      (类似于, 你要往码云上ssh提交信息, 得把公钥给到码云)
        访问一个网站时, 你要和网站交流, 传输对称加密的密钥, 就需要网站将公钥给你
        客户端 和 服务器, 初步互通消息, 客户端发送请求, 可以拿到数字证书

        数字证书: 一般由权威机构颁发, 包含网站基本信息, 到期时间, 公钥, ...
        当用户端, 拿到数字证书, 也就是拿到的公钥, 就可以基于公钥 和 服务端, 传输对称加密的密钥, 进行数据通信

    (3) 数字签名: 防证书篡改
         数字证书
            网站: xxx.xxx.xxx
            公钥: xxxu12u3ufyy213
            到期时间: 2050年10月02日
            签发机构: CA机构

            签名摘要: 上面证书信息的hash加密结果 12ueifusy2y4yuysu123
    ----------------------------------------------------------------

    有哪些加密方案? 不同的加密方案有什么区别?

    1. 对称加密: 相同密钥加密解密, 可逆
       优点: 计算量小, 加密速度快, 加密效率高
       缺点: 如果一开始密钥就泄露了, 没有任何安全性可言了
       我(密钥)          =>         他(密钥)
       我(密钥)          <=         他(密钥)

    2. 非对称加密: 有两把钥匙, 公钥, 私钥, 可逆
       优点: 非对称加密 相比于 对称加密, 更安全
       缺点: 加密 和 解密花费的时间会更长, 速度会慢, 只适合对少量的数据来加密

       ssh 需要本地生成两把钥匙 - 互相配对的两把钥匙: 公钥, 私钥 => 公钥给到 码云
       我电脑(私钥)       =>         码云(公钥)
       我电脑(私钥)       <=         码云(公钥)

    3. Hash算法加密: 不可逆的, 根据一段内容, 生成一段唯一标识, 一般用于验证数据是否被篡改
       abc               =>          idsufyu3hrhfhewf

       后台数据库, 对于密码的存储, 一般情况会用 Hash 算法
       比较老的hash算法, 容易被撞库, 造成泄露  => md5  / sha256


18. http2.x 优势
    1. http2.x 二进制传输数据,  http1.x文本传输, 二进制解析更高效
    2. http2.x 头部压缩技术, 减少请求和响应头中重复携带的数据, 降低网络负担  (老样子)
    3. http2.x 服务器推送方式, 可以主动向客户端推送资源, 提高页面加载效率
    4. http2.x 多路复用机制, 少创建一些连接, 降低资源占用 和 资源损耗
       单个连接可以承载任意数量的双向数据流 


19. 缓存控制
    大类: 数据库缓存  服务端缓存  浏览器缓存
    浏览器端缓存:  HTTP 缓存  / 本地存储 (localStorage sessionStorage cookie websql indexedDB) - 前端

    HTTP缓存: 优化页面加载效率, 如果没有缓存, 所有的信息资源都要重新加载, 会非常慢

    (1)强缓存: 从服务器获取得到一个资源, 图片资源会在响应时标记有效期 (过期时间), 
       将来使用资源前, 会判断资源是否在有效期内, 如果在有效期内, 直接读缓存即可  => 强缓存 cache
       关键标识符:
         - Expires: 指定一个具体的日期  Mon, 19 Apr 2032 16:00:00 GMT (服务器给你的) 到了这个时间, 缓存过期
         - Cache-Control: 指定一个过期时间(相对时间) 315360000s 十年, 从你拿到这个资源开始计时, 十年后, 过期
         以Cache-Control为主, Expires辅助

    (2)协商缓存: 若未命中强缓存(强缓存过期了), 则浏览器就需要发请求到服务器
       状态: 本地有 xx.png 的图片资源缓存, 但是过期了, 肯定要问服务发请求, 要资源 (携带缓存的图片资源信息)
       服务器: 接收到请求, 判断该图片, 是否更新过, 如果没有更新过, 还是原来的图片, 此时不需要响应图片资源
       客户端: 经过了服务器端确认, 直接从缓存中读取, 并且更新新的过期时间

       协商缓存: 哥们, 我手里有一张过期的图片, 这个图片还能不能用了? 服务器瞅了眼, 没啥变化, 还能用十年
       好的, 那我直接从缓存中读, 更新过期时间十年, 十年后我再来问你


       协商缓存的两个状态码:
         304: 协商缓存成功, 协商成功, 询问过期图片是否有变化, 无变化, 直接从缓存中读, 更新新的过期时间
              服务器不会响应图片资源, 会响应新的过期时间
    
         200: 协商缓存失败, 服务器立刻将更新后的图片, 响应给客户端
              服务器会响应图片资源, 新的图片资源也会有过期时间
    
       协商缓存, 如何判断图片是否有变化?  (1: 看最后修改时间, 有没有变化   2: 看校验码有没有变化)
       关键表示符:
         - Last-Modify: 后台返回的该资源最新的更新时间, 精确到秒  Tue, 19 Apr 2022 06:18:56 GMT
           协商请求时, 拿着Last-Modify, 在请求时的If-Modify-Since字段中传给后台, 后台会判断修改时间是否相同, 
           如果相同, 协商成功! 304 直接从缓存中读
           缺点: 修改时间的记录, 最高精确到秒, 如果同一秒修改了多次, 就有可能缓存错文件了
    
         - Etag: 后台返回的该资源的校验码, 保证每一个资源是唯一的,资源变化都会导致ETag变化 (内容变化了)
           协商请求时, 拿着Etag请求服务器, 在请求时的If-None-Match字段中传递Etag, 后台判断校验码是否相同,
           如果相同, 协商成功! 304 直接从缓存中读


    强缓存 和 协商缓存一般一起配合使用, 用于缓存资源 (图片 / html / css / js)

--------------------------- HTTP 协议 End ------------------------------

--------------------------- TCP 协议 Start ------------------------------

20. 一次完整的HTTP服务过程是什么? 在地址栏, 输入 www.baidu.com 具体发生了什么?
  21. 对www.baidu.com进行网址DNS域名解析, 将域名解析得到ip的过程 (计算机只认IP)
      DNS解析: 优先找本机hosts, 如果本机hosts没有配置, 会去公网找DNS解析服务器, 解析到IP
      www.baidu.com  => 112.80.248.75

  22. 根据这个ip找到对应的服务器, 想要建立TCP连接, 发起TCP的三次握手
      ------------------- S 开始服务 ----------------
  23. 建立成功TCP连接后, 进行 HTTP 请求

  24. 服务器响应 HTTP 请求, 浏览器就会得到 html 代码

  25. 浏览器解析 html, 根据link script img/src 加载更多的资源 (js/css/图片)

  26. 浏览器将完整的页面渲染给用户
      ------------------- E 结束服务 ----------------
  27. 服务完成, 关闭TCP连接, TCP四次挥手


21. 三次握手? 四次挥手?
    三次握手 还有 四次挥手, 都能够体现, TCP连接建立断开的谨慎性, 节约服务器资源

    (1) 要建立TCP连接?  => 三次握手  本质是确认通信双方收发数据的能力
        理解: 打电话双方需要, 确定收发消息的能力, 才会进行信息交谈 => 0707能否收到? 0707收到, 请讲? 

        1. 第一次握手, 客户端往服务器发消息    hello, 能听到么?
           本质上服务器确定: 服务器能收消息, 客户端能发消息0
          
        2. 第二次握手, 服务器给客户端回消息    好的,好的, 能听到! 你能听到我说话么?
           本质上客户端确定: 客户端能发消息, 服务器能收消息,  服务器能发消息, 客户端能收消息
          
        3. 第三次握手, 客户端要给服务器回消息  好的, 好的, 我也能听到你!
           本质上服务器确定: 客户端能发消息, 服务器能收消息,  服务器能发消息, 客户端能收消息
          
        经过了三次握手, 确实实现了, 收发能力的确认, 进而进行消息的传递


    (2) 要断开TCP连接?  => 四次挥手  本质不能轻易直接断开连接, 保证数据传输的安全性
        服务已经完成, 我想要断开连接

        1. 第一次挥手, 客户端主动发起, 断开连接       领导, 你刚和我说的说完了么, 说完了, 我要挂了?
        
        2. 第二次挥手, 服务器回应, 我知道了, 你给我等会, 我检查检查有没有什么没传输完的
            
           a few moments ago
        
        3. 第三次挥手, 服务器回应, 兄弟, 确实没有啥要补充的了, 可以断开连接了
        
        4. 第四次挥手, 客户端回应, 好嘞哥, 那我真断开了

--------------------------- TCP 协议 End ------------------------------

22. 浏览器是如何解析CSS选择器的?
       div span a   我们的想法, 先找到所有的 div, 遍历, 判断后代有没有 span, 记录下来, 再判断, 后代有没有 a, 如果有就命中
       div span a   实际浏览器解析, 先找到所有的 a, 遍历, 看这个 a 没有 span 的祖辈, 看 span 有没有 div 的祖辈, 如果有就命中

   实际浏览器解析css选择器: 右侧往左解析 (目的: 效率更高)

      1. 从左往右?  遍历子孙后代
         20000个元素 => 找所有的div  找20000次   找第一层没有任何区别
         某个div下, 有8000个元素 => 找所有的span  找8000次    第二层某个一个元素
         需要遍历子孙后代, 树形结构

      2. 从右往左?  需要遍历祖辈
         20000个元素 => 找所有的a    找20000次   找第一层没有任何区别
         某个a, 是否有一个祖辈, 是span, 祖辈40个 => 尝试找所有的祖辈  找40次     第二层某个一个元素  量级差了很多
         需要遍历祖辈, 线性结构, 一共就那么多祖辈


23.  浏览器底层渲染流程
   24.  解析 HTML, 生成 dom树
   25.  解析 CSS, 生成 样式规则
   26.  将 dom 树 和 样式规则结合, 生成渲染树
   27.  基于渲染树, 进行布局渲染 => Layout (重排/回流)   搭架子
   28.  基于渲染树, 进行绘制    => Paint (重绘)         上色


24. 重绘重排
   25. 结构层面, 布局层面变化 => 触发重排, 重新Layout => Layout之后必然需要重新Paint
   26. 样式层面, 颜色等等变化 => 触发重绘, 重新Paint即可
       重排必将触发重绘, 重绘不一定重排 

   实际工作: 尽可能避免 批量的 重排, 容易影响性能
   优化策略:

   - 1 集中修改样式 (这样可以尽可能利用浏览器的优化机制, 一次重排重绘就完成渲染)
   - 2 尽量避免在遍历循环中, 进行元素 off set Top 等样式值的获取操作, 会强制浏览器刷新队列, 进行渲染
   - 3 利用 transform 实现动画变化效果, 去代替 left top 的变换 (轮播图等)
     transform变换, 只是视觉效果! 不会影响到其他盒子,  只触发了自己的重绘
   - 4 使用文档碎片(DocumentFragment)可以用于批量处理, 创建元素


25. 工作场景 git 的应用
       实际工作git => gitlab => 支持自己的服务器搭建 (自建的git服务器, 不依赖于第三方, 更稳定更安全)
       常见第三方git服务器: github 码云
       但是操作流程gitlab 和 github/码云完全一致 (包括配置ssh) 本地生成公钥和私钥, 把公钥给到gitlab

   以码云为例, 演示基本操作:

   1. 不常见, 远程没有仓库, 需要自建仓库, 建一个空仓库
      本地:
        git init
        git add .
        git commit -m '初次提交'

        git remote add origin 远程仓库地址
        git push origin master

        新建

   2. 常见, 远程已有仓库, 需要拉取代码, 你老大会给你一个地址, 让你去拉代码 (拉代码之前必须先配ssh)
      不配ssh, 没有权限拉代码!!! 且这个仓库会有多个分支 master主分支  dev开发  test测试
      详见工作流程.txt


----------------------------------- Vue相关 Start -------------------------------

26. 什么是MVVM ?
    是一种设计模式, Model(数据层)  View(视图层)  ViewModel(视图数据控制层)

    数据变化 => ViewModel监听到 => 更新视图
    视图变化 => ViewModel监听到 => 更新数据

    双向数据绑定


27. 双向数据绑定的原理
    数据变化的监听? Vue2 Object.defineProperty  Vue3 proxy
    视图变化的监听? 给元素注册事件监听 @change @input ...

    问: Object.defineProperty 和 proxy的差异?
    Object.defineProperty:
      (1) Object.defineProperty 是对于对象属性的劫持, 需要一个个递归劫持, 效率低
      (2) Object.defineProperty 对于数组的更新检测, 比较麻烦
    prxoy:
      proxy对于整个对象的劫持, 对象内部的任何属性的修改, 都会先经过外层proxy, 无需递归, 效率高
      proxy也可以直接监听数组的变化


28. Vue的响应式系统 => 观察者模式   监听到, 更新谁? 如何运作的?
    Vue采用的是观察者模式, 以Vue2为例, 通过 Object.defineProperty 完成对于数据的劫持
    通过观察者模式, 完成对于节点的数据更新

    观察者模式: 是一种 一对多的 设计模式, 一个对象变化了, 其他所有依赖的, 跟着变
    一上来 vue 解析渲染, 会进行依赖收集, 会将渲染watcher, 计算属性watcher, 侦听器watcher, 
    都会收集到对应 dep 中 (依赖收集完成)

    当 Object.defineProperty 监听到数据变化, 就会根据依赖关系, 派发更新, 每个watcher侦听器
    都是一个对象, 都有着callback回调, 执行回调 (调用函数, 派发更新)

    https://vue-js.com/learn-vue/start/#_1-%E5%89%8D%E8%A8%80

    vue2 响应式的缺点是:
    无法监听到对象属性的动态添加和删除 无法监听到数组下班和
    length长度的变化 我们可以用以下方法解决: this.$set  this.$delete


29. Vue生命周期

    1. 开始构建
    2. 初始化数据
    3. 编译模板
    4. 挂载dom
    5. 渲染 / 更新
    6. 卸载

    全了, 11个钩子 (8个基本 + 2个配合keep-alive + 1不常用的)
    8大钩子 Vue2                                             Vue3
    beforeCreate 创建前  数据不可用                     setup
    created    创建后  数据可请求                           setup
    beforeMount  挂载前 方法没解析                     onBeforeMount
    mounted  已挂载dom  可解析                                             onMounted
    beforeUpdate 更新前 数据更新 页面没有              onBeforeUpdate
    updated  更新  页面发生变化才触发                                               onUpdated
    beforeDestroy  销毁前                                     onBeforeUnmount
    destroyed  被销毁                                            onUnmounted


30. Vue组件通信 7种
  31. 父传子 子传父
      (1) 通过 props 父传子, 给组件添加属性传值
          - 
          - 组件内部props接收
      (2) 子传父, 利用 $emit 和 @ 注册事件, 实现数据传递
          - 子组件: this.$emit('事件名', 参数)
          - 父组件: @事件名="处理函数"


  2. 事件总线  (Vue3被移除)
     通过EventBus进行信息的发布和订阅更新
     (1) 创建事件总线
         const eventBus = new Vue()
         export default eventBus
     (2) 在A组件中, 监听, 订阅变化
         eventBus.$on('事件名', function() {
            ...
         })
     (3) 在B组件中, 触发 bus 的事件
         eventBus.$emit('事件名', 参数)

  3. provide inject  (Vue3被增强)
     用于父组件向子孙后代组件共享传递数据
     Vue2, 其实也有 provide 和 inject, 但是不太好用
     Vue3, 大大的增强了 provide 和 inject, 广泛应用

     Vue3:
     (1) provide('共享的键名', '值')
         provide('共享的方法名', function() {
            ...
         })
     (2) let result = inject('共享的键名' , 默认值)
         let fn = inject('共享的方法名') // 子孙后代, 调用祖辈的方法, 传参更新数据


  4. $children $parent $refs
     $children: 用于获取模板中的子组件集合    $children[0] 第一个组件
     $parent:   用于获取父组件
     $refs: 用于配合ref, 获取具体哪一个组件

  5. $attrs  $listeners
     组件二次封装用到 => 需要进行 批量 的跨层级数据传递  (使用起来, 就像父传子, 子传父一样)
     v-bind="$attrs"   批量获取props, 批量往下传递
     v-on="$listeners" 批量监听事件, 批量向上触发

  6. Vuex
     5个核心概念:
      (1) state: 存放数据
      (2) mutations: 存放操作数据的方法
      (3) actions: 存放一些异步操作, 注意: actions 不可以直接操作state, 需要提交mutation
      (4) getters: 计算属性
      (5) modules: 分模块

     同步情况:  页面中    commit('模块名/某mutation', '参数')

     异步情况:  页面中    dispatch('模块名/某action', '参数')
               action中  commit('模块名/某mutation', '参数')

  7. pinia (Vue3)  官方推荐的状态库   将来Vuex5的语法, 就会和pinia的语法类似
     核心概念:
     (1) state: 存数据        data
         state () {
            return {
               count: 100,
               money: 1000
            }
         }
     (2) actions: 存放方法, 既可以同步, 也可以异步, 且可以直接操作状态   methods
         actions: {
            addCount () {
               this.count++
            },
            addCountAsync () {
               setTimeout(() => {
                  this.count++
               }, 1000)
            },
            async getMoney () {
               const res = await xxApi()
               this.money = res.data.money
            }
         }

     (3) getters: 计算属性
      --------------------------------------------------------------

      完整 pinia仓 库写法: store/count.js 一个文件, 就是一个仓库
         export const useCounterStore = defineStore('counter', {
            state() {
               return { count: 0 }
            },
            actions: {
               add() {
                  this.count++
               },
            },
         })

     页面中, 无论同步还是异步, 直接导入对应的仓库, 直接调用方法即可

      1. 导入仓库
         import { useCounterStore } from '@/store/count.js'
         const counter = useCounterStore()

      2. 直接访问使用仓库的数据, 获取调用仓库的方法

         
{{ counter.count }}
31. Vue中key的作用? / React中key的作用? 作用: 给虚拟dom添加标识, 优化复用对比策略, 优化渲染性能 考察点: 32. Vue/React的更新机制: 差异化更新, 对比新旧虚拟dom, 找出新旧dom不同的部分, 进行更新视图 33. 为什么对比虚拟dom? 不对比真实dom? 真实dom的结构, 太复杂, 有着很多无用的属性, 无需对比, 如果对比了浪费性能 虚拟dom, 是一个js对象, 通过js对象, 描述真实的dom, 只记录关键属性, 对比起来性能更高 34. 就算是进行了虚拟dom的优化, 但是dom树, 是树形结构! 虚拟dom树, 也是树形结构, 树形结构的对比? => diff算法 diff算法策略: (1) tree diff 同层比较, 同层对比, 如果发现根级别的元素类型不同, 直接整棵树销毁重建 (2) component diff 同层级别元素类型相同, 按照策略, 对比差异, 记录差异 (3) element diff 子节点列表, 同层兄弟节点, 默认按照下标进行对比, 如果加上key, 就相当于给虚拟dom加上了标识 对比策略: 就是对相同key的元素, 进行比较 子节点列表, 顺序下标, 会发生变化时, 推荐都加上key, 便于正确复用! (列表 v-for 往列表前面加, 中间加) 32. Vue 跳转路由时的传参方式 (Vue2 / Vue3) ---- 必答 ---- 3种 => query传参, params传参, 动态路由传参 1. query传参: 会在地址栏显示, 刷新不会丢失 router.push('/login?username=zs&age=18') router.push({ path: '/login', query: { username: 'zs' age: 18 } }) 获取: route.query.username 33. params传参: 内存中传递, 不显示地址栏, 刷新会丢失, 必须配合命名路由使用 如果要处理刷新会丢失的问题, 需要配合 localStorage { path: '/login', name: 'login', component: () => import('@/views/login/index.vue') } router.push({ name: 'login', params: { car: '小车车' } }) 获取: route.params.car 34. 动态路由传参: 地址栏传递, 显示在地址栏, 刷新不会丢失, 必须配动态路由 { path: '/user/:id', component: () => import('@/views/user/index.vue') } router.push({ path: '/user/92377447', }) 获取: route.params.id 33. Vue权限控制 RBAC: role based access control 基于角色的权限控制 给用户分配角色, 给角色分配权限 核心点: 1. 按钮权限控制 / 表单 / 表格 登录成功时, 获取用户的权限相关信息, 角色roles: [{ xx }, { xx }] 按钮规则btnRules: [{ xx }, { xx }] 根据返回的权限信息, 调用封装好的函数, 判断按钮, 是否需要禁用启用 / 显示隐藏 2. 菜单权限控制 (1) 登录成功时, 获取用户的权限相关信息, menus: ['xx', 'xx', 'xx'] [ { "moduleGroupId": 1001, "moduleGroupName": "部署管理", "requestMapping": "deploy-manage", }, { "moduleGroupId": 1100, "moduleGroupName": "系统管理", "requestMapping": "sys-manage", "moduleList": [ { "moduleId": 1101, "moduleName": "系统日志", "requestMapping": "system-log", "moduleGroupId": 1100, }, { "moduleId": 1102, "moduleName": "系统告警", "requestMapping": "sys-alert", "moduleGroupId": 1100, }, ], } ] (2) 将路由拆分成, 动态路由和静态路由, 动态路由需要权限才能访问的路由, 静态路由默认即可访问的路由 (3) 用户一登录成功, 会动态的新增一些动态路由 addRoutes, 此时不同用户的路由已经不一样了 (4) 但是为了菜单的响应式展示, 动态使用 vuex 维护路由数组 3. api接口url权限控制: 后端控制多一点 34. 首屏渲染优化 网站性能优化实战——从12.67s到1.06s的故事 (首屏优化) 量化数据, 更有说服力 https://juejin.cn/post/6844903613790175240 移动spa商城优化记(一)---首屏优化篇 (首屏优化) https://juejin.cn/post/6844903577815613453 前端黑科技:美团网页首帧优化实践 (白屏问题优化) 35. git工作流程 编码环节: 36. 刚进公司, 拉取公司已有代码 37. 基于 dev 开自己的分支, shuaipeng 38. 分支上写代码, 将该分支, 发布往远程 39. 写完了, 老大, 切到你的分支, 看代码有无问题, 看改了什么, 没啥问题, 合并到dev, 提交发布 测试环节: 将代码从dev分支, 合并到test分支, 让测试去测 如果测试无问题 上线环节: 将代码从test分支, 合并到master, 上线 编码环节: 1. 先配ssh => 本地生成公钥私钥, 将公钥给到git服务器 (码云/gitlab/github) (1)命令行执行: ssh-keygen -t ed25519 -C "[email protected]" (2)三次回车, 生成公私钥 (3)找密钥目录: c/Users/用户名/.ssh/ => .pub 结尾的公钥 (4)将公钥用记事本打开, CV 到 git服务器 ssh 配置中即可 2. git clone 远程仓库地址 克隆远程仓库代码 (注意: 必须先配ssh, 否则没权限) 微信: [email protected]:jepsonpp/test_git_demo.git 去拉代码吧 我: 好的! (1) git clone [email protected]:jepsonpp/test_git_demo.git (2) 如果有提示, 就 yes 即可, 首次连接 3. 切换到 dev 分支, 基于 dev 开新的分支 (1) 左下角, 分支点开, 选择 origin/dev, 就会切换到 dev 分支 (2) 左下角, 确定是dev, 点开, 创建新分支, 输入分支名 shuaipeng 回车 4. 在你的分支下, 写功能, 写代码, 在你的分支下提交 (1) 确定左下角是shuaipeng, 再写代码 (2) 点开左边源代码管理器, 提交代码 (3) 点击发布按钮, 发布远程 => git push origin shuaipeng 5. 写完了, 老大, 切到分支, 检查代码, 最终合并一下代码, 往dev合并 (1) 左下角点开, 点击 origin/shuaipeng, 本地拉取最新的 shuaipeng (2) 确认代码, 无误后, 切换到 dev 分支 (3) 合并 git merge 分支名 (4) 推送 dev 的更新 测试环节: 将代码从dev分支, 合并到test分支, 让测试去测 (1) 切换到 test (2) 将dev的代码合并过来 (3) 推送 test 的更新 上线环节: 将代码从test分支, 合并到master, 上线 (1) 切换到 master (2) 将test的代码合并过来 (3) 推送 master 的更新 36. nextTick和$nextTick区别 1.nextTick(callback):当数据发生变化,更新后执行回调,在下次 DOM 更新 循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。 2.$nextTick(callback):(异步执行)当dom发生变化,更新后执行的回调。将回调延迟到 下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。 37.单页面应用程序的特点: 单页面应用程序将所有的功能局限于一个web 页面中,仅在该web 页面初始化时加载相应的资源(HTML、 JavaScript 和CSS)。 一旦页面加载完成了,SPA 不会因为用户的操作而进行页面的重新加载或跳转。而是利用JavaScript 动态地变换 HTML 的内容,从而实现页面与用户的交互。 1-1.单页面的优点: 1.良好的交互体验: 单页应用的内容的改变不需要重新加载整个页面获取数据也是通过Ajax 异步获取没有页面之间的跳转,不会出现“白屏现象” 2.良好的前后端工作分离模式 后端专注于提供API 接口,更易实现API 接口的复用前端专注于页面的渲染,更利于前端工程化的发展 3.减轻服务器的压力 服务器只提供数据,不负责页面的合成与逻辑的处理,吞吐能力会提高几倍 但是任何一种技术都有自己的局限性 1-2.单页面的缺点: 1.首屏加载慢,路由懒加载代码压缩,CDN 加速网络, 传输压缩 2.不利于SEO,SSR 服务器端渲染 38.flex布局: justify-content:center垂直居中 align-items:center水平居中 justify-content:flex-end尾部开始 justify-content:flex-Start头部开始 justify-content:space-around平分甚于空间 flex-wrap:wrap换行 flex-grow:1平均比例(子盒子) flex-shrink属性指定了flex元素的收缩规则(子盒子) flex-basis属性指定了flex元素在主轴方向上的初始大小(子盒子)

个人总结  仅供参考

你可能感兴趣的:(前端,面试,javascript)