浏览器前言
最近正好在找工作,整理一下可能问到的和遇到过的面试题。
HTML
HTML语义化标签
HTML5新增的语义化标签有:header、nav、main、article、aside、footer等等。
HTML语义化就是让页面内容结构化,他有以下优点:
- 易于用户阅读,当用户因网络原因无法加载css或js时,能够让页面呈现清晰的结构。
- 有利于SEO搜索引擎的抓捕。
- 方便其他设备解析,比如盲人阅读器根据语义化标签渲染网页。
- 有利于开发维护,语义化更具可读性,代码更好维护。
HTML5新标签
HTML5新增的标签有:header、nav、video、audio、canvas、aside等等。
HTML5新增了哪些内容
广义上的HTML5指的是最新一代前端开发技术的总称,包括:HTML5、CSS3、新增的webStorage等webAPI。
HTML中新增了header、footer、nav等语义化标签,还新增了video、audio媒体标签,新增了canvas画布,还有一些标签的新属性,比如:input标签的placeholder属性。
CSS中新增了圆角、阴影、滤镜、vw和vh长度单位,flex布局,媒体查询,过度和动画,伪类等。
js中新增了webStorage浏览器缓存策略、querySelector、webSocket、requestAnimationFrame、Worker线程、地理位置等。
HTML5中的form的自动完成功能是什么
autocomplete
属性规定输入字段是否应该启用自动完成功能,默认为启用。设置autocomplete=off
可以关闭该功能。
自动完成允许浏览器预测对字段的输入。在用户在字段开始键入时,浏览器基于之前键入的值,应该显示出在字段中的选项。
多个浏览器标签页之间的通信
浏览器标签页之间是没办法直接通信的,一般都是通过找一个中介者来实现通信。
- 使用websocket,通信的标签页连接同一个服务器,发送消息到服务器后,服务器推送消息给所有连接的客户端。
- 可以适当的调用localStorage,localStorage在另一个浏览上下文里被添加、修改或删除时,都会触发一个storage事件,可以通过监听storage事件,控制它的值来进行页面信息通信。
- 如果可以获取对应标签的引用,可通过postMessage方法向另一个标签页传递数据。
H5中cookie的原理
cookie就是HTML5提供的一种缓存方式,一个cookie就是存储在用户主机浏览器中的一小段文本文件。Cookies是纯文本形式,他们不包含任何可执行代码。每个cookie相当于一个用户标识,在该页面发送请求时,会携带对应页面存储的cookie值发送给后端用来判断登录状态是否失效。
cookie还可以用来存储数据,但是浏览器供cookie存放数据的空间较少,如果需要存放大容量数据,推荐使用webStorage。
CSS
CSS盒模型
盒模型分为标准盒模型和ie盒模型。标准盒模型为content(内容)+padding(内边距)+border(边框)+margin(外边距),而ie盒模型则为content(内容)+padding(内边距)+margin(外边距)。
标准盒模型和ie盒模型最根本的区别在于标准盒模型将边框的单位长度单独算了进去,而ie盒模型没有算进去,ie盒模型将边框的单位长度算进内边距中。
可以通过box-sizing进行标准盒模型和ie盒模型之间的转换。
标准盒模型:box-sizing: content-box;
ie盒模型:box-sizing: border-box;
rem和em的区别
rem和em都是css的长度单位,rem是相对于根元素html的font-size进行变化,em是根据父元素的fong-size进行变化的。
CSS选择器
常用的选择器
通配符选择器:* ID选择器:#ID 类选择器:.class 元素选择器:p、a、span等 后代选择器:p span、div a等 伪类选择器:a:hover、a:active等 属性选择器:input[type="text"]等
CSS选择器权重
!import > 行内样式 > #id > .class > 元素和伪元素 > * > 继承 > 默认
CSS精灵图
在CSS中,多个图标集成到一张图上使用background-position属性设置的图片,称为精灵图(又称图片精灵)。
CSS新特性
transition: 过渡 transform: 旋转、缩放、移动或者倾斜 animation: 动画 gradient: 渐变 shadow: 阴影 border-radius: 圆角
元素性质:行内元素和块级元素
行内元素(display: inline)
宽度和高度是由内容决定的,与其他元素共占一行不能设置宽高,例如:span标签、a标签、i标签等等
块级元素(display: block)
默认宽度由父容器决定,默认高度由内容决定,并且独占一行可以设置宽高,例如:p标签、div标签、ul标签等。
行内块元素(display: inline-block)
宽度和高度由内容决定,与其他元素共占一行但是可以设置宽高等,例如:img标签等。
绝对定位和相对定位
position:absolute;
绝对定位,是相对于元素最近的已定位的父元素,如果没有已定位的父元素,就相对于body元素进行定位。
position:relative;
相对定位,是相对于元素在文档中的初始位置进行定位。
flex布局
通过display:flex使该元素的子元素脱离文档流,进行一些其他的操作。
align-item: 定义元素横向排列规则
flex-direction: 定义元素纵向排列规则
justify-content: 定义在主轴上的对齐方式
BFC
BFC格式化上下文,就是创作一个独立的渲染区域,让处于BFC内部的元素和外部相互隔离,使内外元素不受影响。
如何产生BFC
最有效的一种方式就是将元素的性质改变为行内块元素(display:inline-block),还可以通过position:absolute/relative进行产生bfc。
最不推荐的一种方式就是给元素添加边框,但是这种方式相比而言最简单。
BFC的作用
- 上外边距重叠,也就是margin塌陷问题
- 浮动引起的高度坍塌
- 文字环绕图片问题,左边图片右边文字。
水平垂直居中
通过flex布局或者line-height属性和text-align属性进行布置
less、sass和stylus的区别
- sass和less语法严谨,stylus相对自由。因为less长得更像css,所以它学习起来更容易。
- sass和stylus都具有类语言的逻辑方式处理:条件、循环等,而less需要通过when等关键字模拟这些功能,这方面less比不上sass和stylus。
- less在丰富性以及特色上都不及sass和stylus,
link和@import的区别
- link的功能比较多,可以定义RSS,定义REL等作用,而@import只能用于加载css。
- 当浏览器解析到link标签时,页面会同步加载所引用的css,而@Import所引用的css会等到页面加载完才被加载。
- @Import需要IE5以上才能使用,link没有这个限制。
- link可以使用js动态引入,@import不行。
多行元素的文本省略号
overflow : hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 3; // 控制行数 -webkit-box-orient: vertical
重绘和回流(Repaint & Reflow)
根据浏览器渲染机制(下方浏览器部分中有解释)可以得知HTML被解析解析成了DOM,CSS被解析成了CSSOM,它俩合并产生了渲染树Render Tree。
重绘
由于节点的集合属性发生改变或者由于样式改变而不会影响布局的,称为重绘。例如outline
、visibility
、color
、background-color
等等,重绘的代价是高昂的,因此浏览器必须验证DOM树上其他元素的可见性。
回流
回流是布局或几何属性发生改变就称为回流。回流是影响浏览器性能的关键因素,因为其变涉及到部分页面(或是整个页面)的布局更新。一个元素的回流可能会导致其素有子元素以及DOM中紧随其后的节点、祖先节点元素随后的回流。大部分的回流将导致页面的重新渲染。
回流必定会发生重绘,重绘不一定会引发回流。
JavaScript
JS的几条基本规范
- 不要再同一行声明多个变量
- 使用===/!==来比较true/false或者数值
- 使用字面量对象替代new Array这种形式
- 尽量不要使用全局变量
- Switch语句必须带有default分支
- 函数不应该有时候有返回值,有时候没有返回值
- for循环必须使用大括号
- if语句必须使用大括号
- forin循环中的变量,应该使用let关键字声明确定作用域,从而避免作用域污染。
JS引入方法
行内引入
直接在标签的方法中执行js语句。
内部引入
在script标签中编写js代码,并执行
外部引入
通过script的src属性指向一个js文件的路径进行引入使用
JS的基本数据类型
string、number、boolean、undefined、null、symbol六种基本数据类型。
数组操作
循环
map函数、forEach函数、for循环、forin循环、forof循环。
过滤
filter函数
查找
对于一些简单函数可以直接使用in
关键字查找,返回值为boolean。
let p = [1, 2, 3]; console.log(1 in p) // true
includes函数:查找匹配项,如果有返回true,没有返回false。
indexOf函数:正序查找匹配数据,返回下标。
lastIndexOf函数:倒叙查找匹配数据,返回下标。
some函数:有匹配项返回true时,整体为true。
every函数:有匹配项返回false时,整体为false。
添加
push函数:在数组的末尾添加一条数据。
unshift函数:在数组的开头添加一条数据。
删除
pop函数:在数组的末尾删除一条数据。
shift函数:在数组的开头删除一条数据。
slice(start, end)函数:根据参数截取数组,并返回一个新数组,不会影响原来的数组。
splice(start, end, value....)函数:根据参数删除数据并替换数据,返回当前操作后的数组,会影响原来的数组。
排序
sort函数:根据回调函数的具体逻辑将数组进行排列顺序。
reverse函数:将数组按照现在的数据顺序进行反转。
其他
concat函数:合并两个数组。
reduce函数:在回调函数中对数组的每一条数据进行操作。
JS内置对象
Object是JavaScript中所有对象的父对象。
数据封装对象:Object、Array、Boolean、Number和String。
其他对象:Function、Arguments、Math、Date、Error、RegExp等。
闭包
一个函数中包含了另一个函数,其中的函数使用到了第一个函数的变量,那么其中的函数就叫做闭包。
闭包就是能够读取其他函数内部变量的函数。
特征:
- 函数内部再嵌套一个函数。
- 内部函数可以引用外层的参数和变量。
- 参数和变量不会被垃圾回收机制回收。
闭包的理解
使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点shi闭包会常驻内存,增大内存使用量,容易造成内存泄漏。在js中,函数即闭包,只有函数才会产生作用域的概念。
闭包最大的好处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中。
js中的同步和异步
因为JavaScript是单线程编程语言,所以同步和异步并不是主线程执行同步代码,再开一个线程执行异步代码,而是通过消息队列和事件循环的方式来进行同步和异步的划分。
当js执行同步代码时,一个任务执行完才会去执行下一个任务,如果前一个任务耗时很长,那么后一个任务不得不一直等着,这样就会堵塞代码执行。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行下一个任务。
异步任务指的是,不进入主线程、而进入消息队列的任务,只有等主线程任务执行完毕,任务队列开始通知主线程,请求执行任务,该任务才会进入主线程执行。
异步运行机制
- 所有同步任务都在主线程上执行,形成一个执行栈。
- 主线程之外,还存在一个"消息队列"。只要异步任务有了结果,就在"消息队列"之中放置一个事件。
- 一旦"任务栈"中的所有同步任务执行完毕,系统就会读取"消息队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
JavaScript的运行机制
简单来说就是同步任务和异步任务的组合。
主线程先执行同步任务,当执行完毕同步任务时,去读取"消息队列",并执行消息队列中的异步任务和其他事件,并且一直重复这个过程(只要主线程中的同步任务为空,就去读取"消息队列")。
注意:
"消息队列"中除异步任务外,还有IO设备的事件以及一些用户产生的事件(比如鼠标点击、页面滚动等等),这些相对耗时的操作。只要指定过这些事件的回调函数,这些事件发生时就会进入"消息队列",等待主线程读取。
JS作用域和作用域链
在js中作用域分为全局作用域和局部作用域。
全局作用域可以在程序的任何地方都能被访问,比如window对象的内置属性就具有全局作用域。
局部作用域只有在当前作用域内才能被访问,否则会报错"xxx is not defined"。
全局作用域可以通过var关键字来声明,也可以在程序的最外部通过let关键字声明。
局部作用域可以在程序的某个代码块中通过let关键字声明。
在使用变量的过程中,会先在当前作用域查找是否定义了这个变量,如果找不到,就会向上级作用域去查,直到查到全局作用域,这个查找的过程叫做作用域链。
原型和原型链
在js中有一句话万物皆对象,每个对象都会在其内部初始化一个prototype属性,这个属性就是原型,当我们访问一个方法或者属性的时候,如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又有自己的prototype,这个样一直找下去,直到找到Object内建对象,这个过程叫做原型链。
函数是一个另类,它具有两个原型,一个prototype,一个proto。
继承
js中的继承分为两种,一种是es6的class类的继承,一种是构造函数的继承。
class类继承
es6中的类继承比较方便,可以直接通过extends
关键字实现继承,并且是直接继承原型及类中的构造方法。
class f{ obj = { a: 1, b: 2 }, m: function(){ console.log(this.obj.a + this.obj.b); } } class n extends f{}
es5构造函数继承
在js的es5版本中的继承是基于原型链的继承,并且必须是继承了原型属性、原型方法及原型才能算是真正的继承成功。
高阶函数
在javascript中,函数的参数可以接收变量,所以一个函数作为另一个函数的参数的这种形式,称为高阶函数。
javascript中具有回调函数的操作数组的方法都是高阶函数。
函数柯里化
js中的柯里化就是一个函数返回另一个函数,最后在调用的时候将函数的参数拆分调用。
简单的柯里化函数:
// 简单函数 function sum(a, b){ return a + b; } // 柯里化后 function curry(a){ return function(b){ return a + b; } } // 调用 sum(1, 2); curry(1)(2);
组件和模块化
当页面代码量过大,逻辑太多或者同一个功能组件在很多页面均有使用,维护起来相当麻烦,这个时候,就需要组件化开发来进行功能拆分、组件封装,达到组件通用性,增强代码得可读性,降低维护成本。
组件化开发的优点
很大程度上降低系统各个功能得耦合性,并且提高了内部功能得聚合性。这对前端工程化及降低代码得维护来说,是有很大的好处的,耦合性的降低,提高了系统的伸展性,降低了开发的复杂性,提升开发效率,降低开发成本。
- 专一:一个组件负责一个功能。
- 可配置性:可通过外部传入数据进行组件内部的数据改变。
- 标准性:必须符和组件的特点(功能拆分、组件封装、组件通用)。
- 复用性:可以多次使用。
- 可维护性:一个完整的组件,必须通过各个小组件来实现,如果后期出现问题可以更快速的定位出现问题的组件。
模块化
出现模块化这个概念的原因是因为早期版本的js没有块级作用域、没有类、没有包、也没有模块,这样就带来了复用、依赖、冲突、代码组织混乱等一系列的问题,也就出现了模块化这一概念。
模块化的好处
- 避免变量冲突,命名污染。
- 提高代码复用率。
- 提高了可维护性。
- 方便依赖关系管理。
模块化的几种方法
- 函数封装。
- 立即执行函数表达式。
图片的预加载和懒加载
- 预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。
- 懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求书。
两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。预加载会增加服务器前端压力,懒加载对服务器有一定的缓解压力。
mouseover和mouseenter的区别
mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。对应的移除事件为mouseout。
mouseenter:当鼠标移除元素本身(不包含子元素)会触发事件,也就是不会冒泡,对应的移除事件是mouseleave。
解决异步回调地狱
通过promise、generator、async/await都可以解决。
对this对象的理解
this总是指向函数的直接调用者(并非简介调用者)。
如果有new关键字,this指向new出来的那个对象。
在事件中,this指向触发这个事件的对象,特殊的是,IE中的attachEvent中的this总是指向全局对象Window。
VUE
Vue生命周期
Vue实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载DOM元素=>渲染、更新=>渲染、卸载等一系列过程,就是Vue的生命周期。
Vue的生命周期中有很多事件钩子,可以通过这些事件钩子更好控制整个Vue实例的渲染和其中的各种逻辑。
Vue生命周期总共有几个阶段
总共有8个阶段:
创建前(beforeCreate) => 创建后(created) => 载入前(beforeMount)=>载入后(mounted)=>更新前(beforeUpdate)=>更新后(updated)=>销毁前(beforeDestroy)=>销毁后(destroyed)
DOM元素渲染在mounted中就已经完成了。
每个生命周期适合的场景
beforecreate : 可以在这加个loading事件,在加载实例时触发
created : 初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用
mounted : 挂载元素,获取到DOM节点
updated : 如果对数据统一处理,在这里写上相应函数
beforeDestroy : 可以做一个确认停止事件的确认框
nextTick : 更新数据后立即操作dom
v-show和v-if的区别
v-show是通过切换元素的css属性,v-if是dom的销毁和重新创建。
使用频繁切换时应该用v-show,使用较少时用v-if。
开发中常用的指令
v-html、v-model、v-bind、v-on、v-show、v-if、v-else-if、v-else等等。
动态化样式属性的方法
对象方法:v-bind:class="{'origin': isRipe, 'green': isNotRipe}"。
数组方法:v-bind:class="[class1, class2]"。
行内绑定:v-bind:style="{color: color, fontSize: fontSize + 'px'}"。
组件通信
父组件向子组件传值
在父组件中直接为子组件绑定自定义属性传递数据,子组件通过props接收。
父组件模板:
<template> <Child :msg="message" /> </template> <script> export default{ template: { Child }, data(){ return { message: "向子组件传递的数据" } } } </script>
子组件模板:
<template> {{ msg }} </template> <script> export default{ props: { // 也可通过数组写法,不进行类型检查和参数限定 msg: { type: String, required: true } } } </script>
子组件向父组件传递数据
因为vue遵循单向数据流,在子组件中无法直接向父组件传递数据,必须通过$emit()触发父组件中的方法并将数据通过参数的方式传递过去。
父组件模板:
<template> <Child @msgFunc="func" /> </template> <script> export default{ template: { Child }, methods: { func(msg){ console.log(msg); // 打印子组件传递过来的数据 } } } </script>
子组件模板:
<template> <button @click="handleClick()">触发父组件的方法,并传递过去数据</button> </template> <script> export default{ data(){ return { message: "子组件的数据" } }, methods: { handleClick(){ this.$emit("msgFunc", this.message); // 此处第二个参数有可能会不生效,推荐重定向下this指向。 } } } </script>
兄弟组件之间传值
兄弟组件之间传值可以通过Bus通信,或者vuex完成。
实例化一个vue实例作为Bus媒介,将该实例挂载到Vue原型链上方便调用,并通过$emit()和$on进行触发方法和传递数据。
main.js文件:
import Vue from "vue"; let Bus = new Vue(); Vue.prototype.$Bus = Bus; new Vue({...});
组件1:
<template> {{ message }} </template> <script> export default{ data(){ return { message: "" } }, mounted(){ this.$Bus.$on("on", (msg) => { // 通过$on事件相应$emit事件 this.message = msg; }); } } </script>
组件2:
<template> <button @lick="toBus()">触发on方法,并传递数据</button> </template> <script> export default{ data(){ return { msg: "兄弟组件中的数据" } }, methods: { toBus(){ this.$Bus.$emit("on", this.msg); } } } </script>
Vue路由跳转的方式
- 通过<router-link>标签进行跳转。
- 通过this.$router.push()函数式跳转。
MVVM什么意思
MVVM,按从左到右的顺序,M代表Model数据模型,V代表View视图,VM代表ViewModel视图和数据的桥接器。
computed和watch有什么区别
computed:
- computed是计算属性,也就是计算值,它更多用于计算值的场景。
- computed具有缓存性,computed的值在getter执行后是会缓存的,只有在它依赖的属性值改变之后,下一次获取computed的值时才会重新调用对应的getter来计算。
- computed适用于计算比较消耗浏览器性能的计算场景。
watch:
- 更多的是观察的作用,在数据进行改变后执行的一些操作,类似于数据的监听回调。
- 没有缓存性,页面重新渲染时值不发生变化也会执行。
当我们需要进行数值计算,并且依赖于其他数据时,就使用computed。如果只是单纯的监听数据变化进行回调事件,那么就使用watch。
Vue中的key有什么作用
key是Vue中节点的唯一标识符,可以使我们的diff操作更准确、快速。
如果不加key,vue会选择复用节点(Vue的就地更新策略),就会导致之前的节点的状态被保留下来,使节点更新不准确,并且不利于被Map数据结构充分利用。
组件中的data为什么是一个函数
vue组件中data是一个函数并返回一个对象,而Vue实例中的data是一个对象。
这是因为在js中对象不具有作用域,而函数具有作用域。vue组件是用来复用的,必须是每个组件的作用域相互隔离的,不会相互影响,而new Vue()实例是不会被复用,因此不存在引用对象问题。
nextTick()
在下一次DOM更新循环结束之后延迟执行回调。在修改数据之后,立即使用的这个回调函数,获取更新后的DOM。
vm.smg = "数据"; // DOM还未更新 Vue.nextTick(() => { // DOM更新 });
Vue插槽
vue中拥有具名插槽、匿名插槽和作用域插槽三种,三种插槽都是通过<slot>标签进行分发内容,但作用域插槽是通过slot-scope属性进行分发内容。
具名插槽拥有name属性对父组件传过来的数据进行匹配并分发内容。
Vue的使用注意事项
可以看下这篇博客,里面讲的优化非常好Vue项目性能优化
vue-router导航守卫
在vue-router中可以通过添加meta属性对路由添加原信息进行路由权鉴,并且可以在vue-router的路由钩子中进行路由的重定向。
vue-router的路由钩子有三种:
第一种:全局导航钩子,router.beforeEach((to, from, next) = {}),在全局路由跳转之前进行拦截。
第二种:组件内的钩子。
第三种:单独路由独享钩子。
vuex
vuex是vue的状态管理组件,其中的state就是数据源存放地,对应一般vue对象里面的data。
state中存放的数据是响应式的,vue组件从store读取数据,若是store中的数据发生改变,依赖该数据的组件也会发生更新。
可以通过mapState把全局的state和getters映射到当前组件的computed计算属性。
vuex有5中属性:分别是state、getter、mutation、action、module。
state
Vuex使用单一状态树,即每个应用将仅仅包含一个store实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。
mutation
在其中定义用于动态修改Vuex的store中的数据状态的方法。
getter
相当于vue的计算属性,主要用来过滤一些数据。
action
actions中定义的方法就是将mutation中的定义的处理数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。vue组件中通过store.dispatch()来分发action,也可以通过mapAction将action中的方法映射进methods中。
vuex一般用于中大型web单页应用中对应用的状态进行管理,对于一些组件间关系较为简单的小型应用,使用vuex的必要性不是很大,因为完全可以用组件props属性或者Bus事件来完成通信,vuex更多地用于解决跨组件通信以及作为数据中心集中式存储数据。
React
虚拟DOM
虚拟DOM相当于真实DOM的一个类对象,当应用的状态发生改变时,会记录新的虚拟DOM树并于原先旧的虚拟DOM树进行比较,再把其中发生的改变更新到真实DOM树上。
虚拟DOM的优点
- 保证性能下限:虚拟DOM可以经过diff找到最小差异,然后批量进行patch,这种操作虽然比不上手动优化,但是比起粗暴的DOM操作性能要好很多,一次虚拟DOM可以保证性能下限。
- 无需手动操作DOM:虚拟DOM的diff和patch都是在一次更新中自动进行的,开发者无需手动操作DOM,极大提高了开发效率。
- 跨平台:虚拟DOM本质上是js对象,而DOM与平台强相关,相比之下虚拟DOM可以进行更方便的跨平台操作,例如服务器渲染、移动端开发等等。
虚拟DOM的缺点
- 无法进行极致优化:在一些性能要求极高的应用中虚拟DOM无法进行针对性的机制优化,比如VScode采用直接手动操作DOM的方式进行极端的性能优化。
React的生命周期
React 16之后有三个生命周期被废弃,但没有移除,所以还是可以使用,不过官方并不推荐这样:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
目前React16.8+的生命周期分为三个阶段,分别是挂载阶段、更新阶段、卸载阶段:
挂载阶段
- constructor: 构造函数,最先被执行,通常在构造函数中初始化state对象和为自定义方法绑定this。
- getDerivedStateFromProps: 如果我们接收到新的属性想去修改我们的state,可以使用此方法。
- render:render函数是纯函数,只返回需要渲染的东西,不应该包含其他业务逻辑,可以返回 原生的DOM、React组件、Fragment、Portals、字符串和数字、Boolean和null等内容。
- componentDidMount:组件装载之后调用,此时我们可以获取到DOM节点并操作,比如对canvas和svg的操作,服务器请求,订阅都可以写在这里,但是记得在componentWillUnmount中取消订阅。
更新阶段
- getDerivedStateFromProps: 此方法在更新个挂载阶段都可能会调用。
- shouldComponentUpdate:
shouldComponentUpdate(nextProps, nextState)
,有两个参数nextProps和nextState,表示新的属性和变化之后的state,返回一个布尔值,true表示会触发重新渲染,false表示不会触发重新渲染,默认返回true,我们通常利用此生命周期来优化React程序性能。 - render: 更新阶段也会触发此生命周期。
- getSnapshotBeforeUpdate:
getSnapshotBeforeUpdate(prevProps, prevState)
,这个方法在render之后,componentDidUpdate之前调用,有两个参数prevProps和prevState,表示之前的属性和之前的state,这个函数有一个返回值,会作为第三个参数传给componentDidUpdate,如果你不想要返回值,可以返回null,此生命周期必须与componentDidUpdate搭配使用。 - componentDidUpdate:
componentDidUpdate(prevProps, prevState, snapshot)
,该方法在getSnapshotBeforeUpdate方法之后被调用,有三个参数prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的,如果触发某些回调函数时需要用到 DOM 元素的状态,则将对比或计算的过程迁移至 getSnapshotBeforeUpdate,然后在 componentDidUpdate 中统一触发回调或更新状态。
卸载阶段
- componentWillUnmount:当我们的组件被卸载或者销毁了久会调用,我们可以在这个函数里去清除一些定时器,取消网络请求,清理无效的DOM元素等垃圾清理工作。
React应用中的请求应该放在哪个声明周期中
如果将请求放在componentWillMount中可以提前进行异步请求,避免白屏,但是当React渲染一个组件时,他不会等待componentWillMount完成任何事情,React会继续执行render事件,没有办法暂停渲染以等待数据的到达。
而且在服务器渲染时,如果在componentWillMount中获取数据,fetch data会执行两次,一次在客户端一次在服务端,这造成了多余的请求,其次,在React 16进行React Fiber重写后,componentWillMount可能在一次渲染中多次调用。
官方推荐在componentDidMount中进行。如果有特殊需求需要提前请求,也可以在constructor中请求。
setState是同步还是异步的
setState
有时会同步,有时会异步
setState
只在合成事件和钩子函数中是异步的,在原生事件和setTimeout
中都是同步的。setState
的异步并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立即拿到更新后的值,形成了所谓的异步,当然可以通过第二个参数setState(partialState, callback)
中的callback
拿到更新后的结果。setState
的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState
,setState
的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState
多个不同的值,在更新时会对其进行合并批量更新。
React组件通信
React中的组件通信和Vue中组件通信差不多,都分为父子组件通讯和其他组件通讯。
父子组件通讯
- 父组件向子组件传递数据:父组件通过自定义属性向子组件直接传递数据,子组件通过props接收。
- 子组件向父组件传递数据:父组件将自定义方法传递到子组件中,子组件通过props接收并传递函数参数调用。
其他组件通讯
- React中组件通讯都可以通过
Context
进行数据传递,Context
设计的目的是为了共享那些对于一个组件树而言是全局的数据。 - 发布订阅模式:发布者发布事件,订阅者监听事件并作出反应。我们可以通过引入event模块进行通信。
- 全局状态管理:借助Redux等全局状态管理工具进行通信,这种工具会维护一个全局状态中心Store,并根据不同事件产生新的状态。
ES6
var、let、const的区别
var声明的变量不具有块级作用域,并且可以重复声明变量,声明后的变量会与window相映射,可以在window对象中查找到,并且var声明的变量具有变量提升,可以先使用后声明。
let声明的变量具有块级作用域,在同一个作用域内不可以重复声明名称相同的变量,并且let声明的变量不具有变量提升,必须先声明后使用。
const声明的是常量,一旦声明赋值过后就无法改变常量的值,并且支持块级作用域。
解构赋值
可以通过数组或对象的形式将数组或对象中的单个数据获取出来。
// 对象解构 let {a, b} = {a: 1, b: 2}; // 数组解构 let [c, d, e] = [1, 2, 3];
还可以将函数的参数通过对象解构的方式一一对应。
function personInfo({name, age, address, gender}){ console.log(arguments === {name, age, address, gender}); // true console.log(name, age, address, gender); } personInfo({gender: 'man', address: 'changsha', name: 'william', age: 18});
forEach、for in、for of的区别
forEach更多的是用来遍历数组。
for in一般常用来遍历对象或json
for of数组对象都可以遍历,遍历对象需要通过和Object.keys()配合
for in循环出来的是key,for of循环出的是value。
箭头函数
箭头函数又称为lambda表达式,是一种简化函数定义的语法糖。
箭头函数可以省略参数那里括号和包裹执行语句的大括号,表示直接返回执行语句。
let example = value => console.log(value);
注意:
- 箭头函数中的this不执行window对象,而是它的父级,就是谁调用它执行谁。
- 不能使用arguments对象获取函数的参数。
- 不能用作构造函数,就是说不能使用new命令,否则会抛出一个错误。
- 不可以使用yield命令,因此箭头函数不能用作Generator函数。
Set和Map
Set用于数据重组,Map用于数据存储。
Set
- 成员不能重复
- 只有值没有键,类似数组
- 可以遍历,方法有add、delete、has
Map
- 本质上是键值对的集合,类似对象
- 可以遍历,可以跟各种数据格式转换。
Promise对象
Promise是一个构造函数,可以通过new关键字声明一个promise函数,它具有三种状态,并且调用它其中一个状态成功时就会凝固在这个状态上,不发生改变。
在Promise函数中可以通过then来调用它成功的状态,catch调用它失败的状态,finaly调用执行结束的状态。
let promise = new Promise((resolve, reject) => { if(true){ resolve(...); }else{ reject(...); } }); promise.then((...) => { ... }).catch((...) => { ... });
AJAX相关
get请求传参
HTTP协议并没有规定GET/POST的请求长度限制,对get请求参数的限制是来源与浏览器或者web服务器,浏览器或者web服务器限制了url的长度,并且,有以下几点:
- HTTP协议并未规定GET和POST的长度限制。
- GET的最大长度显示是因为浏览器和web服务器限制了URL长度。
- 不同的浏览器和web服务器限制的最大长度不一样。
- IE浏览器限制的最大长度大约为2M,chrome浏览器限制的最大长度大约为8M。
get请求和post请求的区别
- 最根本的区别在于传递数据的方式不同,get请求传递的数据会在url中显示,而post请求会被放进虚拟载体中,用户看不见传递的数据。
- get请求通常是获取数据,而post请求是向后端传递数据并获取一定的回应。
同步和异步的区别
同步和异步最根本的区别在于,同步会阻塞代码执行,但是异步不会。
同步相当于只有一条车道,一条车走完过后下一辆车再走,如果中间出现了问题,久会发生阻塞现象。
异步可以理解为有很多条通道,好几辆车同时在不同的车道上行驶,并不会阻塞。
所有的ajax请求都是异步的。
ajax的优缺点
优点
- 无刷新更新数据(在不刷新页面的情况下维持与服务器的通信)
- 异步与服务器通信(使用异步的方式与服务器进行通信,不打断用户的操作)
- 前端和后端负载均衡(将一些后端的工作交给前端,减少服务器与后端的负担)
- 界面和应用相分离(ajax将界面和应用分离也就是数据与呈现相分离)
缺点
- ajax不支持浏览器back按钮
- 安全问题,ajax暴露了与服务器交互的细节
- 对搜索引擎的支持比较弱
- 破坏了back与history后退按钮的正常行为等浏览器机制
如何解决跨域问题
跨域的概念:协议、域名、端口都相同才是同域,否则都是跨域。
- 使用jsonp技术,通过img标签或script标签天生支持跨域的场景请求后端数据。
- cors策略,让后端配置后可跨域访问。
- 配置代理,通过服务器的文件能访问第三方资源。
TCP/IP协议族
tcp协议是以太网协议和ip协议的上层协议,也是应用层协议的下层协议。
TCP协议的主要内容就是三次握手及四次放手
。
- 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
- 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
- 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
- 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
- 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗ *∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
- 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
上面的几个步骤可以用一张图来解释:
浏览器相关
浏览器的渲染原理
- 首先解析收到的HTML文档,根据文档定义构建一颗DOM树,DOM是由DOM元素及属性节点构成。
- 然后对CSS和引入的CSS文档进行解析,生成CSSOM规则树。
- 根据DOM树和CSSOM规则树构建Render Tree(页面渲染树)。渲染树的节点被称为渲染对象,渲染对象是一个包含有颜色和大小等属性的矩形,渲染对象和DOM对象相对应,但这种对应关系不是一对一的,不可见的DOM元素不会被插入渲染树中。
- 当渲染对象被创建并添加到树中,他们并没有位置和大小,所以当浏览器生成渲染树以后,就会根据渲染树来布局(也可以叫回流)。这一阶段浏览器要做的事情就是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为"自动重排"。
- 布局阶段结束后是绘制阶段,比那些渲染树并调用对象的paint方法将它们的内容显示在屏幕上,绘制使用UI基础组件。
渲染引擎并不会等到所有的HTML解析完成之后再去构建和布局Render Tree,而是解析一部分就构建一部分,相当于热更新一样。
从浏览器地址栏输入url到显示页面的步骤
- 浏览器根据请求的
URL
交给DNS
域名解析,找到真实IP
,向服务器发起请求。 - 服务器交给后台处理完成后返回数据,浏览器接收文件(
html、js、css
、图像等等)。 - 浏览器对加载到的资源(
html、css、js
等)进行语法解析,建立相应的内部数据结构(如html
的DOM
)。 - 载入解析的资源文件,渲染页面,完成。