自从大二初次接触前端以来,一直都有记markdown笔记的习惯.又因为掘金最近有一个活动.所以开了这个专栏。我会写一些业务相关的小技巧和前端知识中的重点内容之类的整理一下发上来。难免有点地方不够细致。欢迎大家指教
这篇文章全面介绍了 JavaScript 的基础知识。它涵盖了
- 前端竟态问题 | AbortController | abort | promise的取消
- 栈 | 堆 | 队列 | 内存溢出解决方案
- 线程与进程(开销和内存空间) | 单线程好处
- 事件委托 | addEventListener
- 作用域链 | 执行上下文
- babel | polyfill | core.js
- async | await | promise| 版本差异 | node 的事件循环比较 | 重点
这些概念的辨析和区别
2.1.21 前端竟态问题 | AbortController | abort | promise的取消
javascript
1. 如果是XMLHttpRequest我们new open send之后可以xhr.abort();
2. AbortController
const ac = new AbortController();
const { signal } = ac;
//得到signal,只要第二个参数传入了signal。我们就可以搞定了
fetch(resourceUrl, { signal }).then((res)=>{})
ac.abort({
type: 'USER_ABORT_ACTION',
msg: '用户终止了操作'
});
3.实现一个可以取消的promise
--3.1 eventbus可以解决
--3.2 abortcontroller也可以做到(好处是一次取消多个请求非常方便)
/**
* 自定义的可以主动取消的 Promise
*/
function myCoolPromise ({ signal }) {
return new Promise((resolve, reject) => {
// 如果这个时候 signal 对象的状态是终止的,那么就会抛出一个异常
signal?.throwIfAborted();
// 异步的操作,这里使用 setTimeout 模拟
//resolve('ok')
// 添加 abort 事件监听,一旦 signal 状态改变就将 Promise 的状态改变为 rejected
signal?.addEventListener('abort', () => reject(signal?.reason));
});
}
/**
* 使用自定义可取消的 Promise
*/
const ac = new AbortController();
const { signal } = ac;
myCoolPromise({ signal }).then((res) => console.log(res), err => console.warn(err));
ac.abort({
type: 'USER_ABORT_ACTION',
msg: '用户终止了操作'
});
2.1.22 栈 | 堆 | 队列 | 内存溢出解决方案
ini
1.栈 :方法
它的存储分为访问地址和实际存放的地方; 访问地址是存储在栈中的, 当查询引用类型变量的时候, 会先从栈中读取内存地址(也就是访问地址), 然后再通过地址找到堆中的值.因此, 这种我们也把它叫为引用访问.
v8的堆有这两个:
新生代 就是临时分配的内存,存活时间短, 如临时变量、字符串等
from_space_:正在使用的放到to。然后对调,循环
to_space_:闲置的内存
老生代 是常驻内存,存活的时间长, 如主控制器、服务器对象等;
这就是标记清除了
4.内存溢出解决方案:node --max_old_space_size=8000 build/build.js
2.1.23.线程与进程(开销和内存空间) | 单线程好处
线程=火车 进程=车厢
火车间很难共享 同一火车的不同车厢容易共享
为避免频繁操作DOM带来的同步问题,设计成单线程。后来为了利用多核CPU计算能力,HTML5提出Web Worker标准,允许js脚本创建多个线程,但是子线程由主线程控制且不得操作DOM,所以JS单线程的本质没有改变。
2.1.24 html元素 | Attribute
javascript
document.getElementById('content_views').setAttribute('age', 25);
document.getElementById('content_views').getAttribute('class')
2.1.25 事件委托 | addEventListener
arduino
事件委托主要用来1.减少内存消耗,2.动态绑定事件。减少重复工作
我们一般说的事件委托其实是addEventListener
第一个是我们的事件:比如click,mouseover
第二个是方法
第三个是模式。true是事件在捕获阶段执行。事件在冒泡阶段执行,默认是false,就是默认在冒泡的时候执行
//默认冒泡是 标签的onclick事件->document.onclick->addEventListener
//为true的时候 addEventListener->标签的onclick事件->document.onclick
事件传播的三个阶段
javascript
1、捕获阶段:事件从window对象自上而下向目标节点传播的阶段;
2、目标阶段:真正的目标节点正在处理事件的阶段;
3、冒泡阶段:事件从目标节点自下而上向window对象传播的阶段。
示例
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div onclick="parent()">
<ul class="list" >
<a href="www.baidu.com">百度</a>
<a href="www.baidu.com">搜狐</a>
</ul>
</div>
<script>
function parent(){
console.log("dsadas")
}
document.querySelector('.list').addEventListener('click',(e)=>{
// alert((e || window).event.target.innerHTML)
// 阻止冒泡方法 不让事件向documen上蔓延。
// 只激活内部函数,顶部的onclick就不会执行力
e.stopPropagation()
// 阻止默认行为
e.preventDefault()
})
</script>
</body>
</html>
事件大全
sql
--1.鼠标事件
click 当用户点击某个对象时调用的事件句柄。
contextmenu 在用户点击鼠标右键打开上下文菜单时触发
dblclick 当用户双击某个对象时调用的事件句柄。
mousedown 鼠标按钮被按下。
mouseenter 当鼠标指针移动到元素上时触发。
mouseleave 当鼠标指针移出元素时触发
mousemove 鼠标被移动。
mouseover 鼠标移到某元素之上。
mouseout 鼠标从某元素移开。
mouseup 鼠标按键被松开。
--2.键盘事件
属性 描述 DOM
keydown 某个键盘按键被按下。
keypress 某个键盘按键被按下并松开。
keyup 某个键盘按键被松开。
--3.键盘事件
框架/对象(Frame/Object)事件
abort 图像的加载被中断。 ( )
beforeunload 该事件在即将离开页面(刷新或关闭)时触发
error 在加载文档或图像时发生错误。 ( , 和 )
hashchange 该事件在当前 URL 的锚部分发生修改时触发。
load 一张页面或一幅图像完成加载。
pageshow 该事件在用户访问页面时触发
pagehide 该事件在用户离开当前网页跳转到另外一个页面时触发
resize 窗口或框架被重新调整大小。
scroll 当文档被滚动时发生的事件。
unload 用户退出页面。 ( 和 )
--4.表单事件
表单事件
blur 元素失去焦点时触发
change 该事件在表单元素的内容改变时触发( , , , 和 )
focus 元素获取焦点时触发
focusin 元素即将获取焦点是触发
focusout 元素即将失去焦点是触发
input 元素获取用户输入是触发
reset 表单重置时触发
search 用户向搜索域输入文本时触发 (
--5.剪贴板事件
剪贴板事件
copy 该事件在用户拷贝元素内容时触发
cut 该事件在用户剪切元素内容时触发
paste 该事件在用户粘贴元素内容时触发
--6.打印事件
打印事件
afterprint 该事件在页面已经开始打印,或者打印窗口已经关闭时触发
beforeprint 该事件在页面即将开始打印时触发
--7.拖动事件
拖动事件
drag 该事件在元素正在拖动时触发
dragend 该事件在用户完成元素的拖动时触发
dragenter 该事件在拖动的元素进入放置目标时触发
dragleave 该事件在拖动元素离开放置目标时触发
dragover 该事件在拖动元素在放置目标上时触发
dragstart 该事件在用户开始拖动元素时触发
drop 该事件在拖动元素放置在目标区域时触发
--8.拖动事件
动画事件
animationend 该事件在 CSS 动画结束播放时触发
animationiteration 该事件在 CSS 动画重复播放时触发
animationstart 该事件在 CSS 动画开始播放时触发
--9.拖动事件
message 该事件通过或者从对象(WebSocket, Web Worker, Event Source 或者子 frame 或父窗口)接收到消息时触发
online 该事件在浏览器开始在线工作时触发。
offline 该事件在浏览器开始离线工作时触发。
popstate 该事件在窗口的浏览历史(history 对象)发生改变时触发。 event occurs when the window's history changes
--10.元素在上下文菜单显示时触发
storage 该事件在 Web Storage(HTML 5 Web 存储)更新时触发
toggle 该事件在用户打开或关闭 元素时触发
wheel 该事件在鼠标滚轮在元素上下滚动时触发
2.1.26.作用域链 | 执行上下文
javascript
变量提升把变量或者是function的声明提升到开头的行为。所以我们会用 块级作用域,let 和 const来防止变量提升
原理是 预编译 和 执行
--1.作用域一共有三个 全局作用域、函数作用域,块级作用域。函数的 { },才能形成作用域,比如
// 比如这个对象的 { } 就不是作用域
var xiaoming = {
name: 'xiao ming' // 对象中的属性,也不是局部变量
}
一些特殊例子
if (false) {
var a = 10
}
console.log(a) // 会输出 undefined
{
var b = 1
}
console.log(b) // 会输出 undefined
--2.块级作用域和{} 合并 示例
{
let x = 0
}
console.log(x) // Uncaught ReferenceError: x is not defined
--2.作用域链:作用域链是如果在当前作用域下找不到该变量,那就去上层作用域去寻找,直到全局作用域,如果还找不到会报错
为什么在对象内部访问自己的属性不能直接用xxx,为什么 this 指向的不是 window
const xiaoming = {
name: '小明',
getName: function () {
console.log(name)
}
}
xiaoming.getName() // undefined
因为只有函数的大括号{}才能形成作用域,
--3 说明会先在内部找,接下来回到外部找
--3.1
let a = 56
function b(){
console.log(a)
//var a =12
}
b() //输出56
--3.2
let a = 56
function b(){
console.log(a)
var a =12
}
b() //输出 undefined
作用域是变量和函数生效的区域,分为全局作用域、函数作用域和块级作用域 ,如果在非严格模式下会在全局隐式地声明该变量
2.1.27 babel | polyfill | core.js
less
Babel 能为你做的事情:
1.语法转换
2.通过 Polyfill 方式在目标环境中添加缺失的特性 (通过 @babel/polyfill 模块)
那么他是咋做到的呢?这就不得不提大名鼎鼎的AST了-parsing(解析)、transforming(转化)、printing(生成)-黑海谈判。日本和美国谈判。但是只有荷兰翻译
polyfill(补丁/垫片) 的定义, 他就是把当前浏览器不支持的方法通过用支持的方法重写来获得支持。
core.js 和polyfill类似。每年会出现新的qpi,像:es6的Promise,Set或者es7数组新提供的方法includes,这些新加入的api,就引出一个词""polyfill""(垫片/补丁),就是社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性。新功能转换为大部分现代浏览器都可以支持运行的api补丁包集合。
2.1.28 加密算法
diff
--1.非对称加密就是同一个公钥-私钥对才能正常加解密。(rsa) ,用快递员的密钥加密,然后把把快递给他,只有快递员的密钥才可以解密。
我通过RSA算法生成公钥私钥,快递员通过我的公钥加密数据,我通过自己的私钥解密
--2.对应的就是 对称加密算法(类似于zip的解压缩)
2.1.30 async | await | promise| 版本差异 | node 的事件循环比较
-
async 返回一个promise
-
.then
- 返回一个 promise,v8引擎中会用两层 queuetask 包裹
- 返回function ,正常包裹一层queuetask
- 返回的是一个数字 、 string 之类的
-
await 1 等于 promise.resolve(1),然后把后面的代码包装到promise.then 中。假如promise.then(1) 这种那么没有传入function,会执行 resolve
用同步的方法实现异步操作(指的是.then)。async返回一个promise,await后面接promise那么就是等待返回结果。async只能和await合并使用。但是浏览器调试await可以单独使用
javascript
--1.原理
async/await:原理是包裹一层生成器调用next方法+promise。
注意script中,async 和 defer都是异步加载,但是defer 是有顺序的。,而async 是乱序的,这个东西会阻塞 整个线程
--2.版本差异
v8引擎之前 执行 await 后,会把后面的代码注册到微任务队列。
v8引擎之后 执行await 后,直接跳出函数,在本轮循环的最后被执行。紧跟着await后面的语句相当于放到了new Promise中,下一行及之后的语句相当于放在Promise.then中(而且是比较后面的那一种,不会受到影响)
console.log('script start') // 1
async function async1() {
await async2() //2
console.log('async1 end')//7
}
async function async2() {
console.log('async2 end') //2
return Promise.resolve().then(()=>{
console.log('async2 end1') //5
})
}
async1()//2
setTimeout(function() {
console.log('setTimeout') //8
}, 0)
new Promise(resolve => {
console.log('Promise') //3
resolve()
})
.then(function() {
console.log('promise1') //6
})
console.log('script end') //4
// script start (这里容易,就是普通输出)
// async2 end (这里容易,就是普通await 的 值,这里promise.then是微任务跳过)
// Promise (resolve 是同步任务执行)
// script end (同步任务的最后一步)
// async2 end1 (异步任务 的 第一个微任务)
// promise1 (异步任务 的 第二个微任务)
// async1 end (??? await 後面的会在本轮循环的最后进行执行,但是再慢也比settimeout快)
// setTimeout
加深理解的一题
async function async1 () {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1')
})
console.log('async1 success'); //相当于.then 但是不会执行。因为上面的promise没有then
return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('script end')
// srcipt start
// async1 start
// promise1
// script end
两者最主要的区别在于浏览器中的微任务是在每个相应的宏任务中执行的,而nodejs中的微任务是在不同阶段之间执行的。