持续更新中。。。
CSS部分
水平居中
- 行内元素: text-align: center;
- 块级元素:margin: 0 auto;
- position: absolute + left:50% + transform: translateX(-50%)
- display: flex + justify-content: center
垂直居中
- 设置line-height 等于height
- position: absolute + top:50% + transform: translateY(-50%)
- display: flex + align-items: center
- display:table+display:table-cell + vertical-align: middle;
消除display设置为inline,inline-block产生间距的方法
- 去掉HTML中的空格,代码连成一行,代码可读性差
- 使用margin负值
- 使用font-size:0
- 标签不闭合
<div>
<a>1
<a>2
<a>3
</div>
//或者
<div>
<a>1
<a>2
<a>3</a>
</div>
盒模型
盒模型的组成,由里向外content,padding,border,margin;
在IE盒子模型中,width表示content+padding+border这三个部分的宽度;
在标准的盒子模型中,width指content部分的宽度。
box-sizing 是用于告诉浏览器如何计算一个元素是总宽度和总高度;
//W3C盒子模型
box-sizing: content-box;
//IE盒子模型
box-sizing: border-box;
清除浮动
- 父级div定义overflow:auto 或 overflow:hidden;
- 添加新的块级元素,应用clear:both;
//li是浮动元素
<ul class="cc">
<li></li>
<li></li>
<div style="clear: both;"></div>
</ul>
- 在父级样式添加伪元素:after或者:before
.parent:after {
content: '';
display: block;
clear: both;
}
画一个三角形
<div></div>
div {
width: 0;
height: 0;
border-width: 50px;
border-style: solid;
border-color: #f00 transparent transparent transparent;
}
JS部分
数组方法
1.改变原始数组的方法
- pop() 删除最后一个值,并返回删除的值
- push() 在数组最后添加一个值
- shift() 删除数组的第一个元素,并返回第一个元素的值
- unshift() 向数组的开头添加一个或更多元素,并返回新的长度
- sort() 在原数组上进行排序
- splice() 从数组中添加删除项目,然后返回被删除的项目(数组)
- reverse() 将原来的数组倒序
2.不改变原始数组的方法
- slice(start,end) 返回选定的元素
- concat() 用于连接两个或多个数组
- join() 返回一个字符串
- forEach()
- map()
- filter()
- every() 检测数组所有元素是否都符合指定条件
- some() 检测数组任一元素是否符合指定条件
- find() 返回满足条件的第一个元素的值
- findIndex() 返回满足条件的第一个元素的位置
- Object.keys()
- Object.values()
- Object.entries()
- arr.reduce((total, item, index) => {return total + item })
数组去重
//es5
//简单去重法
var uniqueArray = function(arr) {
var newArr = [];
arr.forEach(function(v){
if(newArr.indexOf(v) === -1){
newArr.push(v);
}
})
return newArr;
}
//数组下标法
var uniqueArray1 = function(arr) {
var newArr = [];
for(var i=0; i<arr.length; i++) {
//如果当前数组的第i项在当前数组中第一次出现的位置是i,才存入数组;否则代表是重复的
if(arr.indexOf(arr[i]) == i) {
newArr.push(arr[i]);
}
}
return newArr;
}
/*
* 速度最快, 占空间最多(空间换时间)
*
* 该方法执行的速度比其他任何方法都快, 就是占用的内存大一些。
* 现思路:新建一js对象以及新数组,遍历传入数组时,判断值是否为js对象的键,
* 不是的话给对象新增该键并放入新数组。
* 注意点:判断是否为js对象键时,会自动对传入的键执行“toString()”,
* 不同的键可能会被误认为一样,例如n[val]-- n[1]、n["1"];
* 解决上述问题还是得调用“indexOf”。*/
function uniq(array){
var temp = {}, r = [], len = array.length, val, type;
for (var i = 0; i < len; i++) {
val = array[i];
type = typeof val;
if (!temp[val]) {
temp[val] = [type];
r.push(val);
} else if (temp[val].indexOf(type) < 0) {
temp[val].push(type);
r.push(val);
}
}
return r;
}
//es6
//方法1
let uniqueArray = [...new Set(arr)];
//方法2
const uniqueArray1 = (arr) => Array.from(new Set(arr));
Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象。
Set类似于数组,但是成员的值都是唯一的,没有重复的值。
找出字符串或数组中第一个独立(只出现一次)的字符
/**
example: "strstrabcd@3$~ab" => c
[1,2,'$',3,3,4,1,2] => $
**/
function getUnique(strOrArr) {
for(let item of strOrArr) {
if(strOrArr.indexOf(item) === strOrArr.lastIndexOf(item)) {
return item;
}
}
}
统计字符串中出现次数最多的
function getMaxN(str) {
let obj = {}, max = 0;
for(let v of str) {
obj[v] ? obj[v]++ : obj[v] = 1;
}
for(let i in obj) {
if(obj[i] > max) max = obj[i];
}
for(let i in obj) {
if(obj[i] === max)
console.log(`出现最多的字符: ${i}, 最多次数: ${obj[i]}`) ;
}
}
getMaxN('babcddefghijklmnopqrstuvwxyz0123456789');
将字符串中的分隔符-变成驼峰
//分隔符-变驼峰,get-camel-case -> getCamelCase
//方法1
//replace()如果第2个参数是回调函数,每匹配到一个结果就回调一次,每次回调都会传递以下参数:(1)result: 本次匹配到的结果(2)$1,...$9:正则表达式中有几个(),就会传递几个参数,$1~$9分别代表本次匹配中每个()提取的结果,最多9个(3)offset:记录本次匹配的开始位置(4)ource:接受匹配的原始字符串
function getCamelCase(str) {
return str.replace(/-([a-z])/g, (v, $1) => $1.toUpperCase());
}
//方法2
function getCamelCase(str) {
let arr = str.split('-');
return arr.map((v, i) => {
if(i === 0) return v;
return v.charAt(0).toUpperCase() + v.slice(1);
}).join('');
}
console.log(getCamelCase('get-camel-case')); //getCamelCase
将字符串中的驼峰变分隔符-
//驼峰变分隔符-,getCamelCase -> get-camel-case
//方法1
function getCase(str) {
return str.replace(/[A-Z]/g, (v) => '-' + v.toLowerCase());
}
//方法2
function getCase(str) {
let arr = str.split('');
return arr.map((v, i) => {
if(v.toUpperCase() === v) {
return '-' + v.toLowerCase();
} else {
return v;
}
}).join('');
}
console.log(getCase('getCamelCase')); //get-camel-case
随机生成指定长度的随机字符串
function randomStr(n, str) {
let newStr = '';
for (let i=0; i<n; i++) {
newStr += str.charAt(Math.round(Math.random() * str.length));
}
return newStr;
}
console.log(randomStr(10,'abcdefghijklmnopqrstuvwxyz0123456789'));
数字千分位处理
//方法1
let num = 1234567890;
num.toLocaleString(); //"1,234,567,890"
num.toLocaleString('zh', { style: 'currency', currency: 'CNY' }); //"¥1,234,567,890.00"
//方法2
function format (num) {
return (num+ '').replace(/(\d{1,3})(?=(\d{3})+(?:$|\.))/g,'$1,');
}
format(1234567890); //"1,234,567,890"
闭包
简单讲,闭包就是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。闭包内最内层函数使用的变量会在自身函数中查找,若找不到向上一级函数查找该变量,以此类推,最后查找全局变量。
for (var i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
// 输出 10次10
//解决方法1
for (let i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
//解决方法2
for (var i = 0; i < 10; i++) {
(function() {
let j = i;
setTimeout(function () {
console.log(j);
}, 1000);
})();
}
闭包的应用
1.在内存中维持一个变量;2.避免全局变量的污染;3.保护函数内的变量安全,加强了封装性。
闭包的缺陷
闭包会比其他函数占用更多的内存,过度使用闭包可能会导致内存占用过多。
解决:闭包不在使用时,要及时释放,将引用内层函数对象的变量赋值为null。
深拷贝 & 浅拷贝
- 浅拷贝:仅仅是复制了引用,彼此之间的操作会互相影响
- 深拷贝:在堆中重新分配内存,不同的地址,相同的值,互不影响
主要区别:复制的是引用还是复制的是实例
(1)Array的slice和concat方法和Object.assign()并不是真正的深拷贝,对于第一层的元素这些是深拷贝,而对于第二层元素这些是浅拷贝。
let a = [1, 2, 3, 4];
let b = a.slice();
a[0] = 5;
console.log(a); // -> [5, 2, 3, 4]
console.log(b); // -> [1, 2, 3, 4]
let a = [[1, 2], 3, 4];
let b = a.slice();
a[0][0] = 0;
console.log(a); // -> [[0, 2], 3, 4]
console.log(b); // -> [[0, 2], 3, 4]
(2)完全的深拷贝
- JSON.parse(JSON.stringify(obj))
- 递归拷贝
function deepCopy(obj) {
if(!obj || typeof obj !== 'object') throw new Error("Error argument");
let targetObj = Array.isArray(obj) ? [] : {};
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
if(obj[key] && typeof obj[key] === 'object') {
targetObj = deepCopy(obj[key]);
} else {
targetObj[key] = obj[key];
}
}
}
return targetObj;
}
微任务与宏任务
宏任务:setTimeout setInterval setImmediate requestAnimationFrame
微任务:Promise.then catch finally process.nextTick MutationObserver
执行顺序: 主线程 -> 微任务 -> 宏任务
//主线程直接执行
console.log('start');
//丢到宏事件队列中
setTimeout(() => {
console.log('2');
new Promise(resolve => {
console.log('4');
resolve();
}).then(() => {
console.log('5')
})
})
//主线程直接执行
new Promise(resolve => {
console.log('7');
resolve();
}).then(() => {
//微事件2
console.log('8')
})
//丢到宏事件队列中
setTimeout(function() {
new Promise(resolve => {
console.log('11');
resolve();
}).then(() => {
console.log('12')
})
})
console.log('end');
//上述两个宏任务setTimeout,当第1个宏任务setTimeout执行完所有代码,才会去执行第2个宏任务setTimeout,执行完所有代码,以此类推。
//结果:start 7 end 8 2 4 5 11 12
get和post区别
- GET请求的数据会附在URL之后,POST把提交的数据则放置在是HTTP包的包体中。
- GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
- GET请求在URL中传送的参数是有长度限制的,而POST么有。
- POST的安全性要比GET的安全性高,get的参数直接暴露在URL上,所以不能用来传递敏感信息。
- GET在浏览器回退时是无害的,而POST会再次提交请求。
- GET请求会被浏览器主动cache,而POST不会,除非手动设置。
Promise
简介
new Promise((resolve, reject) => 异步操作成功 ? resolve(value) : reject(err))
- Promise 是异步编程的一种解决方案,比传统的解决方案回调函数和事件更合理和更强大。
- Promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
- 状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。
优缺点
- 优点:1.可以将异步操作以同步操作的流程表达出来,有效的解决了回调地狱的问题;2.Promise对象提供统一的接口,使得控制异步操作更加容易;3.代码直观,可维护性好。
- 缺点:1.无法取消Promise,一旦新建它就会立即执行,无法中途取消;2.当处于pending状态时,无法得知目前进展到哪一个阶段。
API
- .then(resolved, rejected) 作用是为 Promise 实例添加状态改变时的回调函数;
- .resolve() 将现有对象转为 Promise 对象;
- .reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected;
- .catch() 用于指定发生错误时的回调函数;
- .finally() 用于指定不管 Promise 对象最后状态如何,都会执行的操作;
- .all([p1, p2, p3]) 只要参数实例有一个变成rejected状态,包装实例就会变成rejected状态;如果所有参数实例都变成fulfilled状态,包装实例就会变成fulfilled状态。
- .race([p1, p2, p3])只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数;
- .any([p1, p2, p3]) 只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
.any()跟.race()方法很像,只有一点不同,就是不会因为某个Promise变成rejected状态而结束。
Promsie 与事件循环
Promise在初始化时,传入的函数是同步执行的,然后注册 then 回调。注册完之后,继续往下执行同步代码,在这之前,then 中回调不会执行。同步代码块执行完毕后,才会在事件循环中检测是否有可用的 promise 回调,如果有,那么执行,如果没有,继续下一个事件循环。
手写Promise
function Promise(excutor) {
let self = this
self.status = 'pending'
self.value = null
self.reason = null
self.onFulfilledCallbacks = []
self.onRejectedCallbacks = []
function resolve(value) {
if (self.status === 'pending') {
self.value = value
self.status = 'fulfilled'
self.onFulfilledCallbacks.forEach(item => item())
}
}
function reject(reason) {
if (self.status === 'pending') {
self.reason = reason
self.status = 'rejected'
self.onRejectedCallbacks.forEach(item => item())
}
}
try {
excutor(resolve, reject)
} catch (err) {
reject(err)
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (data) {resolve(data)}
onRejected = typeof onRejected === 'function' ? onRejected : function (err) {throw err}
let self = this
if (self.status === 'fulfilled') {
return new Promise((resolve, reject) => {
try {
let x = onFulfilled(self.value)
if (x instanceof Promise) {
x.then(resolve, reject)
} else {
resolve(x)
}
} catch (err) {
reject(err)
}
})
}
if (self.status === 'rejected') {
return new Promise((resolve, reject) => {
try {
let x = onRejected(self.reason)
if (x instanceof Promise) {
x.then(resolve, reject)
} else {
resolve(x)
}
} catch (err) {
reject(err)
}
})
}
if (self.status === 'pending') {
return new Promise((resolve, reject) => {
self.onFulfilledCallbacks.push(() => {
let x = onFulfilled(self.value)
if (x instanceof Promise) {
x.then(resolve, reject)
} else {
resolve(x)
}
})
self.onRejectedCallbacks.push(() => {
let x = onRejected(self.reason)
if (x instanceof Promise) {
x.then(resolve, reject)
} else {
resolve(x)
}
})
})
}
}
Promise.prototype.catch = function (fn) {
return this.then(null, fn)
}
async / await
async 是 Generator 函数的语法糖, 也是异步编程的一种解决方案;返回的是一个 Promise 对象,所接收的值就是函数 return 的值。
在 async 函数内部可以使用 await 命令,表示等待一个异步函数的返回。await 后面跟着的是一个 Promise 对象,如果不是的话,系统会调用 Promise.resolve() 方法,将其转为一个 resolve 的 Promise 的对象。
Ajax的封装(JS,Promise,async/await)
//js
function getJSON(options) {
//创建一个ajax对象
var xhr = new XMLHttpRequest() || new ActiveXObject("Microsoft,XMLHTTP");
//处理数据
let str = '';
for (let key in options.data) {
str += `&${key}=${options.data[key]}`;
}
str = str.slice(1);
//处理类型
if(options.type === 'GET') {
xhr.open('get', `${options.url}?${str}`);
xhr.send();
} else if(options.type === 'POST') {
xhr.open('post', options.url);
xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");
xhr.send(str);
}
xhr.onreadystatechange = () => {
if(xhr.readyState === 4 && xhr.status === 200) {
options.success && options.success(JSON.parse(xhr.response));
} else if(xhr.status !== 200) {
options.error && options.error(xhr.status);
}
}
}
getJSON({
type:'GET',
url:'./a.json',
options:{a1:1, b2:2},
success: data => {
console.log(data);
getJSON({
type:'GET',
url:`${data.next}.json`,
options:{a1:1, b2:2},
success: data1 => {
console.log(data1);
getJSON({
type:'GET',
url:`${data1.next}.json`,
options:{a1:1, b2:2},
success: data2 => {
console.log(data2);
}
})
}
})
}
})
//Promise
function getJSON(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest() || new ActiveXObject("Microsoft,XMLHTTP");
xhr.open('GET', url);
xhr.send();
xhr.onreadystatechange = () => {
if(xhr.readyState === 4 && xhr.status === 200) {
resolve(JSON.parse(xhr.response));
} else if(xhr.status !== 200) {
reject(xhr.status);
}
}
});
}
getJSON('./a.json').then(data => {
console.log(data);
return getJSON(`${data.next}.json`);
}).then(data1 => {
console.log(data1);
return getJSON(`${data1.next}.json`);
}).then(data2 => console.log(data2));
//async / await
function getJSON(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest() || new ActiveXObject("Microsoft,XMLHTTP");
xhr.open('GET', url);
xhr.send();
xhr.onreadystatechange = () => {
if(xhr.readyState === 4 && xhr.status === 200) {
resolve(JSON.parse(xhr.response));
} else if(xhr.status !== 200) {
reject(xhr.status);
}
}
});
}
async function asyncGetJSON() {
let data = await getJSON('./a.json');
console.log(data);
let data1 = await getJSON(`${data.next}.json`);
console.log(data1);
let data2 = await getJSON(`${data1.next}.json`);
console.log(data2);
}
asyncGetJSON()
函数防抖与节流
- 函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。如下图,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。
//wait时间未到时触发mousemove,定时器都会被清掉;当停止mousemove,并且wait时间到,开始执行定时器timer
function debounce(fn, wait) {
let timer = null;
return () => {
if(timer !== null) clearTimeout(timer);
timer = setTimeout(fn, wait);
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('mousemove', debounce(handle, 1000));
- 函数节流(throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。如下图,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。
//定时器法
function throttle(fn, delay) {
let timer = null;
return () => {
let that = this, agrs = arguments;
if(!timer) {
timer = setTimeout(() => {
fn.apply(that, agrs);
timer = null;
}, delay);
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('mousemove', throttle(handle, 1000));
//时间戳法
function throttle(fn, delay) {
let prev = Date.now();
return () => {
let that = this, now = new Date(), args = arguments;
if(now - prev >= delay) {
fn.apply(that, args);
prev = now;
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('mousemove', throttle(handle, 1000));
多return嵌套函数
function multiReturn() {
console.log(1);
return () => {
console.log(2);
return () => {
console.log(3);
}
}
}
let rel = multiReturn(); //1
let rel1 = rel(); //2
rel1(); //3
multiReturn(); //1
multiReturn()(); //1 2
multiReturn()()(); //1 2 3
性能优化
-
将不影响页面渲染的script放到body的底部;
-
使用defer,defer规定是否对脚本执行进行延迟,直到页面加载为止;
<script src="test.js" type="text/javascript" defer></script> -
动态加载js文件,按需加载js文件;
-
尽量避免使用非必要的全局变量;
-
尽可能的减少对象成员的查找次数和嵌套深度;
-
尽可能的减少DOM的嵌套层数;
-
最小化DOM的操作次数,尽可能的使用局部变量储存DOM节点;
-
尽可能的减少回流和重绘;
-
对于使用外部字体的,可使用font-dispaly:swap;预先加载浏览器默认字体。
从输入URL到页面加载发生了什么
1、浏览器查找当前URL是否存在缓存,并比较缓存是否过期。
2、DNS域名解析。
3、根据IP建立TCP连接(三次握手)。
4、HTTP发起请求。
5、服务器处理请求,浏览器接收HTTP响应。
6、渲染页面,构建DOM树。
7、关闭TCP连接(四次挥手)。
1、浏览器查找当前URL是否存在缓存,并比较缓存是否过期。
HTTP缓存有多种规则,根据是否需要向服务器重新发起请求,分为强制缓存,对比缓存。
强制缓存判断HTTP首部字段:cache-control,Expires。
- Expires是一个绝对时间,即服务器时间。浏览器检查当前时间,与Expires做对比,如果还没到失效时间就直接使用缓存文件。但是该方法存在一个问题:服务器时间与客户端时间可能不一致。因此该字段已经很少使用。
- cache-control中的max-age保存一个相对时间。例如Cache-Control: max-age = 1000,表示浏览器收到文件后,缓存在1000s内均有效。
- 如果同时存在cache-control和Expires,浏览器总是优先使用cache-control。
对比缓存通过HTTP的last-modified,Etag字段进行判断。
- Last-Modified 是由服务器发送给客户端的HTTP请求头标签; If-Modified-Since 则是由客户端发送给服务器的HTTP请求头标签。
- last-modified是第一次请求资源时,服务器返回的字段,表示最后一次更新的时间。下一次浏览器请求资源时就发送if-modified-since字段。服务器用本地Last-modified时间与if-modified-since时间比较,如果不一致则认为缓存已过期并返回新资源给浏览器;如果时间一致则发送304状态码,让浏览器继续使用缓存。
- Etag:该字段存储的是文件的特殊标识(一般都是hash生成的),服务器存储着文件的Etag字段,可以在与每次客户端传送If-no-match的字段进行比较。如果相等,则表示未修改,响应304;反之,则表示已修改,响应200状态码,返回数据。
2、DNS域名解析
域名解析的过程实际是将域名还原为IP地址的过程。
首先在本地域名服务器中查询IP地址,如果没有找到的情况下,本地域名服务器会向根域名服务器发送一个请求,如果根域名服务器也不存在该域名时,本地域名会向com顶级域名服务器发送一个请求,依次类推下去。
3.根据IP建立TCP连接(三次握手)
在获取到IP地址后,便开始建立一次连接,由TCP协议完成,主要通过三次握手进行连接。
第一次握手: 客户端向服务器发出连接请求报文,这时报文首部中的同部位SYN=1,同时随机生成初始序列号 seq=x,此时,客户端进程进入了 SYN-SENT状态,等待服务器的确认。
第二次握手: 服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己随机初始化一个序列号 seq=y,此时,服务器进程进入了SYN-RCVD状态,询问客户端是否做好准备。
第三次握手: 客户端进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,此时,连接建立,客户端进入ESTABLISHED状态,服务器端也进入ESTABLISHED状态。
4.浏览器向服务器发送HTTP请求
HTTP请求报文是由三部分组成: 请求行, 请求报头和请求正文。
- 请求行:常用的方法有: GET, POST, PUT, DELETE, OPTIONS, HEAD。
- 请求报头:允许客户端向服务器传递请求的附加信息和客户端自身的信息。
- 请求正文:当使用POST,PUT等方法时,通常需要客户端向服务器传递数据(储存在请求正文中)。
5.服务器处理请求,浏览器接收HTTP响应
服务器在收到浏览器发送的HTTP请求之后,会将收到的HTTP报文封装成HTTP的Request对象,处理完的结果以HTTP的Response对象返回,主要包括状态码,响应头,响应报文(服务器返回给浏览器的文本信息,通常HTML, CSS, JS,图片等文件就放在这一部分。)三个部分。
6、渲染页面,构建DOM树
浏览器是一个边解析边渲染的过程。首先浏览器解析HTML文件构建DOM树,然后解析CSS文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上。
回流(Reflow):元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树。
重绘(Repaint):元素发生的改变只是影响了元素的一些外观之类的时候(例如,背景色,边框颜色,文字颜色等)。
同步任务
因为JavaScript的单线程,因此同个时间只能处理一个任务,所有任务都需要排队,前一个任务执行完,才能继续执行下一个任务,但是,如果前一个任务的执行时间很长,比如文件的读取操作或ajax操作,后一个任务就不得不等着,拿ajax来说,当用户向后台获取大量的数据时,不得不等到所有数据都获取完毕才能进行下一步操作,用户只能在那里干等着,严重影响用户体验。
异步任务
因此,JavaScript在设计的时候,就已经考虑到这个问题,主线程可以完全不用等待文件的读取完毕或ajax的加载成功,可以先运行排在后面的任务,等到文件的读取或ajax有了结果后,再回过头执行挂起的任务。
异步任务是不会进入主线程,而是会先进入任务队列(栈)。比如说文件读取操作,因为这是一个异步任务,因此该任务会被添加到任务队列中,等到IO完成后,就会在任务队列中添加一个事件,表示异步任务完成,当主线程处理完其它任务有空时,就会读取任务队列,读取里面有哪些事件,排在前面的事件会被优先进行处理,如果该任务指定了回调函数,那么主线程在处理该事件时,就会执行回调函数中的代码。
事件循环
单线程从从任务队列中读取任务是不断循环的,每次栈被清空后,都会在任务队列中读取新的任务,如果没有任务,就会等待,直到有新的任务,这就叫做任务循环,因为每个任务都是由一个事件触发的,因此也叫作事件循环。
浏览器渲染外部资源
浏览器在解析过程中遇到请求外部资源时(图片,JS等)。浏览器将重复1-6过程下载该资源。请求过程是异步的,并不会影响HTML文档进行加载,但是当文档加载过程中遇到JS文件,HTML文档会挂起渲染过程,不仅要等到文档中JS文件加载完毕还要等待解析执行完毕,才会继续HTML的渲染过程。原因是因为JS有可能修改DOM结构,这就意味着JS执行完成前,后续所有资源的下载是没有必要的,这就是JS阻塞后续资源下载的根本原因。CSS文件的加载不影响JS文件的加载,但是却影响JS文件的执行。JS代码执行前浏览器必须保证CSS文件已经下载并加载完毕。