
重绘:样式计算-绘制
重排:生成DOM树-样式计算-生成布局树-构建图层-绘制
利用CSS3的 transform,opacity,filter 等属性就可以实现合成效果,也就是GPU加速
首先我们需要知道:js 是单线程的语言,EventLoop 是 js 的执行机制
异步队列有两种:macro(宏任务)队列和 micro(微任务)队列。宏任务队列可以有多个,微任务队列只有一个
宏任务 (包含整体代码 script,setTimeout,setInterval,setImmediate,MessageChannel, I/O 操作、UI 渲染)
微任务 (Promise,process.nextTick、Object.observe、MutationObserver)
事件循环会不断地从任务队列中取出任务并执行
当调用栈为空时,事件循环会先检查微任务队列,如果有微任务,会依次执行微任务直到微任务队列为空
然后,事件循环会从宏任务队列中取出一个宏任务并执行
执行完一个宏任务后,事件循环会再次检查并执行所有的微任务
重复上述过程,直到任务队列为空
当某个宏任务执行完后,会查看是否有微任务队列,如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前面的任务;执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务

外部输入数据 -> 轮循阶段(poll) -> 检查阶段(check) -> 关闭阶段(close callbacks) -> 定时器检查阶段(timer) -> I/O 阶段(I/O callbacks) -> 闲置阶段(idle, prepare) -> 轮询阶段(poll)
process.nextTick 独立于 EventLoop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其它 microtask 执行

前端领域中,跨域是指在一个域名下的页面,试图访问另一个不同域名下的资源,由于浏览器的同源策略,跨域访问会被阻止
同源策略:协议+域名+端口相同 (即使两个不同的域名指向了同一个 ip 地址,也非同源)
他会限制以下几种行为
简单请求:满足以下两大条件
凡是不满足以上两个条件的,就属于非简单请求,非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为“预检”请求 浏览器先询问服务器,服务器收到预检请求后,检查 Origin、Access-Control-Request-Methods 和 Access-Control-Request-Headers 字段以后,确认允许跨源请求,浏览器才会发出正式的 XMLHttpRequest 请求,否则就报错
function getInfo(data) {
console.log(data); // jsonp 跨域成功
}
let script = document.createElement("script");
script.src = "https://example.com/api?callback=getInfo";
document.body.appendChild(script);// 发送消息
var targetWindow = window.parent;
var message = "hello parent message";
targetWindow.postMessage(message, "*"); // 可以指定域名,这里*表示任意上层parent窗口
// 接收消息
window.addEventListener("message", function(event) {
var message = event.data;
console.log("message = " + message);
});server {
listen 80;
server_name www.doman1.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;
}
}// 前端代码
var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问nginx中的代理服务器
xhr.open("get", "http://www.domain1.com:81/?user=admin", true);
xhr.send();// 后端代码
var http = require("http");
var server = http.createServer();
var qs = require("querystring");
server.on("request", function(req, res) {
var params = qs.parse(req.url.substring(2));
// 向前台写cookie
res.writeHead(200, {
"Set-Cookie": "l=123456;Path=/;Domain=www.domain2.com;HttpOnly" // HttpOnly:脚本无法读取
});
res.write(JSON.stringify(params));
res.end();
});
server.listen(8080);// nodeMiddleServer
const express = require("express");
const { createproxyMiddleware } = require("http-proxy-middleware");
const app = express();
app.use(express.static(__dirname));
// 使用代理
app.use(
"/api",
createproxyMiddleware({
target: "http:localhost:8002",
pathRewrite: {
"^/api": ""
},
changeOrigin: true
})
);
app.listen(8001);// nodeServer.js
const express = require("express");
const app = express();
app.get("/request", (req, res) => {
res.end("request success");
});
app.listen(8002);事件委托本质上是利用了浏览器的事件冒泡机制,因为事件在冒泡过程中会上传到父节点,父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义到父节点上,由父节点的监听函数统一处理多个子元素事件,这种方式称为事件委托
使用事件委托可以不必为每一个子元素都绑定监听事件,减少了内存上的消耗,并且使用事件代理还可以实现事件的动态绑定,比如新增一个子节点,无需单独为它增加一个监听事件,它绑定的事件会交给父元素的监听函数处理
在js中,事件的传播分为3个阶段:捕获阶段(capturing phase)、目标阶段(target phase)、冒泡阶段(bubbling phase)
默认情况下,事件的传播是先捕获再冒泡。也就是说:事件首先从文档的跟节点开始,沿着文档树向下传播,直到到达目标元素(事件的实际目标)。这个过程称为捕获节点。
然后,事件在目标元素上被触发,这个阶段称为目标阶段。最后事件从目标元素开始,沿着文档树向上传播,直到到达文档的根节点,这个过程称为冒泡阶段。
需要注意的是,并非所有的事件都会经历捕获和冒泡阶段。有些事件,如 focus、blur、load 等,只会在目标元素上触发,不会进行捕获和冒泡
在 JavaScript 中,可以通过 addEventListener 方法的第三个参数来指定事件监听器是在捕获阶段还是冒泡阶段触发。如果将第三个参数设置为 true,则事件监听器会在捕获阶段触发;如果设置为 false 或省略,则事件监听器会在冒泡阶段触发
闭包优缺点
[a, b, ...rest] = [10, 20, 30, 40, 50];for...in 遍历对象属性,顺序不确定,取决于 js 引擎实现(无法直接遍历数组),遍历的是对象的属性名(键),(使用该循环时,需要使用 hasOwnProperty 方法过滤原型链上的属性,以确保只遍历对象本身的属性) for...of 遍历可迭代对象(数组,字符串,Map,Set)元素时,按照元素在数组中的顺序进行遍历,遍历的是元素值 foreach 只能用于遍历数组,不能用于遍历对象,遍历的是元素值
async 和 defer 属性只对外部脚本起作用,如果没有 src 属性它们会被忽略
注意的是,如果脚本没有使用 async 或 defer 属性,浏览器会在遇到 <script> 标签时立即下载并执行该脚本,阻塞 HTML 的解析,可能会影响页面的加载性能
异步加载:指同时加载,即某个 js 文件加载的同时,其余文件也可以加载 同步加载:指某个 js 文件加载的同时,其余文件不能加载
defer 比 async 要先引入,他的执行在解析完全完成之后才能开始,它处在 DOMContentLoaded 事件之前。它保证脚本会按照它在 html 中出现的顺序执行,并且不会阻塞解析 async 脚本在他们完成下载后的第一时间执行,它处在 window 的 load 事件之前,这意味着可能设置了 async 的脚本不会按照它在 html 中出现的顺序执行
apply 和 call 都是为了改变某个函数运行时的上下文(context)而存在的,也就是为了改变函数体内部 this 的指向 两者作用是一致的,区别是两者传参的方式不一样,例如
bind 方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind 方法的第二个及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数
总结
var func = function(par1, par2) {};
func.call(this, par1, par2);
func.apply(this, [par1, par2]);function myNew(func) {
return function() {
let obj = {
__proto__: func.prototype
};
const ret = func.apply(obj, Array.prototype.slice.call(arguments));
return typeof ret === "object" ? ret : obj;
};
}// 将传入的对象作为原型
function create(obj) {
function F() {}
F.prototype = obj;
return new F();
} 假如有几十个请求,如何去控制并发
// 使用async/await 和 Promise.all
async function sendRequest (urls, concurrencyLimit = 6) {
const batchs = [];
for (let i = 0; i < urls.length; i++) {
const batch = urls.slice(i, i + concurrencyLimit);
batchs.push(batch);
}
for (const batch of batchs) {
const requests = batch.map(url => fecth(url));
await Promise.all(requests);
}
}// 使用并发控制库,如 p-limit
import pLimit from 'p-limit';
async function sendRequests (urls, concurrencyLimit = 6) {
const limit = pLimit(concurrencyLimit);
const requests = urls.map(url => limit(() => fetch(url)));
await Promise.all(requests);
}// 使用队列和计数器
import axios from 'axios'
export const handQueue = (
reqs // 请求总数
) => {
reqs = reqs || []
const requestQueue = (concurrency) => {
concurrency = concurrency || 6 // 最大并发数
const queue = [] // 请求池
let current = 0
// 这个函数用于从请求池中取出请求并发送。它在一个循环中运行,直到当前并发请求数current达到最大并发数concurrency或请求池queue为空
// 对于每个出队的请求,它首先增加current的值,然后调用请求函数requestPromiseFactory来发送请求
// 当请求完成(无论成功还是失败)后,它会减少current的值并再次调用dequeue,以便处理下一个请求
const dequeue = () => {
while (current < concurrency && queue.length) {
current++;
const requestPromiseFactory = queue.shift() // 出列
requestPromiseFactory()
.then(() => { // 成功的请求逻辑
})
.catch(error => { // 失败
console.log(error)
})
.finally(() => {
current--
dequeue()
});
}
}
// 函数返回一个函数,这个函数接受一个参数requestPromiseFactory,表示一个返回Promise的请求工厂函数。
// 这个返回的函数将请求工厂函数加入请求池queue,并调用dequeue来尝试发送新的请求,当然也可以自定义axios,利用Promise.all统一处理返回后的结果
return (requestPromiseFactory) => {
queue.push(requestPromiseFactory) // 入队
dequeue()
}
}
// 测试
const enqueue = requestQueue(6)
for (let i = 0; i < reqs.length; i++) {
enqueue(() => axios.get('/api/test' + i))
}
}WeakMap 是 Map 的弱版本,它只接受对象作为键名(null 除外),不接受其他类型的值。它的行为类似于其他数据结构,但是没有 size 属性。
举个例子
如果我们使用Map的话,那么对象间是存在强引用关系的
let obj = { name : 'vcplugin'}
const target = new Map();
target.set(obj,'vcplugin Value');
obj = null;虽然我们手动将obj,进行释放,然是target依然对obj存在强引用关系,所以这部分内存依然无法被释放
再来看WeakMap:
let obj = { name : 'vcplugin'}
const target = new WeakMap();
target.set(obj,'vcplugin Value');
obj = null;如果是WeakMap的话,target和obj存在的就是弱引用关系,当下一次垃圾回收机制执行时,这块内存就会被释放掉
原型链的理解:原型链是js中实现继承的主要方式,每个对象都有一个内部指针[[Prototype]],指向它的原型对象。当访问一个对象的属性或方法时,如果这个对象本身没有这个属性或方法,就会沿着原型链向上查找,直到找到为止或者到达原型链末尾(Object.prototype)。
在es5中实现继承的方式有以下几种
// 原型链继承
function Parent() {
this.name = 'Parent';
}
function Child() {
this.age = 18;
}
Child.prototype = new Parent();
var child = new Child();
console.log(child.name);
console.log(child.age);// 构造函数继承
function Parent(name) {
this.name = name;
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
var child = new Child('vcplugin', 18);// 组合继承
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log('Hello from ' + this.name);
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child = new Child('Child', 10);// 原型式继承
function createObject(proto) {
function F() {}
F.prototype = proto;
return new F();
}
var parent = {
name: 'Parent'
};
var child = createObject(parent);
console.log(child.name); // 输出: 'Parent'// 寄生式继承
function createObject(proto) {
function F() {}
F.prototype = proto;
return new F();
}
function createEnhancedObject(proto) {
var obj = createObject(proto);
obj.sayHello = function() {
console.log('Hello from ' + this.name);
};
return obj;
}
var parent = {
name: 'Parent'
};
var child = createEnhancedObject(parent);
console.log(child.name); // 输出: 'Parent'
child.sayHello(); // 输出: 'Hello from Parent'// 发送 AJAX 请求
var xhr = new XMLHttpRequest();
xhr.open('POST', '/set-cookie', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
// 跳转到目标页面
window.location.href = 'https://www.example.com/target-page';
}
};
xhr.send(JSON.stringify({ username: getCookie('username') }));// 在服务器端 (Node.js),可以接收 AJAX 请求,并将 Cookie 值设置到响应头中
app.post('/set-cookie', function(req, res) {
var username = req.body.username;
res.cookie('username', username, { domain: '.example.com', path: '/' });
res.sendStatus(200);
});