后台写html阶段:table标签布局;美工阶段:div+css布局不够语义化;前端阶段:选择正确的标签描述正确的内容。
尽可能少的使用无语义的标签div和span
在语义化不明显时,尽量使用p标签,因为p在默认情况下有上下间距
需要强调的文本,可以包含在strong或em标签中,strong默认样式是加粗(不要用b),em是斜体(不要用i)
什么是语义化?就是用合理、正确的标签来展示内容,比如h1~h6定义标题。
、、
、
、
、
、
、
、
、
:获取canvas、设置2d上下文、设置画笔颜色、设置画笔范围
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>响应式布局title>
<style>
@media screen and (min-width: 992px){
/*pc端*/
}
@media screen and (min-width: 768px) and (max-width: 992px){
/*pad端*/
}
@media screen and (max-width: 768px){
/*移动端*/
}
style>
head>
<body>
body>
html>
使用viewport meta设置视口宽度和默认缩放比(针对移动端浏览器)
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
name="viewport"说明此meta标签定义视口的属性
width=device-width宽度等于设备宽度
initial-scale=1.0初始化缩放比为1
maximum-scale=3允许用户放大至3倍
minimum-scale=0.5允许用户缩小至0.5倍
user-scalable=no禁止用户缩放
<meta name='viewport' content='width=device-width,initial-sacle=1,user-scalable=no'>
让移动端视口是宽度等于设备分辨率宽度
文档声明:告诉浏览器我们使用的版本号为HTML5的版本,它不是HTML标签。
声明文档的解析类型(document.compatMode),避免浏览器的怪异模式。
document.compatMode
:
BackCompat
:怪异模式,浏览器使用自己的怪异模式解析渲染页面。CSS1Compat
:标准模式,浏览器使用W3C的标准解析渲染页面。这个属性会被浏览器识别并使用,但是如果你的页面没有DOCTYPE
的声明,那么compatMode
默认就是BackCompat
,如果你的页面添加了那么,那么就等同于开启了标准模式,那么浏览器就得老老实实的按照W3C的标准解析渲染页面,这样一来,你的页面在所有的浏览器里显示的就都是一个样子了。
深入script的执行顺序
:(优先生效async)HTML规范 里有提到,当出现
async
时, 都会按照
async
的方式去执行;不存在 async
且有 defer
时,才会按照 defer
的方式执行。同时出现的意义在于,有些浏览器可能尚未支持 async
,但是已经支持 defer
了,这时就可以降级到 defer
模式,不阻塞后续解析,体验更好。其实主要就是为了优化 IE 浏览器下的体验。从 caniuse 可知:IE >= 10 才开始支持 async
,而 IE 6~9 就已经支持 defer
了。
:加载 ES module 脚本时,默认采用的是defer的方式。即:浏览器加载 module 入口脚本,并继续解析和渲染后面的 HTML;分析静态依赖,并并发加载所有依赖的模块;等待 DOM 加载完成;从入口开始执行代码。
动态插入:分两种情况:内联脚本;外链脚本
//内联脚本:
console.log(1);
const script = document.createElement('script');
script.textContent = 'console.log(3)';
script.async = true; // 这一行是无效的
document.body.append(script);
console.log(2);
//结果:
//1
//3
//2
//外链脚本:
//script.async = true;
console.log(1);
const script = document.createElement('script');
script.src = '2s.js'; // 加载需 2s,内容是 `console.log('2s')`
script.async = true; // 这一行可以省略
document.body.append(script);
const script = document.createElement('script');
script.src = '1s.js'; // 加载需 1s,内容是 `console.log('1s')`
script.async = true; // 这一行可以省略
document.body.append(script);
console.log(2);
//结果:
//1
//2
//1s
//2s
//script.async = false;
console.log(1);
const script = document.createElement('script');
script.src = '2s.js'; // 加载需 2s,内容是 `console.log('2s')`
script.async = false;
document.body.append(script);
const script = document.createElement('script');
script.src = '1s.js'; // 加载需 1s,内容是 `console.log('1s')`
script.async = false;
document.body.append(script);
console.log(2);
//结果:
//1
//2
//2s
//1s
常见的面试题:从用户在浏览器地址栏输入网址,到看到页面,中间都发生了什么?
涉及知识点:TCP协议的三次握手四次挥手、HTTP报文、状态码、304缓存、DNS缓存、性能优化、重绘回流、AST语法解析树、DOM树、Layout树、Layer树、requestAnimationFrame、同步异步编程、闭包作用域、堆栈内存、面向对象、微任务和宏任务、事件循环、AJAX同源请求和跨域请求解决方案、Cookie和Session以及Token
进程Process:进程通俗讲就是我们开的每一个程序
线程Thread:每个程序(进程)可能会做很多事情(子任务),就是线程
栈内存Stack:运行代码的环境(存放指针,这些指针指向堆内存)
CSS样式表
浏览器在加载网页的过程中,先下载HTML并开始解析。如果发现外部CSS资源链接,就发送下载请求。浏览器在下载CSS资源的同时,解析HTML文件。
在应用样式的时候,不会更改DOM树,而是生成CSS树,因此解析样式表的时候也不会停止文档解析。
JS脚本
解析器遇到标记时,立即解析并执行脚本。一旦发现有脚本文件的引用,就必须等待脚本文件下载完。这时,文档的解析会暂停,并且等到脚本执行完再继续。所以,脚本文件的下载和执行,会阻断其他资源文件的下载。JS会阻塞解析HTML。所以建议
一般放最后面,或者使用defer或者async属性异步加载。
浏览器地址栏输入字符,浏览器进程的UI线程会捕捉输入内容,如果访问的是一个网址,则UI线程会启动一个网络线程,来请求DNS进行域名解析,接着使用IP地址进行TCP连接服务器,再使用HTTP请求数据和响应数据,浏览器主线程开辟执行上下栈,解析html上下文。
主线程:dom tree&&css tree—结合—》layout tree(layout过程)—》update layertree(形成层叠上下文的过程)–》paint(计算层叠上下文,形成表记录在哪绘制,啥时绘制)–》composite(绘制我们记录的表)
合成器线程----》整个过程栅格化数据,把画面分块,(可视化区域)再组合形成Frame—交给—》GPU线程去渲染。
之后的话,重排和重绘以及js执行或者用户行为都会占用主线程。
使用transform属性不会导致重绘和重排,因为是运行在合成器线程和栅格化线程中,硬件加速
JS同步任务时间执行过长,会抢占主线程,变卡顿。
当页面或者动画以每秒60帧的刷新率时,才不会让用户感觉页面卡顿。
如果每一帧在运行动画的时候,还有大量JS任务需要执行。(布局、绘制、JS执行都是在主线程中运行的)当在一帧的时间内布局和绘制结束后,还有剩余的时间JS就会拿到主线程的使用权,如果JS的执行时间过长,就会导致下一帧开始时JS依然没有执行结束(依然占用这主线程,也就是说JS执行可能会阻塞主线程),导致下一帧的动画没有按时渲染,就会出现页面的卡顿。
此时我们就可以使用requestAnimationFrame
这个API来优化。
requestAnimationFrame
这个方法会在每一帧开始时调用执行,在每一帧结束时回调(requestIdleCallback):帮我们把JS运行任务分成一些更小的任务块(分到每一帧结束的位置),在每一帧时间用完前暂停JS运行任务,归还主线程,按时渲染下一帧
DOM标准规定事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段
addEventListener是DOM2级事件新增的指定事件处理程序的操作,这个方法接收3个参数:要处理的事件名,作为事件处理程序的函数和一个布尔值,最后这个布尔值如果是true,表示在捕获阶段调用事件处理程序;默认是false,表示在冒泡阶段调用事件处理程序。
element.addEventListener(event, callback, useCapture)
通过e.stopPropagation()
阻止事件继续传递。
通过e.preventDefault()
阻止标签的默认行为。
e.target
的值是用户行为触发事件的准确target目标
e.currentTarget
的值是绑定事件监听函数的目标
不是所有的事件都能冒泡,例如:blur、focus、load、unload
<body>
<div>
<button>按钮button>
div>
body>
<script type="text/javascript">
document.querySelector('div').addEventListener('click', (e) => console.log('冒泡div',e.target,e.currentTarget), false)
document.querySelector('div').addEventListener('click', (e) => console.log('捕获div',e.target,e.currentTarget), true)
document.querySelector('button').addEventListener('click', (e) => console.log('冒泡button',e.target,e.currentTarget),false)
document.querySelector('button').addEventListener('click', (e) => console.log('捕获button',e.target,e.currentTarget), true)
document.addEventListener('click', (e) => console.log('冒泡domcument',e.target,e.currentTarget), false)
document.addEventListener('click', (e) => console.log('捕获domcument',e.target,e.currentTarget), true)
script>
此时点击按钮,打印结果如下:
捕获domcument <button>按钮button> #document
捕获div <button>按钮button> <div>…div>
冒泡button <button>按钮button> <button>按钮button>
捕获button <button>按钮button> <button>按钮button>
冒泡div <button>按钮button> <div>…div>
冒泡domcument <button>按钮button> #document
事件委托本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理。
e.target
判断。<body>
<ul>
<li>li>
<li>li>
<li>li>
<li>li>
ul>
body>
<script>
const ul = document.querySelector('ul')
ul.addEventListener('click', (e) => {
console.log(e.target)
}, false)
//就不需要每一个子元素都获取然后去指定事件监听
script>
1】直接在标签属性绑定事件
return false;
进行阻止默认事件。 //直接在DOM里绑定事件,需要写成立即执行的格式
2】通过获取DOM绑定事件
return false;
进行阻止默认事件。也可以使用e.preventDefault()
3】添加事件监听函数
e.preventDefault()
来阻止默认事件。
一、图片转换成base64格式的优缺点
(1)base64格式的图片是文本格式,占用内存小,转换后的大小比例大概为1/3,降低了资源服务器的消耗;
(2)网页中使用base64格式的图片时,不用再请求服务器调用图片资源,减少了服务器访问次数。
(1)base64格式的文本内容较多,存储在数据库中增大了数据库服务器的压力;
(2)网页加载图片虽然不用访问服务器了,但因为base64格式的内容太多,所以加载网页的速度会降低,可能会影响用户的体验。
(3)base64无法缓存,要缓存只能缓存包含base64的文件,比如js或者css,这比直接缓存图片要差很多,而且一般HTML改动比较频繁,所以等同于得不到缓存效益。
iframe 的创建比其它包括 scripts 和 css 的 DOM 元素的创建慢了 1-2 个数量级,使用 iframe 的页面一般不会包含太多 iframe,所以创建 DOM 节点所花费的时间不会占很大的比重。但带来一些其它的问题:onload 事件以及连接池(connection pool)
及时触发 window 的 onload 事件是非常重要的。onload 事件触发使浏览器的 “忙” 指示器停止,告诉用户当前网页已经加载完毕。当 onload 事件加载延迟后,它给用户的感觉就是这个网页非常慢。
window 的 onload 事件需要在所有 iframe 加载完毕后(包含里面的元素)才会触发。在 Safari 和 Chrome 里,通过 JavaScript 动态设置 iframe 的 SRC 可以避免这种阻塞情况
浏览器只能开少量的连接到 web 服务器。比较老的浏览器,包含 Internet Explorer 6 & 7 和 Firefox 2,只能对一个域名(hostname)同时打开两个连接。这个数量的限制在新版本的浏览器中有所提高。Safari 3+ 和 Opera 9+ 可同时对一个域名打开 4 个连接,Chrome 1+, IE 8 以及 Firefox 3 可以同时打开 6 个
绝大部分浏览器,主页面和其中的 iframe 是共享这些连接的。这意味着 iframe 在加载资源时可能用光了所有的可用连接,从而阻塞了主页面资源的加载。如果 iframe 中的内容比主页面的内容更重要,这当然是很好的。但通常情况下,iframe 里的内容是没有主页面的内容重要的。这时 iframe 中用光了可用的连接就是不值得的了。一种解决办法是,在主页面上重要的元素加载完毕后,再动态设置 iframe 的 SRC。
搜索引擎的检索程序无法解读 iframe。另外,iframe 本身不是动态语言,样式和脚本都需要额外导入。综上,iframe 应谨慎使用。
可以在table标签中使用
(行)、
(列)
table标签的属性有:
<table border="1">
<tr>
<th>Monthth>
<th>Savingsth>
tr>
<tr>
<td>Januarytd>
<td>$100td>
tr>
table>
td标签的属性有:
href属性:去往的超链接路径(必填属性)
target属性:
_self
:它是a标签的默认值,它使得目标文档载入并显示在相同的框架或者窗口中作为源文档。这个目标是多余且不必要的,除非和文档标题 标签中的 target 属性一起使用。_blank
:浏览器总在一个新打开、未命名的窗口中载入目标文档。_parent
:这个目标使得文档载入父窗口或者包含来超链接引用的框架的框架集。如果这个引用是在窗口或者在顶级框架中,那么它与目标 _self 等效。_top
:(一般也是在iframe页面中使用_top
)这个目标使得文档载入包含这个超链接的窗口,用 _top 目标将会清除所有被包含的框架并将文档载入整个浏览器窗口。点击查看效果
display:flex
flex-direction
:决定主轴的方向(即项目的排列方向)。默认值row
row | row-reverse | column | column-reverse;
flex-wrap
:如果一条轴线排不下,如何换行。默认值nowrap
nowrap | wrap | wrap-reverse;
flex-flow
:flex-direction
属性和flex-wrap
属性的简写形式,默认值为row nowrapjustify-content
:定义项目在主轴上的对齐方式。默认值flex-start左对齐
flex-start | flex-end | center | space-between | space-around | space-evenly;
align-items
:定义项目在交叉轴上如何对齐。默认值stretch(如果项目未设置高度或设为auto,将占满整个容器的高度。)
flex-start | flex-end | center | baseline | stretch;
align-content
:定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。默认值stretch
flex-start | flex-end | center | space-between | space-around | stretch;
flex
:flex-grow
, flex-shrink
和 flex-basis
的简写,默认值为0 1 auto。后两个属性可选。该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。
flex-grow
:定义项目的放大比例,默认为0(即如果存在剩余空间,也不放大)
flex-grow
属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow
属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。flex-shrink
:定义了项目的缩小比例,默认为1(即如果空间不足,该项目将缩小)
flex-shrink
属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink
属性为0,其他项目都为1,则空间不足时,前者不缩小。flex-basis
:定义了在分配多余空间之前,项目占据的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。BFC
:Block Formatting Context
(块级格式化上下文)
只需要记住,BFC
的目的就是:形成一个完全独立的空间,让空间中的子元素不会影响到外面的布局。
我们通过为元素设置一些CSS
属性,就能触发这一空间。以下是四种最为常见的触发规则:
float
不为none
overflow
的值不为visible
position
不为relative
和static
display
的值为table-cell
或inline-block
BFC的布局规则:
普通文档流布局规则
1.浮动的元素是不会被父级计算高度
2.非浮动元素会覆盖浮动元素的位置
3.margin会传递给父级
4.两个相邻元素上下margin会重叠
BFC布局规则
1.浮动的元素会被父级计算高度(父级触发了BFC)
2.非浮动元素不会覆盖浮动元素位置(非浮动元素触发了BFC)
3.margin不会传递给父级(父级触发了BFC)
4.两个相邻元素上下margin会重叠(给其中一个元素增加一个父级,然后让他的父级触发BFC)
浮动的元素脱离文档流,之前占用空间会释放,影响正常文档流的元素位置。在受到浮动影响其位置的元素上面使用clear:both
来使浮动元素回归文档流。
①在浮动元素所在容器内的最后添加空标签clear:both
②在浮动元素所在容器上面使用::after
伪元素content:'';display:block;clear:both
③在浮动元素所在容器上使用overflow:auto/hidden/scroll
,创建BFC包住浮动的元素。(因为容器没有设置高度,BFC布局中,容器会包含住所有内部元素)
::before&::after
CSS中,::before
创建一个伪元素,其将成为匹配选中的元素的第一个子元素。常通过content
属性来为一个元素添加修饰性的内容。此元素默认为行内元素。由::before
和::after
生成的伪元素包含在元素格式框内(相当于就是此元素的子元素)。
Inline Formatting Contexts,也就是“内联格式化上下文”。
某些元素的渲染顺序是由其 z-index
的值影响的。这是因为这些元素具有能够使他们形成一个层叠上下文的特殊属性。
形成层叠上下文的条件:
)position
值为 absolute
或 relative
且z-index
值不为 auto
的元素position
值为 fixed
或 sticky
(粘滞定位)的元素flexbox
)容器的子元素,且z-index
值不为auto
grid
)容器的子元素,且z-index
值不为auto
opacity
属性值小于 1
的元素(在最下面的层叠上下文)none
的元素:(在最下面的层叠上下文)
transform
filter
两大原则:谁大谁上,后来居上
bottom装饰---->布局—>内容top
①标准盒子模型
box-sizing: content-box;
默认
标准盒模型中width指的是内容区域content的宽度;height指的是内容区域content的高度。
标准盒模型下盒子的大小 = content + border + padding + margin
②怪异盒子模型(更方便)
box-sizing: border-box;
怪异盒模型中的width指的是内容、边框、内边距总的宽度(content + border + padding);height指的是内容、边框、内边距总的高度。
怪异盒模型下盒子的大小=width(content + border + padding) + margin
以下都是基于标准盒子模型
clientWidth = width + 左右padding
offsetWidth = width + 左右padding + 左右boder
一个是 column-count 属性,是分为多少列。
一个是 column-gap 属性,是设置列与列之间的距离
瀑布流布局的特点是等宽不等高。(使用一个定长度数组来存)
父元素设置为相对定位,图片所在元素设置为绝对定位。然后通过设置top
值和left
值定位每个元素。
img{
position:absolute;
left:上一排中最小索引值*图片固定宽度 px;
top:最小图片的高度,叠加;
}
用一个数组来存放每一排的图片高度,假如是一排四列图片[100,80,120,150],然后不断叠加
用获取屏幕的宽度/每张图片宽度=列数。
flex-flow:column wrap
纵向排列,换行
缺点:需要固定外部容器的高度。
越具体优先级越高
一样选择器,代码顺序写在后面的覆盖前面的
important!
最高、少用(优先级比行内样式还要高)
行内样式*1000
ID选择器数量 * 100
类、属性、伪类选择器数量 * 10
元素、伪元素选择器数量 * 1
position:sticky
是一个新的css3属性,它的表现类似position:relative
和position:fixed
的合体,当目标区域在屏幕中可见时,它的行为就像position:relative
;当页面滚动超出目标区域时,它的表现就像position:fixed
,它会固定在目标位置。(注意:top和left优先级分别大于bottom和right)
原生实现:
<style>
.header {
width: 100%;
background: #f6d565;
padding: 25px 0;
}
.sticky {
position: fixed;
top: 0;
}
style>
<body>
<div style='height: 200px;'>div>
<div class="header">div>
<div style='height: 1000px;'>div>
body>
<script>
var header = document.querySelector('.header');
var originOffsetY = header.offsetTop;//获取该元素初始的距父元素顶部的高度
function onScroll(e) {
window.scrollY >= originOffsetY//屏幕顶部到页面顶部的距离大于获取的原始值
? header.classList.add('sticky')
: header.classList.remove('sticky')
}
document.addEventListener('scroll', onScroll)
script>
Asynchronous JavaScript and XML(异步js和xml)
(严格依赖于XMLHttpRequest对象)
xhr对象的属性:
xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.open(提交方式get/post,服务器地址url,true);
xmlHttpRequest.setRequestHeader("Content-Type","application/x-www-form-urlencoded");//设置请求头
xmlHttpRequest.send("mobile="+mobile);//post请求key=value的形式,get请求传null值
xmlHttpRequest.onreadystatechange=function(){
if(xmlHttpRequest.readyState ==4 && xmlHttpRequest.status ==200){
var data = xmlHttpRequest.responseText;
alert(data)
}
}
注意:content-Type:application/x-www-form-urlencoded
和content-Type:multipart/form-data
只是post请求在请求体body中数据的两种编码方式。一种支持上传文件,一种不支持。
post参数格式:
Axios的优点:
umi-request:
umi-request 是基于 fetch 的封装,兼具 fetch 和 axios 的特点。
基于回调,解决回调地狱
//Promise基本使用
new Promise((resolve, reject) => {
setTimeout(() => {
//成功的时候调用resolve,传入结果
//resolve('hello')
//失败的时候调用reject,传入结果
reject('error message')
}, 1000)
}).then((data) => {
//then里面处理成功,此时打印hello
console.log(data)
}).catch((err)=>{
//catch里面处理失败,此时打印error message
console.log(err)
})
三种状态:pending(等待态)、fulfilled(满足态)、rejected(拒绝态)
链式调用:.then()
中返回一个新promise对象来形成链式调用。
.then()
不返回值,即默认return undefined
,作为回调参数传递给下一个.then(d=>d)
.then()
返回普通值,将会把这个普通值作为回调参数传递给下一次.then(d=>d)
触发执行.catch()失败:①返回一个reject()
的Promise对象,②抛出异常错误
触发执行.then()成功:①返回一个resolve()
的Promise对象,②返回一个普通值,这个普通值直接传递给下一个then()
,③不显式返回值,将undefined
传给下一个then()
常用API:
Promise.resolve()
Promise.reject()
Promise.all()
[p1,p2,...,pn]
,都会调用Promise.resolve()
来转成Promise对象类型。Promise.race()
自身返回的也是Promise对象。Promise.all = function (arr) {
if(!Array.isArray(promises)){
throw new TypeError('You must pass array')
}
let res = []
return new Promise((resolve, reject) => {
let index = 0
for (let i in arr) {
Promise.resolve(arr[i]).then(data => {
res[i] = data
index++
if (index == arr.length) {//必须在Promise异步中判断
resolve(res)
}
})
}
})
}
Promise.all([1, 2, new Promise((r) => { setTimeout(() => { r(3) }) }), 4]).then(data => { console.log(data) })
//[ 1, 2, 3, 4 ]
Promise.race()
Promise.all()
一致Promise.resolve()
转为Promise对象。Promise.race()
自身返回的也是Promise对象。p1
、p2
、…、pn
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数(then
or catch
)。const p = Promise.race([p1, p2,..., pn]);
p.then(data=>data)//data为率先改变状态的返回值。
Promise.race = function (arr) {
if (!Array.isArray(arr)) {
throw new TypeError('You must pass array')
}
return new Promise((resolve, reject) => {
for (let i in arr) {
Promise.resolve(arr[i])
.then(data => {
resolve(data)
})
.catch(err => {
reject(err)
})
}
})
}
Promise.race([
new Promise((r, j) => { setTimeout(() => { r(1) }, 2000) }),
new Promise((r, j) => { setTimeout(() => { j(2) }, 1000) }),
new Promise((r) => { setTimeout(() => { r(3) }, 1500) })
]).then(data => console.log(data)).catch(err => console.log(err))
Promise.prototype.finally()
Promise.finally()
可以看似于特殊的Promise.then()
方法,也会返回一个 Promise 对象用于传递上一个Promise的结果。class myPromise {
constructor(callback) {
this.status = "pending";
this.reject = undefined;
this.resolve = undefined;
callback(
(resolve) => {
if (this.status === "pending") {
this.resolve = resolve;
this.status = "resolved";
}
},
(reject) => {
if (this.status === "pending") {
this.status = "rejected";
this.reject = reject;
}
}
);
}
then(success, fail) {
//返回一个新promise
return new myPromise((resolve, reject) => {
if (this.status === "resolved") {
//将回调函数的return的值进行resolve处理
resolve(success(this.resolve));
} else if (this.status === "rejected") {
reject(fail(this.reject));
}
});
}
}
//测试
const promise = new myPromise((resolve, reject) => {
resolve(111);
reject(123);
})
.then(
(res) => {
console.log(res);
return 333;
},
(res) => {
console.log(res);
}
)
.then((res) => {
console.log(res);
});
console.log(promise);
//111
//333
//myPromise { status: 'resolved', reject: undefined, resolve: undefined }
//必须使用计数(当成功的次数和传入数组长度一样时返回)
Promise.newAll = (array) => {
return new Promise((resolve, reject) => {
const result = new Array(array.length);
let count = 0;
array.forEach((item, index) => {
return Promise.resolve(item).then(
(res) => {
result[index] = res;
if (++count === array.length) {
resolve(result);
}
},
(err) => {
reject(err);
}
);
});
});
};
//测试
Promise.newAll([1, 2, new Promise((r) => { setTimeout(() => { r(3) },2000) }), 4]).then(data => { console.log(data) })
//[1,2,3,4]
Promise.newAll([1, 2, new Promise((r,e) => { setTimeout(() => { e(3) },2000) }), 4]).then(data => { console.log(data) })
//error!!!
//先resolve之后Promise状态已经是成功态,不会执行resolve和reject
Promise.newRace = (array) => {
return new Promise((resolve, reject) => {
array.forEach((item) => {
Promise.resolve(item).then(
(res) => {
resolve(res);
},
(err) => {
reject(err);
}
);
});
});
};
//测试:
Promise.newRace([1, 2, new Promise((r) => { setTimeout(() => { r(3) },2000) }), 4]).then(data => { console.log(data) })
//1
//exited with code=0 in 2.114 seconds
比如Map、Set、Array数组、String、generator函数、arrayLike类似数组的对象(但是必须要添加迭代的功能)、TypedArray等(需要非常注意的是对象是没有迭代器接口,需要手动添加迭代器功能,或者转换成数组来进行迭代)
[...It]
或Array.from(It)
强转成数组类型。Array.prototype.slice.call(arrayLike)
只能把类数组对象转为数组,不能把迭代器对象转成数组上面提到的数组、Map、Set等都是这样使用[Symbol.iterator]
包装的迭代器
let string = '我是雷浩'
let it1= string[Symbol.iterator]()
let it2= string[Symbol.iterator]()
for (let i of string) {//实际上也是调用了string中的Symbol.iterator方法
console.log(i)//依次打印:我 是 雷 浩
}
for (let i of it1) {
console.log(i)//依次打印:我 是 雷 浩
}
console.log(it2.next().value)//我
console.log(it2.next().value)//是
console.log(it2.next().value)//雷
console.log(it2.next().value)//浩
实现一个对象迭代器:(使用计数器)
var obj = {
name: '雷浩',
age: 21,
address: '四川成都',
[Symbol.iterator]: function () {
let self = this;
let key = Object.keys(self)
let index = 0
return {
next() {
return {
value: obj[key[index]] ? obj[key[index]] : undefined,
done: index++ < key.length ? false : true
}
}
}
}
};
let it = obj[Symbol.iterator]()
console.log(it.next())//{ value: '雷浩', done: false }
console.log(it.next())//{ value: 21, done: false }
console.log(it.next())//{ value: '四川成都', done: false }
console.log(it.next())//{ value: undefined, done: true }
for (let i of obj) {
console.log(i)//依次打印:雷浩 21 四川成都
}
Generator包装一个迭代器:
let obj = {
name: "XX",
age: 20,
job: 'teacher',
* [Symbol.iterator] () {
const self = this;
const keys = Object.keys(self);
for (let index = 0;index < keys.length; index++) {
yield self[keys[index]];
}
}
};
generator(生成器)执行之后返回生产一个 Iterator(迭代器)
yield
关键字必须放在带*
指针的生成器函数里面用。
Iterator.next(param)
接收一个参数,传入的实参会传递给上一次yield的返回值。
在generator生产器函数中支持yield* Iterator
的用法。
function* generator() {
yield 1
yield* 'LH'//yield后面带*号,该值会被继续迭代
yield 2
yield* 'LLY'//字符串是具备迭代器接口的
}
for (let i of generator()) {//执行generator函数返回一个迭代器
console.log(i)
}
//依次打印:1 L H 2 L L Y
//一遇到yeild然后执行yeild后面的值,执行完毕就暂停
function* read(){
let a = yield 'hello';//先执行yield之后立即暂停,所以并没有给a赋值
console.log(a);//undefined
let b = yield 'world';
console.log(b);//undefined
}
let it = read()
console.log(it.next())//{ value: 'hello', done: false } undefined
console.log(it.next())//{ value: 'world', done: false } undefined
console.log(it.next())//{ value: undefined, done: true }
使用Generator yield实现async await:
async await ==语法糖=> generator yield +co(大神tj写的一个nodejs库)
function* event() {
const one = yield new Promise((resolve, reject) => setTimeout(() => { resolve(1) }))
const two = yield one+1
const three = yield new Promise((resolve, reject) => setTimeout(() => { resolve(two+1) }))
}
let it = event()
let { value, done } = it.next()
Promise.resolve(value).then(data => {
console.log(data)//1
let { value, done } = it.next(data)
Promise.resolve(value).then(data => {
console.log(data)//2
let { value, done } = it.next(data)
Promise.resolve(value).then(data => {
console.log(data)//3
})
})
})
Async Generator Functions in JavaScript
总结:记住使用异步迭代器调用next函数之后获得的结果是以Promise的形式进行返回。
异步生成器函数是特别的,因为你可以在函数中同时使用await 和 yield, 异步生成器函数不同于异步函数和生成器函数,因为它不会返回promise或者iterator,而是返回异步迭代器,你可以把异步迭代器作为一个**next()
函数总是返回一个promise的迭代器**。
例子:
异步迭代器函数行为与迭代器函数相似,迭代器函数返回一个有next函数的对象,调用next()
将执行generator函数,直到下一个yield。不同的是,异步迭代器的next()
返回一个promise
async function* run() {
await new Promise(resolve => setTimeout(resolve, 100));
yield 'Hello';
console.log('World');
}
// `run()` returns an async iterator.
// 执行异步生成器生成异步迭代器
const asyncIterator = run();
// The function doesn't start running until you call `next()`
// 异步迭代器调用next函数,得到第一个yield后面跟的结果,并以Promise形式返回。
asyncIterator.next().
then(obj => console.log(obj.value)). // Prints "Hello"
then(() => asyncIterator.next()); // Prints "World"
最干净的方法是使用for/await/of
循环整个异步迭代器函数:
async function* run() {
await new Promise(resolve => setTimeout(resolve, 100));
yield 'Hello';
console.log('World');
}
const asyncIterator = run();//生成异步迭代器
// Prints "Hello\nWorld"
//使用async、await、for:循环一步等待一步
(async () => {
for await (const val of asyncIterator) {
console.log(val); // Prints "Hello"(val是yield后的结果以Promise形式返回,所以需要await去获取它的值)
}
})();
async
函数返回一个 Promise 对象,可以使用then
方法添加回调函数。async
函数内部return
语句返回的值,会成为then
方法回调函数的参数。await
后面的值会被强转成Promise对象async function event() {
const one = await new Promise((resolve, reject) => setTimeout(() => { resolve(1) })); //1
const two = await one + 1 //2
const three = await new Promise((resolve, reject) => setTimeout(() => { resolve(two + 1) })) //3
return three
}
event().then(data => console.log(data))//3
console.log(event())//Promise { }
原生async await=co+generator yield
function* read() {
let content = yield 1
let r = yield 2
return r;
}
function co(it) {
return new Promise((resolve,reject)=>{
function exec(data){
let {value,done} = it.next(data)
if(!done){//如果done一直是flase
Promise.resolve(value).then(res=>{//yield的后面可能是普通值,需要强转
exec(res)//递归
},reject)//出错就跳出
}else{
resolve(data)//停止执行,返回的promse的对象调用.then()获取值
}
}
exec()//第一次执行
})
}
co(read()).then(res=>{
console.log(res)//2
})
键的类型:
键的顺序:
Map
对象以插入的顺序返回键值。Size:
迭代:
Map的方法简单使用:
[[k,v],[k,v]]
的数组类型参数const m = new Map([['one', 1], ['two', 2]]);
const n=[1,2]
let a=m.set(n, 'content')//执行并返回Map
let b=m.get(n)//返回传入键所对应的值
console.log(a)//Map { 'one' => 1, 'two' => 2, [ 1, 2 ] => 'content' }
console.log(b)//content
m.size//3
m.has(n)//true
m.delete(n)//true
m.has(n)//false
m.clear()//清除所有成员,没有返回值。
Map 结构的实例有三个迭代器生成函数和一个遍历方法
Map.prototype.keys()
:返回键名的迭代器。
Object.keys(obj)
:直接返回一个数组存放所有key。Map.prototype.values()
:返回键值的迭代器。
Object.values(obj)
:直接返回一个数组存放所有value。Map.prototype.entries()
:返回所有成员的迭代器。
map[Symbol.iterator]
Map.prototype.forEach()
:遍历 Map 的所有成员。
map.forEach((value,key,self)=>{...})
注意:以下三种迭代方式一样的
Map 结构的默认遍历器接口(Symbol.iterator
属性),就是entries
方法。
map[Symbol.iterator] === map.entries
// true
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
WeakMap
只接受对象作为键名(null
除外),不接受其他类型的值作为键名。
WeakMap中的键名是对象的弱引用(注意:WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用)。
只要WeakMap中键名引用的对象,虽然是被WeakMap引用了,但是依然会被垃圾回收掉,因为这是弱引用。(并不影响引用的那个对象被垃圾回收掉)
无法遍历,不支持keys()
、values()
和entries()
方法,也没有size
属性。
Set集合类似于数组,但是成员的值都是唯一的,没有重复。
Set的基本使用:
Set
的构造函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
let set = new Set([1,2,3,4]);
Set
强转成数组:
let arr=[...set]
let arr=Array.from(set)
let set = new Set();
set.add(1).add(2).add(3)//执行并返回修改后的结果
// Set { 1, 2, 3 }
set.size // 3
set.has(3) // true
set.delete(3) // true
set.has(3) // false
set.clear() // 清除所有成员,没有返回值。
Set 结构的实例有三个迭代器生成函数和一个遍历方法,可以用于遍历成员。
Set.prototype.keys()
:返回键名的迭代器Set.prototype.values()
:返回键值的迭代器Set.prototype.entries()
:返回键值对的迭代器Set.prototype.forEach()
:使用回调函数遍历每个成员
set.forEach((value,key,self)=>{...})
数组Array、集合Set、表Map都具有迭代器生产方法和遍历方法:
instance.keys()
:返回键名的迭代器instance.values()
:返回键值的迭代器instance.entries()
:返回键值对的迭代器instance.forEach()
:使用回调函数遍历每个成员
instance.forEach((value,key,self)=>{...})
WeakSet 的成员只能是对象,而不能是其他类型的值。
也是弱引用。不容易出现内存泄漏。
无法遍历,不支持keys()
、values()
和entries()
方法,也没有size
属性。
Array构造函数中的私有方法:
Array.isArray()
:判断数组类型Array.from()
:一个类数组或可迭代对象强转成一个数组
Array.prototype.slice.call()
都可以转成数组Array.of()
:创建具有所有参数新数组,而不考虑参数的数量或类型。Array原型上的方法:(Array.prototype.method
)
array.push()
入栈、array.pop()
出栈array.unshift()
头增、array.shift()
头删array.indexOf()
、array.includes()
array.keys()
、array.values()
、array.entries()
for of
来执行迭代器array.splice(index,count,item..)
:新增、删除、修改元素,返回包含被删除项目的新数组;会改变原数组
array.slice(start,end)
:裁剪数组,返回裁剪后的数组,并不改变原数组
-1
最后一个元素,-2
倒数第二个元素的顺序来指定元素,依然是遵循包含start不包含end。array.join()
:拼接成字符串,并不改变原数组array.concat()
:合并数组,并不改变原数组array.reverse()
:翻转数组,返回翻转后的数组,会改变原数组array.sort()
:排序数组,返回排序后的数组,会改变原数组
a.sort((pre, back) => pre - back)
升序从小到大a.sort((pre, back) => back - pre)
降序从大到小pre-back>0
前后数字交换;pre-back<0
前后数字不交换array.every()
:检测所有元素是否满足一定条件,满足的话就返回 true,否则就返回 false
array.every((item,index,self)=> item>0 )
array.some()
:如果有一个元素,满足条件就返回 true,否则的话返回 false
array.some((item,index,self)=> item>0 )
array.find()
:方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined
array.find((item,index,self)=> item>0 )
array.filter()
:数组过滤,返回过滤之后的新数组。不会改变原数组。
array.filter((item,index,self)=> item>0 )
array.forEach()
:遍历一次数组并执行回调,回调中 return 返回的值并没有意义,所以它没有返回值,(只是单纯的遍历数组同时执行一次回调函数)不会改变原数组。
array.forEach((item,index,self)=> {...})
array.map()
:遍历一次数组并执行回调,回调中 return 返回的值会组成一个新数组作为返回值。不会改变原数组。
array=[1,2,3]
array.map((item,index,self)=> {return item*2 })
[2,4,6]
array.reduce()
:接收两个参数,第一个参数是回调,第二个参数是初始值
array.reduce(callback,initialValue(可选))
callback=(pre,cur,index,self)=>{...return newValue}
initialValue
,则将initialValue
赋给pre
initialValue
,则取数组的第一个数作为pre
return
后面的newValue
赋给pre
Object构造函数中的私有方法:
Object.assign(target,...sources)
:方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Object.create(obj)
:方法创建一个新对象,使这个新对象的__proto__
指向obj
Object.freeze(obj)
:Object.freeze()
方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze()
返回和传入的参数相同的对象。(改变了传入对象,并返回修改后的对象)
Object.defineProperty(obj, prop, descriptor)
:方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
const obj = {};
Object.defineProperty(obj, 'property', {
get: function () {
console.log('get!!');
return property
},
set: function (value) {
property = value;
console.log('set!!')
}
});
obj.property = 20 // set!!
let i=obj.property // get!!
console.log(i) // 20
console.log(obj) // {}
//注意:此时默认property是不可枚举属性,所以打印obj是{}
//我们在浏览器上面打印对象,浅色的属性都是不可枚举属性,深色的属性才是可枚举属性。
Object.is(value1, value2)
:方法判断两个值是否为同一个值。
console.log(+0 == -0)//true
console.log(Object.is(+0, -0))//false
console.log(Number.NaN == NaN)//false
console.log(Object.is(Number.NaN, NaN))//true
console.log(NaN==NaN)//false
Object.getPrototypeOf(obj)
:方法返回指定对象的原型(内部[[Prototype]]
属性的值
Object.keys(obj)
、Object.values(obj)
、Object.entries(obj)
enumerable:true
…
Object原型上的方法:(Object.prototype.method
)
obj.hasOwnProperty(prop)
:方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)obj.isPrototypeOf(object)
:方法用于测试一个对象是否存在于另一个对象的原型链上
isPrototypeOf()
与instanceof运算符不同。在表达式 object instanceof AFunction
中,object
的原型链是针对 AFunction.prototype
进行检查的,而不是针对 AFunction
本身。obj.propertyIsEnumerable(prop)
:方法返回一个布尔值,表示指定的属性是否可枚举。obj.toString()
:方法返回一个表示该对象的字符串。
toString()
方法。String原型上的方法:(String.prototype.method
)
str.charAt(index)
:返回指定位置的字符。
index
为必须参数,类型为number
(0
到str.length-1
之间,否则该方法返回 空串)str.charAt()
即不带参数和str.charAt(NaN)
均返回字符串的第一个字符str.concat(str1,...,strn)
:用于拼接两个或多个字符串。功能和 “+” 拼接没啥两样。str.indexOf(searchStr,startIndex)
:和数组差不多。str.toUpperCase() / str.toLowerCase()
:用于字符串转换大小写。str.trim()
:用于删除字符串的头尾空格
str.split('')
:用于把一个字符串分割成字符串数组。str.slice()
:和数组一样。str.substring(index1,index2)
:方法用于提取字符串中介于两个指定下标之间的字符。
index1=负数
和index2=负数
,则都会被转成0str.substr(start,length)
:方法可在字符串中抽取从 start
下标开始的指定数目的字符。
lentgh=负数
,则直接返回空字符串start=负数
,则以最后一位为-1
,倒数第二位为-2
的顺序计算Number构造函数中的私有方法:
Number.parseInt(string[, radix])
: 依据指定基数 [ 参数 radix 的值],把字符串 [ 参数 string 的值] 解析成整数。Number.parseFloat(string)
:可以把一个字符串解析成浮点数。该方法与全局的parseFloat()
函数相同Number.isInteger(value)
:用来判断一个值是否为整数。
3.0
和3
是同一个数字,都是整型。Number原型上的方法:(Number.prototype.method
)
num.toString([radix])
:数字转换成字符串
radix
参数(可选):指定要用于数字到字符串的转换的基数(从2到36)。如果未指定 radix 参数,则默认值为 10。num.toFixed(number)
:方法使用定点表示法来格式化一个数值。
ECStack
执行环境栈:当我们浏览器加载页面的时候,它会形成一个执行环境,称为:ECStack
执行环境栈;目的是:让代码放进ECStack
中执行。它属于是栈内存(Stack)
EC执行上下文:在JS代码在浏览器跑起来(执行时)的时候,就会产生一个EC(GLOBAL)
全局执行上下文,此时会将这个全局执行上下文EC(GLOBAL)
,放到执行环境栈ECStack
中执行。
VO:Variable object
变量对象;作用:把执行上下文EC中创建的变量和变量的值,存储到[[Environment]]
中。
AO:activation object
活动对象;在函数上下文中,我们用活动对象(activation object, AO
)来表示变量对象;活动对象和变量对象其实是一个东西,区别在于活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化
function fn() {
let a = 1;
return function fo() {
console.log(a++)
}
}
let x = fn()//fn()执行后的返回值fo被引用到了全局变量x中
x();//1
x();//2
x();//3
//下面两种情况相当于创建了两个实例,
//当前面的函数执行完之后里面的变量和函数就被垃圾回收掉,
//所以不影响后面函数的执行依然打印的是a=1
fn()()//1
fn()()//1
fn()()//1
两种机制:标记清楚法(Chrome)、引用计数法(IE)
函数执行完,形成的执行上下文中,没有变量被上下文以外的内容占用,此上下文就会从执行环境栈中移除(释放),如果有被占用,则该上下文就被继续保留,压缩到执行环境栈底部。(闭包可能会导致内存泄漏,需要手动回收不用的变量)
var data = [];
for (var i = 0; i < 3; i++) {
//此时的for循环是全局的,i也是全局变量,执行完成后,全局变量i就变成了3
data[i] = function () {//当data[0]()执行时,此时函数的上下文中只有全局变量i=3
console.log(i);
};
}
data[0]();//3
data[1]();//3
data[2]();//3
var data = [];
for (var i = 0; i < 3; i++) {//此时这里i也是全局变量,执行完毕后也会变成3
data[i] = (function (i) {
return function(){//闭包,保存了一个匿名函数上下文(其中有一个变量i,相当于是一个快照)
console.log(i);//当data[0]()执行时,就会去找函数上下文作用域链上找变量i(i被一直引用,所以不会被垃圾回收掉)
}
})(i);
}
data[0]();//0
data[1]();//1
data[2]();//2
防抖:一定时间内重复触发会一直被推迟,直到过了规定延时时间,执行最后一次。
function debounce(fn, delay) {
var timer;
return function() {
clearTimeout(timer);//执行取消定时器,来实现
timer = setTimeout(function() {
fn();
}, delay);
}
}
clearTimeout(TimerId)
方法可取消由setTimeout()
方法设置的定时操作。
setTimeout()
返回的 ID 值。节流:触发开始到规定延时时间结束,只执行一次。
function throttle(fun, delay) {
var timer = null;//通过timer去判断是否执行,并没有取消过定时器
return function() {
if (!timer) {
timer = setTimeout(function() {
fun();
timer = null;
}, delay);
}
}
}
①new Object()
重复代码多
var person = new Object()
person.name = 'zhangsan'
person.say = function(){
alert(this.name)
}
②对象字面量
重复代码多
var person = {
name:'zhangsan',
say:function(){
alert(this.name)
}
}
③工厂模式
创建的对象原型链上只有有Object
function createPerson(name,say){
var o = new Object();
o.name=name;
o.say=say
return o
}
var person = createPerson('zhangsan',function(){
alert(this.name)
})
④构造函数模式
构造函数的作用是给实例添加各种属性,形成具有自己独有属性的实例。
function Person(name,say){
this.name=name;
this.say=say
}
var person = new Person('zhangsan',function(){
alert(this.name)
})
//Person原型对象上有个constructor属性
//Person构造函数里面又有一个prototype属性指向Person的原型对象
//此时Person构造函数和Person原型对象就形成了一个环状结构
⑤原型模式
所有创造出来的实例,都共享原型上的属性和方法(公有属性)
function Person(){
}
Person.prototype.name='zhangsan';
Person.prototype.say=function(){
alert(this.name)
}
⑥混合模式
构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性
规定:
new关键字调用构造函数步骤:(new的过程)
function Person(name){
this.name=name
}
Person.prototype.say=function(){
alert(this.name)
}
JS中每个对象都是基于Object对象创建。
__proto__
,因为在原型链由下至上找的过程中,找的第一个对应的属性名就停止了。__proto__
找上一个原型对象,找到了停止;__proto__
,由下至上地找,直到找到为止;undefined
;__proto__
可以省略。①原型继承
CHILD.prototype = new PARENT();
CHILD.prototype.constructor = CHILD;
缺陷:实例共享父类的私有属性
function Parent () {
this.names = ['kevin', 'daisy'];
}
function Child () {
}
Child.prototype = new Parent();
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy", "yayu"]
②call继承
CHILD方法中把PARENT当做普通函数来执行,
让PARENT中的this指向CHILD的实例,
相当于给CHILD的实例设置了很多PARENT的私有属性和方法。
避免了实例共享父类私有属性
function Parent () {
this.names = ['kevin', 'daisy'];
}
function Child () {
Parent.call(this);//执行时,names属性被加在了this实例上面
}
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy"]
③寄生组合继承
function PARENT(x) {
this.x = x
}
PARENT.prototype.getX = function () {
console.log(this.x)
}
function CHILD(y) {
PARENT.call(this, 200)//保留call:继承构造函数中的私有属性或方法
this.y = y
}
//用于继承公用的属性或方法
CHILD.prototype = Object.create(PARENT.prototype)
//又使用原型链继承,此时是新创建一个Object实例,但是这个实例原型指向PARENT的原型
CHILD.prototype.constructor =CHILD//**重要**prototype对象被重置了,需要保留原来的constructor指针指向
//再把重定向的到的constructor指回B
CHILD.prototype.getY = function () {
console.log(this.y)
}
④ES6的extends继承
//实现 父类公有的属性和方法 成为 子类公有的属性和方法
class CHILD extends PARENT {
//=>B.prototype.__proto__=A.prototype
constructor(){
super()//实现 父类私有的属性和方法 成为 子类私有的属性和方法
//=>A.call(this) 把父类当做普通方法执行,给方法传递参数,让方法中的this是子类的实例
}
//如果不写constructor(),浏览器会默认创建
//constructor(...args){ super(...args) }
}
其实所有的函数在调用执行的时候都是使用了.call
或者.bind
方法进行this
的绑定,只是分为了显式绑定和隐式绑定(默认绑定this)而已。
fn.call(asThis,1,2)
fn.bind(asThis,1,2)()
obj.method.call(obj,'hi')
fn(1,2)//等同于fn.call(undefined,1,2)
obj.method('hi')//等同于obj.method.call(obj,'hi')
array[0]('hi')//等同于array[0].call(array,'hi')
this
是call
的第一个参数new
重新设计了this
this
①通过JSON的方式实现
undefined
,function
,RegExp
等等类型的function deepClone(value){
return JSON.parse(JSON.stringify(value))
}
②通过Object.assign(target, source)
的方式实现
function deepClone(value){
return Object.assign({},value)
}
③使用递归的方式实现(推荐)
// 定义一个深拷贝函数 接收目标value参数
function deepClone(value) {
// 定义一个变量
let result;
if (typeof value === 'object') { // 如果当前需要深拷贝的是一个对象的话
if (Object.prototype.toString.call(value) === '[object Array]') { // 如果是一个数组的话
result = [];
for (let i in value) {
result.push(deepClone(value[i]));
}
} else if (value === null) {// 判断如果当前的值是null的话;直接赋值为null
result = null;
}
else if (value.constructor === RegExp) {// 判断如果当前的值是一个RegExp对象的话,直接赋值
result = value;
} else {// 否则是普通对象,直接for in循环,递归赋值对象的所有值
result = {};
for (let i in value) {
result[i] = deepClone(value[i]);
}
}
} else {// 如果不是对象的话,就是基本数据类型,那么直接赋值
result = value;
}
return result;// 返回最终结果
}
function perform(anyMethod, wrappers) {
return function () {
wrappers.forEach(wrapper => wrapper.initialize());
anyMethod();
wrappers.forEach(wrapper => wrapper.close());
}
}
let Func = perform(function () {
console.log('say');
}, [{//wrapper1
initialize: function () {
console.log('wrapper1 beforeSay');
},
close: function () {
console.log('wrapper1 close');
}
}, {//wrapper2
initialize: function () {
console.log('wrapper2 beforeSay');
},
close: function () {
console.log('wrapper2 close');
}
}])
Func()
let arr1 = [1, 2]
let arr2 = [10, 20]
function* chain(params) {
yield 'before'
for (let arr of params) {
yield* arr
}
yield 'after'
}
for (let num of chain([arr1, arr2])) {
console.log(num)
}
//before
//1
//2
//10
//20
//after
function say(who,sth){
console.log(who + '说话时' + sth)
}
Function.prototype.before=function (beforeFunc){
let that=this//也可以使用下面箭头函数,因为箭头函数没有this,没有arguments,没有原型;这里使用that变量不被垃圾回收掉。
return function(){
beforeFunc()
that(...arguments)//如果是this的话,就需要看调用时的上下文,此时使用闭包,保留that变量在函数外也可以使用。
}
}
let newFn=say.before(function (){
console.log('说话前')
})
newFn('我','大笑');//如果是this的话,需要看调用时的上下文。
//使用ES6箭头函数:
Function.prototype.before=function (beforeFunc){
return (...args)=>{
beforeFunc()
this(...args)//箭头函数不支持arguments,而且不支持this,此时这个this是上一个函数的this传进箭头函数中
}
}
function after(times, callback) {//闭包
let index=0;
return function () {//闭包将times变量一直存在栈中
index++;
if (times == index) {
callback();
}
}
}
let fn = after(3, function () {
console.log('函数已执行');
})
fn()
fn()
fn()
//函数已执行
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
//var _args = Array.prototype.slice.call(arguments);
var _args = Array.from(arguments);
return function cur() {
//将下一个函数的参数加入数组中
_args.push(...arguments);
cur.toString = function () {//写一个静态方法,在最后的时候调用执行逻辑
return _args.reduce(function (a, b) {
return a + b;
});
}
return cur//递归返回当前函数
};
}
console.log(add(1)(2)(3).toString());//6
console.log(add(1)(2)(3)(4)(5).toString())//15
console.log(add(2, 6)(1).toString())//9
console.log(add(1)(2, 3)(4).toString())//10
简单手写柯里化:
/*
function sum(a, b, c) {
console.log(a + b + c);
}
const fn = curry(sum);
fn(1, 2, 3); // 6
fn(1, 2)(3); // 6
fn(1)(2, 3); // 6
fn(1)(2)(3); // 6
*/
//使用调用静态方法的形式来结束
function curry1(callback) {
const params = [];
return function handle(...args) {
params.push(...args);
handle.toString = function () {
callback(...params);
};
return handle;
};
}
//根据传入传入参数来结束
function curry2(callback) {
const params = [];
return function handle(...args) {
params.push(...args);
//函数的length是指它的参数个数
if (callback.length === params.length) {
//参数收集够了则调用函数
callback(...params);
}
return handle;
};
}
//test
function sum(a, b, c) {
console.log(a + b + c);
}
const fn1 = curry1(sum);
fn1(1, 2)(3)(4, 5).toString(); // 6
const fn2 = curry2(sum);
fn2(1, 2)(3)(4, 5); // 6
Array.from(new Set([1,3,3]))//[1,3]
[...new Set([1,3,3])]//[1,3]
Array.prototype.slice.call(new Set([1,3,3]))//直接报错,不能转换Set和Map
function unique(arr) {
return arr.reduce((pre, cur) => {
//if (pre.indexOf(cur) === -1) {
if (!pre.includes(cur)) {
pre.push(cur)
}
return pre
}, [])
}
function unique(arr) {
return arr.filter(function(item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item) === index;
});
}
function unique(arr) {
var arrry = [];
var obj = {};
for (var i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) {
obj[arr[i]] = '已有值'
arrry.push(arr[i])
}
}
return arrry;
}
let event = {
_arr:[],
subscribe(fn){//订阅者订阅消息
this._arr.push(fn)
},
publish(){//发布者发布消息发布
this._arr.forEach(fn=>fn())
}
}
subscriber---订阅push--->EventArr---发布遍历--->publisher
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<style>
img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
style>
<body style="margin: 0;">
<img id='dv' src="https://picsum.photos/200/300" />
body>
<script>
//获取元素
var dv = document.getElementById('dv');
var x = 0, y = 0, l = 0, t = 0;
var isDown = false;
//鼠标按下事件
dv.onmousedown = function (e) {
//获取x坐标和y坐标
x = e.clientX;
y = e.clientY;
//获取左部和顶部的偏移量
l = dv.offsetLeft;
t = dv.offsetTop;
//开关打开
isDown = true;
//设置鼠标样式
dv.style.cursor = 'move';
}
//鼠标移动
window.onmousemove = function (e) {
e.preventDefault()
if (isDown == false) {
return;
}
//获取x和y
var nx = e.clientX;
var ny = e.clientY;
//计算移动后的左偏移量和顶部的偏移量
var nl = nx - (x - l);
var nt = ny - (y - t);
dv.style.left = nl + 'px';
dv.style.top = nt + 'px';
}
//鼠标抬起事件
dv.onmouseup = function () {
//开关关闭
isDown = false;
//还原鼠标样式
dv.style.cursor = 'default';
}
script>
html>
在第一个张图前面插入最后一张图,最后一张图后面插入第一张图来实现。
如图所示,当鼠标点击轮播视口Box的左按钮,即向左移动画,动画执行完毕,立即向将定位切为最后一张图的位置。(轮播动画是由装全部图片的slider进行绝对定位来控制)
//commonJS导出一个对象
module.exports={ name:name,age:age }
let xx=require('./index')
//ES modules导出一个对象
export { name:name,age:age }
import { name,age } from './index'//不能任意命名,只能解构赋值
export default { name:name,age:age }
import xx from './index'//导入的对象可以任意取名
commonJS模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化不会影响到这个值。
// common.js
var count = 1;
var printCount = () =>{
return ++count;
}
module.exports = {
printCount: printCount,
count: count
};
// index.js
let v = require('./common');
//commonJS是值的拷贝,可以进行修改
console.log(v.count); // 1
console.log(v.printCount()); // 2
console.log(v.count); // 1
//改写
module.exports = {
printCount: printCount,
get count(){
return count
}
};
//依次打印1 2 2
在ES modules中使用export和import就可以直接实现。ES6模块是动态引用,并且不会缓存,模块里面的便令绑定其所在的模块,而是动态地去加载值,并且不能对其重新赋值
// es6.js
export let count = 1;
export function printCount() {
++count;
}
// main1.js
import { count, printCount } from './es6';
console.log(count)//1
console.log(printCount());//2
console.log(count)//2
总结:
懒加载(延迟加载)通过img标签的src属性和监听屏幕滚动事件来实现
预加载(提前加载)通过image对象的设置url提前加载图片,再通过onload设置回调将其挂载到dom上。
/(http|https)\:\/\/www\.(.*?)\.(com|cn|org)/
'xxxxxxxxxhttp://www.baidu.comxxxxxx'.match(/(http|https)\:\/\/www\.(.*?)\.(com|cn|org)/)
//http://www.baidu.com
数组扁平化是指将一个多维数组变为一维数组。
[1, [2, 3, [4, 5]]] ------> [1, 2, 3, 4, 5]
①es6中的flat函数也可以实现数组的扁平化
let arr1 = [1,2,['a','b',['中','文',[1,2,3,[11,21,31]]]],3];
console.log( arr1.flat( Infinity ) );
//[ 1, 2, 3, 4, 5 ]
②toString & split
function flatten(arr) {
return arr.toString().split(',').map(function(item) {
return Number(item);
})
}
③join & split
function flatten(arr) {
return arr.join(',').split(',').map(function(item) {
return parseInt(item);
})
}
④reduce & 递归
function flatten(arr) {
return arr.reduce((result, item)=> {
return result.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
⑤扩展运算符
[].concat(...[1, 2, 3, [4, 5]]); // [1, 2, 3, 4, 5]
⑥递归
function flatten(arr) {
var res = [];
arr.map(item => {
if(Array.isArray(item)) {
res = res.concat(flatten(item));
} else {
res.push(item);
}
});
return res;
}
⑦yield* & 递归
function* iterTree(tree) {
if (Array.isArray(tree)) {
for (let i = 0; i < tree.length; i++) {
yield* iterTree(tree[i]);
}
} else {
yield tree;
}
}
let arr1 = [1, 2, ['a', 'b', ['中', '文', [1, 2, 3, [11, 21, 31]]]], 3];
let arr =[]
for (const x of iterTree(arr1)) {
arr.push(x)
}
console.log(arr);
//[1,2,'a','b','中','文',1,2,3,11,21,31,3]
⑧arr.some()
& 循环
function iterTree2(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
console.log(iterTree2(arr1));
//[1,2,'a','b','中','文',1,2,3,11,21,31,3]
判断某个元素是否进入了"视口",即用户能不能看到它。(顾名思义:元素与视口交叉区观察)
传统实现方法:监听scroll事件后,调用目标元素的getBoundingClientRect()方法,获得目标元素对于视口左上角的坐标,再去判断是否在视口之内。这种方法的缺点是,由于scroll
事件密集发生,计算量很大,容易造成性能问题,也可以使用防抖节流的方式去优化。
用法:
callback
是可见性变化时的回调函数,option
是配置对象(该参数可选)。
threshold
属性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0]
,即交叉比例(intersectionRatio
)达到0
时触发回调函数。用户可以自定义这个数组。比如,[0, 0.25, 0.5, 0.75, 1]
就表示当目标元素 0%、25%、50%、75%、100% 可见时,会触发回调函数。var io = new IntersectionObserver(callback, option);
//callback参数返回IntersectionObserverEntry对象来描述观察
observe
方法可以指定观察哪个 DOM 节点。var io = new IntersectionObserver(entries=>console.log(entries));
// 开始观察
io.observe(document.getElementById('example'));
// 停止观察
io.unobserve(element);
// 关闭观察器
io.disconnect();
function query(selector) {
return Array.from(document.querySelectorAll(selector));
}
var observer = new IntersectionObserver(
changes => {
changes.forEach(item => {
var container = item.target;
var content = container.querySelector('template').content;
container.appendChild(content);//添加dom
observer.unobserve(container);//停止观察此项目,执行加载完毕
});
}
);
//观察懒加载的所有项目
query('.lazy-loaded').forEach(item => {observer.observe(item)});
var io = new IntersectionObserver(
function (entries) {
// 如果不可见,就返回
if (entries[0].intersectionRatio <= 0) return;
loadItems(10);
console.log('Loaded new items');
});
// 开始观察
io.observe(
document.querySelector('.scrollerFooter')
);
for in循环只会迭代可枚举属性,会遍历原型链。
var obj = {
a:1,
b:2,
c:3
}
obj.__proto__.d=4
for (let key in obj){
//if(obj.hasOwnProperty(key))//阻止遍历原型属性
console.log(key)//a,b,c,d
}
console.log(Object.keys(obj))//a,b,c
在实际开发中,常常出现下面几种报错情况:
// 1. 对象中不存在指定属性
const leo = {};
console.log(leo.name.toString());
// Uncaught TypeError: Cannot read property 'toString' of undefined
// 2. 使用不存在的 DOM 节点属性
const dom = document.getElementById("dom").innerHTML;
// Uncaught TypeError: Cannot read property 'innerHTML' of null
在可选链 ?.
出现之前,我们会使用短路操作 &&
运算符来解决该问题:
const leo = {};
console.log(leo && leo.name && leo.name.toString()); // undefined
?.
是指即使中间的属性不存在,也不会出现错误。如果可选链 ?.
前面部分是 undefined
或者 null
,它会停止运算并返回 undefined
。
语法:
obj?.prop
obj?.[expr]
arr?.[index]
func?.(args)
?.
之前的变量必须已声明arr?.[0]
)obj.fun?.()
)// 1. 对象中不存在指定属性
const leo = {};
console.log(leo?.name?.toString());
// undefined
console.log(leo.name?.toString()); //leo必须存在
// undefined
// 2. 使用不存在的 DOM 节点属性
const dom = document?.getElementById("dom")?.innerHTML;
// undefined
const dom = document.getElementById("dom")?.innerHTML; //document必须存在
// undefined
Function.prototype.newBind = function (...arr) {
let that = this
return function () {
that.call(...arr)
}
}
obj = {
a: 1
}
function say(a, b, c) {
console.log(this)
console.log(a, b, c)
}
say.newBind(obj, 1, 2, 3)()
//{ a: 1 }
//1 2 3
//通过对象调用方法来修改this指向
Function.prototype.call2 = function (context){
const ctx = context || window
ctx.func = this //调用call2时,this是调用call2的方法(call2是在函数对象上)
//测试用例中这个this打印出来是:[function a]
const args = Array.from(arguments).slice(1)//保存参数
//通过在ctx中新建一个 函数对象等于调用时的对象 来调用执行来修改this指向
const res = arguments.length > 1 ? ctx.func(...args) : ctx.func()
delete ctx.func//避免造成全局污染
return res
}
//测试用例:
obj={c:2}
function a(x,y){console.log(this,x,y)}
a.call(obj,1,2)//{c:2} 1 2
a.call2(obj,1,2)//{c:2,func:[function a]} 1 2
Function.prototype.apply2 = function (context){
const ctx = context || window
ctx.func = this
//测试用例中这个this打印出来是:[function a]
//通过在ctx中新建一个 函数对象等于调用时的对象 来调用执行来修改this指向
const res = arguments[1] ? ctx.func(...arguments[1]) : ctx.func()
delete ctx.func//避免造成全局污染
return res
}
//测试用例:
obj={c:2}
function a(x,y){console.log(this,x,y)}
a.call(obj,[1,2])//{c:2} 1 2
a.call2(obj,[1,2])//{c:2,func:[function a]} 1 2
Function.prototype.bind2 = function (context) {
//对context进行深拷贝,防止bind执行后返回函数未执行期间,context被修改
const ctx = JSON.parse(JSON.stringify(context)) || window
ctx.func = this
const args = Array.from(arguments).slice(1)
return function () {
//注意bind方法需要合并两次函数执行的参数
const Allargs = args.concat(Array.from(arguments))
return Allargs.length > 0 ? ctx.func(...Allargs) : ctx.func()
}
}
//测试
obj = { c: 2 }
function a(x,y,z) { console.log(this, x, y, z) }
a.bind(obj,1,2)(3)//{c:2} 1 2 3
a.bind2(obj,1,2)(3)//{c:2,func:[function a]} 1 2 3
babel:需要脚手架工具转行代码(类似于打包)。.babelrl文件进行配置需要用到的presets和plugin
了解babel
「Plugin 会运行在 Preset 之前。
Plugin 会从前到后顺序执行。
Preset 的顺序则 刚好相反(从后向前)。」
参考:手写new
function Animal(type){
this.type=type
}
Animal.prototype.Run = function(){
console.log(this.type + ' Run!!!')
}
const Dog = new Animal('dog')
Dog.Run()//dog Run!!!
自己手写一个new:
function _new(fn,...args){
//创建一个空对象,原型指向构造函数的原型
const obj = Object.create(fn.prototype)
fn.apply(obj,args) //执行构造函数并给空对象赋值
return obj //返回该对象
}
new的具体步骤:
var obj = {}
obj.__proto__=Dog.prototype
glob匹配模式
匹配符 | 说明 |
---|---|
* | 匹配文件路径中的0个或多个字符,但不会匹配 / ,除非 / 出现在末尾 |
** | 匹配路径中的0个或多个目录及其子目录 |
? | 匹配文件路径中的一个字符,不匹配 / |
! | 出现在规则的开头,表示取反。即匹配不命中后面规则的文件 |
[] | 匹配方括号中出现的字符中的任意一个 |
{} | 可以让多个规则用 , 逗号分隔,起到或者的作用 |
{n1…n3} | 匹配n1到n3之间的整数 |
!(pattern|pattern|pattern) | 匹配任何与括号中给定的任一模式都不匹配的 |
?(pattern|pattern|pattern) | 匹配括号中给定的任一模式0次或1次 |
+(pattern|pattern|pattern) | 匹配括号中给定的任一模式至少1次 |
*(pattern|pattern|pattern) | 匹配括号中给定的任一模式0次或多次 |
@(pattern|pattern|pattern) | 匹配括号中给定的任一模式1次 |
type CallBack = (...args: any) => void;
type EventItem = {
callbackStack?: CallBack[];
};
class EventBus {
handlers: {
[eventName: string]: EventItem;
};
constructor() {
this.handlers = {};
}
on(eventName: string, cb: CallBack) {
this._getHandlers(eventName).callbackStack.push(cb);
}
off(eventName: string) {
if (this.handlers[eventName]) {
delete this.handlers[eventName];
}
}
emit(eventName: string) {
this._getHandlers(eventName).callbackStack.forEach((callback) =>
callback.call(callback)
);
}
_getHandlers(eventName: string): EventItem {
if (!this.handlers[eventName]) {
this.handlers[eventName] = {
callbackStack: [],
};
}
return this.handlers[eventName];
}
}
//测试:
const EventInstance = new EventBus();
EventInstance.off("click");
EventInstance.on("click", () => {
console.log("this is click");
});
EventInstance.on("hover", () => {
console.log("this is hover");
});
EventInstance.on("click", () => {
console.log("this is second click");
});
EventInstance.emit("click"); //this is click this is second click
EventInstance.emit("hover"); //this is hover
EventInstance.off("click");
EventInstance.emit("click");
ES5 的对象属性名都是字符串,这容易造成属性名的冲突,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol
的原因。
使用场景:
在手写call、bind、apply时给ctx对象添加属性时使用到了它
给object添加迭代器时也使用到了它
//作为属性名的 Symbol
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
OSI七层模型:
TCP/IP五层模型:
注意:http是一种无状态连接。
http1.0是非持久连接:一次HTTP请求和响应就创建和关闭一次TCP连接,严重影响性能。所以只能串行执行,上一次请求发出并收到响应之后,才能继续发送下一次请求。纯文本形式传输。
http1.1是持久连接:在一个TCP连接上可以传输多个HTTP请求和响应。创建多个TCP连接可以并行发送很多过请求,收到响应是有序的。—和all方法差不多(最多并行6个TCP连接,因为容易造成DDoS攻击,给服务器压力)纯文本形式传输。
Connection:keep-alive
来保持tcp一直连接,不要关闭。Connection:close
客户端通知服务器关闭tcp连接。http2.0更快、额外的流量花销更少
http1.0:每个tcp中只能串行http请求,且每次请求开辟一个tcp连接
http1.1:每个tcp中也是只能串行http请求,但是可以keep-alive,同时最多允许开辟6个tcp连接,来实现并行请求
http2.0:每个tcp中就可以通过二进制分帧层中streamID组合来实现并行http请求
HTTP2特点
1、HTTP2使用的是二进制传送,HTTP1.X是文本(字符串)传送。二进制传送的单位是帧和流。帧组成了流,同时流还有流ID标示
2、HTTP2支持多路复用,多个请求可同时在一个连接上并行执行。因为有流ID,所以通过同一个http请求实现多个http请求传输变成了可能,可以通过流ID来标示究竟是哪个流从而定位到是哪个http请求。每次请求/响应使用不同的 Stream ID。(二进制分帧:每一个流ID都标识一帧数据,客户端和服务器可以把HTTP 消息分解为互不依赖的帧,然后乱序发送,最后再在另一端把它们重新组合起来)
3、HTTP2头部压缩。HTTP2通过gzip和compress压缩头部然后再发送,同时客户端和服务器端同时维护一张头信息表,所有字段都记录在这张表中,这样后面每次传输只需要传输表里面的索引Id就行,通过索引ID查询表头的值
4、HTTP2支持服务器推送。HTTP2支持在未经客户端许可的情况下,主动向客户端推送内容
2xx表示请求成功
200:成功返回,正常抓取
204:成功处理了请求,但没有返回任何内容
206:部分请求成功,
例子:①浏览器先不下载要下载的文件,而是弹窗提醒用户。
②一些流媒体技术比如在线视频,可以边看边下载。就是使用206来实现的。
③使用206状态码来实现断点续传
3xx表示重定向
301:请求的网站*永久*转移到新的URL,搜索引擎访问时,会收录新的URL
302:请求的网页*临时*转移到新的URL,搜索引擎会保留原来的URL
304:被访问网页没有更新,不用再抓取,节省搜索引擎带宽(缓存)。浏览器上的缓存是最新的资源,需要浏览器使用缓存,不需要进一步抓取。
客户端有缓冲的文档并发出了一个条件性的请求。服务器告诉客户端,原来缓冲的文档还可以继续使用。
4xx表示请求出错
400:请求语法错误
401:用户没有访问权限,需要进行身份认证
403:请求被服务器拒绝
404:未找到资源
410:请求资源永久删除,和404类似,这种页面应当用301永久重定向到新位置
5xx表示服务器内部错误(一般是后端程序的问题)
500:内部服务器错误(后端程序报错)
503:服务器无法使用,并发访问高,通常只是暂时的
505:表示Web服务器不支持此HTTP协议的版本
TCP/IP协议栈从下至上分层结构:物理层,网络层(IP),传输层(TCP、UDP),应用层
TCP特点:面向连接,可靠,保证数据是正确稳定可靠无差错的传输。
UDP特点:无连接,尽可能交付,效率高。
TCP三次握手:标志位ACK,SYN / 确认号ack / 序列号seq
四次分手:标志位FIN,ACK / 确认号ack / 序列号seq
Client Server
①---SYN=1---seq=x--------------------->
<---SYN=1---ACK=1---seq=y---ack=x+1---②
③---ACK=1---ack=y+1---seq=x+1--------->
(客户端和服务端相互确认通信没问题,同时*开启资源*)
开启资源<----数据传输---资源交换---->开启资源
①---FIN=1---seq=u--------------------->
<---ACK=1---seq=v---ack=u+1-----------②
.....期间还是可以数据传输.....
<---FIN=1---ACK=1---seq=w---ack=u+1---③
④---ACK=1---seq=u+1---ack=w+1--------->
(客户端先通知服务端关闭连接,服务端收到即回复)
(服务端准备好了才通知客户端关闭连接,服务端收到即回复)
四次挥手的目的是:让对方的*资源不能随意释放*,可靠传输。
(挥手可以看成离婚,②~③之间可以看成冷静期)
SOCKET套接字:ip:port(local)---ip:prot(foreign)
必须是四元组的唯一标识。
可靠传输的实现:以字节为单位的滑动窗口
明文传输:在HTTP中我们使用的是明文传输,黑客可以随时截取传输的数据,显然这并不安全。
对称加密:客户端和服务器使用同种方式对数据进行加密和解密,黑客截取数据依然可以使用这种的方式进行解密。(因为服务器不可能对每个客户端都储存一种加密的方式)
非对称加密:(无法实现服务器对客户端返回数据的加密和解密)
非对称加密来加密密钥,对称加密来加密通信数据。
第三方权威机构认证服务器安全(解决中间人拦截攻击问题)
通过非对称加密+对称加密+GlobalSign CA认证才是最安全的。
打比分:有两个盒子及对应的锁,一个盒子锁着通信数据(对称加密),另一个盒子锁着上个盒子的钥匙(非对称哈希加密)。浏览器来创建对称加密密钥。
Cookie:cookie数据存放在客户的浏览器上,由服务器端来创建(服务器把“JSESSIONID”作为cookie的name,SessionId的值作为cookie的value),每次浏览器请求时都把这个cookie放在请求头里面
Session:session数据存放在服务器上,服务器对Session列表是以Map的形式来管理,Map的key是SessionId,value是Session对象的引用。
Token:不用像SessionId一样保存在服务器上,是通过哈希算法程序计算得到(用时间换取空间),使用过期时间来解决防止token被盗带来的隐患,设置token较短的过期时间,然后重新进行生产token,使其变得动态。userId+哈希算法+密钥=签名token
LocalStorage 和 Cookie 的区别是什么?
浏览器同源策略:协议,端口,主机都相同才同源。
Cross-origin resource sharing
)标准解决方案跨域资源共享,分为简单请求和非简单请求
postMessage(message, targetOrigin)
(在父页面中使用)
window.addEventListener('message', function(e))
(子页面中监听message事件)
CORS跨域资源共享:
完全依靠服务器端来操作,浏览器端只需要设置withCredentials
属性携带cookie限制。(注意cookie在CORS中依然是同源的)
简单请求:这是为了兼容Form表单发出跨域请求,触发条件:HEAD
、GET
、POST
请求且头信息不超过Accept
、Accept-Language
、Content-Language
、Last-Event-ID
、且Content-Type
的值只限于以下三个值application/x-www-form-urlencoded
(不包含文件上传的post请求的两种编码格式)、multipart/form-data
(包含文件上传的post请求的两种编码格式)、text/plain
(纯文本)
简单请求不会触发 CORS 预检请求。
“需预检的请求”要求必须首先使用OPTIONS方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。预检请求的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
Origin
字段,用于后端服务器验证。Access-Control-Allow
字段。Credentials
,前端才会正常被设置cookie和发送cookie,后端才会正常给前端设置cookie和接收cookie。Access-Control-Allow-Origin
就不能设为星号,必须指定明确的、与请求网页一致的域名。一个服务器与浏览器之间的中间人角色,如果网站中注册了service worker那么它可以拦截当前网站所有的请求,进行判断(需要编写相应的判断程序),如果需要向服务器发起请求的就转给服务器,如果可以直接使用缓存的就直接返回缓存不再转给服务器。从而大大提高浏览体验。
面试精选之http缓存、图解 HTTP 缓存
强缓存: Pragma > Cache-control:max-age=xxx > Expires
协商缓存:Last-Modified/If-Modified-Since、Etag/If-None-Match
Expires
策略设置过期时间,是http1.0老版本的,服务器和浏览器约定文件过期时间,用 Expires 字段来控制,时间是 GMT 格式的标准时间,如 Fri, 01 Jan 1990 00:00:00 GMT。Expires在三个 Header 属性中优先级最低。
Pragma
:设置页面是否缓存,no-cache
不缓存,当该字段值为no-cache
的时候,会知会客户端不要对该资源读缓存,即每次都得向服务器发一次请求才行。(是http1.0老版本推出)Pragma 只有一个属性值,就是 no-cache ,效果和 Cache-Control 中的 no-cache 一致,不使用强缓存,需要与服务器验证缓存是否新鲜,在 3 个头部属性中的优先级最高。
Cache-control
策略是http1.1最新的。(在responseHeader中设置)
Expires
选择更多,设置更细致Expires
Cache-directive | 说明 |
---|---|
public |
所有内容都将被缓存(客户端和代理服务器都可缓存) |
private |
内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存) |
no-cache |
必须先与服务器确认返回的响应是否被更改,然后才能使用该响应来满足后续对同一个网址的请求。因此,如果存在合适的验证令牌 (ETag),no-cache 会发起往返通信来验证缓存的响应,如果资源未被更改,可以避免下载。 |
no-store |
所有内容都不会被缓存到缓存或 Internet 临时文件中 |
must-revalidation/proxy-revalidation |
如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证 |
max-age=xxx (xxx is numeric) |
缓存的内容将在 xxx 秒后失效, 这个选项只在HTTP 1.1可用, 并如果和Last-Modified一起使用时, 优先级较高 |
var maxAgeTime = 60 //过期时间
res.writeHead(200, {
"Cache-Control": 'max-age=' + maxAgeTime
})
那么在60s内,如果再去请求这个文件的话,是不会发起请求的。因为还没有过期呢!唯一例外是如果这个文件是你在浏览器地址栏输入的地址来请求的(比如你请求localhost:3030/static/style.css
),当你刷新的时候就会让当前的这个文件所设定的过期时间失效,直接去请求服务器来看是返回个304还是返回新文件。一般这么请求的都是我们常说的入口文件,入口文件一刷新就会重新向服务器请求,但是入口文件里面所引入的文件如js,css等不会随着刷新而另过期时间失效。除非你单找出来那个引入链接,通过浏览器地址栏去查询它并刷新 )。
Cache-Control
的值有很多:常用的有max-age
,no-cache
和no-store
。
max-age
是资源从响应开始计时的最大新鲜时间,一般响应中还会出现age
标明这个资源当前的新鲜程度。
no-cache
会让浏览器缓存这个文件到本地但是不用,Network中disable-cache
勾中的话就会在请求时带上这个header,会在下一次新鲜度验证通过后使用这个缓存。
no-store
会完全放弃缓存这个文件。
服务器响应时的Cache-Control
略有不同,其中有两个需要注意下:
Expires
是一个具体的日期,到了那个日期就会让这个缓存失活,优先级较低,存在max-age
的情况下会被忽略,和本地时间绑定,修改本地时间可以绕过。
另外,如果你的服务器的返回内容中不存在Expires
,Cache-Control: max-age
,或 Cache-Control:s-maxage
但是存在Last-Modified
时,那么浏览器默认会采用一个启发式的算法,即启发式缓存。通常会取响应头的Date_value \- Last-Modified_value
值的10%作为缓存时间,之后浏览器仍然会按强缓存来对待这个资源一段时间,如果你不想要缓存的话务必确保有no-cache
或no-store
在响应头中。
常用的方式为Etag和Last-Modified(Etag优先级比Last-Modified更高)
Last-Modified方式需要用到两个字段:Last-Modified & if-modified-since。
先来看下这两个字段的形式:
当第一次请求某一个文件的时候,就会传递回来一个 Last-Modified 字段,其内容是这个文件的修改时间。当这个文件缓存过期,浏览器又向服务器请求这个文件的时候,会自动带一个请求头字段 If-Modified-Since ,其值是上一次传递过来的 Last-Modified 的值,拿这个值去和服务器中现在这个文件的最后修改时间做对比,如果相等,那么就不会重新拉取这个文件了,返回 304 让浏览器读过期缓存。如果不相等就重新拉取。
浏览器--------------------第一次请求---------------------->服务器
<---响应Cache-Control:max-age=60;last-modified:[文件修改时间]---
---max-age时间过了;If-Modified-Since:[浏览器缓存中的文件修改时间]--->
<------------服务器去对比'文件修改时间'决定是否返304-----------------
这两个值是由服务器生成的每个资源的唯一标识符,只有资源变化的时候这个值就会改变,与last-Modified区别是,当服务器返回304 Not-Modified的时候由于Etag重新生成过,response还是会把Etag返回,即使Etag和之前的相比没有变化。
Etag的出现就是为了解决last-Modified不能解决的问题:
1、有的文件修改过于频繁,而last-Modified只能精确到秒。
2、一些文件周期性改变的文件内容并没有改变,只是时间改变了,此时我们并不希望服务器认为内容变化了,重新去get。
3、(服务器的问题)有些服务器不能精确到文件的具体最后的修改时间。(比如一串字符串不能正确识别,因为last-modified是绝对时间格式)
XSS(cross site Scripting)存在的原因?
防范:
CSRF(Cross-site request forgery)跨站请求伪造?
用户未退出关闭正常站点,同时打开黑客的站点,黑客站点向正常站点请求,此时会携带正常站点的cookie一起发送,造成csrf。
防范:
关于 token 的存储问题
JWT:
方式一、客户端使用cookie直接认证,需要设置cookie为httpOnly,可以防止 xss攻击。但是无法防止csrf攻击。需要设置伪随机数X-CSRF-TOKEN。(推荐!不需要处理xss,并且csrf随机数有完善的应用机制)
方式二、 客户端使用 auth授权头认证,token存储在 cookie中,需要防止xss攻击。可以防止 csrf攻击,因为 csrf只能在请求中携带 cookie,而这里必须从 cookie中拿出相应的值并放到 authorization 头中。实际上cookie不能跨站(同源政策)被取出,因此可以避免 csrf 攻击。(适用于 ajax请求或者 api请求,可以方便的设置 auth头)
方式三、可以将token存储在localstorage里面,需要防止xss攻击。实现方式可以在一个统一的地方复写请求头,让每次请求都在header中带上这个token, 当token失效的时候,后端肯定会返回401,这个时候在你可以在前端代码中操作返回登陆页面,清除localstorage中的token。(适用于 ajax请求或者 api请求,可以方便的存入 localstorage)(小心cors非简单请求预检请求,设置)
设置HTTPS,可以防止提交时的用户名或者密码被拦截或读取。
①Nginx进行代理解决cookie跨域访问
因为cookie存在跨域问题,其中一个解决方法是,设置Nginx代理服务器,将两个服务器域名统一到一个反向代理服务器。
②解决顶级域名与二级域名之间的cookie跨域问题:设置domain
#通过设置domain
#顶级域名服务器与二级域名服务器之间哪个设置都能生效
#设置完毕后写回到客户端,用另一个服务器即可访问此Cookie
cookie.setDomain("test.com");
前端代码
<script src='http://localhost:3000/jsonp?name=leihao&age=20&callback=((res) => {
console.log(res)
})'>script>
后端服务器
router.get('/jsonp', async (ctx, next) => {
const { name, age, callback } = ctx.query
ctx.body = `${callback}(${JSON.stringify({ name, age })})`
})
启动服务,访问html页面,前端控制台会打印{name: "leihao", age: "20"}
这是抓到的接口结果:((res) => { console.log(res)})({"name":"leihao","age":"20"})
,因为是script中的src去请求的字符串,请求到资源之后相当于会自动去执行eval(((res) => { console.log(res)})({"name":"leihao","age":"20"}))
,差不多就是这意思
浏览器DNS域名解析全过程:(实践这一次,彻底搞懂浏览器缓存机制)
最细节的dns文章
.com
域名服务器地址.com
域名服务器,返回baidu.com
域名服务器地址baidu.com
域名服务器,返回www.baidu.com
的IP地址清理本地DNS缓存的命令:
windows:
ipconfig /flushdns
不同的mac版本都可以试试:
sudo killall -HUP mDNSResponder
lookupd -flushcache
dscacheutil -flushcache
1)递归查询
递归查询是一种DNS server的查询模式,在该模式下DNS server接收到客户机请求。必须使用一个准确的查询结果回复客户机。假设DNS server本地没有存储查询DNS 信息,那么该server会询问其它server。并将返回的查询结果提交给客户机。容易出现超时
2)迭代查询
DNS server第二种查询方式为迭代查询,DNS server会向客户机提供其它可以解析查询请求的DNS server地址,当客户机发送查询请求时,DNS server并不直接回复查询结果。而是告诉客户机还有一台DNS server地址,客户机再向这台DNS server提交请求,依次循环直到返回查询的结果为止。
火车票的例子阐述什么是cdn。
内容分发网络(CDN)是一组分布在多个不同地理位置的 Web 服务器。我们都知道,当服务器离用户越远时,延迟越高。CDN 就是为了解决这一问题,在多个位置部署服务器,让用户离服务器更近,从而缩短请求时间。
当用户访问一个网站时,如果没有 CDN,dns解析过程是这样的:
1.浏览器要将域名解析为 IP 地址(也就是浏览器缓存、系统缓存、路由器缓存都没有的情况),所以先向本地 DNS 发出请求。
2.本地 DNS 依次向根服务器、顶级域名服务器、二级域名服务器、三级域名服务器发出请求,得到网站服务器的 IP 地址。
3.本地 DNS 将 IP 地址发回给浏览器,浏览器向网站服务器 IP 地址发出请求并得到资源。
如果用户访问的网站部署了 CDN,过程是这样的:
1.浏览器要将域名解析为 IP 地址,所以先向本地 DNS 发出请求。
2.本地 DNS 依次向根服务器、顶级域名服务器、二级域名服务器、三级域名服务器发出请求,得到全局负载均衡系统(GSLB)的 IP 地址。
3.本地 DNS 再向 GSLB 发出请求,GSLB 可以根据本地 DNS 的 IP 地址判断用户的位置,筛选出距离用户较近的本地负载均衡系统(SLB),并将该 SLB 的 IP 地址作为结果返回给本地 DNS。
4.本地 DNS 将 SLB 的 IP 地址发回给浏览器,浏览器向 SLB 发出请求。
5.SLB 根据浏览器请求的资源和地址,选出最优的缓存服务器发回给浏览器。
6.浏览器再重定向到缓存服务器。
7.如果缓存服务器有浏览器需要的资源,就将资源发回给浏览器。
8.如果没有,就向源服务器请求资源,再发给浏览器并缓存在本地。
主域名相同子域名不同可以设置cookie的domain为主域名,则可以实现cookie共享。
通过访问sso.xxx.com来登录,在这个服务器中记录好session,登录完成之后给xxx.com的主域的cookie中设置sid,并跳转回当前业务系统。业务系统的接口携带cookie即可实现正常访问,此后只要主域名是xxx.com的网站都可以携带该cookie进行访问。
二维码只能是一段文本的编码。(二维码存储的信息由一个登录网址和服务器生成的标识该二维码的id,比如:http://login.weixin.com?qrcodeId=obsbQ-Dzag
)
当打开手机微信扫码二维码后,手机微信会跳转到二维码解析出来的网页地址,并携带扫码人微信的信息。(http://login.weixin.com?qrcodeId=obsbQ-Dzag&weixinId=rayhomie
)
此时微信服务器端就将二维码id和微信id绑定在一起,并通知网页微信,加载该微信人的信息,从而完成登录。
beforeCreate
:初始化事件&生命周期
created
:初始化依赖注入&校验
beforeMount
:el
选项template
转换成render()
函数,diff算法生产VDOM
mounted
:将VDOM渲染挂载到真实DOM上
beforeUpdate
:监听到数据data(state)被修改,diff算法生成vdom
updated
:虚拟dom重新渲染并应用更新
beforeDestroy
:解除绑定销毁组件以及事件监听器
destroyed
:销毁完毕
coumputed 是计算属性,watch是侦听属性,methods是方法
computed 和 methods 相比,最大区别是 computed 有缓存:如果 computed 属性依赖的属性没有变化,那么 computed 属性就不会重新计算。methods 则是看到一次计算一次。
watch 和 computed 相比,computed 是计算出一个属性(废话),而 watch 则可能是做别的事情(如上报数据)。如果在data中没有相应的属性的话,是不能watch的,这点和computed不一样。
方式一:父传子props
、子传父$emit + v-on
、兄弟传值eventBus
(eventBus.$emit + eventBus.$on
)
方式二:直接访问dom实例 $refs + ref
属性
方式三:vuex单一状态树
方式四:localStorage
、sessionStorage
进行传值
Vue 的响应式,核心机制是 观察者模式来实现数据驱动。(只是实现了找到需要更新的组件。)
vue组件在初始化时,首先会递归遍历data选项,使用Object.defineProperty
对里面的属性进行封装(数据劫持),每一个组件生成一个watcher对象,然后组件template进行模板编译解析生成AST,此时模板中的数据触发getter,对应的watcher进行依赖收集,render生成vdom,随后mount,渲染成真实dom。
组件数据更新时,触发setter,通知wacher(一个wacher对应一个组件)需要视图更新,重新render生成新的vdom,调用patch方法进行新旧vdom的diff算法比较,打补丁渲染到真实dom上。
Vue和React的其中一个最重要的区别是它们对于数据更新的管理方式不同,Vue基本上是一套基于getter/setter实现的依赖收集/依赖更新的订阅式机制,而React则是通过显式的触发函数调用来更新数据,比如setState。相比来说Vue的实现方式更细粒度一些, 通过依赖收集,Vue是能够知道一些数据的更新导致了哪些地方需要重新计算的,通过这种机制,Vue能够优雅地实现计算属性、watch,包括视图渲染。而React由于缺少这种细粒度的机制,则更多时候需要一些其它方案来提高性能,于是产生了如PureComponent(区别于普通Component,浅比较state和props)、ImmutableJS、shouldComponentUpdate钩子等等。
this.value = 3
this.setState({value: 3})
其中的区别在于,Vue知道是组件数据中的value字段发生更新了, 而React只知道是组件的State发生了变化,并不知道是什么数据发生了变化。
和
)children
location.hash='foo'
设置或返回从井号 (#) 开始的 URL(锚)。(并不会请求服务器)location.href='xxx'
设置或返回完整的 URL。(会请求服务器)history.pushState()
加入url到浏览器历史列表(类似入栈)使用vue-router来实现的单页应用,访问http://cnode.lsqy.tech,进入首页,点击按钮跳入另一个路由,一切都是很正常的,但当这时刷新页面,发现就会出现404了。出现这样的错误Cannot GET /message/,因为默认浏览器会认为你是在请求服务端的路由,服务端那边没有对应的处理,所以自然就会出错了。
可以引入connect-history-api-fallback中间件来解决
当打包构建应用,JavaScript包会变得非常大,影响页面加载。我们把不同路由对应的组件分割成不同的代码块,就可以实现按需加载。
结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。
const Foo = () => import(/* webpackChunkName: "name" */ './Foo.vue')//懒加载组件Foo
上代码是有下面两部构成:首先,可以将异步组件定义为返回一个 Promise 的工厂函数:
const Foo = () => Promise.resolve({ /* 组件定义对象 */ })
第二,在 Webpack 2 中,我们可以使用动态import语法来定义代码分块点:
import(/* webpackChunkName: "name" */ './Foo.vue')
// 返回 Promise(加上注释来设置异步加载打包后的文件名)
导航守卫就是路由跳转过程中的一些钩子函数。
导航守卫:beforeEach、beforResolve、afterEach
router.beforeEach((to, from, next) => {//前置钩子
// ...
console.log(to,from)
next();//一定要调用该方法来 resolve 这个钩子
})
router.afterEach((to,from)=>{//后置钩子
console.log('----');
})//会发现beforeEach先打印,然后afterEach后打印
每个守卫方法接收三个参数:
to: Route
: 即将要进入的目标路由对象from: Route
: 当前导航正要离开的路由next: Function
: 调用该方法后,才能进入下一个钩子使用白名单进行整个路由登录拦截。
keepalive是一个抽象组件,缓存vnode,缓存的组件没有mounted中的diff步骤,为此提供 activated 和 deactivated 生命周期函数, 使用include、exclude、max 属性值可以控制缓存匹配对应的组件以及组件个数。
配合vue-router使用可通过routes 配置中的meta属性来设置组件是否被keepAlive所缓存。
meta: {
keepAlive: true // 需要被缓存
}
meta: {
keepAlive: false // 不需要被缓存
}
集中式状态管理,单一状态树(唯一的store来管理),是响应式的。
state、getters(计算属性)、mutations(同步)、actions(异步)、modules
官方文档说明:
用法和react中的setState({},callback)
第二个参数callback差不多,都是视图更新后进行回调。
mixins:[mixinObject]
mixin()
方法,里面接收mixin对象,Vue.mixin(mixinObject)
dom diff是通过JS层面的计算,返回一个patch对象,即补丁对象(描述两个虚拟dom差异),在通过特定的操作解析patch对象,完成页面的重新渲染。作用是比较两个虚拟DOM区别,根据两个虚拟dom创建出补丁(描述差异),将这个补丁(差异对象)用来更新真实dom
react的优化:
父组件中的state或props改变,会导致父组件中的所有子组件都触发一次render,子组件触发render都是没必要的,可以子组件使用以下生命周期就是拦截。
shouldComponentUpdate(nextProps,nextState)
class Child extends React.Component{
shouldComponentUpdate(nextProps,nextState){
if(nextState.open!==this.state.open){
return true
}
if(nextProps.color!==this.props.color){
return true
}
return false
}
state={
open:'opened'
}
}
React.PureComponent 与 React.Component 几乎完全相同,但React.PureComponent通过props和state的浅比较来实现了shouldComponentUpate()
Object.js()
判等,不会递归地去深层比较—源码浅比较shallowEqual源码:
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
- 通过is函数对两个参数进行比较,判断是否相同,相同直接返回true:基本数据类型值相同,同一个引用对象都表示相同
- 如果两个参数不相同,判断两个参数是否至少有一个不是引用类型,存在即返回false,如果两个都是引用类型对象,则继续下面的比较;
- 判断两个不同引用类型对象是否相同
3.1 先通过Object.keys获取到两个对象的所有属性,具有相同属性,且每个属性值相同即两个对相同(相同也通过is函数完成)
Immutable Data 就是一旦创建,就不能再被跟更改的数据。对 Immutable对象 的任何的修改或添加删除操作都会返回一个新的 Immutable对象 。
为什么需要数据不能被改变?
redux等相关应用。redux中的数据是只读的,在使用redux操作的时候,第一步是对数据进行拷贝操作。
对于全局数据来讲,不能每个函数都可以进行修改它,会导致项目不稳定出bug。
所以往往我们对数据进行修改的时候都需要去进行拷贝数据。
优点:
缺点:
Immutable基本使用:(只把修改的属性进行拷贝,不修改的属性进行共享)
const { Map } = require('immutable');
let a = Map({
name:'zhangsan',
accont:Map({
QQ:'123456'
})
})
let b = a.set('name','lisi');//只修改了name,并没有修改accont
//此时的变量b就是:先把要修改的属性拷贝一份,然后再去修改它,没有修改的属性就是共享。
//此时我们可以看到a和b地址是不同的
console.log(a===b)//false
//但是a和b中的account又是共享的。
console.log(a.get('account')===b.get('account'))//true
{...obj}=>只浅拷贝了一层
var obj = {
a:1,
b:2
}
var obj1=obj
obj1.a=999
console.log(obj.a)//999
再比如:在react中shouldComponentUpdate(nextProps,nextState)
,当props或state对象中的值是一个引用,我们对引用中的具体数据进行修改,nextState和this.state依然会是相等的。就不能正确判断数据改变了。
我们来看看区别:下面这个问题就可以使用immutable来解决
例子:比较nextProps和this.props的值,会发现尽管两个打印出来的值是一样的,但是使用===比较始终不相等。如果我们使用immutable的话,nextProps和this.props就会共享块地址,当修改属性的时候,就会拷贝一份修改时候的属性,然后返回两个不同的immutable对象,可以对比出差异。
//父组件
export default class HHH extends Component {
state = {
name: 'leihao'
}
render() {
return (
)
}
}
//子组件
export default class XXX extends Component {
shouldComponentUpdate(nextProps) {
return nextProps !== this.props
}
render() {
console.log('render')
return (
{this.props.name}
)
}
}
如果我们此时再来比较nextProps.name和this.props.name,会发现点击修改之后,他们使用===来比较之后就相等了,就可以阻止渲染
setState
方法和state
、props
一样的都是从React
的Component
中继承过来的。
使用格式如下:setState(partialState, callback)
setState
接受两个参数:
{ }
setState是异步的,我们在setState之后,并不能立即打印出更改后的状态。而且setState
的异步还不是一般的异步操作,是react专门通过算法实现的,使用一段代码中setState
多次更新同一项属性,只会被执行一次。
1】方式一:
使用setState()
的第二个参数(回调函数中获取)
changeText() {
this.setState({
message: "你好啊,李银河"
}, () => {//此回调函数会在更新state的时候被调用执行
console.log(this.state.message); // 你好啊,李银河
});
}
2】方式二:
在组件生命周期中进行截获
componentDidUpdate(prevProps, provState, snapshot) {
//渲染更新之后进行获取。
console.log(this.state.message);
}
React
合成事件中,setState
是异步;(异步操作,但特定情况下只会执行一次,性能被优化)setTimeout
或者原生dom
事件中,setState
是同步;(完全就是同步函数)React合成事件一套机制:React并不是将click事件直接绑定在dom上面,而是采用事件冒泡的形式冒泡到document上面,然后React将事件封装给SyntheticEvent函数处理、运行和处理。
React为了避免DOM事件滥用,同时屏蔽底层不同浏览器之间的事件系统差异
e.stopPropagation()
进行阻止冒泡事件,可能会影响react合成事件的先外层冒泡,而导致错误三大原则:
control层:store=createStore(reducer(state,action))
view层:store.dispatch(action.fun(..))
model层:state={xxx:store.getState()}
provider、consumer
React 16 之前的组件渲染方式是递归渲染:渲染父节点 -> 渲染子节点
存在问题:递归渲染看起来十分简单,但是如果想在子节点的渲染过程中执行优先级更高的操作,只能保留调用栈中子节点的渲染及子节点之前节点的渲染,这样是很复杂的,这种调和/渲染也叫做 Stack Reconciler。
可以分为 Scheduler(调度)、Reconciliation(调和)、Commit(提交) 这三个阶段:
React任务调度采用异步可中断的调度策略,不影响浏览器关键事件比如用户输入、浏览器渲染等。官方模拟了requestIdleCallback
,通过requestIdleCallback向浏览器申请时间片,当浏览器执行完关键事件后如果还有剩余时间则调度任务,任务执行后将控制权交还给浏览器。
function sleep(delay) {
let startTime = Date.now();
while (Date.now() - startTime < delay) {}
}
//任务队列
const works = [
() => {
console.log("任务1 start");
sleep(20);
console.log("任务1 end");
},
() => {
console.log("任务2 start");
sleep(20);
console.log("任务2 end");
},
() => {
console.log("任务3 start");
sleep(20);
console.log("任务3 end");
},
];
function workLoop(deadline) {
// deadline.timeRemaining()可以获取到当前帧剩余时间
console.log("本帧剩余时间", deadline.timeRemaining());
while (deadline.timeRemaining() > 0 && works.length > 0) {
//只是控制一帧内的循环执行任务队列中的任务
performUnitOfWork();
}
//虽然本帧剩余时间为0,但是也要将此次回调执行完,执行下面的代码:
//一帧结束的时候再次调用requestIdleCallback
if (works.length > 0) {
window.requestIdleCallback(workLoop);
}
}
function performUnitOfWork(params) {
let work = works.shift();
work();
}
window.requestIdleCallback(workLoop);
调和阶段主要工作是根据虚拟DOM构建对应的Fiber链表,并在fiber节点上记录要更新的副作用信息。
调和阶段结束后将副作用渲染到页面
requestIdleCallback
,调和任务被拆分为许多子任务,这些子任务异步可中断,不影响浏览器关键事件如用户输入等;DOM diff
算法实现增量更新;webpack本身只支持加载JS和JSON文件,在编写JS代码时可以导入任何类型的文件(比如css、png、less等),在webpack打包过程中就必须使用loader对除JS以外的类型文件进行加载,分析出JS代码中对这些文件的依赖关系。最终将这些依赖关系打包到一个bundle.js文件中,并且将这些所有静态资源文件一同拷贝到dist文件夹下。期间可以使用plugin机制对整个打包的过程进行处理,实现很强大的打包功能。(注意:css-loader是让webpack读懂css代码,然后去找有没有@import,将依赖关系打包起来,最终这些css文件是会被拷贝到dist文件夹下)
开发阶段需要模块化组织ES6代码和css等文件,生产阶段js转换成兼容性更高的ES5代码并且将文件内容按照依赖关系打包到一起,线上版本不需要模块化。
以下是webpack.config.js的基本配置:
const path = require('path')
const { cleanWebpackPlugin }=require('clean-webpack-plugin')
const uglifyjsPlugin=require('uglifyjs-webpack-plugin')
const htmlWebpackPlugin=require('html-webpack-plugin')
const copyWebpackPlugin=require('copy-webpack-plugin')
module.exports={
//entry可以接受一个对象或者字符串
entry:{
app:'./src/app.js',
vendors:'./src/vendors.js'
},
output:{
path:path.resolve(__dirname,'dist'),//设置出口路径
filename:'[name].js'//设置打包好的文件的名字,[name]是指定出口文件名对应的是入口的别名
},
modules:{
rules:[{
test:/\.css$/,//根据打包过程中所遇到文件路径匹配是否使用这个loader
use:['style-loader','css-loader']//指定具体使用的loader
*重要*:使用多个loader时,是从后往前读取,这里就是先使用css-loader,再使用style-loader(通过脚本创建