事件触发有三个阶段
window
往事件触发处传播,遇到注册的捕获事件会触发window
传播,遇到注册的冒泡事件会触发事件触发一般来说会按照上面的顺序进行,但是也有特例,如果给一个目标节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行。
// 以下会先打印冒泡然后是捕获 node.addEventListener( 'click', event => { console.log('冒泡') }, false ) node.addEventListener( 'click', event => { console.log('捕获 ') }, true )
通常我们使用 addEventListener
注册事件,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值 useCapture
参数来说,该参数默认值为 false
。useCapture
决定了注册的事件是捕获事件还是冒泡事件。对于对象参数来说,可以使用以下几个属性
一般来说,我们只希望事件只触发在目标上,这时候可以使用 stopPropagation
来阻止事件的进一步传播。通常我们认为 stopPropagation
是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。stopImmediatePropagation
同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件。
node.addEventListener( 'click', event => { event.stopImmediatePropagation() console.log('冒泡') }, false ) // 点击 node 只会执行上面的函数,该函数不会执行 node.addEventListener( 'click', event => { console.log('捕获 ') }, true )
如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该注册在父节点上
<ul id="ul"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ul> <script> let ul = document.querySelector('##ul') ul.addEventListener('click', event => { console.log(event.target) }) </script>
事件代理的方式相对于直接给目标注册事件来说,有以下优点
V8 实现了准确式 GC,GC 算法采用了分代式垃圾回收机制。因此,V8 将内存(堆)分为新生代和老生代两部分。
新生代中的对象一般存活时间较短,使用 Scavenge GC 算法。
在新生代空间中,内存空间分为两部分,分别为 From 空间和 To 空间。在这两个空间中,必定有一个空间是使用的,另一个空间是空闲的。新分配的对象会被放入 From 空间中,当 From 空间被占满时,新生代 GC 就会启动了。算法会检查 From 空间中存活的对象并复制到 To 空间中,如果有失活的对象就会销毁。当复制完成后将 From 空间和 To 空间互换,这样 GC 就结束了。
老生代中的对象一般存活时间较长且数量也多,使用了两个算法,分别是标记清除算法和标记压缩算法。
在讲算法前,先来说下什么情况下对象会出现在老生代空间中:
老生代中的空间很复杂,有如下几个空间
enum AllocationSpace { // TODO(v8:7464): Actually map this space's memory as read-only. RO_SPACE, // 不变的对象空间 NEW_SPACE, // 新生代用于 GC 复制算法的空间 OLD_SPACE, // 老生代常驻对象空间 CODE_SPACE, // 老生代代码对象空间 MAP_SPACE, // 老生代 map 对象 LO_SPACE, // 老生代大空间对象 NEW_LO_SPACE, // 新生代大空间对象 FIRST_SPACE = RO_SPACE, LAST_SPACE = NEW_LO_SPACE, FIRST_GROWABLE_PAGED_SPACE = OLD_SPACE, LAST_GROWABLE_PAGED_SPACE = MAP_SPACE };
在老生代中,以下情况会先启动标记清除算法:
在这个阶段中,会遍历堆中所有的对象,然后标记活的对象,在标记完成后,销毁所有没有被标记的对象。在标记大型对内存时,可能需要几百毫秒才能完成一次标记。这就会导致一些性能上的问题。为了解决这个问题,2011 年,V8 从 stop-the-world 标记切换到增量标志。在增量标记期间,GC 将标记工作分解为更小的模块,可以让 JS 应用逻辑在模块间隙执行一会,从而不至于让应用出现停顿情况。但在 2018 年,GC 技术又有了一个重大突破,这项技术名为并发标记。该技术可以让 GC 扫描和标记对象时,同时允许 JS 运行。
清除对象后会造成堆内存出现碎片的情况,当碎片超过一定限制后会启动压缩算法。在压缩过程中,将活的对象像一端移动,直到所有对象都移动完成然后清理掉不需要的内存。
Proxy
是 ES6 中新增的功能,可以用来自定义对象中的操作
let p = new Proxy(target, handler); // `target` 代表需要添加代理的对象 // `handler` 用来自定义对象中的操作
可以很方便的使用 Proxy 来实现一个数据绑定和监听
let onWatch = (obj, setBind, getLogger) => { let handler = { get(target, property, receiver) { getLogger(target, property) return Reflect.get(target, property, receiver); }, set(target, property, value, receiver) { setBind(value); return Reflect.set(target, property, value); } }; return new Proxy(obj, handler); }; let obj = { a: 1 } let value let p = onWatch(obj, (v) => { value = v }, (target, property) => { console.log(`Get '${property}' = ${target[property]}`); }) p.a = 2 // bind `value` to `2` p.a // -> Get 'a' = 2
一个函数如果加上 async
,那么该函数就会返回一个 Promise
async function test() { return "1"; } console.log(test()); // -> Promise {<resolved>: "1"}
可以把 async
看成将函数返回值使用 Promise.resolve()
包裹了下。
await
只能在 async
函数中使用
function sleep() { return new Promise(resolve => { setTimeout(() => { console.log('finish') resolve("sleep"); }, 2000); }); } async function test() { let value = await sleep(); console.log("object"); } test()
注
上面代码会先打印 finish
然后再打印 object
。因为 await
会等待 sleep
函数 resolve
,所以即使后面是同步代码,也不会先去执行同步代码再来执行异步代码。
async
和 await
相比直接使用 Promise
来说,优势在于处理 then
的调用链,能够更清晰准确的写出代码。缺点在于滥用 await
可能会导致性能问题,因为 await
会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性。
下面来看一个使用 await
的代码。
var a = 0 var b = async () => { a = a + await 10 console.log('2', a) // -> '2' 10 a = (await 10) + a console.log('3', a) // -> '3' 20 } b() a++ console.log('1', a) // -> '1' 1
对于以上代码你可能会有疑惑,这里说明下原理
Map
作用是生成一个新数组,遍历原数组,将每个元素拿出来做一些变换然后 append
到新的数组中。
[1, 2, 3].map((v) => v + 1) // -> [2, 3, 4]
Map
有三个参数,分别是当前索引元素,索引,原数组
['1','2','3'].map(parseInt) // parseInt('1', 0) -> 1 // parseInt('2', 1) -> NaN // parseInt('3', 2) -> NaN
FlatMap
和 map
的作用几乎是相同的,但是对于多维数组来说,会将原数组降维。可以将 FlatMap 看成是 map
+ flatten
,目前该函数在浏览器中还不支持。
[1, [2], 3].flatMap((v) => v + 1) // -> [2, 3, 4]
如果想将一个多维数组彻底的降维,可以这样实现
const flattenDeep = (arr) => Array.isArray(arr) ? arr.reduce( (a, b) => [...a, ...flattenDeep(b)] , []) : [arr] flattenDeep([1, [[2], [3, [4]], 5]])
Reduce
作用是数组中的值组合起来,最终得到一个值
function a() { console.log(1); } function b() { console.log(2); } [a, b].reduce((a, b) => a(b())) // -> 2 1