进程 & 线程(process 和 thread)
启动应用,开启一个
进程,同时申请一块内存
,进程中可以开启多个线程去做很多事情
,线程都可以操作共享这部分内存, 一般浏览器可以是多进程(进程间通过 IPC (Inter Process Communication)进行通信)或者单进程的
Chrome 多进程(四进程)
Browser Process:主进程
- 负责包括地址栏,书签栏,前进后退按钮等部分的工作;
- 负责处理浏览器的一些不可见的底层操作,比如网络请求和文件访问;
Renderer Process :
- 负责一个 tab 内关于网页呈现的所有事情
Plugin Process:
- 负责控制一个网页用到的所有插件,如 flash
GPU Process
- 负责处理 GPU 相关的任务
优点
- 某一渲染进程出问题不会影响其他进程
- 更为安全,在系统层面上限定了不同进程的权限
缺点
- 由于不同进程间的内存不共享,不同进程的内存常常需要包含相同的内容。
- 为了节省内存,Chrome 限制了最多的进程数,最大进程数量由设备的内存和 CPU 能力决定,当达到这一限制时,新打开的 Tab 会共用之前同一个站点的渲染进程。(之前的就会挂掉)
浏览器事件模型
浏览器基于事件驱动,将很多东西都抽象为事件,比如用户交互,网络请求,页面加载,报错等,可以说事件是浏览器正常运行的基石。
事件传播
- 事件传播方式:
事件冒泡
和事件捕获
- 可以使用
事件委托
来优化 子元素的过多事件绑定,占用内存, 基于事件冒泡原理
实现的
// 第三个参数: true:标识是事件捕获,false:(默认) 事件冒泡模型
document.addEventListener("click", () => {}, boolean);
事件对象 和 方法
event : target 、xy 定位信息
preventDefault
用于阻止浏览器的默认行为,比如 a 标签会默认进行跳转,form 会默认校验并发送请求到 action 指定的地址等stopPropagation
用于阻止事件的继续冒泡行为,后面讲事件传播的时候会提到。
鼠标事件
鼠标点击事件
包括 4 个:click(单击)、dblclick(双击)、mousedown(按下)和 mouseup(松开)鼠标移动事件
mouseover 和 mouseout 的效率较低,建议使用 mouseente 和 mouseleavemouseover 与 mouseout mousemove mouseenter 与 mouseleave 鼠标移入、鼠标移出 鼠标在某元素上移动 鼠标移入、鼠标移出 支持冒泡(在子元素内移动,父级元素会被触发) 支持冒泡 不支持冒泡(在子元素内移动,父级元素不会被触发) 鼠标滚轮事件
:mousewheel
移动端事件
touchstart 、 touchover 、 touchend
react 的中事件
React 的合成事件: 如果绑定原生事件,原生事件优先于合成事件被触发 , 好处:兼容性、跨端,方便统一管理
BOM & DOM
BOM 对象:
window 对象:BOM 的核心对象,表示整个浏览器窗口,主要用来操作浏览器窗口。所有全局变量和函数都是它的属性,且所有原生的构造函数及其他函数也都存在于它的命名空间下
document 对象:用于处理页面文档
location 对象:用于获取或设置当前页面的地址 (URL),并把浏览器重定向到新的页面
navigator 对象:提供了与浏览器有关的信息
screen 对象:用来获取用户的屏幕信息
history 对象:包含浏览器的历史
DOM 操作
创建空白的文档片段 createDocumentFragment()
var element = document.getElementById("ul"); // assuming ul exists
var fragment = document.createDocumentFragment();
var browsers = ["Firefox", "Chrome", "Opera", "Safari", "Internet Explorer"];
// 把li 添加到文档片段中,因为fragment在内存中,所以不会引起回流(DOM结果发生变化)
browsers.forEach(function (browser) {
var li = document.createElement("li");
li.textContent = browser;
fragment.appendChild(li);
});
element.appendChild(fragment);
- createElement() 创建一个具体的元素
- createTextNode() 创建一个文本节点
添加、插入
- element.appendChild(aChild)
- parentNode.insertBefore(newNode, referenceNode);
移除
- parentNode.removeChild(child);
替换、
- parentNode.replaceChild(newChild, oldChild);
查找
- getElementsByTagName() 通过标签名称
- getElementsByName() 通过元素的 Name 属性值
- getElementById() 通过元素 id
- document.getElementsByClassName
- document.querySelector
- document.querySelectorAll
事件循环
原因
- JS 单线程:
优点
是防止多线程同时修改 DOM 带来不确定性,缺点
:线程阻塞 - WebAPI: 例如 setTimeout,我们可以通过浏览器把任务延迟执行,webAPI 需要通知主线程执行
- 消息(事件)队列: 浏览器中的所有任务、消息统称为
事件
,队列分为微任务队列、宏任务队列
这是一个事件循环的基本步骤
- 执行同步代码(可以理解为一个宏任务),如遇到异步交由 WebAPI,WebAPI 执行完,放入消息队列中
- 当所有同步代码执行完毕后,检查微任务队列,如果有任务,则依次执行队列中的所有微任务。
- 微任务执行完毕后,检查宏任务队列,如果有任务,则取出一个宏任务并执行。
- 重复以上步骤,直到所有任务执行完毕。
宏任务 & 微任务
类型 | 浏览器 | Node |
---|---|---|
宏任务 | script、setTimeout setInterval、postMessage、MessageChannel | I/O 操作、setImmediate |
微任务 | promise、queueMicrotask、MutationObserver | nextTick |
MutationObserver 提供了 监视对 DOM 树所做更改的能力。
// 选择需要观察变动的节点
const targetNode = document.getElementById('some-id');
// 观察器的配置(需要观察什么变动)
const config = { attributes: true, childList: true, subtree: true };
// 当观察到变动时执行的回调函数
const callback = function(mutationsList, observer) {
console.log(....)
};
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);
// 以上述配置开始观察目标节点
observer.observe(targetNode, config);
// 之后,可停止观察
observer.disconnect();
MessageChannel 双端通道
var channel = new MessageChannel();
var port1 = channel.port1;
var port2 = channel.port2;
port1.onmessage = function (event) {
console.log("port1收到来自port2的数据:" + event.data);
};
port2.onmessage = function (event) {
console.log("port2收到来自port1的数据:" + event.data);
};
port1.postMessage("发送给port2");
port2.postMessage("发送给port1");
// port2收到来自port1的数据: port2
// port1收到来自port2的数据: port1
Promise 源码实现
let PENDING = "PENDING";
let FULFILLED = "FULFILLED";
let REJECTED = "REJECTED";
class Promise {
constructor(exec) {
try {
exec(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
console.log("出错了:", error.message);
}
}
state = PENDING;
value = null;
reason = null;
successCallback = [];
failCallback = [];
resolve(value) {
if (this.state !== PENDING) return;
this.value = value;
this.state = FULFILLED;
while (this.successCallback.length) this.successCallback.shift()();
}
reject(reason) {
if (this.state !== PENDING) return;
this.state = REJECTED;
this.reason = reason;
while (this.failCallback.length) this.failCallback.shift()();
}
/**
* 1. 同步情况
* 2. 异步情况
* 2.1 :所有执行都必须在微任务中
* 2.2 : 返回结果是promise , 不能返回自己
*/
then(success, fail) {
let successCallback = success ? success : (value) => value;
let failCallback = fail
? fail
: (reason) => {
throw new Error(reason);
};
let promise = new Promise((resolve, reject) => {
if (this.state === FULFILLED) {
queueMicrotask(() => {
let res = successCallback(this.value);
resolveRes(res, promise, resolve, reject);
});
} else if (this.state === REJECTED) {
queueMicrotask(() => {
let res = failCallback(this.reason);
resolveRes(res, promise, resolve, reject);
});
} else {
this.successCallback.push(() => {
queueMicrotask(() => {
let res = successCallback(this.value);
resolveRes(res, promise, resolve, reject);
});
});
this.failCallback.push(() => {
queueMicrotask(() => {
let res = failCallback(this.reason);
resolveRes(res, promise, resolve, reject);
});
});
}
});
return promise;
}
catch(fail) {
// let res = fail(this.reason);
// return res instanceof Promise ? res : new Promise((s, r) => r(res));
return this.then(_, fail);
}
finally(callback) {
return this.then(
(value) => {
return Promise.resolve(callback()).then(() => value);
},
(reason) => {
return Promise.resolve(callback()).then(() => {
throw reason;
});
}
);
}
/** 考虑三个点: 链式,参数必须是数组,返回结果必须按输入顺序: arr.length 不能做判断条件 */
static all(params) {
let index = 0;
let p = new Promise((s, r) => {
if (!Array.isArray(params)) {
return new Promise((_, reject) => reject(""));
}
let resArr = [];
for (let i = 0; i < params.length; i++) {
if (p instanceof Promise) {
p.then(
(s) => {
addRes(s, i);
},
(err) => r(err)
);
} else {
resArr[i] = p;
addRes(params[i], i);
}
}
function addRes(res, i) {
resArr[i] = res;
index++;
if (index === params.length) s(resArr);
}
});
return p;
}
static resolve(val) {
return val instanceof Promise ? val : new Promise((s) => s(val));
}
}
const resolveRes = (res, promise, resolve, reject) => {
if (promise === res) {
reject(new Error("不能是相同实例"));
return;
}
if (res instanceof Promise) {
res.then(resolve, reject);
} else {
resolve(res);
}
};
let p = new Promise((resolve, reject) => {
// console.log(111);
// // resolve("success");
// reject("reject");
// console.log(222);
setTimeout(() => {
console.log(111);
resolve("success");
console.log(222);
}, 100);
});
p.then(
(s) => {
console.log("..", s);
return 23;
},
(r) => console.log("...", r)
).then(
(s) => {
console.log("11", s);
},
(r) => console.log("111", r)
);
chrome 和 Edge 处理跨域问题 (94.0 以上的问题)
edge:
edge://flags/ -> Block insecure private network requests. -> disable
chrome:
Block insecure private network requests. -> disable
CSS
at-rules (常用属性)
@property --test-num {
inherits: false;
syntax: "<integer>";
initial-value: 100;
}
常用属性
clip-path
clip-path CSS 属性使用
裁剪方式
创建元素的可显示区域。区域内的部分显示,区域外的隐藏。
用一组坐标
绘制一个多边形 : clip-path: polygon(0 0,25% 0,100% 50%,25% 100%,0 100%)
inset
CSS 属性 inset 为简写属性,对应于 top、right、bottom 和 left 属性。其与 margin 简写属性具有相同的多值语法。