2020年前端面试初体验
1.关于js
(1)宏任务和微任务:
宏任务分类:整体代码的script setTimeout setInterval requrestAnimationFrame
微任务分类:new promise().then(回调) process.nextTick(在node运行环境中优先级高于promise())
实例:
console.log('开始');
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
}).then(function() {
console.log('then');
})
console.log('console');
运行结果: 开始 promise console then setTimetout
解释: 1 执行一个宏任务(栈中没有,就从事件队列中获取)。
2执行过程中如果遇到微任务,就将它添加到微任务队列中。
3宏任务执行完毕后,就立即执行当前微任务队列中所有的微任务(依次执行)。
4.当前宏任务执行完毕,开始检查渲染,然后GUI 线程接管渲染。
5.渲染完毕后,js线程继续接管,开始完成下一共宏任务(从事件队列中获取)。
(2)事件循环机制:
1.同步和异步任务分别进入不同的执行”场所“,同步的进入主线程,异步的进入Event 列表并注册函数。
2.当指定的事情完成时,Event 列表会将这个函数移入 event 队列
3.当栈中代码执行完毕,执行栈中的任务为空时,就会读取任务队列中的事件,去执行对应的回调。
4.如此循环,形成js的事件循环机制
(3)如何理解闭包 ,闭包的使用场景:
闭包就是函数调用自己作用域外的变量,或者说成调用作用域链上的变量,这种引用就叫闭包。 闭包的作用,改变变量的作用域。
应用场景:比如 一个变量触发某个事件的时候会改变值,而另一个函数根据这个值做响应变化。
(4)es5继承和es6继承,区别:
1.ES5先创建子类,在实例化父类并添加到子类this中
2.ES6先创建父类,在实例化子集中通过调用super方法访问父级后,在通过修改this实现继承
es5继承:实质是先创建子类元素child的实例对象,然后再把父类元素parent的原型对象中的属性赋值给了子类元素chid的实例对象里面,从而实现继承。
1.原型继承 :利用call和apply继承this上面的属性和方法
2.原型链继承:将父类的实例作为子类的原型
3.组合继承:利用call apply 继承属性
利用原型链继承方法
es6继承:es6中采用extend继承
继承用extends,当继承后需要用super()来接收父类的constructor构造函数,否在报错,当new一个子类的时候先把参数传入子类构造函数再通过super()讲父类的构造函数引入,就可以调用父类。
(5)垃圾回收:
Js具有自动垃圾回收机制。垃圾收集器会按照固定的时间间隔周期性的执行。
标记清除:工作原理:是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
引用计数 :工作原理:跟踪记录每个值被引用的次数。
(6)什么情况下会发生内存泄漏:
1. 意外的全局变量引起的内存泄漏。
原因:全局变量,不会被回收。
解决:使用严格模式避免。
2. 闭包引起的内存泄漏
原因:闭包可以维持函数内局部变量,使其得不到释放。
解决:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。
3. 没有清理的DOM元素引用
原因:虽然别的地方删除了,但是对象中还存在对dom的引用
解决:手动删除。
4. 被遗忘的定时器或者回调
原因:定时器中有dom的引用,即使dom删除了,但是定时器还在,所以内存中还是有这个dom。
解决:手动删除定时器和dom。
5. 子元素存在引用引起的内存泄漏
原因:div中的ul li 得到这个div,会间接引用某个得到的li,那么此时因为div间接引用li,即使li被清空,也还是在内存中,并且只要li不被删除,他的父元素都不会被删除。
解决:手动删除清空。
(7)深拷贝和浅拷贝:
浅拷贝:浅拷贝就是拷贝了一层,除了对象时拷贝的引用类型,其他都是直接值传递,有自己的内存空间的。
深拷贝:深拷贝时拷贝多层,即使是嵌套了对象,也会拷贝出来。
深拷贝的实现方式:
1.对象只有一层的话可以使用上面的:Object.assign()函数是深拷贝 ,否则是浅拷贝。
2.
手动复制:把一个对象的属性复制给另一个对象的属性
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = 100;
console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);
3.用JSON.stringify
把对象转成字符串,再用JSON.parse
把字符串转成新的对象。
var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1);
// { body: { a: 10 } } <-- 沒被改到
console.log(obj2);
// { body: { a: 20 } }
console.log(obj1 === obj2);
// false
console.log(obj1.body === obj2.body);
但是这种方法也有不少坏处,譬如它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。
也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON。
4.递归
5.数组的化 .splice(0)也是深拷贝。
(8)变量提升,函数的提升:
1.变量声明的提升:通过var定义(声明)的变量,在定义语句之前就可以访问到值:undefined
2.函数声明的提升:通过function(声明)的函数,在之前就可以直接调用值:函数定义(对象)
(9)call,apply,bind 区别:
相同点:
1、都是用来改变函数的this对象的指向的。
2、第一个参数都是this要指向的对象。
3、都可以利用后续参数传参。
1.call的语法:函数名.call(obj,"参数1",”参数2“);
2. apply的语法:函数名.apply(obj,[参数1,参数2,参数3……]);
3.bind 的语法:函数名bind(obj,"参数1",”参数2“)();
(10)js设计模式,应用场景:
1.单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点
应用场景: 一个redux只有一个store 就是单列模式。
2.策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换
应用场景: 表单验证方法
优点
可以有效地避免多重条件语句,将一系列方法封装起来也更直观,利于维护
缺点
往往策略集会比较多,我们需要事先就了解定义好所有的情况
3.代理模式:为一个对象提供一个代用品或占位符,以便控制对它的访问
代理模式主要有三种:保护代理、虚拟代理、缓存代理
保护代理应用场景:保护代理主要实现了访问主体得限制行为,以过滤字符作为简单得例子
虚拟代理模式应用场景:函数得节流,是虚拟代理。
缓存代理:为一些开销大得运算结果提供暂时得缓存,提升效率。 缓存加法操作。
4.迭代器模式:迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示
应用场景:map foreach
5.发布-订阅模式:也是观察者模式,定义了对象间一种一对多得依赖关系,当一个对象得状态发生改变时,所有依赖于它得对象都将得到通知。
应用场景:redux就是订阅者模式,事件监听也订阅者模式。组件自动发现props或者state变化自动更新,就是观察者模式。
6.命令模式:用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系
命令(command)指的是一个执行某些特定事情的指令
应用场景:使用对象字面量得形式定义一个命令。
7.组合模式: 用小得子对象来构建更大得对象,而这些小得子对象本身也许时由更小得”孙对象“构建成得。
应用场景:扫描文件夹种得文件
8.模板方法模式:模板方法模式由两部分结构组成,第一部分时抽象父类,第二部分是具体得实现子类。
应用场景:继承,子类直接调用父类得模板函数来执行。
9.享元模式:享元(flyweight)模式是一种用于性能优化的模式,它的目标是尽量减少共享对象的数量
强调将对象的属性划分为内部状态(属性)与外部状态(属性)。内部状态用于对象的共享,通常不变;而外部状态则剥离开来,由具体的场景决定
应用场景:一个班级男女测量身高,性别为内部状态,其他属性为外部状态。
10.职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链 传递该请求,直到有一个对象处理它为止
应用场景:以展示不同类型的变量为例,设置一条职责链,可以免去多重if条件分支
11.中介者模式:所有的相关 对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可
12.装饰者模式:
13.状态模式:
14.适配器模式:
15.外观模式:
https://www.cnblogs.com/imwtr/p/9451129.html
(11)事件流的三个阶段:
事件的处理过程主要有三个阶段:捕获阶段,目标阶段,冒泡阶段;
1.捕获阶段:当我们在 DOM 树的某个节点发生了一些操作(例如单击、鼠标移动上去),就会有一个事件发射过去。这个事件从 Window 发出,不断经过下级节点直到触发的目标节点。在到达目标节点之前的过程,就是捕获阶段(Capture Phase)。事件由页面元素接收,逐级向下,到具体的元素。
2.目标阶段:当事件不断的传递直到目标节点的时候,最终在目标节点上触发这个事件,就是目标阶段。具体的元素本身
3.冒泡阶段:事件冒泡即事件开始时,由最具体的元素接收(也就是事件发生所在的节点),然后逐级传播到较为不具体的节点。跟捕获相反,具体元素本身,逐级向上,到页面元素(我们平时用的事件绑定就是利用的事件冒泡的原理)。
事件捕获:当使用事件捕获时,父级元素先触发,子元素后触发。
事件冒泡:当使用事件冒泡时,子级元素先触发,父元素后触发。
W3C : 任何事件发生时,先从顶层开始进行事件捕获,直到事件触发到达事件源,再从事件源向上进行事件捕获(事件冒泡)。
事件传播的阻止方法:
在W3C中,使用stopPropagation()方法。
在IE下使用cancelBubble = true方法。
阻止默认行为:
在W3c中,使用preventDefault()方法。
(12)判断是否是数组的几种实现方式:
1.Array.isArray(): 返回true
2.instanceof运算符: 这个运算符可以判断一个对象是否是在其原型链上原型构造函数中的属性
let arr = []; console.log(arr instanceof Array); //true
3.constructor:这个属性是返回对象相对应的构造函数
let arr = []; console.log(arr.constructor == Array); //true
4.写一个函数方法
let arr = []; var isType = function (obj) { return Object.prototype.toString.call(obj).slice(8,-1); } console.log(isType(arr) == 'Array'); //true
(13)MVC和MVVC模式的区别:
MVC:M还是表示Modal层,负责与后台交互数据,V表示View,负责页面上DOM的渲染,C表示绑定在DOM元素上的事件,当Controllor中的事件被调用,会去调用Modal中的数据,然后交给View重新渲染数据
MVVC:react、vue这俩个框架的核心理念都是数据驱动页面渲染,同时他们都是MVVM模式的框架,MVVM模式中的M还是固定表示Modal,V还是表死View,这俩个基本都是不会发生变化,一个页面必然需要数据和渲染俩个部分,那么变化的是如何将Modal渲染到View的过程变了,在MVVM模式中,将View和Modal绑定在一起,只要Modal发生了变化,View就会自动更新,不需要我们认为的再去写如何操作DOM更新的过程了
(14)数据类型是放入堆?还是栈?
js中数据对于存储可以分为两种数据类型:基本类型和引用类型
基本类型:Number,String,Boolean,Null,Undefined
这些类型的值存放在栈区,函数调用时传递的是变量的值(值)。
引用类型:Object,Array,Function
这些类型的对象存放在堆区,对象的地址存放在栈区,函数调用时传递的是对象的地址(址)。
(15)函数的科里化?
科里化:是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
const curry = ( fn, arr = []) => {
return (...args) => {
return ( a => { //a是一个数组
if(a.length === fn.length) {
return fn(...a)
}else{
return curry(fn, a)
}
})([...arr, ...args]) //这里把arr和args摊开成一个数组赋值给a
}
}
或者:const curry = ( fn, arr = []) => (...args) => ( a => a.length === fn.length? fn(...a) : curry(fn, a))([...arr, ...args])
let curryPlus = curry((a,b,c,d)=>a+b+c+d)
curryPlus(1,2,3)(4) //返回10
curryPlus(1,2)(4)(3) //返回10
curryPlus(1,2)(3,4) //返回10
(16)用原生的方式实现数组去重?
function merge(arr) {
if (!Array.isArray(arr) || arr.length == 0)
return [];
var ret = [] ;
for (var i = 0; i < arr.length; i++) {
// 或者 ret.indexOf(arr[i] == -1)
if (arr.indexOf(arr[i]) == i)
{ret.push(arr[i]);}
}
return ret;
}
(17)防抖和节流?
防抖:触发高频事件后N秒内只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。
举例:在百度搜索时,每次输入之后都会有联想词弹出,这个控制联想词的方法就是不可能是输入框内容一改变就触发的,他一定是当你结束输入一段时间之后才触发的。
function debounce(fn,delay){
//记录上一次的延时器
var timer = null;
return function(){
//清除上一次延时器
clearTimeout(timer);
//重新设置新的延时器
timer = setTimeout(function(){
fn.apply(this);
},delay);
}
}
节流:高频事件触发,但在N秒内只会执行一次,所以节流会稀释函数执行的频率
举例:预定一个函数只有在大于等于执行周期时才执行,周期内调用不执行,就像淘宝中抢购某一件热卖商品时,你不断的点击刷新或者购买,可是总有一段时间点击没有效果,这里就是节流。
代码举例:
function scall(fn,delay){
// 记录上一次函数触发的时间
var lastTime = 0;
return function (){
// var nowTime = Date.now();
if(nowTime - lastTime>delay){
fn();
lastTime = nowTime;
}
}
}
document.onscroll = scall(function(){ console.log('scroll事件被触发了'+Date.now());},200)
(18)js异步编程的方法?
1.事件的回调函数
2.定时器
3.promise
4.async ...await
5.generator 函数
2.关于es6
https://blog.csdn.net/joe0217/article/details/80936260#Map%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84
(1)箭头函数
1.通过=>函数可以更简洁快速的定义一个函数
2.如果箭头函数不需要传参或需要多个传参,就需要使用()括起来。
3.如果箭头函数的代码块部分多于一条语句,就要使用大括号将他们括起来,并使用return语句返回。
4.如果箭头函数只有一行语句,且不需要返回值,不用写大括号了
(2)Promise的用法以及实现原理
(3)说出es6新特性
(4)set数据结构
Set
本身是一个构造函数,用来生成 Set 数据结构,可用于数组去重
实例:
.add() 添加成员,返回Set结构本身
.size 返回Set实例的成员总数
.delete() 删除某值,返回Set结构本身
.has() 返回一个布尔值,表示该值是否为Set的成员
.clear() 清除所有成员,没有返回值
.keys() 返回键名的遍历器
.values() 返回键值的遍历器
.entries() 返回键值对的遍历器
.forEach() 使用回调函数遍历每个成员,没有返回值
(5)map数据结构
在JavaScript中对象(Object)本质是键值对的集合,但只能用字符串当作键。而Map 数据结构的出现就是为了解决这个限制,它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键
.set(key, value) 设置key对应的value,返回 Map 结构。如果key已经有值,则键值会被更新。
.get(key) 读取key对应的键值,如果找不到key,返回undefined。
.has(key) 方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
.delete() 方法删除某个键,返回true,如果删除失败,返回false。
.clear() 方法清除所有成员,没有返回值。
.keys(): 返回键名的遍历器。
.values():返回键值的遍历器。
.entries():返回所有成员的遍历器。
.forEach():遍历 Map 的所有成员。
(6)let和const命令
es5只有两种声明变量的方法:var命令和function命令。es6除了let和const命令,另外两种声明变量的方法: import命令和class命令。所以 es6一共有6种方法。
let和const相同的地方:1.只在声明所在的块级作用域内有效 。
2.不可重复声明。
3.同样存在暂时性死区,只能在声明的位置后面使用。
(7)变量的解构赋值
1.数组解构赋值:
2.对象解构赋值:
3.字符串解构赋值:
4.默认值,不管是对象还是数组,解构赋值都允许指定默认值:
用途:1.交换变量的值
例子1:
-
let a = 1;
-
let b = 2;
-
-
[a, b] = [a, b];
2.从函数返回多个值
// 数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
3.函数参数的定义
-
let arr = [1, 2, 3];
-
function f([x, y, z]) { ... }
-
f(arr);
-
-
let obj = {z: 3, y: 2, x: 1};
-
function f({x, y, z}) { ... }
-
f(obj);
4.提取json数据
-
let jsonData = {
-
id: 1001,
-
status: "yes",
-
data: ['Joe', 23]
-
};
-
let { id, status, data} = jsonData;5.遍历Map结构
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}6.输入模块的指定方法
const { fun1, fun2 } = require("funs");
3.关于css2和css3
(1)BFC:块级格式化上下文"
布局规则:
1.内部的BOX会在垂直方向,一个接一个的放置。
2.box垂直方向的距离由margin决定,属于同一个BFC的两个相邻box的margin会发生重叠。
3.每个盒子(块盒与行盒)的margin box的左边,与包含块border box 的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。
4.BFC的区域不会与float box重叠。
5.BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。
6.计算bfc的高度时,浮动元素也参与计算。
创建BFC:1、float 的值不是none
2、position的值不是static 或者relative
3、display的值是inline-block、table-cell、flex、table-caption 或者inline-flex
4、overflow的值不是visible
1.BFC的作用:1.利用BFC避免margin重叠
2.自适应两栏布局 left<div> : float :left, right<div>: overflow:hidden
3.清除浮动:当我们不给父节点设置高度,子节点设置浮动的时候,会发生高度塌 陷,这个时候我们就要清楚浮动
总结:BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此
因为BFC内部的元素和外部的元素绝对不会互相影响,因此, 当BFC外部存在浮动时,它不应该影响BFC内部Box的布局,BFC会通过变窄,而不与浮动有重叠。同样的,当BFC内部有浮动时,为了不影响外部元素的布局,BFC计算高度时会包括浮动的高度。避免margin重叠也是这样的一个道理
(2)清除浮动3种方式:
(3)实现垂直水平居中(新老方式)
1、老方式
#wrap{
width:500px;
height:500px;
background:grey;
position:relative;
}
#wrap .box{
width:200px;
height:200px;
background:pick;
//方式1:
position:absolute;
top:50%;
left:50%;
margin-left:-100px;
margin-top:-100px;
//方式2:
position:absolute;
top:0;
left:0;
right:0;
bottom:0;
margin:auto;
//方式3
transform:translate(-50%,-50%);
}
2、新方式
#wrap{
display:flex;
justify-content:center;
align-items:center;
}
4.关于react
redux中间件原里:改装dispath
(1)hoc:
定义:一种React的进阶使用方法,主要还是为了便于组件的复用。HOC就是一个方法,获取一个组件,返回一个更高级的组件
什么时候使用hoc?
组件添加或者修改一些特定的props,一些权限的管理,或者一些其他的优化之类的。而如果这个功能是针对多个组件的,同时每一个组件都写一套相同的代码,明显显得不是很明智,所以就可以考虑使用HOC。
例子:react-redux的connect方法就是一个HOC,他获取wrappedComponent,在connect中给wrappedComponent添加需要的props。
hoc可以做什么?
1.代码复用,代码模块化
2.增删改props
3.渲染劫持 :组件要在data没有加载完的时候,显示loading。。。
Hoc有什么用例?
1.react redux的connect方法,通过这个hoc方法,监听redux store ,然后把下级组件需要的state(通过mapStateToPtops获取)和action creator(通过mapDispatchToProps 获取)绑定到wrappedComponent的props上
2.logger和debugger 可以用来监控父级组件传入的props的改变:
3.页面权限管理:可以通过hoc对组件进行包裹,当跳转到当前页面的时候,检查用户是否含有对应的权限,如果有的话,渲染页面,如果没有的话,跳转到其他页面(比如无权限页面,或者登陆页面)。
使用Hoc需要注意什么?
1.尽量不要随意修改下级组件需要的props
修改父级传递给下级的props是有一定风险的,可能会造成下级组件发生错误。
2.ref无法获取你想要的ref
因为这里的component经过hoc的封装,已经是hoc里面的那个component了,所以你无法获取你想要的那个ref(wrappedComponent的ref)。
解决方法:
a> 像react redux的connect方法一样,在里面添加一个参数,比如withRef,组件中检查到这个flag了,就给下级组件添加一个ref ,并通过getWrappedinstance方法获取。
b> 父级通过传递一个方法,来获取ref
先看父级组件:
component上面绑定的Static方法会丢失
比如:原来在Component上面绑定了一些static方法,MyComponent.staticMethod = o=>o.但是由于经过Hoc的包裹,父级组件拿到的已经不是原来的组件了,所以当然无法获取staicMethod方法了
https://segmentfault.com/a/1190000008112017?_ea=1553893
(2)hook:
(3)虚拟dom的缺点:
(4)dva和redux相比较优缺点:
dva是一个基于redux和redux-saga的数据流方案,dva还额外内置了react-router和fetch,所以也可以理解为一个轻量级的应用框架。
1.dva封装了redux,减少很多重复代码比如action reducers常量
2.dva操作都是在models层,通过namespace作为key,标识不同的模块state。state存储数据。
3.reducers跟传统的react-redux写法一致,所有的操作放在reducers对象内。
4.异步操作写在effects对象内:
其实*fetchList 就是function *fetchList ,是个Generator状态机
call,put其实是saga的写法,dva集成了saga。
5.ui组件调用使用@connect包裹就可以从this.props上调用方法和数据。
6.dva-loading可以自动处理loading状态,不用一遍遍的写showLoading和hideLoading。
优点
- 框架: dva是个框架,集成了redux、redux-saga、react-router-redux、react-router
- 快速初始化: 可以快速实现项目的初始化,不需要繁琐地配置
- 简化开发:将initState、saga、reducer集成到一个model里面统一管理,避免文件散落在各个文件里面,便于快速查找与开发
- 简洁的API:整个项目中只有dva、app.model、app.router、app.use、app.start几个API
- 无缝对接:跟react的生态没有冲突,例如可以直接使用redux devtool工具
- 动态机制:app.start以后,仍然可以注册model,灵活性较高
- namespace不统一: dva中的action.type格式是namespace/XXX,且在model中不需要添加前缀namespace,但是在组件中dispatch,却需要添加prefix
- action问题:action会散落在两个地方,一个是saga里面,另外一个是component dispatch的时候,当然这个问题在使用redux-saga的时候就会存在,只是dva仍然没有很好地统一起来。
作者:zhenhua-lee
链接:https://www.zhihu.com/question/51831855/answer/225446217
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
作者:zhenhua-lee
链接:https://www.zhihu.com/question/51831855/answer/225446217
(5)从3方面说说diff算法:
1.tree Diff
(1)react通过updateDepth对于Virtual DOM树进行层级控制。
(2)对树分层比较,两棵树只对同一层次节点进行比较,如果该节点不存在时,则该节点及其子节点会被完全删除,不会再进一步比较。
(3)只需遍历一次,就 能完成整棵树DOM树的比较。
(4) diff只简单考虑同层级的节点位置互换,如果时垮层级的话,只有创建节点和删除节点的操作。
2.Component Diff
react对不同组件间的比较,有三种策略”
(1)同一个类型的两个组件,就按原策略(层级比较)继续比较Virtual DOM树即可。
(2)同一类型的两个组件,组件A变化为组件B时,可能Virtual Dom没有任何变化,如果知道这点(变换的过程中,Virtual Dom没有改变),可节省大量计算时间,所以用户可以通过shouldComponentUpdate()来判断是否需要diff计算
(3)不同类型的组件,将一个(将被改变的)组件判断为dirty component (脏组件),从而替换整个组件的所有节点。
如果组件D和组件G的结构相似,但是react判断时不同类型的组件,则不会比较其结构,而是删除组件D及其子节点,创建组件G及其子节点。
3.Element Diff
当节点处于同一层级时,diff提供三种节点操作:删除、插入、移动。
https://www.jianshu.com/p/3ba0822018cf
(6)react框架的原理:
react的设计原理就是其引入的虚拟dom机制:
1、react用javascript在浏览器端实现了一套虚拟dom api。
2、基于react开发的时候所有的dom构造都是基于虚拟dom进行的
3、每当有state更改的时候,react就重新render一整套虚拟dom树,
react机制会将当前的整个dom树和上一次的dom树进行对比
取到diff,进行真实的dom更改。
4、其实state也有一部分实现的是数据、html片段绑定,
直接更新的数据是包含的
深层次一点就是react的diff算法是怎么理解的。
错误的见解:我曾经以为diff算法,就是深层次的diff,算法运算时只比较
不同的。但其实当时浅显的想法,确实是diff运算的结果,但不是
diff运算的算法。
1、tree diff
React对Virtual DOM树进行层级控制,只会对相同层级的DOM节点进行比
较,即同一个父元素下的所有子节点,当发现节点已经不存在了,则会删除掉
该节点下所有的子节点,不会再进行比较。这样只需要对DOM树进行一次遍
历,就可以完成整个树的比较。
即使说a节点以及他的子节点被移动,但是react只关注同级比较,在第二层
把a及其子节点删了,在第三层再重新创建,所以diff运算量大,影响性能
不建议setState直接更改树的结构。最好是state颗粒度小,只改变树中
的某一个小的节点,那么diff的时候只会深度比较这一个小节点。
2、componnet diff
假如说因为某个条件切换,所以要显示不同的组件。
1、比较两个组件的类型(D和G)
2、如果(D和G)不是同一类型,进行diff算法,分析会影响性能
直接删掉上一个虚拟dom组件。 重新创建新的组件。
如果是同一类型的组件,会按照层级策略深层对比每个节点。
3、element diff
精确的对属于同一层级的节点diff时,提供了3种节点操作,分别为INSERT_MARKUP(插入),MOVE_EXISTING(移动),REMOVE_NODE(删除)。
如果同一层级,没有这个新节点会新增插入
如果同一层级,如果有新节点,但是属性不一样,会复用节点,赋值属性
如果同一层次,旧dom有,新dom没有,会删除这个节点。
原文链接:https://blog.csdn.net/running_shuai/java/article/details/80284698
总结:setState()触发一次组件重绘,其实就是虚拟dom重新生成,除非在
shouldComponentUpdate()中实现了一些条件渲染逻辑。来允许和阻止是否需要
调用指定组件的 render 方法。其实这个深入逻辑就是他触发了render,只是是
否触发了内部的diff算法,return false 的时候,不diff,render出来的新
旧dom一样。diff算法的复杂度为0。
(7)redux:
1>redux 是一个独立专门用于做状态管理的js库,不是react插件库
2>作用:集中式管理react应用中多个组件共享的状态和从后台获取的数据。
react-redux简化redux的编码
redux-thunk实现redux的异步编程
使用redux devTools实现chrome 中redux的调试
(8)为什么虚拟dom会提高性能?
(9)this.setStatus 是异步还是同步?
setState
只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout
中都是同步的。setState
的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。setState
的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState
,setState
的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState
多个不同的值,在更新时会对其进行合并批量更新。
(10)生命周期?
5.前端优化
6.前端工程化
1.服务端模块化
一、commonJS 对模块的定义非常简单,主要分为模块引用,模块定义和模块标识3个部分。
1)模块的引用
var add = require('./add.js);
2) 模块定义
module.exports.add = function(){
...
}
3)可以在一个文件中引入模块并导出另一个模块
var add = require('./add .js');
module.exports.increment = function(){
return add (val,1);
}
其实 ,一个文件代表一个模块,一个模块除了自己的函数作用域之外,最外层还有一个模块作用域,module就是代表这个模块,
exports是module的属性,require也是这个模块的上下文中,用来引入外部模块。
3)模块标识
模块标识就是require()函数的参数,规范是这样的
1.必须是字符串
2.可以是以./ ../开头的相对路径
3.可以是绝对路径
4.可以省略后缀名
二、node.js 模块化实现
1.node中一个文件 就是一个模块-----module
一个模块就是一个Module的实例
2.node模块分类:核心模块和文件模块
核心模块:就是node内置的模块比如http,path等。在node的源码的编译时,核心模块就一起被编译进了二进制执行文件,部分核心模块(内建模块)被直接加载进内存中。
文件模块:就是外部引入的模块如node—modules里面通过npm按装模块,或者我们项目工程里自己写一个js文件或者json文件。
node模块的引入过程,一般要经过三个步骤
路径分析
文件定位
编译执行
核心模块:会省略 文件定位和编译执行,并且在路径分析中会优先判断,加载速度比一般模块更快。标识:require(‘http’);
文件模块:三个步骤都要经历。require('./c.js');以. / 或者.. /绝对路径开头。
三、AMD:异步模块加载规范与CommonJS的主要区别就是异步模块加载,就是模块加载过程中即使require的模块还没有获取到,也不会影响后面代码的执行。
- CommonJS一般用于服务端,AMD一般用于浏览器客户端
- CommonJS和AMD都是运行时加载
四、CMD:通用模块规范,与AMD规范的主要区别在于定义模块和依赖引入的部分,AMD需要在声明模块的时候指定所有的依赖,通过形参传递依赖到模块内容中。在依赖示例部分,CMD支持动态引入,require、exports、module通过形参传递给模块,在需要依赖模块时,随时调用require()引入即可。
五、UMD 通用模块规范
六、es6模块
1)导出一个变量
export var name=‘pengpeng’;
2)导出一个函数
export function foo(x,y){}
3)常用导出方式(推荐)
const name = ‘dingman’;
const age = ‘18’;
export{name,age};
4)As用法
const s =1;
export {
s as t,
s as m,
}
可以利用as将模块输出多次。
Es6模块使用-----import
1) 一般用法
import {name,age} from ‘./person,js';
2) As 用法
import {name as personName} from './person.js';
import命令具有提升效果,会提升到整个模块的头部,首先执行,如下也不会报错:
getName();
import {getName} from ’person_module';
3)整体模块加载*
export name = 'xixi';
export age = 23;
// 逐一加载
import {age,name} from './person.js';
// 整体加载
import * as person from './person.js';
console.log(person.name);
console.log(person.age);
es6模块使用 ------export default
使用export default 命令,需要注意的是使用export default命令时,import 是不需要加{}的,而不使用export default时,import是必须加{}。
示例如下:
//person.js
export function getName() {
...
}
//my_module
import {getName} from './person.js';
-----------------对比---------------------
//person.js
export default function getName(){
...
}
//my_module
import getName from './person.js';
export default 其实是导出一个叫做default的变量,所以其后面不能跟变量声明语句。
//错误
export default var a = 1;
值得注意的是 我们可以同时使用export和export default
//person.js export name = 'dingman'; export default function getName(){ ... } //my_module import getName, { name } from './person.js';
commonjs是运行时加载,es6是编译时加载,又有什么区别呢?
ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。所以说ES6是编译时加载,不同于CommonJS的运行时加载(实际加载的是一整个对象),ES6模块不是对象,而是通过export命令显式指定输出的代码,输入时也采用静态命令的形式
//ES6模块
import { basename, dirname, parse } from 'path';
//CommonJS模块
let { basename, dirname, parse } = require('path');
七:webpack模块化
https://zhuanlan.zhihu.com/p/41568986
https://zhuanlan.zhihu.com/p/42853909
7.websocket
(1)websocket和http区别?
http协议是用在应用层的协议,他是基于tcp协议的,http协议建立链接也必须要有三次握手才能发送信息。
http链接分为短链接,长链接,短链接是每次请求都要三次握手才能发送自己的信息。即每一个request对应一个response。长链接是在一定的期限内保持链接。保持TCP连接不断开。客户端与服务器通信,必须要有客户端发起然后服务器返回结果。客户端是主动的,服务器是被动的。
WebSocket
WebSocket他是为了解决客户端发起多个http请求到服务器资源浏览器必须要经过长时间的轮训问题而生的,他实现了多路复用,他是全双工通信。在webSocket协议下客服端和浏览器可以同时发送信息。
建立了WebSocket之后服务器不必在浏览器发送request请求之后才能发送信息到浏览器。这时的服务器已有主动权想什么时候发就可以发送信息到服务器。而且信息当中不必在带有head的部分信息了与http的长链接通信来说,这种方式,不仅能降低服务器的压力。而且信息当中也减少了部分多余的信息。
(2)webSocket如何实现断线重连?
var ws_heart_i = null;
/**
* websocket 每1分钟发一次心跳
*/
function ws_heart() {
if (ws_heart_i) clearInterval(ws_heart_i);
ws_heart_i = setInterval(function () {
console.log('ws_heart');
var func = function () {
var data = {type: 'ping'};
ws.send(JSON.stringify(data));
};
ws_execute(func);
}, 60000);
}
原文链接:https://blog.csdn.net/sybil06/java/article/details/88821125
8.浏览器存储
9.网络问题
(1)http 请求的全部过程?
1.DNS解析:将域名地址解析为ip地址
-浏览器DNS缓存
-系统DNS缓存
-路由DNS缓存
-网络运营商DNS缓存
-递归搜索:blog.baidu.com
-.com域名下查找DNS解析
-.baidu域名下查找DNS解析
-blog域名下查找DNS解析
-出错了
2.TCP连接,TCP三次握手
-第一次握手,由浏览器发起,告诉服务器我要发送请求了
-第二次握手,由服务器发起,告诉浏览器我准备接受了,你赶紧发送吧
-第三次握手,由浏览器发送,告诉服务器, 我马上就发了,准备接受吧
3.发送请求
-请求报文,http协议的通信内容
4.接受响应
-响应报文
5.渲染页面
-遇见HTML标记,浏览器调用HTML解析器解析成Token并构建成dom树
-遇见style/link标记,浏览器调用css解析器,处理css标记并构建cssom树
-遇见script标记,调用JavaScript解析器,处理script代码(绑定事件,修改dom树/cssom树)
-将dom树和cssom树合并成一个渲染树
-根据渲染树来计算布局,计算每个节点的几何信息(布局)
-将各个节点颜色绘制到屏幕上(渲染)
注意:
这个5个步骤不一定按照顺序执行,如果dom树或cssom树被修改了,可能会执行多次布局和渲染,
往往实际页面中,这些步骤会执行多次的
6.断开连接,TCP四次挥手
第一次挥手,由浏览器发起的,发送给服务器,我东西发送完了(请求报文),你准备关闭吧。
第二次挥手,由服务器发起的,告诉浏览器,我东西接收完了(请求报文),我准备关闭了,你也准备吧
第三次挥手,由服务器发起,告诉浏览器,我东西发送完了(响应报文),你准备关闭吧。
第四次挥手,由浏览器发起,告诉服务器,我东西接受完了,我准备关闭了(响应报文),你也准备吧。
(2)如何处理跨域问题?
(3)http缓存机制?
(4)http状态码
状态码
状态代码为3位数字。
1xx:指示信息--表示请求已接收,继续处理。
2xx:成功--表示请求已被成功接收、理解、接受。
3xx:重定向--要完成请求必须进行更进一步的操作。
4xx:客户端错误--请求有语法错误或请求无法实现。
5xx:服务器端错误--服务器未能实现合法的请求。
10 .移动端如何实现适配
11.react选型?
12.react和vue相比较
相同点:
1.都有组件化开发和virtual DOM
2.都支持props进行父子组件间数据通信
3.都支持数据驱动视图,不直接操作真实DOM,更新状态数据界面就自动更新
4.都支持服务端渲染
5.都有支持native的方案,react的react native ,vue的weex
不同点:
1.数据绑定:vue实现了数据的双向绑定,react数据流动时单向的。
2.组件写法不一样,react推荐的做法是JSX,也就是把html和css全部都写进javascript了,即“all in js” vue推荐的做法是webpack+vue-loader的单文件组件格式,即html,css,js 写在同一个文件。
3.state对象在react应用中不可变的,需要使用setState方法更新状态;在vue中,state对象不是必须的,数据由data属性在vue对象中管理
4.virtual Dom不一样,vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树,而对于react而言,每当应用的状态被改变时,全部组件都会重新渲染,所以react中会需要shouldComponentUpdate 这个生命周期函数方法来进行控制
5.react严格上只针对MVC的view层,vue则是mvvm模式。
12.飞冰和antdesign?