HTTP 0.9
只有get方法,没有头信息。只支持纯文本,浏览器智能发送html格式字符串,里面也不能插入图片。
HTTP 1.0
加入了get post请求方法,添加了头部信息。允许支持多种数据格式,因而可以发送图片,视频等等。添加缓存机制,一定时间内连续访问走缓存。还添加了状态码,多字符集支持等。
HTTP1.1
最大变化是引入持久连接,一次HTTP请求响应结束之后不会立刻停止TCP连接,当发现没有一段时间没有活动后会主动停止。添加管道机制,允许同一个TCP连接在同时发送多个HTTP请求,服务器根据请求顺序响应。新增了请求方式PUT、PATCH、OPTIONS、DELETE等。
HTTP2.0
二进制传输:采用二进制传输,相对安全。
多路复用:一个http连接可以同时处理多个多个http请求。
头部压缩:请求头信息传输的是ID而非本来的全文ASCLL码,服务器会更具自己储存的字典翻译这些ID得到完整的头部信息,提高传输效率。
请求可以添加优先级:为了方便流传输,每个流都可以设置权重依赖,系统会根据权重依赖分配硬件资源。
服务器推送:服务器可以主动向客户端推送消息。避免逐个请求。
HTTPS:是以安全为目标的 HTTP 通道,是 HTTP 的安全版。
HTTPS 的安全基础是 SSL。SSL 协议位于 TCP/IP 协议与各种应用层协议之间,
为数据通讯提供安全支持。SSL 协议可分为两层:SSL
记录协议(SSL Record Protocol),它建立在可靠的传输协议(如TCP)之上
,为高层协议提供数据封装、压缩、加密等基本功能的支持。
SSL 握手协议(SSL Handshake Protocol),它建立在 SSL 记录协议之上,
用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、
交换加密密钥等。
加密原理
彻底搞懂HTTPS的加密原理 - 知乎
HTTP缓存分为两种:强缓存和协商缓存
HTTP 缓存机制 - 知乎
这里我补充一些实际操作的问题
当我们了解缓存机制的原理后其实就是应用问题了
缓存机制的设置要通过cache-control头部信息,但是该头部信息在请求头和响应头中都可以设置。如果不设置默认是不走缓存的。
先上一下实验代码
服务器(node.js)
const express = require('express')
const cors = require('cors')
const app = express()
app.use(cors())
// app.use((req, res, next) => {
// res.setHeader('Access-Control-Allow-Credentials', "true")
// res.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:5500")
// next()
// })
app.use((request, response, next) => {
console.log("有人请求了该服务器");
console.log(`HOST:${request.get("Host")}`);
next()
})
app.get("/test", (req, res) => {
// res.setHeader("Cache-Control", "max-age=3")
res.send({ msg: "成功了恭喜1" })
})
const port = 8000
app.listen(port, () => {
console.log(`Server running at http://127.0.0.1:${port}`);
})
客户端(就是个promise封装的ajax)
Document
当我们设置max-age=n的时候意味着开启强缓存,时间为n秒,但是如果我们是设置在客户端可以看一下运行的结果
这里我设置的是3秒
可以看到尽管我疯狂的请求,但是依旧不会走缓存,反而会每次发一个options验证请求,服务器返回204
但是如果我将这个头部信息设置在服务器,如下基本就是
将客户端的注释掉
那么看结果
可以明显的看到,服务器开启了强缓存,且3s过后再次发送请求需要重新请求服务器
所以第一个坑就是,虽然cache-control在服务端和客户端都可以设置,但是只有服务器端才可以开启。
Cache-Control浏览器缓存_时光之里的博客-CSDN博客_cache control
meta 标签的解析实现不是所有浏览器的必然支持的,你给了某个标签,也只是建议浏览器应该怎么做而已,具体的浏览器对页面缓存的设置可能会让表现有异常。
链接:常见的HTTP协议请求头有哪些?__牛客网
常见的HTTP请求头有:Accept,接收的数据类型。Accept-Language,接收的语言。Accept-Encoding,客户端的编码方式。Connection,长连接还是短链接。Host,客户端的主机和端口号。Referer,请求来源网站。User-Agent,客户端的系统和浏览器的信息。Cache-Control,缓存设置(重点)。Cookie,存储用户信息。Range,获取的数据部分。
常见的响应头有:
Location: http://www.it315.org/index.jsp(控制浏览器显示哪个页面)
Server:apache tomcat(服务器的类型)
Content-Encoding: gzip(服务器发送的压缩编码方式)
Content-Length: 80(服务器发送显示的字节码长度)
Content-Language: zh-cn(服务器发送内容的语言和国家名)
Content-Type: image/jpeg; charset=UTF-8(服务器发送内容的类型和编码类型)
Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT(服务器最后一次修改的时间)
Refresh: 1;url=http://www.it315.org(控制浏览器1秒钟后转发URL所指向的页面)
Content-Disposition: attachment; filename=aaa.jpg(服务器控制浏览器发下载方式打开文件)
Transfer-Encoding: chunked(服务器分块传递数据到客户端)
Set-Cookie:SS=Q0=5Lb_nQ; path=/search(服务器发送Cookie相关的信息)
Expires: -1(服务器控制浏览器不要缓存网页,默认是缓存)
Cache-Control: no-cache(服务器控制浏览器不要缓存网页)
Pragma: no-cache(服务器控制浏览器不要缓存网页)
Connection: close/Keep-Alive(HTTP请求的版本的特点)
Date: Tue, 11 Jul 2000 18:23:51 GMT(响应网站的时间)
200 - 请求成功
301 - 资源(网页等)被永久转移到其它URL
302 (Found/找到)
与301有些类似,只是定位头信息中所给的URL应被理解为临时交换地址而不是永久的。注意:在 HTTP 1.0中,消息是临时移动(Moved Temporarily)的而不是被找到,因此HttpServletResponse中的常量是SC_MOVED_TEMPORARILY不是我们以为的SC_FOUND。
304 (Not Modified/为修正)
当客户端有一个缓存的文档,通过提供一个 If-Modified-Since 头信息可指出客户端只希望文档在指定日期之后有所修改时才会重载此文档,用这种方式可以进行有条件的请求。304 (SC_NOT_MODIFIED)是指缓冲的版本已经被更新并且客户端应刷新文档。另外,服务器将返回请求的文档及状态码 200。servlet一般情况下不会直接设置这个状态码。它们会实现getLastModified方法并根据修正日期让默认服务方法处理有条件的请求。这个方法的例程已在2.8部分(An Example Using Servlet Initialization and Page Modification Dates/一个使用servlet初始化和页面修正日期的例子)给出。
403 - 禁止访问
404 - 请求的资源(网页等)不存在
500 - 内部服务器错误
505 - http版本不支持
分类
http options请求 - 简书
1、域名发散 为了突破浏览器对于同一域名并发请求数的限制,http 静态资源采用多个子域名,通常为2~ 4个。 目的是充分利用现代浏览器的多线程并发下载能力。
2、域名收敛 域名收敛和域名发散正好相反:就是将静态资源只放在一个域名下面,而非发散情况下的多个 域名下。 主要是为了适应移动端的发展需求。
1.对www.abc.com这个网址进行DNS域名解析,得到对应的IP地址
2.根据这个IP,找到对应的服务器,发起TCP的三次握手
3.建立TCP连接后发起HTTP请求
4.服务器响应HTTP请求,浏览器得到html代码
5.浏览器解析html代码,并请求html代码中的资源(如js、css、图片等)(先得到html代 码,才能去找这些资源)
6.浏览器对页面进行渲染呈现给用户
7.服务器关闭关闭TCP连接
1. Chorome safari: webkit
2. firefox: Gecko(干烤)
3. IE:Trident
Ajax 跨域携带 Cookie | 爱思路
什么是同源策略,为什么浏览器要使用同源策略_wangliang_001的博客-CSDN博客_同源策略的作用
两个页面地址中的协议,域名,端口号一致,则表示同源
设置同源策略的主要目的是为了安全,如果没有同源限制,在浏览器中的cookie等其他数据可以任意读取,不同域下的DOM任意操作,ajax任意请求其他网站的数据,包括隐私数据。
DN的全称是Content Delivery Network,即内容分发网络。CDN是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。
好处就关键在于这个就近获取。
XSS,跨站脚本攻击,允许攻击者将恶意代码植入到提供给其它用户使用的页面中
XSS涉及到三方,即攻击者、客户端与Web应用XSS的攻击目标是为了盗取存储在客户端的cookie或者其他网站用于识别客户端身份的敏感信息。一旦获取到合法用户的信息后,攻击者甚至可以假冒合法用户与网站进行交互。
(1)存储型:将恶意脚本存储在网站后台数据库中,例如论坛,留言等。
(2)反射型:构造恶意的URL盗取用户信息
(3)DOM型:DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。
防护手段
知道了如何攻击,防护起来就不难,我们对症下药即可。既然输入的参数不合法,我们就很有必要对入参进行校验,比如 <、>、"、"、'、'
这些特殊字符我们很有必要进行转义与校验。
CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。
同样知其症下其药!防护手段如下:
CSRF 攻击的关键就在于利用了用户未过期的 Cookie,那么为了防止 Cookie 的盗取,就需要在 Cookie 中设置 HttpOnly 属性,这样通过程序(XSS 攻击
)就无法读取到 Cookie 信息,避免了攻击者伪造 Cookie 的情况出现。
该防护手段还是针对 Cookie 的盗取,由于请求中所有的用户验证信息都存放于 Cookie 中,因为我们抵御 CSRF 的关键就在于:如何在请求中放入攻击者所不能伪造的信息,并且该信息不能存放在 Cookie 中。那么我们就可以在请求返回中加入一个随机生成的 token
,当请求来到时进行 token 的校验,如果校验不通过则认为是 CSRF 攻击而拒绝该请求。
根据 HTTP 协议,在 HTTP 请求头上有一个字段叫做 referer
,它记录了该Http 请求的来源地址。在通常情况下,访问一个安全受限的页面的请求都来自同一个网站。
在 CSRF 中恶意请求是从 恶意站点 发出的,因此要防御 CSRF 攻击,需要对每一个请求验证其 referer 值即可。
攻击者成功的向服务器提交恶意的SQL查询代码,程序在接收后错误的将攻击者的输入作为查询语句的一部分执行,导致原始的查询逻辑被改变,额外的执行了攻击者精心构造的恶意代码。
DDOS:分布式拒绝服务攻击(Distributed Denial of Service),简单说就是发送大量请求是使服务器瘫痪。DDos攻击是在DOS攻击基础上的,可以通俗理解,dos是单挑,而ddos是群殴,因为现代技术的发展,dos攻击的杀伤力降低,所以出现了DDOS,攻击者借助公共网络,将大数量的计算机设备联合起来,向一个或多个目标进行攻击。
在技术角度上,DDoS攻击可以针对网络通讯协议的各层,手段大致有:TCP类的SYN Flood、ACK Flood,UDP类的Fraggle、Trinoo,DNS Query Flood,ICMP Flood,Slowloris类等等。一般会根据攻击目标的情况,针对性的把技术手法混合,以达到最低的成本最难防御的目的,并且可以进行合理的节奏控制,以及隐藏保护攻击资源。
下面介绍一下TCP协议中的SYN攻击。
如何预防DDOS
阿里巴巴的安全团队在实战中发现,DDoS 防御产品的核心是检测技术和清洗技术。检测技术就是检测网站是否正在遭受 DDoS 攻击,而清洗技术就是清洗掉异常流量。而检测技术的核心在于对业务深刻的理解,才能快速精确判断出是否真的发生了 DDoS 攻击。清洗技术对检测来讲,不同的业务场景下要求的粒度不一样。
// -----------------------------------------------------------
//正则对象 是否包含a var reg = new RegExp("表达式" 匹配模式)
// i 忽略大小写
// g 全局匹配模式
// JavaScript RegExp 对象有 3 个方法:test()、
// exec() 和 compile()。
// (1) test() 方法用来检测一个字符串是否匹配某个正则表达式,
// 如果匹配成功,返回 true ,否则返回 false;
// (2) exec() 方法用来检索字符串中与正则表达式匹配的值。
// exec() 方法返回一个数组,其中存放匹配的结果。如果未找到匹配的值,
// 则返回 null;
// (3)compile() 方法可以在脚本执行过程中编译正则表达式,
// 也可以改变已有表达式。
var reg = new RegExp("a", "i")
console.log(reg);
var str = "a"
var str2 = "Acdd"
//用来测试
console.log(reg.test(str), reg.test(str2));
// -------------------------------------------------------------
//正则表达式 和上面效果一样
var reg2 = /a/i;
//检查是否有a或b或c
var reg3 = new RegExp("a|b")
var reg4 = new RegExp("[ab]")
//检查是否有字母
var reg5 = new RegExp("[a-z]")
console.log(reg5.test("4124123c"));
//除了这几个 包含也行不能完全一样
var reg6 = new RegExp("[^ab]")
console.log("除了", reg6.test("ab"));
console.log("除了", reg6.test("abc"));
// --------------------------------------------------------------------
// 支持正则的string对象语法
// search match(提取符合的) replace split
var test = "123124a1412d131c5"
var test1 = test.split(/[A-z]/)
var test2 = test.match(/[A-z]/g)
var test3 = test.replace(/[A-z]/g, " yes ")
console.log(test1, test2, test3, "11111");
// -------------------------------------------------------------------
// 通过量词 出现3次到5次
var reg7 = /a{3,5}/
// 3次以上
var reg8 = /a{3,}/
// 至少一个
var reg8 = /a+/
// 0个或多个
reg8 = /a*/
console.log("reg7", reg7.test("aaaacccc"));
//---------------------------------------------------------------------
// 是否以a开头
var reg9 = /^a/
//结尾
var reg10 = /a$/
// 完全符合
var reg11 = /^abc$/
//------------------------------------------------------------------
// 检查手机号
var phoneReg = new RegExp("/^1[3-9][0-9]{9}$/")
// . 任意字符
var reg11 = /./
// 转移就看 \. \\表示\
var reg12 = /\./
// ---------------------------------------------------------------
reg = /\w/ //任意字母 数字 和_
reg = /\W/ //和上一个相反
reg = /\d/ //任意数字 s空格 S除了空格
reg = /\bapple\b/ //独立单词
//----------------------------------------------------------------------
// 去除开头空格
var Str3 = " 12312412 "
Str3 = Str3.replace(/^\s*/, "") //头
Str3 = Str3.replace(/\s*$/, "") //尾
Str3 = Str3.replace(/^\s*|\s*$/g, "") //组合
console.log(Str3);
首先我们要明白js是单线程的(至于js为什么是单线程,主要是因为多线程语言涉及读写锁等同步,共享和安全问题,对于web页面来讲,多线程同时修改某一属性时,我们很难判断该以哪次的修改为准,对于一个前端页面多线程修改属性,完成用户交互是完全没有必要的。),单线程最大的问题就是如果任务进行的过程中发生堵塞,那么后面的任务将全部无法执行,为了解决这一问题,引入了Event Loop的模型。
一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。setTimeout/Promise 等API便是任务源,而进入任务队列的是他们指定的具体执行任务。
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
JS分为同步任务和异步任务
宏任务包含我们常见的
整体代码
setTimeout那俩兄弟
UI交互事件等
微任务最经典的就是
promise then的回调。
闭包主要的两种产生途径:
1. 当一个嵌套的函数使用了外部函数变量就会产生闭包 使用谁有谁
function f1() {
console.log(a, b, f2);
var a = 1
var b = 2
function f2() {
console.log(a);
}
f2()
}
2. 注册为全局变量时,闭包也会被保留
function fun1() {
var a = 1
var b = 2
function fun2() {
a++
var c = 3
c++
console.log(a, c);
}
fun2()
return fun2
}
var f = fun1()
对于js的函数来讲,在执行结束后会销毁里面的局部变量,但是当产生闭包时,该变量就会保留到闭包消失。
一个经典的例题:
Document
https://blog.csdn.net/weixin_43558749/article/details/90905723
这里只需要关注第二个按钮的demo2事件就好,因为demo2函数返回一个函数并且被注册为了全局变量,那么在返回函数中,使用了demo2中的num1变量,num1变量就形成了闭包,随着每次点击按钮demo2,num1的值都会递增,因为没有被销毁,而num2的值却永远是1,因为每次执行后num2都会被销毁,再次点击形成新的num2.
闭包的作用就是两大方面,一个是获取并且修改函数内部变量,一个是保留函数内部变量。
缺点就是造成内存泄漏。因为形成闭包后函数内部变量便不在随着函数执行完毕而销毁,我们可以手动销毁闭包以释放内存。例如对于上面的代码。
getdemo2=null //销毁闭包
看我另一篇文章
随着es6的引入,继承现在有两种最流行的方式。
(1)构造函数的方式:
// 第一种继承
function Person(name, age) {
this.name = name
this.age = age
this.sayhello = function () {
console.log("Hello");
}
}
function Student(name, age, sex) {
Person.call(name, age)
this.sex = sex
this.saySudent = function () {
console.log("I'm a student");
}
}
Student.prototype = new Person()
Student.prototype.constructor = Student
let xiaoming = new Student("xiaoming", 19, "female")
xiaoming.saySudent()
xiaoming.sayhello()
(2)类继承(和java的那个继承类基本一模一样)
class CreateDog {
//实例自动执行
static test = "test" //属于类但不属于对象
constructor(name, age) {
this.name = name
this.age = age
}
wangwang() {
console.log("wangwang!!");
}
}
let dog = new CreateDog("dafu", 2)
console.log(dog);
dog.wangwang()
console.log(dog.test, CreateDog.test);
// ------------------------------------------------
// class继承
class smallDog extends CreateDog {
constructor(name, age, tag) {
super(name, age)
this.tag = tag
}
indroduce() {
console.log("small dog");
}
//重写父类方法
wangwang() {
console.log("name: ", this.name);
}
}
let sd = new smallDog("sd", 1, "11111")
console.log(sd);
sd.wangwang()
sd.indroduce()
sd.age = 21
两种继承虽然写法不同,但是原理一样。核心就是子类构造函数的原型指向父类构造函数的实例对象。
先看个简单的例子
Document
Promise这玩意就是个异步回调的作用,promise有三个状态(padding等待,resolved成功,rejected 失败,一旦状态从padding改变其他的两个状态,那么状态将无法继续改变),promise的参数为一个函数,有两个回调,resolve和reject,resolve表示成功的返回,reject表示失败的返回,通过resolve返回的结果可以在then回调函数中接收(注意这玩意就是个微任务,在宏任务结束时才会执行)。catch回调函数会获取reject的返回值。Promise其实有很多高深的用法建议仔细学习。下面是一个promise封装ajax的例子(面试常考)。
function Test2() {
const p = new Promise(function (resolve, reject) {
const xml = new XMLHttpRequest()
xml.open("GET", "https://api.uixsj.cn/hitokoto/get?type=social")
xml.send()
xml.onreadystatechange = function () {
if (xml.readyState == 4) {
if (xml.status >= 200 && xml.status < 300) {
resolve(xml.response)
} else {
reject(xml.status)
}
}
}
})
let pres = p.then((res) => {
console.log("封装", res);
return res
}).catch((err) => {
console.warn(err);
return err
})
console.log(pres); //返回结果也是promise
// 成功的话结果状态是fulfilled 但是如果在then种抛出错误的话那还是reject
}
promise有三个状态:
Pending-promise的初始状态,等到任务完成或是被拒绝;Resolved-执行完成并且成功的状态;Rejected-执行完成并且失败的状态。此三个状态不能相互逆转。
非常好理解,这玩意接收多个promise实例,必须他们全部成功(fullfilled)他才返回成功。只要有一个错误(reject)就错误。
Document
就是不管状态成功与否,都会回调。
Document
注意finally是当其他的异步回调结束后才调用的,所以结果为:
Document
async 是“异步”的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。async返回一个promise
一道经典的面试题(涉及宏任务微任务):
async function fun1() {
console.log("1");
await fun2()
console.log("3");
}
function fun2() {
console.log("2");
}
setTimeout(() => {
console.log("4");
}, 0)
fun1()
console.log("5");
let p = new Promise(function (resolve, reject) {
console.log("6");
resolve("7")
console.log("8");
})
p.then((res) => {
console.log(res);
})
console.log("9");
直接给答案
1
2
5
6
8
9
3
7
4
直接开始介绍:
第一部分声明fun1,没有输出,第二部分声明fun2,没有输出,第三部分定时器,定时器会在本轮tick结束后执行。调用fun1,fun1 打印1,awit表示等待该异步任务执行完毕打印2,3是异步任务,得先等着宏任务,继续向下执行先打印5,声明promise之后,promise内的第一个打印是宏任务直接输出6,第二个resolve状态的7会在then回调中打印,但是then回调是微任务,等宏任务先执行,最后一行宏任务打印9,到现在为止本轮tick宏任务执行结束,开始调用微任务队列中的微任务,(顺序是先fun1里awit后的log3,然后then回调里的log7),然后微任务执行结束,本来tick结束,调用异步宏任务setTimeout log4。可以看出Promise
比setTimeout()
先执行。因为Promise定义之后便会立即执行,其后的**.then()是异步里面的微任务**,而setTimeout()是异步的宏任务。异步任务中先执行微任务后执行宏任务。
看我另一篇文章事件传播。
//高阶函数 符合任何一个
//A接收一个函数
//A返回一个函数
// //柯里化
// 指通过函数调用继续返回函数多次接收参数 最后统一处理
save = (name) => {
return (e) => {
this.setState({ [name]: e.target.value })
}
}
function test(a) {
return function (b) {
return function (c) {
return a + b + c;
}
}
}
console.log(test(1)(2)(3));
结果为6 就是1+2+3 就是这么简单。
防抖和节流都是为了避免频繁触发事件。
防抖:连续触发无效,只有最后一次触发等待一定时间后运行该函数。
节流:连续触发,也是隔一定的时间间隔运行函数。
实例
//防抖
function fd(fun, delay) {
let timer
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
flag = true
fun()
}, delay)
}
}
//节流
function jl(fun, delay) {
let timer
let flag = true
return function () {
if (!flag) {
return
}
flag = false
timer = setTimeout(() => {
flag = true
fun()
}, delay)
}
}
每天一道面试题(7) - JS中的模块化_一只自由的程序媛的博客-CSDN博客
CommonJS
模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS
模块是运行时加载,ES6 模块是编译时输出接口。CommonJS
模块的require()
是同步加载模块,ES6 模块的import
命令是异步加载,有一个独立的模块依赖的解析阶段。var a = 4 >= 6 || true && 1 || false;
console.log(a);
答案为1
解析
操作符之间的优先级(高到低):算数操作符 → 比较操作符 → 布尔(逻辑)操作符 → “=”赋值符号
逻辑操作符中,逻辑与(&&)优先级高于逻辑或(||)
所以执行顺序为
① 4 >= 6,结果是false(比较操作符返回布尔值)
② true && 1,结果是1(逻辑与的规则:第一个操作数是真值,则返回第二个操作数)
原式变为 false || 1 || false(按正常顺序执行)
③ false || 1,结果是1(逻辑或的规则:第一个操作数是假值,则返回第二个操作数)
④ 1 || false,结果是1(逻辑或的规则:第一个操作数是真值,则直接返回第一个操作数)
总结 对于|| 左为真值则直接返回左,否则返回有。
对于&& 左为true则返回右,否则返回左。
真值是相对于假值的:
常见的假值(虚值)undefined null ““ false NaN 0
整数的 除二取余
小数的 乘二取整
https://jingyan.baidu.com/article/eb9f7b6dc692e9c79264e878.html
1.解析HTML文件,创建DOM树
自上而下,遇到任何样式(link、style)与脚本(script)都会阻塞(外部样式不阻塞后续外部脚本的加载)。
2.解析CSS
优先级:浏览器默认设置
特定级:id数*100+类或伪类数*10+tag名称*1
3.将CSS与DOM合并,构建渲染树(renderingtree)
DOM树与HTML一一对应,渲染树会忽略诸如head、display:none的元素
4.布局和绘制,重绘(repaint)和重排(reflow)
Document
来看结果