划重点
资料四处收集来的,如果侵害的原作者的利益,如果造成了侵权等问题,请赶快联系!马上删除!!祝各位早日求职上岸!!冲鸭!!如果有错误,
实现步骤
function Animals(name, color){
this.name = name
}
Animals.prototype.action = function () {
console.log(this.name, ‘walk‘)
首先定义一个构造函数,以及他原型的方法
接着实现一个create
方法来模拟new
function create(constructor, ...args){
const obj = new Object()
obj.__proto__ = constructor.prototype
const res = constructor.apply(obj, args)
return res instanceof Object ? res : obj
}
具体使用则:
const dog = create(Animals, ‘dog‘, ‘red‘)
// const cat = new Animals(‘cat‘, ‘yellow‘)
通过改变一个对象的 [[Prototype]] 属性来改变和继承属性会对性能造成非常严重的影响,并且性能消耗的时间也不是简单的花费在 obj.proto = ... 语句上, 它还会影响到所有继承自该 [[Prototype]] 的对象,如果你关心性能,你就不应该修改一个对象的 [[Prototype]]。
所以我们可以通过别的方法来改变obj原型的指向,通过Object.create()
方法来继承
function create(constructor, ...args) {
const obj = Object.create(constructor.prototype)
const res = constructor.apply(obj, args)
return res instanceof Object ? res : obj
}
首先模拟一下用户输入
<div>
<input id="input"></input>
<div id="text">0</div>
</div>
然后防抖与节流
// 防抖
const debounce = function (fn, delay = 1000) {
let time = null
return function (...args) {
let that = this
time && clearTimeout(time)
time = setTimeout(function () {
fn.apply(that, args)
}, delay)
}
}
// 节流
// 时间戳版
function throttle(fn, delay = 500) {
let last = 0
return function (...args) {
let now = new Date().getTime()
if (now - last > delay) {
last = now
fn.apply(this, args)
}
}
}}
// 定时器版,初次调用会延迟
const throttle = function (fn, delay = 1000) {
let time = null
return function (...args) {
let that = this
if (!time) {
time = setTimeout(function () {
time = null
fn.apply(that, args)
}, delay)
}
}
}
接下来调用即可
const input = document.getElementById(‘input‘)
input.oninput = debounce((e) => {
document.getElementById(‘text‘).innerTexte.target.value
}, 1500)
这样子就可以实现函数防抖和节流啦
其实这里还有另外一个简单一点的方法,就是setTimeout使用箭头函数,这样就可以直接使用this
,就不用额外生成一个变量that
啦
const debounce = function (fn, delay = 1000) {
let time = null
return function (...args) {
time && clearTimeout(time)
time = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
主要是要收集传进来的参数
function curry(){
const argsList = [...arguments]
const fn = function(){
argsList.push(...arguments)
return fn
}
fn.toString = function(){
return argsList.reduce((a, b) => a + b)
}
return fn
}
console.log(curry(1, 2)(3)(4, 5, 6)) // 21
这样就算完成了这个题了,可以自由传入参数来求和,接下来就将一个普通函数变成柯里化函数
function sum(a, b, c) {
return a + b + c
}
function curry(fn) {
const argsList = [...arguments].splice(1)
return function () {
const newArgsList = argsList.concat([...arguments])
if (newArgsList.length < fn.length) {
// 如果接收的参数还没有到达函数参数的个数继续收集参数
return curry.apply(this, [fn, ...newArgsList])
} else {
return fn.apply(this, newArgsList)
}
}
}
const sumAll = curry(sum)
console.log(sumAll(1)(2)(3)) // 6
console.log(sumAll(1)(2, 3)) // 6
当元素样式发生改变,但不影响布局时,浏览器将使用重绘进行元素更新,由于此时只需要UI层面的绘制,因此损耗较小
当元素尺寸、结构或者触发某些属性的时候,浏览器会重新渲染页面,这就叫回流。此时,浏览器需要重新计算,重新进行页面布局,所以损耗较大
一般有以下几种操作:
:hover
回流必定触发重绘,重绘不一定触发回流,重绘代价小,回流代价大
CSS:
position: absolute || fixed
上calc
)JavaScript:
documentFragment
,在他上面应用所有的dom操作,最后再把他添加到文档中display: none
,操作结束后再把它显示出来,因为再display为none的元素上进行dom操作不会引发重绘和回流cookie
通常用于存用户信息,登录状态等,可自行设置过期时间,体积上限为4KlocalStorage
无限期存储,体积上限为4~5MsessionStorage
浏览器窗口关闭则删除,体积上线为4~5Mget
会被浏览器缓存,请求长度受限,会被历史保存记录,浏览器回退时候是无害的,一般不带请求体,发送一个TCP数据包post
更安全,更多编码类型,可以发大数据,浏览器回退的时候会再次提交请求,一般带有请求体,发送两个TCP数据包在网络差的时候,post发送两个TCP请求包,验证完整性更好
为什么要TCP握手呢
为了防止已失效的连接请求报文段突然又传回服务端,从而产生错误
为什么建立是3次握手,而关闭是4次挥手呢?
因为建立连接的时候,客户端接受的是syn + ack包。而关闭的时候,服务端接受fin后,客户端仅仅是不再发送数据,但是还是可以接收数据的。服务端此时可以选择立刻关闭连接,或者再发送一些数据之后,再发送fin包来关闭连接。因此fin与ack包一般都会分开发送。
class Dog {
constructor(name) {
this.name = name;
}
}
class Labrador extends Dog {
// 1
constructor(name, size) {
this.size = size;
}
// 2
constructor(name, size) {
this.name = name;
this.size = size;
}
// 3
constructor(name, size) {
super(name);
this.size = size;
}
};
像上面的代码,1和2都会报错ReferenceError
引发一个引用错误,因为在子类中,在调用super
之前,是无法访问this
的
验证数字的正则表达式集
验证数字:^[0-9]*$
验证n位的数字:^\d{n}$
验证至少n位数字:^\d{n,}$
验证m-n位的数字:^\d{m,n}$
验证零和非零开头的数字:^(0|[1-9][0-9]*)$
验证有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
验证有1-3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
验证非零的正整数:^\+?[1-9][0-9]*$
验证非零的负整数:^\-[1-9][0-9]*$
验证非负整数(正整数 + 0) ^\d+$
验证非正整数(负整数 + 0) ^((-\d+)|(0+))$
验证长度为3的字符:^.{3}$
验证由26个英文字母组成的字符串:^[A-Za-z]+$
验证由26个大写英文字母组成的字符串:^[A-Z]+$
验证由26个小写英文字母组成的字符串:^[a-z]+$
验证由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
验证由数字、26个英文字母或者下划线组成的字符串:^\w+$
验证用户密码:^[a-zA-Z]\w{5,17}$ 正确格式为:以字母开头,长度在6-18之间,只能包含字符、数字和下划线。
验证是否含有 ^%&‘,;=?$\" 等字符:[^%&‘,;=?$\x22]+
验证汉字:^[\u4e00-\u9fa5],{0,}$
验证Email地址:^\w+[-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
验证InternetURL:^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$ ;^[a-zA-z]+://(w+(-w+)*)(.(w+(-w+)*))*(?S*)?$
验证电话号码:^(\d3,4\d3,4|\d{3,4}-)?\d{7,8}$:--正确格式为:XXXX-XXXXXXX,XXXX-XXXXXXXX,XXX-XXXXXXX,XXX-XXXXXXXX,XXXXXXX,XXXXXXXX。
验证身份证号(15位或18位数字):^\d{15}|\d{}18$
验证一年的12个月:^(0?[1-9]|1[0-2])$ 正确格式为:“01”-“09”和“1”“12”
验证一个月的31天:^((0?[1-9])|((1|2)[0-9])|30|31)$ 正确格式为:01、09和1、31。
整数:^-?\d+$
非负浮点数(正浮点数 + 0):^\d+(\.\d+)?$
正浮点数 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
非正浮点数(负浮点数 + 0) ^((-\d+(\.\d+)?)|(0+(\.0+)?))$
负浮点数 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
浮点数 ^(-?\d+)(\.\d+)?$
link,img,script标签可以跨域
const script = document.createElement(‘script‘)
script.type = ‘text/javascript‘
script.src = ‘xxx.com/login?user=xxx&password=123&callback=onBack‘
document.head.appendChild(script)
function onBack(res) {
console.log(res)
}
CORS (Cross-Orgin Resources Share)跨域资源共享,允许浏览器向服务器发出XMLHttpRequest请求,从而克服跨域问题,他需要浏览器与服务器同时支持
orgin
字段,表明当前请求来源Access-Control-Allow-Methods
,Access-Control-Allow-Headers
,Access-Control-Allow-Origin
等字段,指定允许的方法、头部、来源等信息OPTIONS
请求来判断当前是否允许跨域请求方法是以下三种之一:
Http的请求头信息不超过以下几种字段:
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
)后端的响应头信息:
Access-Control-Allow-Orgin
:此字段必须有,他的值要么是请求时orgin
的值,要么是*表示接受任意域名的访问Access-Control-Allow-Credentials
:此字段可选,是一个布尔值,用来表示是否允许发送cookies
Access-Control-Expose-Headers
:此字段可选,如果想XMLHttpRequest对象的getResponseHeader()
方法获取更多请求头,就要在这个字段指定非简单请求是指对服务器有特殊要求的请求,例如:
PUT
和DELETE
Content-Type
为application/json
非简单请求会在正式通信之前,增加一次HTTP查询请求,称为预检请求,就是options
啦
Access-Control-Request-Methor
:此字段必须有,用来表示浏览器的CORS请求会用到哪些HTTP方法,例如PUT
Access-Control-Request-Headers
:此字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的请求头信息GET
请求,CORS支持所有类型的HTTP请求其实平时开发使用
webpack
进行构建的时候,后缀有hash值,其实这就是与缓存相关的东西
缓存可以分为强缓存和协商缓存。强缓存不过服务器,协商缓存需要过服务器。协商缓存返回的状态码是304。两种缓存可以同时存在,强缓存的优先级大于协商缓存。当执行强缓存时,如若缓存命中,则直接使用缓存数据库中的数据,不执行协商缓存。
Expires (HTTP 1.0):Expires
的值为服务端返回的数据过期时间。当再次请求时间小于过期时间,则直接使用缓存数据。但由于服务端和客户端的时间可能有误差,可能会出现缓存命中的误差。另外,大多数使用Cache-Control
代替
Pragma (HTTP 1.0):HTTP 1.0遗留下来的字段,当值为no-cache
时强制验证缓存,Pragma禁用缓存,如果给Expires
定义一个未过期的时间,那么Pragma的优先级更高。服务端响应添加Pragma: no-cache
,浏览器表现的行为和刷新(F5)相似
Cache-Control (HTTP 1.1):有以下几种属性
no-cache不是代表不缓存的意思,no-cache代表的是可以缓存,但是每次都要通过服务器验证缓存是否可用。no-store才是不缓存内容。
当响应头Cache-Control
有指定max-age
时,优先级会比expires
高。
命中强缓存的形式:Firefox浏览器:灰色的200状态码,Chrome浏览器:200(from disk cache)或者200 OK(from memory cache)
协商缓存需要进行对比判断是否可以使用缓存。浏览器第一次请求数据时候,服务器会将缓存标识与数据一起响应给客户端,客户端将他们备份至缓存中。再次请求时候,客户端会将缓存中的标识发送给服务端,服务端根据此表示判断。若未失效,返回304状态码,浏览器拿到此状态码,就可以直接使用缓存数据了。
Last-Modified:服务器再响应请求时,会告诉浏览器资源的最后修改时间,因为可能最后一秒多次修改,或者是服务器与客户端时间不同,可能导致缓存未命中,所以之后推出了etag
if-Modified-Since:浏览器再次请求服务器的时候,请求头会包含此字段,后面跟着在缓存中获得的最后修改时间。服务端收到此请求发现有if-Modified-Since
后,则与被请求资源的最后修改时间进行对比,如果一致则返回304响应,浏览器只需要从缓存中获取数据即可
if-Unmodified-Since:从某个时间点开始,文件是否没有被修改,使用的是相对时间,不需要关心客户端与服务端的时间偏差。
这两个的区别是一个修改了才下载,一个是没修改才下载。
如果在服务器上,一个资源被修改了,但是他的实际内容根本没有发生变化,会因为Last-Modified时间匹配不上而返回了整个数据给客户端(即使客户端缓存里面有一个一模一样的资源),为了解决这个问题,我们就需要用到HTTP 1.1的Etag
Etag:服务器响应请求时,通过此字段告诉浏览器当前资源在服务器生成的唯一标识(生成规则由服务器决定)
If-Match:条件请求,携带上一次请求中资源的Etag
,服务器根据这个字段判断文件是否有新的修改
If-None-Match:再次请求服务器时,浏览器的请求报文头部会包含此字段,后面的值为在缓存中获取的标识,服务器接收到报文后发现If-None-Match
则与被请求的资源的唯一表示进行对比
Etag 的精度比 Last-modified 高,属于强验证,要求资源字节级别的一致,优先级高。如果服务器端有提供 ETag 的话,必须先对 ETag 进行 Conditional Request。
但是实际应用中由于
Etag
的计算是由算法得来的,而算法会占用服务器计算的资源,所有服务器端的资源都是宝贵的,所以就很少使用Etag
了
If-Modified-Since
来查看文件是否过期对于大部分的场景都可以使用强缓存配合协商缓存来解决,但是在一些特殊情况下,可能需要选择特殊的缓存策略
Cache=Control: no-store
来表示该资源不需要缓存Cache-Control: no-cache
并且配合Etag
使用,表示该资源已被缓存,但是每次都会发送请求询问资源是否更新Cache-Control: max-age=31536000
并且配合策略缓存使用,然后对文件进行指纹处理,一旦文件名变动就会立即下载新的文件Function.prototype.myCall = function (obj = window) {
obj.fn = this
const args = [...arguments].splice(1)
const result = obj.fn(...args)
delete obj.fn
return result
}
Function.prototype.myApply = function (obj = window) {
obj.fn = this
const args = arguments[1]
let result = args ?
obj.fn(...args) :
obj.fn()
delete obj.fn
return result
}
Function.prototype.myBind = function (obj = window) {
const that = this
const args = [...arguments].splice(1)
return function () {
return that.apply(obj, [...args, ...arguments])
}
}
// 然后使用即可
function log() {
console.log(this)
console.log(this.value)
console.log(arguments)
}
const obj = {
value: 123
}
log.myCall(obj, 1, 2, 3)
log.myApply(obj, [1, 2, 3])
log.myApply(obj)
log.myBind(obj)()
log.myBind(obj)(1, 2, 3)
log.myBind(obj, 4, 5, 6)(1, 2, 3)
伪类和伪元素是为了修饰不在文档树中的部分,比如一句话的第一个字母,列表中第一个元素。
伪类用于当已有元素处于某种状态时候,为其添加对应的样式,这个状态是根据用户行为变化而变化的。比如说hover。虽然他和普通的css类似,可以为已有的元素添加样式,但是他只有处于dom树无法描述的状态才能为元素添加样式,所以称为伪类
伪元素用于创建一些原本不在文档树中的元素,并为其添加样式,比如说:before
。虽然用户可以看到这些内容,但是其实他不在文档树中。
伪类的操作对象是文档树中已存在的元素,而伪元素是创建一个文档树外的元素。因此,伪类与伪元素的区别就在于:有没有创建一个文档树之外的元素
css规范中使用两个双冒号::
来表示伪元素,一个冒号:
来表示伪类。例如:::before
,:hover
附上层叠上下文表
事件委托就是把一个元素响应时间(click,keydown....)的函数委托到另一个元素。一般来说,会把一个或者一组元素的时间委托到父层或者更外层元素上。真正绑定事件的是外层元素,当事件响应到需要绑定的元素时候,会通过事件冒泡机制,从而触发他的外层元素的绑定事件上,然后再外层函数执行
事件模型分为三个阶段
ul
上统一处理<ul id="list">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
......
<li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li
所以事件委托可以大量减少内存的消耗,节约效率
所以事件委托可以动态绑定时间,减少很多重复工作
window.onload = () => {
const list = document.getElementById(‘list‘)
list.addEventListener(‘click‘, function (event) {
console.log(event.target)
})
}
<ul class="list" id="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
这样点击li标签的时间就会冒泡到ul上面,然后开始执行绑定的方法
分情况分析:
while (x.__proto__) {
if (x.__proto__ === y.prototype) {
return true
}
x.__proto__ = x.__proto__.__proto__
}
if(x.__proto__ === null){
return false
}
判断x对象是否为y的一个实例,会在原型链上一直找,找到则返回true
Student.prototype = new Person(‘b‘)
Student.prototype.constructor = Student
缺点:
new Person
后面,不然会被覆盖function Child(name, parentName) {
Parent.call(this, parentName);
this.name = name;
}
缺点:
function Child(name, parentName) {
Parent.call(this, parentName);
this.name = name;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
缺点:
function Person(name) {
this.name = name
}
Person.prototype.a = function () {
console.log(‘person a‘)
}
Person.prototype.b = function () {
console.log(‘person b‘)
}
function Student(name, superName) {
Person.call(this, superName)
this.name = name
}
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student
Student.prototype.b = function () {
console.log(‘student b‘)
}
const person = new Person(‘p‘)
const student = new Student(‘s‘)
console.log(person)
console.log(student)
person.a()
person.b()
student.a()
student.b()
console.log(student instanceof Person)
const arr = [5, 4, 3, 2, 1]
arr.name = ‘name‘
for(let i in arr){
console.log(i) // 0 1 2 3 4 name(字符串类型的索引)
}
输出的是0 1 2 3 4 name
,因为我们给arr加了一个name的属性,for...in也会把他遍历出来
所以for...in一般用于遍历对象,值是他的键值
const arr = [5, 4, 3, 2, 1]
arr.name = ‘name‘
for(let i in arr){
console.log(i) // 0 1 2 3 4 name(字符串类型的索引)
}
输出的是5 4 3 2 1
,输入数组里面每个的值
总结
Object.keys()
const test = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
]
// 要求将以上数组扁平化排序且去重
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
// 递归版
function flat(arr) {
let list = []
arr.forEach(item => {
if (Array.isArray(item)) {
list.push(...flat(item))
// list = list.concat(flat(item))
} else {
list.push(item)
}
})
return list
}
const res1 = [...new Set(flat(test))].sort((a, b) => a - b)
// ES6版
const res2 = [...new Set(test.flat(Infinity))].sort((a, b) => a - b)
console.log(res1)
console.log(res2)
写代码的时候注意内存泄露(定时器、事件监听、闭包、全局变量、dom引用等)
css部分不要用table布局、css表达式等会引起回流的样式、可以开启3d硬件加速(perspective、backface-visibility修复闪烁)
js部分不要频繁修改样式、操作dom、可以使用防抖节流等、脚本放在页面底部
webpack部分可以压缩js,css、分离公共资源例如ui组件库等、配置组件按需加载(import()实现)
浏览器部分可以设置缓存,使用cdn
图片可以压缩图片,使用webp格式、雪碧图等,图片多的时候可以用懒加载等方法
React部分可以使用shouldComponentUpdate、React.Memo、pureComponent、列表元素设置key,尽量在constructor里面绑定函数等
nginx可以配置负载均衡、多服务器可以用redis来保持会话
页面内容
服务器
Cookie
Css
<head>
中<link
代替@import
JavaScript
图片
移动端
将div平滑滑动,从快至慢,不能用css3的属性
<!DOCTYPE html>
<html>
<head>
<script>
window.onload = () => {
var element = document.getElementById(‘t‘);
function step(timestamp) {
console.log(timestamp)
var progress = timestamp;
// element.style.left = (progress / 30) * (10 - progress / 1000) + ‘px‘;
element.style.left = Math.pow(progress / 5, .5) * 40 + ‘px‘;
if (progress < 5000) {
window.requestAnimationFrame(step);
} else {
window.cancelAnimationFrame(step)
}
}
window.requestAnimationFrame(step);
}
</script>
<style>
.container {
position: relative;
width: 1000px;
}
#t {
position: absolute;
width: 50px;
height: 50px;
background: red;
}
</style>
</head>
<body>
<div class="container">
<div id="t"></div>
</div>
</body>
</html>
有一个input输入框,输入关键词的时候,模拟autocomplete效果补全
<!DOCTYPE html>
<html>
<head>
<script>
window.onload = () => {
const input = document.getElementById(‘input‘)
const words = [‘珠海‘, ‘广州‘, ‘上海‘, ‘杭州‘, ‘成都‘]
input.addEventListener("input", debounce(function (e) {
const value = e.target.value
const index = words.findIndex((item) => value && item.indexOf(value) !== -1)
if (index !== -1) {
e.target.value = words[index]
}
}, 500))
function debounce(fn, wait = 500) {
let timeout = null
return function () {
if (timeout) {
clearTimeout(timeout)
}
timeout = setTimeout(() => {
fn.apply(this, [...arguments])
}, wait)
}
}
}
</script>
<style>
</style>
</head>
<body>
<input id="input" />
</body>
</html>
const deepClone = (obj) => {
let result = null
if (typeof obj === ‘object‘) {
result = Array.isArray(obj) ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === ‘object‘) {
result[key] = deepClone(obj[key])
} else {
result[key] = obj[key]
}
}
}
} else {
// 如果不是对象与数组,则直接赋值
result = obj
}
return result
}
或者使用自带方法
JSON.parse(JSON.stringify(obj))
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
简单版
arr.sort(() => Math.random() - 0.5)
洗牌算法
const shuffle = (arr) => {
let len = arr.length
while (len > 1) {
let index = Math.floor(Math.random() * len--);
[arr[index], arr[len]] = [arr[len], arr[index]]
}
return arr
}
const arr = [1, 1, 1, 2, 3, 4, 5, 5, 3, 2, 1, 1]
简单版
[...new Set(arr)]
循环版
const removeDrop = function () {
const result = []
const map = {}
arr.forEach((item) => {
if (!map[item]) {
map[item] = true
result.push(item)
}
})
return result
}
key是用来帮助react识别哪些内容被更改、添加或者删除。key值需要一个稳定值,因为如果key发生了变化,react则会触发UI的重渲染。
唯一性: key值必须唯一,如果出现了相同,会抛出警告,并且只会渲染第一个重复key值的元素。因为react会认为后续拥有相同key值的都是同一个组件。
不可读性: 虽然在组件定义了key,但是子组件中是无法获取key值的
改变组件的key值的话,可以销毁之前的组件,再创建新的组件
每一次state的更改都会使得render函数被调用,但页面的dom不一定会发生修改
初始化阶段:
更新阶段:
析构阶段
难点在于如何判断旧的对象和新的对象之间的差异 react的办法是只对比同层的节点,而不是跨层对比
步骤分为两步:
Virtual Dom的算法实现主要是三步
class App extends React.Component {
state = { val: 0 }
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
setTimeout(_ => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val);
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
}, 0)
}
render() {
return <div>{this.state.val}</div>
}
}
因为在钩子函数里面setState是异步的,所以前两次无法获得val的值,所以都输出0,然后到setTimeout的时候,setState已经更新了,而且因为是批量更新所以只执行最后一次,所以到setTimeout的时候,val是1。因为setTimeout中是同步的,所以第一次是2,第二次是3
在代码调用setState函数之后,React会将传入的参数对象与组件当前的状态合并,然后触发调和过程(Reconciliation),经过调和过程,React会以相对高效的方式根据新的状态构建React元素树,并且重新渲染整个UI界面。React得到元素树之后,会计算出新树与老树的节点差异,然后根据差异对界面进行最小化的重渲染。在差异算法中,React能够相对精确的知道那些地方发生了改变,这就保证了按需更新,而不是全部重新渲染
React Element是描述屏幕上可见内容的数据结构,是对于UI的对象表述。而React Component这是可以接受参数输入并且返回某个React Element的函数或者类
当组件需要内部状态以及生命周期函数的时候就选择Class Component,否则就是用Functional Component
Refs是React提供的可以安全访问dom元素或者某个组件实例的句柄,可以为元素添加ref属性然后再回调函数中接受该元素在dom树中的句柄
keys是React用于追踪列表中哪些元素被修改、添加或者删除的辅助标识,需要保证key的唯一性。React diff算法中,会借助key值来判断元素是新建的还是移动而来的,从而减少不必要的元素重渲染。此外React还需要借助Key值来判断元素与状态的关联关系
this.state = {
value: ‘‘
}
...
handleChange = (e) => {
this.setState({
value: e.target.value
})
}
...
<input onChange={() => this.handleChange} />
handleChange = () => {
console.log(this.input.value)
}
...
<input refs={(input) => this.input = input}
onChange={() => this.handleChange} />
尽管非受控组件比较容易实现,但是我们有时候会需要对数据进行处理,使用受控组件会更加容易
应该放在componentDidMount()
componentDidMount
中就不会有这些问题了shouldComponentUpdate允许我们手动的判断组件是否更新,就可以避免不必要的更新渲染
为了解决跨浏览器兼容性的问题,React会将浏览器原生事件封装为合成事件传入设置的事件处理器中。合成事件与原生时间采用相同的接口,不过他们屏蔽了底层浏览器的细节差异,保证了行为的一致性。React没有将事件依附在子元素上,而是将所有事件发送到顶层 document
进行处理
JSX上面的事件全部绑定在 document
上,不仅减少了内存消耗,而且组件销毁的时候可以统一移除事件
如果我们不想事件冒泡的话,应该是调用event.preventDefault
,而不是传统的event.stopPropagation
createElement函数是JSX编译之后使用的创建Element的函数,而cloneElement这是复制某个元素并传入新的Props
是一个回调函数,这个函数会在setState函数调用完成并且组件开始重渲染的时候被调用,我们可以通过这个函数来判断更新渲染是否完成
虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能 具体步骤如下:
Flux最大的特点就是,数据单向流动
event.persist()
高阶组件是一个以组件为参数并返回一个新组建的函数。HOC运行重用代码、逻辑和引导抽象。最常见是Redux的connect函数。HOC最好的方式是分享组件之间的行为。如果发现一个地方大量代码用来做一件事情,可以考虑重构为HOC
因为state和props的更新可能是异步的,不能依赖他们去计算下一个state
在回调中可以使用箭头函数,但是问题就是每次渲染都会创建一个新的回调
因为super()
被调用之前,子类是不可以使用this的
React.createClass、ES6 class、无状态函数
调用时间不同
useLayoutEffect比useEffect先执行
React是使用类似单链表的形式来代替数组,通过next按顺序串联所有hook
useEffect特点:
let arr = [] // 一个数组,存储每一个hook
let index = 0 // 下标,存取每个hook对应的位置
function useState(init){
arr[index] = arr[index] || init // 如果存在则用之前的值
const cur = index // 设置一个变量存储当前下标
function setState(newState){
arr[cur] = newState
render() // 渲染,页面触发更新
}
return [arr[index++], setState]
}
function useEffect(callback, deps){
const hasNoDeps = !deps // 如果没有依赖,则直接每次更新
const lastDeps = arr[index] // 获取上一次的依赖数组
const hasChangeDeps = lastDeps
? !deps.every((e, i) => e === lastDeps[i]) // 如果每一项都相同
: true
if(hasNoDeps || hasChangeDeps){
callback()
arr[index] = deps
}
index++
}
import React, {Component, lazy, Suspense} from ‘react‘
const About = lazy(() => import(/*webpackChunkName: "about" */‘./About.jsx‘))
class App extends Component {
render() {
return (
<div>
// 我们需要用suspense来进行过渡,显示一个loading
<Suspense fallback={<div>Loading...</div>}>
<About></About>
</Suspense>
// 这样子是不行的,因为还没有加载完成不知道显示什么
<!--<About></About>-->
</div>
);
}
}
export default App;
class App extends Component {
state = {
hasError: false,
}
static getDerivedStateFromError(e) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <div>error</div>
}
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<About></About>
</Suspense>
</div>
);
}
}
与pureComponent
类似,但是pureComponent
提供的shouldComponentUpdate
只是对比传入的props本身有没有变化,如果内部的值变了,他也不会更新。例如
class Foo extends PureComponent {
render () {
console.log(‘Foo render‘);
return <div>{this.props.person.age}</div>;
}
}
person: {
count: 0,
age: 1
}
// person.age++, 本来应该触发更新,但是不会因为props的第一级没有发生变化
这时候我们可以用memo来优化一下
const Foo = memo(function Foo (props) {
console.log(‘Foo render‘);
return <div>{props.person.age}</div>;
})
memo的第一个参数是函数组件,第二个是与shouldComponentUpdate类似
是一个函数
(nextProps, nextState)=>{
}
常见于子组件的数据存在父组件中
// 父组件
class AllInput extends React.Component {
constructor(props) {
super(props)
this.state = { content: ‘‘ }
this.handleContentChange = this.handleContentChange.bind(this)
}
handleContentChange(newContent) {
this.setState({ content: newContent })
}
render() {
return (
<div>
<Input content={ this.state.content } onContentChange={ this.handleContentChange }/>
<br /><br />
<Input content={ this.state.content } onContentChange={ this.handleContentChange }/>
</div>
)
}
}
// 子组件
class Input extends React.Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
}
handleChange(e) {
setState(修改数据的方法)
this.props.onContentChange(e.target.value)
}
render() {
return (
<input type=‘text‘ value={ this.props.content } onChange={ this.handleChange } />
)
}
}
高阶函数就是接受一个或者多个函数然后返回一个函数的东西
主要功能有两个
属性代理有三个常用的作用
function HigherOrderComponent(WrappedComponent) {
return class extends React.Component {
render() {
const newProps = {
name: ‘大板栗‘,
age: 18,
};
return <WrappedComponent {...this.props} {...newProps} />;
}
};
}
这样子,就可以将自定义的props传给子组件了
// 普通组件Login
import React, { Component } from ‘react‘;
import formCreate from ‘./form-create‘;
@formCreate
export default class Login extends Component {
render() {
return (
<div>
<div>
<label id="username">
账户
</label>
<input name="username" {...this.props.getField(‘username‘)}/>
</div>
<div>
<label id="password">
密码
</label>
<input name="password" {...this.props.getField(‘password‘)}/>
</div>
<div onClick={this.props.handleSubmit}>提交</div>
<div>other content</div>
</div>
)
}
}
//HOC
import React, { Component } from ‘react‘;
const formCreate = WrappedComponent => class extends Component {
constructor() {
super();
this.state = {
fields: {},
}
}
onChange = key => e => {
const { fields } = this.state;
fields[key] = e.target.value;
this.setState({
fields,
})
}
handleSubmit = () => {
console.log(this.state.fields);
}
getField = fieldName => {
return {
onChange: this.onChange(fieldName),
}
}
render() {
const props = {
...this.props,
handleSubmit: this.handleSubmit,
getField: this.getField,
}
return (<WrappedComponent
{...props}
/>);
}
};
export default formCreate;
例如这样,就可以将输入框的state抽离出来
import React, { Component } from ‘react‘;
const refHoc = WrappedComponent => class extends Component {
componentDidMount() {
console.log(this.instanceComponent, ‘instanceComponent‘);
}
render() {
return (<WrappedComponent
{...this.props}
ref={instanceComponent => this.instanceComponent = instanceComponent}
/>);
}
};
export default refHoc;
这样就可以获取组件的示例了
不可以再无状态组件(函数类型组件)上使用refs
,因为无状态组件没有实例
function HigherOrderComponent(WrappedComponent) {
return class extends WrappedComponent {
render() {
return super.render();
}
};
}
反向继承就是一个函数接收一个组件作为参数传入,然后返回一个继承该组件的类,在该类的render
里面调用super.render()
渲染劫持我们可以控制组件的render,有条件的展示组件 例如
function withLoading(WrappedComponent) {
return class extends WrappedComponent {
render() {
if(this.props.isLoading) {
return <Loading />;
} else {
return super.render();
}
}
};
}
这样就可以按条件来控制组件渲染了
//hijack-hoc
import React from ‘react‘;
const hijackRenderHoc = config => WrappedComponent => class extends WrappedComponent {
render() {
const { style = {} } = config;
const elementsTree = super.render();
console.log(elementsTree, ‘elementsTree‘);
if (config.type === ‘add-style‘) {
return <div style={{...style}}>
{elementsTree}
</div>;
}
return elementsTree;
}
};
export default hijackRenderHoc;
//usual
@hijackRenderHoc({type: ‘add-style‘, style: { color: ‘red‘}})
class Usual extends Component {
...
}
一般只有UI表单控件需要双向绑定。
onChange
等在表单交互很多的时候,单向绑定数据更容易追踪管理和维护,但是需要写较多的代码
跨站点伪造请求
用户在一个网站登录之后,产生一个cookie,此时若打开了一个新的网址,此网址返回了一些恶意的请求,就属于CSRF攻击
跨站脚本攻击,一般是通过web页面插入恶意js代码来攻击。 主要分为三类:
<script>
等<
,>
,/
等字符利用转义符号转换一下生命周期是永久,只能存字符串类型
生命周期为,当前窗口或标签页未被关闭,一旦关闭则存储的数据全部清空
cookie生命周期在过期时间之前都有效,同浏览器下,所有同源窗口之间共享
cookie的属性
path=/blog
,发送的路径是/blogroll
也可以。path属性生效的前提是domain属性匹配BFC就是格式化上下文,相当于一个容器,里面的元素和外部相互不影响,创建的方式有:
BFC主要的作用是
Number.prototype.add = function(n) {
return this + n;
};
Number.prototype.minus = function(n) {
return this - n;
};
例1:
var a = 3;
var b = a = 5;
=
var a = 3
var a = 5
var b = a
所以最后a和b都是5
例2:
var a = { n: 1 }
var b = a
a.x = a = { n: 2 }
一开始a和b都引用对象{n:1}
,然后到赋值语句
如果赋值语句中出现.
则他会比=
先一步执行,也就是说,我们先给a.x开辟一个空间
也就是说现在的a和b都是变成了
{
n: 1,
x: {
n: 2
}
}
接下来到a的赋值了,a现在就变成了
{
n: 2
}
display: none
: 不占空间,不能点击,一般用于隐藏元素,会引发回流,性能开销大,非继承属性,子孙节点改变也无法显示(父元素为none)visibility: hidden
: 占据空间,不能点击,显示不会导致页面结构变化,不会撑开,属于重绘操作,是继承属性,子孙节点可以通过修改显示出来opacity: 0
: 占据空间,可以点击,一般与transition一起用for 比 forEact 快 因为
<Link>
和 <a>
有何区别eval
函数内部的代码有他自己的执行上下文执行栈就是一个调用栈,是一个后进先出数据结构的栈,用来存储代码运行时创建的执行上下文
let a = ‘Hello World!‘;
function first() {
console.log(‘Inside first function‘);
second();
console.log(‘Again inside first function‘);
}
function second() {
console.log(‘Inside second function‘);
}
first();
console.log(‘Inside Global Execution Context‘);
分为两个阶段
代码执行前会创建执行上下文,会发生三件事
全局执行上下文中,this指向全局对象
函数执行上下文中,this取决于函数是如何被调用的。如果他被一个引用对象调动,那么this会设置成那个对象。否则是全局对象
let foo = {
baz: function() {
console.log(this);
}
}
foo.baz(); // ‘this‘ 引用 ‘foo‘, 因为 ‘baz‘ 被
// 对象 ‘foo‘ 调用
let bar = foo.baz;
bar(); // ‘this‘ 指向全局 window 对象,因为
// 没有指定引用对象
是一个用代码的词法嵌套结构定义标识符和具体变量和函数关联的东西。用来存储函数声明和let
, const
声明的变量绑定
同样是一个词法环境,用来存储var
变量绑定
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);
只有在调用函数multipy的时候才会创建函数执行上下文,let和const一开始不会关联任何值,但是var已经会设置为undefined了,这就是常说的变量声明提升
任务分为两类
宏任务
微任务
区别:渐进增强是向上兼容,优雅改进是向下兼容
作用
(function(x){
console.log(x);
}(12345))
const root = document.getElementById("root");
function walk(node, path, max) {
if (!node) {
return;
}
path = [...path, node.tagName];
max = Math.max(max, node.dataset.v);
console.log(path, max);
for (let child of node.children) {
walk(child, path, max);
}
}
walk(root, [], -1)
...
<div id="root" data-v="3">
<p data-v="1">p1</p>
<span data-v="2">
span1
<span data-v="4">span2</span>
<span data-v="5">
<p>1</p>
</span>
</span>
<p data-v="99">p2</p>
</div>
var arr = [func1, func2, func3];
function func1(ctx, next) {
ctx.index++
next();
}
function func2(ctx, next) {
setTimeout(function () {
ctx.index++
next();
});
}
function func3(ctx, next) {
console.log(ctx.index);
}
compose(arr)({ index: 0 });
function compose(arr) {
return function (ctx) {
if (arr.length !== 0) {
let func = arr.shift()
func(ctx, next)
}
function next() {
if (arr.length !== 0) {
let func = arr.shift()
func(ctx, next)
}
}
}
}
原因 inline-block
对外是inline
,对内是block
,会将连续的空白符解析为一个空格
解决办法
font-size: 0
letter-spacing: -4px
vertical-align: bottom
去除垂直间隙HTTP是明文传输,传输的每一个环节都可能会被第三方窃取或者篡改。具体来说就是HTTP数据经过TCP层,然后经过WIFI路由器、运营商和目标服务器,都可能被中间人拿到数据并进行篡改,这就是常说的中间人攻击
HTTPS是一个加强版的HTTP。采用对称加密和非对称加密结合的方式来保护浏览器和服务端之间的通信安全。 对称加密算法加密数据 + 非对称加密算法交换密钥 + 数字证书验证身份 = 安全
HTTPS由两部分组成:HTTP + SSL/TLS,也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过TLS进行加密,所以传输的数据是加密后的数据
首先浏览器会给服务器发送一个client_random
和一个加密的方法列表
服务器接受后给浏览器返回另一个随机数server_random
和加密方法
现在两者拥有三种相同的凭证: client_random
, server_random
和加密方法
接着用这个加密方法将两个随机数混合起来生成密钥,这个密钥就是浏览器和服务器通信的暗号
了
对称加密是最简单的方式,指的是加密和解密用的是同样的秘钥,优点是保证了消息的保密性,但缺点是密钥可能会泄露
非对称加密是说,如果有A、B两把密钥,用A加密过的数据包只能用B解密,反之用B加密的数据包只能由A解密,客户端在发消息的时候,先用公钥加密,服务器再用私钥解密。但是因为公钥不是保密的,可能会被窃取然后对消息进行篡改
为了解决非对称加密中公匙来源的不安全性。我们可以使用数字证书和数字签名来解决。
首先本地生成一对密钥,通过公钥去CA申请数字证书
数字签名:CA拿到信息后会通过单向的hash算法把信息加密,然后生成一个摘要,然后CA会用自己的私钥对摘要进行加密,最后得出的结果就是数字签名了
数字证书:最后CA会将申请的信息还有数字签名整合在一起,就生成了数字证书了,其实也就是一个公钥
数字证书如何起作用:
客户端: 你好,我要发起一个 HTTPS 请求,请给我公钥
服务器: 好的,这是我的证书,里面有加密后的公钥
客户端: 解密成功以后告诉服务器: 这是我的 (用公钥加密后的) 对称秘钥。
服务器: 好的,我知道你的秘钥了,后续就用它传输。
https加解密耗时比较长,也很消耗资源,如果不是安全性要求非常高可以不用
为什么要使用process.nextTick(),因为可以允许用户处理错误,或者是在事件循环继续之前重试请求
浏览器任务分为宏任务和微任务
node分为以下几种:
循环之前,会执行以下操作:
开始循环 循环中进行的操作:
浏览器环境下:
while(true){
宏任务队列.shift()
微任务队列全部任务()
}
Node环境下:
while(true){
loop.forEach((阶段)=>{
阶段全部任务()
nextTick全部任务()
microTask全部任务()
})
}
原先React采用的是stack reconciler调度算法。页面可能会出现卡顿,因为setState之后React会立即开始调和过程,会遍历找不同,所有virtual dom都遍历完之后,才会渲染。在调和的过程时候,主线程被js占用了,所以交互、布局都会停止,就会卡顿
所以现在采用了Fiber reconciler来优化
调度是fiber调和的一个过程
如果UI要有不错的表现,那就得考虑多点东西
所以调和的过程是每次只做一个小小的任务,完成之后查看还有没有优先级高的任务需要处理
fiber {
stateNode: {},
child: {},
return: {},
sibling: {},
}
Fiber拆分的单位是按照虚拟dom节点来拆,因为fiber tree是根据虚拟dom来构造出来的
Fiber利用浏览器的apirequestIdleCallback
来让浏览器空闲的时候执行某种任务
function fiber(剩余时间) {
if (剩余时间 > 任务所需时间) {
做任务;
} else {
requestIdleCallback(fiber);
}
}
使用requestAnimationFrame
来渲染动画
{
Synchronous: 1, // 同步任务,优先级最高
Task: 2, // 当前调度正执行的任务
Animation 3, // 动画
High: 4, // 高优先级
Low: 5, // 低优先级
Offscreen: 6, // 当前屏幕外的更新,优先级最低
}
new Plugin()
Complier
对象加载插件,执行Compiler.run
开始编译entry
找出所有入口文件entry
出发,调用配置的loader
,对模块进行转换,同时找出模块依赖的模块,一直递归,一直到找到所有依赖的模块loader
对所有模块进行了转换,得到了转换后的新内容以及依赖关系chunk
代码块,生成文件输出列表主要依赖webpack
, express
, websocket
express
启动本地服务,当浏览器访问的时候做出相应websocket
实现长连接webpack
监听源文件的变化
socket
向客户端推送当前编译的hash值websocket
监听到有文件改动推送过来的hash值,会和上一次进行对比
用法
module.exports
,ES6是export / export default
const x = require(‘xx‘)
,ES6是import xx from ‘xxx‘
普通函数
箭头函数
const fs = require(‘fs‘)
const path = require(‘path‘)
const mimeType = require(‘mime-types‘) // 文件类型
const filePath = path.resolve(‘./01.png‘)
const data = fs.readFileSync(filePath)
const buffer = new Buffer(data).toString(‘base64‘)
const base64 = `data:${mimeType.lookup(filePath)};base64,${buffer}`
console.log(base64)
优点
缺点:
js采用IEEE 754的双精度标准来进行计算,如果他碰到无法整除的小数的时候,就会取一个近似值,如果足够接近就觉得是那个值了
服务端渲染就是React通过Node.js转换成HTML再返回给浏览器,这个过程被称为“同构”,因为应用程序的大部分代码都可以在服务器和客户端上运行
与传统的SPA单页应用程序比,
hashchange
事件,根据hash来切换页面popstate
来改变页面内容,同时禁止a标签的默认事件。hash模式不需要后端配置,兼容性好,history模式需要后端配置才能使用
特点:
Mobx支持单向数据流,动作改变状态,状态更新改变视图
Mobx是同步运行的,有2个好处
主要利用一个autoRun
,这个函数可以让被我们用到的属性改变时候触发回调,而没被使用的属性发生改变则不会发生回调。
其实就是新建一个对象,然后覆盖对象的原型为传入的原型对象(类似继承)
function(constructor){
const F = function(){}
F.prototype = constructor
const res = new F()
return res
}
设置css3属性pointer-events: none
js的async加载还有defer加载都不阻塞页面渲染还有资源加载
如果css后面跟着内嵌js,则会出现阻塞情况,因为浏览器会维持css跟js顺序,样式表必须在嵌入的js之前加载完。
JS 代码在执行前,浏览器必须保证在 JS 之前的所有 CSS 样式都解析完成,不然不就乱套了,前面的 CSS 样式可能会覆盖 JS 文件中定义的元素样式,这是 CSS 阻塞后续 JS 执行的根本原因。
如果对象只有一层,则是深拷贝,如果是多层则是浅拷贝
const obj1 = {
a: 1,
b: {
c: 2
}
}
const obj2 = Object.assign({}, obj1)
console.log(obj1 == obj2)
console.log(obj1, obj2)
obj1.b = 2
console.log(obj1 == obj2)
console.log(obj1, obj2)
最常用的垃圾回收方式,运行的时候他会给变量标记,如果环境中的变量已经无法访问到这些变量,就清除
var m = 0,n = 19 // 把 m,n,add() 标记为进入环境。
add(m, n) // 把 a, b, c标记为进入环境。
console.log(n) // a,b,c标记为离开环境,等待垃圾回收。
function add(a, b) {
a++
var c = a + b
return c
}
例如函数的局部变量和参数,外部访问不了,则就会被清除
判断资源被引用的次数
var arr = [1, 2, 3, 4];
arr = [2, 4, 5]
例如一开始[1,2,3,4]
的引用次数为1,不会被清除,但是下面吧arr的引用换了,[1,2,3,4]
的引用数变成0,将被清除
function foo(arg) {
bar = "this is a hidden global variable";
}
这样子bar会声明在全局变量里面,不会被释放
计时器 如果使用setInterval
而没有关闭,则他会一直存在不会被释放
闭包 闭包会维持局部变量,就无法释放了
没有清理dom引用 dom元素的引用如果不清除,他就会一直存在
var arr = [1,2,3]
arr.length = 0 // 这样子优化可以不用新生成一个空数组
var t = {}
while(){
t.a = 1
}
t = null
尽量复用对象不要每次都生成,然后吼用完设置为null,垃圾回收
function t(){
}
while(){
t()
// var t = function(){} 这样子就不用循环创建很多函数了
}
以下为简略版
function myPromise(executor) {
this.state = ‘pending‘;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = value => {
if (this.state === ‘pending‘) {
this.state = ‘fulfilled‘;
this.value = value;
this.onResolvedCallbacks.forEach(fn => fn());
}
};
let reject = reason => {
if (this.state === ‘pending‘) {
this.state = ‘rejected‘;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
myPromise.prototype.then = function (onFulfilled, onRejected) {
// 声明返回的promise2
let promise2 = new Promise((resolve, reject) => {
if (this.state === ‘fulfilled‘) {
onFulfilled(this.value)
};
if (this.state === ‘rejected‘) {
onRejected(this.reason)
};
if (this.state === ‘pending‘) {
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value)
})
this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
});
// 返回promise,完成链式
return promise2;
}
new myPromise((resolve, reject) => {
console.log(‘1‘)
resolve(1)
console.log(‘2‘)
}).then((res) => {
console.log(res)
console.log(‘4‘)
return 2
})
const myPromiseAll = function (promiseList){
const result = []
return new Promise((resolve, reject) => {
let index = 0
next()
function next(){
promiseList[index].then((res) => {
result.push(res)
index++
if(index === promiseList.length){
resolve(result)
} else {
next()
}
})
}
})
}
正式跨域之前,会发起option请求,预检一下。检测发送的请求是否安全,同时发送请求地址,请求方法,服务器判断来源域名是否再许可名单内,请求方法支不支持,支持则允许请求
复杂请求才会发送预检,以下为复杂请求
为什么要进行预检? 复杂请求可能会对服务器产生副作用,数据修改等。所以要检测一下请求来源在不在许可名单上
function limitPromise(taskList, limit) {
return new Promise((resolve, reject) => {
let index = 0
let active = 0
let finish = 0
let result = []
function next() {
if (finish >= taskList.length) {
resolve(result)
return
}
while (active < limit && index < taskList.length) {
active++
let cur = index
taskList[index].then((res) => {
active--
finish++
result[cur] = res
next()
})
index++
}
}
next()
})
}
tree shaking是用于清除无用代码的方式,webpack3/4开始之后就自动集成
window.getComputedStyle(dom, null).getPropertyValue(‘display‘)
标准盒模型:大小 = content + margin + padding + border
怪异盒模型:大小 = width(content + padding + border) + margin
content-box: 默认,标准盒模型
border-box:border和padding算入width中
.container {
display: flex;
width: 200px;
height: 50px;
}
.item1 {
width: 50%;
background: red;
border: 10px solid black;
}
.item2 {
width: 50%;
background: yellow;
}
<div class="container">
<div class="item1">1</div>
<div class="item2">2</div>
</div>
效果如图

假设现在两个div一行放置,都是50%宽,假如我们再期中一个加上边框,这样另一个就会被挤压
这时候我们可以将box-sizing设置为border-box
.item1 {
width: 50%;
background: red;
border: 10px solid black;
box-sizing: border-box;
}

挤压的问题就没有啦
Object.defineProperty(obj, prop, descriptor)
MDN描述:
obj要在其上定义属性的对象。
prop要定义或修改的属性的名称。
descriptor将被定义或修改的属性描述符。
设置http的header:Connection: keep-alive
,告诉服务器,待会我还要请求,就可以避免再次三次握手了
async总是返回promise
async就是generator的语法糖
async对比generator的改进
如果没有缓存的情况下,请求头设置connection:keep-alive
则可以不重新握手
TCP的keepAlive是侧重于保持客户端和服务端的连接,会不定时发送心跳包验证有没有断开连接,如果没有这个机制的话,一方断开,另一方不知道,就会对服务器资源产生较大的影响
HTTP的keep-alive可以让服务器客户端保持这个连接,不需要重新tcp进行连接了
假如第一次发出连接请求,如果服务器端没有确认,客户端再进行一次请求,然后他们开始连接,在这之后,如果第一次只是延迟了,然后现在发送给客户端,如果没有三次握手的话,就直接开启连接,然而客户端并没有数据要传输,则服务端会一直等待客户端发数据
第一次第二次不可以,因为很容易被人攻击,而第三次握手已经进入establish状态了,已经确认双方的收发能力,可以传输
MSL是报文最大生存时间,客户端进入timewait的状态之后,需要等待2msl的时间,这样可以让最后发送的ack不会丢失,保证连接正常关闭,time_wait就是为了防止最后ack重发可能丢失的情况
http连接具有被动型,只能主动去向服务器请求看有没有新消息,一直轮询。
websocket是一个持久化的协议,连接了就不会自动断开。而且传递信息的时候只需要传递一点点头信息即可。一次握手,持久连接
这个方法只会遍历自身属性,不会从原型链上遍历,可以用来判断属性是否在对象上或者是原型链继承来的
const obj = new Object()
obj.num = 1
Object.prototype.fn = function(){
console.log(2)
}
obj.hasOwnProperty(num) // true
obj.hasOwnProperty(fn) // false
address
,用来指定域名对应的ip地址使用JWT
服务器就不需要保存session数据了,更容易扩展
由三部分组成,中间以.
分隔
{
"alg": "HS256",
"typ": "JWT"
}
alg代表算法,typ代表类型,最后用base64编码转换一下变成字符串
除了官方给的字段,我们还可以自定义字段,例如:
{
"name": "bb",
"age" : 21
}
这是一个签名,用于防止前面的数据被篡改
我们需要自定义一个 密钥 ,不能给用户知道,然后生成签名
Authorization
DOCTYPE是用来声明文档类型和DTD规范的。主要用于验证文件合法性。如果要提高浏览器的兼容性,需要设置一下<!DOCTYPE>
koa是基于express来开发出来的,但是他更简洁,自由度更高,十分轻量。功能都通过插件来实现,这种拔插式的架构设计模式,很符合unix哲学
koa2不在使用generator,而是采用了async/await
express:
koa:
这个是入口文件,继承了events可以执行事件监听还有触发事件。
这个就是koa的应用上下文ctx了,可以通过他来访问请求头啊设置响应头等等
对原生的req,res等做一下封装,可以在这里取到headers设置headers/body等
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(6);
});
app.use(async (ctx, next) => {
console.log(2);
await next();
console.log(5);
});
app.use(async (ctx, next) => {
console.log(3);
ctx.body = "hello world";
console.log(4);
});
输出 1 2 3 4 5 6
意思就是说,先从上到下执行完await next()
之前的内容,然后再从下到上执行await next()
之后的内容
koa通过use函数,吧中间件push到队列中,洋葱模型可以让所有中间件依次执行,每执行完一次,就通过next()
传递ctx参数
next()
就相当于一个接力棒,将参数等传递给下一个中间件。只需要执行一下next就可以将之后的中间件都执行了!
不同于express框架,修改响应需要等请求结束之后,但用koa就可以将修改响应的代码放到next后面就可以了
前期处理 -> 交给并等待其他中间件处理 -> 后期处理
babel是一个js的编译器,用来将代码转换成兼容浏览器运行的代码
核心就是AST抽象语法树。
版本号分为X.Y.Z三位
1.x.2
,就可以是1.4.2
, 1.8.2
主要依赖的是history
库。
顶层router监听history,history发生变化的时候,router调用setState把location向下传递。设置到RouterContext。router根据RouterContext决定是否显示。
Link标签禁用a标签的默认事件,调用history.push方法来改变url。
symbol函数会返回symbol类型的值,他不支持new。
cookie的属性SameSite
有三个值
secure
才能生效需要用babel进行转换,转换成commonJS规范
例如export default
会被转换成exports.default
// babel编译前
export default {}
// babel编译后
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = {};
/*
* exports
* {default: {} , __esModule: true}
*/
使用_interopRequireDefault
来处理一遍,为了兼容和区分commonjs和es6
// babel编译前
import Test, {msg, fn} from ‘test‘
// babel编译后
‘use strict‘;
var _test = require(‘test‘);
var _test2 = _interopRequireDefault(_test);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
webpack中会为
import
和export
添加一个__esModule: true
的标识,来标记这是es6的东东
document.getElementById(‘test‘).addEventListener(‘click‘, function (e) {
console.log(e.target)
console.log(e.currentTarget)
})
<div id="test">
<button>1</button>
</div>
预解析某域名
<link rel="dns-prefetch" href="//img.alicdn.com">
强制开启HTTPS下的DNS预解析
<meta http-equiv="x-dns-prefetch-control" content="on">
react-loadable
主要利用import()
来返回一个promise的性质,进行loading的异步操作。require.ensure()
来实现按需加载require.ensure(dependencies,callback,errorCallback,chunkName)
他会返回一个promise
,先是判断dependencies
是否被加载过,加载过则获取缓存值。没有的话就生成一个promise
,然后缓存起来。接着生成一个script
标签,填充好信息放到html文件上,就完成了按需加载了。
require.ensure可以自定义文件名
利用manifest
属性,当浏览器发现头部有manifest
属性,就会请求manifest文件,如果是第一次,则根据文件内容下载响应的资源。如果已经离线缓存过了,则直接使用离线的资源加载。
攻击者通过iframe嵌套的方式,将iframe设为透明,诱导用户点击。
可以通过http头设置X-FRAME-OPTIONS
来防御iframe嵌套的点击劫持攻击
function Subject() {
this.state = 0
this.observers = []
}
Subject.prototype.attach = function (observer) {
this.observers.push(observer)
}
Subject.prototype.getState = function () {
return this.state
}
Subject.prototype.setState = function (state) {
this.state = state
this.noticefyAllObservers()
}
Subject.prototype.noticefyAllObservers = function () {
this.observers.forEach(observer => {
observer.update()
})
}
function Observer(name, subject) {
this.name = name
this.subject = subject
this.subject.attach(this)
}
Observer.prototype.update = function () {
console.log(this.name, this.subject.getState())
}
const subject = new Subject()
const o1 = new Observer(‘o1‘, subject)
const o2 = new Observer(‘o2‘, subject)
const o3 = new Observer(‘o3‘, subject)
subject.setState(1)
subject.setState(2)
subject.setState(3)
function Event() {
this.events = {}
}
Event.prototype.on = function (event, fn) {
if (!this.events[event]) {
this.events[event] = []
}
this.events[event].push(fn)
}
Event.prototype.emit = function (event) {
const args = [...arguments].splice(1)
this.events[event].forEach(fn => {
fn.apply(this, args)
})
}
Event.prototype.off = function (event, fn) {
const currentEvent = this.events[event]
this.events[event] = currentEvent.filter(item => item !== fn)
}
const publisher = new Event()
const fn1 = function (e) {
console.log(‘a1‘)
console.log(e)
}
const fn2 = function (e) {
console.log(‘a2‘)
console.log(e)
}
publisher.on(‘a‘, fn1)
publisher.on(‘a‘, fn2)
publisher.emit(‘a‘, ‘aowu‘)
publisher.off(‘a‘, fn2)
publisher.emit(‘a‘, ‘aowu‘)
react的合成事件调用之后全都会被重用,所有属性都无效了,如果要异步访问的话,就要用event.persist()
,这样就会从事件池里面删除,允许保留事件的引用
小程序下,native方式性能比web方式好
基本类型不是对象,但是他们又有一些方法,为什么呢?
因为后台会对基本类型进行包装,例如字符串、整数、布尔值会首先利用构造函数创建实例,使用完之后就销毁
let s1 = ‘hello‘
let s2 = s1.substring(2)
// ↓ 后台包装
let s1 = new String(‘hello‘) // 包装
let s2 = s1.substring(2) // 可以调用方法
s1 = null // 销毁
class A {
constructor (x) {
let _x = x
this.showX = function () {
return _x
}
}
}
let a = new A(1)
// 无法访问
a._x // undefined
// 可以访问
a.showX() // 1
class A {
constructor(x) {
// 定义symbol
const _x = Symbol(‘x‘)
// 利用symbol声明私有变量
this[_x] = x
}
showX() {
return this[_x]
}
}
let a = new A(1)
// 1. 第一种方式
a[_x] // 报错 Uncaught ReferenceError: _x is not defined
// 2. 第二种方式
// 自行定义一个相同的Symbol
const x = Symbol(‘x‘)
a[x] // 无法访问,undefined
// 3. 第三种方式,可以访问(正解)
a.showX() //1
interface
自动聚合,也可以跟同名的class
自动聚合object
, function
, class
类型object
, function
, class
类型JavaScript是一个解释型
语言,跟编译型
语言不同,js是一边执行一边解析的。为了提高性能引入了java虚拟机和C++编译器的一些东西
源代码 -> 抽象语法树 -> 字节码 -> JIT -> 本地代码
相比其他的javascript引擎(转换成字节码或解释执行),V8引擎将代码直接编译成原生机器码,使用了内联缓存提高性能。
工作流程:源代码先被解析成抽象语法树然后用JIT编译器直接从抽象语法树生成代码,提高代码执行速度(不用转换成字节吗了)
unicode:世界上所有符号的编码集 utf8、utf16:是unicode的字符集,不同的编码实现
原文:https://www.cnblogs.com/yunzhongjunlang/p/14171448.html