前端知识总结1

对浏览器内核的理解?

  1. 由2部分组成,分别是渲染引擎和js引擎
  2. 渲染引擎:负责取得网页内容整理讯息,以及计算网页显示方式
  3. js引擎:解析和执行js来实现网页动态效果

h5新特性

  1. 新增了canvas video radio
  2. 本地离线存储 localstorage
  3. sessionStorage浏览器关闭后自动删除
  4. 新增一些语义化的标签article、footer、header、nav、section
  5. 表单控件calendar date time email url search
  6. 新的技术webworker websocket Geolocation
  7. IE8/IE7/IE6支持通过document.createElement方法产生的标签
  8. 可以利用这一特性让这些浏览器支持HTML5新标签
  9. 浏览器支持新标签后,还需要添加标签默认的样式

webpack

打包效率:

  • 开发环境采用增量构建,启用热更新
  • 开发环境不做无意义的工作例如提取css计算文件hash等
  • 配置devtool
  • 选择合适的loader
  • 个别loader开启cache如babel-loader
  • 第三方库采用引入方式
  • 提取公共代码
  • 优化构建时搜索路径 指明需要构建目录及不需要构建目录
  • 模块引入需要的部分

loader:

  • loader就是一个node模块,它输出一个函数。当某种资源需要这个loader转换时
    这个函数会被调用。并且,这个函数可以通过提供给他的this上下文访问Loader API

webpack的一些plugin(插件),怎么使用webpack对项目进行优化

  1. 构建优化
  • 减少编译体积
    ContextReplacementPugin,
    IgnorePlugin
    Babel-plugin-import
    babel-plugin-transform-runtime

  • 并行编译
    happypack,
    thread-loader,
    uglifyjsWebpackPlugin
    开启并行

  • 缓存
    cache-loader,
    hard-source-webpack-plugin,
    uglifyjsWebpackPlugin开启缓存,
    babel-loader开启缓存

  • 预编译
    dllWebpackPlugin && DllReferencePlugin
    auto-dll-webpack-plugin

  1. 性能优化
  • 减少编译体积
    Tree-shaking,
    Scope Hositing

  • hash缓存
    webpack-md5-plugin

  • 拆包
    splitChunksPlugin
    import()
    require.ensure


webpack优化项目

  • 对于webpack4.0,打包项目使用production模式,这样会自动开启代码压缩
  • 使用ES6模块开启tree shaking,这个技术可以移除没有使用的代码
  • 优化图片,对于小图可以使用base64的方式写入文件中
  • 按照路由拆分代码,实现按需加载

优化打包速度

  • 减少文件搜索范围
    • 比如通过别名(abc as rua)
    • loader的test,include&exclude
  • webpack默认压缩并行
  • happypack并发调用
  • babel也可以缓存编译

Babel原理

  • 本质上就是编译器:当代码转为字符串AST,对AST进行转变最后在生成新的代码
  • 分为三步:此法分析生成token,语法分析生成AST,遍历AST,根据插件变换相应的节点,最后吧AST转换为代码

如何实现一个插件(封装类,以及封装类的调用)

  • 通过插件apply函数传入compiler对象
  • 通过compiler对象监听事件
    apply (compiler) {
      const afterEmit = (compilation, cb) => {
        cb()
        setTimeout(function () {
          process.exit(0)
        }, 1000)
      }
    
      compiler.plugin('after-emit', afterEmit)
    }
    }
    
    module.exports = BuildEndPlugin
    

cdn

  • 内容分发网络,基本思路是尽可能避开互联网上有可能影响数据传输速度的稳定性的瓶颈和环节,是内容传输的更快,更稳定

内存泄漏

  • 定义:程序中已动态分配的堆内存由于某种原因未释放或者无法释放引发的各种问题

  • 结果:变慢,崩溃,延迟大等

  • 原因:

    • 全局变量过渡使用
    • dom情况时,还存在引用
    • ie中使用闭包
    • 定时器未清除
    • 子元素存在引起的内存泄漏
  • 避免策略

    • 减少不必要的全局变量,或者什么周期较长的对下那个,及时对无用的数据进行垃圾回收
    • 注意程序逻辑,避免死循环之类的
    • 避免创建过多对象原则:不用了的东西 要及时归还
    • 减少层级过多的引用

    前后端路由差别

    • 后端每次路由请求都是重新访问服务器
    • 前端路由实际上只是js根据url来操作dom元素,根据每个页面需要的去服务端请求数据,返回数据后和模板进行组合

bind,call,apply的区别

  • call和apply都是为了解决改变this的指向,作用都是相同的,传参方式有所差别call为字符串参数apply为数组参
  • 第一个参数为this所绑定的对象

简单说下原型链

  • 每个函数都有
    prototype
    属性,除了
    FUnction.prototype.bind()
    ,该属性指向原型

  • 每个对象都有_proto_
    属性,指向创建该对象的构造函数的原型,其实这个属性指向了
    [[prototype]]
    但是
    [[prototype]]
    是内部属性,我们并不能访问到,所以使用
    _proto_
    来访问

  • 对象可以通过
    _proto_
    来寻找不属于该对象的属性,
    _proto_
    将对象连接起来组成了原型链


数组降维

  • 普通数组降维
[1, [2], 3].flatMap(v => v)
// -> [1, 2, 3]
  • 如果想将一个多维数组彻底的降维,实现方法
const flattenDeep = (arr) => Array.isArray(arr)
  ? arr.reduce( (a, b) => [...a, ...flattenDeep(b)] , [])
  : [arr]

flattenDeep([1, [[2], [3, [4]], 5]])

事件代理

  • 如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该注册在父节点上
<ul id="ul">
	<li>1</li>
    <li>2</li>
	<li>3</li>
	<li>4</li>
	<li>5</li>
</ul>
<script>
	let ul = document.querySelector('#ul')
	ul.addEventListener('click', (event) => {
		console.log(event.target);
	})
</script>
  • 事件代理的方式相对于直接给目标注册事件来说,有2个优点
    • 节省内存
    • 不需要给子节点注销事件

性能优化

浏览器缓存

  • 浏览器缓存对于前端性能优化来说是个很重要的点,良好的缓存策略可以降低资源的重复加载提高网页整体的加载速度

主要分为2种:强缓存和协商缓存

  • 强缓存:实现强缓存可以通过2中响应头实现,
    Expires

    Cache-Control
    强缓存表示在缓存旗舰不需要请求,state code为200

    Expires: Wed, 22 Oct 2018 08:41:00 GMT
    

    Expires是HTTP/1.0的产物,表示资源会在Expires: Wed, 22 Oct 2018 08:41:00 GMT后过期,需要再次请求,并且Expires受限于本地时间,如果修改了本地时间,可能造成缓存失效。

    Cache-control: max-age=30
    

    Cache-Control出现于HTTP/1.1,优先级高于Expires。该属性表示资源会在30s后过期,需要再次请求。

  • 协商缓存:

    • 如果缓存过期了,我们就可以使用协商缓存来解决问题。协商缓存需要请求,如果缓存有效返回304.
    • 协商缓存需要客户端和服务端共同实现,和强缓存一样,也有2种实现方式
    Last-Modified和If-Modified-Since
    • Last-Modified表示本文件最后修改日期,If-Modified-Since会将Last-Modefied的值发给服务器,询问服务器在改日期后资源十分有更新,有更新的话会将新的资源发送回来。
    • 但是如果在本地打开缓存文件,就会造成Last-Modified被修改,所以在HTTP/1.1出现了ETag
    ETag和If-None-Match的区别
    • ETag类似于文件指纹,If-None-Match会将ETag发送给服务器,询问该资源ETag十分变动,如果
    选择合适的缓存策略
    • 对于大部分的场景都可以使用强缓存配合协商缓存解决,但是在一些特殊的地方可能需要选择特殊的缓存策略
      • 对于某些不需要缓存的资源,可以使用Cache-control:no-store,表示该资源不需要被缓存
      • 对于频繁变动的资源,可以使用Cache-Control:no-cache并配合ETag使用,表示该资源已经被缓存,但是每次都会发送请求询问资源是否更新。
      • 对于代码文件来说,通常使用Cache-Control:max-age=31536000并配合策略缓存使用,然后对文件进行指纹处理,一旦文件名变动就会立刻下载新的文件

浏览器性能问题

  • 重绘和回流

    • 重绘和回流是渲染步骤中的一小节,但是这2个步骤对于性能影响很大
    • 重绘是当节点需要更改外观而不会影响布局的,比如改变color就称为重绘
    • 回流是布局或者几何属性需要改变就称为回流。
    • 回流必定会发生重绘,重绘不一定发生会回流,回流苏so需要的成本比重绘高得多,改变深层次的节点很可能导致父节点的一系列回流。
  • 可能导致性能问题的几个动作

    • 改变widnow的大小
    • 改变字体
    • 添加或者删除样式
    • 文字改变
    • 定位或者浮动
    • 盒模型
  • 重绘和回流和Event loop的关系

    • 当Event loop执行完Microtasks后,会判断document是否需要更新。因为浏览器是60hz刷新率,每16ms才会更新一次
    • 然后判断是否有resize或者scroll,有的话会去触发事件,所以resize和scroll事件也是至少16ms才会触发一次,并且自带节流功能
    • 判断是否触发了media query
    • 更新动画并且发送事件
    • 判断是否有全屏操作事件
    • 执行requestAnimationFrame回调
    • 执行IntersectionObserver回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不太好
    • 更新界面
    • 以上是一帧中可能会做的事情,如果在一帧中有空闲时间,就回去执行requestIdleCallback回调
  • 减少重绘和回流

    • 用translate代替top
    • 使用visibility替换display:none,前者只会引起重绘,后者会引发回流(改变了布局)
    • 不要把DOM节点的属性值放到一个循环里当成循环的变量
      for(let i = 0; i < 1000; i++) {
        // 获取 offsetTop 会导致回流,因为需要去获取正确的值
        console.log(document.querySelector('.test').style.offsetTop)
      }
      
    • 不要使用table布局,可能很小的改动会造成整个table的重新布局 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用requestAnimationFrame
    • css选择符从右往做匹配查找,避免DOM深度过深
    • 将频繁运行的动画变为图层,图层能够阻止改节点回流影响别的元素。比如video标签,浏览器会自动将改节点变为图层

使用HTTP/2.0

  • 因为浏览器会有并发请求限制,在HTTP/1.1时代,每个请求都需要建立和断开,消耗了好几个RTT时间,并且由于TCP慢启动的原因,加载体积大的文件会需要更多的时间
  • 在HTTP/2.0中引入了多路复用,能够让多个请求使用同一个TCP链接,极大的加快了网页的加载速度。并且还支持Header压缩,进一步的减少了请求的数据大小

预加载

  • 在开发中,可能会遇到这样的情况,有些资源不需要马上用到,但是希望尽早获取,这个时候就可以使用预加载
  • 与价值啊其实是声明式的fetch,强制浏览器请求资源,并且不会阻塞onload事件,可以使用以下代码开启预加载
    <link rel="preload" href="http://example.com">
    
    预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的额文件延后加载,缺点是兼容性不太好

预渲染

  • 可以通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染
    <link rel="prerender" href="http://poetries.com">
    
  • 预渲染虽然可以提高页面的加载速度,但是要确保该页面百分百会被用户在之后打开,否则就白白浪费资源去渲染

懒执行

  • 懒执行就是将某些逻辑延迟到使用时计算。该技术可以用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,就可以使用懒执行。懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒

懒加载

  • 懒加载就是将不关键的资源延后加载
    • 懒加载的原理就是只加载自定义区域(通常是可视区域,但也可以是即将进入可视区域)
      内需要加载的东西。对于图片来说,先设置图片标签的src属性为一张占位图,将真实的图片资源放在一个自定义属性中,当进入自定义区域时,就将自定义属性替换为src属性,这样图片就会去下载资源实现懒加载
  • 懒加载不仅可以用于图片,也可以使用在别的资源上。比如进入可视区才开始播放的视频等。

文件优化

  • 图片优化
    • 如何优化图片,有2个思路
      • 减少像素点
      • 减少每个像素点能够显示的颜色
  • 图片加载优化
    • 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰类图片完全可以用css去代替。
    • 对于移动端来说,屏幕宽度就那么点,完全没必要去加载原图浪费带宽,一般图片都用cdn加载,可以计算出适配屏幕的款都,然后去请求相应裁剪好的图片
    • 小图使用base64格式
    • 将多个图标正和到一张图片中(雪碧图)
    • 选择正确的图片格式:
      • 对于更够显示WebP格式的浏览器尽量使用WebP格式。因为WebP格式具有更好的图片数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点是兼容性不太好
      • 小图使用PNG,其实对于大部分图标这类图片,完全可以使用SVG代替
      • 照片使用JPEG
  • 其他文件优化
    • css文件放在head中
    • 服务端开启文件压缩功能(gzip)
    • 将script标签放在body底部,因为js文件会阻塞渲染。当然吧script标签放在任意位置然后加上defer,表示该文件会并行下载,但是会放到HTML解析完成后顺序执行,对于没有任何依赖的js文件可以加上async,表示加载和渲染后续文档元素的过程将和js文件的加载与执行并行无序进行。执行js代码过长会卡住渲染,对于需要很多时间计算的代码可以考虑用Webworker,Webworker可以让我们另开一个线程执行脚本而不影响渲染
  • cdn
    • 静态资源尽量使用cdn加载,由于浏览器对于单个域名有并发请求上线,可以考虑使用多个cdn域名。对于cdn加载静态资源需要注意域名要与主站的不同,否则每次请求都会带上主站的cookie

安全

xss

  • 跨网站指令码(Cross-site-scripting简称xss)是一种网站应用程式的安全漏洞攻击,是代码注入的一种。他允许恶意使用者将程式码注入到网页上,其他使用者在观看网页时就会受到影响。这类攻击通常包含html以及使用者端脚本语言
  • xss分为三种:反射型,储存型和DOM-based
  • 如何攻击
    • xss通过修改html节点或者执行js代码来攻击网站
    • 例如通过url获取某些参数
      <!-- http://www.domain.com?name=<script>alert(1)</script> -->
      <div>{{name}}</div>
      
    上述 URL 输入可能会将 HTML 改为 <div><script>alert(1)</script></div> ,这样页面中就凭空多了一段可执行脚本。这种攻击类型是反射型攻击,也可以说是 DOM-based 攻击
  • 如何防御
    • 最普遍的做法是转义输入输出的内容,对于引号,尖括号,斜杠进行转义
      function escape(str) {
        str = str.replace(/&/g, "&amp;");
        str = str.replace(/</g, "&lt;");
        str = str.replace(/>/g, "&gt;");
        str = str.replace(/"/g, "&quto;");
        str = str.replace(/'/g, "&##39;");
        str = str.replace(/`/g, "&##96;");
          str = str.replace(/\//g, "&##x2F;");
          return str
      }
      

csrf

  • 跨站请求伪造,也被称为one-click attack或者session riding通缩写为csrf或者xsrf,是一种挟制用户在当前已登录的web应用程序上执行非本意的攻击方法
  • csrf就是利用用户的登录态发起恶意请求
  • 如何攻击
    • 假设网站中有一个通过get请求提交用户评论的接口,那么攻击者就可以在钓鱼网站中加入一个图片,图片的地址就是评论接口
      <img src="http://www.domain.com/xxx?comment='attack'"/>
      
  • 如何防御
    • get请求不对数据进行修改
    • 不让第三方网站访问到用户cookie
    • 阻止第三方网站请求接口
    • 请求时附带验证信息,比如验证码或者token

密码安全

  • 加密的密码添加自定义字段后进行二次加密

ES6中的Set和Map

ES6中的Set

  • ES6中提供Set数据容器,这是一个能够 存储无重复值 的有序列表。
    • 创建Set
      • 通过new Set()可以创建Set,然后通过add方法能够向Set中添加数据项:
        let cc = new Set()
        cc.add(1)
        cc.add("1")
        console.log(cc)//{1,"1"}
        console.log(cc.size);//2
        
      • Set内部使用Object.is()方法来判断两个数据项是否相等,唯一不同的是+0和-0在Set中被判断为是相等的。
      • 同时可以使用数组来构造Set,或者说具有迭代器的对象都可以用来构造Set,并且Set构造器会确保不会存在重复的数据项:
        let dd = new Set([1,2,2,3,3,3,5]);
        console.log(dd)//{1,2,3,5}
        console.log(dd.size)//4
        
      • 可以用has()方法判断某个值是否存在于Set中:
        let ccc = new Set([1,2,3,4,4])
        console.log(ccc.has(4))//true
        console.log(ccc.has(5))//false
        
      • 使用delete()方法从Set中删除某个值,或者使用clear()方法从Set中删除所有值:
        let set = new Set([1,2,3,3,3,3])
        console.log(set.size)//3
        console.log(set.has(5))//false
        set.delete(1)
        console.log(set.has(1))//false
        console.log(set.size)//2
        
      • 可以使用forEach方法来遍历Set中的数据项,该方法传入一个回调函数callback,还可以传入一个this(如果是箭头函数可以省略入参),用于回调函数中:
        let set = new Set([1,2,3,3,3,3])
        let operation = {
          print(value){
            console.log(value)
          },
          iterate(set=[]){
            set.forEach((value,key)=>{
              this.print(value)
              this.print(key)
            })
          }
        }
        operation.iterate(set)
        
      • 将Set转化成数组十分容易,可以将数组传入Set构造器即可;而将Set转换成数组,需要使用扩展运算符。扩展运算符能将数组中的数据项切分开,作为独立项传入到函数,如果将扩展运算符用于可迭代对象的话,就可以将可迭代对象转换成数组:
        let ccc = new Set([1,2,3,1,5])
        let [...arr] = ccc
        console.log(ccc)
        console.log(arr)
        
      • Set在存放对象时,实际上是存放对象的引用,即Set也被称为Strong Set。如果所储存的对象被置为null,但是Set实例仍然存在的话,对象依然无法被垃圾回收器回收,从而无法释放内存:
        let ccc = new Set();
        let key = {}
        let key2 = {}
        ccc.add(key)
        ccc.add(key2)
        console.log(ccc.size)//2
        key = null
        console.log(ccc.size)//2
        
        可以看出就算对象key置为null,但是由于是强引用的方式,Set实例还存在,对象key依然不会被回收。
      • 如果想让对象key正常释放的话,可以使用WeakSet,此时存放的是对象的弱引用,当对象只被Set弱引用的话,并不会阻止对象实例被回收。WeakSet同Set的用法机会一直。可以使用add()方法增加数据项,使用has()方法检查WeakSet中是否包含某项,以及使用delete方法删除某一项。
        let ccc = new WeakSet();
        let key = {}
        set.add(key)
        console.log(set.has(key))//true
        set.delete(key);
        console.log(set.has(key))//false
        
      • 但是需要注意的是:WeakSet构造器不接受基本类型数据,只接受对象。同样的可以使用可迭代的对象如数组,来作为构造器参数没来创建WeakSet
      • 对于WeakSet和Set之间的重要差异:
        1. 对于WeakSet实例,若调用add()方法时传入了非对象的参数,则会抛出错误。如果是has()或者delete()方法中传入了非对象的参数则会返回false;
        2. WeakSet不可迭代,因此不能用于for-of循环
        3. WeakSet无法暴露出任何迭代器(例如keys()与values()方法),因此没有任何编程手段可以用于判断WeakSet的内容。
        4. WeakSet没有forEach()方法
        5. WeakSet没有size属性

ES6中的Map

  • ES6中提供了Map数据结构,能够存放键值对,其中,键的去重是通过Object.is()方法进行比较,键的数据类型可以是基本类型数据也可以是对象,而值也可以是任意类型数据。
    1. 使用set()方法可以给Map添加键值对
    let ccc = new Map();
    ccc.set("rua","6666")
    ccc.set("year","2020")
    console.log(map)//[{key:"rua",value:"6666"},{key:"year",value:"2020"}]
    console.log(map.size)//2
    
    通过set()方法往Map中增加了2个键值对后,可以看到Map的大小就为2
    2. 通过get()方法可以从Map中提取值
    let ccc = new Map();
    ccc.set("rua","6666")
    ccc.set("year","2020")
    console.log(cc.get("rua"))//"6666"
    
    1. has(),delete()以及clear()方法
    • 为了和Set的操作保持一致,Map中同样有has()方法,用来检查某个数据项是否存在于Map中,使用delete方法可以从Map中删除一个数据项,使用clear()方法可以删除Map所有的数据项
      let ccc = new Map();
      ccc.set("rua","6666")
      ccc.set("year","2020")
      console.log(ccc.has("rua"))//true
      ccc.delete("rua")
      console.log(ccc.has("rua"))//false
      ccc.clear()
      console.log(ccc.size)
      
      与Set的初始化一样,Map也可以用数组来初始化Map,该数组中的每一个数据项也是数组,数组的第一个数据项代表建值对的键,第二个数据项是键值对的值:
      //使用数组来创建Map
      let ccc = new Map([["rua","6666"],["year","2020"]])
      console.log(ccc.has(rua))//true
      console.log(ccc.has(year))//true
      console.log(ccc.size)//2
      
    • WeakMap和WeakSet类似
    • ES6中Set和Map的总结

      1. Set是无重复值的有序列表。根据Object.is()方法来判断其中的值不相等,以保证无重复。Set会自动移除重复的值,因此你可以使用它来过滤数组中的重复值并返回结果,Set并不是数组的子类型,所以你无法随机访问其中的值,但你可以使用has()方法来判断某个值是否存在于Set中 ,或者通过size属性来查看其中有多少个值。Set类型还拥有forEach()方法,用于处理每个值。
      2. WeakSet是只能包含对象的特殊Set。其中的对象使用弱引用来存储,意味着当WeakSet中的项是某个对象的仅存引用时,他不会屏蔽垃圾回收。由于内存管理的复杂性,WeakSet的内容不能被检查,因此最好将WeakSet仅用于追踪需要被归组在一起的对象。
      3. Map是有序的键值对,其中的键允许是任何类型。与Set相似,通过调用Object.is()方法来判断重复的键,这意味着能将数值5与字符串"5"作为2个相对独立的键,使用Set()方法能将任何类型的值关联到某个键上,并且该值此后能用get()方法提取出来。Map也拥有一个size属性与一个forEach()方法,让项目访问更容易。
      4. WeakMap是只能包含对象类型的键的特殊Map。与WeakSet相似,键的对象引用是弱引用,因此当他是某个对象的仅存引用时,也不会屏蔽垃圾回收。当键被回收后,所关联的值也同时从WeakMap中移除。

React

  1. React中的keys的作用是什么?
  • Keys是React用于追踪那些列表元素被修改,被添加或者被移除的辅助标识
  • 在开发过程中,我们需要保证某个元素的key在其统计元素中具有唯一性。在React Diff算法中React会借助元素的key值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React还需要借助key值来判断元素与本地状态的关联。
  1. 在生命周期中的哪一步你应该发起ajax请求?
  • 我们将ajax请求放在componentDidMount函数中执行,主要原因:
    • React下一代调和算法Fiber会通过开始或停止渲染的方式优化应用性能,其会影响到componentWillMount的触发算法。对于ComponentWillMount这个什么周期函数的调用次数会变得不确定,React可能会多次频繁调用componentWillMount。如果我们将ajax方法放到componentWillMount函数中,那么显而易见其会被触发多次,自然不是最好的选择。
    • 如果我们将ajax请求放置到什么周期的其他函数中,我们并不能保证请求在组件挂载完毕后才会要求响应。如果我们的数据请求在组件挂载之前完成,并且调用了SetState函数将数据添加到组件状态中,对于未挂载的组件则会报错。而在componentDidMount函数中进行ajax请求则能有效避免这个问题
  1. shouldComponentUpdate的作用
  • shouldComponentUpdate允许我们手动的判断是否要进行组件更新,根据组件的应用场景设置函数的合理返回值能够帮助我们避免不必要的更新
  1. 概述下React中的事件处理逻辑
  • 为了解决跨浏览器兼容性问题,React会将浏览器原生事件(Browser Native Event)封装为合成事件(SyntheticEvent)传入设置的事件处理器中。这里的合成事件提供了与原生事件相同的接口,不过他们屏蔽了底层浏览器的细节差异,保证了行为的一致性。另外有意思的是,React并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理。这样React在更新DOM的时候就不需要考虑如何去处理附着在DOM上的事件监听,最终达到优化性能的目的
  1. redux有什么缺点
  • 一个组件所需要的数据,必须由父组件传过来,而不能像flux直接从store取
  • 当一个组件相关数据更新时,即使父组件不需要用到这个组件,父组件还是会重新render,可能会有效率影响,或者需要写复杂的shouldComponentUpdate进行判断
  1. react生命周期函数
  • 初始化阶段
    • getDefaultProps:获取实例的默认属性
    • getInitialState:获取每个实例的初始化状态
    • componentWillMount:组件即将被装载、渲染到页面上
    • render:组件在这里生成虚拟的DOM节点
    • omponentDidMount:组件真正在被装载之后
  • 运行中状态
    • componentWillReceiveProps:组件将要接收到属性的时候调用
    • shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回false,接收数据后不更新,阻止render调用,后面的函数不会被继续执行了)
    • componentWillUpdate:组件即将更新不能修改属性和状态
    • render:组件重新描绘
    • componentDidUpdate:组件已经更新
  • 销毁阶段
    • componentWillUnmount:组件即将销毁
  1. react性能优化是哪个周期函数
  • shouldComponentUpdate这个方法用来判断是否需要调用render方法重新描绘dom。因为dom的描绘非常消耗性能,如果我们能在shouldComponentUpdate方法中能够写出更优化的dom diff算法,可以极大的提高性能
  1. 为什么虚拟dom会提高性能
  • 虚拟dom相当于在js和真实dom中加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能
  • 具体实现步骤
    • 用js对象结构表示dom树的结构;然后用这个树去构建一个真正的DOM树,插到文档当中
    • 当状态变更的时候,重新构造一颗新的对象树。然后用新的树和旧的树进行比较,记录2棵树的差异
    • 把2所记录的差异应用到步骤1所构建的DOM树上,视图就更新了
  1. diff算法
  • 把树形结构按照层级分解,只比较同级元素
  • 给列表结构的每个单元添加唯一的key属性,方便比较。
  • React只会匹配相同class的component(这里面的class指的是组件的名字)
  • 合并操作,调用component的setState方法的时候,React将其标记为dirty,到每一个事件循环结束,React检查所有标记dirty的component重新绘制
  • 选择性子树渲染。开发人员可以重写shouldComponentUpdate提高diff性能
  1. react性能优化方案
  • 重写shouldComponentUpdate来避免不必要的DOM操作
  • 使用production版本的react.js
  • 使用key来帮助react识别列表中所有子组件的最小变化
  1. react的虚拟dom是怎么实现的
  • 首先说说为什么要是用Virturl DOM,因为操作真实DOM的耗费的性能代价太高,所以react内部使用js实现了一套dom结构,在每次操作真实dom之前,使用实现好的diff算法,对虚拟dom进行比较,递归找出有变化的dom节点,然后对其进行更新操作。为了实现虚拟dom,我们需要把每一种节点类型抽象成对象,每一种节点类型有自己的属性,也就是prop,每次进行diff的时候,react回显比较该节点类型,假如节点类型不一样,那么react会直接删除该节点,然后直接创建新的节点插入到其中,假如节点类型一样,那么会比较prop是否有更新,假如prop不一样,那么react会判定该节点有更新,那么重渲染该节点,然后在对其子节点进行比较,一层一层往下,知道没有子节点
  1. react的渲染过程中,兄弟节点之间是怎么处理的?也就是key值不一样的时候
  • 通常我们输出节点的时候都是map一个数组然后返回ReactNode,为了方便内部进行优化,我们必须给每一个reactNode添加key,这个key prop在设计值处不是给开发者用的,而是给react用的,大概的作用就是给每一个reactNode添加一个身份表示,方便react进行识别,在重新渲染过程中,如果key一样,若组件属性有所变化,则react只更新组件对应点额属性;没有变化则不更新,如果key不一样,则react先销毁该组件然后重新创建该组件

VUE

1.详细说下你对vue生命周期的理解

  • 总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后
    • 创建前/后:在beforeCreate阶段,vue实例的挂载元素el和数据对象data都是undefined还未初始化。在created节点,vue实例的数据对象data有了el还没有
    • 载入前/后:在beforeMount阶段,vue实例的¥el和data都初始化了,但还是在挂载之前的虚拟dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。
    • 更新前/后:当data变化时,会触发beforeUpdate和update方法
    • 销毁前/后:在执行destroy方法后,对data的改变不会触发触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在
  1. vue路由的钩子函数
  • 首页可以控制导航跳转,beforeEach,afterEach等,一般用于页面title的修改。一些需要登录才能调整的页面的重定向功能。
    • beforeEach主要有三个参数to,form,next
    • to:route即将进入的目标路由对象
    • form:route当前当行正要离开的路由
    • next:function一定要调用该方法resolve这个钩子。执行效果依赖next方法的调用参数。可以控制网页的跳转
  1. r o u t e 和 route和 routerouter的区别
  • $route是路由信息对象,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
  • 而$router是路由实例对象包括了路由的跳转方法,钩子函数
  1. keep-alive的作用是什么
  • keep-alive包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染。
    • 比如有一个列表和一个详情,那么用户就会经常执行打开详情=>返回列表=>打开详情。。这样的话列表和详情都是一个频率很高的页面,那么就可以对列表组件使用keep-alive进行缓存,这样用户每次返回列表的时候,都能从缓存中快速渲染,而不是重新渲染
  1. NextTick
  • nexttick可以让我们在下次DOM更新循环结束之后执行延迟回调。用于获得更新后的DOM
  1. vue的优点是什么?
  • 低耦合。视图(view)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的“view”上,当view变化的时候Model可以不变,当Model变化的时候View也可以不变
  • 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑
  • 可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写
  1. vue组件的data为什么必须是函数
  • 每个组件都是vue实例
  • 组件共享data属性,当data的值是同一个引用类型的值时,改变其中一个会影响其他的
  1. mvvm
  • 在mvvm中,ui是通过数据驱动的,数据一旦改变就会相应的刷新对应的ui,ui如果改变,也会改变对应的数据。这种方式就可恶意在业务处理中只关心数据的流转,而无需直接和页面打交道。viewmodel只关心数据和业务的处理,不关心view如何处理数据,在这种情况下,view和model都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可服用的逻辑放在一个viewmodel中,让多个view复用这个viewmodel
  • 在mvvm中最核心的也就是数据双向绑定,例如angular和react的脏数据检测,vue中的数据劫持
    • 脏数据检测
      • 当触发了指定的事件后会进入脏数据检测,这时会调用 d i g e s t 循 环 遍 历 所 以 的 数 据 观 察 者 , 判 断 当 前 值 是 否 和 先 前 的 值 有 区 别 , 如 果 检 测 到 变 化 的 话 , 会 调 用 digest循环遍历所以的数据观察者,判断当前值是否和先前的值有区别,如果检测到变化的话,会调用 digestwatch函数u,然后再次调用$digest循环直到没有发现变化。循环至少为2次 至多为10次
      • 脏数据检测虽然存在低效问题,但是不关心数据是通过什么方式改变的,都可以完成任务,但是这在vue中的双向绑定是存在问题的。并且脏数据检测可以实现批量检测出更新的值,再去统一更新ui,大大减少操作DOM的次数,所以低效是相对的。
    • 数据劫持
      • vue内部使用了Object。definePropperty()来实现双向绑定,通过这个函数可以监听到set和get的事件
        let data = { name : "fsh"}
        observer(data)
        let name = data.name
        data.name = "rua"
        function observer(obj){
          //判断类型
          if( !obj || typeof obj !== "object" ){
            return
          }
          Object.keys(obj).forEach(key=>{
            defineReactive(obj,key,obj[key])
          })
        }
        
        function defineReactive(obj,key,val){
          //递归子属性
          observer(val)
          Object.defineProperty(obj,key,{
            enumerable:true,
            configurable:true,
            get:function reactiveGetter(){
              console.log("get value")
              return val
            },
            set:function reactiveSetter(newVal){
              console.log("change value")
              val = newVal
            }
          })
        }
        
      • proxy与Object.defineProperty对比
        • Object.defineProperty虽然已经能够实现双向绑定了,但是他还是有缺陷的
      • 只能对属性进行数据劫持,所以需要深度遍历整个对象对于数组不能监听到数据的变化
      • 虽然vue中确实能检测到数组数据的变化,但是其实是使用的hack方法,并且也是有缺陷的
        const arrayProto = Array.prototype
        export const arrayMethods = Object.create(arrayProto)
        // hack 以下几个函数
        const methodsToPatch = [
          'push',
          'pop',
          'shift',
          'unshift',
          'splice',
          'sort',
          'reverse'
        ]
        methodsToPatch.forEach(function (method) {
          // 获得原生函数
          const original = arrayProto[method]
          def(arrayMethods, method, function mutator (...args) {
            // 调用原生函数
            const result = original.apply(this, args)
            const ob = this.__ob__
            let inserted
            switch (method) {
              case 'push':
              case 'unshift':
                inserted = args
                break
              case 'splice':
                inserted = args.slice(2)
                break
            }
            if (inserted) ob.observeArray(inserted)
            // 触发更新
            ob.dep.notify()
            return result
          })
        })
        
      • 反观proxy就没有以上的问题,原生支持数组变化,并且可以直接整个对象进行拦截,所以vue也将在下个大版本使用proxy替换Object.defineProperty
        let onWatch = (obj, setBind, getLogger) => {
          let handler = {
            get(target, property, receiver) {
              getLogger(target, property)
              return Reflect.get(target, property, receiver);
            },
            set(target, property, value, receiver) {
              setBind(value);
              return Reflect.set(target, property, value);
            }
          };
          return new Proxy(obj, handler);
        };
        
        let obj = { a: 1 }
        let value
        let p = onWatch(obj, (v) => {
          value = v
        }, (target, property) => {
          console.log(`Get '${property}' = ${target[property]}`);
        })
        p.a = 2 // bind `value` to `2`
        p.a // -> Get 'a' = 2
        
  1. Virtual Dom
  • 为什么需要Virtual Dom?
    • 众所周知,操作dom是很耗费性能的一件事情,既然如此,我们可以考虑通过js对象来模拟dom对象,毕竟操作js对象比操作dom省时的多
    • Virtual Dom算法简述
      • 既然我们已经通过js来模拟实现DOM,那么接下来的难点就在于如何判断旧的对象和新的对象之间的差异
      • DOM是多叉树的结构,如果需要完整的比较2颗树的差异,那么需要的时间复杂度会是O(n^3),这个复杂度肯定是不能够接受的,于是React团队优化了算法,实现了O(n)的复杂度来对比差异
      • 实现O()复杂度的关键就是只对比同层的节点,而不是跨层对比,这也是考虑到在实际业务中很少会去跨层的移动DOM元素
      • 所以判断差异的算法就分为了2步
        • 首先从上至下,从左往右遍历对象,也就是树的深度遍历,这一步中会给每个节点添加索引,便于最后的渲染差异
        • 一旦节点有子元素,就去判断子元素是否有不同
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值