Chrome 浏览器
js 代码在浏览器中是如何被执行的呢?
1.在浏览器输入一个网站链接,会经过dns 域名解析转换成IP 地址,ip 地址一般是服务器地址;
2.进入服务器地址之后,服务器一般会返回index.html;
3.浏览器从index.html解析到的css 文件和 js 文件都会从服务器下载下来;
浏览器内核
浏览器内核会将 html、css、js 解析,渲染成网页
- Geoko:早期,现在很少了
- Trident:edge 之前使用,现在 edge 已经转向 Blink 了
- Webkit: 苹果开发,之前谷歌浏览器也在使用
- Blink:Webkit 的一个分支,谷歌开发,目前应用于谷歌浏览器、Edge、Opera
浏览器内核也会影响兼容性
实际上浏览器内核就是浏览器的排版引擎,也叫浏览器引擎、页面渲染引擎
浏览器渲染过程
在DOM 变成 DOM 树这个过程,html 解析的时候遇到 js 标签,应该怎么办呢?
会停止解析 html,而去加载和执行 js 代码;
那 js 代码由谁来执行呢?;
js 引擎
js 引擎
为什么需要 js 引擎呢?
js是一门高级编程语言;
高级编程语言都是需要转化成机器指令来执行的;
js 代码无论是交给浏览器和node执行,最后都是需要被cpu执行的;
但是cpu只认识自己的指令集,也就是机器指令;
所以需要js 引擎将js 代码转换成cpu 指令;
那有哪些常见的 js 引擎呢?
- spiderMonkey:第一款 js 引擎
- Chakra
- JavaScriptCore:webkit 中的 js 引擎
- v8:谷歌开发的强大 js 引擎,(c++编写)
- ...
浏览器内核和 js 引擎的关系
以 webkit 为例,它由两部分组成
- webcore:负责 html 解析、布局、渲染等工作
- JavaScriptcore:解析执行 js 代码
js 代码生成的抽象语法树网站:astexplorer.net
v8 引擎原理
js 代码-->ast(抽象语法树)-->字节码-->机器指令
其中字节码的存在是为了跨平台和收集热函数;
什么叫热函数?执行次数比较多的函数;
将执行次数多的函数直接转换成机器指令放到字节码里,就不用每次都是字节码变机器指令,提升性能;
预解析
并不是所有就是代码一开始就会被执行,如果对所有的 js 代码进行解析会影响网页的运行效率
v8 引擎实现了 Lazy Parsing延迟解析的方案,它的作用是将不必要的函数进行预解析;
在也就是解析暂时需要的内容,而对函数的全量解析是函数被调用时才会进行;
事件循环
进程和线 程
进程
启动一个应用程序,就会默认启动一个进程(也可能多个)
线程
每一个进程中,至少一个线程来执行程序中的代码,这个线程被称之为主线程
js 的线程
- js 是单线程的,他的进程容器是:浏览器或 node
- 同一时刻只能做一件事
- 如果这件事非常耗时,意味着当前的线程就会被阻塞
- 所以耗时的操作,并不会放在 js 线程上(放在其它线程)
- 比如网络请求、定时器
浏览器的事件循环
有三个角色:
- js 线程
- 其它线程
- 事件队列
- js 线程执行代码
- 当发现耗时操作时
- 会将这操作(会有回调的函数)交给其它线程处理
- 当其它线程处理完,会将回调函数放到事件队列中
- js 线程会定时地来事件队列执行那些回调函数
这三个角色形成一个闭环,不停地进行这些操作,称之为事件循环
事件队列
分 2 种:
- 微任务队列(microtask queue)
- 宏任务队列(macro queue)
微任务
哪些回调放微任务呢?
- queueMicrotask
- Promise.then()中的回调函数
宏任务
哪些回调放宏任务呢?
- 定时器
- ajax
- DOM 监听
- UI Rendering
执行顺序
原则:
在执行任何宏任务之前,都需要保证微任务队列已经被清空
node 事件循环
node 有个核心的库 libuv(c 语言),专注于文件 IO,使得 js 也能进行服务器开发
与浏览器大同小异
阶段
一次完整的时间循环分很多阶段:
- 定时器
- 待定回调
- idle,prepare
- 轮询
- 检测
- 关闭的回调函数
事件队列
node 的事件队列分复杂一点
宏任务
- 定时器
- IO 事件
- close 事件
微任务
- promise 的 then 回调
- process.nexTick(微任务中优先)
- queueMicrotask
事件监听
监听方式
- script 中
- 通过元素的 on
- 通过 EventTarget 中的 addEventListener
元素的 on
<div class="box" onclick="console.log(123)"></div>
<script src="./test.js"></script>
这种现在基本不在使用~
script 中
<div class="box"></div>
<script src="./test.js"></script>
const divE1 = document.querySelector(".box");
divE1.onclick = function () {
console.log(123);
};
缺点
不能重复,不能多个函数对其响应
当有多个相同事件,后面会覆盖掉前面
addEventListener
可以多个响应函数
目前用的比较多就是这种方式
<div class="box"></div>
<script src="./test.js"></script>
const divE1 = document.querySelector(".box");
divE1.onclick = function () {
console.log(123);
};
事件流
为什么会产生事件流呢?
当我们在浏览器上对着一个元素点击时,你点击的不仅仅是这元素本身
还有可能点击了其它元素,因为元素之间是可以层叠的~
事件冒泡
事件(如点击)从最内层 往外依次传递的顺序,称事件冒泡
<div class="box">
<span></span>
</div>
<script src="./test.js"></script>
const divE1 = document.querySelector(".box");
const spanE1 = document.querySelector("span");
divE1.addEventListener("click", () => {
console.log("div被点击");
});
spanE1.addEventListener("click", () => {
console.log("span被点击");
});
document.body.addEventListener("click", () => {
console.log("body被点击");
});
// 点击span时,打印
// span被点击
// div被点击
// body被点击
事件捕获
与事件冒泡相反,事件从最外层往内依次传递,称事件捕获
为什么会产生两种不同处理流呢?
浏览器早期开发时,ie 和 Netscape 公司都发现了这个问题,但是他们采取了相反的策略:
- ie:事件冒泡
- Netscape:事件捕获
监听事件捕获
如何去监听事件捕获呢?
addEventListener 第 3 个参数,true,默认是 false,事件冒泡
<div class="box">
<span></span>
</div>
<script src="./test.js"></script>
const divE1 = document.querySelector(".box");
const spanE1 = document.querySelector("span");
divE1.addEventListener(
"click",
() => {
console.log("div被点击");
},
true
);
spanE1.addEventListener(
"click",
() => {
console.log("span被点击");
},
true
);
document.body.addEventListener(
"click",
() => {
console.log("body被点击");
},
true
);
// 点击span时,打印
// body被点击
// div被点击
// span被点击
要是同时有事件捕获和事件冒泡,事件传递顺序是:先事件捕获再事件冒泡
事件对象 event
当产生一个事件时,和事件相关的信息也伴随着产生了,浏览器将这些信息放在一个对象里, 并且传给了对应的处理函数
<div class="box"></div>
<script src="./test.js"></script>
const divE1 = document.querySelector(".box");
divE1.addEventListener("click", (event) => {
console.log(event);
}); // PointerEvent {isTrusted: true, pointerId: 1, width: 1, height: 1, pressure: 0, …}
常见属性
- 事件类型 type
- 事件源 target
- 事件发生位置 offsetX/Y
常见方法
| preventDefault | 阻止默认行为 | 如 a 元素的默认跳转 |
|---|---|---|
| stopPropagation | 阻止事件进一步传递 |
BOM-DOM
BOM
浏览器对象模型
Browser Object Model,BOM
BOM,连接js 和浏览器窗口的桥梁。
主要的对象模型
- window
- location
- history
- document
window
全局属性、方法以及控制浏览器窗口相关的属性,方法
两个身份
- 全局对象
- 浏览器窗口对象
window 对象由Window这个类创建,而Window类继承自EventTarget
所以 window 对象也有一下方法:
- addEventListener
- removeEventListener
- dispatchEventListener
查看:MDN 文档~
常见属性
查文档~
常见方法
MDN~
常见事件
| onload | 资源加载完毕 | |
|---|---|---|
| onfocus | 获取焦点 | |
| onblur | 失去焦点 | |
| onhashchange | 链接 hash 值发生改变 | 前端路由 |
| addEventListener | ||
| removeEventListener | ||
| dispatchEvent | 派发 | 参数传事件类型 |
location
浏览器连接到的对象的位置 URL
常见属性、方法
window.location 打印自己看~
history
操作浏览器的历史
MDN~
document
当前窗口操作文档的对象
DOM
Document Object Model
架构(继承关系)
前端渲染
从服务器请求到的数据生成相应的 html,然后 再交给浏览器渲染
document
常见属性
- head
- title
- body
- children[]
- location
常见方法
| 创建元素 | createElement | |
|---|---|---|
| 添加子元素 | appendChild | 注意:document 不能使用该方法添加子元素(不知道要在哪里添加)document.body.appendChild 才可以 |
| 获取元素 | getElementBy...(少用啦) | Id、TagName |
| querySelector | 获取第一个 | |
| querySelectorAll | 获取全部 |
Element
常见属性
- id
- tagName
- children
- className
- ...
常见方法
特有
| getAttribute | 获取属性 | |
|---|---|---|
| setAttribute | 设置属性 |
json 和数据存储
JSON 由来
JavaScript Object Notation(对象符号),提出主要应用 js,目前已独立于编程语言
道格拉斯设计
服务器和客户端传输的数据格式
其它格式:
- XML 越来越少使用
- Protobuf 越来越多使用
应用场景
- 网络数据传输 JSON 数据
- 配置文件
- 非关系型数据(NoSql)库存储格式
基本语法
- 双引号
- 不支持undefined
- 不能有注释
- 最后位置不能有逗号
序列化
某些情况希望将 js复杂类型转化成JSON 格式的字符串,方便处理,比如**localStorage.setItem()**需要传字符 串,直接传对象的话,会直接被解析 Object~
const obj = {
name: "zsf",
age: 18,
friend: {
name: "why",
},
};
const objString = JSON.stringify(obj);
console.log(objString); // {"name":"zsf","age":18,"friend":{"name":"why"}}
还可以将JSON 格式的字符串转回对象
const obj = {
name: "zsf",
age: 18,
friend: {
name: "why",
},
};
const objString = JSON.stringify(obj);
console.log(objString);
const info = JSON.parse(objString);
console.log(info); // {name: 'zsf', age: 18, friend: {…}}
stringify
- 参数 1:对象
- 参数 2:replacer
- 参数 3:space
参数 2
参数 2 传入数组,按需转化
const obj = {
name: 'zsf',
age: 18,
friend: {
name: 'why'
}
}
const objString = JSON.stringify(obj, ['name', 'age'])
console.log(objString)// {"name":"zsf","age":18}
参数 2 传入回调函数,加工数据再序列化
const obj = {
name: "zsf",
age: 18,
friend: {
name: "why",
},
};
const objString = JSON.stringify(obj, (key, value) => {
if (key === "age") {
return value + 1;
}
return value;
});
console.log(objString); // age+1了
参数 3
缩进空格,默认是 2,提高可读性,也可以用其他字符当空格
特殊情况
如果 obj 对象中有 toJSON 方法,直接使用对象的
解析
parse
- 参数 1:JSON 字符串
- 参数 2:revier
参数 2
也可以拦截数据,加工再解析,类似
stringfy 的参数 2
进行深拷贝
浅拷贝例子
例子,展开运算符
const obj = {
name: "zsf",
age: 18,
friend: {
name: "why",
},
};
const obj1 = { ...obj };
obj1.friend.name = "kobe";
console.log(obj.friend.name); // kobe
只是对 obj 对象里简单类型的属性做了深拷贝,而引用类型的属性做的是浅拷贝,拷贝的是地址,指向依然是同一个(obj.friend = obj1.friend),所以obj.friend.name才会被影响
利用 JSON 的序列化和解析可以进行对象的深拷贝
const obj = {
name: "zsf",
age: 18,
friend: {
name: "why",
},
};
const jsonString = JSON.stringify(obj);
const info = JSON.parse(jsonString);
obj.friend.name = "kobe";
console.log(info.friend.name); // 还是why
但是这种方式有个
缺点
stringify不会对函数做处理。。。
对象里要是有函数会默认移除~
浏览器存储方案
WebStorage
浏览器提供一种比 cookie 更直观的 key-value 存储方式
有两个:
- locaStorage
- sessionStorage
locaStorage
永久性的存储方法,在关闭掉网页重新打开时,存储的内容依然保留
sessionStorage
本次会话的存储,关闭掉会话时,存储的内容会被清除
常见属性、方法
| length | 只读属性,通常搭配 key()做遍历 |
|---|---|
| setItem(key, value) | |
| getItem(key) | |
| key() | 搭配 length 属性遍历 |
| removeItem(key) | |
| clear() |
遍历
const obj = {
name: "zsf",
age: 18,
friend: {
name: "why",
},
};
const jsonString = JSON.stringify(obj);
localStorage.setItem(obj, jsonString);
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
console.log(localStorage.getItem(key));
}