js原理题
1.什么是闭包,举个例子说明一下?
答:
“闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。”
举例:创建闭包最常见方式,就是在一个函数内部创建另一个函数。下面例子中的 closure 就是一个闭包, function func(){
vara =1 ,b = 2; funciton closure(){ return a+b; } return
closure; }
2.apply/call/bind 有什么区别?
答: 这三者的作用就是改变函数运行时this的指向。 call方法: 语法:call([thisObj[,arg1[,
arg2[,
[,.argN]]]]]) 定义:调用一个对象的一个方法,以另一个对象替换当前对象。 说明:call
方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。 apply方法: 语法:apply([thisObj[,argArray]]) 定义:应用某一对象的一个方法,用另一个对象替换当前对象。 说明:如果
argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。
如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。 bind方法: 语法:bind(thisArg[, arg1[, arg2[, ...]]])
定义:将接受多个参数的函数变换成接受一个单一参数。
说明:bind()方法所返回的函数的length(形参数量)等于原函数的形参数量减去传入bind()方法中的实参数量(第一个参数以后的所有参数),因为传入bind中的实参都会绑定到原函数的形参。
3.介绍下原型链(解决的是继承问题吗)
答:
JavaScript原型: 每个对象都会在其内部初始化一个属性,就是prototype(原型)。 原型链:
当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
特点:
JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
4.简单说说js中的继承?
答: 有以下六种方法
1.原型链继承 JavaScript实现继承的基本思想:通过原型将一个引用类型继承另一个引用类型的属性和方法。
2.借用构造函数继承(伪造对象或经典继承)
JavaScript实现继承的基本思想:在子类构造函数内部调用超类型构造函数。 通过使用apply()和call()方法可以在新创建的子类对象上执行构造函数。
3.组合继承(原型+借用构造)(伪经典继承) JavaScript实现继承的基本思想:将原型链和借用构造函数的技术组合在一块,从而发挥两者之长的一种继承模式。
将原型链和借用构造函数的技术组合到一起,从而取长补短发挥两者长处的一种继承模式。
4.原型式继承 JavaScript实现继承的基本思想:借助原型可以基于已有的对象创建新对象,同时还不必须因此创建自定义的类型。
5.寄生式继承 JavaScript实现继承的基本思想:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真正是它做了所有工作一样返回对象。
寄生式继承是原型式继承的加强版。
6.寄生组合式继承 JavaScript实现继承的基本思想:通过借用函数来继承属性,通过原型链的混成形式来继承方法。
5.文件上传如何做断点续传?
答: 文件断点续传是HTML5引入的新特性,HTML5的FILE
api,有一个slice方法,可以将BLOB对象进行分割。前端通过FileList对象获取到相应的文件,按照指定的分割方式将大文件分段,然后一段一段地传给后端,后端再按顺序一段段将文件进行拼接。
断点续传原理
目前比较常用的断点续传的方法有两种,一种是通过websocket接口进行文件上传,另一种是通过ajax,两种方法各有千秋,虽然websocket听起来比较高端些,但是除了用了不同的协议外其他的算法基本上都是很相似的,并且服务端要开启ws接口,这里用相对方便的ajax来说明断点上传的思路。
说来说去,断点续传最核心的内容就是把文件“切片”然后再一片一片的传给服务器。
首先是文件的识别,一个文件被分成了若干份之后如何告诉服务器你切了多少块,以及最终服务器应该如何把你上传上去的文件进行合并?
因此在文件开始上传之前,我们和服务器要有一个“握手”的过程,告诉服务器文件信息,然后和服务器约定切片的大小,当和服务器达成共识之后就可以开始后续的文件传输了。
前台要把每一块的文件传给后台,成功之后前端和后端都要标识一下,以便后续的断点。
当文件传输中断之后用户再次选择文件就可以通过标识来判断文件是否已经上传了一部分,如果是的话,那么我们可以接着上次的进度继续传文件,以达到续传的功能。
6.介绍this各种情况?
this的情况:
1.以函数形式调用时,this永远都是window
2.以方法的形式调用时,this是调用方法的对象
3.以构造函数的形式调用时,this是新创建的那个对象
4.使用call和apply调用时,this是指定的那个对象
5.箭头函数:箭头函数的this看外层是否有函数
如果有,外层函数的this就是内部箭头函数的this
如果没有,就是window
- 特殊情况:通常意义上this指针指向为最后调用它的对象。这里需要注意的一点就是如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例
7.JS里垃圾回收机制是什么,常用的是哪种,怎么处理的?
答:
JS的垃圾回收机制是为了以防内存泄漏,内存泄漏的含义就是当已经不需要某块内存时这块内存还存在着,垃圾回收机制就是间歇的不定期的寻找到不再使用的变量,并释放掉它们所指向的内存。
JS中最常见的垃圾回收方式是标记清除。
工作原理:是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
工作流程:
垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。
去掉环境中的变量以及被环境中的变量引用的变量的标记。
再被加上标记的会被视为准备删除的变量。
垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。
8.Promise和setTimeout的区别?
答:
回顾JavaScript事件循环并发模型,我们了解了setTimeout和Promise调用的都是异步任务,这一点是它们共同之处,也即都是通过任务队列进行管理/调度。那么它们有什么区别吗?
任务队列
前文已经介绍了任务队列的基础内容和机制,可选择查看,本文对任务队列进行拓展介绍。JavaScript通过任务队列管理所有异步任务,而任务队列还可以细分为MacroTask
Queue和MicoTask Queue两类。
MacroTask Queue MacroTask
Queue(宏任务队列)主要包括setTimeout,setInterval, setImmediate,
requestAnimationFrame, NodeJS中的`I/O等。 MicroTask Queue MicroTask
Queue(微任务队列)主要包括两类: 独立回调microTask:如Promise,其成功/失败回调函数相互独立;
复合回调microTask:如 Object.observe,
MutationObserver 和NodeJs中的
process.nextTick ,不同状态回调在同一函数体; MacroTask和MicroTask
JavaScript将异步任务分为MacroTask和MicroTask,那么它们区别何在呢?
依次执行同步代码直至执行完毕;
检查MacroTask 队列,若有触发的异步任务,则取第一个并调用其事件处理函数,然后跳至第三步,若没有需处理的异步任务,则直接跳至第三步;
检查MicroTask队列,然后执行所有已触发的异步任务,依次执行事件处理函数,直至执行完毕,然后跳至第二步,若没有需处理的异步任务中,则直接返回第二步,依次执行后续步骤;
最后返回第二步,继续检查MacroTask队列,依次执行后续步骤; 如此往复,若所有异步任务处理完成,则结束;
vue原理面试题
1.vue响应式原理
当创建 Vue 实例时,vue 会遍历 data 选项的属性,利用 Object.defineProperty 为属性添加 getter 和 setter 对数据的读取进行劫持(getter 用来依赖收集,setter 用来派发更新),并且在内部追踪依赖,在属性被访问和修改时通知变化。
每个组件实例会有相应的 watcher 实例,会在组件渲染的过程中记录依赖的所有数据属性(进行依赖收集,还有 computed watcher,user watcher 实例),之后依赖项被改动时,setter 方法会通知依赖与此 data 的 watcher 实例重新计算(派发更新),从而使它关联的组件重新渲染。
一句话总结:
vue.js 采用数据劫持结合发布-订阅模式,通过 Object.defineproperty 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调
2.computed 的实现原理
computed 本质是一个惰性求值的观察者。computed 内部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立刻求值,同时持有一个 dep 实例。
其内部通过 this.dirty 属性标记计算属性是否需要重新求值。
当 computed 的依赖状态发生改变时,就会通知这个惰性的 watcher,
computed watcher 通过 this.dep.subs.length 判断有没有订阅者,
有的话,会重新计算,然后对比新旧值,如果变化了,会重新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 重新渲染,本质上是一种优化。)
没有的话,仅仅把 this.dirty = true。 (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。)
3. computed 和 watch 有什么区别及运用场景?
区别
computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
watch 侦听器 : 更多的是「观察」的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。
运用场景
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算。
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
4.为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?
Object.defineProperty 本身有一定的监控到数组下标变化的能力,但是在 Vue 中,从性能/体验的性价比考虑,尤大大就弃用了这个特性(Vue 为什么不能检测数组变动 )。为了解决这个问题,经过 vue 内部处理后可以使用以下几种方法来监听数组
push();pop();shift();unshift();splice();sort();reverse();
由于只针对了以上 7 种方法进行了 hack 处理,所以其他数组的属性也是检测不到的,还是具有一定的局限性。
Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue 2.x 里,是通过 递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象是才是更好的选择。Proxy 可以劫持整个对象,并返回一个新的对象。Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。
5.Vue 组件 data 为什么必须是函数
new Vue()实例中,data 可以直接是一个对象,为什么在 vue 组件中,data 必须是一个函数呢?
因为组件是可以复用的,JS 里对象是引用关系,如果组件 data 是一个对象,那么子组件中的 data 属性值会互相污染,产生副作用。
所以一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。new Vue 的实例是不会被复用的,因此不存在以上问题。
6.说说 Vue 的渲染过程
调用 compile 函数,生成 render 函数字符串 ,编译过程如下:
parse 函数解析 template,生成 ast(抽象语法树)
optimize 函数优化静态节点 (标记不需要每次都更新的内容,diff 算法会直接跳过静态节点,从而减少比较的过程,优化了 patch 的性能)
generate 函数生成 render 函数字符串
调用 new Watcher 函数,监听数据的变化,当数据发生变化时,Render 函数执行生成 vnode 对象
调用 patch 方法,对比新旧 vnode 对象,通过 DOM diff 算法,添加、修改、删除真正的 DOM 元素
7.聊聊 keep-alive 的实现原理和缓存策略
原理
获取 keep-alive 包裹着的第一个子组件对象及其组件名
根据设定的 include/exclude(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例
根据组件 ID 和 tag 生成缓存 Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该 key 在 this.keys 中的位置(更新 key 的位置是实现 LRU 置换策略的关键)
在 this.cache 对象中存储该组件实例并保存 key 值,之后检查缓存的实例数量是否超过 max 的设置值,超过则根据 LRU 置换策略删除最近最久未使用的实例(即是下标为 0 的那个 key)
最后组件实例的 keepAlive 属性设置为 true,这个在渲染和执行被包裹组件的钩子函数会用到,这里不细说
LRU 缓存淘汰算法
LRU(Least recently used)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
keep-alive 的实现正是用到了 LRU 策略,将最近访问的组件 push 到 this.keys 最后面,this.keys[0]也就是最久没被访问的组件,当缓存实例超过 max 设置值,删除 this.keys[0]
8.vm.$set()实现原理是什么?
受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃),Vue 无法检测到对象属性的添加或删除。
由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。
那么Vue 内部是如何解决对象新增属性不能响应的问题的呢?
如果目标是数组,使用 vue 实现的变异方法 splice 实现响应式
如果目标是对象,判断属性存在,即为响应式,直接赋值
如果 target 本身就不是响应式,直接赋值
如果属性不是响应式,则调用 defineReactive 方法进行响应式处理
9. 怎样理解 Vue 的单向数据流?
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。
这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。
有两种常见的试图改变一个 prop 的情形 :
这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。 在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:
props: ['initialCounter'],
这个 prop 以一种原始的值传入且需要进行转换。 在这种情况下,最好使用这个 prop 的值来定义一个计算属性
props: ['size'],
10.Vue 组件间通信有哪几种方式?
Vue 组件间通信是面试常考的知识点之一,这题有点类似于开放题,你回答出越多方法当然越加分,表明你对 Vue 掌握的越熟练。Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信。
(1)props / $emit 适用 父子组件通信
这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。
(2)ref 与 children 适用 父子组件通信
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
children:访问父 / 子实例
(3)EventBus (on) 适用于 父子、隔代、兄弟组件通信
这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
(4)listeners 适用于 隔代组件通信
attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
listeners" 传入内部组件
(5)provide / inject 适用于 隔代组件通信
祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
(6)Vuex 适用于 父子、隔代、兄弟组件通信
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
11.你使用过 Vuex 吗?
vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
主要包括以下几个模块:
State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。
12.使用过 Vue SSR 吗?说说 SSR?
Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
即:SSR大致的意思就是vue在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的html 片段直接返回给客户端这个过程就叫做服务端渲染。
服务端渲染 SSR 的优缺点如下:
(1)服务端渲染的优点:
更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
更快的内容到达时间(首屏加载更快):SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;
(2) 服务端渲染的缺点:
更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。
如果没有 SSR 开发经验的同学,可以参考本文作者的另一篇 SSR 的实践文章《Vue SSR 踩坑之旅》,里面 SSR 项目搭建以及附有项目源码。
13.能说下 vue-router 中常用的 hash 和 history 路由模式实现原理吗?
(1)hash 模式的实现原理
早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 '#search':
https://www.word.com#search
hash 路由模式的实现主要是基于下面几个特性:
URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。
(2)history 模式的实现原理
HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:
window.history.pushState(null, null, path);
history 路由模式的实现主要基于存在下面几个特性:
pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。
React原理面试题
1.vue-router 和 react-router的区别
vue-Router
hash:使用URL hash值来作路由。默认模式
history:依赖HTML5 History API和服务器配置。查看HTML5 History模式。
abstract:支持所有的JavaScript运行环境,入Node.js服务器端
React-Router
<HashRouter>URL格式为Hash路由组件
<MemoryRouter>内存路由组件
<NativeRouter>Native的路由组件
<StaticRouter>地址不改变的静态路由组件
前端路由本质的两种支持:(改变视图的同时不会向后端发出请求)
1.hash——即地址栏URL中的#符号以及之后的数据。
2.history——利用了HTML5 History Interface中新增的pushState()和replaceState()方法。(使用需要注意兼容性)(Apache或者Nginx需要配置)
前端路由,简单来说,就是当浏览器的url产生变化时,不向服务器进行请求,而是直接控制前端页面产生变化,以期待前端在比如功能切换时,产生类似页面跳转等效果。
vue-router 和 react-router本质区别:
vue-router是全局配置方式,react-router是全局组件方式
vue-router仅支持对象形式的配置,react-router支持对象形式和jsx语法的组件形式配置。
vue-router任何组件都会被渲染到<router-view/>的位置,react-router子组件作为children被传入父组件。而根组件被渲染到<Router/>位置。
使用方式上的不同,获取参数的方式不同
2.如果进行三次setState会发生什么,循环执行setState组件会一直重新渲染吗
setState这个方法是用来告诉react组件数据有更新,有可能需要重新渲染。它是异步的,react通常会集齐一批需要更新的组件,然后一次性更新来保证渲染的性能,所以进行三次只有一次的效果,并不会导致多次渲染。
此外再使用setState改变状态之后,立刻使用this.state去拿最新的状态往往是拿不到的,如果需要最新的state做业务的话,可以在componentDidUpdate或者setState的回调函数里获取。(官方推荐第一种)
3.react使用什么更新节点?原理和过程是什么?
主要使用了diff算法,react根据两个假设
1.两个相同的组件产生类似的DOM结构,不同组件产生不同DOM结构
2.对于同一层次的一组子节点,它们可以通过唯一的id区分
对传统的diff作了更改。将算法复杂度从O(n^3)降低到O(n)。
react的diff函数只对同层的节点进行比对比,即父节点相同会进行对比,因为不同组件的dom结构不同,所以没必要对比子节点。
如果出现节点不同的情况,会直接删除节点,重新新建节点。
如果节点相同属性不同,则会更新属性。
4.react diff的原理
1.React通过制定大胆的diff策略,将 O(n3) 复杂度的问题转换成 O(n) 复杂度的问题;
React是如何将O(n3) 复杂度的问题转换成 O(n) 的?
-只进行同级比较
-不同类的React组件会被当做完全不同的DOM结构而被完全替换
-key prop:开发人员可以通过给Virtual DOM一个唯一的key属性暗示React这是同一个DOM结构,反之若key值不同则会被当做完全不同的DOM结构。
2.React通过分层求异的策略,对tree diff进行算法优化;
3.React通过相同类生成相似树形结构,不同类生成不同树形结构的策略,对component diff进行算法优化。
4.React通过设置唯一key的策略,对element
diff进行算法优化;
5.建议,在开发组件时,保持稳定的DOM结构会有助于性能的提升;
6.建议,在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响React的渲染性能。
5.请简述react的事件机制?
react利用了事件委托机制实现事件机制,事件并没有绑定在真实的dom节点上面,是绑定在最外层的docment上。使用一个统一的监听器,所有的事件都由这个监听器统一分发。
组件挂载更新时,会将事件分门别类放进事件池,事件触发根据event找到对应的组件,再组件标识和事件类型找到对应的事件进行监听回调,然后,执行回调函数。
6.fiber是什么?
答:因为js的运数、页面布局和页面绘制都在浏览器的主线程里,所以,如果js占用浏览器主线程事件长会造成掉帧。所以,react团队重写了Reconciler 层,即组件生命周期和diff算法,react团队命名为fiber Reconciler,建立了自己的组件调用栈,让diff计算可打断。
7. 请简述fiber的原理和流程?
答:Fiber 树在首次渲染的时候会一次过生成,这个树是由Virtual Dom和一些调度信息组成,每个节点包括父节点、子节点、兄弟节点、节点实列。后续进行diff计算时,会根据已有树和最新 Virtual DOM 的信息,生成一棵新的树。这个树每生成一个节点,都会把控制权交还个浏览器,去查看有没有优先级较高的任务需要处理,没有就继续构建。如果有就会放弃当前生成的树,等空闲再重新执行。生成fiber 树的过程中,如果发现需要更新的信息时,fiber Reconciler 会将信息保存Effect List当中,等树构建完之后批量更新,批量更新跟构建fiber不同,是无法打断的。
8.什么是服务器渲染?
答:服务器接到用户请求之后,计算出用户需要的数据,然后将数据更新成视图(也就是一串dom字符)发给客户端,客户端直接将这串字符塞进页面即可。
9.服务器渲染和浏览器渲染对比有什么优点?有什么缺点?
答:页面相应比较快,用户体验比较好。另外对于搜索引擎比较友好,优化seo。缺点是增加了一层nodejs,增加了服务器的计算。前后端分工不明,不能很好并行开发。
webpack面试题
1.webpack打包原理
把所有依赖打包成一个 bundle.js 文件,通过代码分割成单元片段并按需加载。
2.分别介绍bundle,chunk,module是什么
bundle:是由webpack打包出来的文件,
chunk:代码块,一个chunk由多个模块组合而成,用于代码的合并和分割。
module:是开发中的单个模块,在webpack的世界,一切皆模块,一个模块对应一个文件,webpack会从配置的entry中递归开始找出所有依赖的模块
3.分别介绍什么是loader?什么是plugin?
loader:模块转换器,用于将模块的原内容按照需要转成你想要的内容
plugin:在webpack构建流程中的特定时机注入扩展逻辑,来改变构建结果,是用来自定义webpack打包过程的方式,一个插件是含有apply方法的一个对象,通过这个方法可以参与到整个webpack打包的各个流程(生命周期)。
模块热更新是webpack的一个功能,它可以使得代码修改之后,不用刷新浏览器就可以更新。在应用过程中替换添加删出模块,无需重新加载整个页面,是高级版的自动刷新浏览器。
优点:
只更新变更内容,以节省宝贵的开发时间。调整样式更加快速,几乎相当于在浏览器中更改样式
5.webpack-dev-server 和 http服务器的区别
webpack-dev-server使用内存来存储webpack开发环境下的打包文件,并且可以使用模块热更新,比传统的http服务对开发更加有效。
浏览器在用户访问页面的时候,为了加快加载速度,会对用户访问的静态资源进行存储,但是每一次代码升级或者更新,都需要浏览器去下载新的代码,最方便和最简单的更新方式就是引入新的文件名称。
在webpack中,可以在output给出输出的文件制定chunkhash,并且分离经常更新的代码和框架代码,通过NameModulesPlugin或者HashedModulesPlugin使再次打包文件名不变。
7.什么是Tree-sharking?CSS可以Tree-shaking吗?
Tree-shaking是指在打包中去除那些引入了,但是在代码中没有被用到的那些死代码。在webpack中Tree-shaking是通过uglifySPlugin来Tree-shakingJS。Css需要使用Purify-CSS。
标签:中级,Vue,函数,渲染,对象,前端,面试,组件,属性 From: https://www.cnblogs.com/qinlinkun/p/18061253