html/css相关
1.h5新特性
- 语义化标签
- 增强型表单
- 新事件(onrize,onScroll,ondrag,onerror,onplay,onpause)
2.css3
- 伪类添加效果
- 伪元素,创建了元素
- background border
- 文本效果
- transform transition animation
3.移动端适配
将视图的宽度设为页面的宽度
rem
将视图按照一定的比例划分。动态设置根元素font-size
// rem-demo/util/rem.js
// 设置基准大小
const baseSize = 32
function setRem () {
// 当前页面宽度相对于 750 宽的缩放比例
const scale = document.documentElement.clientWidth / 750
document.documentElement.style.fontSize = (baseSize * Math.min(scale, 2)) + 'px'
}
// 初始化
setRem()
window.onresize = function () {
setRem()
}
vw/vh
将页面分成100份,1vw = device-width/100,缺点无法修改,无法通过js干预。
4. 重绘、重排
重绘
元素外观改变重新绘制
- 改变元素的颜色、背景、阴影等
重排(回流)
浏览器重新计算元素的几何属性
- 添加或删除可见的dom元素
- 元素尺寸改变
优化建议:样式集中操作、对脱离文档流的元素进行修改,使用transform属性
现代浏览器会对频繁的回流或重绘操作进行优化:浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。
5. BFC
里面的元素不影响外面的元素 创建:display:inline-block position:absolut 应用:margin重叠,清除内部浮动
js相关
1. 事件冒泡、事件委托
事件冒泡
在一个对象上触发事件,如果对象绑定了事件就会触发,如果没有,就会像父级对象传播,最终由父级对象触发。
事件委托
因为事件在冒泡过程中会上传到父节点,父节点可以通过事件对象获取到目标节点,所以可以把子节点的监听函数定义在父节点上,由父节点统一处理子节点的事件,这种方式叫做事件代理。
2.路由模式
2.1 后端路由
接收到客户端的http请求,读取文件,返回数据。
优点:安全性,SEO好
缺点:加大了服务器的压力
2.2 前端路由
优点:访问新页面只是换了下路径,路径匹配到对应的组件,然后渲染,用户体验好 缺点:前进后退会重新发起请求,没有利用好缓存,不利于SEO
2.2.1 hash
利用location.hash来实现,#后面的值,利用hashchange来监听hash的变化。
2.2.2 history API
利用history.pushState,history.replaceState,监听popState事件
3.escape,encodeURI,encodeURIComponent区别
escape编码字符串 encodeURI方法不会对下列字符编码 ASCII字母 数字 ~!@#$&*()=:/,;?+'
encodeURIComponent方法不会对下列字符编码 ASCII字母 数字 ~!*()'
所以encodeURIComponent比encodeURI编码的范围更大。
4.XHR和fentch的区别
对比内容 | xhr | fetch |
---|---|---|
设计模式 | 事件机制,业务代码耦合在事件中 | promise的异步模式,更解耦 |
请求中止 | 可实现 | 不可实现 |
异常状态 | 可差别处理 | 也认为正常,需要追加自定义的回调函数处理 |
超时是否支持 | 支持 | 不支持 |
携带cookie | 携带 | 不携带,需要主动添加 |
兼容性 | 无 | 只有部分浏览器支持 |
是否支持跨域 | 不支持,需要使用jsonp | 原生支持 |
稳定性 | 佳 | 试验阶段 |
5.Promise
用Promise.race解决接口超时间未回应。返回最快的结果。
-每一个异步任务都想得到结果就使用Promise.allSettled() -异步任务要求每个都成功才能往下执行就使用Promise.all()
6.event-loop
浏览器或node解决js单线程运行被阻塞的机制。
一些概念
- 堆(Heap):一种数据结构,利用完全二叉树维护的一组数据。
- 栈(Stack):一种数据结构,按照后进先出原则存储数据。
- 队列(Queue):按照先进先出原则存储数据。
宏任务和微任务
- MacroTask(宏任务):Script全部代码,setTimeout,setInterval,I/O,UI Rendering
- MicroTask(微任务):Promise,Object.observe,MutationObserve,Process.NextTick(Node)
浏览器中的EventLoop
j完毕,栈被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放到栈中执行(异步任务有了运行结果,就会在任务队列中放置一个事件)。每次栈被清空,都会去读取任务队列中有没有任务,有就读取执行,一直循环读取执行操作。当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件, 然后再去宏任务队列中取出一个事件。同一次事件循环中, 微任务永远在宏任务之前执行。
js中的异步任务
执行栈在执行完同步任务后,会检查执行栈是否为空,如果为空就会检查微任务队列是否为空,如果为空,就执行宏任务,否则就一次性执行完所有微任务。每次执行完耽搁宏任务,就会检查微任务队列是否为空,如果不为空的话,就按照先入先出的原则执行完全部微任务后设置微任务队列为null,在去执行宏任务,如此循环。
7.call apply
传参格式不同
let arr1 = [14,5,7,9]
Math.max.call(null,14,5,7,9) //14
Math.max.call(null,arr1) //NaN
Math.max.apply(null,arr1) //14
8.bind
返回改变上下文后的一个函数,可以绑定参数
9.this-指针,指向调用函数的对象
1. 默认绑定
默认规则通常是独立函数调用
function sayHi(){
console.log('Hello,', this.name);
}
var name = 'YvetteLau';
sayHi();
调用sayHi(),非严格模式下this指向全局对象,打印Hello,YvetteLau。严格模式下,this指向undefined,会报错。
2. 隐式绑定
调用是在某个对象上触发的,调用位置上存在上下文关系。
function sayHi(){
console.log('Hello,', this.name);
}
var person1 = {
name: 'YvetteLau',
sayHi: function(){
setTimeout(function(){
console.log('Hello,',this.name);
})
}
}
var person2 = {
name: 'Christina',
sayHi: sayHi
}
var name='Wiliam';
person1.sayHi();
setTimeout(person2.sayHi,100);
setTimeout(function(){
person2.sayHi();
},200);
- Hello,Wiliam:this是默认绑定,非严格模式下指向全局
- Hello,Wiliam:this是隐式绑定,person2.sayHi赋值给了一个变量,绑定丢失,最后执行这个变量,this就和person2没有关系了
- Hello,Christina:this是隐式绑定,指向person2
3. 显式绑定
通过call,apply,bind显式的指定this所指向的对象。
function sayHi(){
console.log('Hello,', this.name);
}
var person = {
name: 'YvetteLau',
sayHi: sayHi
}
var name = 'Wiliam';
var Hi = person.sayHi;
Hi.call(person); //Hi.apply(person)
输出Hello,YvetteLau,将Hi绑定到了person。 但是用了硬绑定依然会绑定丢失
function sayHi(){
console.log('Hello,', this.name);
}
var person = {
name: 'YvetteLau',
sayHi: sayHi
}
var name = 'Wiliam';
var Hi = function(fn) {
fn();
}
Hi.call(person, person.sayHi);
输出Hello,Wiliam。虽然Hi的this绑定到了person,但是person.sayHi赋值给fn,最后执行fn,隐式绑定丢失默认全局。
调用时硬绑定可以防止绑定缺失
function sayHi(){
console.log('Hello,', this.name);
}
var person = {
name: 'YvetteLau',
sayHi: sayHi
}
var name = 'Wiliam';
var Hi = function(fn) {
fn.call(this);
}
Hi.call(person, person.sayHi);
输出Hello,YvetteLau,person.sayHi赋值给fn,fn用call硬绑定person.sayHi,sayHi的this指向person。
4. new绑定
function sayHi(name){
this.name = name;
}
var Hi = new sayHi('Yevtte');
console.log('Hello,', Hi.name);
输出Hello,Yevtte,用new sayHi的this绑定到了Hi对象上。
5. 绑定的优先级
new绑定>显式绑定>隐式绑定>默认绑定
6. 绑定例外
将null、undefined作为this传入call bind apply 会被忽略,应用默认绑定。
var foo = {
name: 'Selina'
}
var name = 'Chirs';
function bar() {
console.log(this.name);
}
bar.call(null); //Chirs
7. 箭头函数
(1)函数体内的this对象,继承的是外层代码块的this。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
(5)箭头函数没有自己的this,所以不能用call()、apply()、bind()这些方法去改变this的指向.
var obj = {
hi: function () {
console.log(this);
return () => {
console.log(this);
}
},
sayHi: function () {
return function () {
console.log(this);
return () => {
console.log(this);
}
}
},
say: () => {
console.log(this);
}
}
let hi = obj.hi(); //显式绑定,obj
hi(); //箭头函数,继承上一层this,obj
let sayHi = obj.sayHi(); //把return的function赋值给sayHi
let fn1 = sayHi(); //执行赋值后的sayHi,绑定丢失,执行默认绑定,输出window
fn1(); //箭头函数,继承上一层this,输出window
obj.say(); //箭头函数,网上找this,找到了全局的this指向window
10.如何判断this指向
- 函数是否存在new 调用,如果有,this绑定的是新创建的对象。
- 函数是否通过call apply调用,或者bind硬绑定,如果是this指向的是绑定的对象。
- 函数是否在某个上下文对象中调用,如果是,this绑定的是这个上下文对象。
- 如果以上都不是,使用默认绑定,严格模式下绑定到undefined,否则绑定到全局对象。
- null undefined作为this传入call apply bind 会被忽略,应用默认绑定规则。
- 箭头函数继承外层代码块的this。
11. 变量
11.1块级作用域只要存在let这个区域就不受外部影响
var a = 123
if (true) {
a = 'abc' // ReferenceError
let a;
}
11.2不能在函数内部重复申明变量
function func(arg) {
let arg;
}
func()
// Uncaught SyntaxError: Identifier 'arg' has already been declared
11.3 const所保证的不是变量的值不得改动
而是变量指向的内存地址所保存的数据不得改动。对于简单数据类型,值就保存在变量指向的内存相当于常量。对于复杂的数据类型,变量指向的内存地址,保存的是指向实际数据的指针。const只能保证这个指针是不变的。
12.正则
12.1 模糊匹配
字符组
- 比如[123456abcdefGHIJKLM],可以写成[1-6a-fG-M]
- 排除字符组,[^abc],表示是一个除"a"、"b"、"c"之外的任意一个字符
- 常见简写 有了字符组的概念后,一些常见的符号我们也就理解了。因为它们都是系统自带的简写形式。
**
\d
**就是[0-9]
。表示是一位数字。记忆方式:其英文是digit(数字)。**
\D
**就是[^0-9]
。表示除数字外的任意字符。**
\w
**就是[0-9a-zA-Z_]
。表示数字、大小写字母和下划线。记忆方式:w是word的简写,也称单词字符。**
\W
**是[^0-9a-zA-Z_]
。非单词字符。**
\s
**是[ \t\v\n\r\f]
。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符。记忆方式:s是space character的首字母。**
\S
**是[^ \t\v\n\r\f]
。 非空白符。
.
就是[^\n\r\u2028\u2029]
。通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符除外。记忆方式:想想省略号...中的每个点,都可以理解成占位符,表示任何类似的东西。
量词
- 常见简写
{m,}
表示至少出现m次。
{m}
等价于{m,m}
,表示出现m次。
?
等价于{0,1}
,表示出现或者不出现。记忆方式:问号的意思表示,有吗?
+
等价于{1,}
,表示出现至少一次。记忆方式:加号是追加的意思,得先有一个,然后才考虑追加。
*
等价于{0,}
,表示出现任意次,有可能不出现。记忆方式:看看天上的星星,可能一颗没有,可能零散有几颗,可能数也数不过来。
-
贪婪模式和惰性模式 贪婪模式:尽可能多的匹配 惰性模式:加个问号{m,}?,尽可能少的匹配
-
多选分支
(p1|p2|p3)
,其中p1
、p2
和p3
是子模式,用|
(管道符)分隔,表示其中任何之一。是惰性模式。
12.2 位置匹配
12.2.1 ^和$
^
(脱字符)匹配开头,在多行匹配中匹配行开头。
$
(美元符号)匹配结尾,在多行匹配中匹配行结尾。
12.2.2 \b和\B
\b
是单词边界,具体就是\w
和\W
之间的位置,也包括\w
和^
之间的位置,也包括\w
和$
之间的位置。
\B
就是\b
的反面的意思,非单词边界。\w
与\w
、\W
与\W
、^
与\W
,\W
与$
之间的位置。
12.2.3 (?=p)和(?!p)
(?=p)
,其中p
是一个子模式,即p
前面的位置。
比如(?=l)
,表示'l'字符前面的位置
而(?!p)
就是(?=p)
的反面意思
(?<=p)
和(?<!p)
环视,即看看右边或看看左边
12.3括号的使用
12.3.1 分组和分支结构
/a+/
匹配连续出现的“a”,而要匹配连续出现的“ab”时,需要使用/(ab)+/
多选分支结构(p1|p2)
中,提供了子表达式的所有可能。
12.3.1 分组替换
其中replace
中的,第二个参数里用$1
、$2
、$3
指代相应的分组
var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$1");
console.log(result); // => "06/12/2017"
12.3.3 反向引用
var regex = /\d{4}(-|/|.)\d{2}\1\d{2}/;
var string1 = "2017-06-12";
var string2 = "2017/06/12";
var string3 = "2017.06.12";
var string4 = "2016-06/12";
console.log( regex.test(string1) ); // true
console.log( regex.test(string2) ); // true
console.log( regex.test(string3) ); // true
console.log( regex.test(string4) ); // false
注意里面的\1
,表示的引用之前的那个分组(-|/|.)
。不管它匹配到什么(比如-),\1
都匹配那个同样的具体某个字符。
我们知道了\1
的含义后,那么\2
和\3
的概念也就理解了,即分别指代第二个和第三个分组。
未完待续。。。
13.数组方法
ES5
13.1 push/pop
const colors = [];
colors.push('red', 'black')
console.log(colors);//['red', 'black']
const item = colors.pop();
console.log(item);//'black'
console.log(colors);//['red']
13.2 shift/unshift
const colors1 = ['red', 'black'];
const item1 = colors1.shift();
console.log(item1);//'red'
console.log(colors1);//['black']
colors1.unshift('yellow')
console.log(colors1);//['yellow','black']
13.2 reverse/sort
const value = [1, 5, 66, 3, 98]
value.reverse();
console.log(value);// [98, 3, 66, 5, 1]
value.sort()
console.log(value);//[1, 3, 5, 66, 98]
value.sort((v1, v2) => v2 - v1)
console.log(value);//[98, 66, 5, 3, 1]
13.3 concat/slice/splice
const colors = ['red', 'green', 'blue'];
const colors1 = colors.concat('yellow', ['black'])
console.log(colors1);//["red", "green", "blue", "yellow", "black"]
const colors2 = colors1.slice(1, 3);
console.log(colors1); //["red", "green", "blue", "yellow", "black"]
console.log(colors2);//["green", "blue"]
const colors3 = colors1.splice(1, 3);
console.log(colors1); //["red", "black"]
console.log(colors3);//["green", "blue", "yellow"]
13.4 indexOf & lastIndexOf
const colors = ['red', 'green', 'blue', 'green', "blue", "yellow"];
console.log(colors.indexOf('green'))//1
console.log(colors.indexOf('green', 2))//3
console.log(colors.lastIndexOf('green'))//3
console.log(colors.lastIndexOf('green', 2))//1
13.5 every & some , filter, forEach, map
const colors = ['red', 'green', 'blue', 'green', "blue", "yellow"];
const someResult = colors.some(item => item.length > 4)
console.log(someResult);//true
const everyResult = colors.every(item => item.length > 4)
console.log(everyResult);//false
const filterResult = colors.filter(item => item.length > 4)
console.log(colors)//['red', 'green', 'blue', 'green', "blue", "yellow"]
console.log(filterResult);//["green", "green", "yellow"]
const forEachResult = colors.forEach(item => console.log(item.len))
console.log(colors)//['red', 'green', 'blue', 'green', "blue", "yellow"]
console.log(forEachResult);//undefined
const mapResult = colors.map(item => ({ color: item }))
console.log(colors)//['red', 'green', 'blue', 'green', "blue", "yellow"]
console.log(mapResult);//[{color: "red"},{ color: "green" }.....]
13.6 reduce&reduceRight
const colors = ['red', 'green', 'blue', 'green', "blue", "yellow", "blue",];
const reduceResult = colors.reduce((prev, cur, idx) => {
prev[cur] ? prev[cur] = Array.isArray(prev[cur]) ? [...prev[cur], cur] : [prev[cur], cur] : prev[cur] = cur
return prev
}, {})
console.log(colors);// ["red", "green", "blue", "green", "blue", "yellow", "blue"]
console.log(Object.values(reduceResult)); //["red",["green", "green"],["blue", "blue", "blue"], "yellow"]
const reduceRightResult = colors.reduceRight((prev, cur, idx) => {
prev[cur] ? prev[cur] = Array.isArray(prev[cur]) ? [...prev[cur], cur] : [prev[cur], cur] : prev[cur] = cur
return prev
}, {})
const reduceReverse = colors.reverse().reduce((prev, cur, idx) => {
prev[cur] ? prev[cur] = Array.isArray(prev[cur]) ? [...prev[cur], cur] : [prev[cur], cur] : prev[cur] = cur
return prev
}, {})
console.log(colors);// ["red", "green", "blue", "green", "blue", "yellow", "blue"]
console.log(Object.values(reduceRightResult)); //[["blue", "blue", "blue"], "yellow",["green", "green"],"red"]
console.log(colors);// ["red", "green", "blue", "green", "blue", "yellow", "blue"]
console.log(Object.values(reduceReverse)); //[["blue", "blue", "blue"], "yellow",["green", "green"],"red"]
ES6
13.7 Array.from() & Array.of()
// Array.from()对类数组或可迭代对象创建一个新的浅拷贝
const obj = {
0: 'red',
1: 'black',
length: 3
}
const arr = Array.from(obj)
console.log(arr);//["red", "black", undefined]
console.log(Array.from('foo'));//["f", "o", "o"]
// Array.of()创建数组实例
console.log(Array.of('foo'));//["foo"]
13.8 fill
const color = ['red', 'black', 'yellow']
const fillResult = color.fill('white')
console.log(color);//["white", "white", "white"]
console.log(fillResult);//["white", "white", "white"]
13.8 find & findIndex
const color = ['red', 'black', 'yellow'];
const findResult = color.find(i => i.length > 3)
console.log(color);//["red", "black", "yellow"]
console.log(findResult); //black
const findIndexResult = color.findIndex(i => i.length > 3)
console.log(color);//["red", "black", "yellow"]
console.log(findIndexResult); //1
13.9 entries(), keys() & values()
const colorObj = {
name: 'red',
rgba: '(255,0,0)',
af: 'f00'
}
console.log(Object.entries(colorObj));//[["name", "red"],"rgba", "(255,0,0)"],["af", "f00"]]
console.log(Object.keys(colorObj));//["name", "rgba", "af"]
console.log(Object.values(colorObj));//["red", "(255,0,0)", "f00"]
ES7
13.10 includes
const color = ['red', 'black', 'yellow'];
console.log(color.includes('red'));//true
console.log(color.includes('red', 1));//false
ES10
13.11 flat
const value = [1, 2, [3, 4, [5, 6]]];
const flatResult = value.flat();
console.log(value); //[1, 2, [3, 4, [5, 6]]]
console.log(flatResult);//[1, 2, 3, 4, [5, 6]]
const flatResultDeep = value.flat(Infinity);
console.log(flatResultDeep);//[1, 2, 3, 4, 5, 6]
const flatMapResult = value.flatMap(i => i * 2);
console.log(value); //[1, 2, [3, 4, [5, 6]]]
console.log(flatMapResult);//[2, 4, NaN]
浏览器相关
1. 网络安全
1.1 sql注入
后端依赖前端返回的数据进行sql拼接查询
解决办法:后端对前端传递的数据进行非法校验,业务校验
1.2XSS跨站脚本攻击
通过某种方式在前端代码中注入js,最常见就是表单提交。
解决办法:标签替换/cookie http-only/Content-Security-Policy域名白名单
1.3CSRF跨站请求伪造
利用操作者权限完成某个操作,原理就是cookie的同源策略
解决办法:增加额外校验/get改post/判断urlReferer/判断IP/增加时效性
1.4网页嵌套攻击
通过iframe嵌套网页,嵌套透明化,页面点击触发自己的事件。
解决办法:header设置不允许嵌套X-FARME-OPTIONS/判断当前页面是不是顶层窗口
2. 从url输入之后发生了什么
- 查找缓存
- DNS解析
- TCP连接
- 发送请求
- 接受响应
- 关闭tcp连接
- 渲染页面
- 构建dom树
- 构建css树
- 构建render tree
- 布局
- 绘制
3. 浏览器缓存
强缓存
直接从浏览器缓存获取,Expires
(一个绝对的时间)和 Cache-Control
(可设置缓存最大时间,是否被服务器缓存等)
协商缓存
浏览器带着缓存标志像服务器请求,服务端决定是否使用缓存。Last-Modified / If-Modified-Since
(最后修改的时间/是否被缓存)和 Etag / If-None-Match
(文件是否被修改)
React
1.React事件机制
JSX上的事件并没有绑定在真实dom上,而是在document监听了所有事件,当合成事件冒泡到document,React将事件内容封装交给真正的处理函数。
2.合成事件目的
- 抹平了浏览器之前的兼容性
- 减少内存消耗,可统一订阅移除事件。原生浏览器事件每一个都会有事件对象,而合成事件有一个事件池来统一创建喝销毁,当事件被需要可以从池子里复用对象。
4.React事件和HTML事件区别
- 事件名称命名方式
- 事件函数处理语法
- 阻止默认行为
5.React事件代理
利用虚拟dom实现了一个合成事件层 事件委派:把事件绑定到做外层,用统一的事件监听器,监听器内部映射了所有组件内部事件的监听和处理函数。 自动绑定:React组件中每个方法的上下文都会指向改组件的实例,即自动绑定this为当前组件。
6.React高阶组件,Render props, hooks
React高阶组件
是一种设计模式,高阶组件是一个函数,参数是组件返回一个新的组件。
Render props
告诉组件需要渲染什么内容的函数。
hooks
不编写class的情况下使用React特性
7.React Fiber(基于浏览器的单线程调度算法)
React V15 渲染对比虚拟dom后会同步更新,React V16 通过Fiber让这个过程变得异步可中断。
8.React.Component React.PureComponent
React.PureComponent是一个纯组件,减少render函数执行次数,提高组件性能。当state或者props更新时可以通过shouldComponentUpdate return false来阻止更新。React.PureComponent会自动执行shouldComponentDidUpdate,并对数据进行浅比较,如果是引用数据类型的数据只会比较是不是同一个地址。
9.React.createClass和extends React.Component
区别在于方法定义和静态属性的声明。React.createClass本质是一个函数。
10.React重新渲染
1.setState
2.父组件重新渲染
11.重新渲染render做什么
1.对新旧虚拟DOM进行对比。 2.对新旧两棵树进行深度优先遍历,把新节点和旧节点进行对比,有差异就放到一个对象里。 3.遍历差异对象,根据差异的类型,用相应的规则更新DOM树。
12.React Fragment
不添加节点对子列表进行分组。
13.React获取dom元素
- 元素内字符串格式定义ref
- 元素内函数形式定义ref
- React16 create Ref render阶段不可以获取refs,因为dom还没生成
14.React后生命周期
创建期
- constructor(props)
- componentWillMount()
- render()
- componentDidMount() 组件dom节点插入到dom树中加载完后立即执行
props发生变化时
- componentWillReceiveProps(nextProps,nextContext)
- shouldComponentUpdate(nextProps,nextState,nextContext) 渲染新的props,state前被调用,如果返回false,getSnapShotBeforeUpdate(),render,componentDidMount不会被调用。主要用于性能优化。
- componentWillUpdate(nextProps, nextState, nextContext)
- render() 5.componentDidUpdate(prevProps,prevStates,snapshot)
state发生变化时
- shouldComponentUpdate(nextProps,nextState,nextContext)
- componentWillUpdate(nextProps, nextState, nextContext)
- render() 4.componentDidUpdate(prevProps,prevStates,snapshot)
卸载时
- componentWillUnmount 组件被卸载销毁前调用,如取消定时器,取消网络请求,componentDidMount中创建的监听器。
16.3版本新增
- static getDerivedStateFromProps(props,status) 当组件需要props的改变来更新state,返回值为state对象,每次触发render前都会触发此方法。
- getSnapShotBeforeUpdate(prevProps,prevStat) render输出被渲染到dom之前调用,使组件能够在被更改之前获取到当前值。这个生命周期返回的任何值都会被作为第三个参数传递给componentDidUpdate()。
15.hooks为什么不能写在条件判断里
react靠调用的顺序知道那个state调用哪个useState,多次渲染只要保证hooks调用顺序一致,React就能将state与对应的hook关联。
16. 类组件与函数组件有什么异同?
相同点: 组件是 React 可复用的最小代码片段,它们会返回要在页面中渲染的 React 元素。也正因为组件是 React 的最小编码单位,所以无论是函数组件还是类组件,在使用方式和最终呈现效果上都是完全一致的。
我们甚至可以将一个类组件改写成函数组件,或者把函数组件改写成一个类组件(虽然并不推荐这种重构行为)。从使用者的角度而言,很难从使用体验上区分两者,而且在现代浏览器中,闭包和类的性能只在极端场景下才会有明显的差别。所以,基本可认为两者作为组件是完全一致的。
不同点:
- 它们在开发时的心智模型上却存在巨大的差异。类组件是基于面向对象编程的,它主打的是继承、生命周期等核心概念;而函数组件内核是函数式编程,主打的是 immutable、没有副作用、引用透明等特点。
- 之前,在使用场景上,如果存在需要使用生命周期的组件,那么主推类组件;设计模式上,如果需要使用继承,那么主推类组件。但现在由于 React Hooks 的推出,生命周期概念的淡出,函数组件可以完全取代类组件。其次继承并不是组件最佳的设计模式,官方更推崇“组合优于继承”的设计概念,所以类组件在这方面的优势也在淡出。
- 性能优化上,类组件主要依靠 shouldComponentUpdate 阻断渲染来提升性能,而函数组件依靠 React.memo 缓存渲染结果来提升性能。
- 从上手程度而言,类组件更容易上手,从未来趋势上看,由于React Hooks 的推出,函数组件成了社区未来主推的方案。
- 类组件在未来时间切片与并发模式中,由于生命周期带来的复杂度,并不易于优化。而函数组件本身轻量简单,且在 Hooks 的基础上提供了比原先更细粒度的逻辑组织与复用,更能适应 React 的未来发展。
17 Vue与React的选择?
Webpack
静态模块打包器,将程序根据入口生成一个依赖关系,将这些模块打包成一个或多个bundle.
1.核心概念
- input入口
- optput出口
- loader转换器,将文件转换成浏览器能认识的
- plugins扩展插件,在构建流程中加入扩展逻辑。
2.loader
babel-loader/style-loader/css-loader/postcss-loader/less-loader/autoprefixer/url-loader/file-loader test:匹配规则,针对符合规则的文件处理 use:使用的loader,可以是字符串,也可以是数组,数组中的内容可以是对象,配置options
3.plugins
html-webpack-plugin/clean-webpack-plugin/terser-webpack-plugin/css-minimizer-webpack-plugin/mini-css-extract-plugin/copy-webpack-plugin将单个文件或目录复制到构建目录/ProvidePlugin配置一些文件全局,不需要引入直接使用/HotModalReplacementPlugin/
4.devServer
配置一些本地启动的信息,host,端口,打印限制stats
5.devtool
source-map
6.按需加载
用到的时候再去import
7.resolve
modules 配置三方模块查找的路径 alias 设置路径别名
8.定义环境变量
webpack-merge DefinePlugin
9.跨域解决
deServer里配置proxy
devServer: {
proxy: {
'/api': {
target: 'http://localhost:4000',
pathRewrite: {
'/api': ''
}
}
}
}
10.Babel
10.1 Preset
一些Plugin组成的合集
常见的Preset
- @babel/preset-env,将高版本的js代码根据内置规则转译成低版本js,仅针对语法阶段的转译,如箭头函数,const/let。
- babel-preset-react,将jsx转译,jsx最终会被编译成React.createElement()
- babel-preset-typescript,讲ts编译成js.
10.2 前端基建中babel配置
- babel-loader:识别匹配文件,接受对应参数的函数。
- babel-core:将代码词法语法语义分析成AST抽象语法数。
- babel-preset-env,通过babel-core告诉babel以什么样的规则进行转译。
10.3 polyfill
api和实例方法的转译需要polyfill
@babel/polyfill
往全局对象上添加属性,直接修改内置对象的原型的方法,这种方式会造成全局污染。 在babel-preset-env中有一个useBuiltIns参数,决定如何在preset-env种使用@babel/polyfill。
{
"presets": [
["@babel/preset-env", {
"useBuiltIns": false //- `useBuiltIns`--`"usage"`| `"entry"`| `false`
}]
]
}
false:只会转译语法。不会转译任何API和方法。 entry:需在入口文件手动引入core-js,根据broswerList全量引入polyfill uasge:根据broswerList以及代码中用到的API按需引入polyfill.
@babel/runtime
按需加载解决方案,需要自己手动引入。
@babel/plugin-transform-runtime
解决babel/runtime手动引入以及编译过程中重复生成冗余代码。
网络相关
1. http与https的区别与优缺点
- http是超文本传输协议,是明文传输,连接是简单无状态的,端口是80.
- https是在http的基础上加了一层ssl加密传输协议,比较安全,默认端口号是443,握手状态比较费时,需要ca证书。
2. https通信步骤
- 客户端发送支持加密的协议和版本给客户端。
- 服务端获取合适的协议。
- 服务端发送证书和公钥给客户端。
- 客户端通过根证书验证证书,生成对称密钥,通过公钥加密发送给客户端。
- 服务端通过私钥解密获得对称秘钥,加密数据发送给客户端。
- 客户端解密获取数据,建立连接。
3. TCP的三次握手
- 客户端发送syn到服务端
- 服务端确认收到后发送syn和ack到客户端。
- 客户端收到后发送ack到服务端。
4. 四次挥手
- 客户端发送链接释放给服务端
- 服务端收到后发送ack到客户端,等待关闭
- 服务端确认数据已经发送完成
- 客户端发送ack关闭连接
5. post和get的区别
get:数据放在url上,只发送一个tcp连接,大小有限制,会被浏览器主动缓存
post:数据放在请求体内,参数不被浏览器缓存所以更安全,发送两个tcp,先头后请求体
6. TCP和UDP
UDP | TCP | |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输,使用流量控制和拥塞控制 |
连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 只能是一对一通信 |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
适用场景 | 适用于实时应用(IP电话、视频会议、直播等) | 适用于要求可靠传输的应用,例如文件传输 |
手写js
1.防抖
触发多次重置倒计时只在倒计时结束后执行,如input输入
function debunce(fun, time) {
let timer;
return function () {
clearTimeout(timer)
timer = setTimeout(() => {
fun.apply(this)
}, time)
}
}
2.节流
单位时间内执行一次,如滚动加载
function throttle(fun, time) {
let t1 = 0
return function () {
let t2 = new Date();
if (t2 - t1 > time) {
fun.apply(this)
t1 = t2
}
}
}
4.函数柯里化
高阶函数的一种特殊应用。将多参数的一个函数,转成一系列只有一个参数的函数。不停的递归拼接参数,参数满足后调用传入的函数。
4.1高阶函数
- 函数的参数是一个函数,回调函数是一种高阶函数。
- 函数返回一个函数,当前函数也是一个高阶函数。
const curry(fun,...args){
const funLen= fun.length;
return function(...innerArgs){
innerArgs=args.concat(innerArgs);
if(innerArgs.length<funLen){
return curry(fun,...innerArgs)
}else{
fun(...innerArgs)
}
}
}
5.手写allpy
```js
Function.prototype.apply2 = function (context) {
// 不传参默认执行环境为window
context = context || window;
// 把调用apply2的函数(this)添加到执行环境上
context.fn = this;
// 拿到传入apply2的第二个参数
const params = arguments[1];
// 把参数放入需要执行的函数上
const result = params ? context.fn(...params) : context.fn();
// 删除添加的函数
delete context.fn;
// 返回执行结果
return result;
}
testThis.apply2({ name: 'Ada' }, [30, 'teacher'])
```
6. 手写call
Function.prototype.call2 = function (context) {
// 不传参默认执行环境为window
context = context || window;
// 把调用call2的函数(this)添加到执行环境上
context.fn = this;
// 拿到传入call2的剩余参数
const params = [...arguments].slice(1)
// 把参数放入需要执行的函数上
const result = context.fn(...params);
// 删除添加的函数
delete context.fn;
// 返回执行结果
return result;
}
// testThis(20)
testThis.call2({ name: 'Lily' }, 10, 'student')
7.手写bind
Function.prototype.bind2 = function (context) {
// 不传参默认执行环境为window
context = context || window;
// 拿到传入bind2的剩余参数
const params = [...arguments].slice(1);
let _this = this;
// 把调用bind2的函数(this)添加到执行环境上
context.fn = this;
return function F() {
// 被当作构造函数调用
if (this instanceof F) {
_this(...params)
} else {
const result = context.fn(...params);
// 删除添加的函数
delete context.fn;
// 返回执行结果
return result;
}
}
}
const bind2Test = testThis.bind2({ name: 'Rax' }, 40, 'president')
bind2Test();
new bind2Test();
8. new的实现
function NewMy(context) {
const obj = new Object;
obj._proto_ = context.prototype;
const res = context.apply(obj, [...arguments.slice(1)])
return typeof res === 'object' ? res : obj;
}
9. 深拷贝
9.1 JSON.parse(JSON.stringify());
缺点:可以实现数组或对象深拷贝,但不能处理函数和正则
9.2 递归实现
function clone(target) {
if (typeof target === 'object') {
let newTarget = Array.isArray(target) ? [] : {};
for (let key in target) {
newTarget[key] = clone(target[key])
}
} else {
return target
}
}
9.3 存在循环引用,并且有其他类型的情况
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 如果以上条件都不满足进行深拷贝
// 查找之前是否已经拷贝过
if (map.get(obj)) return map.get(obj);
// 创建一个新的所属类
let newTarget = new obj.constructor();
map.set(obj, newTarget)
for (let key in obj) {
console.log(key);
// 判断值是否在实例上定义
if (obj.hasOwnProperty(key)) {
console.log(key);
newTarget[key] = deepClone(obj[key], map)
}
}
return newTarget
}
10.Promise
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
try {
executor(this.resolve, this.reject)
} catch (err) {
this.reject(err)
}
}
status = PENDING;
value = null;
reason = null;
// 储存回调函数
onFulfilledCallback = [];
onRejectedCallBack = [];
// 箭头函数让this指向当前实例
resolve = value => {
if (this.status == PENDING) {
this.status = FULFILLED;
this.value = value;
// 判断回调是否存在,如果存在就调用
this.onFulfilledCallback && this.onFulfilledCallback.forEach(fn => fn(value));
}
}
reject = reason => {
if (this.status == PENDING) {
this.status = REJECTED;
this.reason = reason;
// 判断回调是否存在,如果存在就调用
this.onRejectedCallBack && this.onRejectedCallBack.forEach(fn => fn(reason));
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
// 为了链式调用需要创建一个promise并且return
const promise2 = new MyPromise((resolve, reject) => {
if (this.status == FULFILLED) {
// 创建微任务等待promise2初始化完成
queueMicrotask(() => {
try {
// 拿到成功回调的执行结果
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
} else if (this.status == REJECTED) {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
} else if (this.status == PENDING) {
// 不知道状态的变化所以成功回调和失败回调都要存起来。
this.onFulfilledCallback.push(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
});
this.onRejectedCallBack.push(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}
})
return promise2;
}
static resolve(param) {
// 传入promise直接返回
if (param instanceof MyPromise) { return param }
return new MyPromise(resolve => {
resolve(param)
})
}
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
}
function resolvePromise(promise2, x, resolve, reject) {
// console.log(promise2);
// console.log(x);
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise # < Promise >'))
}
if (x && (typeof x === 'object' || typeof x === 'function')) {
// 实现值穿透,拿到传入promise的value,并将value resolve成创建的promise的value将创建的promise的状态改变。
// x.then(value => resolve(value), reason => reject(reason))
let then;
try {
then = x.then;
} catch (err) {
return reject(err)
}
console.log(then);
// x是promise的时候处理
if (typeof then === 'function') {
// 如果resolvePromise 和 rejectPromise 都调用了第一个优先调用,后面忽略
let called = false;
try {
then.call(x,
y => {
if (called) return;
called = true;
// 还是做值穿透,y就是x.then里面的value
resolvePromise(promise2, y, resolve, reject)
},
r => {
if (called) return;
called = true;
reject(r)
}
)
} catch (err) {
if (called) return;
reject(err)
}
} else {
// x是普通对象及函数直接处理
resolve(x)
}
} else {
// x是对象及函数外直接处理
resolve(x)
}
}
function other() {
return () => { console.log('it is a function'); }
return {
'a': 111,
'b': 222
}
return new MyPromise((resolve, reject) => {
resolve('other')
})
}
const promise = new MyPromise((resolve, reject) => {
// 传入value或者reason,改变status
// throw new Error('执行器error')
resolve('success')
// reject('err')//不会执行,status已经改变。
})
.then()
.then()
// 拿到value或者reason,执行传入的回调函数
.then(value => {
console.log('resolve1', value);
// throw new Error('then error')
return other()
// return MyPromise.resolve('resolve static')
}, reason => {
// throw new Error('then error')
console.log('reject', reason);
})
.then(value => {
console.log('resolve2', value);
}, reason => {
console.log('reject', reason);
})
11. 获取url参数
function getSearchParams(url) {
const decodeUrl = decodeURIComponent(url)
let params = {};
const urlArr = decodeUrl.split("?");
const paramsArr = urlArr[1].split("&");
for (let i = 0; i < paramsArr.length; i++) {
const queryArr = paramsArr[i].split("=");
try {
JSON.parse(queryArr[1])
params[queryArr[0]] = JSON.parse(queryArr[1])
} catch (e) {
params[queryArr[0]] = queryArr[1];
}
}
return params;
}
12.手写instanceof
function myInstanceof(target, origin) {
if (typeof target !== 'object' || target == null) return;
if (typeof origin !== 'function') {
throw new TypeError('origin must be function')
};
let proto = Object.getPrototypeOf(target)
while (proto) {
if (proto == origin.prototype) return true;
proto = Object.getPrototypeOf(proto)
}
return false
}
console.log(myInstanceof([1, 2, 3], Array));
13.数组扁平化
const arr = [[1, 2], [2, 3, [3, 4, 5]]]
function flat(arr, depth = 1) {
if (depth > 0) {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flat(cur, depth - 1) : cur)
}, [])
}
return arr;
}
console.log(flat(arr, 2))
14.手写reduce
Array.prototype.myReduce = function (cb, initialV) {
const arr = this;
const total = initialV;
for (let i = 0; i < arr.length; i++) {
total = cd(total, arr[i], i, arr)
}
return total
}
15.带并发的异步调度器
class Scheduler {
constructor() {
this.wating = [];
this.executing = [];
this.max = 2;
}
add(promiseMaker) {
if (this.executing.length < this.max) {
this.executing.push(promiseMaker)
this.run(promiseMaker)
} else {
this.wating.push(promiseMaker)
}
}
run(promiseMaker) {
promiseMaker().then(() => {
this.executing.slice(this.executing.length - 1, 1);
this.wating.length > 0 && this.run(this.wating.shift())
})
}
}
const timeout = (time) => new Promise((resolve) => {
setTimeout(resolve, time);
});
const scheduler = new Scheduler();
const addTask = (time, order) => {
scheduler.add(() => timeout(time).then(() => console.log(order)));
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
16.数组去重
function unique1(arr) {
return [...new Set(arr)]
}
function unique2(arr) {
return arr.filter((item, idx, array) => {
return array.indexof(item) === idx
})
}
17.链式调用
function Query(data) {
this.data = data;
let whereArr = [];
let result = [];
this.where = function (predicate) {
for (let i = 0; i < data.length; i++) {
if (predicate(data[i])) {
whereArr.push(data[i])
}
}
return this
}
this.orderBy = function (key, desc) {
function compare(property, desc) {
return function (a, b) {
var value1 = a[property];
var value2 = b[property];
if (desc) {
return value2 - value1;
}
return value1 - value2;
}
}
whereArr.sort(compare(key, desc))
return this
}
this.groupBy = function (key) {
result = Object.values(whereArr.reduce((res, item) => {
res[item[key]] ? res[item[key]].push(item) : res[item[key]] = [item];
return res;
}, {}));
return this
}
this.execute = function () {
return result
}
}
const query = new Query(data)
console.log(query.where(item => item.age > 18).orderBy('age', true).groupBy('city').execute());
class Calculator {
constructor(value) {
this.value = value || 0;
}
add(count) {
this.value += count;
return this;
}
minus(count) {
this.value -= count;
return this;
}
multi(count) {
this.value *= count;
return this;
}
div(count) {
if (this.value == 0) return;
this.value = this.value / count;
console.log(this.value);
return this;
}
}
const myCalculator = new Calculator(121);
myCalculator.add(1).minus(2).multi(3).div(4)