- CSS
- JavaScript
- ES6
- React
- VUE
- v-if和v-for哪个优先级更高
- Vue组建中为什么data必须是个函数,而根实例不需要
- key的作用和工作原理
- Vue中的Diff算法
- 谈一谈对Vue组件化的理解">谈一谈对Vue组件化的理解
- 谈一谈你对Vue设计原则的理解
- 谈谈你对MVC、MVP和MVVM的理解
- 你了解哪些 Vue 性能优化方法?
- v-model和v-bind绑定数据的异同
- 实现双向绑定Proxy比defineproperty优劣如何
- 你对 Vue3.0 的新特性有没有了解?
- 简单说一说 vuex 使用及其理解?
- vue 中组件之间的通信方式
- vue-router 中的导航钩子由那些?
- 你知道 nextTick 的原理吗?
- 你对vue响应式的理解?
- vue的生命周期
- Vue和React的对比
- webpack
- 浏览器缓存
- http和https
- 性能优化
- 什么是浏览器的事件循环(Event Loop)?">什么是浏览器的事件循环(Event Loop)?
CSS
盒模型
- 标准盒子模型:一个块的总宽度= width + margin(左右) + padding(左右) + border(左右)
- IE盒子模型(怪异盒模型): 一个块的总宽度= width + margin(左右)(即width已经包含了padding和border值)
- box-sizing:border-box 告诉浏览器:你想要设置的边框和内边距的值是包含在width内的
BFC(块级格式化上下文)
块级格式化上下文,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。
IE下为 Layout,可通过 zoom:1 触发
备注:Zoom属性是IE浏览器的专有属性, 它可以设置或检索对象的缩放比例。
- 触发条件:
- 根元素
- position: absolute/fixed
- display: inline-block / table
- float 元素
- ovevflow !== visible
- 规则:
- 属于同一个 BFC 的两个相邻 Box 垂直排列
- 属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠
- BFC 中子元素的 margin box 的左边, 与包含块 (BFC) border box的左边相接触 (子元素 absolute 除外)
- BFC 的区域不会与 float 的元素区域重叠
- 计算 BFC 的高度时,浮动子元素也参与计算
- 文字层不会被浮动层覆盖,环绕于周围
- 应用
- 阻止margin重叠
- 可以包含浮动元素 —— 清除内部浮动(清除浮动的原理是两个div都位于同一个 BFC 区域之中)
- 自适应两栏布局
- 可以阻止元素被浮动元素覆盖
css-清除浮动的原理与方法
- 浮动的缺陷:影响它的兄弟元素的位置和父元素产生高度塌陷
- 清除浮动的常见方法
- clear: both;clear 会为元素添加足够的空白空间,使到该元素的位置会放置在它前一个浮动元素之下,这跟增加元素外边距使到元素占据满行而强制换行的效果是一样的,但是,clear只是清除了对兄弟元素的影响,并没有解决父元素高度塌陷的问题,我们需要更高级的清除浮动——闭合浮动
- 闭合浮动法:
- 空 div 方法:在浮动元素的后面加一个空的div
- overflow方法:在浮动元素的父元素上设置了 overflow 的值为 hidden 或 auto ,可以闭合浮动。另外在 IE6 中还需要触发 hasLayout ,例如为父元素设置容器宽高或设置 zoom:1
- 使用 :after 伪元素的方法:
<style>
.clearfix {/* 触发 hasLayout */ zoom: 1; }
.clearfix:after {content: "." display: block; height: 0; clear: both; visibility: hidden; }
</style>
<div class="box clearfix">
<div class="main left">我设置了左浮动 float: left</div>
<div class="aside left">我是页脚,但是我也设置了左浮动。</div>
</div>
居中布局
- 水平居中
行内元素: text-align: center
块级元素: margin: 0 auto
absolute + transform
flex + justify-content: center
- 垂直居中
line-height: height
absolute + transform
flex + align-items: center
table
- 水平垂直居中
absolute + transform
flex + justify-content + align-items
Position(定位)
- position 属性的五个值:
- static
- relative
- fixed
- absolute
- sticky
元素可以使用的顶部,底部,左侧和右侧属性定位。然而,这些属性无法工作,除非是先设定position属性。他们也有不同的工作方式,这取决于定位方法。
- static 定位
- HTML 元素的默认值,即没有定位,遵循正常的文档流对象。
- 静态定位的元素不会受到 top, bottom, left, right影响。
- fixed 定位
- 元素的位置相对于浏览器窗口是固定位置;
- 即使窗口是滚动的它也不会移动;
- Fixed定位使元素的位置与文档流无关,因此不占据空间;
- Fixed定位的元素和其他元素重叠。
- relative 定位
- 相对定位元素的定位是相对其正常位置。
- 移动相对定位元素,但它原本所占的空间不会改变。
- 相对定位元素经常被用来作为绝对定位元素的容器块。
- absolute 定位
- 绝对定位的元素的位置相对于 static 定位以外的第一个父元素进行定位;
- absolute 定位使元素的位置与文档流无关,因此不占据空间。
- absolute 定位的元素和其他元素重叠。
- sticky 定位
- sticky 英文字面意思是粘,粘贴,所以可以把它称之为粘性定位
- position: sticky; 基于用户的滚动位置来定位
- 粘性定位的元素是依赖于用户的滚动,在 position:relative 与 position:fixed 定位之间切换。
- 而当页面滚动超出目标区域时,它的行为就像 position:relative; 它的表现就像 position:fixed;,它会固定在目标位置。
- Internet Explorer, Edge 15 及更早 IE 版本不支持 sticky 定位。 Safari 需要使用 -webkit- prefix
div.sticky {
position: -webkit-sticky; /* Safari */
position: sticky;
top: 0;
background-color: green;
border: 2px solid #4CAF50;
}
Flex布局
http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
.box{
display: -webkit-flex; /* Safari */
display: flex;
}
容器的属性:
- flex-direction 属性决定主轴的方向(即项目的排列方向)。
- flex-wrap 默认情况下,项目都排在一条线(又称”轴线”)上。
flex-wrap
属性定义,如果一条轴线排不下,如何换行 - flex-flow 属性是
flex-direction
属性和flex-wrap
属性的简写形式,默认值为row nowrap
。 - justify-content 属性定义了项目在主轴上的对齐方式。
- align-items 属性定义项目在交叉轴上如何对齐
- align-content 属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用
项目的属性:
order
属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。flex-grow
属性定义项目的放大比例,默认为0
,即如果存在剩余空间,也不放大。flex-shrink
属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。flex-basis
属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto
,即项目的本来大小。flex
属性是flex-grow
,flex-shrink
和flex-basis
的简写,默认值为0 1 auto
。后两个属性可选。align-self
属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items
属性。默认值为auto
,表示继承父元素的align-items
属性,如果没有父元素,则等同于stretch
。JavaScript
js的基本数据类型和复杂数据类型区别
数据类型:
- 区别原始数据类型: number,string,boolean,undefined, null,object
- 基本类型(简单类型),值类型: number,string,boolean
- 复杂类型(引用类型):object
- 空类型:undefined,null
区别:
- 基本数据类型把数据名和值直接存储在栈当中
- 复杂数据类型在栈中存储数据名和一个堆的地址,在堆中存储属性及值,访问时先从栈中获取地址,再到堆中拿出相应的值
js函数的执行机制
- 普通函数的执行
- 成一个私有的作用域
- 形参赋值
- 变量提升,函数声明
- 代码执行
- 栈内存释放与否
new运算符的执行过程
new
运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。new
关键字会进行如下的操作:
- 创建一个空的简单JavaScript对象(即
{}
); - 链接该对象(即设置该对象的构造函数)到另一个对象 ;
obj.__proto__ = Con.prototype
- 将步骤1新创建的对象作为
this
的上下文 ,也就是绑定this; - 如果该函数没有返回对象,则返回
this
。
this指向
- 在方法中,this 表示该方法所属的对象。
- 如果单独使用,this 表示全局对象。
- 在函数中,this 表示全局对象。
- 在函数中,在严格模式下,this 是未定义的(undefined)。
- 在事件中,this 表示接收事件的元素。
- 类似 call() 和 apply() 方法可以将 this 引用到任何对象。
判断是一个数组的六种方法
const arr =[1]
Array.isArray(arr) true
arr.constructor === Array true
arr instanceof Array true
Object.prototype.toString.call(arr).indexOf('Array') !== -1; true
Array.prototype.isPrototypeOf(arr) true
arr.constructor.toString().indexOf("Array") !== -1
原型 / 构造函数 / 实例
- 原型(prototype): 一个简单的对象,用于实现对象的属性继承。可以简单的理解成对象的爹。在 Firefox 和 Chrome 中,每个JavaScript对象中都包含一个**proto (非标准)的属性指向它爹(该对象的原型),可obj.__proto__**进行访问。
- 构造函数: 可以通过new来新建一个对象的函数。
- 实例: 通过构造函数和new创建出来的对象,便是实例。 实例通过**proto**指向原型,通过constructor指向构造函数。
原型链
原型链是由原型对象组成,每个对象都有 proto 属性,指向了创建该对象的构造函数的原型,proto 将对象连接起来组成了原型链。是一个用来实现继承和共享属性的有限的对象链。
- 属性查找机制: 当查找对象的属性时,如果实例对象自身不存在该属性,则沿着原型链往上一级查找,找到时则输出,不存在时,则继续沿着原型链往上一级查找,直至最顶级的原型对象Object.prototype,如还是没找到,则输出undefined;
- 属性修改机制: 只会修改实例对象本身的属性,如果不存在,则进行添加该属性,如果需要修改原型的属性时,则可以用: b.prototype.x = 2;但是这样会造成所有继承于该对象的实例的属性发生改变。
执行上下文(EC)
执行上下文可以简单理解为一个对象:
- 它包含三个部分:
- 变量对象(VO)
- 作用域链(词法作用域)
- this指向
- 它的类型:
- 全局执行上下文
- 函数执行上下文
- eval执行上下文
- 代码执行过程:
- 创建 全局上下文 (global EC)
- 全局执行上下文 (caller) 逐行 自上而下 执行。遇到函数时,函数执行上下文 (callee) 被push到执行栈顶层
- 函数执行上下文被激活,成为 active EC, 开始执行函数中的代码,caller 被挂起
- 函数执行完后,callee 被pop移除出执行栈,控制权交还全局上下文 (caller),继续执行
作用域
作用域其实可理解为该上下文中声明的变量和声明的作用范围。可分为块级作用域、函数作用域和块级作用域
- 全局作用域
- 任何地方都能访问到的对象拥有全局作用域
- 函数外面定义的变量拥有全局作用域
- 未定义直接赋值的变量自动声明为拥有全局作用域
- window对象的属性拥有全局作用
- 局部作用域,也叫做函数作用域
- 局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部,所以在一些地方会把这种作用域成为函数作用域
- 局部作用域在函数的外部无法访问
- ES6的块级作用域
- 没有块级作用域,会带来下面问题:
- 变量提升可能会导致内部变量覆盖外部变量
- 用来计数的循环变量泄漏为全局变量
- ES6引入了块级作用域,明确允许在块级作用域中声明函数,let和const命令都涉及块级作用域。
- 块级作用域允许声明函数只在使用大括号的情况下成立,如果未使用大括号,会报错。
- 没有块级作用域,会带来下面问题:
- 申明提前
- 变量声明提前
console.log(a); //undefined
2 var a;
3 a=10;
4 console.log(a) //10;
- 函数声明提前
function a(){
console.log(10);
}
a(); //20
function a(){
console.log(20);
}
a(); //20
作用域链
- 由于作用域是相对于变量而言的,而如果存在多级作用域,这个变量又来自于哪里?我们把这个变量的查找过程称之为变量的作用域链
- 作用域链的意义:查找变量(确定变量来自于哪里,变量是否可以访问)
- 简单来说,查找一个变量来自哪里,能否被访问,需要以下四步:
- 查看当前作用域,如果当前作用域声明了这个变量,可以直接访问
- 查找当前作用域的上级作用域,也就是当前函数的上级函数,看看上级函数中有没有声明,有就返回变量,没有继续下一步
- 再查找上级函数的上级函数,直到全局作用域为止,有则返回,无则继续
- 如果全局作用域中也没有,我们就认为这个变量未声明(xxx is not defined)
对象的继承
**
参考链接:https://wangdoc.com/javascript/oop/prototype.html
在 JS 中,继承通常指的便是 原型链继承,也就是通过指定原型,并可以通过原型链继承原型上的属性或者方法。
- 原型链继承
- 特点:
- 子类的实例也是父类的实例
- 可以方便的继承父类型的原型中的方法,但是属性的继承无意义
- 缺点:
- 只执行一次,无法给属性传值
- 属性的继承无意义
- 特点:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function() {
console.log("Hi,my name is " + this.name + ",I am " + this.age + "岁");
};
function Student() {
this.score = 100;
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
var s1 = new Student("janney", 26);
s1.sayHi();
console.log(s1.name);
console.log(s1.age);
- 构造函数继承
- 核心:
- 在子类的内部调用父类,通过call改变父类中this的指向
- 等于是复制父类的实例属性给子类
- 特点:
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承
- 可以方便的继承父类型的属性,但是无法继承原型中的方法
- 缺点:
- 实例并不是父类的实例,只是子类的实例
- 无法继承原型中的方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
- 核心:
function Animal(name, food) {
this.name = name;
this.food = food;
}
Animal.prototype.eat = function() {
console.log("My name is " + this.name + ", I eat " + this.food);
};
function Cat(name, food, age) {
Animal.call(this, name, food);
this.age = age;
}
let c1 = new Cat("小花", "猫粮", 19);
// c1.cat(); // c1.cat is not a function
console.log(c1.name);
console.log(c1.food);
console.log(c1.age);
console.log(c1 instanceof Animal); // false
console.log(c1 instanceof Cat); // true
- 组合继承(原型继承和构造函数继承组合)
- 特点:
- 即时子类的实例,也是父类的实例
- 可传参
- 函数可复用
- 特点:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function() {
console.log("Hi,my name is " + this.name + ",I am " + this.age + "岁");
};
function Student(name, age, score) {
Person.call(this, name, age);
this.score = score;
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
let s1 = new Student("janney", 26, 100);
s1.sayHi(); // Hi,my name is janney,I am 26岁
console.log(s1.name, s1.age, s1.score); // janney 26 100
console.log(s1 instanceof Person); //true
console.log(s1 instanceof Student); // true
- 原型继承
- 寄生式继承
- 寄生组合式继承
核心实现是:用一个F
空的构造函数去取代执行了Parent
这个构造函数。 ```javascript function Parent(name) { this.name = name; } Parent.prototype.sayName = function() { console.log(‘parent name:’, this.name); } function Child(name, parentName) { Parent.call(this, parentName);
this.name = name;
} function create(proto) { function F(){} F.prototype = proto; return new F(); } Child.prototype = create(Parent.prototype); Child.prototype.sayName = function() { console.log(‘child name:’, this.name); } Child.prototype.constructor = Child;
var parent = new Parent(‘father’); parent.sayName(); // parent name: father
var child = new Child(‘son’, ‘father’);
- 最优化: 圣杯模式
```javascript
var inherit = (function(c,p){
var F = function(){};
return function(c,p){
F.prototype = p.prototype;
c.prototype = new F();
c.uber = p.prototype;
c.prototype.constructor = c;
}
})();
- 使用 ES6 的语法糖 class / extends
class Animal {
constructor(props) {
this.name = props.name;
}
sayName() {
console.log("My name is " + this.name);
}
}
class Cat extends Animal {
constructor(props, myAttr) {
super(props);
this.type = props.type;
this.attr = myAttr;
}
myAge() {
console.log("My age is " + this.attr.age);
}
myAttrs() {
console.log(this.attr);
}
}
let c1 = new Cat(
{ name: "花花", type: "猫科动物" },
{ age: 3, sex: "公" }
);
c1.sayName(); // My name is 花花
c1.myAge(); // My age is 3
c1.myAttrs(); // {age: 3, sex: "公"}
闭包
闭包属于一种特殊的作用域,称为 静态作用域。它的定义可以理解为: 父函数被销毁 的情况下,返回出的子函数的[[scope]]中仍然保留着父级的单变量对象和作用域链,因此可以继续访问到父级的变量对象,这样的函数称为闭包。
- 闭包的特点:
- 让外部访问函数内部变量成为可能;
- 局部变量会常驻在内存中;
- 可以避免使用全局变量,防止全局变量污染;
- 会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
- 闭包会产生一个很经典的问题:
- 多个子函数的[[scope]]都是同时指向父级,是完全共享的。因此当父级的变量对象被修改时,所有子函数都受到影响。
- 解决:
- 变量可以通过 函数参数的形式 传入,避免使用默认的[[scope]]向上查找
- 使用setTimeout包裹,通过第三个参数传入
- 使用 块级作用域,让变量成为自己上下文的属性,避免共享
- 闭包的创建:
- 闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。闭包会发生内存泄漏,每次外部函数执行的时 候,外部函数的引用地址不同,都会重新创建一个新的地址。但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象。
- 闭包的释放
- 需要手动设置为null
闭包的应用场景
setTimeout:原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果。
function f1(a) {
function f2() {
console.log(a);
}
return f2;
}
var fun = f1(1);
setTimeout(fun,1000);//一秒之后打印出1
定义行为,然后把它关联到某个用户事件上(点击或者按键)。代码通常会作为一个回调(事件触发时调用的函数)绑定到事件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
<a href="#" id="size-12">12</a>
<a href="#" id="size-20">20</a>
<a href="#" id="size-30">30</a>
<script type="text/javascript">
function changeSize(size){
return function(){
document.body.style.fontSize = size + 'px';
};
}
var size12 = changeSize(12);
var size14 = changeSize(20);
var size16 = changeSize(30);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-20').onclick = size14;
document.getElementById('size-30').onclick = size16;
</script>
</body>
</html>
函数防抖:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。实现的关键就在于
setTimeOut
这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现。 ```javascript /*
- fn [function] 需要防抖的函数
delay [number] 毫秒,防抖期限值 */ function debounce(fn,delay){ let timer = null //借助闭包 return function() {
if(timer){
clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
timer = setTimeOut(fn,delay)
}else{
timer = setTimeOut(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时
}
} }
- 封装私有变量如下面代码:用js创建一个计数器
```javascript
function f1() {
var sum = 0;
var obj = {
inc:function () {
sum++;
return sum;
}
};
return obj;
}
let result = f1();
console.log(result.inc());//1
console.log(result.inc());//2
console.log(result.inc());//3
防抖与节流
防抖与节流函数是一种最常用的 高频触发优化方式,能对性能有较大的帮助。
- 防抖 (debounce): 将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可。
function debounce(fn, wait, immediate) {
let timer = null
return function() {
let args = arguments
let context = this
if (immediate && !timer) {
fn.apply(context, args)
}
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(context, args)
}, wait)
}
}
- 节流(throttle): 每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景: 滚动条事件 或者 resize 事件,通常每隔 100~500 ms执行一次即可。
function throttle(fn, wait, immediate) {
let timer = null
let callNow = immediate
return function() {
let context = this,
args = arguments
if (callNow) {
fn.apply(context, args)
callNow = false
}
if (!timer) {
timer = setTimeout(() => {
fn.apply(context, args)
timer = null
}, wait)
}
}
}
对象的拷贝
- 浅拷贝: 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响
- Object.assign
- 展开运算符(…)
- 深拷贝: 完全拷贝一个新对象,修改时原对象不再受到任何影响
- JSON.parse(JSON.stringify(obj)): 性能最快
- 具有循环引用的对象时,报错
- 当值为函数、undefined、或symbol时,无法拷贝
- 递归进行逐一赋值
- JSON.parse(JSON.stringify(obj)): 性能最快
function deepClone(source){
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for(let keys in source){ // 遍历目标
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
}else{ // 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
数组的方法(Array)
- map: 遍历数组,返回回调返回值组成的新数组
- forEach: 无法break,可以用try/catch中throw new Error来停止
- filter: 创建一个新的数组,返回指定数组中符合条件的所有元素。
- find:当数组中的元素在测试条件时返回 true 时, find() 返回符合条件的元素,之后的值不会再调用执行函数。
- some: 有一项返回true,则整体为true。
- from: 方法用于通过拥有 length 属性的对象或可迭代的对象来返回一个数组。如果对象是数组返回 true,否则返回 false。
- every: 用于检测数组所有元素是否都符合指定条件(通过函数提供),有一项返回false,则整体为false
- join: 通过指定连接符生成字符串
- push / pop: 末尾推入和弹出,改变原数组, 返回推入/弹出项
- unshift / shift: 头部推入和弹出,改变原数组,返回操作项
- sort(fn) / reverse: 排序与反转,改变原数组
- concat: 连接数组,不影响原数组, 浅拷贝
- slice(start, end): 返回截断后的新数组,不改变原数组
- splice(start, number, value…): 返回删除元素组成的数组,value 为插入项,改变原数组
- indexOf / lastIndexOf(value, fromIndex): 查找数组项,返回对应的下标
- reduce / reduceRight(fn(prev, cur), defaultPrev): 两两执行,prev 为上次化简函数的return值,cur 为当前值(从第二项开始)
- copyWithin:方法用于从数组的指定位置拷贝元素到数组的另一个指定位置中。
- 数组乱序:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function () {
return Math.random() - 0.5;
});
- 数组拆解: flat: [1,[2,3]] —> [1, 2, 3]
Array.prototype.flat = function() {
return this.toString().split(',').map(item => +item )
}
let arr = [1, [2, [3, [4, 5]]], 6];
let arr_flat = arr.flat(Infinity);
map和forEach的区别:
forEach()方法不会返回执行结果,而是undefined。也就是说,forEach()会修改原来的数组。而map()方法会得到一个新的数组并返回。
面试题:
实现从0-100的一个整数
function sum (m,n){
var num = Math.floor(Math.random()*(m - n) + n);
alert(num)
}
js的设计模式
setTimeout async promise执行顺序总结
https://blog.csdn.net/baidu_33295233/article/details/79335127
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log( 'async2');
}
console.log("script start");
setTimeout(function () {
console.log("settimeout");
},0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log('script end');
// 输出结果
script start
async1 start
async2
promise1
script end
promise2
async1 end
settimeout
require和import的区别
https://juejin.im/post/6844904161847279629
千分位的实现方法
var num = 123123;
// 第一种方法
num.toLocaleString();
// 第二种方法
num.toString().replace(/(\d)(?=(?:\d{3})+$)/g,'$1,')
封装为函数:
function numFormat(num) {
var c = (num.toString().indexOf ('.') !== -1) ? num.toLocaleString() : num.toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,');
return c;
}
用promise封装setstate
class Home extends React.Component{
state = {
count: 0
}
setStateP = (newState) => {
return new Promise((resolve, reject)=>{
this.setState(newState, resolve)
})
}
render(){
return (<div
onClick={()=>{
this.setStateP({count:1})
.then(()=>console.log(this.state))
}}>
{this.state.count}
</div>)
}
}
export default Home;
ES6
https://es6.ruanyifeng.com/#docs/intro#Babel-%E8%BD%AC%E7%A0%81%E5%99%A8
React
让你自己写一个react,你会从哪些方面考虑?
state和setState的特性
- 不能直接修改state,需要setState()
- State 的更新可能是异步的
- setState在合成事件和生命周期中是异步的,执行批量更新,可以优化性能
- setState在setTimeOut和原生事件中是同步的
- 出于性能考虑,React 可能会把多个
setState()
调用合并成一个调用,想要实现都更新的效果setState()
可以接收一个函数 - state是向下流动的,可以作为一个props传递给子组件
生命周期函数
React v16.3之前的生命周期函数如下图所示:
shouldComponentUpdate()如果没有返回值的话会报错,提示你必须返回true或者false
React v16.3,引入了两个新的生命周期函数:
- getDerivedStateFromProps
随着getDerivedStateFromProps的推出,同时干掉了一组生命周期API,包括:- componentWillReceiveProps
- componentWillMount
- componentWillUpdate
这个getDerivedStateFromProps是一个静态函数,所以函数体内不能访问this,简单说,就是应该一个纯函数,纯函数是一个好东西啊,输出完全由输入决定。
static getDerivedStateFromProps(nextProps, prevState) {
//根据nextProps和prevState计算出预期的状态改变,返回结果会被送给setState
}
每当父组件引发当前组件的渲染过程时(无论是Mounting还是Updating),getDerivedStateFromProps会被调用,这样我们有一个机会可以根据新的props和之前的state来调整新的state
- getSnapshotBeforeUpdate
getSnapshotBeforeUpdate,这函数会在render之后执行,而执行之时DOM元素还没有被更新,给了一个机会去获取DOM信息,计算得到一个snapshot,这个snapshot会作为componentDidUpdate的第三个参数传入。 ```javascript getSnapshotBeforeUpdate(prevProps, prevState) { console.log(‘#enter getSnapshotBeforeUpdate’); return ‘foo’; }
componentDidUpdate(prevProps, prevState, snapshot) { console.log(‘#enter componentDidUpdate snapshot = ‘, snapshot); }
- 官方给了一个例子,用getSnapshotBeforeUpdate来处理scroll。
总结:
用一个静态函数getDerivedStateFromProps来取代被deprecate的几个生命周期函数,就是强制开发者在render之前只做无副作用的操作,而且能做的操作局限在根据props和state决定新的state而已。
**经典面试题:组件的渲染顺序问题,A(父组件)和B(子组件)的在初始化的时候生命周期执行的顺序是什么?**
- A-constructor()
- A-componentWillMount()
- A-render()
- B-constructor()
- B-componentWillMount()
- B-render()
- B-componentDidMount()
- A-componentDidMount()
<a name="07926f7d"></a>
### 状态提升
在 React 中,将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state。需要在父组件中传递值和一个事件,通过在子组件触发父组件的事件来把值传递给父组件,这样父组件就可以通过setState修改props的值,再传递给子组件了。
<a name="context"></a>
### context
我们可以通过和context,让消费者consumer拿到provider中的value值。
<a name="redux"></a>
### redux
Redux只是个纯粹的状态管理器,默认只⽀持同步,实现异 步任务 ⽐如延迟,⽹络请求,需要中间件的⽀持,⽐如我们 试⽤最简单的redux-thunk和redux-logger 。 中间件就是⼀个函数,对store.dispatch⽅法进⾏改造, 在发出 Action 和执⾏ Reducer 这两步之间,添加了其他功 能。
Redux流程:
1. 需要⼀个store来存储数据
1. store⾥的reducer初始化state并定义state修改规则
1. 通过getState获取state
1. 通过dispatch⼀个action来提交对数据的修改
1. action提交到reducer函数⾥,根据传⼊的action的type, 返回新的state
1. subscribe 监听变更<br />store/index.js
```javascript
import { createStore } from 'redux'
// 创建reducer
function counterReducer(state = 0, action) {
switch (action.type) {
case 'ADD':
return state + 1
case 'MINUS':
return state - 1
default:
return state
}
}
const store = new createStore(counterReducer)
export default store
- ReduxPage.js ```javascript import React, { Component } from ‘react’ import store from ‘../store’
class ReduxPage extends Component { componentDidMount() { store.subscribe(() => { this.forceUpdate() }) } render() { console.log(store) return (
reduxPage
{store.getState()}
export default ReduxPage
Redux的使用场景:
1. 你有着相当⼤量的、随时间变化的数据
1. 你的state 需要有⼀个单一可靠数据来源
1. 你觉得把所有state 放在最顶层组件中已经⽆法满足需要了
1. 某个组件的状态需要共享
> redux中间件如何分辨同步和异步
**redux实现异步:**
1. 不借用第三方库实现<br />**场景建立**:先来个很常见的场景,在Http请求之前我们一般会用loading组件来表示数据正在请求,等Http请求结束就关闭loading。下图所示的是Http请求的3种状态:<br />![](https://cdn.nlark.com/yuque/0/2020/png/338969/1599743179409-b2a54b5f-9e6c-4566-84e2-6f1ddcd67330.png#align=left&display=inline&height=102&margin=%5Bobject%20Object%5D&originHeight=102&originWidth=443&size=0&status=done&style=none&width=443)
```javascript
dispatch({type:"LOAD_ACTION_START"}); //开始请求
dispatch({type:"LOAD_ACTION_SUCCESS"}); //请求成功
dispatch({type:"LOAD_ACTION_ERROR"}); //请求失败
大致的实现流程:
class DemoView extends Component {
getHttpData=()=>{ //获取HTTP请求——核心代码
Store.dispatch(LOAD_ACTION_FUNS(LOAD_ACTION_START));
setTimeout(()=>{ //模拟http请求
const res={ //模拟服务器返回的数据
flag:"success",
msg:"",
datas:[
{
title:"请求成功"
},{
title:"请求成功"
},{
title:"请求成功"
}
]
}
if(res["flag"] == "success"){ //请求成功
Store.dispatch(LOAD_ACTION_FUNS(LOAD_ACTION_SUCCESS,{datas:res['datas']}));
}else{ //请求失败
Store.dispatch(LOAD_ACTION_FUNS(LOAD_ACTION_SUCCESS,{msg:res['msg']}));
}
},3000);
}
render(){
//忽略
return null;
}
}
借用第三方库实现Redux异步
- react-redux:属于将react项目与redux进行绑定关联的第三方库;
- redux-chunk:可以将dispatch()的参数定义为函数的中间件;
Redux中间件:中间件一般用来处理action对象。而action对象通过dispatch()方法派发给reducer处理。在进入reducer之前那阶段称之为中间件管道。也就是通过在中间管道之间执行一系列中间件功能以达到增强action功能,最终进入reducer处理;
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
)
//connect()方法的用法
import React, { Component } from 'react';
import {View,Text,Button,FlatList} from "react-native";
import {connect} from "react-redux";
//这里封装了redux异步写法
import {MeSettingAction} from "../../redux/mesetting";
class MeSettingView extends Component{
componentDidMount(){
this.props.initUserInfo();
}
render(){
//使用connect(mapStateToProps)中userInfo
const userInfo=this.props.userInfo;
return (
<View style={styles.mAddBox}></View>
);
}
}
export default connect((state)=>{ //mapStateToProps
return {
userInfo:state['userInfo']
}
},(dispatch)=>{ //mapDispatchToProps
return {
initUserInfo:()=>dispatch(MeSettingAction())
}
})(MeSettingView);
import {HttpAjax,} from "../utils";
const initState={
type:0, //是否请求成功的状态->0:未开始;1:正在请求;2:请求成功;3:请求失败
msg:"", //请求后的描述
datas:[] //请求返回的数据
};
export const MeSettingReducer=(state=initState,action)=>{
switch(action['type']){
case "MESETTING_START":
return {...state,type:1};
case "MESETTING_SUCCESS":
return {...state,type:2,datas:action['payload']};
case "MESETTING_ERROR":
return {...state,type:3,datas:action['payload']};
default:
return state;
}
}
export const MeSettingAction=()=>{
return async (dispatch)=>{
//开始请求
dispatch({type:"MESETTING_START"});
const url="";
const sendData={};
return new Promise((resolve,reject)=>{
HttpAjax(url,sendData,'get').then((res)=>{
const result=JSON.parse(res);
if(result['flag'] == "success"){
//请求成功
dispatch({type:"MESETTING_SUCCESS",payload:result['datas']});
}else{
//请求失败
dispatch({type:"MESETTING_ERROR",payload:result['msg']});
}
resolve(result);
}).catch((err)=>{
//请求失败
dispatch({type:"MESETTING_ERROR",payload:"请求失败"});
reject(err);
});
})
}
}
react-redux
react-redux提供了了两个api:
- Provider 为后代组件提供store
- connect 为组件提供数据和变更更⽅方法
import React, { Component } from 'react'
import { connect } from 'react-redux'
export default connect((state) => ({ num: state }), {
add: () => ({ type: 'ADD' }),
minus: () => ({ type: 'MINUS' }),
})(
class ReactReduxPage extends Component {
render() {
console.log(this.props)
const { num, add, minus } = this.props
return (
<div>
<h3>react-reduxPage</h3>
<p>{num}</p>
<button onClick={() => add({ type: 'ADD' })}>ADD</button>
<button onClick={() => minus({ type: 'MINUS' })}>MINUS</button>
</div>
)
}
}
)
API
使组件层级中的 connect() ⽅法都能 够获得 Redux store。正常情况下,你的根组件应该嵌套在 中才能使⽤ connect() ⽅法。
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
连接 React 组件与 Redux store。 返回⼀个新的已与 Redux store 连接的组件类。
参数:
mapStateToProps(state, [ownProps]): stateProps ] (Function)
该回调函数必须返回⼀个纯对象,这个对象会与组件的 props 合并。 如果定义该参数,组件将会监听 Redux store 的变化,否 则 不监听。 ownProps是当前组件⾃身的props,如果指定了,那么只 要组件接收到新的 props,mapStateToProps 就会被调 ⽤,mapStateToProps 都会被重新计 算,mapDispatchToProps 也会被调⽤。注意性能!mapDispatchToProps(dispatch, [ownProps]): dispatchProps ] (Object or Function):
如果你省略这个 mapDispatchToProps 参数,默认情况 下,dispatch 会注⼊到你的组件 props 中。 如果传递的是⼀个对象,那么每个定义在该对象的函数都 将被当作 Redux action creator,对象所定义的⽅法名将 作为属性名;每个⽅法将返回⼀个新的函数,函数中 dispatch⽅法会将action creator的返回值作为参数执 ⾏。这些属性会被合并到组件的 props 中。 如果传递的是⼀个函数,该函数将接收⼀个 dispatch 函 数,然后由你来决定如何返回⼀个对象。 ownProps是当前组件⾃身的props,如果指定了,那么只 要组件接收到新的 props,mapDispatchToProps 就会被 调⽤。注意性能!mergeProps(stateProps, dispatchProps, ownProps): props ] (Function)
如果指定了这个参数,mapStateToProps() 与 mapDispatchToProps() 的执⾏结果和组件⾃身的 props 将传⼊到这个回调函数中。该回调函数返回的对象 将作为 props 传递到被包装的组件中。你也许可以⽤这个 回调函数,根据组件的 props 来筛选部分的 state 数据, 或者把 props 中的某个特定变量与 action creator 绑定在 ⼀起。如果你省略这个参数,默认情况下返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的结果。
react-router
路由的实现主要有两种实现方式:一种是利用hash 与hashchange事件监听,另一种是利用 history与pushstate,repalceState和popstate事件监听。
react-router包含3个库,react-router、react-router-dom和 react-router-native。
react-router中奉⾏⼀切皆组件的思想,路由器-Router、链 接-Link、路由-Route、独占-Switch、重定向-Redirect都 以组件形式存在
Route渲染优先级:children>component>render。
路由守卫思路:创建⾼阶组件包装Route使其具有权限判断功能,创建PrivateRoute
Hook
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使你在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷
State Hook
import React, { useState } from 'react';
function Example() {
// 声明一个叫 “count” 的 state 变量。
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
在这里,useState
就是一个 Hook,通过在函数组件里调用它来给组件添加一些内部 state。React 会在重复渲染时保留这个 state。useState
会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState
,但是它不会把新的 state 和旧的 state 进行合并。
Effect Hook
在 React 组件中执行过数据获取、订阅或者手动修改过 DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”。
useEffect
就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMount
、componentDidUpdate
和 componentWillUnmount
具有相同的用途,只不过被合并成了一个 API。
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 相当于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 使用浏览器的 API 更新页面标题
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
当你调用 useEffect
时,就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数。由于副作用函数是在组件内声明的,所以它们可以访问到组件的 props 和 state。默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。副作用函数还可以通过返回一个函数来指定如何“清除”副作用。
Hook的使用规则
- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中)。
自定义Hook
我们可以在各种场景自定义 Hook,如表单处理、动画、订阅声明、计时器等。
Hook 是一种复用状态逻辑的方式,它不复用 state 本身。事实上 Hook 的每次调用都有一个完全独立的 state —— 因此你可以在单个组件中多次调用同一个自定义 Hook。
规则:
自定义 Hook 是一个函数,名称以 “use
” 开头,函数内部可以调用其他的 Hook
Virtual DOM
它是一个JSON对象,表述了当前的UI,告诉React当前的UI是什么状态,并通过ReactDom等类库实现与真实Dom的同步。
react中⽤JSX语法描述视图,通过babel-loader转译 后它们变为React.createElement(…)形式,该函数将⽣成 vdom来描述真实dom。将来如果状态变化,vdom将作出相 应变化,再通过diff算法对⽐新⽼vdom区别从⽽做出最终 dom操作
Diff算法
算法复杂度O(n)
diff策略:
- Tree-diff:同级⽐较,Web UI 中 DOM 节点跨层级的移动操作 特别少,可以忽略不计。
- Component diff:拥有相同类的两个组件生成相似的树形结构,拥有不同类型的两个组件将会⽣成不同的树形结构。
- Element diff:对于同一层级的一组子节点,通过唯一id区分。
Tree-diff
- React通过updateDepth对Virtual DOM树进行层级控制。
- 对树分层比较,两棵树 只对同一层次节点 进行比较。如果该节点不存在时,则该节点及其子节点会被完全删除,不会再进一步比较。
- 只需遍历一次,就能完成整棵DOM树的比较。
如果DOM节点出现了跨层级操作,diff只简单考虑同层级的节点位置变换,如果是跨层级的话,只有创建节点和删除节点的操作。
component diff
React对不同的组件间的比较,有三种策略
- 同一类型的两个组件,按原策略(层级比较)继续比较Virtual DOM树即可。
- 同一类型的两个组件,组件A变化为组件B时,可能Virtual DOM没有任何变化,如果知道这点(变换的过程中,Virtual DOM没有改变),可节省大量计算时间,所以 用户 可以通过 shouldComponentUpdate() 来判断是否需要 判断计算。
- 不同类型的组件,将一个(将被改变的)组件判断为dirty component(脏组件),从而替换 整个组件的所有节点。
注意:如果组件D和组件G的结构相似,但是 React判断是 不同类型的组件,则不会比较其结构,而是删除 组件D及其子节点,创建组件G及其子节点。
element diff
当节点处于同一层级时,diff提供三种节点操作:删除、插入、移动。
插入:组件 C 不在集合(A,B)中,需要插入
删除:(1)组件 D 在集合(A,B,D)中,但 D的节点已经更改,不能复用和更新,所以需要删除 旧的 D ,再创建新的。(2)组件 D 之前在 集合(A,B,D)中,但集合变成新的集合(A,B)了,D 就需要被删除。
移动:组件D已经在集合(A,B,C,D)里了,且集合更新时,D没有发生更新,只是位置改变,如新集合(A,D,B,C),D在第二个,无须像传统diff,让旧集合的第二个B和新集合的第二个D 比较,并且删除第二个位置的B,再在第二个位置插入D,而是 (对同一层级的同组子节点) 添加唯一key进行区分,移动即可。
fiber
组件的渲染问题
假如有A,B,C,D组件,B是A的子组件,C,D是B的子组件,如下图所示:
那么在挂载阶段,A,B,C,D的生命周期渲染顺序是如何的呢?
以render()函数为分界线。从顶层组件开始,一直往下,直至最底层子组件。然后再往上。组件update阶段同理。
前面是react16以前的组建渲染方式。这就存在一个问题:如果这是一个很大,层级很深的组件,react渲染它需要几十甚至几百毫秒,在这期间,react会一直占用浏览器主线程,任何其他的操作(包括用户的点击,鼠标移动等操作)都无法执行。对于⼤型项⽬,组件树会很⼤,这个时候递归遍历的 成本就会很⾼,会造成主线程被持续占⽤,结果就是 主线程上的布局、动画等周期性任务就⽆法⽴即得到 处理,造成视觉上的卡顿,影响⽤户体验。Fiber架构就是为了解决这个问题。
fiber架构 组建的渲染顺序
加入fiber的react将组件更新分为两个时期:phase1和phase2,这两个时期以render为分界。render前的生命周期为phase1, render后的生命周期为phase2
phase1的生命周期是可以被打断的,每隔一段时间它会跳出当前渲染进程,去确定是否有其他更重要的任务。此过程,React 在 workingProgressTree (并不是真实的virtualDomTree)上复用 current 上的 Fiber 数据结构来一步地(通过requestIdleCallback)来构建新的 tree,标记处需要更新的节点,放入队列中。
phase2的生命周期是不可被打断的,React 将其所有的变更一次性更新到DOM上。
这里最重要的是phase1这是时期所做的事。因此我们需要具体了解phase1的机制。- 如果不被打断,那么phase1执行完会直接进入render函数,构建真实的virtualDomTree
- 如果组件再phase1过程中被打断,即当前组件只渲染到一半(也许是在willMount,也许是willUpdate~反正是在render之前的生命周期),那么react会怎么干呢? react会放弃当前组件所有干到一半的事情,去做更高优先级更重要的任务(当然,也可能是用户鼠标移动,或者其他react监听之外的任务),当所有高优先级任务执行完之后,react通过callback回到之前渲染到一半的组件,从头开始渲染。(看起来放弃已经渲染完的生命周期,会有点不合理,反而会增加渲染时长,但是react确实是这么干的)
那么这里就会存在一个问题了:
也就是 所有phase1的生命周期函数都可能被执行多次,因为可能会被打断重来
这样的话,就和react16版本之前有很大区别了,因为可能会被执行多次,那么我们最好就得保证phase1的生命周期每一次执行的结果都是一样的,否则就会有问题,因此,最好都是纯函数。
(所以react16目前都没有把fiber enable,其实react16还是以 同步的方式在做组建的渲染,因为这样的话,很多我们用老版本react写的组件就有可能都会有问题,包括用的很多开源组件,但是后面应该会enable,让开发者可以开启fiber异步渲染模式~)
程墨大神还提到一个问题,饥饿问题,即如果高优先级的任务一直存在,那么低优先级的任务则永远无法进行,组件永远无法继续渲染。这个问题facebook目前好像还没解决,但以后会解决~
所以,facebook在react16增加fiber结构,其实并不是为了减少组件的渲染时间,事实上也并不会减少,最重要的是现在可以使得一些更高优先级的任务,如用户的操作能够优先执行,提高用户的体验,至少用户不会感觉到卡顿~
VUE
v-if和v-for哪个优先级更高
v-if的优先级要高于v-for的,如果同时出现,会先进行循环再判断。这样无论如何判断都会进行循环,浪费了性能。最好的做法在外层嵌套一个template,进行v-if的条件判断,在内层进行v-for的循环。
Vue组建中为什么data必须是个函数,而根实例不需要
Vue组件可能存在多个实例,如果使用对象的形式定义data,就会导致他们公用一个data对象,那么组件之间的状态变更就会受到影响,这是不合理的。如果采用函数的形式定义的,在initData的时候会将其作为工厂函数返回一个全新的对象出来,有效的规避了多实例之间的状态污染。而跟实例只有一个不需要担心这种情况。
key的作用和工作原理
key的作用主要是为了高效的更新dom,其原理是vue在patch的过程中可以通过key判断两个节点是否是同一个,从而避免频繁的更新不同的元素,使得整个patch的过程更加的高效,减少dom的操作,提高了性能。
Vue中的Diff算法
谈一谈对Vue组件化的理解
回答总体的思路:
组件化的定义、优点、使用场景和注意事项。同时要强调组件化中的一些特点。
- 组件是独立和可复用的代码组织单元。组件系统是 Vue 核心特性之一,它使开发者使用小型、 独 立和通常可复用的组件构建大型应用;
- 组件化开发能大幅提高应用开发效率、测试性、复用性等;
- 组件使用按分类有:页面组件、业务组件、通用组件;
- vue 的组件是基于配置的,我们通常编写的组件是组件配置而非组件,框架后续会生成其构造函 数,它们基于 VueComponent,扩展于 Vue;
- vue 中常见组件化技术有:属性 prop,自定义事件,插槽等,它们主要用于组件通信、扩展等;
- 合理的划分组件,有助于提升应用性能;
- 组件应该是高内聚、低耦合的;
- 遵循单向数据流的原则。
谈一谈你对Vue设计原则的理解
官网定义:
- 渐进式JavaScript框架,学习也可以循序渐进。
- 易用、灵活高效
渐进式 JavaScript 框架:
与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅 易 于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结 合使 用时,Vue 也完全能够为复杂的单页应用提供驱动。
易用性
vue 提供数据响应式、声明式模板语法和基于配置的组件系统等核心特性。这些使我们只需要关注应 用 的核心业务即可,只要会写 js、html 和 css 就能轻松编写 vue 应用。
灵活性
渐进式框架的最大优点就是灵活性,如果应用足够小,我们可能仅需要 vue 核心特性即可完成功能; 随 着应用规模不断扩大,我们才可能逐渐引入路由、状态管理、vue-cli 等库和工具,不管是应用体积 还是 学习难度都是一个逐渐增加的平和曲线。
高效性
开课吧 web 全栈架构师 超快的虚拟 DOM 和 diff 算法使我们的应用拥有最佳的性能表现。 追求高效的过程还在继续,vue3 中引入 Proxy 对数据响应式改进以及编译器中对于静态内容编译的改 进 都会让 vue 更加高效。
谈谈你对MVC、MVP和MVVM的理解
MVC
Model:负责保存应用数据,与后端数据进行同步 Controller:负责业务逻辑,根据用户行为对 Model 数据进行修 改 View:负责视图展示,将 model 中的数据可视化出来。
缺点:这样的模型,在理论上是可行的。但往往在实际开发中,并不会这样操作。因为开发过程并不灵活。 例 如,一个小小的事件操作,都必须经过这样的一个流程,那么开发就不再便捷了。
MVP
MVP 与 MVC 很接近,P 指的是 Presenter,presenter 可以理解为一个中间人,它负责着 View 和 Model 之 间的数据流动,防止 View 和 Model 之间直接交流。
我们可以通过看到,presenter 负责和 Model 进行双向交互,还和 View 进行双向交互。这种交互方 式, 相对于 MVC 来说少了一些灵活,VIew 变成了被动视图,并且本身变得很小。虽然它分离了 View 和 Model。但是应用逐渐变大之后,导致 presenter 的体积增大,难以维护。
MVVM
MVVM 可以分解成(Model-View-VIewModel)。ViewModel 可以理解为在 presenter 基础上的进阶版。
ViewModel 通过实现一套数据响应式机制自动响应 Model 中数据变化;
同时 Viewmodel 会实现一套更新策略自动将数据变化转换为视图更新;
通过事件监听响应 View 中用户交互修改 Model 中数据。 这样在 ViewModel 中就减少了大量 DOM 操作代码。 MVVM 在保持 View 和 Model 松耦合的同时,还减少了维护它们关系的代码,使用户专注于业务逻辑, 兼顾开发效率和可维护性。
总结:
- 这三者都是框架模式,它们设计的目标都是为了解决 Model 和 View 的耦合问题。
- MVC 模式出现较早主要应用在后端,如 Spring MVC、ASP.NET MVC 等,在前端领域的早期也有 应 用,如 Backbone.js。它的优点是分层清晰,缺点是数据流混乱,灵活性带来的维护性问题。
- MVP 模式在是 MVC 的进化形式,Presenter 作为中间层负责 MV 通信,解决了两者耦合问题,但 P 层 过于臃肿会导致维护问题。
- MVVM 模式在前端领域有广泛应用,它不仅解决 MV 耦合问题,还同时解决了维护两者映射关系 的 大量繁杂代码和 DOM 操作代码,在提高开发效率、可读性同时还保持了优越的性能表现
你了解哪些 Vue 性能优化方法?
路由懒加载
const router = new VueRouter({
routes: [
{ path: '/foo', component: () => import('./Foo.vue') }
]
})
keep-alive 缓存页面
使用 v-show 复用 DOM
<template>
<div class="cell">
<!--这种情况用 v-show 复用 DOM,比 v-if 效果好-->
<div v-show="value" class="on">
<Heavy :n="10000"/>
</div>
<section v-show="!value" class="off">
<Heavy :n="10000"/>
</section>
</div>
</template>
v-for 遍历避免同时使用 v-if
<template>
<ul>
<li
v-for="user in activeUsers"
:key="user.id">
{{ user.name }}
</li>
</ul>
</template>
<script>
export default {
computed: {
activeUsers: function () {
return this.users.filter(function (user)
{
return user.isActive
}
)
}
}
}
</script>
长列表性能优化
如果列表是纯粹的数据展示,不会有任何改变,就不需要做响应化。
export default {
data: () => ({
users: []
}),
async created() {
const users = await axios.get("/api/users");
this.users = Object.freeze(users);
}
};
如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容
<recycle-scroller
class="items"
:items="items"
:item-size="24"
>
<template vslot="{ item }">
<FetchItemView
:item="item"
@vote="voteItem(item)"/>
</template>
</recycle-scroller>
参考: vue-virtual-scroller、vue-virtual-scroll-list
- 图片懒加载
对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区 域 内的图片先不做加载, 等到滚动到可视区域后再去加载。
参考项目:vue-lazyload
- 第三方插件按需引入
element-ui 这样的第三方组件库可以按需引入避免体积太大。
import Vue from 'vue';
import { Button, Select } from 'element-ui';
Vue.use(Button)
Vue.use(Select)
无状态的组件标记为函数式组件
<template functional>
<div class="cell">
<div v-if="props.value" class="on"></div>
<section v-else class="off"></section>
</div>
</template>
<script>
export default {
props: ['value']
}
</script>
子组件分割
因为每个组件对应一个watcher,避免watcher太大。
<template>
<div>
<ChildComp/> // 防止引起不必要的更新
</div>
</template>
<script>
export default {
components: {
ChildComp: {
methods: {
heavy () { /* 耗时任务 */ }
},
render (h) {
return h('div',this.heavy());
}
}
}
}
</script>
变量提前保存
<script>
import { heavy } from '@/utils'
export default {
props: ['start'],
computed: {
base () { return 42 },
result () {
const base = this.base // 不要频繁引用
this.base let result = this.start
for (let i = 0; i < 1000; i++) {
result += heavy(base)
}
return result
}
}
}
</script>
-
v-model和v-bind绑定数据的异同
- v-bind是数据绑定,没有双向绑定效果,但不一定在表单元素上使用,任何有效元素上都可以使用;
- v-model是双向绑定,基本上只用在表单元素上;
- 当v-bind和v-model同时用在一个元素上时,它们各自的作用没变,但v-model优先级更高,而且需区分这个元素是单个的还是一组出现的。
<input :value="name" v-model="body">
v-model其实是v-bind和v-on的语法糖
<input v-model="something">其实是<input v-bind:value="something" v-on:input="something = $event.target.value">的语法糖
实现双向绑定Proxy比defineproperty优劣如何
https://www.jianshu.com/p/2df6dcddb0d7
你对 Vue3.0 的新特性有没有了解?
更快:
- 虚拟 DOM 重写
- 优化 slots 的生成
- 静态树提升
- 静态属性提升
- 基于 Proxy 的响应式系统
更小:
- 通过摇树优化核心库体积
更容易维护:
- TypeScript + 模块化
更加友好
- 跨平台:编译器核心和运行时核心与平台无关,使得 Vue 更容易与任何平台(Web、 Android、iOS)一起使用
更容易使用
vue 中组件之间的通信方式
vue-router 中的导航钩子由那些?
路由守卫有哪几种?执行的顺序是怎么样的?
他们之间有什么区别?
可以获取组件实例的是哪一个?
前后端的路由有什么区别?
前端路由的实现方式有哪些?
next()是怎么实现的?高阶函数,可以处理不同的参数
你知道 nextTick 的原理吗?
你对vue响应式的理解?
vue的生命周期
Vue和React的对比
- 设计思想
- vue
- 渐进式框架
- 双向绑定/双向数据流,使用Object.defineProperty()
- react
- 函数式编程,组件式开发,单向数据流 - 双向数据流可以通过onChange方法,setState 方法
- 编写语法
- vue推荐的做法是webpack+vue-loader的单文件组件格式,vue保留了html、css、js分离的写法
- React推荐的做法是 JSX + inline style, 也就是把HTML和CSS全都写进JavaScript了,即’all in js’。
- 构建工具
- vue提供了CLI 脚手架,可以帮助你非常容易地构建项目。
- React在这方面也提供了create-react-app,但是现在还存在一些局限性,不能配置等等。
- 数据绑定
- vue使用双向数据绑定,就是说View的变化能实时让Model发生变化,而Model的变化也能实时更新到View。
- React使用单向数据流,React中是不允许更改属性(props)的值,但是状态(state)是可以更改的。React中在更改state值的时候不能通过this.state这种方式更改,需要调用this.setState()方法区修改state值,而且this.setState()方法是异步执行的。
- 组件通信的区别
- Vue 中有三种方式可以实现组件通信
- 父组件通过 props 向子组件传递数据或者回调,虽然可以传递回调,但是我们一般只传数据,而通过 事件的机制来处理子组件向父组件的通信
- 子组件通过 事件 向父组件发送消息
- 通过 V2.2.0 中新增的 provide/inject 来实现父组件向子组件注入数据,可以跨越多个层级
- 另外有一些比如访问 parent /children等
- React 中
- 父组件通过 props 可以向子组件传递数据或者回调
- 可以通过 context 进行跨层级的通信,这其实和 provide/inject 起到的作用差不多
webpack
webpack编译过程:
1.拿到webpack.config文件的配置内容
2.读取入口文件里的内容
3.利用@babel/parser的parser.parse(content)把文件内容转化成ast树,通过@babel/traverse的traverse(ast, {ImportDeclaration()})拿到文件内容中依赖的模块。
4.利用@babel/core的transformFromAst(ast, null, {presets: [‘@babel/preset-env’],})处理成标准的代码。
5.如果有依赖模块,循环递归执行2,3,4,拿到所有文件中的内容
6.把文件内容生成一个文件,在自执行函数中,处理require和export,执行code。
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const uglify = require('uglifyjs-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
mode: "production",
entry: {
index: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename:'[name].bundle.js'
},
devServer: {
contentBase:path.resolve(__dirname,'dist'), //要运行的文件的绝对路径
host:'localhost', //本地主机 相当于后台的ip
port:8080, //端口号
open:true, //启动服务器自动打开浏览器
hot:true, //热更新
},
// 关于模块配置
module:{
rules:[
// 模块规则(配置 loader、解析器等选项)
// 配置css-loader
{
test:/\.css$/,
// use:['style-loader','css-loader'], // 使用style-loader,css-loader进行检测css文件
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},
// 配置bable-loader
{
test:/\.(js|jsx)$/, //就是所有js,jsx结尾的都去检查
use:['babel-loader'],//还需要配置预设,可以在这里面,也可以单独建立文件夹
exclude:/node_modules/ //但是node_modules里面的js忽视不检查
}
]
},
plugins:[
new ExtractTextPlugin("styles.css"), // 分离css
new uglify(), // 版本压缩
new webpack.HotModuleReplacementPlugin(),// 引用热更新
new CleanWebpackPlugin(), // 表示每次运行之前先删除dist文件夹
new HtmlWebpackPlugin({
minify:{
collapseWhitespace:true, // 对打包以后要上线的dist包里面的所有文件进行去空格代码压缩
removeAttributeQuotes:true // 删除属性双引号
},
hash:true,// 生成文件后拽hash,防止缓存文件
title:'标题是可以配置的,配置你想要的标题',
chunks:['index'], // 配置入口文件的key
template:'./src/index.html' // 里面放的是模版地址就是src里面源文件index.html的地址
}),
]
}
webpack运行流程
在了解 Webpack 原理前,需要掌握以下几个核心概念,以方便后面的理解:
Entry
:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。Module
:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。Chunk
:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。Loader
:模块转换器,用于把模块原内容按照需求转换成新内容。Plugin
:扩展插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
- 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
- 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
- 确定入口:根据配置中的 entry 找出所有的入口文件;
- 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
- 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
eventLoop
执行栈在执行完同步任务后,查看执行栈是否为空,如果执行栈为空,就会去执行Task(宏任务),每次宏任务执行完毕后,检查微任务(microTask)队列是否为空,如果不为空的话,会按照先入先出的规则全部执行完微任务(microTask)后,设置微任务(microTask)队列为null,然后再执行宏任务,如此循环。
浏览器缓存
浏览器缓存控制分为强缓存和协商缓存,协商缓存必须配合强缓存使用。
- 强制缓存如果生效,不需要再和服务器发生交互
- 而对比缓存不管是否生效,都需要与服务端发生交互
两类缓存规则可以同时存在,强制缓存优先级高于对比缓存,也就是说,当执行强制缓存的规则时,如果缓存生效,直接使用缓存,不再执行对比缓存规则
1. 强缓存
- Expires
Expires的值为服务端返回的到期时间,即下一次请求时,请求时间小于服务端返回的到期时间,直接使用缓存数据。(不过Expires 是HTTP 1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。)
另一个问题是,到期时间是由服务端生成的,但是客户端时间可能跟服务端时间有误差,这就会导致缓存命中的误差。(所以HTTP 1.1 的版本,使用Cache-Control替代.)
- Cache-Control
Cache-Control 是最重要的规则。常见的取值有private、public、no-cache、max-age,no-store,默认为private。
Cache-Control描述的是一个相对时间,在进行缓存命中时都是利用浏览器时间判断。Expires和Cache-Control这两个header可以只启用一个,也可以同时启用,同时启用时Cache-Control优先级高于Expires
2. 协商缓存
浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。 再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。
一共分为两种标识传递:
- Last-Modified
服务器在响应请求时,告诉浏览器资源的最后修改时间
- If-Modified-Since
再次请求服务器时,通过此字段通知服务器上次请求时,服务器返回的资源最后修改时间
服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对
若资源的最后修改时间大于If-Modified-Since,说明资源又被改动过,则响应整片资源内容,返回状态码200。
- Etag
服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)
- If-None-Match
再次请求服务器时,通过此字段通知服务器客户段缓存数据的唯一标识。
服务器收到请求后发现有头If-None-Match 则与被请求资源的唯一标识进行比对。
不同,说明资源又被改动过,则响应整片资源内容,返回状态码200。
相同,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache
优先级高于Last-Modified / If-Modified-Since
http和https
原文链接:http 和 https
什么是http?
超文本传输协议,是一个基于请求与响应,无状态的,应用层的协议,常基于TCP/IP协议传输数据,互联网上应用最为广泛的一种网络协议,所有的WWW文件都必须遵守这个标准。设计HTTP的初衷是为了提供一种发布和接收HTML页面的方法
http1与http2的主要区别?
- 二进制传输:http2采用,相较于文本传输的http1来说更加安全可靠。
- 多路复用:http1一个连接只能提交一个请求,而http2可以同时处理无数个请求,可以降低连接的占用数量,进一步提升网络的吞吐量。
- 头部压缩:http2通过gzip与compress对头部进行压缩,并且在客户端与服务端各维护了一份头部索引表,只需要根据索引id就可以进行头部信息的传输,缩小了头部容量,间接提升了传输效率。
- 服务端推送:服务端可以主动推送资源给客户端,避免客户端花过多的时间逐个请求资源,这样可以降低整个请求的响应时间。
面试题:
- http1和http2有什么区别?
- http2怎么解决的队头阻塞?
- 多路复用是怎么做的?
- http1队头阻塞在Chrome里最大连接数量是多少?
- http1和htpp2在做持久连接上有什么区别?
- http2的服务端推送是怎么做的?
什么是HTTPS?
HTTPS是身披SSL外壳的HTTP。HTTPS是一种通过计算机网络进行安全通信的传输协议,经由HTTP进行通信,利用SSL/TLS建立全信道,加密数据包。HTTPS使用的主要目的是提供对网站服务器的身份认证,同时保护交换数据的隐私与完整性
http特点
- 无状态:协议对客户端没有状态存储,对事物处理没有“记忆”能力,比如访问一个网站需要反复进行登录操作
- 无连接:HTTP/1.1之前,由于无状态特点,每次请求需要通过TCP三次握手四次挥手,和服务器重新建立连接。比如某个客户机在短时间多次请求同一个资源,服务器并不能区别是否已经响应过用户的请求,所以每次需要重新响应请求,需要耗费不必要的时间和流量。
- 基于请求和响应:基本的特性,由客户端发起请求,服务端响应
- 简单快速、灵活
- 通信使用明文、请求和响应不会对通信方进行确认、无法保护数据的完整性,也就是说http协议发送内容,对数据不进行任何加密
HTTPS特点
- 内容加密:采用混合加密技术,中间者无法直接查看明文内容
- 验证身份:通过证书认证客户端访问的是自己的服务器
- 保护数据完整性:防止传输的内容被中间人冒充或者篡改
为什么手机要安装证书
证书是哪个机构颁发的
如何截取了Web浏览器和网站服务器之间的传输报文
ssl协议,依靠证书来验证服务器的身份
https协议需要到ca申请证书,如何申请,如何配置到自己的服务器
http:80
https:443
A-B聊天的过程:
- 服务端产生公钥
- 客户端产生随机密钥
- 使用公钥对对称密钥加密
- 客户端发送加密后的对称密钥给服务端进行通信
对称加密算法
对协商过程加密
非对称加密算法
非对称加密”的加密算法,特点是私钥加密后的密文,只要是公钥,都可以解密,但是公钥加密后的密文,只有私钥可以解密。私钥只有一个人有,而公钥可以发给所有的人
使用非对称加密算法进行对称加密算法协商过程
如何协商
HTTPS要使客户端与服务器端的通信过程得到安全保证,必须使用的对称加密算法,但是协商对称加密算法的过程,需要使用非对称加密算法来保证安全,然而直接使用非对称加密的过程本身也不安全,会有中间人篡改公钥的可能性,所以客户端与服务器不直接使用公钥,而是使用数字证书签发机构颁发的证书来保证非对称加密过程本身的安全。这样通过这些机制协商出一个对称加密算法,就此双方使用该算法进行加密解密。从而解决了客户端与服务器端之间的通信安全问题
性能优化
- 减少 HTTP 请求
- 使用 HTTP2
- 使用服务端渲染
- 静态资源使用 CDN
- 将 CSS 放在文件头部,JavaScript 文件放在底部
- 使用字体图标 iconfont 代替图片图标
- 善用缓存,不重复加载相同的资源
- 压缩文件
- 图片优化
- 图片延迟加载
- 响应式图片
- 调整图片大小
- 降低图片质量
- 尽可能利用 CSS3 效果代替图片
- 通过 webpack 按需加载 JavaScript 代码
- 减少重绘重排
- 浏览器渲染过程
- 解析HTML生成DOM树。
- 解析CSS生成CSSOM规则树。
- 将DOM树与CSSOM规则树合并在一起生成渲染树。
- 遍历渲染树开始布局,计算每个节点的位置大小信息。
- 将渲染树每个节点绘制到屏幕。
- 重排
- 当改变 DOM 元素位置或大小时,会导致浏览器重新生成渲染树,这个过程叫重排
- 重绘
- 当重新生成渲染树后,就要将渲染树每个节点绘制到屏幕,这个过程叫重绘。不是所有的动作都会导致重排,例如改变字体颜色,只会导致重绘。记住,重排会导致重绘,重绘不会导致重排
- 什么操作会导致重排?
- 添加或删除可见的 DOM 元素
- 元素位置改变
- 元素尺寸改变
- 内容改变
- 浏览器窗口尺寸改变
- 如何减少重排重绘?
- 用 JavaScript 修改样式时,最好不要直接写样式,而是替换 class 来改变样式。
- 如果要对 DOM 元素执行一系列操作,可以将 DOM 元素脱离文档流,修改完成后,再将它带回文档。推荐使用隐藏元素(display:none)或文档碎片(DocumentFragement),都能很好的实现这个方案。
- 浏览器渲染过程
- 使用事件委托
- 事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术, 使用事件委托可以节省内存。
- 注意程序的局部性
- 时间局部性
- 空间局部性
- if-else 对比 switch
- 当判断条件数量越来越多时,越倾向于使用 switch 而不是 if-else。
- 查找表
- 当条件语句特别多时,使用 switch 和 if-else 不是最佳的选择,这时不妨试一下查找表。查找表可以使用数组和对象来构建
- 避免页面卡顿
- 使用 requestAnimationFrame 来实现视觉变化
- 使用 Web Workers
- 使用位操作
- 不要覆盖原生方法
- 降低 CSS 选择器的复杂性
- 浏览器读取选择器,遵循的原则是从选择器的右边到左边读取
- CSS 选择器优先级:内联 > ID选择器 > 类选择器 > 标签选择器
- 使用 flexbox 而不是较早的布局模型
- 使用 transform 和 opacity 属性更改来实现动画
- 合理使用规则,避免过度优化