一.HTML + CSS
盒模型
1.IE盒模型的content包括border和padding。
一个块的总宽度= width + margin(左右)(即width已经包含了padding和border值)
2.标准盒模型的content不包括border和padding
在标准模式下,一个块的总宽度= width + margin(左右) + padding(左右) + border(左右)
3.box-sizing属性可以指定盒模型的类型
div水平垂直居中
1.给div的父级添加相对定位(position: relative),
自己position: absolute;left:50%;top:50%;transform: translate(-50%, -50%);
2.给div的父级添加相对定位(position: relative),
自己position: absolute;top left right bottom 都设置0,margin: auto;
3.弹性盒主轴侧轴居中:display:flex;align-items:center;justify-content:center;
4.通过定位+maring偏移,给div的父级添加相对定位(position: relative),
自己position:absolute;left:50%;top:50%;margin-left: 负的宽度的一半;margin-top:负的高度的一半;
简述一下px、em和rem,vw,vh的区别。
px是相对长度单位,相对于显示器屏幕分辨率而言的。
em相对单位,参考物是父元素的font-size,如果自身定义了font-size按自身来计算
rem是CSS3新增的一个相对单位。使用rem为元素设定字体大小时,仍然是相对大小,但相对的只是HTML根元素。通过它既可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应
vw和vh是视口单位.1vw = 可视窗口的宽度的百分之一。
哪些CSS属性可以继承?哪些不可以继承?
可继承: font-size font-family color list-style等;
不可继承 :border padding margin width height background等;
flex弹性盒
1.flex-direction:决定主轴的方向(即项目的排列方向)
2.flex-wrap:定义子元素是否换行显示
3.justify-content:定义了项目在主轴上的对齐方式。
4.align-items:定义项目在交叉轴上如何对齐。
5.flex-grow:规定项目将相对于其他灵活的项目进行扩展的量。 放大缩小和固定 设置固定数值是
6.flex-shrink:规定项目将相对于其他灵活的项目进行收缩的量。
用fex:1 就会有一个压没的效果, 设置一个最小值
(flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。)
H5新特性
- 绘图 `canvas`
- 用于媒介回放的 `video` 和 `audio` 元素
- 本地离线存储 `localStorage` 、`sessionStorage`
- 语义化更好的内容元素,如:`header`、`article`、`nav`、`section`、`footer`等
- 表单控件 `calendar`、`date`、`time`、`email`、`url`、`search` 等
- 新的技术 `webworker`、`websocket`
- 新的文档属性 `document.visibilityState`
移除的:
- 纯表现的元素:`basefont`、`big`、`center`、`s`、`tt`、`u`
- 对可用性产生负面影响的元素:`frame`、`frameset`、`noframes`
CSS3 新特性
1.选择器
伪类选择器(:nth-child)
伪元素选择器(:before :after)
2.背景和边框
background-size:规定背景图片的尺寸(cover:填充;100% 100%:拉伸)
border-radius:圆角
border-image:边框图片
3.文本效果
text-shadow 向文本添加阴影
4.2D/3D 转换
transform
translate 平移
rotate 旋转
scale() 缩放
skew() 扭曲
5.动画、过渡
transition 过渡
animation 动画
清除浮动(解决高度塌陷)
1.给高度塌陷的元素添加 overflow: hidden;
形成了BFC,会形成一个独立的空间,在这个空间里面计算高度时,浮动元素也参与计算。
缺陷: 溢出隐藏,隐藏掉元素外的内容
2.在高度塌陷的元素里面的最下面添加一个空的div
给空的div添加属性 clear: both;
缺陷: 添加空标签,页面里面会出现很多无用的元素,导致代码冗余
3.万能清除法
.box1::after {
content: '.';
display: block;
height: 0;
overflow: hidden;
visibility: hidden;
clear: both;
}
简述一下BFC。
概念:
BFC块级格式上下文,是页面盒模型布局中的一种 CSS 渲染模式,相当于一个独立的容器,里面的元素和外部的元素相互不影响。
作用:
清除浮动,解决margin塌陷问题
防止同一 BFC 容器中的相邻元素间的外边距重叠问题
形成了BFC的区域不会与float box重叠
触发条件:
-html根元素
-float值不为none的元素
-position值为absolute或者fixed的元素
-display值为inline-block table-cell flex inline-flex 等的元素
-overflow 除了 visible 以外的值(hidden,auto,scroll)的元素
特点:
-内部盒子会在垂直方向,一个接一个地放置
-BFC元素的垂直方向上会发生边距重叠
-BFC元素和浮动元素不会发生重叠
-在BFC中,每一个盒子的左外边缘(margin-left)会触碰到容器的左边缘(border-left)(对于从右到左的格式来说, 则触碰到右边缘)
-计算BFC高度时,浮动元素也参与计算
sass和less的区别
Sass 和 LESS 都是是 CSS 预处理器
1.Sass是基于Ruby的,是在服务器端处理的,而less是基于js,在客户端处理。
2.Sass中定义变量用$,而less中定义变量是@
3.Sass可以使用循环,条件等逻辑语句,而less不支持
回流和重绘
1.概念:
- `回流`:当 DOM 的变化影响了元素的几何信息,浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做回流(也可以叫做重排)。表现为重新生成布局,重新排列元素。
- `重绘`:当一个元素的外观发生改变,重新把元素外观绘制出来的过程,叫做重绘。表现为某些元素的外观被改变。
2.常见引起回流和重绘的属性和方法:
任何会改变元素几何信息(元素的位置和尺寸大小)的操作都会触发回流。
- 添加或删除可见的 DOM 元素
- 元素尺寸改变--边距、填充、宽度、高度
- 浏览器尺寸改变-- resize 事件发生时
- 计算 offsetWidth 和 offsetHeight 属性
- 设置 style 属性的值
- 修改网页默认字体
**回流必定会发生重绘,重绘不一定会引发回流。**`
**回流所需的成本比重绘高得多**
二.JavaScript
瀑布流
瀑布流,又称瀑布流式布局。视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。
原理分析
1、瀑布流就是第一排从左到右依次排序,第二排的第一个盒子排列在第一排中最小盒子的后面,后面的就以此类推。 2、这里需要计算每排盒子的高度+外边距、宽度+外边距。 3、最后通过定位来放在高度最小的盒子的后面。
如果按照正常的效果来说 图片的布局不会是这样的 如果ui给的图片 宽度一致高度不一致的话
将会产生大量的留白 知道css布局的就知道 如果不设置 瀑布流的话 第二行 将会是 第一行最高的那张图片底部作为起点
上面产生大量留白 那是css默认的布局方式导致的 我们说了这么多还是直接上效果图比较明显
null和undefined的区别
null是一个表示"空对象",转为数值时为0
undefined是一个表示"无"的原始值,转为数值时为NaN
当声明的变量还未被初始化时,变量的默认值为undefined
null用来表示尚未存在的对象,常用来表示函数返回一个不存在的对象
数据类型有哪几种, 检测方法是什么??
Ruby ES5-------Number/Boolean/String/Undefined/Null
ES6新增---symbol
ES11新增---bigInt
引用数据类型
Object
检测方法4种
https://segmentfault.com/a/1190000019259209
Object.prototype.toString.call()
**作用: 可以检测所有数据类型**
**所有数据类型都可以检测,而且非常正确**
语法: Object.prototype.toString.call( 'xxx'/11/[ ] )
返回值: [object Xxx], Xxx 就是对象的类型
constructor
**作用: 可以检测基本数据类型和引用数据类型**
**弊端: 把类的原型进行重写, 很有可能把之前的constructor覆盖 检测出来的结果就会不准确**
语法: ("xx")/([ ])/(function(){ }).constructor === String/Array/Function
返回值: true/false
instanceOf
**作用: 判断左边的对象是否是右边构造函数的实例**
**原理: 判断对象类型,基于原型链去判断(obj instanceof Object)**
**左边对象的原型链__proto__上是否有右边构造函数的proptotype属性**
**弊端: 用于引用类型的检测, 对于基本数据类型不生效**
语法: " "/[ ]/true instanceOf String/Array/Boolean
返回值: true/false
typeOf
**作用: 用于检测基本数据类型和函数**
**弊端: 检测引用数据类型(Arrary/object)只会返回Object, 不起作用**
语法: typeOf " "/[ ]/xx
返回值: "string"/"boolean"/"object" (无法区分)
数组的方法
push() 从队尾添加,改变原数组
pop() 移除数组末尾最后一项,返回移除的项
shift() 删除数组第一项,返回删除元素的值,如果数组为空返回undefined
unshift() 添加头部,改变原数组
sort() 数组排序,参数为一个匿名函数,如果匿名函数返回正值,则升序排列,反之相反
reverse() 翻转数组项的顺序 原数组改变
concat() 将参数添加到原数组,将参数添加到数组的末尾,并返回一个新数组,不改变原数组
slice() 返回原数组中指定开始下标到结束下标之间的项组成的新数组,slice接受两个参数,如果致谢一个参数,slice方法返回从该参数到数组末尾的所有项,如果有两个参数,该方法返回起始位置和结束位置之间的项,但不包括结束位置的项
splice() 可以实现删除,插入,替换 删除(可以删除任意属相的项,只需要指定2个参数,要删除的第一项的位置和要删除的项) 插入,替换(可以向指定位置插入任意数量的项,只需提供3个参数:起始位置,0(要删除的项),插入的项),splice()方法始终都会返回一个数组,数组中包括从原数组中删除的项,如果没有删除任何项则返回一个空数组
map() 对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组
some() 判断数组中是否存在满足条件的项,只要有一项满足条件,就返回true
every() 判断数组中每一项都是否满足条件,只有所有选项都满足条件,才会返回true
filter() 过滤功能,数组中的每一项运行给定函数,返回满足过滤条件组成的数组
forEach() 对数组进行循环遍历,对数组中的每一项运行给定函数,这个方法没有返回值,参数都是function类型,默认有传参功能,参数分别是,便利的数组内容,对应的索引,数组本身
indexOf() 接受两个参数,要查找的项和表示查找起点位置的索引,返回查找的项在数组的位置,没找到的情况下返回-1
reduce()是数组的一种方法,它接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
对象的遍历方法
1.for in
for in 循环是最基础的遍历对象的方式,它还会得到对象原型链上的属性
// 创建一个对象并指定其原型,bar 为原型上的属性
const obj = Object.create({
bar: 'bar'
})
// foo 为对象自身的属性
obj.foo = 'foo'
for (let key in obj) {
console.log(obj[key]) // foo, bar
}
在这种情况下可以使用对象的 hasOwnProperty() 方法过滤掉原型链上的属性
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(obj[key]) // foo
}
}
2.Object.keys()
是 ES5 新增的一个对象方法,该方法返回对象自身属性名组成的数组,它会自动过滤掉原型链上的属性,然后可以通过数组的forEach()方法来遍历
Object.keys(obj).forEach((key) => {
console.log(obj[key]) // foo
})
3.object.values()
该方法返回对象自身属性值组成的数组
let meals = {
mealA: 'Breakfast',
mealB: 'Lunch',
mealC: 'Dinner'
};
for (let mealName of Object.values(meals)) {
console.log(mealName);// 'Breakfast' 'Lunch' 'Dinner
}
4.Object.getOwnPropertyNames
Object.getOwnPropertyNames() 也是 ES5 新增的一个对象方法,该方法返回对象自身属性名组成的数组,包括不可 枚举的属性,也可以通过数组的 forEach 方法来遍历
// 创建一个对象并指定其原型,bar 为原型上的属性
// baz 为对象自身的属性并且不可枚举
const obj = Object.create({
bar: 'bar'
}, {
baz: {
value: 'baz',
enumerable: false
}
})
obj.foo = 'foo'
// 不包括不可枚举的 baz 属性
Object.keys(obj).forEach((key) => {
console.log(obj[key]) // foo
})
// 包括不可枚举的 baz 属性
Object.getOwnPropertyNames(obj).forEach((key) => {
console.log(obj[key]) // baz, foo
})
事件流
1.事件流:事件流描述的是从页面中接收事件的顺序。
网景:netscape 事件捕获,事件由最不具体的元素先接收,传播到最具体的元素。
微软:microsoft 事件冒泡,事件由最具体的元素接收,传播到最不具体元素,直到document.
2.事件委托(事件代理)
事件委派:利用事件冒泡的原理,将子元素的事件委托给父元素执行.
原型/原型链,请简述prototype、proto constructor三者的关系
//原型的作用: 数据共享,节约内存扩展属性和方法可以实现类之间的继承
1.每一个函数都有一个prototype这个属性,而这个属性指向一个对象,这个对象我们叫做原型对象
2._proto_:每一个对象都有一个_proto_属性,_proto_ 指向创建自己的那个构造函数的(prototype)原型对象,因此对象可以直接访问到prototype里面的属性和方法
3.constructor:是原型对象中的指向自己的那个构造函数
//总结: 当我们创建一个构造函数的时候这个构造函数自带了一个prototype属性,而这个属性指向一个对象,也就是原型对象。 这个原型对象里面有一个constructor构造器,它的作用是指向创建自己的构造函数。除此之外 prototype还可以存放公共的属性和方法。 当我们实例化一个对象的时候(被new调用的时候),这个对象自带了一个_proto_属性,这个_proto_指向创建自己的构造函数的原型对象。可以使用这个原型对象里面的属性和方法
原型链:
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数中的prototype,如果还没有找到就会在构造函数的prototype的__proto__中查找,这样一层一层的向上查找就会形成一个链式结构,我们称为原型链。
new操作符都做了什么
1、创建一个空对象,并且 this 变量引用该对象,// let target = {};
2、继承了函数的原型。// target.proto = func.prototype;
3、属性和方法被加入到 this 引用的对象中。并执行了该函数func// func.call(target);
4、新创建的对象由 this 所引用,并且最后隐式的返回 this.
继承的方式--------------
//通过改变原型指向实现的继承
//缺陷:因为改变原型指向的同时实现继承,直接初始化了属性,继承过来的属性的值都是一样的了,所以,这就是问题
//只能重新调用对象的属性进行重新赋值,
function Person(name,age,sex,weight) {
this.name=name;
this.age=age;
this.sex=sex;
this.weight=weight;
}
Person.prototype.sayHi=function () {
console.log("您好");
};
function Student(score) {
this.score=score;
}
//希望人的类别中的数据可以共享给学生---继承
Student.prototype=new Person("小明",10,"男","50kg");
var stu1=new Student("100");
console.log(stu1.name,stu1.age,stu1.sex,stu1.weight,stu1.score);
stu1.sayHi();
var stu2=new Student("120");
stu2.name="张三";
stu2.age=20;
stu2.sex="女";
console.log(stu2.name,stu2.age,stu2.sex,stu2.weight,stu2.score);
stu2.sayHi();
var stu3=new Student("130");
console.log(stu3.name,stu3.age,stu3.sex,stu3.weight,stu3.score);
stu3.sayHi();
//构造函数继承
//借用构造函数:构造函数名字.call(当前对象,属性,属性,属性....);
//解决了属性继承,并且值不重复的问题
//缺陷:父级类别中原型的方法不能继承
function Person(name, age, sex, weight) {
this.name = name;
this.age = age;
this.sex = sex;
this.weight = weight;
}
Person.prototype.sayHi = function () {
console.log("您好");
};
function Student(name,age,sex,weight,score) {
//借用构造函数
Person.call(this,name,age,sex,weight);
this.score = score;
}
var stu1 = new Student("小明",10,"男","10kg","100");
console.log(stu1.name, stu1.age, stu1.sex, stu1.weight, stu1.score);
var stu2 = new Student("小红",20,"女","20kg","120");
console.log(stu2.name, stu2.age, stu2.sex, stu2.weight, stu2.score);
var stu3 = new Student("小丽",30,"妖","30kg","130");
console.log(stu3.name, stu3.age, stu3.sex, stu3.weight, stu3.score);
//组合继承:原型继承+借用构造函数继承
//属性和方法都被继承了
function Person(name,age,sex) {
this.name=name;
this.age=age;
this.sex=sex;
}
Person.prototype.sayHi=function () {
console.log("阿涅哈斯诶呦");
};
function Student(name,age,sex,score) {
//借用构造函数:属性值重复的问题
Person.call(this,name,age,sex);
this.score=score;
}
//改变原型指向----继承
Student.prototype=new Person();//不传值
Student.prototype.eat=function () {
console.log("吃东西");
};
var stu=new Student("小黑",20,"男","100分");
console.log(stu.name,stu.age,stu.sex,stu.score);
stu.sayHi();
stu.eat();
var stu2=new Student("小黑黑",200,"男人","1010分");
console.log(stu2.name,stu2.age,stu2.sex,stu2.score);
stu2.sayHi();
stu2.eat();
//ES6中的class继承
extends super两个关键字
子类必须在`constructor`方法中调用`super`方法,否则新建实例时会报错。这是因为子类自己的`this`对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用`super`方法,子类就得不到`this`对象。super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
普通构造函数constructor与class的区别??
Class在语法上更贴合面向对象的写法。
Class实现继承更加易读、易理解。
更易于写java等后端语言的使用。
本质是语法糖,使用prototype。
Ky跨域出现的原因/解决方法??
//跨域:由于浏览器的同源策略,-域名不同-协议不同-端口不同,即属于不同域的⻚面之间不能相互访问各自的⻚面内容。
1.CORS服务端代理(普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置) 后端在响应头上设置Access-Control-Allow-Origin
具体的方法是在服务端设置Response Header响应头中的Access-Control-Allow-Origin为对应的域名,实现了 CORS(跨域资源共享)
2.Nginx反向代理
实现思路:通过nginx配置一个代理服务器做跳板机,反向代理访问服务器接口,实现跨域登录。
3.jsonp:原理利用浏览器的"漏洞" src不受同源策略的影响,可以请求任何链接 。动态创建script标签,将事先写好的函数名传给服务器,供服务器使用(但是只限于get 请求。)
4.Nodejs中间件代理跨域
利用node + webpack + webpack-dev-server代理接口跨域。
5.服务器代理进行跨域 vue.config.js
devServer: {
port: 8080,
proxy: {
'/api': {
target: `https://api.iynn.cn/film`,
changeOrigin: true
}
}
}
CORS解决跨域(xhr2)
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源(协议 + 域名 + 端口)服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
需要服务器(提供接口的源码里面)添加下面两句话。
header('Access-Control-Allow-Origin:*');
header('Access-Control-Allow-Method:POST,GET');
jsonp是一种非正式传输协议,用于解决跨域问 题流程: 1、创建一个全局函数 2、创建一个script标签 3、给script添加src 4、给src添加回调函数test(callback=test) callback是传给后端的一个参数 5、将script放到⻚面上 6、script请求完成,将自己从⻚面上删除
跨域怎么携带cookie
withCredentials属性(withCredentials是XMLHttpRequest的一个属性)
默认值为false,获取同域资源时设不设置没有影响
true:跨域请求时,会携带用户凭证(在需要跨域携带cookie时,要把withCredentials设置为true)
false:在跨域请求时,不会携带用户凭证,返回的response里也会忽略cookie
//解析:
默认情况下,一般浏览器的CORS跨域请求都是不会发送cookie等认证信息到服务端的,除非指定了xhr.withCredentials = true,但是只有客户端单方面的设置了这个值还不行,服务端也需要同意才可以,所以服务端也需要设置好返回头Access-Control-Allow-Credentials: true;还有一点要注意的,返回头Access-Control-Allow-Origin的值不能为星号,必须是指定的域,否则cookie等认证信息也是发送不了。
闭包原理/优点/缺点/使用场景??
// 什么是闭包?用途?注意的地方?
1、闭包:打破了作用域链的规则 闭包就是能够读取其他函数内部变量的函数
2、用途:a:可以读取函数内部的局部变量 b:让这些变量始终保持在内存当中
3、注意:由于闭包会使得函数中的变量都被保存在内存当中,内存会消耗很大,所以不能够滥用
// 优点
不会污染全局环境,可以重复使用变量,并且不会造成变量污染,
// 缺点
比普通函数更占用内存,会导致网页性能变差,不恰当使用会造成内存泄漏
// 闭包的常用场景
1.通过循环给页面上多个dom节点绑定事件 (在事件函数中使用当次循环的值或节点,而不是最后一次循环的值或节点)
2.对象设置私有变量并且利用特权方法去访问私有属性
function Fun(){
var name = 'tom';
this.getName = function (){
return name;
}
}
var fun = new Fun();
console.log(fun.name);//输出undefined,在外部无法直接访问name
console.log(fun.getName());//可以通过特定方法去访问
3.节流防抖
前端堆/栈|---------深/浅拷贝及方法
堆--引用类型地址传递
堆:动态分配的内存,大小不定,不会自动释放。**存放引用类型的值 先进后出FILO**
栈--基本类型值传递
栈:自动分配内存空间,系统自动释放,**存放基本类型的值和引用类型的地址 先进先出FIFO**
基本类型: Undefined、Null、Boolean、Number 和 String, Symbol(ES6新增)
访问方法: 按值访问, 直接操作内存中的值
引用类型: Object(Arrary, Date, Math,Function, Object)
访问方法: JS不能直接访问内存中的值, 只能操作对象的地址, 所以产生深/浅拷贝问题**
深/浅拷贝针对的是 **引用类型**
浅拷贝--新旧互相影响,改变的是地址
新值===原值, 只能拷贝一层
> 1. 数组方法 `slice截取`, `concat拼接`, `filter过滤` , `map`,
> 2. 对象方法 `Object.assign({ },obj)`,
> 3. 展开运算符`{…obj}`
深拷贝--新旧互不影响,改变的是值
新值=/=原值 , 可以拷贝多层
> 1. JSON序列化 **JSON.parse( JSON.stringify(obj) )** 对象-->字符串-->对象
缺陷:1.如果对象里有RegExp对象,则序列化的结果将只得到空对象;
2.如果对象里有函数,undefined,则序列化的结果会把函数或 undefined丢失;
3.对象中含有NaN,则序列化的结果会变成null
> 2. 原生实现
> - 递归
// 递归实现深拷贝
function cloneDeep(obj) {
// 1.根据obj为对象或者数组,产生一个空的对象或数组,存放数据
var newobj = obj instanceof Array ? [] : {},
// 2.for...in进入循环
for (var k in obj) {
// 3.判断对象的第一个属性是否为数组或者对象,如果是,则进入递归
if (typeof obj[k] === 'object') {
newobj[k] = cloneDeep(obj[k])
} else {
// 4.如果数据为基本类型,则直接赋值
newobj[k] = obj[k]
}
}
// 5.把存放了数据的新对象返回出去
return newobj
}
> 3. 工具实现【 第三方封装库 】
> - loadsh _.cloneDeep(obj)
> - 缺点
> - Immutable
4.Object.create()
promise-------async...await-------Generator??
异步编程的一种解决方案,是一个对象。
好处:
可以避免多层异步调用嵌套问题(回调函数)
Promise对象提供了简洁的API,使控制异步操作更加容易
//Promise 是ES6提出的异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
Promise对象有以下两个特点。
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、resolve(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为resolve和从pending变为rejected。
//有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的API比如then,catch,使得控制异步操作更加容易。
/*
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
*/
then参数中的函数返回值,返回新的Promise实例对象,返回的该实例对象会调用下一个then,返回普通值,返回的普通值会直接传递给下一个then,通过then参数中函数的参数接收该值,(产生一个默认promise对象,从而保证下一个then可以链式操作)
//Promise.all()和Promise.race()
Promise.all()方法,该方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后并且执行结果都是成功的时候才执行回调。
接收一个数组作为参数,只有数组里面的每个promise对象的状态都变成resolve,则新的 Promise 实例状态才会变成resolve.
//这样以后就可以用all并行执行多个异步操作,并且在一个回调中处理所有的返回数据,比如你需要提前准备好所有数据才渲染页面的时候就可以使用all,执行多个异步操作将所有的数据处理好,再去渲染.
Promise.race()方法和all是相反的,谁先执行完成就先执行回调。先执行完的不管是进行了race的成功回调还是失败回调,其余的将不会再进入race的任何回调
race的使用比如可以使用在一个请求在10s内请求成功的话就走then方法,如果10s内没有请求成功的话进入reject回调执行另一个操作。
//请求某个table数据
function requestTableList(){
var p = new Promise((resolve, reject) => {
//去后台请求数据,这里可以是ajax,可以是axios,可以是fetch
resolve(res);
});
return p;
}
//延时函数,用于给请求计时 10s
function timeout(){
var p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('请求超时');
}, 10000);
});
return p;
}
Promise.race([requestTableList(), timeout()]).then((data) =>{
//进行成功回调处理
console.log(data);
}).catch((err) => {
// 失败回调处理
console.log(err);
});
请求一个接口数据,10s内请求完成就展示数据,10s内没有请求完成就提示请求失败
这里定义了两个promise,一个去请求数据,一个记时10s,把两个promise丢进race里面赛跑去,如果请求数据先跑完就直接进入.then成功回调,将请求回来的数据进行展示;如果计时先跑完,也就是10s了数据请求还没有成功,就先进入race的失败回调,就提示用户数据请求失败进入.catch回调,(ps:或者进入reject的失败回调,当.then里面没有写reject回调的时候失败回调会直接进入.catch)
//async/await 是一种特殊的语法,能够更好的处理promise,可以让你编写基于Promise的代码像同步一样,async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。
await
await关键字只能在async函数中使用。可以用来等待Promise状态变成resolved并有返回值。await后面通常跟的是一个promise对象,如果不是,会立即被包装成resoled状态的promise。
如果await后面的 promise 正常resolve,await promise便会返回结果。但是在reject的情况下,便会抛出异常,并且这种异常需要用try/catch来捕获,否则会导致进程崩溃。
//总结:
async函数之前的关键字有两个作用:
使它总是返回一个promise。
允许在其中使用await。
await promise之前的关键字使得JavaScript等待,直到这个promise的状态为resolved
如果是reject,则产生异常,需通过try/catch捕获。
否则,它返回结果,所以我们可以将它的值赋值给一个变量。
async/await使我们少写promise.then/catch,但是不要忘记它们是基于promise的。
//Generator/yield
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator 最大的特点就是可以控制函数的执行。
语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
yield是暂停标志,每次程序运行到yield时都会暂停,等待下一次指令的执行;它只能在generator函数里,后面跟着一个表达式。
Generator 返回值是一个遍历器对象。
next方法可启动,调用next方法会得到结构为一个内含value和done属性的对象,value是yield后面表达式的值,done是遍历是否结束的标志位
只有执行了next才会开始调用generator函数,yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
fetch和axios的区别??
axios是一种对ajax的封装,fetch是一种浏览器原生实现的请求方式,跟ajax对等
Fetch:可以很好的实现分离原则。 也原生支持目前流行的异步流(promise) 模型,但是很多平台都不实现,不能进行复杂的io操作 Fetch携带cookie credentials: "include"
https://cloud.tencent.com/developer/article/1359550
axios怎么取消请求
原生中有一个取消的方法,可以调用XMLHttpRequest对象上的abort方法 在axios拦截器里, 查找axios的文档,发现 可以通过使用CancelToken来取消axios发起的请求
回流/重绘 | 防抖/节流??
//什么是重绘?回流(重排)?
1. 当页面中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流。每个页面至少需要一次回流,就是在页面第一次加载的时候。
2. 当页面中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。
注:回流必将引起重绘,而重绘不一定会引起回流。
//什么时候会发生回流?
当页面布局和几何属性改变时候就需要回流。下面的情况会引发浏览器的回流:
1:添加或者删除可见的DOM元素
2:元素位置改变;
3:元素尺寸改变----边距、填充、边框、高度和宽度
4:内容改变---比如文本改变或者图片大小改变而引起的计算值宽度和高度改变
5:页面渲染初始化
6:浏览器窗口尺寸改变---resize事件发生时
//什么时候发生重绘?
1.改变字体
2.增加或者移除样式表
3.内容变化(input框输入文字)
4.激活css伪类 eg :hover
5.计算offsetWidth、offsetHeigth属性(。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。浏览器的可见高度)
6.设置style属性的值
//如何减少回流、重绘
1.使用DocumentFragment进行缓存操作,引发一次回流和重绘
2. csstext(利用cssText属性合并所有改变,然后一次性写入)
3.使用trsansform使用改变位置(margin-left等)这些更加流畅
4.如果你要改变多个属性,最好将这些属性定义在一个class中,直接修改class名,这样只用引起一次回流。
//防抖和节流
//函数防抖:当事件被触发一段时间后再执行回调,如果在这段时间内事件又被触发,那么就重新计时,只会执行一次。
在事件触发时,开始计时,在规定的时间(delay)内,若再次触发事件,将上一次计时(timer)清空,然后重新开始计时。保证只有在规定时间内没有再次触发事件之后,再去执行这个事件。 1次
//函数节流(throttle):连续触发事件但是在指定时间内中只执行一次函数
在事件触发之后,开始计时,在规定的时间(delay)内,若再次触发事件,不对此事件做任何处理。保证在规定时间内只执行一次事件.
https://blog.csdn.net/qq_40421277/article/details/87990882
//函数防抖的应用场景
连续的事件,只需触发一次回调的场景有:
搜索框搜索输入。只需用户最后一次输入完,再发送请求
手机号、邮箱验证输入检测
窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
//函数节流的应用场景
间隔一段时间执行一次回调的场景有:
滚动加载,加载更多或滚到底部监听
谷歌搜索框,搜索联想功能
高频点击提交,表单重复提交
事件循环EventLoop
在js中,所有任务都分为同步任务和异步任务两大类。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
1.从宏任务开始,script整体代码属于宏任务,开始执行代码,script整个放入"调用栈"里处理
2.遇到异步任务,交给web api处理,web api提供"异步机制",安排异步任务去哪个队列中,括宏任务队列和微任务队列
3.当script整体代码执行完了,也就是这个宏任务执行完毕,会清空调用栈,web api开始分配任务队列,然后去检查"微任务队列",如果微任务队列里有任务,就会把微任务队列里的任务拿到调用栈中执行
4.微任务队列清空之后,去检查"宏任务队列",把宏任务拿到调用栈中执行,每执行完一个宏任务,都会询问一次微任务队列,如果微任务队列里有任务,就会去执行,如果微任务队列里没有任务,检查宏任务队列,当异步队列中没有任务,eventloop结束
只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)
宏任务包含: setTimeout,setInterval
微任务队列: Promise是同步 async awite Promise.then是异步 muationobserver, nextTick
有时候 setTimeout明明写的延时3秒,实际却5,6秒才执行函数,这又是因为什么?
答:setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。
浏览器的JS引擎遇到setTimeout,拿走之后不会立即放入异步队列,同步任务执行之后,timer模块会到设置时间之后放到异步队列中。js引擎发现同步队列中没有要执行的东西了,即运行栈空了就从异步队列中读取,然后放到运行栈中执行。所以setTimeout可能会多了等待线程的时间。
这时setTimeout函数体就变成了运行栈中的执行任务,运行栈空了,再监听异步队列中有没有要执行的任务,如果有就继续执行,如此循环,就叫Event Loop。
this的理解
1.this是Javascript语言的一个关键字。
2.它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。
3.随着函数使用场合的不同,this的值会发生变化。但是有一个总的原则,那就是this指的是调用函数的那个对象。
//指向:
1.在浏览器里,在全局范围内this 指向window对象;
2.在函数中,this永远指向最后调用他的那个对象;
3.构造函数中,this指向new出来的那个新的对象;
4.call、apply、bind中的this被强绑定在指定的那个对象上;
5.箭头函数中this比较特殊,箭头函数this为父作用域的this,不是调用时的this.要知道前四种方式,都是调用时确定,也就 是动态的,而箭头函数的this指向是静态的,声明的时候就确定了下来;
5.apply、call、bind都是js给函数内置的一些API,调用他们可以为函数指定this的执行,同时也可以传参。
arguments 的对象是什么?
//arguments对象是函数中传递的参数值的集合。它是一个类似数组的对象,因为它有一个length属性,我们可以使用数组索引表示法arguments[1]来访问单个值,但它没有数组中的内置方法,如:forEach、reduce、filter和map。
我们可以使用Array.prototype.slice将arguments对象转换成一个数组。
Array.prototype.slice.call(arguments)
哪些操作会造成内存泄漏?
1.意外的全局变量
2.被遗忘的计时器或回调函数
3.脱离 DOM 的引用
4.闭包
第一种情况是我们由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
第二种情况是我们设置了setInterval定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
第三种情况是我们获取一个DOM元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它也无法被回收。
第四种情况是不合理的使用闭包,从而导致某些变量一直被留在内存当中。
cookie,localStorage,sessionStorage
//介绍
1.cookie又叫会话跟踪技术,是由web服务器保存在用户浏览器上的小文本文件,它可以记录用户ID、密码、浏览过的网页、停留的时间等信息。当用户再次来到该网站时,网站通过读取cookie,得知用户相关信息,如果用户清理了cookie,那么用户曾登录过的网站信息就没有了。
2.sessionStorage和localStorage是H5新增的本地存储
//区别
1.存储大小的不同
localStorage的大小一般为5M
sessionStorage的大小一般为5M
cookies的大小一般为4K
2.有效期不同:
localStorage的有效期为永久有效,除非你进行手动删除。
sessionStorage在当前会话下有效,关闭页面或者浏览器时会被清空。
cookies在设置的有效之前有效,当超过有效期便会失效。
3.与服务器端的通信
localStorage不参与服务器端的通信。
sessionStorage不参与服务器端的通信。
cookies参与服务器端通信,每次都会携带http的头信息中。(如果使用cookie保存过多数据会带来性能问题)
//应用场景
1.因为考虑到每个 HTTP 请求都会带着 Cookie 的信息,所以 Cookie 当然是能精简就精简啦,比较常用的一个应用场景就是判断用户是否登录。针对登录过的用户,服务器端会在他登录时往 Cookie 中插入一段加密过的唯一辨识单一用户的辨识码,下次只要读取这个值就可以判断当前用户是否登录啦。曾经还使用 Cookie 来保存用户在电商网站的购物车信息,如今有了 localStorage,现在基本更加方便
2.sessionStorage可以用来统计当前页面元素的点击次数。
3.localStoragese:常用于长期登录(判断用户是否已登录),适合长期保存在本地的数据。sessionStorage:敏感账号一次性登录;
//登陆信息用cookie好还是localStorage好?
1.建议登陆信息用 cookie。即设置过期时间的cookie,看法是 cookie 默认会发送回到后端,这样方便后端读取,而
使用localStorage,你需要在每次请求的时候,都手动带上这个信息,这大大增加了开发过程中带来的困难,非常麻烦,而且还要手动维护过期时间。
2.cookie有时效,而localStorage如果不手动清理则永久保存。
3.如果要设置关闭网页/标签就失效,请用SessionStorage。 这个类似你设置“不带时间的cookie”。
微任务和宏任务
宏任务:script(全局任务), setTimeout, setInterval, te, I/O, UI rendering.
微任务:process.nextTick (node.js中进程相关的对象), Promise, Object.observer, MutationObserver。
运行机制:
js是单线程执行遇到宏任务和微任务 会把他们放到微宏任务队列主线程 执行完微任务然后执行宏任务
微任务先运行
执行栈选择最先进入队列的宏任务(一般都是script),执行其同步代码直至结束;
检查是否存在微任务,有则会执行至微任务队列为空;
如果宿主为浏览器,可能会渲染页面;
开始下一轮tick,执行宏任务中的异步代码(setTimeout等回调)。
ES6
1.let------- const2 let和const声明形成块作用域,而var不存在此作用域3 let在相同作用域内,不允许重复声明同一个变量4 var声明变量存在变量提升,在代码块内,使用let和const不存在变量提升5 const:定义常量(不能修改),使用时必须 初始化(就是要赋值,比如:const a = 5;可以,不能只写const a;),只能在块级作用域里访问 62.结构赋值7 从数组和对象中提取值,对变量进行赋值,这被称为解构.83.扩展运算符9 扩展运算符用三个点号表示,功能是把数组或类数组对象展开成一系列用逗号隔开的值.104.模板字符串11#
5.箭头函数12 1.箭头函数是个匿名函数,不能作为构造函数,不能使用new13 2.箭头函数不能绑定arguments14 3.箭头函数没有原型属性15 4.箭头函数的this指向永远指向其上下文的this,没有办法改变其指向,而普通函数的this指向他的调用者16
6.数组和对象的扩展17 1.Array.from()方法用于将对象转为真正的数组(类数组转数组)18 2.Array.of()方法用于将一组值,转换为数组。19 console.log(Array.of(1,2,3,4,4,50));//[1, 2, 3, 4, 4, 50]20 3.Object.assign(目标对象,对象1,对象2)用于对象的合并,将源对象的所有可枚举属性,复制到目标对象。(浅拷贝)21 #7.Symbol--表示独一无二的值,它是js中的第七种数据类型。属于基本类型22 23 #8.Generators生成器函数24 总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。25 26 #9.Set结构和Map结构27 1.Set:它类似于数组,但是成员的值都是唯一的,没有重复的值。28 Set本身是一个构造函数,用来生成 Set 数据结构,数组作为参数。29 //数组去重30 // let arr=[1,2,3,4,5,6,6,5,4,3,2,1];31 // console.log([...new Set(arr)]);//[1, 2, 3, 4, 5, 6]32 33 2.Map数据结构。它类似于对象,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。34 35 10.promise36 #11.class37 12.模块化js
ES2017新增 ES8
1、async await
2、对象尾逗号 es2017允许函数对象的定义调用时参数可以加入尾逗号,以及json对象array对象都允许
3、.String.padStart和String.padEnd
padStart:[length,string] 在字符串首位开始添加string直到满足length为止并返回新的字符串;
console.log ( "test".padStart (8,"123456789123") )//1234test
4、.Object.values/Object.entries/Object.getOwnpropertyDescriptors
values: [obj],返回obj自身可枚举属性的属性值的集合;
entries:[obj], 与values类似,返回的一个2元素的数组
getOwnpropertyDescriptors: [obj],返回obj对象的属性描述符
5、共享内存、原子对象
ES2018新增 ES9
1.Promise.finally()
无论 promise 执行成功还是失败,finally() 都会运行其中的代码:
2.rest(剩余)属性和Spread(展开)属性
const { first, second, ...others } = { first: 1, second: 2, third: 3, fourth: 4, fifth: 5 }
const items = { first, second, ...others }
3.异步迭代
新的构造函数 for-await-of 允许你使用异步可迭代对象作为循环迭代:
for await (const line of readLines(filePath)) {
console.log(line)
}
因为这里用了 await ,所以你只能在 async 函数内部使用它,就像一个普通的 await(参考 async/await)。
ES2020新增 ES11
1.空值合并运算符(双问号操作符)
如果我们想要的变量为 undefined 或 null 则必须给变量设置默认值
所以双问号可以更准确得去处理 null 和 undefined
const a = b ?? 123; //仅在第一项为 null 或 undefined 时设置默认值
2.可选操作符
如果要访问对象的深层嵌套属性,则必须通过很长的布尔表达式去检查每个嵌套级别中的属性。必须检查每个级别中定义的每个属性,直到所需的深度嵌套的属性为止,如下代码所示
let name = user && user.info && user.info.name;
如果在任何级别的对象中都有 undefined 或 null 的嵌套对象,如果不进行检查,那么的程序将会崩溃。这意味着我们必须检查每个级别,以确保当它遇到 undefined 或 null 对象时不会崩溃。
使用可选链运算符,只需要使用 ?. 来访问嵌套对象。而且如果碰到的是 undefined 或 null 属性,那么它只会返回 undefined。通过可选链,可以把上面的代码改为:
let name = user?.info?.name;
3.BigInt 一种新的数据类型 可以解决大数字精度丢失的问题
4.动态导入import()
在项目中,某些功能可能很少使用,而导入所有依赖项可能只是浪费资源。现在可以使用动态导入依赖项。
import()函数返回一个Promise对象,加载模块成功以后,这个模块会作为一个对象,当作then回调的参数,import()是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块
5.Promise.allSettled()
Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个 对象数组,每个对象表示对应的promise结果。
当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。
相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个reject时立即结束。
三.Vue2.0
mvvm和mvc的区别
1.mvc
MVC包括view视图层、controller控制层、model数据层。各部分之间的通信都是单向的。
View(视图层) 传送指令到 Controller(控制层),Controller(控制层) 完成业务逻辑后,要求 Model(数据层) 改变状态 Model(数据层) 将新的数据发送到 View(视图层),用户得到反馈
2.mvvm
MVVM包括view视图层、model数据层、viewmodel(监听数据的改变和控制视图的东西,用来同步View 和 Model的对象)。
采用双向数据绑定,它实现了View和Model的自动同步,也就是当Model的属性改变时,我们不用再自己手动操作Dom元素,来改变View(视图)的显示,而是改变属性后该属性对应View(视图)显示会自动改变。
vue双向绑定原理
Vue是采用数据劫持结合发布者/订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给watcher(订阅者),触发相应的监听回调,实现数据和视图的同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变
diff算法
通过新旧dom比较,计算出Virtual DOM(虚拟DOM)中真正变化的部分,将变化的部分更新到真实的dom上,只针对该部分进行原生DOM操作,而非重新渲染整个页面
$nextTick的使用
1.Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM挂载已完成。
2.当项目中你想在改变DOM元素的数据后基于新的dom做点什么,对新DOM一系列的js操作都需要放进Vue.nextTick()的回调函数中;通俗的理解是:更改数据后当你想立即使用js操作新的dom的时候使用它
Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOm操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
当你设置 vm.someData = 'new value',DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
当你修改了data的值然后马上获取这个dom元素的值,是不能获取到更新后的值,需要使用$nextTick这个回调,让修改后的data值渲染更新到dom元素之后在获取,才能成功。
ref
//获取dom,用来操作dom
<div ref="domName">11111</div> 用法:this.$refs.domName
v-modal的使用及原理
v-model用于表单数据的双向绑定,其实它就是一个语法糖,这个背后就做了两个操作:
v-bind绑定一个value属性;
v-on指令给当前元素绑定input事件。
他就是v-bind和v-on的一个语法糖,v-on绑定相应的事件在输入框内容改变时触发回调,将最新的值保存
vue组件传值
1.父传子:
在父组件的子组件标签上通过v-bind绑定要传递的数据,然后在子组件内部通过props接收。
2.子传父:
在子组件中定义一个方法,然后在方法里面通过this.$emit进行传递(使用$emit进行传递(emit有两个参数(自定义事件名称,要传递的数据))。
//子组件
selectVal (val) {
this.$emit('parentReceive', val) // 第一个参数:自定义事件名称;第二个参数要传递的数据
}
在父组件的子组件标签上通过@自定义事件名称监听,然后通过回调函数去处理响应的逻辑:
// 父组件
<template>
<select-list :dataList="dataArr" @parentReceive="changeVal"/>
</template>
<script>
changeVal (val) {
console.log(`我是子组件传递过来的数据${val}`)
}
</script>
3.兄弟传值
4.子孙传值
provide / inject
适用于隔代组件通信祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
如果是单一的只是拿数据使用,在父组件定义,则在所有子组件都能为之所用
官网不建议在应用中直接使用该办法,理由很直接:他怕你"管不好"
1.一般情况使用都是在app.vue配置为:
provide () {
return {
isTest: 'aaa'
}
},
所有子组件都可以引用 拿到app.vue里面的所有数据
inject: ['isTest'],
5.中央事件总线(bus)实现跨组件通信。
(1).创建Vue实例(把bus放在vue原型上进行访问)
Vue.prototype.bus = new Vue();
或者const Bus = new Vue();
(2).传递事件
this.bus.$emit("aaa",this.data)
(3).监听事件
this.bus.$on("aaa",()=>{
console.log('触发事件的回调')
})
6.vuex
vuex可以实现多组件的状态管理,多个组件之间可以数据共享。
vuex有5个核心,其中state和mutation是必须的,其他的可以按需求添加。
(1)state
负责状态管理,类似于vue中的data,用于初始化数据。
(2)mutation
唯一能够操作state的数据,通过commit触发。
(3)action
用于处理异步操作,通过dispatch触发,不能直接修改state的数据,首先要在组件中通过dispatch触发 action,然后在action函数内部通过commit触发mutation,然后就可以通过mutation来修改state的数据。
(4)getter
vue中的计算属性,相当于vue中的computed,依赖于state的状态值,状态值一旦改变,getter就会重新计 算,也就是说,当一个数据依赖于另一个数据发生变化时,就要使用getter。
(5)module
主要就是模块化管理。
vue生命周期
vue实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载DOM→渲染、更新→渲染、卸载等一系列过程,我们称这是Vue的生命周期。
beforeCreate阶段:
vue实例的挂载元素el、data、computed、watch、methods都为undefined,还未初始化。
可以在这加个loading事件,在加载实例时触发
created阶段:
vue实例创建完成data、computed、watch、methods已经可以访问了,el还没有挂载
如在这结束loading事件,一般异步请求就在这里调用
beforeMount阶段:
虚拟DOM已经加载完毕,但是对应的真实DOM还未加载,在这里面是获取不到真实DOM结点的.
mounted阶段:
真实DOM、数据、事件处理对象已经加载完毕,可以进行对应的修改。从这里开始进行数据修改都会触发update函数
挂载元素,获取到DOM节点
更新前/后:
当data变化时,会触发beforeUpdate和updated方法。
beforeUpdate:
这里访问更新之前的DOM,
千万不要在这里面进行数据修改,否则会陷入死循环!
updated:
当这个钩子被调用时,组件 DOM 已经更新,可以对DOM进行操作。在大多数情况下,应该避免在这里更改状态。 如果想改变状态,通常最好使用计算属性或watcher
在这里一样不能进行数据修改!
销毁前/后:
beforeDestroy:
实例销毁之前调用。在这一步,实例仍然完全可用。
适合移除事件、定时器等等
destroyed:
Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实 例也会被销毁。
nextTick:更新数据后触发此回调函数,在这里获取更新后dom的数据,进行dom操作
created:实例已经创建完成之后调用,在这一步,实例已经完成数据观测、属性和方法的运算,watch、event事件回调,然而,挂载阶段还没有开始,$el属性目前还不可见
mounted:el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子,如果root实例挂在了一个文档内元素,当mounted被调用时vm.$el也在文档内。
keep-alive 独有的生命周期,分别为 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数。
更新data数据并刷新视图
在vue中,data中有一个msg的变量,你修改它,那么在页面上,msg的内容就会自动发生变化。但是如果对于一个复杂的对象,例如一个对象数组,你直接去给数组上某一个元素增加属性,或者直接把数组的length变成0,vue无法知道发生了改变。
this.$set()
this.$set(数组,下标,值)
this.$set(对象,key,value值)
$forceUpdate是强制更新
this.$forceUpdate();
data为什么必须是函数
data是对象的话,对象属于引用数据类型,会影响到所有的实例,
data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响
key的作用
在使用v-for的时候,一定要设置key值,并且保证每个key值是独一无二的,
这便于diff算法进行优化,为了高效的更新虚拟DOM,
其原理是vue可通过key可以精准判断两个节点是否相同,
从而避免频繁更新,减少不必要的DOM操作量,提高性能。
v-if 和 v-for优先级和产生的问题
v-for优先级比v-if高
带来性能方面的浪费(每次渲染都会先循环再进行条件判断)
如果是判断整个列表是否显示,可以在列表外加一层进行判断在进行循环
如果是判断列表内,部分数据显示,最好在展现前将数据筛选一遍;
v-if 和 v-show的区别
-----区别-----
- 1.手段:v-if是通过控制dom节点的存在与否来控制元素的显隐;
v-show是通过设置DOM元素的display样式,block为显示,none为隐藏;
- 2.编译过程:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;
v-show只是简单的基于css切换;
- 3.编译条件:v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载);
v-show是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留;
- 4.性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;
-----使用场景-----
基于以上区别,因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
注意:v-show 不支持<template>语法,也不支持v-else语法
vue 中的计算属性(computed)
计算属性会进行缓存,如果多次使用时,计算属性只会调用一次,只有依赖数据发生改变时才会重新求值
不支持异步,当computed内有异步操作时无效,无法监听数据的变化
对于任何复杂逻辑,你都应当使用计算属性
每个计算属性都包含一个getter和一个setter
return一个值
vue中的监听(watch)
当我们需要在数据变化时执行异步或开销较大的操作时,
使用 watch 可以执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的
监听引用数据类型(或数组中嵌套对象)需要深度监听deep:true
vue中computed和watch的区别和使用场景
计算属性计算结果会被缓存,只有依赖数据发生改变,才会重新进行计算,避免了大量重复计算
watch不支持缓存,数据变或者触发重新渲染时,直接会触发相应的操作。
watch支持异步
computed异步操作时无法监听数据的变化的值
computed中的函数必须要用return返回
watch中的函数不是必须要用return
使用场景:
computed-当一个属性受多个属性影响的时候,使用computed-------购物车商品结算。
watch--当一条数据影响多条数据的时候,使用watch-------搜索框
vue混入(mixins)的使用
Mixin类通常作为功能模块使用,在需要该功能时“混入”,有利于代码复用又避免了多继承的复杂
Mixin本质其实就是一个js对象,它可以包含我们组件中任意功能选项,如data、components、methods、created、computed等等
我们只要将共用的功能以对象的方式传入 mixins选项中,当组件使用 mixins对象时所有mixins对象的选项都将被混入该组件本身的选项中来
在Vue中我们可以局部混入跟全局混入
局部混入:
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
Vue.component('componentA',{
mixins: [myMixin]
})
全局混入:
Vue.mixin({
created: function () {
console.log("全局混入")
}
})
使用场景:
在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立
这时,可以通过Vue的mixin功能将相同或者相似的代码提出来
例如:弹窗组件,提示框
vue-router的两种模式及区别
hash模式:
#后面 hash 值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面。每次 hash 值的变化,会触发hashchange这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化。然后我们便可以监听hashchange来实现更新页面部分内容的操作:
hash模式背后的原理是onhashchange事件,可以在window对象上监听这个事件:
history模式: mode :history
因为HTML5标准发布,多了两个 API,pushState() 和 replaceState()。通过这两个 API (1)可以改变 url 地址且不会发送请求,(2)不仅可以读取历史记录栈,还可以对浏览器历史记录栈进行修改。
除此之外,还有popState().当浏览器跳转到新的状态时,将触发popState事件.
history.go(-2);//后退两次
history.go(2);//前进两次
history.back(); //后退
hsitory.forward(); //前进
区别:
前面的hashchange,你只能改变#后面的url片段。而pushState设置的新URL可以是与当前URL同源的任意URL。
history模式则会将URL修改得就和正常请求后端的URL一样,如后端没有配置对应/user/id的路由处理,则会返回404错误
当用户刷新页面之类的操作时,浏览器会给服务器发送请求,所以这个实现需要服务器的支持,需要把所有路由都重定向到根页面。
history模式怕啥
不怕前进,不怕后退,就怕刷新,(如果后端没有准备的话),因为刷新是实实在在地去请求服务器的。
在history模式下,你可以自由的修改path。history模式最终的路由都体现在url的pathname中,这部分是会传到服务器端的,因此需要服务端对每一个可能的path值都作相应的映射。
当刷新时,如果服务器中没有相应的响应或者资源,会分分钟刷出一个404来。
router的区别
1. $route从当前router跳转对象里面可以获取name、path、query、params等(<router-link>传的参数由 this.$route.query或者 this.$route.params 接收)
2. $router为VueRouter实例。想要导航到不同URL,则使用$router.push方法;返回上一个history也是使用$router.go方法
路由传参及params和query的区别
this.$router.push或者用<router-link to="/跳转路径/传入的参数"></router-link>
1.params只能用name来引入路由,query用path/name来引入
2.params类似于post,query更加类似于我们ajax中get传参,在浏览器地址栏中不显示参数,后者显示,所以params传值相对安全一些。
3.取值用法类似分别是this.$route.params.name和this.$route.query.name。
4.params传值一刷新就没了,query传值刷新还存在
vue-router中的几种导航钩子
1.导航钩子的作用
vue-router提供的导航钩子主要用来拦截导航,让它完成跳转或取消。
2.导航钩子的分类
全局守卫:
主要有两种钩子:前置守卫、后置钩子
beforeEach和afterEach
路由独享守卫:
单个路由独享的导航钩子,它是在路由配置上直接进行定义的
beforeEnter: (to, from ,next) => {}
组件内路由守卫:
是指在组件内执行的钩子函数,类似于组件内的生命周期
beforeRouteEnter: (to, from ,next) {}//路由进入之前调用
beforeRouteUpdate: (to, from ,next) {}//在当前路由改变时,并且该组件被复用时调用,就是传参改变路由,这个组件被复用
beforeRouteLeave: (to, from ,next) //导航离开该组件的对应路由时调用
vue-router中的钩子有三种
全局导航钩子 router.beforeEach 可以做全局校验如果有权限问题可以跳到指定的页面
路由中配置的单个导航钩子
组件内部的导航钩子
当点击切换路由时:beforeRouterLeave-->beforeEach-->beforeEnter-->beforeRouteEnter-->beforeResolve-->afterEach-->beforeCreate-->created-->beforeMount-->mounted-->beforeRouteEnter的next的回调
当路由更新时:beforeRouteUpdate
这三种钩子函数的参数用法一样
参数介绍
to:要进入的目标
from:当前正要离开的路由
next:这是一个必须需要调用的方法,而具体的执行效果则依赖 next 方法调用的参数
vue-router和location.href跳转区别
使用location.href='/url'来跳转,简单方便,但是刷新了页面;
使用history.pushState('/url'),无刷新页面,静态跳转;
引进router,然后使用router.push('/url')来跳转,使用了diff算法,实现了按需加载,减少了dom的消耗。
其实使用router跳转和使用history.pushState()没什么差别的,因为vue-router就是用了history.pushState(),尤其是在history模式下。
vue-router实现路由懒加载
import方法
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export const otherRouter ={
routes: [
{
path: '/',
name: 'HelloWorld',
component: () => import("@/components/HelloWorld")
}
]
}
vue异步组件实现懒加载:一般使用
component: resolve=>(require(["@/components/HelloWorld"],resolve))
vuex的使用
多个组件共享数据或者是跨组件传递数据时
使用流程:
页面通过mapAction异步提交事件到action。action通过commit把对应参数同步提交到mutation,mutation会修改state中对应的值。最后通过getter把对应值跑出去,在页面的计算属性中,通过,mapGetter来动态获取state中的值。
vuex几种属性:
有五种,分别是State , Getter , Mutation , Action , Module (就是mapAction)
1. state:vuex的基本数据,用来存储变量
2. geeter:从基本数据(state)派生的数据,相当于state的计算属性
3. mutation:提交更新数据的方法,必须是同步的(如果需要异步使用action)。每个mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数,提交载荷作为第二个参数。
4. action:和mutation的功能大致相同,不同之处在于 ==》1. Action 提交的是 mutation,而不是直接变更状态。 2. Action 可以包含任意异步操作。0000000000
5. modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
vue数据持久化
在使用vuex的时候 刷新以后里面存储的state就会被浏览器释放掉,因为我们的state都是存储在内存中的,所以一刷新页面就会把state中的数据重置,这就涉及到vue数据持久化的问题。
可以通过localstorage保存,或者通过 vuex-persistedstate这个插件,来实现将数据存储到本地,从而实现刷新后state仍然还存在。
把VUEX中的所有数据存到localStorage中
plugins: [createPersistedState(可配置)]
配置对象:
storage: window.sessionStorage //存储到sessionStorage
storage: {//使用cookie
getItem: key => Cookies.get(key),
setItem: (key, value) => Cookies.set(key, value, { expires: 7 }),
removeItem: key => Cookies.remove(key)
}
reducer(val) {
return {
assessmentData: val.assessmentData//只储存state中的assessmentData
}}
vue封装组件的过程
使用 Vue.extend 方法创建一个组件,然后使用 Vue.component 方法注册组件,子组件需要数据,
可以在 props 中接受定义,而子组件修改好数据后,想把数据传递给父组件,可以采用$emit 方法,
我自己可以预留一些插槽
vue3.0
1. 响应式系统提升
vue2在初始化的时候,对data中的每个属性使用definepropery调用getter和setter使之变为响应式对象。如果属性值为对象,还会递归调用defineproperty使之变为响应式对象。
vue3使用proxy对象重写响应式。proxy的性能本来比defineproperty好,proxy可以拦截属性的访问、赋值、删除等操作,不需要初始化的时候遍历所有属性,另外有多层属性嵌套的话,只有访问某个属性的时候,才会递归处理下一级的属性。
优势:
可以监听动态新增的属性;
可以监听删除的属性 ;
可以监听数组的索引和 length 属性;
2.包的体积变小了
Vue 3.0 的目标是让 Vue 核心变得更小、更快、更强大,因此 Vue 3.0增加以下这些新特性:
(1)监测机制的改变
3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言覆盖的反应性跟踪。这消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制:
只能监测属性,不能监测对象
检测属性的添加和删除;
检测数组索引和长度的变更;
支持 Map、Set、WeakMap 和 WeakSet。
新的 observer 还提供了以下特性:
用于创建 observable 的公开 API。这为中小规模场景提供了简单轻量级的跨组件状态管理解决方案。
默认采用惰性观察。在 2.x 中,不管反应式数据有多大,都会在启动时被观察到。如果你的数据集很大,这可能会在应用启动时带来明显的开销。在 3.x 中,只观察用于渲染应用程序最初可见部分的数据。
更精确的变更通知。在 2.x 中,通过 Vue.set 强制添加新属性将导致依赖于该对象的 watcher 收到变更通知。在 3.x 中,只有依赖于特定属性的 watcher 才会收到通知。
不可变的 observable:我们可以创建值的“不可变”版本(即使是嵌套属性),除非系统在内部暂时将其“解禁”。这个机制可用于冻结 prop 传递或 Vuex 状态树以外的变化。
更好的调试功能:我们可以使用新的 renderTracked 和 renderTriggered 钩子精确地跟踪组件在什么时候以及为什么重新渲染。
(2)模板
模板方面没有大的变更,只改了作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。
同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。
(3)对象式的组件声明方式
vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。
3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易。
此外,vue 的源码也改用了 TypeScript 来写。其实当代码的功能复杂之后,必须有一个静态类型系统来做一些辅助管理。
现在 vue3.0 也全面改用 TypeScript 来重写了,更是使得对外暴露的 api 更容易结合 TypeScript。静态类型系统对于复杂代码的维护确实很有必要。
四.React
vue和react的区别
1.vue是响应式的数据双向绑定系统,而react是单向数据流,没有双向绑定
2.vue多了指令系统,让模版可以实现更丰富的功能,而React只能使用JSX语法
3.react的思路是all in js,通过js来生成html,所以设计了jsx,还有通过js来操作css等;而 vue是把html,css,js组合到一起,用各自的处理方式,vue有单文件组件,可以把html、css、js写到一个文件中,html提供了模板引擎来处理。
4. react中每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过shouldComponentUpdate这个生命周期方法来进行控制,如果为true继续渲染、false不渲染,但Vue将此视为默认的优化。
webPack|前端自动化构建工具??
webpack与gulp:Gulp是基于流的前端自动化构建工具,基于流的任务式的工具。WebPack可以看做是模块打包机,它会把开发中的所有资源(图片,css,js等)都看成模块,通过loader(加载器)和plugins(插件)对资源进行处理,打包成符合生成环境的前端资源。
webpack热更新:不用刷新浏览器而将新变更的模块替换掉旧的模块。
模块热更新是webpack的一个功能,他可以使得代码修改过后不用刷新浏览器就可以更新,是高级版的自动刷新浏览器。
devServer中通过hot属性可以控制模块的热替换
//webpack的核心概念分为:入口(Entry)、加载器(loader)、插件(plugins)、出口(output)。
1.入口(Entry):入口起点告诉webpack从哪里开始,并根据依赖关系图确定需要打包的文件内容。
2.出口(output):输出配置
//(loader , plugin分别什么作用,哪个配置可以把依赖包抽离出来,不打包进去)**
3.Loader:用于对模块源码的转换,loader描述了webpack如何处理非js模块,并且在build中引入这些依赖。比如说:CSS-Loader,style-loader,babel-loader等。
有哪些常见的Loader?他们是解决什么问题的?
file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
//url-loader:图片打包,在图片很小的情况下以 base64 的方式把图片注入到代码中去
source-map-loader:加载额外的 Source Map 文件,以方便断点调试
//image-loader:加载并且压缩图片文件
//babel-loader:把 ES6 转换成 ES5,babel-loader优雅降级配置ES高版本转成低版本
//css-loader:将css文件编译成js,支持模块化、压缩、文件导入等特性
style-loader:把 CSS 代码注入到 JavaScript 中。
eslint-loader:通过 ESLint 检查 JavaScript 代码
4.plugin可以扩展webpack的功能,使得webpack更加灵活。可以在构建的过程中通过webpack的api改变输出的结果,在plugins中进行配置比如资源管理、bundle文件优化等操作
有哪些常见的Plugin?他们是解决什么问题的?
//html-webpack-plugin:html产出,可以生成创建html入口文件,为html文件中引入的外部资源如script、link动态 添加每次compile后的hash,防止引用缓存的外部文件问题
//extract-text-webpack-plugin:css抽离,将webpack编译过的css文件以css外部引用的形式引入,也就是说样式将 不再内嵌到 JS bundle中,而是会放到一个单独的CSS文件当中
//transform-remove-console:移除所有的console语句
commons-chunk-plugin:提取公共代码
//uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码
//webpack打包优化
1.使用 url-loader 优化, 将小图片转化成base64压缩,防止小图片太多请求次数太多。
2.压缩代码。uglifyJsPlugin压缩js代码, mini-css-extract-plugin 压缩css代码,html-webpack-plugin 对html压缩
3.多进程多实例构建,资源并行解析
thread-loader (推荐使用这个)
4.分离 css 文件并压缩 css 文件
使用 extract-text-webpack-plugin 插件将css文件分离出来。为了使项目加载时候尽早优先加载css样式,也为了解 决js文件体积过大的问题
5.使用 CDN 加载
通过 externals 加载外部 CDN 资源
默认情况下,通过 import 语法导入的第三方依赖包,最终会被打包合并到同一个文件中,从而导致打包成功后,单文件体积过大的问题。
为了解决上述问题,可以通过 webpack 的 externals 节点,来配置并加载外部的 CDN 资源。凡是声明在externals 中的第三方依赖包,都不会被打包。
(react,react-router,axios,lodash,echaers)
6.splitChunks
提取公共代码,抽离公共模块
在用webpack 打包的时候,对于一些不经常更新的第三方库,比如 react,lodash,vue 我们希望能和自己的代码分离开,这样既能减小打包的总体积,也能避免单个包体积过大。
受控组件和非受控组件
受控组件:根据用户的操作进行更新状态。他会自动将改变后的状态保存在react中,实现绑定一个on··change方法将改变后的状态通过setState进行保存
非受控组件:数据由DOM本身处理。即不受react,setState()的控制,只能使用 ref从DOM获取表单值
调用 React.createRef() 方法创建ref对象
将创建好的 ref 对象添加到文本框中
通过ref对象获取到文本框的值
setState概述(同步异步)
//1. 两个参数及用法
第一个参数可以是对象或者函数,是更新state 第二个参数获取最新的state,副作用操作,dom操作事件触发声明,数据获取,第三方库实例化
异步更新state,将短时间内的多个setState合并成一个
为了解决异步更新导致的问题,增加另一种形式的setState:接受一个函数作为参数,在函数中可以得到前一个状态并返回下一个状态
//2. 同步/异步原理
setState在合成事件和钩子函数中是异步的
1.保证内容的一致性。
2.可以达到性能优化。多个setState合并一起更新,减少render
在原生事件和setTimeout中是同步的
setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行.
//setState合并
为了性能优化,同一个方法中执行多次的setState,会合并到一个数组中,统一处理,减少render
短时间内快速进行了多次的setState操作时,我们的setState会合并成一个
https://www.jianshu.com/p/7ab07f8c954c
react合成事件
1. 合成事件原理
如果DOM上绑定了过多的事件处理函数,整个页面响应以及内存占用可能都会受到影响。React为了避免这类DOM事件滥用,同时屏蔽底层不同浏览器之间的事件系统差异,实现了一个中间层——SyntheticEvent。
当用户在为onClick添加函数时,React并没有将Click事件绑定在DOM上面。
而是在document处监听所有支持的事件,当事件发生并冒泡至document处时,React将事件内容封装交给中间层SyntheticEvent(负责所有事件合成)
所以当事件触发的时候,对使用统一的分发函数dispatchEvent将指定函数执行。
2. 与原生事件的区别
React合成事件一套机制:React并不是将click事件直接绑定在dom上面,而是采用事件冒泡的形式冒泡到document上面,然后React将事件封装给正式的函数处理运行和处理。
react如何性能优化
1.利用shouldcomponentUpdata钩子函数来判断组件是否需要渲染。
2.给dom遍历加上唯一的key值,尽量不要使用index。
3.函数的bind尽量写在constructor里,避免每次render重新bind。
4.减少对真实DOM的操作。
5.组件异步加载
6.能用const就用const。
7.多使用无状态组件
8.immutable,immutable的特点就是不可修改,改变它的任何数据时,都会重新生成一个新的对象,修改只会在新生成的对象上修改,原数据不会发生改变,这样就可以避免把所有节点都复制一遍,降低性能损耗;immutable的实现原理就是数据结构共享;
组件通信5种方法
https://www.jianshu.com/p/fb915d9c99c4
1.父-->子
父组件通过向子组件传递 props,子组件得到 props 后进行相应的处理。
2.子-->父
利用回调函数,可以实现子组件向父组件通信:父组件将一个函数作为 props 传递给子组件,子组件调用该回调函数,便可以向父组件通信。
3. 兄<-->弟
把state放到共有的父组件上,通过父子,子父进行通信。
4.跨组件通信
所谓跨级组件通信,就是父组件向子组件的子组件通信,向更深层的子组件通信。
使用 context 对象,context 相当于一个全局变量,是一个大容器,我们可以把要通信的内容放在这个容器中,这样一来,不管嵌套有多深,都可以随意取用。
使用 context 也很简单,需要满足两个条件:
上级组件要声明自己支持 context,并提供一个函数来返回相应的 context 对象
子组件要声明自己需要使用 context
5.redux
首先由view dispatch拦截action,然后执行对应reducer并更新到store中,最终views会根据store数据的改变执行界面的刷新渲染操作。
1)首先先把redux相关工具安装好
yarn add redux react-redux redux-thunk redux-devtools-extension -D
2)通过创建一个store实例createStore,接收一个rootReducer和中间件执行函数
3)创建分块的数据rootReducer,通过combineReducers打造rootReducer,里面放分块的数据
4)在组件中通过高阶组件connect函数,
接收store里的数据,
把ActionCreators里的方法绑定到组件身上,并且可以发送动作action给reducer
5)在reductor中根据action中的type动作类型,判断动作修改数据
redux-saga namespace state effects subscriptions事件订阅 监听路由变化 键盘变化鼠标变化 reducers
redux-saga
namespace命名空间 数据只在此组件中使用
state 定义状态
effects 调用数据请求 发送action给reducer
subscription 监听事件变化 键盘变化 鼠标变化 根据条件触发dispatch需要的action
reducers 修改状态
redux全局状态管理??
1. 是什么/适用场景?
Redux是一款状态管理库,随着应用程序单页面需求的越来越复杂,应用状态的管理也变得越来越混乱,而Redux的就是为解决这一问题而出现的。在一个大型的应用程序中,应用的状态不仅包括从服务器获取的数据,还包括本地创建的数据,以及反应本地UI状态的数据,而Redux正是为解决这一复杂问题而存在的
2. 组成部分/4个API?
首先由view dispatch拦截action,然后执行对应reducer并更新到store中,最终views会根据store数据的改变执行界面的刷新渲染操作
ReactComponents=views,视图
store 存储管理数据
ActionCreators 创建动作
Reducers 动作触发者 修改数据
3. 工作流/数据流??
redux作为一种单向数据流的实现,配合react非常好用,尤其是在项目比较大,逻辑比较复杂的时候,单向数据流的思想能使数据的流向、变化都能得到清晰的控制,并且能很好的划分业务逻辑和视图逻辑。
4. 三大原则??
• 单一数据源
整个应用的State被存储在一个状态树中,且只存在于唯一的Store中。
• State是只读的
对于Redux来说,任何时候都不能直接修改state,唯一改变state的方法就是通过触发action来间接的修改。而这一看似繁琐的状态修改方式实际上反映了Rudux状态管理流程的核心思想,并因此保证了大型应用中状态的有效管理。
• 应用状态的改变通过纯函数来完成
Redux使用纯函数方式来执行状态的修改,Action表明了修改状态值的意图,而真正执行状态修改的则是Reducer。且Reducer必须是一个纯函数,当Reducer接收到Action时,Action并不能直接修改State的值,而是通过创建一个新的状态对象来返回修改的状态。
5. 弊端?
生命周期钩子函数?
1. 4个阶段??
//初始化--
1.constructor:属性的继承 ,定义状态,绑定方法
它接受两个参数:props和context,当想在函数内部使用这两个参数时,需使用super()传入这两个参数。只要使用了constructor()就必须写super(),否则会导致this指向错误
2.componentWillMount-17弃用
UNSAFE_componentWillMount:组件即将挂载,初始化事件,为挂载做准备,内部操作
3.render:解析this.state,this.props, render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染,render函数中不允许使用this.setState()
4.componentDidMount:1.组件挂载结束2.vdom->真实dom3.发送数据请求,这是组件挂载到DOM之后的生命周期钩子。组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求
//更新阶段---
1.componentWillReceiveProps(nextProps)-17弃用**:
1.1当props发生变化时执行,可以判断组件身上的props是否改变
1.2在这个生命周期中,可以在子组件的render函数执行前获取新的props,从而更新子组件自己的state。
2.shouldComponentUpdate(nextProps,nextState):
1.2可以决定组件是否要重新渲染
2.2.接收新旧状态(更新前的props和state),用于做对比(浅对比),
组件接受到新的属性或新的状态时调用,在这里可以做一次性能优化。默认是true更新,可以控制是否需要更新来达到一次性能优化。因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断
3.componentWillUpdate(nextProps,nextState)-17弃用:1.表示组件更新前的准备,这里的属性和状态是不能修改的。,否则会陷入死循环
***禁止在 shouldComponentUpdate 和 componentWillUpdate中调用setState,这会造成循环调用,直至耗光浏览器内存后崩溃.shouldComponentUpdate 或者 componentWillUpdate里调用 setState 会再次触发这两个函数,然后在两个函数又触发了 setState,然后再次触发这两个函数。从而死循环。
4.render
5.componentDidUpdate(prevProps,prevState) 组件更新完毕。
组件更新完毕后,react只会在第一次初始化成功会进入componentDidmount,之后每次重新渲染后都会进入这个生命周期,这里可以拿到prevProps和prevState,即更新前的props和state
//--销毁阶段
componentWillUnmount:组件销毁,清除无用的实例,事件,定时器等
//错误阶段
componentDidCatch:用于捕获组件throw的错误,然后显示回退UI,网络有延迟等
17版本 废弃componentWillMount componentWillReceiveProps componentWillUpdate
getDerivedStateFromProps(nextProps, prevState)
1.这个生命周期函数是为了替代componentWillReceiveProps
2.静态函数,不能通过this去访问属性
3通过参数提供的nextProps与prevState中的值来进行判断,根据当前的 props 来更新组件的 state
getSnapshotBeforeUpdate(prevProps, prevState)
1.代替componentWillUpdate
2.getSnapshotBeforeUpdate中读取到的 DOM 元素状态是可以保证与 componentDidUpdate 中一致的。
3.此生命周期的返回值可以作为参数传递给componentDidUpdate,所以可以通过getSnapshotBeforeUpdat方法,获取dom信息,返回给componentDidUpdate,然后componentDidUpdate通过参数接收。
15版本 getDefaultProps定义属性 , getInitialState定义状态
2. 15.x VS 16.3
3. 16.3 VS 17.x
4. 业务中常用的?
constructor,render,componentDidMount,shouldComponentUpdate,componentDidUpdate
如果一个react 组件key值改变了,会经历哪些生命周期函数
// componentwillUnmount => componentwillmount => render =>componentDidMount
Hooks
1. 出现原因??
为了让函数组件拥有类组件的功能。
2. 优点4点??
让类组件可以定义状态
让类组件可以使用生命周期、监听数据
让类组件可以有Ref的功能(父组件获取子组件)
优化函数组件 useMemo、useCallback
自定义hooks来复用状态,
代码量比类组件更少,更清爽。
3. 使用规则2点
不要在循环,条件或嵌套函数中调用 Hook
只在函数组件中使用 Hooks
// 不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确
4. 常用Hook
useState:定义状态 修改状态
const [ name, setname ] = useState([])
useEffect:相当于componentDidMount componentDidUpdate componentWillUnmount ,同useLayoutEffect
// 在函数式组件中没有声明周期钩子函数的概念
// useEffect 相当于是 类组件中的 componentDidMount componentDidUpdate componentWillUnmount的合体
// useEffect(() => {}, []) 相当于 componentDidMount --- 最常用的方式
// useEffect(() => {} ) 相当于 componentDidMount componentDidUpdate的合体
// useEffect(() => { // 相当于 componentDidMount componentDidUpdate componentWillUnmount的合体
// return () => {}
// } )
// useEffect(() => { // 相当于 componentDidMount componentWillUnmount的合体
// return () => {}
// }, [] )
useContext:跨组件通信,可以解决react逐层通过Porps传递数据,通过createContext创建一个组件
const Name = React.createContext()
组件内useContext(Name)获取
<Name.Provider value={this.state.name}>
useReducer(),Action 钩子。useReducer() 提供了状态管理,其基本原理是通过用户在页面中发起action, 从而通过reducer方法来改变state, 从而实现页面和状态的通信。使用很像redux
useDebugValue:自定义 hook 的标签 方便调试台查看
useMemo和useCallback:可缓存函数的引用或值,useMemo用在计算值的缓存,注意不用滥用。经常用在下面两种场景(要保持引用相等;对于组件内部用到的 object、array、函数等,如果用在了其他 Hook 的依赖数组中,或者作为 props 传递给了下游组件,应该使用 useMemo/useCallback)
useMemo:有返回值,记忆组件,根据第二个参数依赖项动态缓存计算值,新值和旧值一样,不重新渲染页面,优化作用,类似于shouldComponentUpdate,useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数
useLayoutEffect:会在渲染的内容更新到DOM上之前进行,会阻塞DOM的更新,可能会出现空白屏的情况,useLayoutEffect 的执行时机要早于 useEffec
useRef:useRef跟createRef类似,都可以用来生成对DOM对象的引用,用来对 DOM 进行一些操作,监听事件等等,返回一个可变的ref对象
5.自定义hook
类似于高阶组件
高阶组件返回的一个类组件,而自定义Hook可以返回任何东西
高阶组件必须传递一个组件作为参数,而自定义Hook不需要
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// 在开发者工具中的这个 Hook 旁边显示标签
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
可以理解为数据的操作都在hook里进行,而外部只关心自己想要的。我只要数据列表,获取产品钩子(可能并不需要,可通过参数变更从而触发重新获取数据)、删除产品钩子
https://www.jianshu.com/p/76901410645a
高阶组件HOC
1.名词解释/作用??
高阶组件是react中用于复用组件逻辑的一种技巧.
高阶组件是一个函数,高阶组件接收一个组件作为参数,且需要在render函数中return返回这个组件
高阶组件的目的是为了:复用组件,将多个组件都要使用的类似逻辑放在同一个地方进行处理
2.常用高阶组件4个??
React.memo()
connet()
provider()
withRouter()
3.有自己封装过吗?
react中组件按需加载的方法??
1.动态import()语法,返回的是一个promise对象
2.lazy + Suspense组件实现路由懒加载
使用 React.lazy 函数允许你将动态导入的组件作为常规组件进行渲染。当组件开始渲染时,它会自动加载包。它必须返回一个 Promise,该 Promise 解析后为一个带有默认导出 React 组件的模块。
如果父组件在渲染时包含动态加载的模块尚未加载完成,在此加载过程中,你必须使用一个 loading 指示器显示后备内容。这可以使用 Suspense 组件来实现。
const OtherComponent = lazy(() => import('./OtherComponent'));
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
懒加载的组件被包装在 Suspense 组件中。
3.webpack提供的require.ensure()
{
path: '/about',
component: resolve =>require.ensure([], () => resolve(require('@/components/about')))
}
4.第三方库比如react-loadable
react-router原理和react-router-dom和react-router 的区别
//原理:
实现URL与UI界面的同步,其中在react-router中URL对应loaction对象,而UI是由react 组件components来决定的,这样就转变成了location与组件component之间的同步问题了。
//react-router3到4有什么变化
在 V3 中,我们 是将整个庞大的 router 直接丢给 DOM,而在 V4 中,除了 BrowserRouter, 我们丢给 DOM 的是我们的应用程序本身 另外,V4 中,我们不再使用 {props.children} 来嵌套组件了,替代的 <Route>,当 route 匹配时,子组件会被渲染到 <Route> 书写的地方 (router3需要进行集中式管 理,router4的话哪里需要在哪里配置就可以了)
其中在react-router中,URL对应Location对象,而UI是由react components来决定的,这样就转变成location与components之间的同步问题
Router 在 react component 生命周期之组件被挂载前 componentWillMount 中使用 this.history.listen 去注册了 url 更新的回调函数。回调函数将在 url 更新时触发,回调中的 setState 起到 render 了新的 component 的作用。
react路由
优点:
1.简单:不需要手工维护路由state,使代码变得简单。
2.路由配置:可以通过组件配置对象来进行路由的配置。
3.路由切换:可以通过<Link to=''><NavLink to="/地址"> 进行路由切换。或者js跳转this.props.history.push('/')
this.props.history.push({pathname:'/',query:{name:''}})传值
4.路由加载:可以同步,异步加载实现按需加载。
5.使用方式:不仅可以再浏览器端使用,也可以在服务器端使用。
缺点:
API不太稳定,在升级版本的时候需要进行代码变动。
//react路由模式
我们一直在使用的路由方式是BrowserRouter,也就是浏览器的路由方式,其实React还有几种路由方式:
1、BrowserRouter:浏览器的路由方式,也就是在开发中最常使用的路由方式
2、HashRouter:在路径前加入#号成为一个哈希值,Hash模式的好处是,再也不会因为我们刷新而找不到我们的对应路径
3、MemoryRouter:不存储history,所有路由过程保存在内存里,不能进行前进后退,因为地址栏没有发生任何变化
4、NativeRouter:经常配合ReactNative使用,多用于移动端
5、StaticRouter:设置静态路由,需要和后台服务器配合设置,比如设置服务端渲染时使用
//BrowserRouter和HashRouter的区别
1.BrowserRouter使用HTML5 history API,保证UI界面和URL保存同步,
HashRouter使用URL(即window.location.hash)的哈希部分来保持UI与URL同步的。
2.BrowserRouter中有back、forward、go等方法,可以实现前进后退,而HashRouter中没有
flux??
//介绍
flux 是 react 中的类似于 vuex 的公共状态管理方案,它是 Facebook 官方给出的应用架构,利用数据的单向流动的形式对公共状态进行管理。现已不推荐使用
//组成
1.View:视图层
2.Action:视图发出的消息
3.Dispatcher:派发者,用来接收Action,执行回调函数
4.Store:数据层,存放状态,一旦发生改动
//流程
flux 在进行数据更新时,会经历以下几步:
用户与 View 层交互,触发 Action
Action 使用 dispatcher.dispatch 将Action自己的状态发送给dispatcher
dispatcher 通过register注册事件,再通过Action传入的类型来触发对应的 Store 回调进行更新
Store 里进行相应的数据更新,并触发 View 层事件使试图也同步更新
View层 收到信号进行更新
redux
Redux 的几个组成对象:action 、reducer、store:
store:就是保存数据的地方,使用createStore函数,用来生成 Store。用getState()方法获取state
action:它是store数据的唯一来源;要想更改状态,需要分发一个action,action描述当前发生的事情,改变state的唯一办法,通过dispatch type属性触发相对应的reducer进行改变
reducer:action 发出了做某件事的请求,并没有去改变 state 来更新界面,而reducer 就是根据 action 的 type 来处理不同的事件,它接受一个Action和当前 State 作为参数,返回一个新的 State
redux的流程:
1. 组件视图 通过 事件 发送 dispatch一个action
2. store 接收到 action , 把action和 oldState 当做参数发送给 reducers
3. reducers 接收 action 判断对应的类型(type)和 oldState 通过计算返回新的 newState 给 store
4. store 会把 state 重新的渲染到 组件内视图
首先由view dispatch拦截action,然后执行对应reducer并更新到store中,最终views会根据store数据的改变执行界面的刷新渲染操作
redux与react-reudx的关系
redux是独立的应用状态管理工具。它是可以独立于react之外的。如果我们需要在react当中运用它,那么我们需要手动订阅store的状态变化,来对我们的react组件进行更新。那么react-reudx这个工具,就帮我们实现了这个功能,我们只需对store进行处理,react组件就会有相应的变化。
Redux的核心由三部分组成:Store, Action, Reducer。
Store : 是个对象,贯穿你整个应用的数据都应该存储在这里。
Action: 是个对象,必须包含type这个属性,reducer将根据这个属性值来对store进行相应的处理。除此之外的属性,就是进行这个操作需要的数据。
Reducer: 是个函数。接受两个参数:要修改的数据(state) 和 action对象。根据action.type来决定采用的操作,对state进行修改,最后返回新的state。
总结
Redux: store, action, reducer
store: getState, dispatch, subscribe
combineReducers
createStore
store ️ dispatch ️ action ️ reducer
react-redux:
connect: 将store作为props注入
Provider: 使store在子孙组件的connect中能够获取到
react-reudx
react-reudx
## redux-thunk|异步action
```js
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html
好处
可以进行前后端数据交互
// 缺点
1.将带有数据请求的action和没有带有数据请求的action混在一起了
2.异步请求的action代码过于复杂,且异步操作太分散。
3.action的形式不统一,有多个不统一的异步请求操作,就要写多个。
export default {
getList(){
return dispatch => {
axios.get("http://59.110.226.77:3000/api/category")
.then(res => {
console.log(res.data.data.data);
dispatch({
type:"GET_LIST",
payload:res.data.data.data
})
})
}
},
increment(){
return {
type:"ADD"
}
}
}
缺点解决: 弃用redux-thunk,使用redux-saga
redux-saga可以将异步action和普通action区别开来
redux-saga|集中处理异步action
https://www.jianshu.com/p/eff14939498c
https://www.cnblogs.com/vvjiang/p/9505646.html
// 优点
1.集中处理了所有的异步操作。
2.将异步与reducer区分开了,更加优雅,适合大量的api请求。redux-saga可以将异步action和普通action区别开来,控制器与更优雅的异步处理
而redux-saga就是用Generator来处理异步。
redux-saga同样是一个redux中间件,它的定位就是通过集中控制action,起到一个类似于MVC中控制器的效果。
同时它的语法使得复杂异步操作不会像promise那样出现很多then的情况,更容易进行各类测试。
state:{
lists:[]
},
effects:{
//Generator函数 异步处理
*getList({payload},{call,put}){
/*
payload表示组件传递过来的数据
call是用于调用异步数据请求
put是用于将action发送给reducer
*/
const result = yield call(getHomeList);
yield put({type:"GET_LIST",payload:result})
}
},
reducers:{
//方法的名称就是put中的type类型
GET_LIST(state,action){
state.lists = action.payload;
return {...state}
}
}
Mobx|类似双向数据绑定
https://blog.csdn.net/qq_37210523/article/details/84110035
1. 组成部分
actions->state->computed values->Reactions
2. 工作流
在mobx中, 数据是通过加 @observable 作为可监测的被观察者, 在view层中, 你可以通过添加@observer 将view作为观察者,对数据进行监测, 如果要触发改变数据,则使用@action, 事实上,你可以直接在view层改变数据, 但这种方式不便监控数据,因此不推荐直接改变数据。 而@computed可以用来计算数据, 也可以是计算多个数据之后返回新的数据, 如果其中数据改变, @computed也会触发改变
3. 优点
不同于redux的单一数据流, mobx中,你可以同时在各个地方使用同一份state, 也可以在一个页面中使用多个store文件
DVA与CRA相比的优点??
dva: dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。--- 来自官方。相比于cra只是多了内置的redux和redux-saga,帮我们处理了数据流这方面的需求而已。如果只是想要达到这个效果的话,直接在cra中增加dva-core的依赖也是可以做到的。
umi:是一个可插拔的企业级 react 应用框架。umi和cra都是应用框架,可能相比cra来说umi的功能点更多一些,只能说是功能性的话umi要相对来说更胜一筹
封装了哪些组件??
1.通过prismjs插件和Clipboard封装过一个具有复制功能的代码高亮显示的组件,在父组件中使用,父组件通过自定义属性code给子组件传递所需要高亮显示的内容,子组件通过接收code拿到内容,进行处理
React新特性和diff算法
// react的diff算法是怎么完成的
1.把树形结构按照层级分解,只比较同级元素。
2.只匹配class类名相同的组件,进行对比。
3.通过给列表结构的每一个元素添加唯一的key值进行区分同层次的子节点比较。
4.选择性渲染。
//react16版本以上把diff算法改成了filber算法,filber算法是将大任务分成了一个个小块,给其他的任务有了可执行的机会,这样就不会导致代码运行卡主。filber算法是一个优化方案。
React Fiber的方法其实很简单——分片。把一个耗时长的任务分成很多小片,每一个小片的运行时间很短,虽然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务依然有运行的机会。
//新的生命周期函数
由于异步渲染的改动,componentWillMount, componentWillReceiveProps,componentWillUpdate 三个函数将被废弃。
由于这是一个很大的改变会影响很多现有的组件,所以需要慢慢的去改。
目前react 16 只是会报warning,在react 17就只能在前面加UNSAFE_ 的前缀来使用
diff算法的作用计算出Virtual DOM中真 正变化的部分,并只针对该部分进行原生DOM操作,而非重新渲染整个页面
分层对比,把DOM树按层级拆分,简化了 复杂度,只比较同级元素,只匹配相同组件名字的组件,给列表结构的每个单元添加唯一的key 属性,方便比较(为什么不用index,如果用index的话追加在后面不会影响,如果追加到前面会 影响性能)
getDerivedStateFromProps
static getDerivedStateFromProps(props, state)在调用render方法之前调用,无论是在初始安装还是后续更新。它应返回一个对象来更新状态,或者返回null以不更新任何内容。
根据props更新state
这个生命周期可用于替代componentWillReceiveProps
getSnapshotBeforeUpdate()
getSnapshotBeforeUpdate(prevProps, prevState)在最近呈现的输出被提交到例如DOM之前调用。它使组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)。此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
hooks
lazy、suspense
lazy需要跟Suspence配合使用。
lazy实际上是帮助我们实现代码分割的功能。
由于有些内容,并不一定要在首屏展示,所以这些资源没有必要一开始就要去获取,那么这些资源就可以动态获取。
这样的话,相当于把不需要首屏展示的代码分割出来,减少首屏代码的体积,提升性能。
Suspence 很像Error Boundary,不同的是Error Boundary是用来捕获错误,显示相应的callback组件。而Suspence是用来捕获还没有加载好的组件,并暂停渲染,显示相应的callback。
react 16版本新特性
https://www.cnblogs.com/qingmingsang/articles/10624710.html
React 16.6.X版本的更新功能
https://www.jianshu.com/p/406bcc058790
cnblogs.com/zhanglanzuopin/p/12987660.html
Component和PureComponent的区别
PureComponent自带通过props和state的浅对比来实现 shouldComponentUpate(),而Component没有。
继承自Component的组件,若是shouldComponentUpdate返回false,就不会渲染了,继承自PureComponent的组件不用我们手动去判断prop和state,所以在PureComponent中使用shouldComponentUpdate会有警告
五.网络---浏览器
网页从输入网址到渲染完成经历了哪些过程?
大致可以分为如下7步:
输入网址;
1.发送到DNS服务器,并获取域名对应的web服务器对应的ip地址;
2.与web服务器建立TCP连接;
3.浏览器向web服务器发送http请求;
4.web服务器响应请求,并返回指定url的数据(或错误信息,或重定向的新的url地址);
5.浏览器下载web服务器返回的数据及解析html源文件;
6.生成DOM树,解析css和js,渲染页面,直至显示完成;
Content-Type
1.浏览器默认的 application/x-www-form-urlencoded
这应该是最常见的 POST 提交数据的方式了。浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。
2.multipart/form-data
这也是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,就要让 form 的 enctype 等于这个值
3.application/json
除了低版本 IE 之外的各大浏览器都原生支持 JSON.stringify
4.text/xml
相比于JSON,XML不能更好的适用于数据交换,它包含了太多的包装, 而且它跟大多数编程语言的数据模型不匹配,让大多 数程序员感到诧异,XML是面向数据的,JSON是面向对象和结构的,JSON会给程序员一种更加亲切的感觉。
谈一下HTTP缓存
HTTP缓存指的是,当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器中存在“要请求资源”的副本,就可以直接从浏览器缓存中提取,从而不需要从原始服务器中提取这个资源。
//浏览器缓存的优点有:
1.减少了冗余的数据传输
2.减少了服务器的负担,大大提升了网站的性能
3.加快了客户端加载网页的速度
//HTTP缓存主要分为强缓存和协商缓存。
1.强缓存的基本原理是所请求的数据在缓存数据中尚未过期,不予服务器进行交互,直接使用缓存中的数据。
2.协商缓存主要原理是从缓存数据中取出缓存的标识,然后向浏览器发送请求验证请求的数据是否已经更新,如果已更新则返回新的数据,若未更新则使用缓存数据库中的缓存数据。
两者的共同点是,都是从客户端缓存中读取资源;区别是强缓存不会发请求,协商缓存会发请求。
浏览器渲染HTML的步骤
//HTML渲染大致分为如下几步:
1. HTML被HTML解析器解析成DOM Tree, css则被css解析器解析成CSSOM Tree。
2. DOM Tree和CSSOM Tree解析完成后,被附加到一起,形成渲染树(Render Tree)。
3. 浏览器将渲染树绘制到页面上,也就是首次显示页面
4. 最后,body底部的JS脚本下载完成后通过DOM API修改DOM, 通过CSSOM API修改样式,每次修改都会造成渲染树RenderTree的重新布局和重绘.
理论上,每一次的dom更改或者css几何属性更改,都会引起一次浏览器的重排/重绘过程,而如果是css的非几何属性更改,则只会引起重绘过程。所以说重排一定会引起重绘,而重绘不一定会引起重排。
XSS攻击和CSRF攻击
//XSS攻击
1.什么是XSS攻击
XSS即跨站脚本,指的是通过利用网页开发时留下的漏洞,注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。
XSS攻击的核心原理是:不需要你做任何的登录认证,它会通过合法的操作(比如在url中输入、在评论框中输入),向你的页面注入脚本(可能是js、hmtl代码块等)。最后导致的结果可能是:盗用Cookie破坏页面的正常结构,插入广告等恶意内容D-doss攻击
2.XSS攻击有哪些类型
2.1反射型
发出请求时,XSS代码出现在url中,作为输入提交到服务器端,服务器端解析后响应,XSS代码随响应内容一起传回 给浏览器,最后浏览器解析执行XSS代码。这个过程像一次反射,所以叫反射型XSS。
2.2存储型存
储型XSS和反射型XSS的差别在于,提交的代码会存储在服务器端(数据库、内存、文件系统等),下次请求时目标 页面时不用再提交XSS代码。(常见的是在评论区插入攻击脚本,如果脚本被储存到服务端,那么所有看见对应评论 的用户都会受到攻击。)
3.XSS的防范措施(encode + 过滤)
1、编码:
对用户输入的数据进行HTML Entity 编码。
Encode的作用是将等一些字符进行转化,使得浏览器在最终输出结果上是一样的。
2、过滤:
移除用户输入的和事件相关的属性。如onerror可以自动触发攻击,还有onclick等。(总而言是,过滤掉一些不安全的内 容)移除用户输入的Style节点、Script节点、Iframe节点。(尤其是Script节点,它可是支持跨域的,一定要移 除)。
3、校正
避免直接对HTML Entity进行解码。使用DOM Parse转换,校正不配对的DOM标签。
比较常用的做法是,通过第一步的编码转成文本,然后第三步转成DOM对象,然后经过第二步的过滤。
// CSRF攻击
1.什么是CSRF攻击
CSRF即跨站请求伪造,是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。
(1)登录受信任网站A,并在本地生成Cookie。(如果用户没有登录网站A,那么网站B在诱导的时候,请求网站A的api接 口时,会提示你登录)
(2)在不登出A的情况下,访问危险网站B(其实是利用了网站A的漏洞).
2.如何防御CSRF攻击
方法一、Token 验证:(用的最多)
(1)服务器发送给客户端一个token;
(2)客户端提交的表单中带着这个token。
(3)如果这个 token 不合法,那么服务器拒绝这个请求。
方法二:隐藏令牌:
把 token 隐藏在 http 的 head头中。
方法二和方法一有点像,本质上没有太大区别,只是使用方式上有区别。
方法三、Referer 验证:
Referer 指的是页面请求来源。意思是,只接受本站的请求,服务器才做响应;如果不是,就拦截。
// CSRF 和 XSS 的区别
区别一:CSRF:需要用户先登录网站A,获取 cookie。XSS:不需要登录。
区别二:(原理的区别)
CSRF:是利用网站A本身的漏洞,去请求网站A的api。XSS:是向网站 A 注入 JS代码,然后执行 JS 里的代码,篡改网 站A的内容。
URL从输入到页面展示的过程
1.用户在浏览器的地址栏输入访问的URL地址。浏览器会先根据这个URL查看浏览器缓存-系统缓存-路由器缓存,若缓存中有,直接跳到第6步操作,若没有,则按照下面的步骤进行操作。
2.浏览器根据输入的URL地址解析出主机名。
3.浏览器将主机名转换成服务器ip地址。浏览器先查找本地DNS缓存列表,看缓存里面是否存在这个ip,如果有则进入第4步,如果缓存中不存在这个ip地址,就再向浏览器默认的DNS服务器发送查询请求,同时缓存当前这个ip到DNS缓存列表中。
4.拿到ip地址后,浏览器再从URL中解析出端口号。
5.拿到ip和端口后,浏览器会建立一条与目标Web服务器的TCP连接,也就是传说中的三次握手。
6.浏览器向服务器发送一条HTTP请求报文。
7.服务器向浏览器返回一条HTTP响应报文。
8.关闭连接,浏览器解析HTML、CSS、JS等资源,最后进行页面渲染,呈现给用户。
HTTP和HTTPS的区别
1.HTTP是超文本传输协议,信息是明文传输的,HTTPS是具有ssl/tls加密传输协议。
2.默认端口不同,前者是80,后者是443。
3.HTTPS比HTTP安全
4.HTTPS协议需要到CA申请证书,需要一定费用。
http状态码
200 OK 正常返回信息
201 Created 请求成功并且服务器创建了新的资源
202 Accepted 服务器已接受请求,但尚未处理
301 Moved Permanently 请求的网页已永久移动到新位置。
302 Found 临时性重定向。
303 See Other 临时性重定向,且总是使用 GET 请求新的 URI。
304 Not Modified 自从上次请求后,请求的网页未修改过。请求资源未发生改变
400 Bad Request 服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。
401 Unauthorized 请求未授权。
403 Forbidden 禁止访问。
404 Not Found 找不到如何与 URI 相匹配的资源。
500 Internal Server Error 最常见的服务器端错误。
503 Service Unavailable 服务器端暂时无法处理请求(可能是过载或维护)。
六.项目模块
axios解决跨域
1、配置 baseURL
2、配置vue.config.js
3、修改 axios 使用方式
项目优化
1.做一些节流防抖的操作
2.使用CDN加载公用库
通过 externals 加载外部 CDN 资源
默认情况下,通过 import 语法导入的第三方依赖包,最终会被打包合并到同一个文件中,从而导致打包成功
后,单文件体积过大的问题。
为了解决上述问题,可以通过 webpack 的 externals 节点,来配置并加载外部的 CDN 资源。凡是声明在
externals中的第三方依赖包,都不会被打包。
3.小图片使用Base64代替(使用 url-loader 优化, 将小图片转化成base64压缩,防止小图片太多请求次数太多。)
4.图片懒加载,路由懒加载等;
5.开启服务器Gzip压缩,JS、CSS文件压缩合并
6.优化用户等待体验:白屏使用加载进度条、菊花图、骨架屏代替等;
7. 移动端性能优化?
尽量使用css3动画,开启硬件加速
适当使用touch时间代替click时间
避免使用css3渐变阴影效果
可以用transform: translateZ(0) 来开启硬件加速
不滥用float。float在渲染时计算量比较大,尽量减少使用
不滥用web字体。web字体需要下载,解析,重绘当前页面
合理使用requestAnimationFrame动画代替setTimeout
css中的属性(transitions、3D transforms、opacity、webGL、video)会触发GUP渲染
1. 项目情况
业务内容:
服务对象:
开发形式:
人员配置:
办公地址:
七.TS
1.什么是TS :
TS是由微软开发和维护的免费开源的编程语言,它JS的超集,可编译为纯JS,它是可以在任何浏览器上执行的
2.数据类型:
TypeScript包含的最简单的数据单元有:数字,字符串,布尔值,Null 和 Undefined等
// 元组 Tuple
1.元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
// 声明一个元组类型 x
let x: [string, number]
// 初始化 x
x = ['hello', 10] // OK
当访问一个已知索引的元素,会得到正确的类型:
console.log(x[0]) // hello
// 枚举
2.enum类型是对JavaScript标准数据类型的一个补充。枚举类型提供的一个便利是你可以由枚举的值得到它的名字。
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2]
console.log(colorName) // 'Green'
// 接口 interface
3.自定义类型
interface People{
readonly id: number, //只读属性
name: string,
age: number,
sex?: string,//表示sex是一个可传属性
[propName:string]: any//表示新增的属性可以是任意类型
}
//! 通过一个接口来完成一个自定义类型
const people:People = {
id: 0,
name: 'lakers',
age: 18
}
people.sex = 'man'
people.a = 1
people.b = 2
people.c = 3
2.为什么要用TS:
2.1:ts快速、简单、容易学习。
2.2:ts支持面向对象编程,比如类、接口、继承、泛型等。
2.3:ts还提供了错误检查功能。
2.4: ts支持所有js库,因为他是js的超集。
2.5:ts通过使用继承来实现可复用性。
2.6:ts增加了代码的可读性和可维护性。
3.TS和JS的区别
3.1:TS有可选参数类型,JS没有。
3.2:TS支持泛型,JS不支持。
3.3:TS必须指定数据类型,而JS不需要
3.4:TS在开发是突显错误,JS要在运行时才突显错误。
4.TS的优点
- 它提供了可选静态类型的优点。在这里,Typescript提供了可以添加到变量、函数、属性等的类型。
- Typescript能够编译出一个能在所有浏览器上运行的JavaScript版本。
- TypeScript总是在编译时强调错误,而JavaScript在运行时指出错误。
- TypeScript支持强类型或静态类型,而这不是在JavaScript中。
- 它有助于代码结构。
- 它使用基于类的面向对象编程。
- 它提供了优秀的工具支持和智能感知,后者在添加代码时提供活动提示。
- 它通过定义模块来定义名称空间概念。
5.TS的缺点
5.1:有一定的学习成本,需要理解接口,泛型,类,枚举等前端工程师可能不是很熟的概念。
5.2:短期可能会增加一些开发成本,需要多写一些类型的定义,但对于需要长期维护的项目ts能够减少维护成 本。
5.3: 可能和一些库结合的不是很完美。
八.NODE
1.query string模块 字符串格式化
parse: 将字符串转为对象
stringify: 将对象转为字符串
2.fs模块 读取文件
fs.createReadStream( './demo.txt' ) //读出数据
3.express模块 打造接口,创建api服务器
后端解决跨域问题:
1. 设置请求头
2. 使用中间件:第三方的包 cors
4.path模块:提供了一些工具函数,用于处理文件与目录的路径
path.join():用于完成路径的拼接
path.dirname():获取路径地址
path.parse():将一个字符串路径转换为一个路径对象
5.node异步
nextTick setImmediate
setImmediate属于宏任务,而process.nextTick属于微任务
除了script整体代码,micro-task(微任务)的任务优先级高于macro-task(宏认为)的任务优先级。
在node环境下,process.nextTick的执行优先级高于promise的回调。
九.git
Workspace:工作区
Index / Stage:暂存区/缓存区
Repository:本地仓库
Remote:远程仓库
常用命令:
新增文件的命令:git add .
提交文件的命令:git commit –m或者git commit –a
查看工作区状况:git status –s
拉取合并远程分支的操作:git pull
Git branch name 创建名字为name的branch
Git checkout xxx_dev 切换到名字为xxx_dev的分支
Git pull 从远程分支拉取代码到本地分支
Git checkout -b dev 创建并切换到 dev 分支
Git push origin dev 执行推送的操作,完成本地分支向远程分支的同步
提交时解决冲突:
因为在合并分支的时候,master分支和dev分支恰好有人都修改了同一个文件,GIT不知道应该以哪一个人的文件为准,所以就产生了冲突了。 两个分支相同文件相同位置的的不同操作!
发生冲突,一般都是对比本地文件和远程分支的文件,然后把远程分支上文件的内容手工修改到本地文件,然后再提交冲突的文件使其保证与远程分支的文件一致,这样才会消除冲突,然后再提交自己修改的部分。特别要注意下,修改本地冲突文件使其与远程仓库的文件保持一致后,需要提交后才能消除冲突,否则无法继续提交。必要时可与同事交流,消除冲突。
发生冲突,也可以使用命令。
通过git stash命令,把工作区的修改提交到栈区,目的是保存工作区的修改;
通过git pull命令,拉取远程分支上的代码并合并到本地分支,目的是消除冲突;
通过git stash pop命令,把保存在栈区的修改部分合并到最新的工作空间中;
Git提交代码的步骤:
git clone (这个是你新建本地git仓库,如已有可忽略此步)
git status 查看当前状态
git add + 文件 添加到缓存区
git commit -m "备注" 推送修改到本地git库中
git push 把当前提交到git本地仓库的代码推送到远程主机的某个远程分之上
权限
之前在前后端不分离的情况下,是由后端控制一个权限的问题,现在分离了,一般都是前端处理权限的问题,
1 、 在配置路由的时候,之前都是写死的,现在我们通过根据不同的用户显示不同的组件, 比如admin 所有的页面都可以看得到 比如vip 只能看到vip的权限, 比如svip 只能看到svip的权限