目录
HTML&CSS
Meta标签
meta 标签由多个属性的参数确定,content表示参数对应的参数值
属性
- charset:定义文档的字符编码
- name
-
- keywords:页面关键词
- description:页面的概括描述
- robots:告诉搜索机器人哪些页面需要索引,哪些页面不需要索引,它的content参数值有all可以被检索且链接可以查询,none不能被检索,index文件可被检索
- viewpoint:视频移动端,控制视口的大小和比例,content的参数值包括视口的宽高和缩放比例等
- http-equiv属性:相当于http的文件头作用,它可以向浏览器传回一些有用的信息,以帮助正确和精确地显示网页内容
作用:提高网站的SEO,可以通过对Keywords和description的设置来提高网站的搜索点击率
HTML5新特性
语义化标签
结构清晰便于阅读,便于开发和维护,有利于seo,提升了用户体验
- header:定义页面的介绍展示区域,通常包括网站logo、主导航、全站链接以及搜索框
- nav:导航栏,对文档中重要的链接群使用
- main:页面主要内容,一个页面只能使用一次
- article:定义页面独立的内容,它可以有自己的header、footer、sections等
- section:定义文档中的节
- aside:定义与主要内容相关的模块。通常显示为侧边栏
- footer:文档的底部区域,通常包含文档的作者,著作权信息,链接的使用条款,联系信息等
媒体标签:视频video,音频<audio>
表单新增属性:如文本提示属性
新增input类型:如指定输入的内容必须是邮箱格式或日期格式
dom查询操作:document. querySelector返回html中匹配的第一个元素或document. queryAllSelector返回html中匹配的所有元素
CSS
选择器
隐藏元素的方法
- display: none,直接彻底消失,不会占用空间,无继承性;元素本身占有的空间就会被其他元素替代,会导致浏览器的回流和重绘
- visibility: hidden,仅仅是隐藏该元素,还占据页面空间且样式都在,但无法响应点击事件,会产生重绘但不会回流
- opacity: 0,让元素透明度为0使其不可见,占据页面空间,可以响应点击事件
- overflow:hidden,该元素的内容若超出了给定的宽度和高度属性,那么超出的部分将会被隐藏不占有位置
- position: absolute,通过使用绝对定位,设置足够大的偏移量将元素移除可视区域内
CSS3新特性
新增选择器
- 属性选择器
- 伪类选择器:根据文档结构来选择元素,常用于根据父级选择器选择里面的子元素,在元素选择器上加⼊伪类改变元素状态
- 伪元素选择器:利用CSS创建新标签,在内容元素的前后插⼊额外的元素或样式,但是这些元素并不在Dom⽂档中,只在外部显示可⻅,如::before 、::after,从而简化HTML结构
过渡属性:transition是一个过渡的效果没有中间状态,它的实现需要触发事件(⽐如光标经过或点击)才执行,需要设置开始帧和结束帧
动画属性:nimation是一个动画效果,不需要触发事件就可以⾃⼰执⾏,且可以设置循环等属性。可以设置多个关键帧完成动画可以在任意一个中间帧设置状态
圆角属性:border-radius 可接收1到4个值,指定每个角相应的弧度
边框属性:border-radius:创建圆角边框;box-shadow:为元素添加阴影
背景色渐变
文字和盒子阴影
grid和flex布局
水平居中
- 行内元素:给父元素设置 text-align: center;(如果父元素不是块级元素,这要先转化为块级)
- flex布局:display: flex; justify-content:center; align-items:center
垂直居中
- 利⽤绝对定位,先将元素的左上⻆通过top:50%和left:50%定位到⻚⾯的中⼼,然后再通过translate来调整元素的中⼼点到⻚⾯的中⼼/若盒子宽高已知也可通过margin-top和margin-left为盒子宽1高一半的负值来调整元素的中⼼点到⻚⾯的中⼼
- 使⽤flex布局,通过align-items:center和justify-content:center设置容器的垂直和⽔平⽅向上为居中对⻬,然后它的⼦元素也可以实现垂直和⽔平的居中,适用于移动端布局
- 利用绝对定位+margin:auto
position定位属性
- absolute:生成绝对定位的元素,相对于static定位以外的一个父元素进行定位。为 absolute 设置偏移值 top、left,如果有父元素设置了 position:relative/absolute/fixed ,就以父元素元素为基准定位,如果没有就以浏览器定位
- relative:生成相对定位的元素,相对于其原来的位置进行定位。元素的位置通过left、top、right、bottom属性进行规定。
- fixed:生成绝对定位的元素,指定元素相对于屏幕视⼝(viewport)的位置来指定元素位置。元素的位置在屏幕滚动时不会改变,⽐如回到顶部的按钮⼀般都是⽤此定位⽅式。
- static:默认值,没有定位,元素出现在正常的文档流中,会忽略 top, bottom, left, right 或者 z-index 声明,块级元素从上往下纵向排布,⾏级元素从左向右排列。
移动端适配(响应式设计)
定义:网页能够兼容不同的设备宽高,网页布局会根据不同视口来动态调整
原理:通过媒体查询检测不同的设备屏幕尺⼨做处理,⻚⾯头部必须有mate声明的 viewport才能兼容
动态em/rem
概念:em相对于父元素字体大小的单位,rem是一个相对于页面根元素字体大小的单位,它的大小是会随着根元素html的font-size的改变而改变的,根据不同屏幕尺寸设置不同根元素html的字体大小,它随视口大小等比缩放
方法:flexible.js是淘宝为了适配移动端开发的JS插件,使不同的终端设备都能实现页面匹配,它根据不同的宽度给网页中html根节点设置不同的font-size,然后所有的px都用rem来代替这样就实现了不同大小的屏幕都适应相同的样式了
viewpoint
概念:vw表示相对于视图窗口的宽度,vh表示相对于视图窗口高度;1vw /h= 1/100视口宽度/高度
使用情况:移动端和PC端可用同一套代码,适用于简单网页如企业官网
媒体查询
概念:在css文件中针对不同的媒体类型或不同的屏幕尺寸设置不同的样式,重置浏览器大小的过程中页面也会根据宽高重新渲染页面
媒体类型:all: 在所有设备都加载,print 在打印模式下加载,screen 电脑屏幕,平板电脑,智能手机等中加载
原理:如果媒体查询中指定的媒体类型匹配展示文档所使用的类型且表达式都为true ,则媒体查询内的样式将会生效
两栏布局
概念:左边一栏宽度固定,右边一栏宽度自适应,且左右两边不能重叠
方法
- 使用 foat 左浮左边栏
- 右边模块使用 margin-left 撑出内容块做内容展示
- 为父级元素添加BFC,防止下方元素飞到上方内容左右两栏宽度固定,中间自适应的布局
三栏布局
概念:左右两栏宽度固定,中间⾃适应的布局
方法
- 利用浮动,左右两栏设置固定大小,并分别设置左右浮动。中间一栏设置左右两个方向的margin值
- 利用绝对定位,左右两栏设置为绝对定位,中间设置对应方向大小的margin的值
- 利用flex布局,左右两栏设置固定大小,中间一栏设置为fex:1
Flex布局
定义
灵活高效地实现居中、对齐等有规律的弹性布局;采用flex布局的元素称为flex容器,容器内的子元素称为项目
容器属性
- flex-direction属性决定主轴的方向
- flex-wrap 决定容器内项目是否可换行
- justify-content属性定义了项目在主轴上的对齐方式
- align-items属性定义项目在侧轴上如何对齐
- align-content属性定义了多根轴线的对齐方式
项目属性
- flex-grow属性定义项目的放大比例,默认为0
- flex-shrink属性定义了项目的缩小比例,默认为1
- flex-basis属性定义了元素在主轴上的初始尺寸
- flex属性为以上三个的简写,默认为0 1 auto
flex:1 表示什么
自动填充满剩余空间,布局最后一个盒子时使用fex: 1便可得到整个剩余宽度
BFC
概念:bfc是块级格式化上下文,实现一个独立的容器,里面的子元素与外面的元素不会相互影响
哪些元素会生成bfc:
- 对父元素设置overflow: hidden
- 元素设置绝对定位或固定定位
- 元素设置浮动
- flex或行内块元素布局
特点
- BFC就是一个块级元素,一行只能有一个
- BFC就是页面中的一个隔离的独立容器,容器里的标签不会影响到外部标签
- 垂直方向的距离由margin控制, bfc内部的元素外边距会发生重叠,实际距离取值更大的
解决的问题
- ⾼度塌陷的问题:如对⼦元素设置浮动后,⽗元素的⾼度变为0。只需对⽗元素触发BFC,如设置溢出隐藏
- 解决margin的重叠问题:BFC是⼀个独⽴的区域,内部的元素不受外部影响,将两个元素都设为BFC,就解决了margin重叠的问题,如两个p标签之间的margin有重叠:在p外面包裹一层容器,并触发这个容器生成一个BFC,那么两个p就不属于同一个BFC
- 创建⾃适应两栏布局:左侧设置 float:left ,右侧设置 overflow: hidden 。这样右边就触发了BFC,BFC的区域不会与浮动元素发⽣重叠,所以两侧就不会发⽣重叠
实现三角形
使用 border 的边框实现
原理:主要用到的是border属性,边框是由四个三角形拼接成的;通过上下左右边框来控制三角形的方向,用边框的宽度比来控制三角形的角度,让任何三边的边框的颜色为 transparent,则可以得到各种角度的三角形
线性渐变 linear-gradient 实现
实现一个 45° 的渐变,让它的颜色从渐变色变为两种固定的颜色,再让其中一个颜色透明即可
JS
数据类型
基本数据类型 (值类型)
- 数值型number:表示整数和浮点数,不带引号
- 字符串型string:表示一个字符序列,字符串需要使用单引号或双引号括起来。
- 布尔型Boolean:取真(true)和假(false)两种数值;只有0,空字符串,null,undefined,NaN这些为false,其它值都是 true
- undefined型:声明了变量但未对其赋值
- null型:赋值了但是 内容为空
- symbol 型 :唯一标识符,表示独一无二的值
- bigInt型:可安全地存储和操作大整数一旦js中的数字超过了最大安全范围,就需要第三方库来解决
引用数据类型
- object型:是一个无序的键(key)值(value)对的集合,如数组和map、set等结构
存储位置
- 基本数据类型存储在栈:这些数据占用空间小且大小固定可直接访问和修改,需要频繁使用
- 引用类型地址存在栈区,值存在堆区 ,这些数据占用空间大且大小不固定,在堆中按优先级排序以免影响程序性能;堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用
数据类型检测方式
- typeOf:typeof会直接返回一个变量的数据类型
-
- 特殊结果:typeof null,null指针的标识符是000刚好与object相同;typeof NaN,NaN是一个警戒值但没有真实值可表示,指出数字类型中存在不是数值型的错误但它本身也是一个数值型
- instanceof:用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,即判断谋对象是否为某构造函数的实例,最后返回布尔值
-
- 原理:顺着原型链去找,直到找到相同的原型对象,返回true,否则为false;以此判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置
- 缺点:可以更改原型链的指向,导致检测数据类型不准确;但不能判断基本数据类型
- constructor:constructor是原型对象的属性指向构造函数,可以检查基本数据类型,但修改原型同样可能导致错误
- Object.prototype.toString. call(thisArg):最准确的数据类型检测方式,Object.prototype.toString这个方法其实是返回当前实例所属类的信息,结构为:’[object 所属的类]’,让它里面的参数设为要检测的数据再调用call方法,即让参数执行Object.prototype.toString方法
深拷贝和浅拷贝
浅拷贝
定义:对于引用数据类型只是复制了对象的引用地址,新旧对象还是共享同一块内存,修改其中一个对象的值另一个对象的值也会改变
方法:Object.assign、Array.prototype.slice(),、Array.prototype.concat()
深拷贝
定义:不但拷贝了指针而且还拷贝了数据即为拷贝的对象开辟了一片新内存空间,两个对象属性相同但对应两个不同地址,修改新对象不会影响原对象
方法:构造一个深拷贝函数通过递归调用的方式拷贝每一层;借用JSON对象的stringify方法和parse方法来分别实现序列化和反序列化
区别
对于基本数据类型两者都是直接复制数据值,区别在于引用数据类型的复制
- 浅拷贝两个变量的指向是同一个地址,当原对象或拷贝对象发生改变时另一对象的值也会跟着变化 ;深拷贝两个变量指向不同地址,在引用类型中改变复制对象的值也不会影响原对象
- 深拷贝相比于浅拷贝速度较慢并且开销较大
- 浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行深层次地递归复制
扩展运算符和object. assigh的拷贝分析:浅拷贝;只能拷贝一层数组或对象即里面只有基本数据类型元素//因为这种拷贝只能拷贝一层而刚好可以覆盖,如果包含多层元素即含有引用类型数组或对象时的元素时除第一层外都不能拷贝
为什么0.1+0.2≠0.3
浮点数精度造成的:十进制转二进制时只能存储52位的小数,在保留53位有效数字后产生了误差;由于十进制的0.1只能在二进制中用近似0.1的数表示,再从二进制转换为十进制数时这种误差便反映到了结果中
解决办法:用tofixed方法并保存两位小数点
数据类型转换规则
显式转换
- Number(变量)将任意类型的值转化为数值
-
- undefined 转为NaN
- null转为0
- 布尔型:true1 false0
- 字符串型
-
-
- 数字值转为引号内的数值型
- 数字值转为NaN
- 空字符串转为0
-
- String(变量)可将任意类型的值转化成字符串
-
- null和undefined 布尔型的true 和false 都会带上字符串符号'null'
- 数值类型直接转化为字符串
- Boolean(变量)可将任意类型的值转为布尔值:只有0,空字符串,null,undefined,NaN这些转为false,其它值都是 true
隐式转换(自动转换)
- 自动转换成字符串:在+运算中,只要一方存在字符串,则会进行字符串拼接操作
- 自动转换成数值:除了+号的其他运算符都会把运算自动转成数值
- 自动转换为布尔值:在需要布尔值的地方如while和if的条件,就会将非布尔值的参数自动转为布尔值:undefined、null、false、NaN、""会转为false,其余都为true
map和set区别
JSON
arguments
定义:是函数的内置对象,存储了传递的所有实参;它是一个伪数组:属性是从 0 开始依次递增的数字,还有长度这些属性;但它却没有数组方法如pop和push
类数组转换为数组的方法
- 通过 call 调用数组的 slice 或splice方法来实现转换Array.prototype.slice.call(arrayLike),Array.prototype.slice表示数组的原型中的slice方法,slice方法返回的是一个Array类型的对象
- 通过 apply 调用数组的 concat 方法(与空数组合并)来实现转换Array.prototype.concat.apply([], arrayLike)
- 使用Array.from方法将类数组转化成数组
遍历方法:先转化为数组再遍历
DOM 和 BOM
DOM:DOM 指的是文档对象模型,以文档为对象定义了处理网页内容的方法和接口
常见的DOM操作有哪些
- DOM 节点的获取
-
- getElementById // 按照 id 查询
- getElementsByTagName // 按照标签名查询
- getElementsByClassName // 按照类名查询
- querySelectorAll // 按照 css 选择器查询
- DOM 节点的创建
获取父节点
创建新节点并设置内容
把新创建的节点用appendChild方法加到父节点后
- DOM 节点的删除
-
- 先获取元素再用removeChild方法
- DOM 元素的修改
BOM
BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互;它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在
变量提升和函数提升
在变量声明的前段代码中就能访问到该变量而不会报错
变量提升:声明一个变量的时候,该变量会被提升到作用域最前面,但是执行部分并不会被提升
函数提升:js代码执行之前,会把函数名提升到作用域最前面,值为undefined以函数形式来进行调用会报错,而函数执行部分不提升;把所有的函数声明提升到作用域最前面且优先级高于变量提升,但是不提升调用函数;
原理
JS的运行机制分两步操作即解析和执行:预解析是把所有的var和function提升到作用域的最前面;在执行阶段,就是按照代码的顺序依次执行
作用:声明提升可以提高性能,让函数可以在执行前预先为变量分配栈空间;提高JS代码的容错性,使一些不规范的代码也可以正常执行
es6新特性
- let和const
- symbol数据类型
- 模板字符串:在反引号内以${}的方式嵌入变量,使代码的可读性更高;空格、缩进、换行都会被保留,支持“运算”式的表达式,嵌入的变量内可完成一些计算
箭头函数
箭头函数和普通函数的区别
- 箭头函数更加简洁:如果函数体的返回值只有一句可省略大括号和return;不需要返回值可加个void
- 箭头函数没有自己的this且this指向不会改变
-
- 不会创建自己的this,只会从自己作用域链的上一层即它的父级继承this
- 对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象(即使再增加一层对象嵌套也是如此)
- 箭头函数不能作为构造函数使用,不可以对箭头函数使用new命令
- 使用call, apply, bind方法也不变改变this指向
- 不能使用arguments:可以用剩余运算符的rest参数(...args)来替代,用于获取函数的多余参数;当函数参数不确定时用rest作为形参把传入函数的参数整合成数组
字符串新增方法
- includes:判断字符串与子串的包含关系
- startsWith:判断字符串是否以某个/某串字符开头:
- endsWith:判断字符串是否以某个/某串字符结尾:
es6新增扩展
数组新增
- 扩展运算符
-
- 对象扩展运算符:(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。
- 数组扩展运算符
- 遍历方法:keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历
- 构造函数的方法
- 实例方法:fill()使用给定值,填充一个数组,new Array(3).fill(7)// [7, 7, 7]
对象新增
解构表达式
- 数组解构:以元素的位置为匹配条件来提取想要的数据
- 对象解构:以属性的名称即键名为匹配条件(严格以属性名作为定位依据,就算调换变量位置也不影响),来提取想要的数据
const stu = { name: ′Bob′, age: 24} const { name, age } = stu //变量name提取到了'bob', age提取到了24
复杂对象解构:通过冒号+{目标属性名}这种形式,进一步解构它,一直解构到拿到目标数据为止,const { classes: { stu: { name } }} = school
数组方法
增
push()将元素添加到数组末尾,返回数组的最新长度
unshift()将元素添加到数组开头,然后返回新的数组长度
splice:传入三个参数,分别是开始位置、0、插入的元素,会影响原数组
concat()首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组,不会影响原始数组
删
pop() 方法用于删除数组的最后一项,同时减少数组的length 值,返回被删除的项
shift()方法用于删除数组的第一项,同时减少数组的length 值,返回被删除的项
splice()传入两个参数,分别是开始位置,删除元素的数量,返回修改后的数组
slice(start,end):返回一个新数组(不会改变原数组),截取从start到end(不包含end)的数组
查
indexOf()返回要查找的元素在数组中的位置,如果没找到则返回 -1
includes()返回要查找的元素在数组中的位置,找到返回true,否则false
find()返回第一个匹配的元素
排序
reverse()将数组元素方向反转
sort()方法接受一个比较函数,用于判断哪个值应该排在前面
拼接方法
join()
遍历方法
-
- map()方法;不会改变原数组的值:返回一个新数组,新数组中的值为原数组调用函数处理之后的值;遍历是有序的:有内置迭代器的【symbol iterator】调用next迭代
- forEach()方法会对每个元素都执行一次提供的函数,对数据的操作会改变原数组而没有返回值:参数为一个函数,函数的形参1代表数组元素,形参2代表元素下标arr.forEach(function( item, index){ })
- for...in和for...of的区别
for…of 遍历获取的是对象的元素值,for…in 获取的是对象的key或数组,字符串的下标
for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象
filter()
过滤数组中的元素,返回包含符合条件的元素的数组
every() 和 some()
对数组中的元素检查看是否满足某一条件:some()只要有一个是true,便返回true;而every()只要有一个是false,便返回false.
find() 和 findIndex()
检查符合函数中数组元素的值:find()返回的是第一个符合条件的值;findIndex()返回的是第一个符号条件的元素下标
keys、values、entries
keys – 返回元素索引
values – 返回元素值
entries – 返回元素值和下标
reduce和reduceright
累加器会迭代数组的所有项,然后构建一个最终返回值:reduce()对数组从第一项开始遍历;reduceRight()对数组从最后一项遍历
reduce跟常用的map,forEach一样,也是用于遍历循环并且可以设置初始值
字符串的方法
增
除了常用+以及${}进行字符串拼接之外,还可通过concat用于将一个或多个字符串拼接成一个新字符串
删
substr(a,b),从下标为a的位置开始截取,一直截取到下标为b的位置
如果只有一个参数a,则从下标a开始截取至字符串末尾
substring(a,b),是从下标为a的位置开始截取,截取到下标为b-1的位置
改
trim()、trimLeft()、trimRight()删除前、后或前后所有空格符,再返回新的字符串
toLowerCase()、 toUpperCase()大小写转化
查
startWith()、includes()从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值
indexOf()从字符串开头去搜索传入的字符串,并返回下标(如果没找到,则返回 -1 )
转换方法
split('x') 方法
用于把一个字符串通过x分割成由单个字符组成的数组(但数组中的元素仍然是字符串形式),"2:3:4:5".split(":") //将返回["2", "3", "4", "5"]
JS模块化
- CommonJS规范:运行在服务端的模块化规范
-
- 使用方法:每个模块内部module变量代表当前模块,它的exports属性(即module.exports)是对外的接口;require方法引入外部模块
- 特点
变量的作用范围只在模块内,避免了全局污染
模块之间是按顺序同步执行的
模块首次执行后会缓存,再次执行时会返回缓存的结果
- ES6模块化规范:每个js文件都是一个独立的模块
import命令用于输入其他模块提供的功能
export命令用于规定模块的对外接口
- 两者区别
CommonJS是模块运行时加载,es6模块是编译时输出接口
CommonJS模块的导入是同步加载模块,es6是异步加载
CommonJS是输出值的拷贝,es6模块输出的是值的引用
new和构造函数
构造函数:把对象中公共的属性和方法抽取出来封装到这个函数里实现代码复用,它用来初始化对象,即与new一起使用创建一个新对象
实例成员和静态成员
实例成员:由构造函数创建出来对象才能调用的属性和方法
静态成员:由构造函数直接调用的属性、方法
new的执行过程
在内存中创建一个空对象
将构造函数的this绑定到新对象上(将对象与构造函数通过原型链连接起来)
执行构造函数中的代码,为新的对象添加属性和方法
返回这个新对象
原型和原型链
原型prototype
定义:每个构造函数都有一个prototype对象,这个对象的所有属性和方法都会被构造函数所拥有,函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型
意义
- 可把不变的方法或属性直接添加在prototype对象上,这样所有创建的新对象都可以共享这些方法或属性(一般把公共属性定义在构造函数里,公共的方法定义在原型对象上)
- 每创建一个实例不必再为相同的方法分配空间,解决了构造函数浪费内存的问题,增加了代码的复用性
原理
如果对象本身没有对应的属性或者方法,就会查看构造函数的原型对象prototype,如果有就会返回它的属性值或调用方法
相关属性
- 隐式原型__proto__:实例对象上的属性,指向其构造函数的原型对象prototype,正是因为有__proto__,使得对象可以访问到原型上的方法和属性并可以向上层层查找
- constructor:__proto__和prototype上都有的属性,指向创建该对象的构造函数
原型链
原理
- 由于每个prototype都会有隐式原型,如果对象本身没有对应的属性或方法,那么它就会去该对象的隐式原型上(即构造函数的prototype)查找
- 构造函数的原型对象又会有自己的隐式原型,于是就这样一级一级找下去,找到时就返回属性值或调用方法
- 直到object的原型对象的下一级则返回null查找失败:原型链上的所有原型都是对象,所有的对象最终都是由Object构造的,而Object.prototype的下一级是Object.prototype.__proto__等于null,即原型链的终点是null
所有函数都可以看做是Function()的实例,即它们的构造函数就是Function(),所有对象都是object的后代,即所有类都是object的实例对象
谁调用this,this就指向谁,没有调用者则指向window
this指向
- 作为对象的方法(函数)调用:this指向该对象
- 作为普通函数调用、匿名函数和立即执行函数、定时器函数:this指向全局对象即window(相当于是window调用了该函数)
- 构造函数(new关键字):构造函数中的this指向构造函数的实例,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象
- 事件绑定方法:this指向绑定事件的对象
- call和apply、bind:可以动态地改变this的指向
- 箭头函数:箭头函数没有自己的this,只会从自己作用域链的上一层继承this
call/apply/bind
作用:调用该函数并改变该函数内this的指向,相当于让方法中括号内的对象调用该函数;
第一个参数都是this要绑定的对象,如果没有或参数为undefined或null,则默认指向全局window
区别:
传参
- call 传入的参数数量不固定:第一个参数指定了函数体内this对象的指向,其余参数为函数调用的实参
- apply 接受两个参数,第一个参数指定了函数体内 this 指向,第二个参数是可包含多个参数的数组
- apply和call是一次性传入参数,而bind可以分为多次传入
执行时机
- apply、call 是对函数直接执行
- bind是改变this指向后不会立即执行,而是返回一个永久改变this指向的新函数,调用新函数才会执行
垃圾回收与内存泄漏
浏览器的垃圾回收机制
- 垃圾回收:JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不再运行时,就需要系统收回被占用的内存空间,这就是垃圾回收。
- 回收机制:
-
- Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放
- 全局变量的生命周期会持续要页面卸载才释放;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,占有的空间就会被释放
- 当局部变量被外部函数使用时,若存在闭包,在函数执行结束后局部变量仍在使用,所以不会回收
- 垃圾回收的方式
-
- 标记清除:当变量进入执行环境时,就标记这个变量“进入环境”,被标记为“进入环境”的变量不能被回收;变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放
- 引用计数:跟踪记录每个值被引用的次数。当声明了一个变量赋值时,引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当引用次数变为0时就被释放
减少垃圾回收的方式
虽然浏览器可垃圾自动回收,但是当代码比较复杂时来的代价比较大,所以应该尽量减少垃圾回收
- 对数组进行优化: 在清空一个数组时可将数组长度设为0
- 对object进行优化: 对象尽量复用,对于不再使用的对象,就将其设置为null,尽快被回收
- 对函数进行优化: 在循环中的函数表达式,如果可以复用,尽量放在函数的外面
内存泄漏
内存泄漏是指由于疏忽造成程序未能释放已经不再使用的内存,造成系统内存的浪费,会影响程序运行
- 意外的全局变量:若变量没有声明就使用(a=10没有let声明),就会创建一个全局变量,而使这个变量一直留在内存中无法被回收;解决方式: 在js文件开头添加 ‘use strict’,开启严格模式
- 忘记取消计时器或回调函数:定时器setInterval或者setTimeout在不需要使用的时候,没有被clear,导致定时器的回调函数及其内部依赖的变量都不能被回收;解决方式:当不需要interval或者timeout的时候,调用clearInterval或者clearTimeout
- 不合理地使用闭包:导致某些变量一直被留在内存当中
- 没有清理对 DOM 的引用:获取一个 DOM 元素的引用,而后面这个元素被删除,但由于保留了对这个元素的引用,所以无法被回收
异步编程
异步概念
同步任务和异步任务
- 同步任务是指没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务
- 异步是指先挂起异步任务等后面代码完成后再继续执行,程序的执行顺序与任务的排列顺序是不一致的、异步的;异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对应的回调函数
为什么需要异步
JS是单线程的遇到耗时任务容易会堵塞,影响后续代码的执行且造成资源浪费,通过异步的事件循环机制提升效率而避免程序阻塞,即解决单线程的缺点
JS单线程
- 定义同一时间只能执行一个任务,所有操作都在同一线程
- 原因:JS主要用途是与用户互动以及操作DOM,如果不是单线程则会带来复杂的同步问题(多线程需要共享资源、且有可能修改彼此的运行结果)
- 好处:更加稳定不会产生多线程冲突,线程之间无需频繁切换造成资源浪费
- 缺点:遇到耗时任务容易会堵塞,影响后续代码的执行且造成资源浪费
- 通过webworker可开启js的多线程:html5的webworker允许javascript多线程;在原有主线程之外分了worker线程出来,但是子线程完全受主线程控制,且不得操作DOM从而避免线程冲突
事件循环机制
规则
- 遇到同步任务就直接推到执行栈中立即执行然后出栈
- 异步任务先放到一个任务队列里等待执行,异步任务分为宏任务:script代码块、定时器、点击事件、Ajax等由浏览器发起的,放在宏任务队列,以及微任务: Promise的then、catch、finally,async/await等由JS引擎发起的,放在微任务队列
执行顺序
首先直接输出执行栈中同步任务,同步任务清空时再往下调用任务队列中的异步任务
检查微任务队列,将可执行的微任务拿到执行栈中执行
微任务队列清空后再将宏任务队列中的任务拿到执行栈执行
回到第二步继续循环
异步实现方式
回调函数
把一个函数以参数的形式写到函数里,不会马上执行它而是等到重新执行这个任务的时候,再调用这个函数;回调函数易于实现、便于理解,但需要依次执行多个异步操作时会层层嵌套造成的回调地狱不利于代码维护
定时器
- setTimeout
定时器为何不准确 如何解决
并不是隔n秒后就立即执行回调函数,而是n秒后才加入宏任务队列;如果队列前面还有其他任务,则要等这些任务执行完再执行
解决方法
动态计算时差:根据定时器最开始时间计算当前时间(回调函数执行时间)与开始时间的误差,用期望时差减误差作为下一次任务的时间间隔;使用 web Worker:将定时器函数作为独立线程执行
- setInterval:作用和 setTimeout 基本一致,只是该函数是每隔一段时间执行一次回调函数
缺点:不能保证在预期的时间执行任务;如果定时器执行过程中出现了耗时操作,多个回调函数会在耗时操作结束以后同时执行,这样会降低性能
- requestAnimationFrame
会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率;自带节流功能,该函数的延时效果是精确的,没有其他定时器时间不准的问题
Promise对象
定义:Promise里面保存着某个未来才会结束的事件即异步操作的结果,是为了解决回调地狱而产生的
好处:将回调函数的嵌套,改成链式调用;提供了简洁的API,使得控制异步操作更加容易
原理:每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数;回调函数变成了链式写法,每个异步函数执行完成后,才会去执行下一个then函数但是造成了代码冗余问题
状态:
- 分类:Pending(进行中)、Resolved(已完成)、Rejected(失败)
- 特点:promise对象的状态改变,只有两种可能:从pending变为fulfilled,从pending变为rejected,没有其他状态之间的转换;且状态一旦改变就不会修改,只有异步操作的结果可以决定当前状态
基本用法
Promise作为构造函数接受一个函数作为参数使用new Promise()来创建promise对象,该函数的两个形参分别是resolve和reject分别处理成功和失败两种情况,并通过p.then获取处理结果(p. then后有两个函数,分别对应成功和失败的两个结果)
- resolve函数目的是将Promise对象状态变成成功状态,在异步操作成功时调用,Promise.resolve(value)的返回值也是一个promise对象,可以对返回值进行.then调用
- reject函数的目的是将Promise对象的状态变成失败状态,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去
- Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数
API
- then()
-
- then是接受两个回调函数作为参数,第一个参数是指定成功(resolve)的函数,第二个参数指定失败(reject)的函数;通过then拿到异步数据
- then方法返回的是一个新的Promise实例,下一个then拿到的数据是前一个then的返回值,也就是promise能链式书写的原因
- catch ()
-
- 在then结束时使用,相当于then方法的第二个参数,指向reject的回调函数
- catch方法还有一个作用,就是在任一阶段出现错误不会停止运行,而是进入catch方法中(有了catch后可以省略reject)
- finally ()
finally方法用于指定不管 Promise 对象最后状态如何,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
- all()
接收一个数组,数组的每一项都是一个promise对象。当数组中所有的promise的状态都达到resolved的时候,all方法的状态就会变成resolved,如果有一个状态变成了rejected,那么all方法的状态就会变成rejected;Promise.all可以将多个Promise实例包装成一个新的Promise实例。成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
- race()
Promise.race([p1, p2, p3])中只要有一个任务完成就得到结果,里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
- any()
与race类似,区别为any是首先拿到的成功任务将该结果返回,所有任务都不成功才返回失败,将多个 Promise 实例,包装成一个新的 Promise 实例
生成器函数 Generator
定义:当遇到异步函数执行的时候,将函数执行权转移出去,当异步函数执行完毕时再将执行权给转移回来
方法:yield表达式可以暂停函数执行,next方法用于恢复函数执行,这使得Generator函数可将异步任务按同步顺序来写,同时可以遍历对象
形式:function关键字与函数名之间有一个星号,函数体内部使用yield表达式,定义不同的内部状态
原理
- 通过next方法才会遍历到下一个内部状态//括号内还能传参
- 遇到yield就暂停执行后面的操作,并返回yield后面的那个表达式的值
- 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
- 遇到return语句时,将后面表达式的值返回;没有return则返回值为undefined,done为true
async/await
定义:async/await是Generator 的语法糖,它是为优化then链的冗余书写效果而开发出来的
async
在function前添加关键字async,用于申明一个 异步的函数,其return的任何值都会被包装成promise对象
await:await 声明一个异步函数能延迟后面代码执行;await 可以用于等待一个 async 函数的返回值,await函数执行完后再将后面代码加入微任务队列
- await接Promise对象:await会阻塞函数后面的代码,等着Promise对象执行完后得到resolve的值,作为await表达式的运算结果
- 如果promise没有resolve则不会执行后面代码
- await 接数值:自动包装成成功态的promise对象
- await 接promise 为空(promise没有返回值):面没有任何状态改变,而await一直在等待状态改变所以后面代码不会执行
- await 接 promise 为 error:报错:await相当于成功态的 .then,没有成功则不会执行后面的代码
好处:以同步的方式书写来实现异步操作,避免了then的冗余书写;async/await可以通过try/catch来捕获错误更加高效
执行上下文/作用域链/闭包
闭包
定义:闭包是指一个函数可以访问另一个函数作用域中的变量,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中
作用:
- 将函数需要用到的变量在函数作用域内声明以避免全局污染,然后通过调用闭包函数再访问到该函数内部的变量
- 使已经运行结束的函数的变量继续留在内存中,延长该变量的使用时间和作用范围
缺点:闭包会导致内存占用过高,因为变量都没有释放内存 造成内存泄漏使网页性能下降
应用场景
- 防抖和节流:通过开启一个定时器,将需要执行防抖的事件放入这个定时器中,通过闭包来将定时器缓存起来,每次触发该延时器之前都会清空上一次的延时器
- 柯里化函数:被柯里化函数包裹的函数就会具备一种能够缓存参数的能力,这样就能实现调用该函数的时候可以分别传参
为何外部函数销毁了内部的函数仍能访问到变量
闭包函数的执行上下文维护了一个作用域链,即使创建它的上下文已经销毁,依然可以通过 f 函数的作用域链找到它
作用域和作用域链
作用域
定义:变量(变量作用域又称上下文)和函数生效(能被访问)的区域
分类
- 全局作用域:
-
- 任何不在函数中或是大括号中声明的变量,都是在全局作用域下
- 未声明就直接赋值的变量为全局作用域即使它是在函数内部声明的,如a=10
- 所有window对象的属性拥有全局作用域;window.变量名=变量值
- 局部作用域(函数作用域)
-
- 函数内部声明的变量,该变量只能在函数内部使用
- 块级作用域
-
- 由{ }包裹的代码块,在代码块外部不可以使用变量;比如if{}块、for(){}块
- 大括号中使用let和const声明的变量存在于块级作用域中。只能在大括号之内访问这些变量
作用域链
定义:如果在自己作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到window对象就被终止,这查找过程的链条就是作用域链
意义:通过作用域链,可以有序地访问到外层环境的变量和函数
执行上下文
定义:在代码之前之前会先预解析:创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用
类型
- 全局执行上下文:浏览器只有一个全局对象就是 window 对象,this 指向这个全局对象,任何不在函数内部的都是全局执行上下文
- 函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文;一个函数执行之前,也会创建一个函数执行上下文环境; 相比全局上下文会多出this.arguments和函数的参数
执行栈:用于存储在代码执行期间创建的所有执行上下文
- 当JS执行第一行代码时就会创建一个全局执行上下文并且压入执行栈中
- 每当遇到一个函数调用,就会为该函数创建新的执行上下文并压入栈中
- 执行位于执行栈栈顶的执行上下文,执行完毕即从栈顶弹出然后继续执行下一个上下文
- 当所有的代码都执行完毕之后,从栈中弹出全局执行上下文
全局变量和局部变量的区别
- 作用域
全局变量的作用域为整个程序即可以在程序的任意位置访问;局部变量作用域只能在函数内部访问
- 存储方式
全局变量存储在全局数据的堆区,局部变量存储在栈区
- 生命周期
全局变量浏览器关闭才销毁,局部变量当程序执行完时就销毁更节约内存
- 声明
在函数或花括号外部声明就是全局变量;直接给变量名赋值,在函数内部还是外部都是全局变量
let, const, var的区别
- 作用域
-
- let和const具有块级作用域;var是函数作用域
- let/const声明的全局变量会保存在script的作用域内,也不会成为全局对象的属性;而var 声明的变量会成为 window 对象的属性
- 变量提升
-
- var存在变量提升
- let和const不存在,需要先声明变量才能使用变量否则会报错
- 重复声明
var可以重复声明同一变量,后面声明的会覆盖前面的
let和const不能重复声明
- 赋值
var和let可不用设置初始值,后面可对变量重新赋值
const必须设置初始值且为常量,即不允许对该变量重新赋值
- 使用
能用const的情况尽量使用const,其他情况下大多数使用let,避免使用var
const的的值可以修改吗
- const保证的并不是变量的值不能改动,而是变量指向的内存地址不能改动
- 对基本数据类型,他的值就保存在变量指向的内存地址,因此不能改变值
- 对引用数据类型如数组和对象,只能保证指向整体数据的指针不改变,而内部的数据结构可以改变如可以对数组内的某一元素重新赋值,但不能对整个数组重新赋值
防抖和节流
背景:浏览器的事件机制在触发时,会不断调用绑定在事件上的回调函数,极大地浪费资源;为了优化性能,需要对这类事件进行调用次数的限制,对此我们就可以采用 防抖 和 节流来减少调用频率
防抖
事件触发时延迟n 秒后再执行,若在 n 秒内被重复触发则重新计时,使得多次频繁触发只有最后一次生效,如防抖在连续的事件,只需触发一次回调的场景有输入结束后n秒才进行搜索请求,n秒内又输入的内容,就重新计时
节流
n 秒内只执行一次,触后n秒内将不能被再次触发,使得函数执行频率直接稀释,如节流在间隔一段时间执行一次回调的场景有搜索框的搜索联想功能
对象继承的方式
定义:继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码
方式
- 原型链继承:将父类的实例挂载到子类的原型上,根据原型链的指向,子类的实例还可以使用到父类的方法和属性
-
- 优点:子类的实例也是父类的实例 两者属性都可以继承;父类新增原型方法或属性,子类都能访问的到
- 缺点:多个实例的原型对象共享内存,修改一个会影响另一个;无法实现多继承
- 构造函数继承:在子类构造函数中调用父类构造函数(复制父类的实例属性给子类),通过在子类构造函数中使用call()方法改变this指向
-
- 优点:可以实现多继承(call多个父类对象)
- 缺点:方法都在构造函数中定义,只能继承父类的实例属性和方法,不能继承原型上的属性和方法;无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
- 组合继承:同时使用原型链继承和构造函数继承的操作:通过原型链继承原型属性和方法,通过构造函数继承父类属性
-
- 优点:结合了原型链继承和构造函数继承得优点
- 缺点:要调用两次构造函数,增加了性能开销
- 寄生组合继承:通过寄生方式砍掉父类的实例属性, 通过Object.create方法把父类的原型对象挂载在子类的原型对象上,在调用两次父类构造函数时,无需初始化两次实例的方法/属性,避免了组合继承的缺点
-
- 优点:只调用了一次父类构造函数,并且因此避免了在父类的原型上面创建不必要的、多余的属性,而且原型链能保持不变
- es6的class继承
-
- class通过extends关键字实现继承使得继承更加清晰和方便,父类的所有属性和方法都会自动被子类继承
- constructor方法:创建的每一个class类,都会有一个constructor()方法,该方法是一种用于创建和初始化class创建的对象的特殊方法--构造函数方法
- super关键字:当做函数使用时表示父类构造函数,但返回的是之类实例对象,内部this指向子类实例;作为对象时在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
- es6继承和es5继承的区别:es5实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this));es6实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this
设计模式
MVC和MVVM
MVC
MVVM
React
对react的理解
定义:react是用于构建用户界面的js库,他把项目分成了各个独立的组件,这些组件可以相互组合或嵌套并且复用,使前端开发更高效和规范
特性
- 基于jsx语法:遇到尖括号就当做HTML解析,遇到花括号就当js解析
- 组件为核心的灵活性:将程序相同的逻辑封装成一个组件,提升了可复用性和可维护性
- 单向数据流的可控性:数据主要从父节点通过props传递到子节点,如果某一级的props改变了,react会重新渲染它的子节点但不会影响到它的上层节点
- 虚拟dom交互的高效性:相比于传统dom复杂又耗费性能的操作,react利用虚拟dom最大限度减少了与虚拟dom的交互,大大提升了效率
- 函数式编程:组件的render过程是一个纯函数,它不会改变原数据只会根据原数据渲染出新数据,且渲染结果只与props或state有关,即UI=f(state)
虚拟dom
与真实dom区别
- 定义:真实dom是基于文档对象模型的,dom节点体现在渲染树的真实结构中;虚拟dom是用对象与真实dom建立一一对应的关系,即先把代码编译成js对象树再映射成真实dom
- 回流与重绘:虚拟dom大幅降低了回流与重绘:虚拟dom会将多次更新dom的结果合并到一次,从而减少了页面渲染次数;真实dom会频繁回流与重绘:每修改一次dom就会重新渲染一次页面
- dom操作:虚拟dom由于与真实dom通过对象形成了映射关系,dom的更改就变成了对js属性的更改以便很快就能操作dom节点;真实dom每次查询dom都需要遍历整颗dom树
特点
- 优点:渲染性能高:通过diff算法对比新旧dom树找出dom的真正变化之处,而只对更改的部分局部更新,避免了没有必要的dom操作;跨平台:由于虚拟dom本质是js对象,可以配合不同的渲染工具实现跨平台渲染如服务端渲染、uniapp
- 缺点:多了一层diff计算首次渲染会变慢
原理(diff算法)
过程
- react在内存中维护一个跟真实dom一样的虚拟dom树
- 在改动了组件后会再生成一个新的虚拟dom树
- 根据前后两颗虚拟dom树做对比,找出两个dom树不同的地方,尽可能地做到节点复用
- 只将更新的补丁作用于真实dom树,减少了浏览器的回流与重绘
组件
组件通信
- 父组件向子组件通信:父组件通过 props 向子组件传递需要的信息
- ⼦组件向⽗组件通讯: props+回调函数的⽅式
- 兄弟组件通信:通过使用父组件作为中间层来实现数据的互通,由⽗节点转发信息进⾏通信
函数组件和类组件
类组件:基于 ES6 Class 这种写法,通过继承 React.Component 得来的 React 组件;在里面可以写入状态、生命周期、构造函数(super调用父类的构造器,必须在this之前使用);但类组件内部的逻辑难以实现拆分和复用,this问题难以理解
函数组件: 只接受外部传来的数据即props进行展示,而没有自己的数据和状态;语法更短、更简单,这使得它更容易开发、理解和测试
两者区别
编写形式
类组件需要继承React.Component,要创建render来渲染逻辑并返回UI结构
函数式组件是一个纯函数,它是需要接受props参数并且返回一个React元素就可以了
调用方式
函数组件,调用则是执行函数即可
类组件要先实例化一个组件,然后调用实例对象的render方法创建组件
类组件有状态管理,而函数式组件的状态需要用hooks实现
业务逻辑
类组件是用生命周期函数来实现,而函数式组件使用react Hooks来实现
高阶组件
定义:高阶组件是一个函数,接收要包装的组件然后返回一个新的组件
实现方式:在高阶组件内部创建一个类组件,在类组件中提供状态复用
特点:主要功能是封装并分离组件的通用逻辑,让通用逻辑在组件间更好地被复用;但是需要在原组件上进行包裹或者嵌套,会影响代码的可读性
withRouter:不是所有组件都直接与路由相连(通过路由跳转到此组件)的,当这些组件需要路由参数时,使用withRouter就可以给此组件传入路由参数,将history、location、match三个对象传入props对象上,此时就可以使用this.props
props
作用
- 传递数据:给组件标签添加属性
- 接收数据:函数组件通过props接收数据
props和state区别
- props 是外部传递给组件的数据,而 state 是在组件内被组件自己管理的,一般在 constructor 中初始化
- props 在组件内部是不可修改的,但 state 在组件内部可以进行修改
fiber
定义:React16新定义的一种数据结构 ,解决了由于js的单线程运行,遇到计算量较大的情况,导致动画和交互卡顿的问题
作用:优先级高的任务可以中断低优先级的任务。然后再重新优先级低的任务;增加了异步处理,通过调用requestIdleCallback api,浏览器空闲的时候再执行之前的暂停任务
原理:Fiber把渲染更新过程拆分成多个子任务,每次只做一小部分做完看是否还有剩余时间,如果有继续下一个任务;如果没有,挂起当前任务,将时间控制权交给主线程,等主线程不忙的时候在继续执行;dom diff树变成了链表,一个dom对应两个队列,这都是为找到被中断的任务重新执行
React render
定义:类组件中render指的就是组件里的render方法;而在函数组件中,指的就是整个函数组件
原理:React 将新调用的 render函数返回的树与旧版本的树进行比较,然后进行 diff 比较更新 DOM树
触发时机
- 类组件调用 setState 修改状态:只要执行了 setState 方法,就一定会触发 render 函数执行
- 函数组件通过useState hook修改状态:useState 会先判断当前值发生变化了才会重新渲染
优化渲染 避免不必要render
原因:父组件一旦render渲染,子组件一定也会执行render渲染;子组件没有任何变化时也会重新渲染造成了性能浪费
方法
- shouldComponentUpdate:返回 true 表示重新渲染,如果传进来的数据相同则false 表示不重新渲染
- PureComponent:自带通过props和state的浅对比来实现判断是否需要重新渲染
- React.memo用来缓存组件的渲染,避免不必要的更新
setState
概念
- 组件的显示由数据状态state和外部参数props所决定;通过调用setState来更新state状态,然后重新执行render函数更新页面
- setState第一个参数是对象或函数,而第二个参数是一个回调函数,用于可以实时的获取到更新之后的数据
- 对同一个值进行多次 setState, setState 的批量更新策略会对其进行覆盖,取最后一次的执行结果
调用原理:setState方法来告知react组件state已经发生了改变,当需要修改里面的值的状态需要通过调用setState来改变,从而达到更新组件内部数据的作用
setState调用之后发生了什么 ?是同步还是异步
在组件生命周期或React合成事件中,setState是异步的
- 先执行setstate后面的代码再执行setstate ,在调用 setState 之后,this.state部 会立即拿到新的值。如果需要基于当前的 state 来计算出新的值,第二个参数应该传递一个函数
- 如果是同步的则每次更新都会重新渲染浪费资源性能差;故将其统一添加到异步队列里面,等所有同步任务执行完后,统一执行setstate更新状态
Hooks
定义:hook就是一个特殊的函数一般以use开头,hooks应用函数组件来为组件提供状态;hooks结合了类组件和函数组件各自的优点,为函数组件提供状态、生命周期等原本类组件才有的功能
作用
- 使组件之间的复用更加容易:高阶组件虽然也可以实现复用但需要改变组件结构,可读性差;而Render 中props的复用容易产生嵌套造成回调地域问题
- 解决了类组件本身存在的问题:当业务量不断增多时,class组件会越来越复杂且this指向问题难以理解容易造成错误
- 增强了代码的健壮性和可维护性:hooks将组件中相互关联的部分拆成更小的函数,而并非按照生命周期强制划分,使复杂的组件简单化性能也更加优秀
常用的hooks函数有哪些
useState
作用:声明并更新变量,为函数组件提供状态
参数:第一个为state初次渲染时是useState中传入的默认的值;第二个参数为更新state的函数
const [count,setCount]=useState(0)
它与类组件中声明state的区别是什么
- state声明方式:在hooks中可通过useState传递进行来的默认值直接获取,而类组件要通过在constructor中设置
- 读取方式:在函数组件中可直接使用该变量,而在类组件中需要this.state.count的方式获取
- 更新方式:在函数组件中通过setCount更新,类组件中通过this.setState更新
为什么useState需要使用数组而不是对象
返回数组就可以直接按顺序解构可以对数组中的元素命名,使用起来更加方便;
useEffect
作用:处理函数中的副作用(除了根据数据渲染UI这一主作用外其余效果都是副作用,如Ajax请求、操作定时器,注册事件等)
参数:
useEffect(() => { document.title = `当前已点击 ${count} 次` }, [count])
- 第一个参数是处理副作用的回调函数,在组件第一次渲染或更新后执行;
- 第二个参数是数组表示依赖项
- 没有依赖项:每次渲染时都要调用回调函数,即组件中有任何变化都会执行effect函数
- 依赖项为空数组:只在组件第一次渲染后执行回调函数
- 依赖项为数值:只有该值改变时才执行回调函数
useEffect和useLayoutEffect的区别?
- useEffect是异步调用的,组件的渲染不必等待useEffect函数执行完再开始,适用于绝大多数场景
- useLayoutEffect是同步调用的需要该函数执行完再执行组件,适用于当使用useEffect时屏幕会出现闪烁(后者不会有闪烁问题因为在改变屏幕像素之前就执行了)
useMemo和useCallBack
作用:这两个API都是通过缓存来优化组件的性能
参数:第一个为函数,第二个参数为数组作为依赖项,当函数再依赖项改变时才运行以避免很多额外开销
区别:
useMemo缓存的是回调函数中返回来的值,保存每次渲染时不会发生变化的值减少重复计算造成的资源浪费;useCallback缓存的是函数,在函数组件中只要state发生变化就会触发整个组件更新,而一些函数是没有必要更新的就应该缓存起来以提高性能
useRef
可以存储不需要引起页面渲染的数据
useContext
实现跨组件的数据传输
hooks和组件生命周期的关系
生命周期
定义:类组件从产生到销毁的整个过程,包括从创建、初始化数据、更新数据、渲染页面、卸载等过程
三大阶段
- 初始化:
-
- constructor构造器对应hooks的useState,在方法内部通过super关键字获取来自父组件的props
- componentWillUnmount和componentDidUnmount分别表示将要挂载和正在挂载时,对应hooks的useEffect
- 更新阶段:
-
- shouldComponentUpdate表示是否要更新state或props来重新渲染对应useMemo
- render要渲染的内容即表示函数本身
- componentDidUpdate组件更新结束后触发
- 卸载阶段:componentWillUnmount清理监听事件或取消订阅请求:相当于 useEffect 里面返回的 cleanup 函数
路由
作用:在不需要刷新页面的情况下就能切换到不同的页面,实现单页spa程序优化用户体验
hash模式和history模式
两个Router组件都是作为容器组件包裹其他内容
- hash模式-HashRouter组件:在URL后加上#成为哈希值 ,通过监听hashchange事件,感知 hash 的变化来确定要渲染的组件
- history模式:BroserRouter组件:允许操作浏览器里的历史会话记录,它使用的是H5的historyAPI,根据页面的路径决定渲染哪个组件(当我们点击某链接时就根据它的URL匹配到的Route组件渲染出来),应用最广泛
两者区别
- 路由跳转history模式进行组件跳转时可以传递任意参数实现组件之间的通信,而hash模式不行
- 刷新网页对state的影响:history模式的state保存在history对象中,刷新后没有任何影响;hash模式刷新后会导致state参数丢失
路由API
- Route组件:用于路径的匹配,并将匹配到的组件进行渲染,它有以下属性
- path属性:用于设置匹配到的路径
- component属性:设置匹配path成功后要渲染的组件
- render属性:设置匹配到路径后要渲染的内容
- exact属性:开启精准匹配,URL必须和path路径完全一致才能渲染,避免造成页面堆叠,如果path为单斜杠则一定要设置该属性
<BrowserRouter> <Switch> <Route exact path="/" component={HomePage} /> <Route path="/signIn" component={SignInPage} /> <Route path="/register" component={RegisterPage} /> <Route path="/detail/:touristRouteId" component={DetailPage} /> <Route render={() => <h1>404 not found 页面去火星了 !</h1>} /> </Switch> </BrowserRouter>
- Link组件:会渲染出a标签用于指定路由导航,to属性类似于a标签中的href
- NavLink组件:特殊的LInk组件,使指定的当前组件高亮
- Redirect组件:路由重定向,跳转到to对应的路径
- Switch组件:用于包裹多个Route组件,第一个组件匹配成功时后面组件就不再继续匹配(所以把404页面对应的组件放在最后,其他组件都未渲染成功时才执行)
Redux
作用:
- 将所有的状态进行集中管理,以便集中更新状态,而不用关心状态是如何分发到每一个组件内部的,redux就是这有一个实现集中管理的容器
- 数据传递可以不受组件之间层级的限制,数据流清晰稳定,简化了组件之间的通讯问题
使用场景:
- 组件之间需要共享状态state
- 某状态需要在随时随地都能被访问
- 某个组件需要改变另一个组件的状态时
原理:把所有数据都放在store这一公共存储空间内,当某一组件改变了store里的数据,其他组件读去store的内容时也能共享到数据的变化,从而间接地实现数据传递
核心组成
- state:一个数据集合,可理解为加工的原材料
- action:改变state的指令,描述要发生什么;type属性表示action的名称,palyLoad属性用作store的变更
- reducer:加工state的函数,接收参数state和action,通过action计算后返回一个更新后的state给store
- store:action和reducer的桥梁,
核心API
Provider 组件:用来包裹整个 React 应用,接收 store 属性,为应用提供 state 和 操作 state 的方法
connect 函数:连接 Redux 和 React 组件,为被包裹的组件提供 state 和 操作 state 的方法。组件中通过 props 获取 Redux store 的内容
方法
- mapStateToProps :将 state 状态内容转化为 props 属性
- mapDispatchToProps:将派发方法内容转成 Props 属性对象
计算机网络
状态码
- 1**:处理过程的中间状态
- 2**:成功状态,报文被成功收到且正确,服务器成功处理了客户端的请求
- 3**:重定向状态,客户端请求的资源位置发生了变动,需要新的URL重发请求
-
-
- 301/302:永久/临时重定向,请求的资源永久/临时移动了位置
- 304:请求的页面内容与上次访问时客户端缓存的一样,则直接调用缓存内容不比二次下载,节省资源;但304过多会说明网站长时间不更新影响网站的SEO
-
- 4**:错误码,客户端发送的报文有误服务器无法处理
- 5**:客户端请求报文正确,但服务器内部处理时发生了错误
http特性(优点)
- 简单快速:客户端向服务器发出请求时只需携带请求方法和路径,服务器压力小通信速度快
- 灵活且易于扩展:http协议里的内容都是可以扩充的
- 快连接:每次连接只处理一个请求,当请求完成后立即断开连接提高了传输效率
- 无状态:服务器不需要额外资源来记录http的状态,使得负载压力较小;但由于没有保存用户信息,使得每次请求都要验证用户身份问题,增大了内存消耗
-
-
- 解决方法:把验证信息保存在cookie上,在第一次请求时服务端的响应体上就返回一个包含客户信息的cookie,下一次发出请求上带上这个cookie服务器就能识别出
-
- 不安全:没有验证请求方的身份,且通信过程是明文传输,没有加密可能会被窃听报文可能遭篡改
-
-
- 解决方法:使用HTTPS通信,即引入ssl/tsl作为http和tcp之间的中间层
-
- 队头阻塞问题:http规定每发送一个报文请求前都要先收到服务器应答才能发送,由于服务器应答是按顺序进行,这就形成了先进先出的队列,当队头的请求处理过慢时就会阻塞后面的处理,造成性能下降
-
-
- 解决方法:并发连接:对于一个域名分配多个长连接通道,相当于增加了任务队列,不同队列不会相互阻塞;域名分片:把同一服务器处理的域名分出多个二级域名,相当于把同一长连接并发出多个长连接来减少阻塞
-
连接方式(性能分析)
http是基于请求-应答模式的,即为每次通信都要建立连接
- 非持续连接(短连接):是Http1.0的连接方式,客户端每发起一个请求服务端都要重新建立一个连接,任务结束再中断连接,增加了通信开销
-
- 适用场景:对数据刷新频率较低的场景,如新闻浏览不需要频繁向服务器发请求
- 持续连接(长连接/keep-live):http1.1的连接方式,TCP连接默认不关闭,可以被多个请求复用,减少了TCP频繁切换断开所造成的服务器负载和额外开销
-
- 适用场景:需要频繁交互的应用如聊天、直播
- 管道网络传输:http1.1中可以使用的传输方式,在同一TCP连接客户端发出的多个请求中,当第一个请求发出去时不必重新建立连接就能发出下一个请求,可提高传输效率,但可能会造成队头阻塞问题
http组成部分(报文组成)
请求报文
响应报文
响应行:由网络协议版本,状态码和状态码的原因短语组成,例如 HTTP/1.1 200 OK
响应体:服务器响应的数据
GET和POST的区别
- 适用场景:get是请求从服务器获取资源,post是向服务器提交数据更新相应资源
- 安全性:GET携带的数据是放在URL后面且会被保留在历史数据中安全性差,而post是把数据放在请求报文的主体中安全性好
- 缓存:get请求的参数会被浏览器缓存刷新后也不会丢失,post请求在浏览器刷新后会被重新提交
- 请求长度:浏览器对URL的长度有限制,所以get携带的参数也会受到限制,而post提交的数据没有限制
http新版本
http1.1
- 通信连接:使用的是长连接
- 新增了请求方法:如HEAD、PUT、OPTIONS
- 浏览器缓存:引入了更多的缓存控制策略,如Etag、If-Unmodified-Since、If-Match、If-None-Match
http2.0
- 二进制协议:报文的头部和数据体都是使用的二进制方法
- 多路复用:在一个TCP连接中,可以不再用一应一答的方式,客户端和服务器都可以同时发送多个请求和响应,而且服务器不用按顺序一一回复请求报文,避免了队头堵塞问题,极大提高了效率
- 数据流标记:由于报文不是按顺序回应的,为了避免混乱出错,对请求报文和响应报文都用独一无二的ID标记
- 头信息压缩:为避免头信息中包含很多重复信息影响效率,对头信息采用压缩机制后再发送:在客户端和服务器都维护一个存储头信息的哈希set,所有字段都生成一个索引号,相同的字段对应相同的索引号,客户端在下次发出请求时只需要发出索引号即可以避免重复
- 服务器推送:允许服务器在没有得到请求的情况下就向客户端推送资源,以减少延迟时间
http3.0
基于udp协议但还保证了安全性,结合了tcp和udp各自的优点,这套功能被称为QUIC协议。
- 流量控制、可靠传输:在UDP的基础上增加了一层如tls加密功能来保证可靠性
- 多路复用:解决了队头阻塞问题
HTTPS
超文本安全传输协议,相比于http通信它增加了SSL/TLS安全层来加密数据包
SSL/TLS协议
作用:提供身份认证、信息加密等功能以避免http在明文传输过程中被劫持篡改的风险,他会对发起的http加密和对接收的http请求解密
工作原理(保证安全的方法):
- 散列函数:基于散列算法验证信息的完整性,对数据的任何修改都会改变散列函数的结果,通过对比与预期结果是否一致来判断数据是否被篡改
- 对称加密:通信双方使用同一个密钥对数据进行加密和解密,但是这个密钥也是要通过网络传输还是可能被窃取造成信息泄露的风险
-
- 特点:以一对一的形式进行数据传输且要共享相同的密码,
- 适用场景:对速度要求高或需要同时处理大量数据但对安全要求不高的场景,如视频直播
- 非对称加密:有私钥和公钥两个密钥,公钥是公开的而私钥保密;公钥加密的信息只能是私钥解开,私钥加密的信息只能公钥解开;公钥用于留给要通信的客户端而私钥放在服务器以确保安全,这样客户端的公钥之间不能相互通信只能和服务端的私钥通信,解决了对称加密中密钥可能在传输中丢失的问题
-
- 特点:加密和解密的配对是一对多的,即一个服务器的公钥可以和多个客户端的私钥通信;安全可靠,但是通信过程更加复杂使得效率降低消耗更多服务器资源
- 混合加密:结合了对称加密和非对称加密各自的优点,在通信建立前使用非对称加密的方式:让对称加密的密钥使用非对称加密的公钥进行加密后再发送,通信时接收方使用私钥解密,然后通信过程都可以用对称加密来沟通(通信过程没有密钥泄露的风险了故用对称加密来提高效率)
数字证书
背景
非对称加密也不一定能保证安全,因为并不能保证公钥就一定是安全的,可能会被中间人窃取了公钥并调包成恶意公钥,当使用这个恶意公钥加密信息时就可能被恶意方的私钥解密;所以要引入公信力高的第三方(CA)颁发证书证明身份,防止被中间人攻击
原理
- CA加密:使用一种hash算法对对公钥和其他信息加密生成一个信息摘要,然后让证中心(CA)的私钥对信息摘要进行加密形成签名,然后将原始信息和签名结合形成数字证书
- CA解密:当接收方收到数字证书时,先根据原始信息使用同样的hash算法生成另一个摘要,然后使用CA的公钥对数字证书加密过的摘要解密,将生成的摘要与解密后的摘要对比就能得知信息是否被篡改了
HTTPS握手过程
在tcp三次握手后还要进行SSL/TLS四次握手才能建立连接
- 客户端发出请求:包含客户端支持的加密协议版本、支持的加密和压缩方法,以及客户端用于生成对话密钥的随机数
- 服务端回应:收到请求后,服务器将回应确认使用的加密协议版本(如果与客户端支持的版本不一致则关闭通信),支持的加密方法,以及一个用于生成对话密钥的随机数
- 客户端回应:首先验证服务器证书是否合法,如果不合法则发出警告并终止通信,如果合法则继续以下通信流程;生成一个新的随机数并用数字证书的公钥加密后发给服务器,并携带一个包含前面所有内容的hash值供服务器校验
- 服务器最后回应:服务器使用自己的私钥来解密客户端发出来的随机数,并提供前面所有内容的hash值供客户端检验
-
- 握手结束后,客户端和服务器就根据约定的加密方法通过三个随机数来生成对话密钥,以后的对话过程都使用这个密钥来加密信息
DNS解析协议
定义:将域名解析为服务器IP地址的协议,使得我们通过输入域名就能访问到对应IP地址的网站
DNS是基于什么协议的
- 在区域传输时使用TCP协议:定时向服务器查询数据是否有变动,如有变动就对数据同步,需要tcp建立可靠连接来保证数据的可靠性
- 在域名解析时用UDP协议:客户端向DNS服务器查询域名时一般返回的内容都很小,用udp传输即可来得到更快的响应
DNS解析过程(工作原理)
- 服务器收到客户端的域名解析请求后,首先会在浏览器缓存中查找对应的IP地址,如果查到就返回否则下一步
- 将请求发送给本地DNS服务器,在本地域名服务器缓存中查询,如果查到就返回否则下一步
- 本地DNS服务器向根域名服务器发送请求,然后向根域名服务器返回的顶级域名服务器发送请求,如果查到就返回否则下一步
- 本地DNS服务器向权威域名服务器发出请求然后得到对应的返回结果
- 本地DNS服务器将返回结果保存在缓存中以备下次使用,同时将这一结果返回给浏览器
DNS劫持
- 定义:黑客修改了域名解析的结果,使原本对该域名的访问由原IP地址转入到修改后的恶意IP地址,造成原网址不能访问或访问到钓鱼网站
- 劫持类型:
-
- DDoS攻击:客户端发送请求后服务器回应确认数据包,但是客户端确不再发送下一个确认包导致服务器会一直等待造成资源消耗
TCP
主要特点(和UDP的区别)
- 面向连接:在发送数据前必须要在通信双方通过三次握手建立连接
- 点对点传输:TCP传输的连接只有两个端点
- 可靠传输:tcp基于ARQ协议和滑动窗口协议来提供可靠的数据传输服务,保证数据没有丢包、乱序、重复等问题
- 拥塞控制:当网络出现拥塞时,就会动态控制一个拥塞窗口来减少数据的注入
- 全双工通信:通信双方在任何时候都能向对方发送数据
- 基于字节流:相对于udp的报文独立传输,其数据没有明确的边界
- 适用场景:效率要求不高但需要保证安全的场景如文件传输
拥塞控制
背景:当网络需求资源超过可用资源时,资源供应不足网络性能就要变坏,所以需要维护一个动态变化的拥塞窗口控制网络的拥塞程度
控制方法
- 慢开始和拥塞避免:一开始只发送少量数据然后再慢慢提速试探网络的承受能力,到达门限值前时是以指数增长;当慢开始到达门限值后就要使用拥塞避免算法即让拥塞窗口线性缓慢地增长,当检测到出现网络拥塞时就让拥塞窗口瞬间退回到慢开始时的大小,然后重新开始执行慢开始算法但它的门限值是上次慢开始的一半
- 快重传和快恢复:接收方在收到一个失序的报文段后就会立即发出重复确认(让发送方知道没有正确送达),当发送方连续收到三个重复确认时就直接重传该报文段而不用等重传计时器到期;然后跳过慢开始算法,让门限值相对此时直接减半然后执行拥塞避免算法
流量控制
定义:接收方让发送方的速度不要太快,要让接收方来得及接收
控制方法——滑动窗口
通过窗口大小控制能接收的数据量(注意区分和滑动窗口协议的区别),,让发送方的发送窗口不能超过接收方的接收窗口大小
- 当连接建立时为两端都分配一个缓冲区来保存输入的数据
-
- 接收方每次收到数据包后,在回应确认报文的同时告诉发送方自己的缓冲区还有多少是空闲的即接收窗口的大小,当缓冲区都被填满时回复的窗口大小为0
- 发送方根据接收窗口的大小调整发送数据量,当收到接收窗口的大小为0时就停止发送数据,然后开启一个定时器每隔一段时间就去询问接收方,当接收窗口大于0时再重新发送
可靠传输机制
原则:发送方发送数据前都要等待应答才能继续发送,接收方收到数据后都要反馈应答才能继续下一帧
重传机制
- 背景:为避免传输过程数据出现重复、丢失等情况,tcp会重传其认为已经丢失的包
- 基于时间的重传:在发送一个数据后就开启一个定时器,如果在规定时间内还没有收到发送数据的ACK确认报文,则对该报文重传,当多次重传都失败时就会放弃以提高效率
- 基于确认信息的重传:当发送方收到接收方的三个冗余的确认应答后,说明该报文段以后的报文段很有可能发生丢失了,那么发送方会启用快速重传机制,就是当前定时器结束前,发送所有的已发送但未确认的报文段
- 接收到出错或乱序帧时:
-
- 发送方的选择重传机制:只重传出错或超时的帧,对于失序的帧只要它还在接收窗口内就先缓存,等所缺序号的帧收到后再一起交付,并让接收窗口继续向后移动
- 接收方的累积确认机制:对于所有有序到达且正确的报文段,接收方累积只返回一个确认报文,如果收到了乱序的报文段则直接丢弃,然后只给最近按序到达的报文段回复肯定报文,并让接收方从出错的地方开始重传
三次握手
建立一个tcp连接时,需要客户端和服务器总共发送三个包,以确认双方的发送能力和接收能力都正常
握手过程
- 第一次:客户端向服务器发送报文,标记位为SYN表示请求建立新连接,Seq=x表示每个数据包的序号,随后客户端加入发送状态等待服务器确认
- 第二次:服务器如果同意连接请求就会回复报文,该应答报文的标记位为SYN=1和ACK=1表示客户端的seq序号有效,服务器成功接收到数据并创建连接,确认号ack=x+1,返回的数据包序号seq=y,服务器进入收到状态
- 第三次:客户端再向服务器返回一个确认报文,标记位是ACk表示确认收到服务器的同意连接信号,确认号为ack=y+1,数据包序号为seq=x+1,随后服务器进入连接建立完毕状态
为何不是两次握手
确认双方的接收能力和发送能力都正常,让服务器能知道自己的序号得到确认
防止失效的请求报文被服务器接收造成服务器开启无用的连接而使资源浪费:如果客户端发出的连接请求因网络延迟而滞留,在超时后客户端就会重发第二个请求与服务端建立连接;当该连接释放时,第一个滞留的连接请求可能又传到了服务端,而服务端又认为这是一个新的连接请求,在没有客户端最后发来的确认下就建立了连接,客户端此时本不需要发送数据而服务器又会一直等待其发送数据,造成不必要的性能消耗
四次挥手
- 若客户端认为数据发送完成,则它需要向服务端发送连接释放请求
- 服务端收到连接释放请求后,会回复确认报文,并进入等待关闭状态,此时表明客户端到服务端的连接已经释放,不再接收客户端发的数据了。但是因为 TCP 连接是双向的,所以服务端仍旧可以发送数据给客户端。
- 服务器发完数据,就发送连接释放报文段fin,关闭tcp连接
- 客户端收到释放请求后,向服务端发送确认应答,此时客户端进入 TIME-WAIT 状态
为什么需要四次
tcp连接是全双工信道,需要双方分别释放到对方的连接,单独一方的连接释放只代表不能再向对方发送数据,连接处于的是半释放的状态。
为何要等待一段时间再关闭
等待2msl后连接彻底关闭 ;2MSL是一个发送和回复所需的最大时间,确保TCP连接断开的可靠性;防止发送给服务器的确认报文段丢失或者出错,从而导致服务器端不能正常关闭
TCP粘包
浏览器中输入URL的执行过程
- URL解析:检查URL是否为合法链接,如果合法则解析出URL的组成部分如协议、域名、端口号、携带的参数等,如果地址不全浏览器还会自动补全,如果不合法浏览器会从历史记录和书签等缓存内容中查找输入内容
- DNS解析
- 三次握手建立tcp连接
- 客户端发送http或https请求:客户端发送HTTP请求获取服务器端的静态资源
- 服务端响应请求:服务器发送HTTP响应报文给客户端,客户端获取到页面静态资源
- 解析渲染页面
- tcp四次挥手断开连接
websocket
定义:是一种位于应用层的网络传输协议。可在单个TCP连接上进行全双工通信,能更好的节省服务器资源并达到实时通迅;客户端和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输
优点:
- 实时性强:全双工通信,允许数据在两个方向上同时传输,相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少
- 性能开销小:不需要像http请求每次都要携带完整的头部
适用场景:视频弹幕、直播等实时性强的场景
OSI七层模型
原理:发送端从应用层开始向下传递对数据每层都添加封装,接收端再从物理层开始依次解封装最后得到数据
浏览器缓存
定义:浏览器将加载过的静态资源存储到本地中,如果静态资源没有更新则当浏览器再次请求时就可以直接从本地读取而不必再向服务器发出请求
好处:减少了向服务器发起的多余请求,从而减轻服务器负担提高了网站性能,同时加快了用户在网页的加载速度
缓存过程
- 浏览器第一次加载资源成功后就会从服务器下载资源文件并缓存资源文件与文件头,以供下次对比使用
- 再次加载资源时首先判断强缓存是否命中:比较当前请求与上一次请求成功的时间差,如果没有超过cache-control设置的max-age,则说明没有过期就命中强缓存可直接从本地读取资源
- 如果资源已过期没有命中强缓存则开始协商缓存:向服务器发送带有 If-None-Match 和 If-Modified-Since 的请求
-
- 请求带有Etag值(优先级更高)
- :服务器首先根据Etag的值判断请求的资源是否修改
-
-
- 如果Etag的值一致则没有修改命中协商缓存,服务器返回304
- Etag的值不一致则说明资源已被修改,返回新的资源并携带新的Etag值,同时服务器返回200
-
-
- 请求没有Etag值:比较If-Modified-Since 和被请求资源的最后修改时间last-modified的值
-
-
- 如果一致则命中协商缓存返回304
- 不一致则返回新的 last-modified,同时服务器返回200
-
协商缓存和强缓存
强缓存
定义:强缓存是指不必向服务器发起请求就可直接使用的本地缓存资源
设置方式(header字段)
- Expires属性:资源的绝对过期时间,在过期时间内则命中强缓存即直接获取到该资源
-
- 缺点:表示的是服务器的绝对时间,可能与客户端的时间不一样,且客户端的时间可以人为修改,容易产生误差
- Cache-Control属性:指定所有缓存机制在整个请求中必须要服从的指令;优先级更高可以精确控制对资源的缓存
-
- 可设置的字段
-
-
- public:设置该字段表示可被如何对象缓存
- private:表示只能被用户浏览器缓存
- no-cache:防止从缓存中获取过期资源:先和服务器确认资源是否过期,没有过期才会使用缓存资源
- no-store:表示禁止使用任何缓存,每次向服务器发请求都要拉取最新资源
- max-age:设置缓存的最大有效期
-
协商缓存
当没有命中强缓存时就会使用协商缓存,先向服务器发出请求,如果资源没有更改则返回304状态码,浏览器获取到本地缓存;如果资源已经修改则返回修改后的资源
设置方式
- 响应头Last-Modified/请求头If-Modified-Since:
-
- 浏览器在发出第一次请求时就会响应头上添加Last-Modified属性表示资源的最后修改时间,再次请求资源时在请求头上添加If-Modified-Since属性,为上一次请求返回的Last-Modified值,通过比较其与响应头的Last-Modified值是否相同判断资源是否被修改,如果资源未修改就说明没有变化则命中协商缓存,即返回304并让客户端使用本地缓存,如果资源修改了就则返回修改后的最新资源
- 缺点:Last-Modified的值只能精确到秒级,如果资源在1秒内修改多次就会使其判断失效
- 响应头etag/响应头if-None-Match:
-
- 请求头中的Etag属性是资源的唯一标识符,随资源的改变而改变;在下一次请求时浏览器就会在请求头中添加If-None-Match 属性,值为上一次返回的Etag值;通过对比前后的Etag值来判断资源是否发生改变即是否命中协商缓存
- 特点:相比于Last-Modified对比文件时间戳的形式,Etag属性对比的是文件独一无二的指纹所以更加准确优先级更高;但是基于算法的计算会占用过多服务器资源消耗性能
浏览器渲染
渲染过程
- 解析dom资源构建dom树,dom树由dom元素和属性节点组成
- 解析CSS资源,构建CSS树
- 根据dom树和CSS树构建渲染树:渲染对象由渲染树上的节点表示并与dom元素相对应,不可见的dom节点不会插入到dom元素
- 布局渲染树:浏览器确定出各个节点在页面中的大小和位置(回流)
- 绘制渲染树:遍历渲染树并将每个节点的内容展示在屏幕
渲染优化
针对js
原因:js会阻塞css和HTML的解析,所有浏览器在下载js文件时都会组织一切其他活动,直到js解析执行完才继续从中断的地方恢复解析(为避免js的操作可能会删除dom,造成浏览器资源的浪费刚解析的dom就又被js删除,所以干脆将dom解析放在js执行完后)
为了提高页面加载速度就要让页面延迟加载:
- 异步引入外部的js文件,即下载完js文件后就立即异步加载
-
- defer属性:加载好js文件后,要先等dom树构建好后才能执行,dom树已经准备好时才能立即执行,有多个defer时则按顺序加载
- async属性:加载好js文件后就立即执行,有多个async属性时不能保证执行顺序(谁先加载完就谁执行)
- 设置定时器来手动延迟加载
- 将js文件放在body最后
针对css
css对渲染的影响:
- 不会阻塞dom解析:dom解析和css解析是两个并行的过程,所以dom不会受到css的影响
- 会阻塞dom树的渲染:渲染是依赖于dom树和css树的,无论dom树是否已构建完成等要等待css树构建完成才能开始渲染
- css会阻塞js的执行:需要先等css样式下载完后才能继续执行js脚本
优化方法
- 导入外部样式时尽量使用Link而不用@import(import会使gui线程暂时停止渲染
- 如果css较少则尽量使用内联样式即直接写在style标签中(gui线程直接渲染)
针对dom树和css树
- 尽量用语义化的标签
- 减少代码的嵌套层级
减少回流和重绘
定义
- 回流是指网页中元素的大小和位置发生改变时需要对网页结构重新布局(画好元素形状)
- 重绘是指网页中某些元素的样式发生了变化如颜色的改变,但是尺寸和位置没有变化不会影响网页布局,浏览器只需对元素重新绘制(画好颜色)
- 触发回流一定触发重绘,但触发重绘不一定回流,回流比重绘更加消耗性能
减少回流和重绘的方法
- 尽量在低层级的dom节点进行操作,避免影响更多的dom节点
- 避免频繁操作dom:
-
- 避免使用table布局
- 创建一个文档片段documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中
- 对多个dom设置类名
- 将元素先设置为display:none,操作结束后再把它显示出来,或对元素使用绝对定位或固定定位
- 浏览器本身的优化原理:浏览器会将所有的回流、重绘操作放在一个队列中,当队列中的操作到一定数量时再批量处理,这样就让多次的回流和重绘变为一次
浏览器本地存储
cookie和session
cookie:一种纯文本文件,每次http请求都会携带cookie,用于存储用户的数据信息以便让服务器辨别用户身份
组成
- Name/Value:cookie的名字/值
- Size:cookie的大小
- Path:可以访问此cookie的页面路径
- Secure:指定是否使用https安全协议发送cookie
- Expires:表示cookie的过期时间,到达此时间后cookie就失效
特性
- cookie是无法跨域名的,即不同域名下cookie的内容无法共享(为阻止其他网站非法获取);如果想要跨域共享cookie则需要使用Nginx反向代理
- 同一域名下数量不能超过20个且每个cookie大小不超过4kb
使用场景:将sessionid存储到cookie中,每次请求都会携带这个sessionId,让服务器知道是谁发起的请求
怎么能防止cookie被窃取呢?
通过配置cookie的:Secure / HttpOnly
Secure属性指定浏览器只有在加密协议 HTTPS 下,才能将这个 Cookie 发送到服务器。另一方面,如果当前协议是 HTTP,浏览器会自动忽略服务器发来的Secure属性。该属性只是一个开关,不需要指定值。如果通信是 HTTPS 协议,该开关自动打开。
HttpOnly属性指定该 Cookie 无法通过 JavaScript 脚本拿到,主要是Document.cookie属性、XMLHttpRequest对象和 Request API 都拿不到该属性。这样就防止了该 Cookie 被脚本读到,只有浏览器发出 HTTP 请求时,才会带上该 Cookie。
cookie能跨域携带吗?
- 前端请求时在request对象中配置"withCredentials": true;
- 服务端在response的header中配置"Access-Control-Allow-Origin", "http://xxx:${port}";
- 服务端在response的header中配置"Access-Control-Allow-Credentials", "true"
session:是一个存储信息的对象,用于保存用户状态;服务器会为每个浏览器创建唯一的sessionID并保存在cookie做为客户端与服务器的验证标识
作用:服务器收到客户端请求要产生一个session时,会先检查客户端的cookies中是否有sessionId,并且判断它是否过期;sessionId且还没有过期,则会根据该sessionId的值将服务器中的session检索出来;否则,将产生一个新的session,当创建一个新session后,服务器也会产生一个sessionId号,回写到客户端的cookies当中。
cookie和session的区别
- 存储位置:cookie存储在客户端,session存放在服务器
- 存储大小:cookie能存储的数据量小,而session没有限制
- 存储类型:cookie只能存储字符串,session可以存储任意的数据类型
- 安全性:cookie不安全容易被截取,session保存在服务器不能被伪造更加安全
- 作用范围:cookie在跨域名下无法共享,而session代表客户端和服务器的一次会话过程范围是本次会话,即使客户端关闭也不会消失直到session生命周期结束
其他存储方式
LocalStorage
优点:
- 可以存储较大信息
- 可长期保存在本地,除非主动清理否则一直存在,并不会随着页面的关闭而消失
- 不需要每次http请求时都携带
特点:当前窗口关闭后就失效了,并且只能被同一个窗口的同源页面所访问共享
SessionStorage
定义:本地存储会话(session)中的数据,主要用于临时保存同一窗口(或标签页)的数据,关闭窗口或标签页之后将会删除这些数据
特点:这些数据只有在同一个会话当中的页面才可以访问(页面刷新不会导致数据丢失),当页面管壁后数据才跟着销毁
IndexedDB
浏览器提供的本地数据库,可以被网页脚本创建和操作,允许存贮大量数据,提供查找能建立索引
特点
- 键值对储存:每一个数据都对应着独一无二的主键
- 异步:读写IndexedDB的同时用户依然可以进行其他操作
- 同源限制:不能访问跨域数据库
- 储存空间大:一般来说不少于250MB,没上限
- 支持二进制储存:可以存储字符串,而且可以存储二进制数据
跨域
同源策略
- 同源是指URL的协议、域名、端口号都一致,如果不同源则为跨域
- 跨域限制:当前域下的js文件在没有授权的情况下不能访问其他域的本地存储、不能操作其他域的dom、不能发送跨域请求,以隔离潜在的恶意文件保护用户安全
解决跨域
cors
定义:发起跨域请求时使用额外的http头,让浏览器允许不同源服务器上指定资源的访问
请求分类:
- 简单请求:请求方法是HEAD GET POET之一;
-
- 请求过程:
-
-
- 浏览器在请求头中增加一个origin字段,该字段由于说明本次请求来自哪个源,服务器会根据该值决定是否同意这次请求
- 如果origin在指定域名内,服务器返回的响应就至少多出Access-Control-Allow-Origin这一字段;如果浏览器没有收到该字段就会知道出错了
-
- 非简单请求:对服务器有特殊要求的请求如PUT/DELETE
-
- 请求过程
-
-
- 在正式通信前先进行一次预检请求,预检请求使用的方法是OPTIONS表示这个请求是用于询问的
-
-
jsonp
script标签不受同源策略限制,可以直接引用外部资源即通过src属性请求非同源的script脚本,只支持GET请求且不安全,可能遇到XSS攻击
开发阶段配置webpack的proxy :
Nginx配置反向代理: 生产环境部署时用的nginx Nginx 是一种高性能的反向代理服务器,反向代理即帮其它的服务器拿到请求,然后选择一个合适的服务器,将请求转交给它,实现服务器集群的负载均衡。在配置文件的http块下的server块中,删除/注释掉 location / 下的内容,配置成项目运行的地址,再添加 location /api 配置此时的配置内容意思为,将http://127.0.0.1:8080和http://127.0.0.1:9090都代理到localhost:8000地址下,这样就不会有跨域
WebSocket
请求头信息中有Origin字段,表示请求源来自哪个域,服务器可以根据这个字段判断是否允许本次通信,如果在白名单内,就可以通信
浏览器事件机制
事件定义:事件是用户操作网页时发生的交互动作如鼠标点击移动或者网页本身的一些操作。事件被封装成一个event对象,包含了属性和方法
事件触发过程(事件流)
- 事件捕获:事件从最外层开始发生到目标源的过程
- 处于目标阶段:到达事件目标节点。如果阻止事件冒泡,那么该事件对象将在此阶段完成后停止传播。
- 事件冒泡:事件会从目标源最深的具体节点开始发生,一直向上传播直到document
阻止事件冒泡:event.stopPropagation()方法
事件委托/事件代理:把原本需要绑定在子元素的响应事件通过冒泡机制委托给父元素,父节点捕获到事件后再进行处理
好处:大量节省内存占用,减少事件注册
前端安全
XSS攻击
定义:跨站脚本攻击,攻击者在页面里插入了恶意script代码
攻击类型
- 存储型:浏览器将恶意代码到服务器,并将恶意代码存储到数据库
- 反射型:浏览器提交恶意代码到服务器,服务器再将恶意代码传回客户端
- dom型:恶意代码没有提交只是在客户端运行
防御
- 利用浏览器的执行机制
-
- 对输入进行过滤:对url中的参数进行过滤,过滤掉会导致恶意脚本执行的内容;过滤特殊标签如iframe: iframe中的内容是由第三⽅来提供的,默认情况下他们不受控制;以及JS不必要的一些dom事件
- 对输出进行编码:对要渲染的内容做出编码转义
- 限制URL的输入长度或对cookie设置http only属性,js脚本就不能读取到cookie,但是浏览器还是能够正常使用cookie
- 使用 CSP白名单:告诉浏览器哪些外部资源可以加载和执行
CSRF 攻击
不需要注入恶意代码而是伪造用户请求,在用户不知情时借用户名义做出恶意行为
攻击类型
- get/post类型:将请求接口隐藏在网页中,当用户打开这个网站时就会自动发起get/post请求
- 链接类型:诱导用户点击非法链接
攻击的必要条件:目标站点一定要有 CSRF 漏洞;用户要登录过目标站点,并且在浏览器上保持有该站点的登录状态;需要用户打开一个第三方网站
防御策略
- 同源策略验证请求的来源站点:服务器根据http请求头的refer字段判断是否为允许访问的站点但是请求头仍然有可能被伪造
- 使用 CSRF Token 进行验证:在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求;但需要给网站中的所有请求都添加上这个 token,操作比较繁琐
- 对 Cookie 进行双重验证
- 设置严格模式: cookie 在任何情况下都不可能作为第三方 Cookie 使用