此文章的题是同事给我发的一个PDF文档里的题,原版我也不清楚是哪里的,好像是某个培训机构的题库。题比较全,但是原版很多题的答案不是很清晰,在此基础上我重新补充了一下
最近应该会持续的把这些题的答案更新完
2023.2.17
https 的 SSL 加密是在传输层实现的。
http 传输的数据都是未加密的,也就是明文的,网景公司设置了SSL 协议来对http 协议传输的数据进行加密处理,简单来说 https 协议是由 http 和 ssl 协议构建的可进行加密传输和身份认证的网络协议,比 http 协议的安全性更高
主要的区别如下:
客户端在使用 HTTPS 方式与 Web 服务器通信时有以下几个步骤:
确认客户端和服务端的接收与发送能力是否正常,因此需要三次握手。
简化三次握手:
从图片可以得到三次握手可以简化为:C发起请求连接S确认,S也发起请求连接C确认
每次握手的作用:
WebSocket 是 HTML5 中的协议,支持持久连续,http 协议不支持持久性连接。Http1.0和 HTTP1.1 都不支持持久性的链接,HTTP1.1 中的 keep-alive,将多个http 请求合并为1 个
HTTP 的生命周期通过 Request 来界定,也就是 Request 一个 Response,那么在Http1.0协议中,这次 Http 请求就结束了。在 Http1.1 中进行了改进,是的有一个connection:Keep-alive,也就是说,在一个 Http 连接中,可以发送多个 Request,接收多个Response。但是必须记住,在 Http 中一个 Request 只能对应有一个 Response,而且这个Response是被动的,不能主动发起。
WebSocket 是基于 Http 协议的,或者说借用了 Http 协议来完成一部分握手,在握手阶段与 Http 是相同的。我们来看一个 websocket 握手协议的实现,基本是2 个属性,upgrade,connection。
基本请求如下:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
多了下面 2 个属性:
Upgrade:webSocket
Connection:Upgrade
告诉服务器发送的是websocket
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
HTTP 请求方式一共有 9 种,分别为 POST 、GET 、HEAD、PUT 、PATCH 、 OPTIONS 、DELETE 、CONNECT 、 TRACE 。
其中前三种 POST 、GET 、HEAD 是 HTTP 1.0 定义的,后六种 PUT 、PATCH 、 OPTIONS 、DELETE 、CONNECT 、 TRACE 是 HTTP 1.1 定义的。
请求的返回头里面,用于浏览器解析的重要参数就是 OSS 的 API 文档里面的返回http头,决定用户下载行为的参数。
下载的情况下:
能够被残障人士使用的网站才能称得上一个易用的(易访问的)网站。残障人士指的是那些带有残疾或者身体不健康的用户。
使用 alt 属性:
<img src="person.jpg" alt="this is a person"/>
有时候浏览器会无法显示图像。具体的原因有:
Bom 是浏览器对象。
1. location
对象
http://www.baidu.com/baidu.php?id=5&name=baidu
返回包括(?)后面的内容?id=5&name=baidu2. history
对象
3. Navigator
对象
4. window
对象
让一个元素被拖拽需要添加 draggable 属性,再加上全局事件处理函数如下
<p id="p1" draggable="true">This element is draggable.p>
首先补充一下,http 和 https 的区别,相比于 http,https 是基于 ssl 加密的http 协议
简要概括:http2.0 是基于 1999 年发布的 http1.0 之后的首次更新。
1. 下面是常见的 HTTP 状态码:
2. HTTP 状态码分类
HTTP 状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型。响应分为五类:信息响应(100–199),成功响应(200–299),重定向(300–399),客户端错误(400–499)和服务器错误 (500–599):
分类 | 分类描述 |
---|---|
1** | 信息,服务器收到请求,需要请求者继续执行操作 |
2** | 成功,操作被成功接收并处理 |
3** | 重定向,需要进一步的操作以完成请求 |
4** | 客户端错误,请求包含语法错误或无法完成请求 |
5** | 服务器错误,服务器在处理请求的过程中发生了错误 |
3. HTTP状态码列表:
状态码 | 状态码英文名称 | 中文描述 |
---|---|---|
100 | Continue | 继续。客户端应继续其请求 |
101 | Switching Protocols | 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议 |
200 | OK | 请求成功。一般用于GET与POST请求 |
201 | Created | 已创建。成功请求并创建了新的资源 |
202 | Accepted | 已接受。已经接受请求,但未处理完成 |
203 | Non-Authoritative Information | 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本 |
204 | No Content | 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 |
205 | Reset Content | 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 |
206 | Partial Content | 部分内容。服务器成功处理了部分GET请求 |
300 | Multiple Choices | 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 |
301 | Moved Permanently | 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 |
302 | Found | 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI |
303 | See Other | 查看其它地址。与301类似。使用GET和POST请求查看 |
304 | Not Modified | 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 |
305 | Use Proxy | 使用代理。所请求的资源必须通过代理访问 |
306 | Unused | 已经被废弃的HTTP状态码 |
307 | Temporary Redirect | 临时重定向。与302类似。使用GET请求重定向 |
400 | Bad Request | 客户端请求的语法错误,服务器无法理解 |
401 | Unauthorized | 请求要求用户的身份认证 |
402 | Payment Required | 保留,将来使用 |
403 | Forbidden | 服务器理解请求客户端的请求,但是拒绝执行此请求 |
404 | Not Found | 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 |
405 | Method Not Allowed | 客户端请求中的方法被禁止 |
406 | Not Acceptable | 服务器无法根据客户端请求的内容特性完成请求 |
407 | Proxy Authentication Required | 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 |
408 | Request Time-out | 服务器等待客户端发送的请求时间过长,超时 |
409 | Conflict | 服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突 |
410 | Gone | 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 |
411 | Length Required | 服务器无法处理客户端发送的不带Content-Length的请求信息 |
412 | Precondition Failed | 客户端请求信息的先决条件错误 |
413 | Request Entity Too Large | 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息 |
414 | Request-URI Too Large | 请求的URI过长(URI通常为网址),服务器无法处理 |
415 | Unsupported Media Type | 服务器无法处理请求附带的媒体格式 |
416 | Requested range not satisfiable | 客户端请求的范围无效 |
417 | Expectation Failed | 服务器无法满足Expect的请求头信息 |
500 | Internal Server Error | 服务器内部错误,无法完成请求 |
501 | Not Implemented | 服务器不支持请求的功能,无法完成请求 |
502 | Bad Gateway | 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应 |
503 | Service Unavailable | 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 |
504 | Gateway Time-out | 充当网关或代理的服务器,未及时从远端服务器获取请求 |
505 | HTTP Version not supported | 服务器不支持请求的HTTP协议的版本,无法完成处理 |
之所以会发送2次请求,那是因为我们使用了带预检(Preflighted)的跨域请求。该请求会在发送真实的请求之前发送一个类型为OPTIONS的预检请求。预检请求会检测服务器是否支持我们的真实请求所需要的跨域资源,唯有资源满足条件才会发送真实的请求。
相同点:
存储在客户端
不同点
Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。
Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。
if (typeof(Worker) !== "undefined") {
// Yes! Web worker support!
// Some code.....
} else {
// Sorry! No Web Worker support..
}
let i = 0;
function timedCount() {
i ++;
postMessage(i); //用于将消息发送回 HTML 页面。
setTimeout("timedCount()",500);
}
timedCount();
if (typeof(w) == "undefined") {
w = new Worker("demo_workers.js");
}
完整
<!DOCTYPE html>
<html>
<body>
<p>Count numbers: <output id="result"></output></p>
<button onclick="startWorker()">Start Worker</button>
<button onclick="stopWorker()">Stop Worker</button>
<script>
let w;
function startWorker() {
if (typeof(w) == "undefined") {
w = new Worker("demo_workers.js");
}
//向 web worker 添加一个 "onmessage" 事件侦听器。
//当 Web Worker 发布消息时,将执行事件侦听器中的代码。来自 Web Worker 的数据存储在 event.data 中。
w.onmessage = function(event) {
document.getElementById("result").innerHTML = event.data;
};
}
function stopWorker() {
w.terminate();
w = undefined;
}
</script>
</body>
</html>
HTML5 语义化标签是指正确的标签包含了正确的内容,结构良好,便于阅读,比如nav表示导航条,类似的还有 article、header、footer 等等标签。
语义化的优点:
定义: iframe 元素会创建包含另一个文档的内联框架
提示: 可以将提示文字放在 < iframe> < /iframe> 之间,来提示某些不支持iframe 的浏览器
缺点:
Doctype 声明于文档最前面,告诉浏览器以何种方式来渲染页面,这里有两种模式,严格模式和混杂模式。
XSS(跨站脚本攻击)是指攻击者在返回的 HTML 中嵌入 javascript 脚本,为了减轻这些攻击,需要在 HTTP 头部配上,set-cookie:
httpOnly
-这个属性可以防止 XSS,它会禁止 javascript 脚本来访问cookiesecure
- 这个cookie只能用https协议发送给服务器,用http协议是不发送的HTTP 是一个无状态协议,因此 Cookie
的最大的作用就是存储; sessionId
用来唯一标识用户。
就是用 URL 定位资源,用 HTTP对服务器端的资源进行操作,实现服务器端上资源的“表现层状态转移”
具体可以参考下面这篇文章
https://github.com/forthealllight/blog/issues/13
上面文章总结: 常用的自适应解决方案包括媒体查询、百分比、rem和vw/vh等。
像素:我们在js或者css代码中使用的px单位就是指的是css像素,物理像素也称设备像素,只与设备或者说硬件有关,同样尺寸的屏幕,设备的密度越高,物理像素也就越多。
视口:广义的视口,是指浏览器显示内容的屏幕区域,狭义的视口包括了布局视口、视觉视口和理想视口
分辨率
,给定设备物理像素的情况下,最佳的“布局视口”DPR(Device pixel ratio)设备像素比
1 DPR = 物理像素/分辨率
1 CSS像素 = 物理像素/分辨率 (不缩放的情况下)
在pc端的布局视口通常情况下为980px,移动端以iphone6为例,分辨率为375 * 667,也就是说布局视口在理想的情况下为375px。比如现在我们有一个750px * 1134px的视觉稿,那么在pc端,一个css像素可以如下计算:
PC端: 1 CSS像素 = 物理像素/分辨率 = 750 / 980 =0.76 px
iphone6:1 CSS像素 = 物理像素 /分辨率 = 750 / 375 = 2 px
不同的移动设备分辨率不同,也就是1个CSS像素可以表示的物理像素是不同的,因此如果在css中仅仅通过px作为长度和宽度的单位,造成的结果就是无法通过一套样式,实现各端的自适应
使用@media媒体查询可以针对不同的媒体类型定义不同的样式,特别是响应式页面,可以针对不同屏幕的大小,编写多套样式,从而达到自适应的效果
但是媒体查询的缺点也很明显,如果在浏览器大小改变时,需要改变的样式太多,那么多套样式代码会很繁琐。
为了了解百分比布局,首先要了解的问题是:
height或width
,是相对于子元素的直接父元素的height或widthtop和bottom 、left和right
高度
宽度
padding或margin
:子元素的padding或者margin如果设置百分比,不论是垂直方向或者是水平方向,都相对于直接父亲元素的width
,而与父元素的height无关border-radius
:如果设置border-radius为百分比,则是相对于自身的宽度rem是一个灵活的、可扩展的单位,由浏览器转化像素并显示。与em单位不同,rem单位无论嵌套层级如何,都只相对于浏览器的根元素(HTML元素)的font-size。当页面的size发生变化时,只需要改变font-size的值,那么以rem为固定单位的元素的大小也会发生响应的变化。默认情况下,html元素的font-size为16px,所以:
1 rem = 16px
html{ font-size: 62.5% } //为了计算方便,通常可以将html的font-size设置62.5%
1 rem = 10px //上述情况下
但是在使用时,如果我们使用rem单位,每次都要把设计稿的px计算之后转换为rem,这样就很麻烦,我们可以在使用的时候依旧用px单位,然后把px再转化为rem
转换的插件:px2rem-loader
postcss-loader
vw
相对于视窗的宽度,视窗宽度是100vwvh
相对于视窗的高度,视窗高度是100vhvmin
vw和vh中的较小值vmax
vw和vh中的较大值vw单位换算
1px = (1/375)*100 vw
postcss-px-to-viewport
插件原因:2007年苹果发布首款Iphone上ios搭载的safari,采用了双击缩放的方案。
手机端浏览器不能区分用户的单机操作还是双击操作,所以设置了300ms的延迟时间,用来判断用户是点击还是双击。浏览器会在捕获用户第一次单击时,开启300ms定时,若300ms捕获不到第二次单继,则判断用户就是单击操作;若在300ms内,用户有第二次单继操作,则对区域进行缩放操作
解决方法:
<meta name="viewport" content="width=device-width,user-scalable=no">
touch-action:none那么当触控事件发生在元素上时,不进行任何操作——即不会出现滑动和缩放的效果
touch-action:none; //
FastClick
插件原理: 在检测到touchend事件的时候,会通过DOM自定义事件立即发出模拟一个click事件,并把300ms之后发出的click事件阻止掉。
但是使用FastClick,就会发现一个缺点: 在某些ios上,点击输入框启动键盘,触点不是很灵敏,必须重压或者长按才能成功唤启,快速点击是不会唤起软键盘的
解决: 在引用fastClick模块后,重写focus方法
addEventListener(event, function, useCapture)
event 指定事件名;function 指定要事件触发时执行的函数;useCapture 指定事件是否在捕获或冒泡阶段执行。
Accept 可接受的响应内容类型
(Content-Types)。
Accept: application/json 浏览器可以接受服务器回发的类型为 application/json。
Accept: / 代表浏览器可以处理所有类型,(一般浏览器发给服务器都是发这个)。
Accept-Charset
可接受的字符集
Accept-Encoding
可接受的响应内容的编码方式。
Accept-Encoding: gzip, deflate 浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate),(注意:这不是只字符编码)。
Accept-Language
可接受的响应内容语言列表。
Accept-Language:zh-CN,zh;q=0.9 浏览器申明自己接收的语言。
Accept-Datetime 可接受的按照时间来表示的响应内容版本
Authorization 用于表示 HTTP 协议中需要认证资源的认证信息
Cache-Control
用来指定当前的请求/回复中的,是否使用缓存机制。
Connection
客户端(浏览器)想要优先使用的连接类型
Connection: keep-alive 当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。
Connection: close 代表一个Request完成后,客户端和服务器之间用于传输HTTP数据的TCP连接会关闭, 当客户端再次发送Request,需要重新建立TCP连接。
Cookie
由服务器通过Set-Cookie设置,用来存储一些用户信息以便让服务器辨别用户身份的
Content-Length 以 8 进制表示的请求体的长度
Content-MD5 请求体的内容的二进制 MD5 散列值(数字签名),以Base64 编码的结果
Content-Type
请求体的 MIME 类型 (用于 POST 和PUT 请求中)
Date 发送该消息的日期和时间(以 RFC 7231 中定义的"HTTP 日期"格式来发送)
Expect 表示客户端要求服务器做出特定的行为
From 发起此请求的用户的邮件地址
Host
表示服务器的域名以及服务器所监听的端口号。如果所请求的端口是对应的服务的标准端口(80),则端口号可以省略。
If-Match 仅当客户端提供的实体与服务器上对应的实体相匹配时,才进行对应的操作。主要用于像 PUT 这样的方法中,仅当从用户上次更新某个资源后,该资源未被修改的情况下,才更新该资源。
If-Modified-Since 允许在对应的资源未被修改的情况下返回304 未修改
If-None-Match 允许在对应的内容未被修改的情况下返回304 未修改(304 Not
Modified ),参考 超文本传输协议的实体标记
If-Range 如果该实体未被修改过,则向返回所缺少的那一个或多个部分。否则,返回整个新的实体
If-Unmodified-Since仅当该实体自某个特定时间以来未被修改的情况下,才发送回应。
Max-Forwards 限制该消息可被代理及网关转发的次数。
Origin 发起一个针对跨域资源共享的请求(该请求要求服务器在响应中加入一个 Access-Control-Allow-Origin 的消息头,表示访问控制所允许的来源)。
Pragma 与具体的实现相关,这些字段可能在请求/回应链中的任何时候产生。
Proxy-Authorization 用于向代理进行认证的认证信息。
Range 用于断点续传
bytes=0-5 指定第一个字节的位置和最后一个字节的位置。用于告诉服务器自己想取对象的哪部分。始。
Referer
当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器我是从哪个页面链接过来的,服务器籍此可以获得一些信息用于处理
TE 浏览器预期接受的传输时的编码方式:可使用回应协议头Transfer-Encoding 中的值(还可以使用"trailers"表示数据传输时的分块方式)用来表示浏览器希望在最后一个大小为0 的块之后还接收到一些额外的字段。
User-Agent
User-Agent:Mozilla/…,告诉HTTP服务器, 客户端使用的操作系统和浏览器的名称和版本
Upgrade 要求服务器升级到一个高版本协议。
Via 告诉服务器,这个请求是由哪些代理发出的。
Warning 一个一般性的警告,表示在实体内容体中可能存在错误。
Cache-Control(对应请求中的Cache-Control)
Content-Type
Content-Type: text/html;charset=UTF-8 告诉客户端,资源文件的类型,还有字符编码,客户端通过utf-8对资源进行解码,然后对资源进行html解析。通常我们会看到有些网站是乱码的,往往就是服务器端没有返回正确的编码。
Content-Encoding
Content-Encoding:gzip 告诉客户端,服务端发送的资源是采用gzip编码的,客户端看到这个信息后,应该采用gzip对资源进行解码。
Date
Date: Tue, 03 Apr 2020 03:52:28 GMT 这个是服务端发送资源时的服务器时间,GMT是格林尼治所在地的标准时间。http协议中发送的时间都是GMT的,这主要是解决在互联网上,不同时区在相互请求资源的时候,时间混乱问题。
Server
Server:Tengine/1.4.6 这个是服务器和相对应的版本,只是告诉客户端服务器信息。
Transfer-Encoding
Transfer-Encoding:chunked 这个响应头告诉客户端,服务器发送的资源的方式是分块发送的。一般分块发送的资源都是服务器动态生成的,在发送时还不知道发送资源的大小,所以采用分块发送,每一块都是独立的,独立的块都能标示自己的长度,最后一块是0长度的,当客户端读到这个0长度的块时,就可以确定资源已经传输完了。
Expires
Expires:Sun, 1 Jan 1994 01:00:00 GMT 这个响应头也是跟缓存有关的,告诉客户端在这个时间前,可以直接访问缓存副本,很显然这个值会存在问题,因为客户端和服务器的时间不一定会都是相同的,如果时间不同就会导致问题。所以这个响应头是没有Cache-Control:max-age=*这个响应头准确的,因为max-age=date中的date是个相对时间,不仅更好理解,也更准确。
Last-Modified
Last-Modified: Dec, 26 Dec 2019 17:30:00 GMT 所请求的对象的最后修改日期(按照 RFC 7231 中定义的“超文本传输协议日期”格式来表示)
Connection
Connection:keep-alive 这个字段作为回应客户端的Connection:keep-alive,告诉客户端服务器的tcp连接也是一个长连接,客户端可以继续使用这个tcp连接发送http请求。
Etag
ETag: “637060cd8c284d8af7ad3082f209582d” 就是一个对象(比如URL)的标志值,就一个对象而言,比如一个html文件,如果被修改了,其Etag也会别修改,所以,ETag的作用跟Last-Modified的作用差不多,主要供WEB服务器判断一个对象是否改变了。比如前一次请求某个html文件时,获得了其 ETag,当这次又请求这个文件时,浏览器就会把先前获得ETag值发送给WEB服务器,然后WEB服务器会把这个ETag跟该文件的当前ETag进行对比,然后就知道这个文件有没有改变了。
Refresh
Refresh: 5; url=http://baidu.com 用于重定向,或者当一个新的资源被创建时。默认会在5秒后刷新重定向。
Access-Control-Allow-Origin
Access-Control-Allow-Origin: * 号代表所有网站可以跨域资源共享,如果当前字段为那么Access-Control-Allow-Credentials就不能为true
Access-Control-Allow-Origin: www.baidu.com 指定哪些网站可以跨域资源共享
Access-Control-Allow-Methods
Access-Control-Allow-Methods:GET,POST,PUT,DELETE 允许哪些方法来访问
Access-Control-Allow-Credentials
Access-Control-Allow-Credentials: true 是否允许发送cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。如果access-control-allow-origin为*,当前字段就不能为true
Content-Range
Content-Range: bytes 0-5/7877 指定整个实体中的一部分的插入位置,它也指示了整个实体的长度。在服务器向客户返回一个部分响应,它必须描述响应覆盖的范围和整个实体长度。
缓存类型 | 获取资源形式 | 状态码 | 发送请求到服务器 |
---|---|---|---|
强缓存 | 从缓存取 | 200(from cache) | 否,直接从缓存取 |
协商缓存 | 从缓存取 | 304(not modified) | 是,通过服务器来告知缓存是否可用 |
强缓存相关字段有 expires,cache-control。如果 cache-control 与expires 同时存在的话,cache-control 的优先级高于 expires。
协商缓存相关字段有 Last-Modified/If-Modified-Since,Etag/If-None-Match
强缓存是利用http头中的Expires和Cache-Control两个字段来控制的,用来表示资源的缓存时间。强缓存中,普通刷新会忽略它,但不会清除它,需要强制刷新。浏览器强制刷新,请求会带上Cache-Control:no-cache和Pragma:no-cache
协商缓存就是由服务器来确定缓存资源是否可用,所以客户端与服务器端要通过某种标识来进行通信,从而让服务器判断请求资源是否可以缓存访问。
普通刷新会启用弱缓存,忽略强缓存。只有在地址栏或收藏夹输入网址、通过链接引用资源等情况下,浏览器才会启用强缓存,这也是为什么有时候我们更新一张图片、一个js文件,页面内容依然是旧的,但是直接浏览器访问那个图片或文件,看到的内容却是新的。
参考:https://segmentfault.com/a/1190000008956069
301 代表永久性转移(Permanently Moved)
302 代表暂时性转移(Temporarily Moved )
参考:https://blog.csdn.net/grandPang/article/details/47448395
301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取(用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)——这是它们的共同点。
他们的不同在于。301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址;302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。
当一个网站或者网页24—48小时内临时移动到一个新的位置,这时候就要进行302跳转
使用301跳转的场景:
1)域名到期不想续费(或者发现了更适合网站的域名),想换个域名。
2)在搜索引擎的搜索结果中出现了不带www的域名,而带www的域名却没有收录,这个时候可以用301重定向来告诉搜索引擎我们目标的域名是哪一个。
3)空间服务器不稳定,换空间的时候。
从网站A(网站比较烂)上做了一个302跳转到网站B(搜索排名很靠前),这时候有时搜索引擎会使用网站B的内容,但却收录了网站A的地址,这样在不知不觉间,网站B在为网站A作贡献,网站A的排名就靠前了
301跳转对查找引擎是一种对照驯良的跳转编制,也是查找引擎能够遭遇的跳转编制,它告诉查找引擎,这个地址弃用了,永远转向一个新地址,可以转移新域名的权重。而302重定向很容易被搜索引擎误认为是利用多个域名指向同一网站,那么你的网站就会被封掉,罪名是“利用重复的内容来干扰Google搜索结果的网站排名”。
当客户第一次请求服务器资源,服务器成功返回资源,这时状态码为200。所以,状态码为200的数据包往往包含用户从服务器获取的数据。
每个资源请求完成后,通常会被缓存在客户端,并会记录资源的有效时间和修改时间。当客户再次请求该资源,客户端首先从缓存中查找该资源。如果该资源存在,并且在有效期,则不请求服务器,就不会产生对应的请求数据包。
如果不在有效期,客户端会请求服务器,重新获取。服务器会判断修改时间,如果没有修改过,就会返回状态码304,告诉客户端该资源仍然有效,客户端会直接使用缓存的资源。针对304的响应,渗透人员可以分析对应的请求包,获取资源路径。如果该资源不限制访问,就可以直接请求获取。否则,就需要进行Cookie劫持,进行获取。
div {
width:0px;
height:0px;
border-top:10px solid red;
border-right:10px solid transparent;
border-bottom:10px solid transparent;
border-left:10px solid transparent;
}
首先在浏览器中输入URL
查找缓存:浏览器先查看浏览器缓存-系统缓存-路由缓存中是否有该地址页面,如果有则显示页面内容。如果没有则进行下一步。
DNS域名解析:浏览器向DNS服务器发起请求,解析该URL中的域名对应的IP地址。DNS服务器是基于UDP的,因此会用到UDP协议。
建立TCP连接:解析出IP地址后,根据IP地址和默认80端口,和服务器建立TCP连接
发起HTTP请求:浏览器发起读取文件的HTTP请求,,该请求报文作为TCP三次握手的第三次数据发送给服务器
服务器响应请求并返回结果:服务器对浏览器请求做出响应,并把对应的html文件发送给浏览器
关闭TCP连接:通过四次挥手释放TCP连接
浏览器渲染:客户端(浏览器)解析HTML内容并渲染出来,浏览器接收到数据包后的解析流程为:
JS引擎解析过程:调用JS引擎执行JS代码(JS的解释阶段,预处理阶段,执行阶段生成执行上下文,VO,作用域链、回收机制等等)
参考:http://www.javashuo.com/article/p-asqnzngi-mg.html
二进制分帧
对性能优化的贡献
:二进制分帧主要是为下文中的各类特性提供了基础。它能把一个数据划分封装为更小更便捷的数据。首先是在单连接多资源方式中,减小了服务端的连接压力,内存占用更少,连接吞吐量更大。 另外一方面,因为TCP连接的减小而使网络拥塞状态得以改善,同时慢启动时间的减小。使拥塞和丢包恢复的速度更快首部压缩
对性能优化的贡献
: 使报头更紧凑,更快速传输,有利于移动网络环境。减小每次通信的数据量,使网络拥塞状态得以改善
流量控制
多路复用
对性能优化的贡献
:
请求优先级
对性能优化的贡献
:服务器能够根据流的优先级控制资源分配(CPU、内存、宽带),而在响应数据准备好以后,优先将最高优先级的帧发送给客户端。浏览器能够在发现资源时当即分派请求,指定每一个流的优先级,让服务器决定最优的响应次序。这样请求就不用排队了,既节省了时间,又最大限度的利用了每一个链接。服务器推送
对性能优化的贡献
:服务端推送是一种在客户端请求以前发送数据的机制。在HTTP2.0中,服务器能够对一个客户端的请求发送多个响应。若是一个请求是由你的主页发送的,服务器可能会响应主页内容、logo以及样式表,由于他知道客户端会用到这些东西。这样不但减轻了数据传送冗余步骤,也加快了页面响应的速度,提升了用户体验cache-control 是一个通用消息头字段被用于 HTTP 请求和响应中,通过指定指令来实现缓存机制,这个缓存指令是单向的,常见的取值有 private、no-cache、max-age、must-revalidate 等,默认为 private。
构造两棵树,DOM 树和 CSSOM 规则树,
当浏览器接收到服务器相应来的 HTML 文档后,会遍历文档节点,生成DOM树,CSSOM 规则树由浏览器解析 CSS 文件生成。
防范:
304:如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个304 状态码。
encodeURI()
html5:
css3:
IE盒模型中,盒子宽度等于内容区宽度、内边距和边框三部分的总和,如下所示
可以借助CSS3的box-sizing属性切换盒模型,如下所示
<meta name="viewport"
content="width=device-width,
initial-scale=0.5, minimum-scale=0.5, maximum-scale=0.5"/>
这样子就能缩放到原来的0.5倍,如果是1px那么就会变成0.5px。,但这样也意味着UI需要按2倍图的出,整体面面的单位都会放大一倍。
transform: scale(0.5,0.5);
使用boxshadow
设置box-shadow的第二个参数为0.5px,表示阴影垂直方向的偏移为0.5px
<style>
.boxshadow {
height: 1px;
background: none;
box-shadow: 0 0.5px 0 #000;
}
style>
<p>box-shadow: 0 0.5px 0 #000p>
<div class="boxshadow">div>
采用 border-image的方式
首先需要自己制作一个0.5像素的线条作为线条背景图片;
使用background-image结合SVG的方式
使用svg的line元素画线,stroke表示描边颜色,默认描边宽度stroke-width=“1”,由于svg的描边等属性的1px是物理像素的1px,相当于高清屏的0.5px
这样在Chrome在能很好的显示,但在firefox挂了,究其原因是因为firefox的background-image如果是svg的话只支持命名的颜色,如"black"、"red"等,如果把上面代码的svg里面的#000改成black的话就可以显示出来,但是这样就很不灵活了。因此,只能把svg转成base64的形式,最终如下:
.hr.svg {
background: url("data:image/svg+xml;utf-8,
<svg xmlns='http://www.w3.org/2000/svg'
width='100%' height='1px'>
<line x1='0' y1='0' x2='100%' y2='0' stroke='#000'>
line>
svg>");
//使用base64来使得支持firefox
background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMDAlJyBoZWlnaHQ9JzFweCc+PGxpbmUgeDE9JzAnIHkxPScwJyB4Mj0nMTAwJScgeTI9JzAnIHN0cm9rZT0nIzAwMCc+PC9saW5lPjwvc3ZnPg==");
}
Animation 和 transition 大部分属性是相同的,他们都是随时间改变元素的属性值,他们的主要区别是
transition 需要触发一个事件才能改变属性,而 animation 不需要触发任何事件的情况下才会随时间改变属性值
transition 只有两个状态:开始状态 和 结束状态 。animation 可能是多个状态, 有帧的概念 。
animation 控制动效上要比 transition 强,因为它具备一些控制动效的属性,比如“播放次数”、“播放方向”、“播放状态”等。
以下6个属性设置在容器上
flex-direction
:决定主轴的方向 ,取值 row | row-reverse | column | column-reverse
flex-wrap
: 如果一条轴线排不下,如何换行 ,取值 nowrap | wrap | wrap-reverse
flex-flow
:flex-direction
和flex-wrap
的简写形式,默认值为row nowrap
justify-content
:项目在主轴上的对齐方式,取值 flex-start | flex-end | center | space-between | space-around;
align-items
:项目在交叉轴上如何对齐,取值flex-start | flex-end | center | baseline | stretch
align-content
:多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用,取值flex-start | flex-end | center | space-between | space-around | stretch
以下6个属性设置在项目上
order
:项目的排列顺序。数值越小,排列越靠前,默认为0flex-grow
:项目的放大比例,默认为0,即如果存在剩余空间,也不放大。如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。flex-shrink
: 项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小flex-basis
:定义了在分配多余空间之前,项目占据的主轴空间。览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。它可以设为跟width或height属性一样的值flex
:flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选;该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。align-self
:允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。取值auto | flex-start | flex-end | center | baseline | stretch
简单的理解:这是一个独立的渲染区域,规定了内部如何布局,并且这个区域的子元素不会影响到外面的元素,其中比较重要的布局规则有内部box 垂直放置
生成BFC的元素
position: absolute;
margin: auto;
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
top: 50%;
left: 50%;
margin-top: -190px; /*height 的一半*/
margin-left: -240px; /*width 的一半*/
这里也可以将 marin-top 和 margin-left 负值替换成,transform:translateX(-50%)和 transform:translateY(-50%)
渲染线程分为 main thread (主线程)和 compositor thread(复合线程),如果 css 动画只改变transform和opacity,在Chromium基础上的浏览器中,这时整个 CSS 动画得以在复合线程 完成(而 JS 动画则会在主线程执行,然后触发复合线程进行下一步操作),特别注意的是如果改变transform和opacity是不会 layout(布局) 或者 paint (绘制)的。 如果在CSS动画或JS动画都触发了paint或layout时,需要main thread进行Layer树的重计算,这时CSS动画或JS动画都会阻塞后续操作
区别:
1、js动画的控制能力比css3动画强;
2、js动画的效果比css3动画丰富;
3、js动画大多数情况下没有兼容性问题,而css3动画有兼容性问题;
4、js动画的复杂度高于css3动画。
js动画
优点:
缺点:
CSS3动画
优点:
缺点:
css动画比js动画流畅的前提
单行
white-space: nowrap; /* 文本超过容器最大宽度不换行(若文本自动显示在一行则不需要这个属性) */
overflow: hidden; /* 本文超出容器最大宽度的部分不显示 */
text-overflow: ellipsis; /* 文本超出容器最大宽度时显示三个点 */
多行
overflow:hidden;
display:-webkit-box; /* 让容器变成一个弹性伸缩盒子 */
-webkit-line-clamp:2; /* 最大显示的文本行数 */
-webkit-box-orient:vertical; /* 设置或检索伸缩盒对象的子元素纵向排列 */
opacity:0
,透明度为0,不会改变页面布局,并且,如果该元素已经绑定一些事件,如 click 事件,那么点击该区域,也能触发点击事件的visibility:hidden
,不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见。不能点击,子孙元素继承visibility:hidden样式,可设置子孙样式visibility:visibile覆盖祖先的visibility:hidden样式,可见的子孙元素和它本身都绑定click事件时,点击子孙元素会触发子孙的click事件,也会冒泡到visibility:hidden元素上display:none
,会让元素完全从渲染树中消失,渲染的时候不占据任何空间。不能点击,子孙元素不继承该样式,但是由于display:none元素不渲染,所以子孙不可显示多个相邻(兄弟或者父子关系)普通流的块元素垂直方向 marigin 会重叠折叠的结果为:
因为浮动元素脱离了普通流,会出现一种高度坍塌的现象:原来的父容器高度是内部元素撑开的,但是当内部元素浮动后,脱离普通流浮动起来,那父容器的高度就坍塌,变为高度0px。
清除浮动的5种方法
在浮动元素后使用一个空元素设置 .clear{clear:both;}
父级div定义overflow:hidden;或 overflow:auto
为父元素设置容器宽高或设置 zoom:1
。父级div定义height
使用邻接元素处理
,什么都不做,给浮动元素后面的元素添加clear属性。
父级div定义伪元素:after和zoom
,推荐使用这种方法,建议定义公共类,以减少CSS代码
.clearfix:after{
content: "020";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
.clearfix {
/* 触发 hasLayout */
zoom: 1;
}
内联选择器,ID选择器,class选择器,属性选择器,伪类选择器,元素选择器,通配符选择器,继承选择器等
block
display:none; visibility:hidden; opacity: 0; position 移到外部,z-index 涂层遮盖等等
三栏布局:两边固定,中间自适应
flex 实现
: 两边定宽,中间flex:1;首先绘制左右两栏,再绘制中间一栏,主要内容无法最先加载流体布局 float+margin
: 两边定宽,中间设置margin为两边定宽的值;项目绘制按照左中右排列BFC
:左右浮动,中间添加overflow:hidden或者display: flex,因为BFC不会和浮动元素重叠的规则,主要内容无法最先加载position定位
:左右绝对定位,中间设置margin为两边定宽的值;主要内容可以优先加载table布局
:父级设置display:table,三个子元素设置 display: table-cell,左右两栏设置宽度,中间无需设置;缺点就是无法设置栏间距圣杯布局
:中间栏的div要写在最前面, 三栏都使用float:left 进行浮动,左右定宽,中间宽度100%,左栏设置margin-left为-100%,右栏的margin-left为其宽度的负值,这时中间沾满100%,需要父元素设置margin为左右两边的宽度,左右两边再设置为相对相位,就可以把中间的盒子完整显示出来双飞翼布局
:双飞翼布局前两步和圣杯布局一样,只是处理中间栏部分内容被遮挡的问题解决方案不同,只用在中间盒子内部添加一个div,给这个div设置左右margin,即可来避开左右遮挡grid布局
:父元素设置display:grid,子元素设置 grid-template-columns: 200px auto 200px; 有兼容性问题用户动态计算长度值,任何长度值都可以使用 calc()函数计算,需要注意的是,运算符前后都需要保留一个空格,例如:width: calc(100% - 10px)
display:table 和本身 table 是相对应的,区别在于,display:table 的css 声明能够让一个html 元素和它的子节点像 table 元素一样,使用基于表格的 css 布局,是我们能够轻松定义一个单元格的边界,背景等样式,而不会产生因为使用了 table 那样的制表标签导致的语义化问题。
目前,在大多数开发环境中,已经基本不用table元素来做网页布局了,取而代之的是div+css,那么为什么不用table系表格元素呢?
z-index属性只能在设置了position: relative | absolute | fixed的元素和父元素设置了 display: flex属性的子元素中起作用,在其他元素中是不作用的。
可以更改父元素的 color
line-height 一般是指布局里面一段文字上下行之间的高度,是针对字体来设置的,height 一般是指容器的整体高度。
background-color 设置的背景颜色会填充元素的 content、padding、border 区域
img确实是行内元素 但它也是置换元素;置换元素就是浏览器根据元素的标签和属性,来决定元素的具体显示内容。置换元素拥有内在尺寸,内置宽高 他们可以设置width/height属性。他们的性质同设置了display:inline-block的元素一致。
回流:也叫做重排,当我们对 DOM 的修改引发了 DOM 尺寸的变化时,浏览器需要重新计算元素的几何属性,其他元素的几何属性和位置也会因此受到影响,然后再将计算的结果绘制出来。这个过程就是回流
重绘:改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。这个过程就叫重绘
重绘不一定导致回流,回流一定会导致重绘。
减少重绘和回流的方法
boder的内边缘
主要取值有 none,block,inline-block,inline,flex 等
/* precomposed values */
display: block;
display: inline;
display: inline-block;
display: flex;
display: inline-flex;
display: grid;
display: inline-grid;
display: flow-root;
/* box generation */
display: none;
display: contents;
/* two-value syntax */
display: block flow;
display: inline flow;
display: inline flow-root;
display: block flex;
display: inline flex;
display: block grid;
display: inline grid;
display: block flow-root;
/* other values */
display: table;
display: table-row; /* all table elements have an equivalent CSS display value */
display: list-item;
/* Global values */
display: inherit;
display: initial;
display: revert;
display: revert-layer;
display: unset;
六种布局方式总结:圣杯布局、双飞翼布局、Flex 布局、绝对定位布局、表格布局、网格布局。
less,sass 等
误区:我们经常说 get 请求参数的大小存在限制,而 post 请求的参数大小是无限制的。实际上 HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对get 请求参数的限制是来源与浏览器或 web 服务器,浏览器或 web 服务器限制了 url 的长度。为了明确这个概念,我们必须再次强调下面几点:
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建一个函数,通过内部函数访问外部函数的局部变量。
闭包中外部函数返回内部函数,也可以返回对象中的函数
闭包的优缺点
优点:
缺点:
解决方法:
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
这样就生成了一个 Animal 类,实力化生成对象后,有方法和属性
1. 原型链继承:
直接让子类的原型对象指向父类实例。当子类实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承
function Cat(){ }
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name);//cat
console.log(cat.eat('fish')); //cat正在吃:fish
console.log(cat.sleep());//cat正在睡觉!
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
优点:通过原型链继承的方式,原先存在父类型的实例中的所有属性和方法,现在也能存在于子类型的原型中了
缺点:
2. 构造函数继承:
在子类的构造函数中执行父类的构造函数,并为其绑定子类的this,通过call()函数,改变this指针的指向,让父类的构造函数把成员属性和方法都挂到子类的this上去
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name); //Tom
console.log(cat.sleep());//Tom正在睡觉!
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
优点:可以实现多继承,避免实例之间共享一个原型实例,能向父类构造方法传参
缺点:只能继承父类实例的属性和方法,不能继承原型上的属性和方法。
3. 组合继承
: 相当于构造函数继承和原型链继承的组合体。通过调用父类构造函数,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// Test Code
var cat = new Cat();
console.log(cat.name);//Tom
console.log(cat.sleep());//Tom正在睡觉!
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
优点:可以继承实例属性/方法,也可以继承原型属性/方法;组合继承拥有上面两种方法的优点。同时还能避免上面两种方法的缺点。
缺点:调用了两次父类构造函数,生成了两份实例
4. 寄生式继承
: 使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。
let parent5 = {
name: "parent5",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
function clone(original) {
let clone = Object.create(original); //用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)
clone.getFriends = function() {
return this.friends;
};
return clone;
}
let person5 = clone(parent5);
console.log(person5.getName());
console.log(person5.getFriends());
优点:解决了组合继承中每次创建子类实例都执行了两次构造函数
缺点:
5. 寄生组合继承
: 使用寄生式继承来继承父类的原型,将结果指定给子类型的原型;砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性
function Parent6() {
this.name = 'parent6';
this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {
return this.name;
}
function Child6() {
Parent6.call(this);
this.friends = 'child6';
}
function clone (parent, child) {
// 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
}
clone(Parent6, Child6);
Child6.prototype.getFriends = function () {
return this.friends;
}
let person6 = new Child6();
console.log(person6); //{name: 'parent6', play: [1, 2, 3], friends: 'child6'}
console.log(person6.getName()); //parent6
console.log(person6.getFriends()); //child6
优点:
1. 下面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法
class Box{//类名首字母必须大写,驼峰式命名
a=1;//描述改对象的属性值,ES7支持,不需要let或者var
constructor(a,b){// 构造函数
console.log(a+b);//8
}
play(){
console.log(this===b)//this 就是通过构造函数实例化对象b,谁调用该方法,this就是谁
}
}
let b=new Box(5,3) //实例化,当实例化时执行构造函数constructor
b.play()
console.log(b.constructor === Box)//true
//对象的构造函数就是当前的类名
console.log(b.constructor === Box.prototype.constructor)//true
//b是Box类的实例,它的constructor方法就是Box类原型的constructor方法
2. 因为js中构造函数就是类名,因此我们可以根据对象的构造函数是否是某个类名来判断它是否属于该类
//通过实例化创建的对象就是一个类,可以根据构造函数判断是否属于该类
var arr=new Array(1,2,3);
console.log(arr.constructor.name);//Array
//单独打印constrctor是一个函数
//构造函数有一个name属性就是这个类的类名
console.log(arr.constructor===Array);//true
var date=new Date();
console.log(date.constructor===Date);//true
var reg=/a/g;
console.log(reg.constructor===RegExp);//true
var div=document.createElement("div");
console.log(div.constructor===HTMLDivElement);//true
3. 类中除了函数中的局部变量就是类中的属性,类中的this就是实例化对象
//以下代码只为解释定义一个人类的类,并且去如何使用它,中文编程万万不可以
class man{
eyes=2;
mouse=1;
ear=2;
hands=2;
foots=2;
name;//创建了一个man的类,有以上属性
constructor(_name){
this.name=_name;
}
walk(){//man的类有walk() run() eat() fire()的方法
console.log(this.name+"走")
}
run(){
console.log(this.name+"跑")
}
eat(){
console.log(this.name+"吃")
}
fire(){
console.log(this.name+"生火")
}
}
var xiaoming=new man("小明");
//创建一个xioaming的实例化对象,具备这个man的类的所有方法和属性,可以直接使用
xiaoming.eat();
xiaoming.run();
var xiaohua=new man("小花");
//创建了一个xioahua的实例化对象
xiaohua.run();
xiaohua.fire();
Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
比如,要继承Parent类,只需要使用extends就可以继承过来Parent的属性和方法了,继承父类后,必须在构造函数中使用super()调用超类的构造函数,超类的是什么,这个就执行的是什么;需要注意的是这里的super就可以理解为调用父类的constructor ,所以里面会传一些和父类相同的参数,而且必须写在最前面
ES6 的继承必须先调用super(),这样会生成一个继承父类的this对象,没有这一步就无法继承父类。这意味着新建子类实例时,父类的构造函数必定会先运行一次
class Child extends Parent{
constructor(r){
// 继承父类后,必须在构造函数中调用超类的构造函数
super();//超类的构造函数,超类是什么,这个就是什么
//超类的构造函数如果有参数的,在这里子类中必须传入这个需要参数
}
}
具体可查看该篇文章https://blog.csdn.net/weixin_44157964/article/details/103933204
promise、generator、async/await
HTML 中与 javascript 交互是通过事件驱动来实现的,例如鼠标点击事件onclick、页面的滚动事件 onscroll 等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念
什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2 级事件流包括下面几个阶段
什么是冒泡?
冒泡是从下往上执行的,比如有个子事件和父事件,点击子事件后触发父事件,浏览器是默认事件冒泡的
事件委托
就是不在事件当前的dom上进行事件,而是在父级进行事件监听,通过事件冒泡来触发子事件
最常见的就是往ul 上绑定事件来触发 li
addEventListener
:addEventListener 是 DOM2 级事件新增的指定事件处理程序的操作,这个方法接收 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。
IE 只支持事件冒泡
根据w3c标准,应先捕获再冒泡。若要实现先冒泡后捕获,给一个元素绑定两个addEventListener,其中一个第三个参数设置为false(即冒泡),另一个第三个参数设置为true(即捕获),调整它们的代码顺序,将设置为false的监听事件放在设置为true的监听事件前面即可。
还有一种,在执行捕获时,设置setTimeOut(方法名,0),把它放到下一个宏任务
事件委托指的是,不在事件的发生地(直接 dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。
举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。
好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。
可以在父元素层面阻止事件向子元素传播,也可代替子元素执行某些操作。
懒加载:图片的懒加载指的是在长网页中延迟加载图像,是一种很好的优化网页性能的方式。用户滚动到他们之前,可视区域之外的图像是不会加载的。在某种情况下,他还可以帮助减少服务器负载,常适使图片很多,页面很长的电商网站场景中。
为什么要使用懒加载?
懒加载实现原理
首先先将页面上的图片的src地址设置为空字符串,而图片上的真实路径设置在data-original属性中,当页面
发生滚动时需要去监听scroll事件,在scroll事件的回调中,判断我们的懒加载的图片是否进入可视区域,如
果图片在可视区域的话将src设置为data-original的值,这样就达到了延迟加载的效果。
预加载就是将所需的资源提前加载到本地,这样当用户使用到该资源时,就会自动从缓存中取出。
预加载实现方法
<img src="http://pic26.nipic.com/20121213/6168183 0044449030002.jpg" style="display:none"/>
这样当我们进行加载页面时,也会加载src中的图片资源,此时该图片就会缓存到本地,当我们使用到该图片
时,就会自动读取缓存中的内容。
<script src='./myPreload.js'></script>
//myPreload.js文件
var image = new Image()
image.src="http://pic26.nipic.com/20121213/6168183 004444903000 2.jpg"
两种技术的本质:
参考https://blog.csdn.net/weixin_47450807/article/details/123376938
常用的鼠标事件分为三部分: 鼠标点击事件、鼠标经过事件、鼠标移动事件
鼠标点击事件
鼠标点击事件包括 click(单击)
、dblclick(双击)
、mousedown(按下)
和 mouseup(松开)
四个;其中 click 事件类型比较常用,而mousedown
和 mouseup
事件类型多用在鼠标拖放、拉伸操作中
鼠标经过事件
mouseover
:无论论鼠标指针穿过被选元素或其子元素,都会触发 mouseover 事件;有一个重复触发,冒泡的过程
mouseenter
:只有在鼠标指针穿过被选元素时,才会触发 mouseenter 事件;不会冒泡
mouseout
:不论鼠标指针离开被选元素还是任何子元素,都会触发 mouseout 事件;有一个重复触发,冒泡的过程
mouseleave
:只有在鼠标指针离开被选元素时,才会触发 mouseleave 事件;不会冒泡
鼠标移动事件
mousemove
事件类型是一个实时响应的事件,当鼠标指针的位置发生变化时(至少移动一个像素),就会触发 mousemove
事件。该事件响应的灵敏度主要参考鼠标指针移动速度的快慢以及浏览器跟踪更新的速度
通过 apply 和 call 改变函数的 this 指向,他们两个函数的第一个参数都是一样的表示要改变指向的那个对象,第二个参数,apply 是数组,而 call 则是arg1,arg2…这种形式。 call和apply都会会立即执行该函数
通过 bind 改变 this 作用域会返回一个新的函数,这个函数不会马上执行。需要再调用一次
如果使用call、apply、bind时,第一个参数是null,就意味着将函数中this重定向到window
只有apply的参数是array
如果要设置物体拖拽,那么必须使用三个事件,并且这三个事件的使用顺序不能颠倒。
拖拽的基本原理就是根据鼠标的移动来移动被拖拽的元素。鼠标的移动也就是x、y坐标的变化;元素的移动就是style.position的top和left的改变。当然,并不是任何时候移动鼠标都要造成元素的移动,而应该判断鼠标左键的状态是否为按下状态,是否是在可拖拽的元素上按下的。
也可以通过 html5 的拖放(Drag 和 drop)来实现
因为同步加载存在问题!
JS在默认情况下是以同步模式(又称阻塞模式)加载的,这里“加载”的意思是“解释、执行”。在最新版本的浏览器中,浏览器对于代码请求的资源都是瀑布式的加载,而不是阻塞式的,但是JS的执行总是阻塞的。这会引起什么问题呢?如果在页面中加载一些JS,但其中某个请求迟迟得不到响应,位于此JS后面的JS将无法执行,同时页面渲染也不能继续,用户看到的就是白屏
如果js在标签中,如果JS迟迟无法加载,于是阻塞了后面代码的执行,页面得不到渲染
如果把JS代码放到标签之前(这也是所提倡的页面结构),页面瞬间被渲染,问题似乎解决了,可是…如果我们在引用js代码的后面再写一段js代码,如果上个js请求阻塞了,后面的代码也不会加载,所以问题依然存在:改变JS的加载位置只能改变页面的渲染,JS还是会阻塞。
ajax
发送请求前加上 anyAjaxObj.setRequestHeader("If-Modified-Since","0")
。ajax
发送请求前加上 anyAjaxObj.setRequestHeader("Cache-Control","no-cache")
。 URL
后面加上一个随机数: "fresh=" + Math.random()
。URL
后面加上时间搓:"nowtime=" + new Date().getTime()
。 jQuery
,直接这样就可以了 $.ajaxSetup({cache:false}
)。防抖的应用场景很多:
节流的应用场景:
总之,依然是密集的事件触发,但是这次密集事件触发的过程,不会等待最后一次才进行函数调用,而是会按照一定的频率进行调用
// 通过闭包实现防抖
function debounce(fn, delay) {
let timer = null;
return function () {
if (timer !== null) {
clearTimeout(timer);
}
timer = setTimeout(() => {
// 箭头函数没有自己的this,改变指向,使其指向input。同时执行fn函数
fn.call(this);
}, delay);
};
}
可以看出,防抖的关键点在于:利用闭包,在返回的函数体里,通过不断开启和清除定时器,在限定时间内不断点击,仍然只执行最后一次
function throttle(fn, wait) {
// 获取初始时间
let start = 0;
return function () {
let now = new Date().getTime();
// 判断前后时间戳的差值
if (now - start < wait) return;
fn();
// 重新设置初始时间
start = now;
};
}
必要性:由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript 程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript 的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。
这段话解释了为什么需要系统需要垃圾回收,JS 不像 C/C++,他有自己的一套垃圾回收机制(Garbage Collection)。JavaScript 的解释器可以检测到何时程序不再使用一个对象了,当他确定了一个对象是无用的时候,他就知道不再需要这个对象,可以把它所占用的内存释放掉了
垃圾回收常用的方法:标记清除、计数引用
标记清除
缺点:碎片化。经过多轮空间分配和垃圾回收后,堆从一整片完整的空间被划分为了很多不连续的碎片,这就导致另外一个问题:分配速度受限。因为每次重新分配空间时,都要遍历所有空闲链表,去寻找足够大的分块,最坏的情况下,每次进行分配都要遍历整个空闲链表。
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
计数引用
记录每个值的被引用的次数,当它被初始化,并被赋值给一个变量,引用为 1;如果另外一个变量引用了它,引用加 1;如果一个变量不再引用它,引用减 1。当一个值的引用数变为 0,那就说明它再没法被访问了,因此它成为垃圾,可以放心回收
缺点:无法回收循环引用的对象,造成内存泄漏
function problem() {
let objectA = new Object();
let objectB = new Object();
objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}
在上面这个例子中,objectA 和 objectB 通过各自的属性相互引用,于是它们的引用数都是 2。函数运行结束,objectA 和 objectB 的引用次数各自减 1,但因为它们还引用着彼此,所以它们的引用次数永远不会置为 0,也就无法被回收
还有一些别的GC算法:复制算法、标记 - 整理算法、分代式垃圾回收、增量式垃圾回收
具体参考https://www.jianshu.com/p/20364ba1d7a6
它的功能是将对应的字符串解析成 JS 并执行,如果传入的参数不是字符串,它会原封不动地将其返回
应该避免使用eval,因为非常消耗性能(2次,一次解析成 JS,一次执行)
eval缺点:
前端模块化就是复杂的文件编程一个一个独立的模块,比如 JS 文件等等,分成独立的模块有利于重用(复用性)和维护(版本迭代),这样会引来模块之间相互依赖的问题,所以有了 commonJS 规范,AMD,CMD 规范等等,以及用于 JS 打包(编译等处理)的工具 webpack
JS模块化的演变经历了一个漫长的过程,从最初的CommonJS ,到后来的AMD和CMD,再到今天的ES6模块化方案
开始于服务器端的模块化,同步定义的模块化,每个模块都是一个单独的作用域,模块输出,modules.exports
,模块加载 require()
引入模块
为什么不在浏览器也是用CommonJS ?
CommonJS的 require 语法是同步的,当我们使用require 加载一个模块的时候,必须要等这个模块加载完后,才会执行后面的代码。如果知道这个事实,那我们的问题也就很容易回答了。NodeJS 是服务端,使用 require 语法加载模块,一般是一个文件,只需要从本地硬盘中读取文件,它的速度是比较快的。但是在浏览器端就不一样了,文件一般存放在服务器或者CDN上,如果使用同步的方式加载一个模块还需要由网络来决定快慢,可能时间会很长,这样浏览器很容易进入“假死状态”。所以才有了后面的AMD和CMD模块化方案,它们都是异步加载的,比较适合在浏览器端使用。
1. 导出
CommonJs
中使用module.exports
导出变量及函数,也可以导出任意类型的值
// 导出一个对象
module.exports = {
name: "hh",
age: 18
}
// 导出任意值
module.exports.name = "hh"
导出也可以省略module
关键字,直接写exports
导出也可以
exports.name = "hh"
混合导出,exports
和module.exports
可以同时使用,不会存在问题。
exports.name = "hh"
module.exports.age = 18
2. 导入
// index.js
module.exports.name = "hh"
module.exports.age = 18
let data = require("./index.js")
console.log(data) // { name: "hh", age: 18 }
重复导入:不管是CommonJs还是Es Module都不会重复导入,就是只要该文件内加载过一次这个文件了,我再次导入一次是不会生效的。
let data = require("./index.js")
let data = require("./index.js") // 不会再执行了
动态导入: CommonJs支持动态导入,什么意思呢,就是可以在语句中,使用require语法
let lists = ["./index.js", "./config.js"]
lists.forEach((url) => require(url)) // 动态导入
if (lists.length) {
require(lists[0]) // 动态导入
}
导入值的变化 CommonJs导入的值是拷贝的,所以可以修改拷贝值,但这会引起变量污染,一不小心就重名
3. 总结
CommonJs解决了变量污染,文件依赖等问题,上面我们也介绍了它的基本语法,它可以动态导入(代码发生在运行时),不可以重复导入。
在Es Module中导出分为两种,单个导出(export
)、默认导出(export default
),单个导出在导入时不像CommonJs一样直接把值全部导入进来了,Es Module中可以导入我想要的值。那么默认导出就是全部直接导入进来,当然Es Module中也可以导出任意类型的值。
1. 导出
// 导出变量
export const name = "hh"
// 导出函数也可以
export function fn() {}
export const test = () => {}
// 如果有多个的话
const name = "hh"
const sex = "male"
export { name, sex }
混合导出: 可以使用export
和export default
同时使用并且互不影响,只需要在导入时地方注意,如果文件里有混合导入,则必须先导入默认导出的,在导入单个导入的值
export const name = "hh"
export const age = 18
export default {
fn() {},
msg: "hello hh"
}
2. 导入
Es Module使用的是import
语法进行导入。如果要单个导入则必须使用花括号{}
,注意:这里的花括号跟解构不一样
// index,js
export const name = "hh"
export const age = 24
import { name, age } from './index.js'
console.log(name, age) // "hh" 24
// 如果里面全是单个导出,我们就想全部直接导入则可以这样写
import * as all from './index.js'
console.log(all) // {name: "hh", age: 24}
==混合导入:==混合导入,则该文件内用到混合导入,import语句必须先是默认导出,后面再是单个导出,顺序一定要正确否则报错
。
// index,js
export const name = "hh"
export const age = 24
export default {
msg: "hh"
}
import msg, { name, age } from './index.js'
console.log(msg) // { msg: "hh" }
导入值的变化: export导出的值是值的引用,并且内部有映射关系,这是export关键字的作用。而且导入的值,不能进行修改也就是只读状态。
Es Module是静态: 就是Es Module语句``import只能声明在该文件的最顶部,不能动态加载语句,Es Module`语句运行在代码编译时
if (true) {
import xxx from 'XXX' // 报错
}
3. 总结
Es Module也是解决了变量污染问题,依赖顺序问题,Es Module语法也是更加灵活,导出值也都是导出的引用,导出变量是可读状态,这加强了代码可读性
AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。这里介绍用require.js实现AMD规范的模块化:用require.config()指定引用路径等,用define()定义模块,用require()加载模块。
CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范其实是在sea.js推广过程中产生的。
UMD是AMD和CommonJS的糅合
AMD模块以浏览器第一的原则发展,异步加载模块。CommonJS模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。这迫使人们又想出另一个更通用的模式UMD (Universal Module Definition)。希望解决跨平台的解决方案。
UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。
参考https://juejin.cn/post/6938581764432461854#heading-18
1. json暴力转化
通过JSON.stringify()
和 JSON.parse()
将对象转为字符串之后在转为对象。
var obj = {name:'123'}
var obj2 = JSON.parse(JSON.stringify(obj)
这种简单粗暴的方式有局限性,当值为undefined、function、symbol会在转换过程中被忽略。
2. es6解构赋值
var obj = {name:'123',age:13};
var obj2 = {...obj}
只能深度拷贝对象的第一层,如果对象中的属性也是对象的话,没有办法进行深度拷贝的。
3. for in 循环遍历对象
var obj = {
name: "小明",
age: 20
}
var obj1 = {}
for (var key in obj) {
obj1[key] = obj[key]
}
console.log(obj1);
同样的,只能深度拷贝对象的第一层,如果对象中的属性也是对象的话,没有办法进行深度拷贝的。
4. Object.assign() 对象的合并
利用Object.assign(), 第一个参数必须是空对象
var obj = {name:'123',age:13};
var obj2 = Object.assign({},obj1);
只能深度拷贝对象的第一层,如果对象中的属性也是对象的话,没有办法进行深度拷贝的。
5. 利用循环和递归的方式
function deepClone(obj, newObj) {
var newObj = newObj || {};
for (let key in obj) {
if (typeof obj[key] == 'object') {
newObj[key] = (obj[key].constructor === Array) ? [] : {}
deepClone(obj[key], newObj[key]);
} else {
newObj[key] = obj[key]
}
}
return newObj;
}
在循环递归中需要注意设置临界值(typeof obj[key] == ‘object’),否则会造成死循环
循环递归可以处理对象中嵌套数组或对象的问题。相当于第三种方法的优化
function ones(func){
var tag=true;
return function(){
if(tag==true){
func.apply(null,arguments);
tag=false;
}
return undefined
}
}
var myNewAjax=function(url){
return new Promise(function(resolve,reject){
var xhr = new XMLHttpRequest();
xhr.open('get',url);
xhr.send(data);
xhr.onreadystatechange = function(){
if(xhr.status == 200 && readyState == 4){
var json=JSON.parse(xhr.responseText);
resolve(json)
}else if(xhr.readyState == 4 && xhr.status != 200){
reject('error');
}
}
})
}
1. 在 ES5 中可以通过 Object.defineProperty 来实现已有属性的监听
defineProperty 只能劫持对象的某一个属性,不能对整个对象进行劫持,如果需要监听某一个对象的所有属性,需要遍历对象的所有属性并对其进行劫持来进行监听
let val = user.name
Object.defineProperty(user,'name',{
set(newValue){
val = newValue
},
get(){
return val
}
})
console.log(user.name) //调用get
user.name = 1 //调用set
缺点:如果 id 不在 user 对象中,则不能监听 id 的变化;,数组对象是个特例,监听不到改变
2. 在 ES6 中可以通过 Proxy 来实现
const proxyData = new Proxy(data, {
get(target, prop) {
console.log('%c 调用get', 'color: green')
return Reflect.get(target, prop)
},
set(target, prop, value) {
console.log('%c 调用set', 'color: blue')
return Reflect.set(target, prop, value) // Reflect通过代理对象更改目标对象的属性值
}
})
console.log('proxyData.name -> ', proxyData.name) // Jane
console.log('data.name -> ', data.name) // Jane
proxyData.name = 'Jian'
console.log('proxyData.name -> ', proxyData.name) // Jian
console.log('data.name -> ', data.name) // Jian
设置代理对象的属性后,原始对象和代理对象都发生了变化,但是获取原始对象的属性不会触发 getter ,只有访问代理对象的属性才能触发 getter,但是触发了 getter 和 setter 都是去给原始对象获取属性值和设置属性值,因为这里的 target 就是这个原始对象
3. Object.defineProperty和Proxy的区别
defineProperty 是对属性劫持,proxy 是对对象代理
defineProperty 无法监听对象新增属性,proxy 可以
defineProperty 无法监听对象删除属性,proxy 可以
defineProperty 监听数组的操作需要重载原型方法,proxy 不需要对数组的方法进行重载
proxy 是浏览器支持的原生 API 直接通过浏览器的引擎就可以执行,defineProperty 是循环遍历对象属性的方式来进行监听,自然会比 proxy 对整个对象进行监听的方式要耗性能
参考https://blog.csdn.net/weixin_43443341/article/details/124041094
(function (window) {
var name = '我是私有变量,其他人都找不到我'
window.getName = function () {
return name
}
})(window)
console.log(getName()) //'我是私有变量,其他人都找不到我'
console.log(name) // undefined
console.log(window.name) //undefined
function Private(){
let a='私有变量';
this.getName=function(){
return a;
}
}
let obj=new Private();
console.log(obj.a) //undefine
console.log(obj.getName()); //私有变量
class private {
constructor(){
let a='class私有';
this.getName=function(){
return a;
}
}
}
let p=new private();
console.log(p.a); //undefine
console.log(p.getName());//class私有
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
setTimeout
setTimeout()方法用来指定某个函数或字符串在指定的毫秒数之后执行。它返回一个整数,表示定时器的编号,这个值可以传递给clearTimeout()用于取消这个函数的执行
setInterval
setInterval的用法与setTimeout完全一致,区别仅仅在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行
[注意]:HTML5标准规定,setTimeout的最短时间间隔是4毫秒;setInterval的最短间隔时间是10毫秒,也就是说,小于10毫秒的时间间隔会被调整到10毫秒
requestAnimationFrame
大多数电脑显示器的刷新频率是60Hz,大概相当于每秒钟重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是1000ms/60,约等于16.6ms
而setTimeout和setInterval的问题是,它们都不精确。它们的内在运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行
requestAnimationFrame采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果
使用
requestAnimationFrame的用法与settimeout很相似,只是不需要设置时间间隔而已。requestAnimationFrame使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。它返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame用于取消这个函数的执行
//控制台输出1和0
var timer = requestAnimationFrame(function(){
console.log(0);
});
console.log(timer);//1
cancelAnimationFrame(timer);
优点:
【1】requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率
【2】在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量
【3】requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销
参考https://www.cnblogs.com/xiaohuochai/p/5777186.html
bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用
var obj = {name:"Smiley"};
var greeting = function(str, lang){
this.value = 'greetingValue';
console.log("Welcome "+this.name+" to "+str+" in "+lang);
};
var objGreeting = greeting.bind(obj, 'the world');
var newObj = new objGreeting('JS');
console.log(newObj.value);
JS
就会是undefinedarg=arg.concat(Array.prototype.slice.call(newArg));
Function.prototype.bind=function(obj,arg){
var arg=Array.prototype.slice.call(arguments,1);
var context=this;
var bound=function(newArg){
arg=arg.concat(Array.prototype.slice.call(newArg));
return context.apply(obj,arg);
}
var F=function(){}
//这里需要一个寄生组合继承
F.prototype=context.prototype;
bound.prototype=new F();
return bound;
}
参考https://zhuanlan.zhihu.com/p/85438296
var timeWorker = {}
var mySetInterval= function(fn, time) {
// 定义一个key,来标识此定时器
var key = Symbol();
// 定义一个递归函数,持续调用定时器
var execute = function(fn, time) {
timeWorker[key] = setTimeout(function(){
fn();
execute(fn, time);
}, time)
}
execute(fn, time);
// 返回key
return key;
}
var myClearInterval = function(key) {
if (key in timeWorker) {
clearTimeout(timeWorker[key]);
delete timeWorker[key];
}
}
//使用
let time1 = mySetInterval(() => {console.log(111)}, 3000);
let time2 = mySetInterval(() => {console.log(222)}, 3000);
//清除定时器
setTimeout(() => {
myClearInterval(time2);
}, 4000)
function loadImage(src) {
return new Promise(function (resolve, reject) {
var img = new Image();
img.src = src;
img.onload = function () {
resolve(img);
};
img.onerror = function () {
reject("错误的地址:" + src);
};
});
}
var arr = [];
for (var i = 5; i < 14; i++) {
arr.push(loadImage("./img/" + i + "-.jpg"));
}
Promise.all(arr).then(function (list) {
list.forEach((item) => {
console.log(item.src, item.width, item.height);
});
});
function loadImage(src) {
return new Promise(function (resolve, reject) {
var img = new Image();
img.src = src;
img.onload = function () {
resolve(img);
};
img.onerror = function () {
reject("错误的地址:" + src);
};
});
}
async function loadAll() {
var arr = [];
for (var i = 5; i < 14; i++) {
var img = await loadImage("./img/" + i + "-.jpg");
arr.push(img);
}
console.log(arr);
}
loadAll();
setTimeout(function () {
console.log(1);
}, 0);
new Promise(function (resolve, reject) {
console.log(2);
resolve();
})
.then(function () {
console.log(3);
})
.then(function () {
console.log(4);
});
process.nextTick(function () {
console.log(5);
});
console.log(6);
输出 2,6,5,3,4,1
sleep函数作用是让线程休眠,等到指定时间在重新唤起。
function sleep(ms) {
var start = Date.now(),
expire = start + ms;
while (Date.now() < expire);
console.log("1111");
return;
}
执行 sleep(1000)之后,休眠了 1000ms 之后输出了 1111。上述循环的方式缺点很明显,容易造成死循环
function sleep(ms) {
var temple = new Promise((resolve) => {
console.log(111);
setTimeout(resolve, ms);
});
return temple;
}
sleep(500).then(function () {
console.log(222)
});
先输出了 111,延迟 500ms 后输出 222
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function test() {
var temple = await sleep(1000);
console.log(1111);
return temple;
}
test();
function* sleep(ms) {
yield new Promise(function (resolve, reject) {
console.log(111);
setTimeout(resolve, ms);
});
}
sleep(500)
.next()
.value.then(function () {
console.log(2222);
});
单独开了个文章https://blog.csdn.net/weixin_44157964/article/details/129525061
有4个规则一定要记住,如下:
如果把函数当做对象,那么Function就是它对应的构造函数,所以Function.__proto__==Function.prototype
Object并不是Function的构造函数,所以是false
Function.__proto__==Object.prototype //false
Function.__proto__==Function.prototype//true
this主要分为以下几种
具体看我写的另一篇https://blog.csdn.net/weixin_44157964/article/details/103931479
typeof
instanceof
__proto__
原型链上,是否存在右侧的 prototype 原型。constructor
Object.prototype.toString.call()
序号 | 方法 | 作用 | 返回值 | 是否改变原数组 |
---|---|---|---|---|
1 | join | 拼接为一个字符串,默认使用逗号作为分隔符 | 新字符串 | 不改变 |
2 | push | 从数组末尾向数组添加元素,可以添加一个或多个元素 | 数组长度 | 改变 |
3 | pop | 删除数组的最后一个元素 | 删除的元素 | 改变 |
4 | shift | 删除第一个 | 删除的元素 | 改变 |
5 | unshift | 向数组的开头添加一个或更多元素 | 新的长度 | 改变 |
6 | splice | 可以实现删除、插入和替换 | 被删除元素组成的数组 | 改变 |
7 | slice | 从a(下标)截取到b之前 | 截取的数组 | 不改变 |
8 | concat | 连接两个以及多个数组 | 连接的新数组 | 不改变 |
9 | indexOf | 查找元素,两个参数(元素,从哪位开始查) | 下标 | 不改变 |
10 | laseIndexOf | 查找元素,从后往前,两个参数(元素,从哪位开始逆向查) | 下标 | 不改变 |
11 | reverse | 翻转数组 | 原数组 | 改变 |
12 | sort | 排序,默认升序 | 原数组 | 改变 |
13 | forEach | 遍历 | 无返回值 | |
14 | map | 遍历 | 返回原始数组处理后的数组 | 不改变 |
15 | filter | 过滤 | 返回满足条件的过滤后的数组 | 不改变 |
16 | fill | es6新增,填充 | 填充后的原数组 | 改变 |
17 | every | 每一项满足返回true | true或者false | 不改变 |
18 | some | 有一项满足返回true | true或者false | 不改变 |
19 | includes | es7新增,一个数组是否包含一个指定的值 | true或者false | 不改变 |
20 | reduce | 把上一次的结果参与计算 | 处理后的结果 | 不改变 |
21 | reduceRight | 同上,但是是从最后一项开始遍历 | 处理后的结果 | 不改变 |
22 | toString | 将数组每一项用,号连接转换为字符串 | 字符串 | 不改变 |
23 | toLocalString | 同上,但是可以传入拼接的参数 | 字符串 | 不改变 |
24 | find | 查找 | 返回查找的值 | 不改变 |
25 | findIndex | 查找符合条件的索引 | 返回查找的索引 | 不改变 |
26 | copyWithin | 浅复制数组的一部分到同一数组中的另一个位置,三个参数(复制到该位置,复制起始位置,复制结束位置之前) | 返回复制后的原数组 | 改变 |
27 | flat | 多层数组展开,默认深度是1 | 展开的新数组 | 不改变 |
28 | flatMap | 对每个成员先执行map,再执行flat | 展开的新数组 | 不改变 |
29 | entries | 遍历数组返回一个遍历器对象,可以用for…of遍历,对键值对进行遍历 | 遍历器对象 | 不改变 |
30 | keys | 同上,对键进行遍历 | 遍历器对象 | 不改变 |
31 | values | 同上,对值进行遍历 | 遍历器对象 | 不改变 |
循环判断当前元素与其后面所有元素对比是否相等,如果相等删除;(执行速度慢)
var arr = [1,23,1,1,1,3,23,5,6,7,9,9,8,5];
function removeDuplicatedItem(arr) {
for(var i = 0; i < arr.length-1; i++){
for(var j = i+1; j < arr.length; j++){
if(arr[i]==arr[j]){
arr.splice(j,1);//console.log(arr[j]);
j--;
}
}
}
return arr;
}
arr2 = removeDuplicatedItem(arr);
console.log(arr);
console.log(arr2);
借助新数组 判断新数组中是否存在该元素如果不存在则将此元素添加到新数组中
function unique4(arr){
var res = [];
for(var i=0; i<arr.length; i++){
if(res.indexOf(arr[i]) == -1){
res.push(arr[i]);
}
}
return res;
}
console.log(unique4([1,1,2,3,5,3,1,5,6,7,4]));
利用对象属性存在的特性,如果没有该属性则存入新数组
function unique3(arr){
var res = [];
var obj = {};
for(var i=0; i<arr.length; i++){
if( !obj[arr[i]] ){
obj[arr[i]] = 1;
res.push(arr[i]);
}
}
return res;
}
console.log(unique3([1,1,2,3,5,3,1,5,6,7,4]));
先将原数组排序,再与相邻的进行比较(新数组最后一个元素),如果不同则存入新数组
function unique2(arr){
var arr2 = arr.sort();
var res = [arr2[0]];
for(var i=1; i<arr2.length; i++){
if(arr2[i] !== res[res.length-1]){
res.push(arr2[i]);
}
}
return res;
}
console.log(unique2([1,1,2,3,5,3,1,5,6,7,4]));
定义一个新数组,并存放原数组的第一个元素,然后将原数组一一和新数组的元素对比,若不同则存放在新数组中
function unique(arr){
var res = [arr[0]];
for(var i=1; i<arr.length; i++){
var repeat = false;
for(var j=0; j<res.length; j++){
if(arr[i] === res[j]){
repeat = true;
break;
}
}
if(!repeat){
res.push(arr[i]);
}
}
return res;
}
console.log(unique([1,1,2,3,5,3,1,5,6,7,4]));
利用数组原型对象上的includes方法
function unique5(arr){
var res = [];
for(var i=0; i<arr.length; i++){
if( !res.includes(arr[i]) ){ // 如果res新数组包含当前循环item
res.push(arr[i]);
}
}
return res;
}
console.log(unique5([1,1,2,3,5,3,1,5,6,7,4]));
利用数组原型对象上的 lastIndexOf 方法
function unique9(arr){
var res = [];
for(var i=0; i<arr.length; i++){
res.lastIndexOf(arr[i]) !== -1 ? '' : res.push(arr[i]);
}
return res;
}
console.log(unique9([1,1,2,3,5,3,1,5,6,7,4]));
利用 ES6的set 方法
function unique10(arr){
//Set数据结构,它类似于数组,其成员的值都是唯一的
return Array.from(new Set(arr)); // 利用Array.from将Set结构转换成数组
}
console.log(unique10([1,1,2,3,5,3,1,5,6,7,4]));
借助indexOf()方法判断此元素在该数组中首次出现的位置下标与循环的下标是否相等
var ar = [1,23,1,1,1,3,23,5,6,7,9,9,8,5];
function rep2(arr) {
for (var i = 0; i < arr.length; i++) {
if (arr.indexOf(arr[i]) != i) {
arr.splice(i,1);//删除数组元素后数组长度减1后面的元素前移
i--;//数组下标回退
}
}
return arr;
}
var a1 = rep2(ar);
console.log(ar);
console.log(a1);
利用数组中的filter方法
var arr = ['apple','strawberry','banana','pear','apple','orange','orange','strawberry'];
var r = arr.filter(function(element,index,self){
return self.indexOf(element) === index;
});
console.log(r);
双重循环,判断当前元素是否与后边元素有重复,如果没有重复,push进新数组,有重复则将重复元素中最后一个元素push进新数组
var arr = [12, 2, 44, 3, 2, 32, 33, -2, 45, 33, 32, 3, 12];
var newArr = [];
for (var i = 0; i < arr.length; i++) {
var repArr = [];//接收重复数据后面的下标
//内层循环找出有重复数据的下标
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) {
repArr.push(j);//找出后面重复数据的下标
}
}
//console.log(repArr);
if (repArr.length == 0) {//若重复数组没有值说明其不是重复数据
newArr.push(arr[i]);
}
}
console.log(newArr);//[ 44, 2, -2, 45, 33, 32, 3, 12 ]
参考https://www.cnblogs.com/web-record/p/9141373.html
str.replace(/(^\s*)|(\s*$)/g, "")
是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对 JavaScript 实施的安全限制,那么只要协议、域名、端口有任何一个不同,都被当作是不同的域。跨域原理,即是通过各种方式,避开浏览器的安全限制。
jsonp
<script>
function show(params) {
console.log(params);
}
</script>
<script src="https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=a&cb=show"></script>
CORS
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制
Access-Control-Allow-Origin
即可,前端无须设置document.domain + iframe跨域
此方案仅限主域相同,子域不同的跨域应用场景(网页一级域名相同,只是二级域名不同)。实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域
父窗口:(www.a.com/a.html)
<iframe id="iframe" src="http://child.a.com/b.html"></iframe>
<script>
document.domain = 'a.com';
var user = 'admin';
</script>
子窗口:(child.a.com/b.html)
<script>
document.domain = 'a.com';
// 获取父窗口中变量
alert('get js data from parent ---> ' + window.parent.user);
</script>
location.hash
实现原理: a与b跨域相互通信,通过中间页c来实现(且c与a是同域)。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。
具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象
a.html:(www.a.com/a.html)
<iframe id="iframe" src="http://www.b.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 向b.html传hash值
setTimeout(function() {
iframe.src = iframe.src + '#user=admin';
}, 1000);
// 开放给同域c.html的回调方法
function onCallback(res) {
alert('data from c.html ---> ' + res);
}
</script>
b.html:(www.b.com/b.html)
<iframe id="iframe" src="http://www.a.com/c.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 监听a.html传来的hash值,再传给c.html
window.onhashchange = function () {
iframe.src = iframe.src + location.hash;
};
</script>
c.html:(www.a.com/c.html)
<script>
// 监听b.html传来的hash值
window.onhashchange = function () {
// 再通过操作同域a.html的js回调,将结果传回
window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
};
</script>
window.name + iframe跨域
浏览器窗口有window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。并且可以支持非常长的 name 值(2MB)
这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。
postMessage跨域
HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。
这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。
postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*
,表示不限制域名,向所有窗口发送。
它可用于解决以下方面的问题:
nginx代理跨域
1、nginx配置解决iconfont跨域
浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置
location / {
add_header Access-Control-Allow-Origin *;
}
2、 nginx反向代理接口跨域
跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录
#proxy服务器
server {
listen 81;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
WebSocket
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现
WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
原生WebSocket API使用起来不太方便,可以使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容
Nodejs中间件代理跨域
node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。
webpack-dev-server代理(vue中配置)
module.exports = {
entry: {},
module: {},
...
devServer: {
historyApiFallback: true,
proxy: [{
context: '/login',
target: 'http://www.domain2.com:8080', // 代理跨域目标接口
changeOrigin: true,
secure: false, // 当代理某些https服务报错时用
cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改
}],
noInfo: true
}
}
参考https://juejin.cn/post/6844903809118896135#heading-10
基本:String Boolean Number Undefined Null Symbol BigInt
引用:Object、Array、RegExp、Date、Function、特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math)
function cloneDeep(obj) {
if (obj == null) {
return obj;
}
if (typeof obj !== 'object') {
return obj;
}
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Date) return new Date(obj);
let newObj = new obj.constructor;
for (let key in obj) {
newObj[key] = cloneDeep(obj[key])
}
return newObj;
}
要比较相等性之前,不能将 null 和 undefined 转换成其他任何值,但null == undefined会返回 true 。ECMAScript 规范中是这样定义的
在代码块内,使用 let、const 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
let/const作用域为块级作用域,变量不会提升;而var的作用域为全局作用域,可以进行变量提升,这也就是为什么var没有暂时性死区
原因可能是:
解决办法:
按需加载是网站性能优化立竿见影的其中一项,按需加载可以了解为 当用户触发某个动作的时候,才主动去请求资源,这样带来的优化好处:减少了HTTP请求,节省宽带,让页面首屏的内容更快展现在用户的视线范围内,可见极大提高了用户体检。触发的动作有很多,如:鼠标点击、输入文字、拉动滚动条,鼠标移动、窗口大小更改等。加载的文件,可以是 JS、图片、CSS、HTML 等
虚拟 dom 是相对于浏览器所渲染出来的真实 dom 的
以往,我们改变更新页面,只能通过首先查找dom对象,再进行修改dom的方式来达到目的。 但这种方式相当消耗计算资源, 因为每次查询 dom ,都需要遍历整颗 dom 树。
现在,我们用对象的方式来描述真实的 dom,并且通过对象与真实dom建立了一一对应的关系,那么每次 dom 的更改,我通过找到相应对象,也就找到了相应的dom节点,再对其进行更新。这样的话,就能节省性能,因为js 对象的查询,比对整个dom 树的查询,所消耗的性能要少。
Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存,就是用树型结构的JS对象来描述真实的DOM结构的信息,这个树结构的JS对象包含了整个DOM结构的信息
优点:
缺点:
webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。
在webpack 看来, 前端的所有资源文件(js/json/css/img/less/…)都会作为模块处理。
它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。
优点:
缺点:
第一个是用 let 块级作用域
for(let i=0;i<5;i++){
setTimeout(function(){
console.log(i)
},1000*i)
}
第二个方法闭包
for(var i=0;i<5;i++){
(function(i){
setTimeout(function(){
console.log(i)
},1000*i)
})(i)
}
其实Vue实例的生命周期,主要分为三个阶段,每个阶段都会执行不同的钩子函数,分别为
挂载(初始化相关属性,例如watch属性,method属性)
更新(元素或组件的变更操作)
销毁(销毁相关属性)
Symbol是ES6中引入的一种新的基本数据类型,用于表示一个独一无二的值。它是JavaScript中的第
七种数据类型,与undefined、null、Number(数值)、String(字符串)、Boolean(布尔值)、
Object(对象)并列
Symbol特点:
事件分为DOM 0级事件和Dom 2级事件,DOM2级事件也叫做事件监听。DOM 0级事件的缺点是如果事件相同 后者的事件会覆盖前者的事件,DOM2级事件可以解决这个问题
document.onclick = function(){
alert(1);
}
document.onclick = function(){
alert(2);
}
//只会执行第二个函数 弹出2
DOM2级事件的方法(事件监听)
主流浏览器 addEventListener() 【有三个参数】
function a(){
alert(1)
}
function b(){
alert(2)
}
document.addEventListener("click",a);
document.addEventListener("click",b); //点击后先执行第一个函数,再执行第二个函数
document.removeEventListener("click",a) //不在执行a()这个函数
IE浏览器:attachEvent()
function a(){
alert(1)
}
function b(){
alert(2)
}
document.attachEvent("onclick",a);
document.attachEvent("onclick",b);//点击后先执行第二个函数,再执行第一个函数
document.detachEvent("onclick",a)//不在执行a()这个函数
参考:https://www.cnblogs.com/Tian-yy/p/9060669.html
null
Function.prototype(Object也是构造函数)
Object.prototype
Array.prototype.unique = function(){
}
参考上面38数组去重
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
个人理解:通常做法就是一个变量初始值为空,每次创建之前判断这个变量,变量存在,复用之前的,不存在,则初始化
var Singleton = (function () {
var instance;
var CreateSingleton = function (name) {
this.name = name;
if (instance) {
return instance;
}
// 打印实例名字
this.getName();
// instance = this;
// return instance;
return instance = this;
}
// 获取实例的名字
CreateSingleton.prototype.getName = function () {
console.log(this.name)
}
return CreateSingleton;
})();
// 创建实例对象 1
var a = new Singleton('a');
// 创建实例对象 2
var b = new Singleton('b');
console.log(a === b); //true
Promise 解决的问题:回调地狱
Generator 函数
function* g() {
var o = 1;
yield o++;
yield o++;
}
var gen = g();
console.log(gen.next()); // Object {value: 1, done: false}
var xxx = g();
console.log(gen.next()); // Object {value: 2, done: false}
console.log(xxx.next()); // Object {value: 1, done: false}
console.log(gen.next()); // Object {value: undefined, done: true}
generator 和异步控制: 利用 Generator 函数的暂停执行的效果,可以把异步操作写在 yield 语句里面,等到调用next 方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在 yield 语句下面,反正要等到调用 next 方法时再执行。所以,Generator 函数的一个重要实际意义就是用来处理异步操作,改写回调函数async
async 表示这是一个 async 函数,await 只能用在这个函数里面。
await 表示在这里等待异步操作返回结果,再继续执行。
await 后一般是一个 promise 对象
如果 async 函数返回的是一个同步的值,这个值将被包装成一个理解resolve 的Promise,等同于 return Promise.resolve(value)。
await 用于一个异步操作之前,表示要“等待”这个异步操作的返回值。await 也可以用于一个同步的值。
function underlineToHump(arr){
//判断下划线位置,下划线位置加一,调用js的大写方法toUpperCase();
let a = arr.indexOf('_');
let lit = arr.split('');
lit[a+1] = lit[a+1].toUpperCase();
arr = lit.join('');
arr = arr.split('_').join('');
console.log(arr);
}
let arr = "as_dad";
underlineToHump(arr);
startsWith() 方法用来判断当前字符串是否以另外一个给定的子字符串开头,并根据判断结果返回 true 或 false;有两个参数(要搜索的子字符,在 str 中搜索 searchString 的开始位置,默认值为 0)
Indexof 函数,indexof 函数可返回某个指定字符串在字符串中首次出现的位置
通过函数 parseInt(),可解析一个字符串,并返回一个整数,语法为parseInt(string ,radix)
string:被解析的字符串
radix:表示要解析的数字的基数(以该进制读取,转换为十进制输出),默认是十进制,如果 radix<2 或>36,则返回NaN
当忽略参数 radix , JavaScript 默认数字的基数如下:
如果 string 以 “0x” 开头,parseInt() 会把 string 的其余部分解析为十六进制的整数。
如果 string 以 0 开头,那么 ECMAScript v3 允许 parseInt() 的一个实现把其后的字符解析为八进制或十六进制的数字。
如果 string 以 1 ~ 9 的数字开头,parseInt() 将把它解析为十进制的整数。
块级作用域是指变量或函数在一个代码块内有效,在代码块外无效的作用域。常见的代码块如if语句、for循环等。在 JavaScript 中,使用大括号 {} 定义块级作用域
众所周知,ES6新增了let关键字用来定义局部变量,它使得JS的变量有了块级作用域。
块级作用域是指变量只在{}内有效
那么在ES6以前如何给变量实现块级作用域呢?答案就是使用立即执行匿名函数
先看看没有块级作用域的情况:
function a(){
for(var i=0;i<3;i++){
}
console.log(i);
}
a();
上述例子中,将输出 i=3,因为i使用var声明,作用域是整个函数。
如果需要让变量i只在for循环中有效呢,我们可以这样实现:
即在for循环外面包裹一层匿名函数,在匿名函数外加圆括号表示立即执行
function a(){
(function(){
for(var i=0;i<3;i++){
console.log(i);
}
})();
console.log(i); //输出undefined
}
a();
setTimeout(function () {
console.log(1)
}, 0);
new Promise(function (resolve, reject) {
console.log(2)
for (var i = 0; i < 10000; i++) {
if (i === 10) { console.log(10) }
i == 9999 && resolve();
}
console.log(3)
}).then(function () {
console.log(4)
})
console.log(5);
分为移动端调试和pc端调试
pc就是chorome控制台
移动端比较复杂,因为我们的项目都是在我们app内部,
Not a Number
null是表示一个"无"的对象,指向空的对象,这个对象是不存在的,转为数值时为0。null的使用场景:经常用作函数的参数,或作为原型链的终点
undefined是表示一个"无"的原始值,变量被声明了但还没有赋值,就为undefined,转为数值时为NaN
相同点:都是表示值的空缺,所以两者等于(= =)是相等的
不同点:null的类型是Object对象,undefined的类型就是Undefiend,所以两者全等(= = =)不相等
setTimeout()函数只是将事件插入了任务列表,必须等到当前代码执行完,主线程才会去执行它指定的回调函数,有可能要等很久,所以没有办法保证回调函数一定会在setTimeout指定的时间内执行,100毫秒是插入队列的时间+等待的时间
首先定义一个对象
//定义对象
var obj = {
['str']: 'String property', [Symbol()]: 'Symbol property',
}
//定义不可枚举的字符串属性
Object.defineProperty(obj, 'unenum', {
value: ' Non-enumerable property', writable: true, enumerable: false, configurable: true,
});
//定义不可枚举的Symbol属性
Object.defineProperty(obj, Symbol('unenum'), {
value: 'Non-enumerable Symbol value', writable: true, enumerable: false, configurable: true,
});
//在原型链上定义一个字符串属性和一个Symbol属性
Object.setPrototypeOf(obj, { foo: 'bar', [Symbol('foo')]: 'bar' });
Object.keys()
& Object.values()
& Object.entries
这三个方法都是为了来获得对象的属性与值的,最终返回值是一个数组,不过只获取对象本身的可枚举字符串属性
。
console.log(Object.keys(obj)); //["str"]
console.log(Object.values(obj)); // ["String property"]
console.log(Object.entries(obj)); //[["str", "String property"]]
Object.getOwnPropertyNames()
是获取对象自身上的字符串属性,包括可枚举的与不可枚举的属性,最后返回一个数组
console.log(Object.getOwnPropertyNames(obj)); //["str", "unenum"]
Object.getOwnPropertySymbols()
是获取对象自身上的Symbol属性,包括可枚举的与不可枚举的,最后返回一个数组
console.log(Object.getOwnPropertySymbols(obj)); //[Symbol(), Symbol(unenum)]
for...in..
用来遍历对象上可枚举的字符串属性的,包括原型链上的可枚举的字符串属性。
for(let key in obj){
console.log(key); //"str" "foo"
}
includes()、startsWith() 、endsWith()、repeat()、padStart()、padEnd()
let person = {name,age}
参考https://blog.csdn.net/ZLJ_999/article/details/124122540
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
console.log(i)
在一秒后输出 5 个 5
每次 for 循环的时候 setTimeout 都会执行,但是里面的 function 则不会执行被放入任务队列,因此放了 5 次;for 循环的 5 次执行完之后不到 1000 毫秒;1000 毫秒后全部执行任务队列中的函数,所以就是输出 5 个 5。
// promise
A.then(B).then(C).catch(...)
// async/await
(async () => {
await a();
await b();
await c();
})()
public:public 表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用
private:private 表示私有,私有的意思就是除了 class 自己之外,任何人都不可以直接使用
Function.prototype.a = 1;
Object.prototype.b = 2;
function A() { }
var a = new A();
console.log(a.a, a.b);
console.log(A.a, A.b);
async-await是promise和generator的语法糖
async 函数返回的是一个promise 对象
指定 script 标签的 async 属性。
如果 async=“async”,脚本相对于页面的其余部分异步地执行(当页面继续进行解析时,脚本将被执行)
如果不使用 async 且 defer=“defer”:脚本将在页面完成解析时执行
没有 promise,可以用回调函数代替
arguments 是类数组对象,有 length 属性,不能调用数组方法
可用 Array.from()转换
图片轮播的原理就是图片排成一行,然后准备一个只有一张图片大小的容器,对这个容器设置超出部分隐藏,在控制定时器来让这些图片整体左移或右移,这样呈现出来的效果就是图片在轮播了
如果有两个轮播,可封装一个轮播组件,供两处调用
JSON 只支持 get,因为 script 标签只能使用 get 请求;
JSONP 需要后端配合返回指定格式的数据。
文档对象模型(Document Object Model,简称 DOM),是 W3C 组织推荐的处理可扩展标志语言的标准编程接口。在网页上,组织页面(或文档)的对象被组织在一个树形结构中,用来表示文档中对象的标准模型就称为 DOM
直接修改元素的样式
elem.style.color = 'red';
elem.style.setProperty('font-size', '16px');
elem.style.removeProperty('color');
动态添加样式规则
var style = document.createElement('style');
style.innerHTML = 'body{color:red} #top:hover{background-color: red;color: white;}';
document.head.appendChild(style);
classList获取样式属性
了解dom节点样式(classList)的remove, add, toggle, contains, replace等方法的使用。
window.getComputedStyle 通过 element.sytle.xxx 只能获取到内联样式,借助 window.getComputedStyle 可以获取应用到元素上的所有样式,IE8或更低版本不支持此方法
var style = window.getComputedStyle(element[, pseudoElt]);。
参考https://blog.csdn.net/weixin_43613849/article/details/121560210
异步的javascript和xml AJAX 是一种用于创建快速动态网页的技术。 ajax用来与后台交互
//创建 XMLHttpRequest 对象
var ajax = new XMLHttpRequest();
//规定请求的类型、URL 以及是否异步处理请求。
ajax.open('GET',url,true);
//发送信息至服务器时内容编码类型
ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
//发送请求
ajax.send(null);
//接受服务器响应数据
ajax.onreadystatechange = function () {
if (obj.readyState == 4 && (obj.status == 200 || obj.status == 304)) {
}
};
//字符串转对象
JSON.parse(json)
eval('(' + jsonstr + ')')
// 对象转字符串
JSON.stringify(json)
分为readyState(状态值)和status(状态码)
readyState
,是指运行AJAX所经历过的几种状态,无论访问是否成功都将响应的步骤,可以理解成为AJAX运行步骤,使用“ajax.readyState”获得
status
,是指无论AJAX访问是否成功,由HTTP协议根据所提交的信息,服务器所返回的HTTP头信息代码,使用“ajax.status”获得
总体理解:可以简单的理解为state代表一个整体的状态。而status是这个大的state下面具体的小的状态
readyState
readyState总共有5个状态值,分别为0~4,每个值代表了不同的含义
status
发出两个有顺序的 ajax,可以用回调函数,也可以使用 Promise.then 或者async 等
ajax 的优缺点:
axios的优缺点:
概念
PWA 全称 Progressive Web App,即渐进式 WEB 应用。一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用. 随后添加上 App Manifest 和Service Worker
来实现 PWA 的安装和离线等功能
纵观现有 Web 应用与原生应用的对比差距,如离线缓存、沉浸式体验等等,可以通过已经实现的 Web 技术去弥补这些差距,最终达到与原生应用相近的用户体验效果
特性
安全可靠
使用 Service Work 技术实现即时下载,当用户打开应用后,页面资源的加载不再完全依赖于网络,而是使用 Service Work 缓存离线包存在本地,确保为用户提供即时可靠的体验。
访问更快
首屏可以部署在服务端,节省网页请求时间,加载速度更快,拥有更平滑的动态效果和快速的页面响应。
响应式界面
支持各种类型的终端和屏幕。
沉浸式体验
在支持 PWA 的浏览器和手机应用上可以直接将 Web 应用添加到用户的主屏幕上,无需从应用商店下载安装。从主屏幕上打开应用之后,提供沉浸式的全屏幕体验。
功能
手机应用配置(Web App Manifest)
可以通过 manifest.json 文件配置,使得可以直接添加到手机的桌面上。
离线加载与缓存(Service Worker+Cache API )
可以通过 Service Worker + HTTPS +Cache Api + indexedDB 等一系列 Web 技术实现离线加载和缓存。
消息推动与通知(Push&Notification )
实现实时的消息推送与通知
数据及时更新(Background Sync )
后台同步,数据及时更新
优势与劣势
谷歌
基于 Chromium 开发的浏览器 Chrome 和 Opera 已经完全支持 PWA 。
这里说一下 Chromium 和 Chrome 的区别。
Chromium 是谷歌的开源项目,由开源社区去维护。拥有众多的版本包括Windows、Mac、Linux。国内所有的 “双核浏览器”,都是基于 Chromium 开发的,而我们下载的 Chromium 浏览器是其源码未经修改的直接编译版本。
Chrome 是基于 Chromium 开发的,是闭源的,跨平台多端支持,特性更加丰富。
Google上线了两个新网站,web.dev 和 squoosh.app 都支持 PWA( web.dev 是宣传和推广 PWA 的,解释了 PWA 的几个关键技术。squoosh.app 是一个图片压缩工具) 。
微软
微软将 PWA 带到了 Windows 10。同时 Windows Edge(windows 10 之后微软推出的浏览器,比 IE更流畅、外观 UI 更舒适) 也支持 PWA。
IOS
随着 iOS 11.3 的发布,iOS 正式开始支持 PWA,可以将它放在苹果手机主屏。
Android
Twitter 和 Flipboard 都推出了 PWA,可以将它放在安卓手机主屏。
国内
国内支持 PWA 的应用有微博、淘宝、豆瓣和饿了么。
参考https://blog.csdn.net/weixin_44135121/article/details/105528430
优点在于其容易上手,根据 flex 规则很容易达到某个布局效果
缺点是:浏览器兼容性比较差,只能兼容到 ie9 及以上
优点:可以快速适用移动端布局 字体图片 高度
缺点:
产生原因
主要是跟一个东西有关,DPR(devicePixelRatio) 设备像素比,它是默认缩放为100%的情况下,设备像素和CSS像素的比值 window.devicePixelRatio=物理像素 /CSS像素
目前主流的屏幕DPR=2 (iPhone 8),或者3 (iPhone 8 Plus)。拿2倍屏来说,设备的物理像素要实现1像素,而DPR=2,所以css 像素只能是 0.5。一般设计稿是按照750来设计的,它上面的1px是以750来参照的,而我们写css样式是以设备375为参照的,所以我们应该写的0.5px就好了啊! 试过了就知道,iOS 8+系统支持,安卓系统不支持
解决方案
WWDC对iOS统给出的方案 推荐指数:**
在 WWDC大会上,给出来了1px方案,当写 0.5px的时候,就会显示一个物理像素宽度的 border,而不是一个css像素的 border。 所以在iOS下,你可以这样写
border:0.5px solid #E5E5E5
可能你会问为什么在3倍屏下,不是0.3333px 这样的?经过我测试,在Chrome上模拟iPhone 8Plus,发现小于0.46px的时候是显示不出来
优点: 简单,没有副作用
缺点: 支持iOS 8+,不支持安卓。后期安卓follow就好了
使用边框图片推荐指数:**
border: 1px solid transparent;
border-image: url('./../../image/96.jpg') 2 repeat;
优点: 没有副作用
缺点: border颜色变了就得重新制作图片;圆角会比较模糊
使用box-shadow实现 推荐指数:***
box-shadow: 0 -1px 1px -1px #e5e5e5, //上边线
1px 0 1px -1px #e5e5e5, //右边线
0 1px 1px -1px #e5e5e5, //下边线
-1px 0 1px -1px #e5e5e5; //左边线
前面两个值 x,y 主要控制显示哪条边,后面两值控制的是阴影半径、扩展半径
优点:使用简单,圆角也可以实现
缺点:模拟的实现方法,仔细看谁看不出来这是阴影不是边框
使用伪元素 推荐指数:****
1 条border
.setOnePx{
position: relative;
&::after{
position: absolute;
content: '';
background-color: #e5e5e5;
display: block;
width: 100%;
height: 1px; /*no*/
transform: scale(1, 0.5);
top: 0;
left: 0;
}
}
可以看到,将伪元素设置绝对定位,并且和父元素的左上角对齐,将width 设置100%,height设置为1px,然后进行在Y方向缩小0.5倍
4 条border
.setBorderAll{
position: relative;
&:after{
content:" ";
position:absolute;
top: 0;
left: 0;
width: 200%;
height: 200%;
transform: scale(0.5);
transform-origin: left top;
box-sizing: border-box;
border: 1px solid #E5E5E5;
border-radius: 4px;
}
}
同样为伪元素设置绝对定位,并且和父元素左上角对其。将伪元素的长和宽先放大2倍,然后再设置一个边框,以左上角为中心,缩放到原来的0.5倍
优点:全机型兼容,实现了真正的1px,而且可以圆角
缺点:暂用了after 伪元素,可能影响清除浮动
设置viewport的scale值 推荐指数:*****
这个解决方案是利用viewport+rem+js 实现的
<html>
<head>
<title>1px question</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta name="viewport" id="WebViewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<style>
html {
font-size: 1px;
}
* {
padding: 0;
margin: 0;
}
.top_b {
border-bottom: 1px solid #E5E5E5;
}
.a,.b {
box-sizing: border-box;
margin-top: 1rem;
padding: 1rem;
font-size: 1.4rem;
}
.a {
width: 100%;
}
.b {
background: #f5f5f5;
width: 100%;
}
</style>
<script>
var viewport = document.querySelector("meta[name=viewport]");
//下面是根据设备像素设置viewport
if (window.devicePixelRatio == 1) {
viewport.setAttribute('content', 'width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no');
}
if (window.devicePixelRatio == 2) {
viewport.setAttribute('content', 'width=device-width,initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no');
}
if (window.devicePixelRatio == 3) {
viewport.setAttribute('content', 'width=device-width,initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no');
}
var docEl = document.documentElement;
var fontsize = 32* (docEl.clientWidth / 750) + 'px';
docEl.style.fontSize = fontsize;
</script>
</head>
<body>
<div class="top_b a">下面的底边宽度是虚拟1像素的</div>
<div class="b">上面的边框宽度是虚拟1像素的</div>
</body>
</html>
优点:全机型兼容,直接写1px不能再方便
缺点:适用于新的项目,老项目可能改动大
参考:https://juejin.cn/post/6844903877947424782#heading-16
to B(business)即面向企业,to C( customer)即面向普通用户
toC 产品更注重产品用户的共性而淡化角色关系,而 toB 产品则更强调面向用户、客户的角色关系,而淡化共性提取。实际上,这是由服务对象所引起的,C 端产品的服务对象,由终端所限,是一个面向个体的服务。而 B 端服务使用最终是面向一个系统体系组织,在干系人间配合使用中发挥产品价值。
一个好的产品 toB 可以让组织的系统变得更好,最终反哺于系统中的各个单位。需求动力之不同 toC 的产品方法论,用户体验是几乎最为重要的需求来源,腾讯此前,也以"以用户体验为归依"来驱动企业产品打造。
但 B 端产品则不同,B 端在一个商业的背景之下,B 端的决策思路是,“以企业获益为归依”,系统是否有利于企业的生产力,竞争力等,单纯的用户体验,仅能让员工得到片刻的享受, 但无法说服企业,企业并不会为一个不能"赚钱"的东西买单。需求动力的不同,引发的这是购买使用决策体系的变化。
toB 产品应更考虑 获益与系统性价值,部分情况还有可能会牺牲掉局部个体的利益,对于使用者而言应该是自律或他律的,toC 产品则更考虑的是个体用户的偏好,并长时间内,基于技术效率的提升,产品的服务中心更多地围绕着更高效地帮助用户的"欲望"释放进行设计,对于使用者而言是一个释放自我的存在。
原因:ios无法识别 - 格式的日期,例如:2022-01-13
解决:使用replace将 - 替换成 /
let newTime = oldTime.replace(/-/g, "/")
原因:使用rem做屏幕适配会出现这个问题;,rem在换算为px时,会是一个带小数点的值,安卓对小于1px的做了处理(不同浏览器对小于1px的处理方式不同,有的采用四舍五入,有的大于某个值展示1px否则就舍去),从而导致圆角不圆;在ios下就没有这个问题
解决:
1、 先把width,height的值放大一倍,然后用transform scale(.5)缩小一倍,接着用transform-origin调整下圆的位置就大功告成了
i{
display inline-block
width .16rem
height .16rem
background-color #D0021B
border-radius 50%
transform scale(.5)
transform-origin: 0% center
}
2、设置圆角时,设置具体的数据,不用百分比的形式
原因:推测可能是Android在排版计算的时候参考了primyfont字体的相关属性(即HHead Ascent、HHead Descent等),而primyfont的查找是看font-family里哪个字体在fonts.xml里第一个匹配上,而原生Android下中文字体是没有family name的,导致匹配上的始终不是中文字体,所以解决这个问题就要在font-family里显式申明中文,或者通过什么方法保证所有字符都fallback到中文字体
解决:设置字体为系统字体,在不是要求一定使用特殊字体的情况下可以参考以下字体的设置
// 通用设置
body {
font-family: system-ui, —apple-system, Segoe UI, Roboto, Emoji, Helvetica, Arial, sans-serif;
}
// emoji字体
@font-face {
font-family: Emoji;
src: local("Apple Color Emojiji"), local("Segoe UI Emoji"), local("Segoe UI Symbol"), local("Noto Color Emoji");
unicode-range: U+1F000-1F644, U+203C-3299;
}
// 衬线字体
.font-serif {
font-family: Georgia, Cambria, "Times New Roman", Times, serif;
}
// 等宽字体
.font-mono {
font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
原因:未知
解决:设置input的line-height为normal
input {
line-height: normal
}
原因:未知
解决:
Element.scrollIntoView()和Element.scrollIntoViewIfNeeded()方法让当前的元素滚动到浏览器窗口的可视区域内。
使用Element.scrollIntoView()和Element.scrollIntoViewIfNeeded()解决遮挡问题,监听输入框聚焦事件,调用上面的方法将激活的元素(输入框)滚动到可视区域
通过监听页面resize事件来解决点击原生键盘的关闭按钮收回键盘时,输入框没有失焦的问题
可参考以下代码(使用vue实现)
data() {
return {
originHeight: 0,
isAndroid:
/Android/gi.test(navigator.userAgent) ||
/adr/gi.test(navigator.userAgent),
resizeTimer: null
};
},
methods: {
resizeFn() {
// 防止部分手机触发两次resize事件导致无法拉起键盘
if (this.resizeTimer) return;
this.resizeTimer = setTimeout(() => {
let resizeHeight =
document.documentElement.clientHeight ||
document.body.clientHeight;
if (this.originHeight > resizeHeight) {
// 拉起键盘会有动画,所以需要加延时,否则不管用
setTimeout(() => {
if ("scrollIntoView" in document.activeElement) {
document.activeElement.scrollIntoView();
} else {
document.activeElement.scrollIntoViewIfNeeded();
}
}, 0);
} else {
document.activeElement.blur();
document.removeEventListener("resize", this.resizeFn);
}
clearTimeout(this.resizeTimer);
}, 100);
}
},
mounted() {
if (this.isAndroid) {
this.originHeight =
document.documentElement.clientHeight ||
document.body.clientHeight;
window.addEventListener("resize", this.resizeFn);
}
}
原因:未知
解决:在父元素加transform: rotate(0deg)属性
.father {
transform: rotate(0deg)
}
原因:iOS的事件处理机制有关,iOS最先响应屏幕反应。响应顺序依次为Touch——Media——Service——Core架构,当用户只要触摸接触了屏幕之后,系统就会最优先去处理屏幕显示也就是Touch这个层级,然后才是媒体(Media),服务(Service)以及Core架构。所以说,当系统接收到Touch事件之后会优先响应,此时会暂停屏幕上包括js、css的渲染。这个时候不光是css动画不动了,哪怕页面没有加载完如果你手指头还停留在屏幕上那么页面也不会继续加载,直到你的手松开
解决:给动画元素设置transform: translate3d(0, 0, 0);
.animation {
transform: translate3d(0, 0, 0);
}
原因:苹果公司提出的安全区域概念(safe area),简单的说就是我们的移动端页面可操作区域应该避开刘海区域和小黑条,因为在这两处地方的操作是不会响应我们的页面,即如果我们的按钮在这两块区域范围,那我们的点击就不会触发按钮上的事件
解决:官方给出的适配方案 iOS11同时新增了一个特性,constant(safe-area-inset-*),这是Webkit的一个CSS函数,用于获取安全区域与边界的距离,有四个预定义的变量(单位px):
safe-area-inset-left:安全区域距离左边界距离,横屏时适配
safe-area-inset-right:安全区域距离右边界距离,横屏时适配
safe-area-inset-top:安全区域距离顶部边界距离,竖屏下刘海屏为44px,iphone6系列20px,竖屏刘海适配关键
safe-area-inset-bottom:安全区域距离底部边界距离,竖屏下为34px,竖屏小黑条适配关键
一般使用 safe-area-inset-top,safe-area-inset-bottom
// 让页面占满全屏
<meta name="viewport" content="viewport-fil=cover">
// 使用@supports查询机型是否支持constant()或env()实现兼容代码隔离,个别安卓也会成功进入这个判断,因此加上-webkit-overflow-scrolling: touch的判断可以有效规避安卓机。
env() 是为了防止大于IOS11版本不支持constant()
@supports ((height: constant(safe-area-inset-top)) or (height: env(safe-area-inset-top))) and (-webkit-overflow-scrolling: touch) {
.fullscreen {
/* 适配齐刘海 */
padding-top: constant(safe-area-inset-top);
padding-top: env(safe-area-inset-top);
/* 适配底部小黑条 */
padding-bottom: costant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
}
原因:未知
解决:给滚动的区域设置-webkit-overflow-scrolling: touch属性
.scroll {
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
原因:未知
解决:使用better-scroll插件做页面的滚动
原因:未知
解决:将margin-bottom改成padding-bottom
原因:手贱设置了全部元素无法被选中(需求上不允许用户复制图片和文字)
* {
webkit-user-select: none;
}
解决:
输入框元素不设置这个属性,如果还存在问题,尝试在输入框和其父元素设置-webkit-user-select:text !important属性
原因:字体原因
解决:
1.使用系统字体
2.设置颜色时透明度设置为0.99
p {
color: rgba(#B96E16, 0.99);
}
原因:未知
解决:在做动画的元素的父元素设置transform: translate3d(0, 0, 1); backface-visibility: hidden;
.father {
transform: translate3d(0, 0, 1);
backface-visibility: hidden;
}
原因:猜测是ios手机又视角概念,使用transform rotateY()旋转后,视角出现问题
解决:在父元素增加perspective: 1;
.father {
perspective: 1;
}
原因:元素样式包含transform时形成新的堆叠上下文
解决:
1.父级,任意父级,非body级别,设置overflow:hidden可恢复和其他浏览器一样的渲染。
2.元素设置transform: translateZ(120px)
原因:未知
解决:设置父级元素背景渐变,设置padding,元素覆盖在上面营造成渐变边框的效果
原因:未知
解决:
1.在div内设置font-size和行高为0,使用flex布局居中
2.设置图片diaplay:block
原因:未知
解决:宽高等属性设置为两倍,然后缩放0.5
背景:用户上传图片,前端将这张图片绘制到canvas画布上
问题:绘制在canvas上的图片出现旋转(ios版本大于等于13.4.1的手机不需要前端对其调整图片方向,无论倒着拍,还是旋转拍,图片上传后的方向都是正确的,所以需要对ios的版本进行判断)
原因:在手机中默认横排才是正确的拍照姿势,如果我们手机竖着拿然后逆时针旋转90°这才是正确的拍照姿势,这时候拍出来的照片展示在canvas中是不会被旋转的。如果以其他角度拍搜时,就会发生旋转
解决:
function imgToCanvasWithOrientation(img, width, height, orientation) {
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
canvas.width=width
canvas.height=height
if (判断机型系统不高于ios13.4.1) {
switch (orientation) {
case 3:
ctx.rotate(Math.PI)
ctx.drawImage(img, -width, -height, width, height);
break;
case 6:
ctx.rotate(0.5 * Math.PI)
ctx.drawImage(img, 0, -height, width, height);
break;
case 8:
ctx.rotate(3 * Math.PI / 2)
ctx.drawImage(img, -width, 0, width, height);
break;
default:
ctx.drawImage(img, 0, 0, width, height);
break
}
}
return canvas;
}
原因:浏览器的“休眠”模式,页面未激活状态时,浏览器为节省性能,会停止或减少定时任务
解决:使用visibilitychange监听页面是否可见(激活)去重新拉取后台时间,使用setTimeout去进行倒计时,setTimeout会有误差,每次执行需要计算出减去误差后的时间作为下次执行的间隔
原因:设置opacity小于等于0.01时,就会出现这个问题
解决:设置opacity大于0.01
.box {
opacity:0.011
}
原因:历史包袱问题:以前网站都是为大屏幕电脑设计的,手机上预览就会导致内容被缩小了,为解决这个问题,就约定双击屏幕就将网页等比例放大缩小,如何判断用户是否是双击了呢?那就在首次点击后等待300毫秒,判断用户是否再次点击了屏幕,点击了就判断是双击。这也是会有上述 300 毫秒延迟的主要原因
解决:
1.在HTML文档头部添加如下meta标签,添加了user-scalable=no会禁止缩放
<meta name="viewport" content="width=device-width,user-scalable=no">
2.CSS touch-action属性
html {
touch-action: none
}
原因:因为使用了rem的原因
解决:给元素设置line-height的值大于设置的font-size的值
.test {
font-size: 20px;
line-height: 24px
}
原因:globalThis为undefined的原因
解决:给在入口的html文件将globalThis指向window
<script>
if (globalThis === undefined) { var globalThis = window; }
</script>
原因:转换需要给图片设置允许跨域,但是在ios手机上允许跨域和给src赋值有顺序的区别,在chrome模拟没顺序问题
解决:先给Image对象设置允许跨域,再给Image对象的src赋值
原因:vite 在启动本地服务时,只处理语法转译不包含 polyfill,可选链操作符语法在 ios13.4 以下版本不支持,所以会报错白屏
解决:使用rollup-plugin-esbuild将可选链操作符语法降级到兼容低版本浏览器。如果是生产环境使用*@vitejs/plugin-legacy*
npm install rollup-plugin-esbuild -D
// vite.config.ts
import vue from "@vitejs/plugin-vue";
import legacy from "@vitejs/plugin-legacy";
import esbuild from "rollup-plugin-esbuild";
export default defineConfig(({ command }) => {
const plugins = [vue()];
if (command === "build") {
plugins.push(
legacy({
targets: ["Android >= 8", "iOS >= 10"],
})
);
}
if (command === "serve") {
plugins.push(
esbuild({
target: "ios12",
loaders: { ".vue": "js", ".ts": "js" },
})
);
}
return {
plugins,
};
});
1、安卓手机里面由于在调出键盘的时候整个body高就是可视区的高,只需要fixed或者position为bottom:0就可以,
2、iphone手机在调出键盘的时候body的高度始终不变,在设备信息os 后面的版本号低于11的手机里面可以在聚焦以后把 document.body.scrollTop = document.body.scrollHeight; 放在定时器里面刷,失焦时解除定时器,版本号高于11的版本不需要做这一步document.body.scrollTop = document.body.scrollHeight
参考https://juejin.cn/post/7103835385280593957#heading-0
第一种方法 通过 css 的 DevicePixelRatio 媒体查询属性:
/*默认大小*/
.photo {background-image: url(image100.png);}
/* 如果设备像素大于等于2,则用2倍图 */
@media screen and (-webkit-min-device-pixel-ratio: 2),
screen and (min--moz-device-pixel-ratio: 2) {
.photo {
background-image: url(image200.png);
background-size: 100px 100px;
}
}
/* 如果设备像素大于等于3,则用3倍图 */
@media screen and (-webkit-min-device-pixel-ratio: 3),
screen and (min--moz-device-pixel-ratio: 3) {
.photo {
background-image: url(image300.png);
background-size: 100px 100px;
}
}
.photo {width:100px;height:100px;}
第二种方法通过 scss 的mixin动态修改背景图片,判断设备dpr
@mixin bg-image($url) {
background-image: url($url + "@2x.png");
@media (-webkit-min-device-pixel-ratio: 3),(min-device-pixel-ratio: 3) {
background-image: url($url + "@3x.png");
}
}
.div{
width:30px;
height:20px;
background-size:30px 20px;
background-repeat:no-repeat;
//在这里相当于调用了上面媒体查询的方法 ,传入图片url
@include bg-image('special_1');
} ```
第三种方法也是推荐的方法,通过img标签属性动态切换url:
< img width="100" height="100" src="image100.png" srcset="image200.png 2x,image300.png 3x"/>
浏览器会通过 srcset 属性来自动选择2X,3X图,比如用 iPhone 6s Plus,就会自动选择3x 的图像。
参考https://blog.csdn.net/azhou820567753/article/details/100232334
用二倍图
这个问题是 devicePixelRatio 的不同导致的,因为手机分辨率太小,如果按照分辨率来显示网页,字会非常小,所以苹果系统当初就把 iPhone 4的960x640 像素的分辨率在网页里更改为480x320像素,这样 devicePixelRatio=2。而Android 的 devicePixelRatio比较乱,值有 1.5、2和3。为了在手机里更为清晰地显示图片,必须使用 2 倍宽高的背景图来代替 img标签(一般情况下都使用 2倍 )。
例如一个 div 的宽高是100px 100px,背景图必须是200px200px,然后设置 background-size:contain 样式,显示出来的图片就比较清晰了
方案一:禁用缩放
<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=1,maximum-scale=1">
表明这个页面是不可缩放的,那双击缩放的功能就没有意义了,此时浏览器可以禁用默认的双击缩放行为并且去掉300ms的点击延迟。
这个方案有一个缺点,就是必须通过完全禁用缩放来达到去掉点击延迟的目的,然而完全禁用缩放并不是我们的初衷,我们只是想禁掉默认的双击缩放行为,这样就不用等待300ms来判断当前操作是否是双击。但是通常情况下,我们还是希望页面能通过双指缩放来进行缩放操作,比如放大一张图片,放大一段很小的文字
方案二:更改默认的视口宽度
<meta name="viewport" content="width=device-width">
因为双击缩放主要是用来改善桌面站点在移动端浏览体验的,而随着响应式设计的普及,很多站点都已经对移动端坐过适配和优化了,这个时候就不需要双击缩放了,如果能够识别出一个网站是响应式的网站,那么移动端浏览器就可以自动禁掉默认的双击缩放行为并且去掉300ms的点击延迟。如果设置了上述meta标签,那浏览器就可以认为该网站已经对移动端做过了适配和优化,就无需双击缩放操作了。
这个方案相比方案一的好处在于,它没有完全禁用缩放,而只是禁用了浏览器默认的双击缩放行为,但用户仍然可以通过双指缩放操作来缩放页面。
方案三:CSS touch-action
跟300ms点击延迟相关的,是touch-action这个CSS属性。这个属性指定了相应元素上能够触发的用户代理(也就是浏览器)的默认行为。如果将该属性值设置为touch-action: none
,那么表示在该元素上的操作不会触发用户代理的任何默认行为,就无需进行300ms的延迟判断。
方案四:FastClick
FastClick 是 FT Labs 专门为解决移动端浏览器 300 毫秒点击延迟问题所开发的一个轻量级的库。FastClick的实现原理是在检测到touchend事件的时候,会通过DOM自定义事件立即出发模拟一个click事件,并把浏览器在300ms之后的click事件阻止掉
点击穿透问题
click.stop
,js的解决办法是,直接在事件的方法中添加event.stopPropagation()
element{
-webkit-appearance:none
}
dpr = 物理像素 / css 像素
element{
-webkit-touch-callout:none
}
element{
-webkit-tap-highlight-color:rgba(255,255,255,0) /*设置透明*/
}
在iOS系统的移动设备中,需要在按钮元素或body/html上绑定一个touchstart事件才能激活:active状态。
第一种方案
<body ontouchstart="">
第二种方案
document.body.addEventListener('touchstart', function () { //...空函数即可});
CSS 属性 mask 允许使用者通过遮罩或者裁切特定区域的图片的方式来隐藏一个元素的部分或者全部可见区域。
mask-image这两个单词翻译过来就是,面具 图片,的确很形象,真的就像是给元素带上一个面具一样
使用方法跟ackground类似
兼容处理:
css属性:-webkit-mask-image;这是最简单的方式。
例如:-webkit-mask-image: url(mouse.png);或者-webkit-mask-image: -webkit-linear-gradient(top, rgba(0,0,0,1), rgba(0,0,0,0));等
SVG标签:标签调用标签;
<svg>
<defs> <mask id="mask" maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse"> 遮罩图片 </mask> </defs>
<foreignObject width="400px" height="300px" style="mask: url(#mask);">被遮对象</foreignObject>
</svg>
参考
https://segmentfault.com/a/1190000011838367
https://www.likecs.com/show-307452954.html
.wrap {
-webkit-transform-style: preserve-3d; /*设置内联的元素在 3D 空间如何呈现:保留
3D*/
-webkit-backface-visibility: hidden; /*设置进行转换的元素的背面在面对用户时是否可见:隐藏*/
-webkit-perspective: 1000;
}
background-clip: padding-box;
Babel是什么?
Babel是一个工具集,主要用于将ES6版本的JavaScript代码转为ES5等向后兼容的JS代码,从而可以运行在低版本浏览器或其它环境中。
因此,你完全可以在工作中使用ES6编写程序,最后使用Babel将代码转为ES5的代码,这样就不用担心所在环境是否支持了。下面是一个示例:
转换前,代码里使用ES6箭头函数
var fn = (num) => num + 2;
转换后,箭头函数变成ES5的普通函数。这样就可以在不支持箭头函数的浏览器里运行了
var fn = function fn(num) {
return num + 2;
}
使用Babel进行ES6转ES5时,转化之后默认是严格模式
Babel依赖Node.js,没有安装的话,去官网下载安装最新稳定版本的Node.js。
在本地新建一个文件夹babel01,在该文件夹下新建一个js文件,文件命名为babel.config.js
。该文件是 Babel配置文件 ,我们在该文件里输入如下内容:
module.exports = {
presets: ["@babel/env"],
plugins: []
}
然后在该文件夹下新建一个js文件main.js,该js里的代码是我们需要转译的,我们写入代码
var fn = (num) => num + 2;
然后执行下面的命令安装三个npm包,这些npm包是Babel官方包
// npm一次性安装多个包,包名之间用空格隔开
npm install --save-dev @babel/cli @babel/core @babel/preset-env
安装完成后,执行下面的命令进行转码,该命令含义是把main.js转码生成compiled.js文件
npx babel main.js -o compiled.js
此时文件夹下会生成compiled.js,该文件是转换后的代码:
"use strict";
var fn = function fn(num) {
return num + 2;
};
这就是一个最简单的Babel使用过程,我们把用ES6编写main.js转换成了ES5的compiled.js。
babel.config.js是Babel执行时会默认在当前目录寻找的Babel配置文件。
除了babel.config.js
,我们也可以选择用.babelrc
或.babelrc.js
这两种配置文件,还可以直接将配置参数写在package.json。它们的作用都是相同的,只需要选择其中一种,接下来默认使用babel.config.js
@babel/cli
,@babel/core与
@babel/preset-env
是Babel官方的三个包,它们的作用如下:
一个完整的Babel转码工程通常包括如下
我们通过以下命令对单个JS文件进行转码:
npx babel main.js -o compiled.js
如果安装npm包慢的话,通过以下命令设置npm镜像源为淘宝npm后再安装
npm config set registry https://registry.npm.taobao.org
npx babel main.js -o compiled.js命令里npx是新版Node里附带的命令。它运行的时候默认会找到node_modules/.bin/下的路径执行
总体来说,Babel的主要工作有两部分:
上一节Babel快速入门我们讲的是其实是用Babel进行语法转换,把ES6的箭头函数语法转换成了ES5的函数定义语法。 箭头函数语法、async函数语法、class定义类语法和解构赋值等等都是ES6新增的语法
那什么是补齐API? 简单解释就是,通过 Polyfill 的方式在目标环境中添加缺失的特性
我们按照上一节的操作对var promise = Promise.resolve(‘ok’)进行转换,会发现转换后的代码并没有改变,过程如下。
我们新建babel02文件夹,新建babel配置文件 babel.config.js ,内容如下
module.exports = {
presets: ["@babel/env"],
plugins: []
}
新建main.js文件,内容如下
var fn = (num) => num + 2;
var promise = Promise.resolve('ok')
然后执行下面的命令安装三个npm包
// npm一次性安装多个包,包名之间用空格隔开
npm install --save-dev @babel/cli @babel/core @babel/preset-env
然后执行命令
npx babel main.js -o compiled.js
整个过程与上一节基本一样,只是main.js里的代码多了一行
var promise = Promise.resolve('ok')
此时文件夹下会生成新的compiled.js,代码如下
"use strict";
var fn = function fn(num) {
return num + 2;
};
var promise = Promise.resolve('ok');
我们观察转换后生成的compiled.js代码,发现Babel并没有对ES6的Promise进行转换
我们通过一个index.html文件引用转码后的 compiled.js ,在比较老的浏览器( 例如火狐27 )里打开HTML文件后后控制台报错:Promise is not defined。
为何 Babel没有对ES6的Promise进行转换 ?
因为Babel默认只转换新的JavaScript语法(syntax),而不转换新的 API。
新的API分类两类,一类是Promise、Map、Symbol、Proxy、Iterator等全局对象及其对象自身的方法,例如Object.assign,Promise.resolve;另一类是新的实例方法,例如数组实例方法[1, 4, -5, 10].find((item) => item < 0)
如果想让ES6新的API在低版本浏览器正常运行,我们就不能只做语法转换。
在前端web工程里,最常规的做法是使用polyfill,为当前环境提供一个垫片。所谓垫片,是指垫平不同浏览器之间差异的东西。polyfill提供了全局的ES6对象以及通过修改原型链Array.prototype等实现对实例的实现
polyfill广义上讲是为环境提供不支持的特性的一类文件或库,狭义上讲是polyfill.js文件以及@babel/polyfill这个npm包
我们可以直接在html文件引入polyfill.js文件来作为全局环境垫片, polyfill.js 有Babel官方的 polyfill.js,也有第三方的。我们引入一个Babel官方已经构建好的polyfill脚本。
简单起见,我们通过在html里引入polyfill.js的方式。
<script src="https://cdn.bootcss.com/babel-polyfill/7.6.0/polyfill.js"></script>
我们在IE9打开验证,也可以用Firefox27等低版本的浏览器验证。这个时候发现可以正常运行了
补齐API的方式除了通过引入 polyfill.js 文件 ,还有通过在构建工具入口文件(例如webapck),babel配置文件等方式进行。本节讲的通过在HTML里直接引入 polyfill.js 文件 这种方式进行在现代前端工程里逐渐淘汰,很少使用了。但这种方式对初学者理解 polyfill 是做什么的是简单直接的。后续章节我们会学习到其它补齐API的方式
小结
本节讲了通过 polyfill.js 文件来补齐代码运行时环境所缺失的API。
通过上一节讲的语法转换和本节讲的补齐API,就可以使一个使用ES6编写项目完整运行了不支持ES6语言的环境上了。
目前,前端开发领域使用的Babel版本主要的Babel6和Babel7这两个版本。
你可能想问,怎么查看使用的Babel是哪个版本?
在入门章节,我们讲过Babel是一个工具集,而这个工具集是围绕@babel/core这个核心npm包构成的。每次@babel/core发布新版本的时候,整个工具集的其它npm包也都会跟着升级到与@babel/core相同的版本号,即使它们的代码可能一行都没有改变
因此,我们提到Babel版本的时候,通常是指@babel/core这个Babel核心包的版本
在一次次版本变更的过程中,很多Babel工具以及npm包等都发生了变化,导致其配置文件有各种各样的写法。同时,很多Babel相关的文章没有注意到版本的问题,这给学习者也造成了很大的困惑。
web前端开发有必要了解这两个版本的变化。
Babel7的npm包都是放在babel域下的,即在安装npm包的时候,我们是安装@babel/这种方式,例如@babel/cli、@babel/core等。而在Babel6,我们安装的包名是babel-cli,babel-core等。其实它们本质是一样的,都是Babel官方的cli命令行工具和core核心包,而且功能是一样的,只是名称版本变化了一下而已。在平时开发和学习的过程中,碰到’@babel/'和’babel-'应该下意识认识到他俩原本是一个包,只是版本不一样而已。
配置文件
无论是通过命令行工具babel-cli来进行编译,还是webpack这类的构建工具,通常情况下,我们都需要建立一个Babel配置文件来指定编译的规则。
Babel的配置文件是Babel执行时默认会在当前目录寻找的文件,主要有.babelrc,.babelrc.js,babel.config.js和package.json。它们的配置项都是相同,作用也是一样的,只需要选择其中一种。
对于.babelrc,它的配置是这样子
{
"presets": ["es2015", "react"],
"plugins": ["transform-decorators-legacy", "transform-class-properties"]
}
对于babel.config.js和.babelrc.js,它的配置是一样的,通过module.exports输出配置项
module.exports = {
"presets": ["es2015", "react"],
"plugins": ["transform-decorators-legacy", "transform-class-properties"]
}
对于package.json,就是在package.json中增加一个babel属性和值,它的配置是这样子
{
"name": "demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"babel": {
"presets": ["es2015", "react"],
"plugins": ["transform-decorators-legacy", "transform-class-properties"]
}
}
仔细观察上述几种配置文件,会发现它们的配置项其实都是plugins和presets。
另外,除了把配置写在上述这几种配置文件里,我们也可以写在构建工具的配置里。对于不同的构建工具,Babel也提供了相应的配置项,例如webpack的babel-loader的配置项,其本质和配置文件是一样的
配置文件总结起来就是配置plugins
和presets
这两个数组,我们分别称之为插件数组和预设数组
除了plugins和presets这两个配置项,还有minified、ignore等,但我们平时都用不到,大家还是把精力放在plugins和presets上
推荐使用后缀名是js配置文件,因为可以使用js做一些逻辑处理,适用性更强。举一个例子
// 这里只是举个例子,实际项目中,我们可以传入环境变量等来做处理
var year = 2020;
var presets = [];
if (year > 2018) {
presets = ["@babel/env"];
} else {
presets = "presets": ["es2015", "es2016", "es2017"],
}
module.exports = {
"presets": presets,
"plugins": []
}
插件与预设
plugin代表插件,preset代表预设,它们分别放在plugins和presets,每个插件或预设都是一个npm包。
本节开头提到了通过Babel配置文件来指定编译的规则,所谓编译的规则,就是在配置文件里列出的编译过程中会用到的Babel插件或预设。这些插件和预设会在编译过程中把我们的ES6代码转换成ES5。
Babel插件的数量非常多,处理ES2015的有
处理ES2018的有
所有的插件都需要先安装npm包到node_modules后才可以使用。
Babel插件实在太多,假如只配置插件数组,那我们前端工程要把ES2015,ES2016,ES2017…下的所有插件都写到配置项里,我们的Babel配置文件会非常臃肿。
preset预设就是帮我们解决这个问题的。预设是一组Babel插件的集合,用大白话说就是插件包,例如babel-preset-es2015就是所有处理es2015的二十多个Babel插件的集合。这样我们就不用写一大堆插件配置项了,只需要用一个预设代替就可以了。另外,预设也可以是插件和其它预设的集合。Babel官方已经对常用的环境做了一些preset包
所有的预设也都需要先安装npm包到node_modules。
plugin与preset的短名称
插件可以在配置文件里写短名称,如果插件的npm包名称的前缀为 babel-plugin-,可以省略前缀。例如
module.exports = {
"presets": [],
"plugins": ["babel-plugin-transform-decorators-legacy"]
}
可以写成短名称
module.exports = {
"presets": [],
"plugins": ["transform-decorators-legacy"]
}
如果npm包名称的前缀带有npm作用域@,例如@org/babel-plugin-xxx,短名称可以写成@org/xxx。
目前Babel7的官方npm包里绝大部分插件已经升级为@babel/plugin-前缀的,这种情况的短名称比较特殊了,绝大部分可以像babel-plugin-那样省略@babel/plugin-。但babel官方并没有给出明确的说明,所以还是推荐用全称。
预设的短名称规则与插件的类似,预设npm包名称的前缀为babel-preset-或作用域@xxx/babel-preset-xxx的可以省略掉babel-preset-。
对于Babel7的官方npm包里绝大部分预设已经升级为@babel/preset-前缀的,这种情况的短名称比较特殊了,绝大部分可以像babel-preset-那样省略@babel/preset-。但babel官方并没有给出明确的说明,例如,@babel/preset-env的短名称就是@babel/env,所以还是推荐用全称。
plugins插件数组和presets预设数组是有顺序要求的。如果两个插件或预设都要处理同一个代码片段,那么会根据插件和预设的顺序来执行。规则如下:
Babel插件和预设的参数
每个插件是插件数组的一成员项,每个预设是预设数组的一成员项,默认情况下,成员项都是用字符串来表示的,例如"@babel/preset-env"。
如果要给插件或预设设置参数,那么成员项就不能写成字符串了,而要改写成一个数组。数组的第一项是插件或预设的名称字符串,第二项是个对象,该对象用来设置第一项代表的插件或预设的参数。例如给@babel/preset-env设置参数:
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry"
}
]
]
}
Babel7.8官方的插件和预设目前有100多个,数量这么多,我们一个个都学习其作用是要花费大量时间的。
不过,我们没有必要全部学习。在我们现在的web前端工程里,常用的插件和预设其实只有几个。抓住重点,有的放矢地学习这几个,然后举一反三,这是最快掌握Babel的途径。
preset预设的选择
在Babel6的时代,常见的preset有babel-preset-es2015、babel-preset-es2016、babel-preset-es2017、babel-preset-latest、babel-preset-stage-0、babel-preset-stage-1、babel-preset-stage-2等。
babel-preset-es2015、babel-preset-es2016、babel-preset-es2017分别是TC39每年发布的进入标准的ES语法的转换器预设,我们在这里称之为年代preset。
目前,Babel官方不再推出babel-preset-es2017以后的年代preset了。
babel-preset-stage-0、babel-preset-stage-1、babel-preset-stage-2、babel-preset-stage-3是TC39每年草案阶段的ES语法转换器预设
从Babel7版本开始,上述的预设都已经不推荐使用了,babel-preset-stage-X因为对开发造成了一些困扰,也不再更新。
babel-preset-latest,在Babel6的时候是你在使用它的时候所有年代preset的集合,在Babel6最后一个版本,它是babel-preset-es2015、babel-preset-es2016、babel-preset-es2017这三个的集合。因为Babel官方不再推出babel-preset-es2017以后的年代preset了,所以babel-preset-latest定义变成了TC39每年发布的进入标准的ES语法的转换器预设集合。其实,和Babel6时的内涵是一样的。
@babel/preset-env包含了babel-preset-latest的功能,并对其进行增强,现在@babel/preset-env完全可以替代babel-preset-latest。
经过一番梳理,可以总结为以前要用到的那么多preset预设,现在只需一个@babel/preset-env
就可以了
在实际开发过程中,除了使用@babel/preset-env对标准的ES6语法转换,我们可能还需要类型检查和react等预设对特定语法转换。这里有三个官方预设可以使用:
总结起来,Babel官方的preset,我们实际可能会用到的其实就只有4个:
一个普通的vue工程,Babel官方的preset只需要配一个"@babel/preset-env"就可以了。
plugin插件的选择
虽然Babel7官方有90多个插件,不过大半已经整合在@babel/preset-env和@babel/preset-react等预设里了,我们在开发的时候直接使用预设就可以了。
目前比较常用的插件只有@babel/plugin-transform-runtime
。目前我做过的几个项目,前端工程已经很少见到里使用其它的插件了。
小结
这节课我们主要学习了插件和预设的选择,经过一番筛选后,我们找出了会在我们开发的过程中可能用到4个预设和1个插件。分别是@babel/preset-env
、@babel/preset-flow
、@babel/preset-react
、@babel/preset-typescript
这4个预设,以及 @babel/plugin-transform-runtime
这1个插件。
babel-polyfill在Babel7以后名字是@babel/polyfill
polyfill广义上讲是为环境提供不支持的特性的一类文件或库,既有Babel官方的库,也有第三方的。babel-polyfill指的是Babel官方的polyfill,本教程默认使用babel-polyfill。polyfill传统上分两类,一类是已构建成JS文件的polyfill.js,另一类是未构建的需要安装npm包@babel/polyfill。因为@babel/polyfill本质是由两个npm包core-js与regenerator-runtime组合而成的,所以在使用层面上还可以再细分为是引入@babel/polyfill本身还是其组合子包。
总体来说,Babel官方的polyfill使用方法主要有如下几种:
所有的例子,我们仍以火狐27.0不支持的Promise为例,进行演示。该版本的火狐,在遇到如下代码的时会报错
var promise = Promise.resolve('ok');
console.log(promise);
报错信息为:ReferenceError: Promise is not defined
我们需要做的就是让火狐27.0可以正常运行我们的代码,下面对上文提到的7种方法进行讲解。
直接在html文件引入Babel官方的polyfill.js脚本文件
该方法在分类上属于使用已构建成JS文件polyfill.js的一类,该方法在引入polyfill一节已经讲过,本节不再重复讲解。
在前端工程的入口文件里引入polyfill.js
该方法在分类上属于使用已构建成JS文件polyfill.js的一类,该我们以目前业界最为流行的webpack打包工具为例,讲述该方法。
我们的工程里有a.js与index.html文件,a.js文件的内容是
var promise = Promise.resolve('ok');
console.log(promise);
index.html文件在head标签里直接引入了a.js文件,这个时候在火狐27.0下打开该html会报错。
在之前的例子里,我们是在index.html里单独引入了polyfill.js文件对API进行补齐。现在,我们换一种方式,通过在工程入口文件a.js引入polyfill.js进行补齐。
我们使用webpack来讲述这个过程,首先进行webpack和其命令行工具的安装
npm install webpack webpack-cli --save-dev
在webpack4.0和node8.2以上版本,我们可以使用npx webpack a.js -o b.js命令来进行打包。该命令的意思是,指定工程入口文件是a.js,最后打包成b.js。
为了方便,我们在package.json里配置scripts项,现在,只需要执行npm run dev,就会自动执行npx webpack a.js -o b.js命令,即可完成打包。
"scripts": {
"dev": "npx webpack a.js -o b.js"
},
在我们这个例子里,前端工程入口文件是a.js,我们只需要在a.js最上方加入一句
import './polyfill.js';
然后执行npm run dev,就可以把polyfill打包到我们的最终生成的文件里(我们需要提前在相应的文件目录里存放polyfill.js)。
现在,我们把index.html使用的a.js改成b.js,然后在火狐27.0打开,可以看到控制台已经正常。
在前端工程的入口文件里引入@babel/polyfill
该方法在分类上属于使用未构建的需要安装npm包@babel/polyfill的一类,其实整个过程和上面的例子非常像,不一样的地方如下。
import './polyfill.js';
改成 import '@babel/polyfill';
npm install --save @babel/polyfill
除了这两点,其余的地方和上面的例子完全。
执行npm run dev,然后和之前一样在火狐打开进行验证正常。
在前端工程的入口文件里引入core-js/stable与regenerator-runtime/runtime
该方法在分类上属于使用未构建的需要安装npm包@babel/polyfill的组合子包的一类,我们仍以目前业界最为流行的webpack构建工具为例,讲述该方法。后续默认是使用webpack。
该方法需要我们单独安装单独安装core-js与regenerator-runtime这两个npm包,这种方式core-js是默认是3.x.x版本。
需要注意的是,我们使用该方法的时候,不能再安装@babel/polyfill了。因为@babel/polyfill在安装的时候,会自动把core-js与regenerator-runtime这两个依赖安装上了,而@babel/polyfill使用的core-js已经锁死为2.x.x版本了。core-js的2.x.x版本里并没有stable文件目录,所以安装@babel/polyfill后再引入core-js/stable会报错。
其实这个方法和上面的例子也是非常像,就是把一个npm包换成了两个而已。不一样的地方具体如下
import '@babel/polyfill';
改成如下import "core-js/stable";
import "regenerator-runtime/runtime";
2)安装两个npm包core-js和regenerator-runtime
```
npm install --save core-js regenerator-runtime
```
替换之前安装的@babel/polyfill
执行npm run dev,然后和之前一样在火狐打开进行验证正常。
在前端工程构建工具的配置文件入口项引入polyfill.js
webpack的配置文件有多种类型,我们采用webpack.config.js,其它类型的webpack配置文件与其一致。
因为要在webpack配置文件里指定入口文件,我们就不手动使用webpack a.js -o b.js来进行打包了,而是在webpack.config.js进行设置。
const path = require('path');
module.exports = {
entry: ['./a.js'],
output: {
filename: 'b.js',
path: path.resolve(__dirname, '')
},
mode: 'development'
};
webpack的配置文件的入口项是entry,这里entry的值我们设置成数组,a.js就是入口文件。然后,package.json里的dev命令改为
"scripts": {
"dev": "npx webpack"
},
现在我们执行npm run dev,webpack就完成了打包。现在我们index.html直接引用b.js,火狐27会报错。原因我们都知道,我们没有使用polyfill。
那么,在前端工程构建工具的配置文件入口项引入polyfill.js,该怎么操作呢?
其实很简单,那就是把数组的第一项改成’./polyfill.js’,原先的入口文件作为数组的第二项,polyfill就会打包到我们生成后的文件里了。
const path = require('path');
module.exports = {
entry: ['./polyfill.js', './a.js'],
output: {
filename: 'b.js',
path: path.resolve(__dirname, '')
},
mode: 'development'
};
现在再执行npm run dev进行打包,然后index.html就不会在火狐27里报错了。
在前端工程构建工具的配置文件入口里引入@babel/polyfill;
如果你对之前讲的方法都理解的话,那么相信你也会很容易理解该方法。该方法就是把上个方法的entry的第一项换成@babel/polyfill,并且安装了@babel/polyfill这个包就可以了。
npm install --save @babel/polyfill
webpack.config.js配置如下
const path = require('path');
module.exports = {
entry: ['@babel/polyfill', './a.js'],
output: {
filename: 'b.js',
path: path.resolve(__dirname, '')
},
mode: 'development'
};
现在再执行npm run dev进行打包,然后index.html就不会在火狐27里报错了。
npm install --save core-js regenerator-runtime
然后webpack.config.js的entry项数组的前两项改为core-js/stable和regenerator-runtime/runtime const path = require('path');
module.exports = {
entry: ['core-js/stable', 'regenerator-runtime/runtime', './a.js'],
output: {
filename: 'b.js',
path: path.resolve(__dirname, '')
},
mode: 'development'
};
现在再执行npm run dev进行打包,然后index.html就可以正常在火狐27.0运行了。从babel7.4开始,官方不推荐再使用@babel/polyfill了,因为@babel/polyfill本身其实就是两个npm包的集合:core-js与regenerator-runtime。
官方推荐直接使用这两个npm包。虽然@babel/polyfill还在进行版本升级,但其使用的core-js包为2.x.x版本,而core-js这个包本身已经发布到了3.x.x版本了,@babel/polyfill以后也不会使用3.x.x版本的包了。新版本的core-js实现了许多新的功能,例如数组的includes方法。
虽然从babel7.4开始,不推荐再使用@babel/polyfill了,但我们仍然把传统@babel/polyfill的使用方式在本节进行讲解,这对于理解其使用方式是非常有帮助的。
ES6补齐API的方式,除了上述几种在前端工程入口文件或构建工具的配置文件里使用polyfill(或是其子包)的方式,还有使用Babel预设或插件进行补齐API的方式。
上述使用polyfill的方式,是把整个npm包或polyfill.js放到了我们最终的项目里了。完整的polyfill文件非常大,会影响我们的页面加载时间。
如果我们的运行环境已经实现了部分ES6的功能,那实在没有必要把整个polyfill都给引入。我们可以部分引入,这个时候需要使用Babel预设或插件了。
Babel预设或插件不光可以进行补齐API,还可以对API进行转换
小结
在Babel6时代,这个预设名字是 babel-preset-env,在Babel7之后,改成@babel/preset-env
@babel/preset-env是整个Babel大家族最重要的一个preset。不夸张地说,所有配置项仅需要它自己就可以完成现代JS工程所需要的所有转码要求。
在使用它之前,需要先安装
npm install --save-dev @babel/preset-env
@babel/preset-env是Babel6时代babel-preset-latest的增强版。该预设除了包含所有稳定的转码插件,还可以根据我们设定的目标环境进行针对性转码。
在Babel快速入门一节,我们简单使用过@babel/preset-env的语法转换功能。除了进行语法转换,该预设还可以通过设置参数项进行针对性语法转换以及polyfill的部分引入。
@babel/preset-env的参数项,数量有10多个,但大部分我们要么用不到,要么已经或将要弃用。这里建议大家掌握重点的几个参数项,有的放矢。重点要学习的参数项有targets、useBuiltIns、modules和corejs这四个,能掌握这几个参数项的真正含义,就已经超过绝大部分开发者了。
对于preset,当我们不需要对其设置参数的时候,其写法是只需要把该preset的名字放入presets对于的数组里即可,例如
module.exports = {
presets: ["@babel/env"],
plugins: []
}
注意,@babel/env是@babel/preset-env的简写
如果需要对某个preset设置参数,该preset就不能以字符串形式直接放在presets的数组项了。而是应该再包裹一层数组,数组第一项是该preset字符串,数组第二项是该preset的参数对象。如果该preset没有参数需要设置,则数组第二项可以是空对象或者不写第二项。以下几种写法是等价的:
module.exports = {
presets: ["@babel/env"],
plugins: []
}
module.exports = {
presets: [["@babel/env", {}]],
plugins: []
}
module.exports = {
presets: [["@babel/env"]],
plugins: []
}
如果你使用过vue或react的官方脚手架cli工具,你一定会在其package.json里看到browserslist项,下面该项配置的一个例子:
"browserslist": [
"> 1%",
"not ie <= 8"
]
上面的配置含义是,目标环境是市场份额大于1%的浏览器并且不考虑IE8及以下的IE浏览器。Browserslist叫做目标环境配置表,除了写在package.json里,也可以单独写在工程目录下.browserslistrc文件里。我们用browserslist来指定代码最终要运行在哪些浏览器或node.js环境。Autoprefixer、postcss等就可以根据我们的browserslist,来自动判断是否要增加CSS前缀(例如’-webkit-')。我们的Babel也可以使用browserslist,如果你使用了@babel/preset-env这个预设,此时Babel就会读取browserslist的配置。
如果我们的@babel/preset-env不设置任何参数,Babel就会完全根据browserslist的配置来做语法转换。如果没有browserslist,那么Babel就会把所有ES6的语法转换成ES5版本。
在本教程最初的例子里,我们没有browserslist,并且@babel/preset-env的参数项是空的,ES6箭头函数语法被转换成了ES5的函数定义语法。
转换前
var fn = (num) => num + 2;
转换后
"use strict";
var fn = function fn(num) {
return num + 2;
};
如果我们在browserslist里指定目标环境是Chrome60,我们再来看一下转换结果
"browserslist": [
"chrome 60"
]
转换后
"use strict";
var fn = num => num + 2;
我们发现转换后的代码仍然是箭头函数,因为Chrome60已经实现了箭头函数语法,所以不会转换成ES5的函数定义语法。
现在我们把Chrome60改成Chrome38,再看看转换后的结果
"browserslist": [
"chrome 38"
]
转换后
"use strict";
var fn = function fn(num) {
return num + 2;
};
我们发现转换后的代码是ES5的函数定义语法,因为Chrome38不支持箭头函数语法。
注意,Babel使用browserslist的配置功能依赖于@babel/preset-env,如果Babel没有配置任何预设或插件,那么Babel对转换的代码会不做任何处理,原封不动生成和转换前一样代码。
既然@babel/preset-env可以通过browserslist针对目标环境不支持的语法进行语法转换,那么是否也可以对目标环境不支持的特性API进行部分引用呢?这样我们就不用把完整的polyfill全部引入到最终的文件里,可以大大减少体积。
答案是可以的,但需要对@babel/preset-env的参数项进行设置才可以,这个我们接下来讲。
该参数项可以取值为字符串、字符串数组或对象,不设置的时候取默认值空对象{}。
该参数项的写法与browserslist是一样的,下面是一个例子
module.exports = {
presets: [["@babel/env", {
targets: {
"chrome": "58",
"ie": "11"
}
}]],
plugins: []
}
如果我们对@babel/preset-env的targets参数项进行了设置,那么就不使用browserslist的配置,而是使用targets的配置。如不设置targets,那么就使用browserslist的配置。如果targets不配置,browserslist也没有配置,那么@babel/preset-env就对所有ES6语法转换成ES5的。
正常情况下,我们推荐使用browserslist的配置而很少单独配置@babel/preset-env的targets。
useBuiltIns项取值可以是"usage" 、 “entry” 或 false。如果该项不进行设置,则取默认值false。
useBuiltIns这个参数项主要和polyfill的行为有关。在我们没有配置该参数项或是取值为false的时候,polyfill就是我们上节课讲的那样,会全部引入到最终的代码里。
useBuiltIns取值为"entry"或"usage"的时候,会根据配置的目标环境找出需要的polyfill进行部分引入。让我们看看这两个参数值使用上的不同。
useBuiltIns:“entry”
我们在入口文件用import语法引入polyfill(也可以在webpack的entry入口项)。此时的Babel配置文件如下:
module.exports = {
presets: [["@babel/env", {
useBuiltIns: "entry"
}]],
plugins: []
}
需要安装的npm包如下:
npm install --save-dev @babel/cli @babel/core @babel/preset-env
npm install --save @babel/polyfill
我们指定目标环境是火狐58,package.json里的browserslist设置如下:
"browserslist": [
"firefox 58"
]
转换前端的代码如下:
import '@babel/polyfill';
var promise = Promise.resolve('ok');
console.log(promise);
使用npx babel a.js -o b.js命令进行转码
转码后:
"use strict";
require("core-js/modules/es7.array.flat-map");
require("core-js/modules/es7.string.trim-left");
require("core-js/modules/es7.string.trim-right");
require("core-js/modules/web.timers");
require("core-js/modules/web.immediate");
require("core-js/modules/web.dom.iterable");
var promise = Promise.resolve('ok');
console.log(promise);
我们可以看到Babel针对火狐58不支持的API特性进行引用,一共引入了6个core-js的API补齐模块。同时也可以看到,因为火狐58已经支持Promise特性,所以没有引入promise相关的API补齐模块。你可以试着修改browserslist里火狐的版本,修改成版本26后,会引入API模块大大增多,有几十个。
useBuiltIns:“usage”
"usage"在Babel7.4之前一直是试验性的,7.4之后的版本稳定。
这种方式不需要我们在入口文件(以及webpack的entry入口项)引入polyfill,Babel发现useBuiltIns的值是"usage"后,会自动进行polyfill的引入。
我们的Babel配置文件如下:
module.exports = {
presets: [["@babel/env", {
useBuiltIns: "usage"
}]],
plugins: []
}
需要安装的npm包如下:
npm install --save-dev @babel/cli @babel/core @babel/preset-env
npm install --save @babel/polyfill
我们指定目标环境是火狐27,package.json里的browserslist设置如下:
"browserslist": [
"firefox 27"
]
转换前端的代码如下:
var promise = Promise.resolve('ok');
console.log(promise);
使用npx babel a.js -o b.js命令进行转码。
下面是转换后的代码:
"use strict";
require("core-js/modules/es6.promise");
require("core-js/modules/es6.object.to-string");
var promise = Promise.resolve('ok');
console.log(promise);
观察转换的代码,我们发现引入的core-js的API补齐模块非常少,只有2个。为什么呢?
因为我们的代码里只使用了Promise这一火狐27不支持特性API,使用useBuiltIns:"usage"后,Babel除了会考虑目标环境缺失的API模块,同时考虑我们项目代码里使用到的ES6特性。只有我们使用到的ES6特性API在目标环境缺失的时候,Babel才会引入core-js的API补齐模块。
这个时候我们就看出了’entry’与’usage’这两个参数值的区别:'entry’这种方式不会根据我们实际用到的API进行针对性引入polyfill,而’usage’可以做到。另外,在使用的时候,'entry’需要我们在项目入口处手动引入polyfill,而’usage’不需要。
需要注意的是,使用’entry’这种方式的时候,只能import polyfill一次,一般都是在入口文件。如果进行多次import,会发生错误。
该参数项的取值可以是2或3,没有设置的时候取默认值为2(还有一种对象proposals取值方法,我们实际用不到,忽略掉即可)
这个参数项只有useBuiltIns设置为’usage’或’entry’时,才会生效。
取默认值或2的时候,Babel转码的时候使用的是core-js@2版本(即core-js2.x.x)。因为某些新API只有core-js@3里才有,例如数组的flat方法,我们需要使用core-js@3的API模块进行补齐,这个时候我们就把该项设置为3。
需要注意的是,corejs取值为2的时候,需要安装并引入core-js@2版本,或者直接安装并引入polyfill也可以。如果corejs取值为3,必须安装并引入core-js@3版本才可以,否则Babel会转换失败并提示:
‘@babel/polyfill’ s deprecated. Please, use required parts of ’core-js’ and regenerator-runtime/runtime ’separately’
这个参数项的取值可以是"amd"、“umd” 、 “systemjs” 、 “commonjs” 、“cjs” 、“auto” 、false。在不设置的时候,取默认值"auto"。
该项用来设置是否把ES6的模块化语法改成其它模块化语法。
我们常见的模块化语法有两种:(1)ES6的模块法语法用的是import与export;(2)commonjs模块化语法是require与module.exports。
在该参数项值是’auto’或不设置的时候,会发现我们转码前的代码里import都被转码成require了。
如果我们将参数项改成false,那么就不会对ES6模块化进行更改,还是使用import引入模块。
使用ES6模块化语法有什么好处呢。在使用Webpack一类的打包工具,可以进行静态分析,从而可以做tree shaking 等优化措施。
本节主要讲@babel/plugin-transform-runtime以及@babel/runtime。
在我们用Babel做语法转换的时候(注意,这里是单纯的做语法转换,暂时不使用polyfill补齐API),需要Babel在转换后的代码里注入一些函数才能正常工作,先看一个例子
Babel配置文件如下,用@babel/preset-env做语法转换:
{
"presets": [
"@babel/env"
],
"plugins": [
]
}
转换前的代码使用了ES6的class类语法:
class Person {
sayname() {
return 'name'
}
}
var john = new Person()
console.log(john)
Babel转码后生成的代码如下:
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
var Person = /*#__PURE__*/function () {
function Person() {
_classCallCheck(this, Person);
}
_createClass(Person, [{
key: "sayname",
value: function sayname() {
return 'name';
}
}]);
return Person;
}();
var john = new Person();
console.log(john);
可以看到转换后的代码上面增加了好几个函数声明,这就是注入的函数,我们称之为辅助函数。@babel/preset-env在做语法转换的时候,注入了这些函数声明,以便语法转换后使用。
但样这做存在一个问题。在我们正常的前端工程开发的时候,少则几十个js文件,多则上千个。如果每个文件里都使用了class类语法,那会导致每个转换后的文件上部都会注入这些相同的函数声明。这会导致我们用构建工具打包出来的包非常大。
那么怎么办?一个思路就是,我们把这些函数声明都放在一个npm包里,需要使用的时候直接从这个包里引入到我们的文件里。这样即使上千个文件,也会从相同的包里引用这些函数。通过webpack这一类的构建工具打包的时候,我们只会把使用到的npm包里的函数引入一次,这样就做到了复用,减少了体积。
@babel/runtime就是上面说的这个npm包,@babel/runtime把所有语法转换会用到的辅助函数都集成在了一起。
我们先安装这个包:
npm install --save @babel/runtime
npm install --save-dev @babel/cli @babel/core @babel/preset-env
然后到node_modules目录下看一下这个包结构
_classCallCheck, _defineProperties与 _createClass这个三个辅助函数就在图片所示的位置,我们直接引入即可。
我们手动把辅助函数替换掉函数声明,之前文件的代码就变成如下所示:
"use strict";
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
var _defineProperties = require("@babel/runtime/helpers/defineProperties");
var _createClass = require("@babel/runtime/helpers/createClass");
var Person = /*#__PURE__*/function () {
function Person() {
_classCallCheck(this, Person);
}
_createClass(Person, [{
key: "sayname",
value: function sayname() {
return 'name';
}
}]);
return Person;
}();
var john = new Person();
console.log(john);
这样就解决了代码复用和最终文件体积大的问题。不过,这么多辅助函数要一个个记住并手动引入,平常人是做不到的,我也做不到。这个时候,Babel插件@babel/plugin-transform-runtime就来帮我们解决这个问题。
@babel/plugin-transform-runtime有三大作用,其中之一就是自动移除语法转换后内联的辅助函数(inline Babel helpers),使用@babel/runtime/helpers里的辅助函数来替代。这样就减少了我们手动引入的麻烦。
现在我们除了安装@babel/runtime包提供辅助函数模块,还要安装Babel插件@babel/plugin-transform-runtime来自动替换辅助函数:
npm install --save @babel/runtime
npm install --save-dev @babel/cli @babel/core @babel/preset-env @babel/plugin-transform-runtime
现在,我们的Babel配置文件如下:
{
"presets": [
"@babel/env"
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
转换前a.js代码:
class Person {
sayname() {
return 'name'
}
}
var john = new Person()
console.log(john)
执行"npx babel a.js -o b.js"命令后,转换生成的b.js里代码如下:
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var Person = /*#__PURE__*/function () {
function Person() {
(0, _classCallCheck2["default"])(this, Person);
}
(0, _createClass2["default"])(Person, [{
key: "sayname",
value: function sayname() {
return 'name';
}
}]);
return Person;
}();
var john = new Person();
console.log(john);
可以看到,它生成的代码比我们完全手动引入@babel/runtime里的辅助函数更加优雅。实际前端开发的时候,我们除了安装@babel/runtime这个包外,一定会安装@babel/plugin-transform-runtime这个Babel插件包的。
注:
每个转换后的文件上部都会注入这些相同的函数声明,那为何不用webpack一类的打包工具去掉重复的函数声明,而是要单独再引一个辅助函数包?
webpack在构建的时候,是基于模块来做去重工作的。每一个函数声明都是引用类型,在堆内存不同的空间存放,缺少唯一的地址来找到他们。所以webpack本身是做不到把每个文件的相同函数声明去重的。因此我们需要单独的辅助函数包,这样webpack打包的时候会基于模块来做去重工作。
自动移除语法转换后内联的辅助函数(inline Babel helpers),使用@babel/runtime/helpers里的辅助函数来替代;
当代码里使用了core-js的API,自动引入@babel/runtime-corejs3/core-js-stable/,以此来替代全局引入的core-js/stable;
当代码里使用了Generator/async函数,自动引入@babel/runtime/regenerator,以此来替代全局引入的regenerator-runtime/runtime;
作用2和3其实是在做API转换,对内置对象进行重命名,以防止污染全局环境。
在babel-polyfill一节,我们学习了引入’babel-polyfill’或’core-js/stable与regenerator-runtime/runtime’来做全局的API补齐。但这样可能有一个问题,那就是对运行环境产生了污染。例如Promise,我们的polyfill是对浏览器的全局对象进行了重新赋值,我们重写了Promise及其原型链。
有时候我们不想改变或补齐浏览器的window.Promise,那么我们就不能使用’babel-polyfill’或’core-js/stable与regenerator-runtime/runtime’,因为其会对浏览器环境产生污染(即修改了浏览器的window.Promise)。
这个时候我们就可以使用@babel/plugin-transform-runtime,它可以对我们代码里ES6的API进行转换。还是以Promise举例子。
Babel转换前的代码
var obj = Promise.resolve();
若使用了’babel-polyfill’或’core-js/stable与regenerator-runtime/runtime’来做全局的API补齐,那么Babel转换后的代码仍然是
var obj = Promise.resolve();
polyfill只是补齐了浏览器的window.Promise对象。
若我们不使用polyfill,而开启@babel/plugin-transform-runtime的API转换功能。那么Babel转换后的代码将是
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var obj = _promise["default"].resolve();
看到效果了没?@babel/plugin-transform-runtime把我们代码里的Promise变成了_promise[“default”],而_promise[“default”]拥有ES标准里Promise所有的功能。现在,即使浏览器没有Promise,我们的代码也能正常运行。
开启core-js相关API转换功能的Babel配置与安装的npm包如下
配套代码是github仓库 https://github.com/jruit/babel-tutorial 的babel14例子
{
"presets": [
"@babel/env"
],
"plugins": [
["@babel/plugin-transform-runtime", {
"corejs": 3
}]
]
}
npm install --save @babel/runtime-corejs3
npm install --save-dev @babel/cli @babel/core @babel/preset-env @babel/plugin-transform-runtime
那么,上面讲的API转换有什么用,明明通过polyfill补齐API的方式也可以使代码在浏览器正常运行?
其实,API转换主要是给开发JS库或npm包等的人用的,我们的前端工程一般仍然使用polyfill补齐API。
可以想象,如果开发JS库的人使用polyfill补齐API,我们前端工程也使用polyfill补齐API,但JS库的polyfill版本或内容与我们前端工程的不一致,那么我们引入该JS库后很可能会导致我们的前端工程出问题。所以,开发JS库或npm包等的人会用到API转换功能。
当然,我们前端工程开发的时候也是可以使用@babel/plugin-transform-runtime的API转换功能,毕竟没有污染全局环境,不会有任何冲突。@babel/plugin-transform-runtime的默认设置下,就是对generators/async开启了API转换功能。
细心的你可能已经发现了,我们安装npm包的时候,安装的是@babel/runtime-corejs3,而上一节我们安装的是@babel/runtime。
看名字挺像的,那么这两者有什么不同呢?
在我们不需要开启core-js相关API转换功能的时候,我们只需要安装@babel/runtime就可以了。上一节我们已经知道,@babel/runtime里存放的是Babel做语法转换的辅助函数。
在我们需要开启core-js相关API转换功能的时候,就需要安装@babel/runtime的进化版@babel/runtime-corejs3。这个npm包里除了包含Babel做语法转换的辅助函数,也包含了core-js的API转换函数。
除了这两个包,还有一个@babel/runtime-corejs2的包。它和@babel/runtime-corejs3的功能是一样的,只是里面的函数是针对core-js2版本的。
上面的例子主要是拿Promise来讲的,它属于作用2,即对core-js的API进行转换。其实理解了作用2,也就理解了作用3。
下面简单说一下作用3。
在之前章节,若我们转码前代码里有Generator函数或async函数,转码后需要引入’regenerator-runtime/runtime’做全局API补齐。
全局API补齐必然会对浏览器的window对象进行修改,如果我们不想要污染window,那么我们就不能引入’regenerator-runtime/runtime’了。
这个时候,我们可以开启@babel/plugin-transform-runtime的作用3,对Generator/async进行API转换。
需要注意的是,@babel/plugin-transform-runtime对Generator/async进行API转换功能,默认是开启的,不需要我们设置。
如何开启或关闭@babel/plugin-transform-runtime的某个功能,除了与安装的npm包有关,也与Babel配置文件的配置有关
注:
@babel/plugin-transform-runtime是否要开启某功能,都是在配置项里设置的,某些配置项的设置是需要安装npm包的。
@babel/plugin-transform-runtime在没有设置配置项的时候,其配置项参数取默认值。下面的两个配置作用是等效的。
{
"plugins": [
"@babel/plugin-transform-runtime"
]
}
// 是上方的默认值
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"helpers": true,
"corejs": false,
"regenerator": true,
"useESModules": false,
"absoluteRuntime": false,
"version": "7.0.0-beta.0"
}
]
]
}
配置项讲解
helpers
该项是用来设置是否要自动引入辅助函数包,这个我们当然要引入了,这是@babel/plugin-transform-runtime的核心用途。该项取值是布尔值,我们设置为true,其默认值也是true,所以也可以省略不填
corejs和regenerator
这两项是用来设置是否做API转换以避免污染全局环境,regenerator取值是布尔值,corejs取值是false、2和3。这个上一节已经讲过了,在前端业务项目里,我们一般对corejs取false,即不对Promise这一类的API进行转换。而在开发JS库的时候设置为2或3。regenerator取默认的true就可以
useESModules
该项用来设置是否使用ES6的模块化用法,取值是布尔值。默认是fasle,在用webpack一类的打包工具的时候,我们可以设置为true,以便做静态分析。
absoluteRuntime
该项用来自定义@babel/plugin-transform-runtime引入@babel/runtime/模块的路径规则,取值是布尔值或字符串。没有特殊需求,我们不需要修改,保持默认false即可。
version
该项主要是和@babel/runtime及其进化版@babel/runtime-corejs2、@babel/runtime-corejs3的版本号有关系,这三个包我们只需要根据需要安装一个。我们把安装的npm包的版本号设置给version即可。例如,在上节的babel14例子里,安装的@babel/runtime-corejs3版本是7.10.4,那么配置项version也取’7.10.4’。
其实该项不填取默认值就行,目前填写版本号主要是可以减少打包体积。
另外,在Babel6版本,该插件还有两个配置选项polyfill和useBuiltIns,在v7版本已经移除了,大家不需要再使用。
小结:
要使用@babel/plugin-transform-runtime插件,其实只有一个npm包是必须要装的,那就是它自己@babel/plugin-transform-runtime。
对于@babel/runtime及其进化版@babel/runtime-corejs2、@babel/runtime-corejs3,我们只需要根据自己的需要安装一个。
如果你不需要对core-js做API转换,那就安装@babel/runtime并把corejs配置项设置为false即可。
如果你需要用core-js2做API转换,那就安装@babel/runtime-corejs2并把corejs配置项设置为2即可。
如果你需要用core-js3做API转换,那就安装@babel/runtime-corejs3并把corejs配置项设置为3即可。
注:
@babel/core是我们使用Bable进行转码的核心npm包,我们使用的babel-cli、babel-node都依赖这个包,因此我们在前端开发的时候,都需要安装这个包。
在我们的工程目录里,执行下面的命令,安装@babel/core。
npm install --save-dev @babel/core
对于大部分开发者来说,这一小节的知识到这来就可以结束了,只需要知道Babel转码必须要安装这个包即可。下面的内容会讲解@babel/core自身对外提供的API。
无论我们是通过命令行转码,还是通过webpack进行转码,底层都是通过Node来调用@babel/core相关功能API来进行的。
我们来看一个例子,来看一下Node是如何调用@babel/core的API来进行转译的。
我们先新建一个index.js文件,这个文件在写完后直接用Node来执行。
var babelCore = require("@babel/core");
var es6Code = 'var fn = (num) => num + 2';
var options = {
presets: ["@babel/env"]
};
var result = babelCore.transform(es6Code, options);
console.log(result);
console.log('--------------');
console.log('--------------');
console.log(result.code);
我们来看一下这段代码的意思。
第1行我们引入了@babel/core模块,并将模块输出赋值给了变量babelCore。第2行变量es6Code是一个字符串,字符串内容是一个箭头函数,该字符串内容是我们需要转译的代码,这个变量传递给了接下来transform方法的第1个参数。第3行options是一个对象,这个对象传递给了接下来transform方法的第2个参数。最后,我们调用babelCore的transform方法,我们把结果打印在Node的控制台上。为了方便看输出结果,中间用’------'隔开。
可以看到,transform后的结果是个对象,该对象的code就是我们转码后的结果。
这就是@babel/core底层的一个调用过程。
transform也可以有第3个参数,第3个参数是一个回调函数,用来对转码后的对象进行进一步处理。@babel/core除了transform这个API,还有transformSync、transformAsync和transformFile等同步异步以及对文件进行转码的API,这里就不展开讲了,用法和上面的transform大同小异。
@babel/cli是一个npm包,安装了它之后,我们就可以在命令行里使用命令进行转码了。
它的安装方法有全局安装和项目本地安装。
执行下面的命令可以进行全局安装
npm install --global @babel/cli
执行下面的命令可以进行项目本地安装
npm install --save-dev @babel/cli
@babel/cli如果是全局安装的,我们在命令行使用babel XXX进行转码,如果是项目本地安装的,那么命令行里使用npx babel XXX进行转码。下面是一个基本例子,把a.js文件转码为b.js。
提醒:转码前不要忘记写Babel配置文件,以及安装@babel/core。
# @babel/cli如果是全局安装的
babel a.js -o b.js
# @babel/cli如果是本地安装的
npx babel a.js -o b.js
两种方法是等效的,正常情况下,我们都是推荐项目本地安装。
对于大部分开发者来说,这一小节的知识到这来就完全够用了。下面介绍一下@babel/cli的其它命令。
将转译后的代码输出到Node.js的标准输出流
npx babel a.js
将转译后的代码写入到一个文件(上方刚使用过)
npx babel a.js -o b.js
或
npx babel a.js --out-file b.js
-o是–out-file的简写
转译整个文件夹目录
npx babel input -d output
或
npx babel input --out-dir output
-d是–out-dir的简写
@babel/node在真正做前端项目开发的时候,是用不到的。该工具执行的时候需要占用大量内存空间,Babel官方不建议在生产环境使用该工具。因此若不是想深入研究该工具,下面的内容可以跳过。
@babel/node其实和node的功能非常接近,@babel/node的优点是在执行命令的时候可以配置Babel的编译配置项。如果遇到node.js不支持的ES6语法,我们通过@babel/node就可以完成。
在Babel6版本的时候,@babel/node这个工具是 @babel/cli附带的,所以只要安装了@babel/cli ,就可以直接使用 babel/node。但Babel7里,我们需要单独安装。
npm install --save-dev @babel/node
然后我们就可以用@babel/node的babel-node命令来运行js文件。
提醒:不要忘记写Babel配置文件,以及安装core。
var promise = Promise.resolve('ok')
console.log(promise)
然后执行
npx babel-node a.js
可以看到命令行输出了promise实例。
@babel/node也可以像node那样进入REPL环境。在命令行下执行下面的命令进入REPL环境
npx babel-node
然后在REPL交互环境输入下面的内容
> (x => x + 10)(5)
注意,>是交互环境提示符,不需要我们输入。
可以看到输出结果15。
小结
@babel/node提供了比Node.js更强大的功能,但我们开发的时候用不到它的。
@babel/register这个工具在我们平时的前端工程开发过程中也是用不到的。但若是想开发某些特殊的包,你可能会需要它。
@babel/register只有一个功能,就是重写node的require方法。
@babel/register在底层改写了node的require方法,在代码里引入@babel/register模块后,所有通过require引入并且以.es6, .es, .jsx 和 .js为后缀名的模块都会经过babel的转译。
babel-loader是用于webpack的一个loader,以便webpack在构建的时候用Babel对JS代码进行转译,这样我们就不用再通过命令行手动转译了。我们在配置该loader的时候需要先安装它
npm install babel-loader
在webpack配置文件中,我们把babel-loader添加到module的loaders列表中
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
在这里,我们通过options属性给babel-loader传递预设和插件等Babel配置项。我们也可以省略这个options,这个时候babel-loader会去读取默认的Babel配置文件,也就是.babelrc,.babelrc.js,babel.config.js等。在现在的前端开发中,建议通过配置文件来传递这些配置项。
参考https://www.jiangruitao.com/babel/
babel 的转译过程也分为三个阶段,这三步具体是:
。。。待更新
132 小程序
前端进阶… 1383.1 |
前端工程化…
138 Babel 的原理是什么?..
138 如何写一个 babel 插件?..
139 你的 git 工作流是怎样的?..
143 rebase 与 merge 的区别?..
148 git reset、git revert 和 git checkout 有什么区别…
149 webpack 和 gulp 区别(模块化与流的区别)…
21
1503.2 | Vue 框架…
151 有使用过 Vue 吗?说说你对 Vue 的理解…
151 说说 Vue 的优缺点…
151Vue 和 React 有什么不同?使用场景分别是什么?…
151 什么是虚拟 DOM?…
152 请描述下 vue 的生命周期是什么?…
152 vue 如何监听键盘事件?…
155 watch 怎么深度监听对象变化…
156 删除数组用 delete 和 Vue.delete 有什么区别?…
156 watch 和计算属性有什么区别?… 1
56 Vue 双向绑定原理…
157 v-model 是什么?有什么用呢?…
157 axios 是什么?怎样使用它?怎么解决跨域的问题?…
157 在 vue 项目中如何引入第三方库(比如 jQuery)?有哪些方法可以做到?
157
说说 Vue React angularjs jquery 的区别…
157 Vue3.0 里为什么要用 Proxy API 替代 defineProperty API?…
158 Vue3.0 编译做了哪些优化?…
158 Vue3.0 新特性 —— Composition API 与 React.js 中Hooks 的异同点…
159 Vue3.0 是如何变得更快的?(底层,源码)…
161 vue 要做权限管理该怎么做?如果控制到按钮级别的权限怎么做?…
162 vue 在 created 和 mounted 这两个生命周期中请求数据有什么区别呢?
162 说说你对 proxy 的理解…
30
1623.3 | React 框架…
162 angularJs 和 React 区别…
162 redux 中间件…
163 redux 有什么缺点…
163 React 组件的划分业务组件技术组件?…
163 React 生命周期函数…
163 React 性能优化是哪个周期函数?…
164 为什么虚拟 dom 会提高性能?..
164 diff 算法?..
165 React 性能优化方案…
165 简述 flux 思想…
165 React 项目用过什么脚手架?Mern? Yeoman?..
165 你了解 React 吗?…
166 React 解决了什么问题?…
166 React 的协议?…
166 了解 shouldComponentUpdate 吗?…
166 React 的工作原理?…
166 使用 React 有何优点?…
166 展示组件(Presentational component)和容器组件(Container component)之间有何不同?…
167 类组件(Class component)和函数式组件(Functional component)之间有何不同?…
167 (组件的)状态(state)和属性(props)之间有何不同?…
167 应该在 React 组件的何处发起 Ajax 请求?…
168 在 React 中,refs 的作用是什么?…
168 何为高阶组件(higher order component)?…
168 使用箭头函数(arrow functions)的优点是什么?…
168 为什么建议传递给 setState 的参数是一个 callback 而不是一个对象?
168 除了在构造函数中绑定 this,还有其它方式吗?…
169 怎么阻止组件的渲染?…
169 当渲染一个列表时,何为 key?设置 key 的目的是什么?…
169 何为 JSX ?…