基本数据类型

只有false, null,0, "", undefined, 和 NaN 在 if 语句中返回 false

Symbol

symbol 是作为对象的特殊索引出现的,可以避免与字符串索引的命名冲突。
可以通过const s = Symbol(desc)创建一个新的 Symbol。
其中desc只是辅助说明,创建的两个 Symbol 即使说明相同也并不是一个符号
Symbol 有一个以说明为索引的全局注册表:
通过Symbol.for(desc)注册并取得 Symbol,其中desc一样时,拿到的 Symbol 也一样
通过Symbol.hasFor(sym)取得sym的注册表中键值。如果有返回字符串,没有为undefined

二进制类型

https://zhuanlan.zhihu.com/p/97768916

DOM事件/捕获冒泡机制

dom事件经过两个阶段eventPhase:向下捕获阶段1,到达目标2,向上冒泡阶段3
currentTarget是事件处理程序所在元素
target是当前执行到的元素

  • preventDefault阻止DOM默认在该事件执行的动作
  • 通过stopPropagation阻止继续捕获和冒泡
  • 只有通过addEventListener加入的useCapture=true的事件,才会在捕获阶段执行,其余只会在冒泡阶段执行
  • 冒泡要注意,不需要

    焦点事件

    https://zhuanlan.zhihu.com/p/73992526

    鼠标触摸事件/DOM坐标相关

  • [ ] pc端与移动端的事件

鼠标是一种指针设备,鼠标可以移动点击来进行交互。
鼠标事件涉及到三个坐标
目前这块内容还很有争议,确定不下来。。

鼠标移动

移入移出的过程:enter,move,leave
image.png
鼠标从正方形中间从左向右划过:enter``over|out``over|out``over|out``leave
image.png
overout时间顺序在夹层里面,其中的子节点区域默认不认为是自己的区域,实际上不推荐使用
[mouseenter](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/mouseenter_event)鼠标从外面移到绑定的dom区域中,不冒泡,与mouseover有区别
[mousemove](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/mousemove_event)会冒泡,但是鼠标移动的位置只有最上层节点(一般是它自己或者它的子节点冒泡上来)触发一次
[mouseleave](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/mouseleave_event)和enter对应,不冒泡

鼠标点击

就是基本的down,move,up
一个特殊的事件,就是click,移动端不移动点击的结束时和pc端点击的结束时触发

mousemove/touchmove

  1. 在 pc 端是鼠标移动,只会执行mousemove事件,传入接口是[MouseEvent](https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent)
    在移动端是触摸后移动,只会执行touchmove事件,传入接口是[TouchEvent](https://developer.mozilla.org/zh-CN/docs/Web/API/TouchEvent)
    就是说,如果要pc端和移动端都做移动响应,你需要这两个事件都定义,且处理好接口的不同(最好做一个抽象封装)
  2. move事件总是延后一帧,在onmousemove执行后更新光标坐标,可以看到dom的光标位置与你的移动点位置有延迟(不知是不是react导致的问题)。
    mousemove移动点在挂载节点的dom区域内,事件才会响应,移出后就不会
    touchmove只要开始触屏在节点内,后面不论移到哪里(事件的坐标也可超出节点占据区域),事件都会持续触发,直到松开。
    动画.gif动画.gif
    如果向在鼠标点击时mousemove实现touchmove的效果,
    你需要在mousedown点击时刻,在window上挂mousemove使之响应移动,挂mouseup是鼠标松开时取消上面响应,清除新加的侦听器。
  3. 2022/6/12 在edge移动端模拟测试,发现目前这个鼠标事件比我预想的要复杂。
    在移动端,react的鼠标事件还是会触发(原生应该也一样)
    如果只是点一下,会按照down,up的顺序同时触发鼠标事件,就像鼠标瞬移到那个位置然后点了一下。不知道是模拟器的问题还是真实移动端也有这个情况,没法正式测试。
    不过在pc,移动端的touch事件确实是完全不触发。
    应该和cocos一样的设定,全用touch事件才是正确的最佳实践
  4. 此外目前还有一种pointer事件,不知道它的浏览器支持具体如何,好像不是非常好。这个api在移动端和客户端都有比较好的效果

    复制粘贴事件

    https://juejin.cn/post/6868087257154289671
    这里说明一下selection

    防抖和节流

    防抖:触发后一定间隔执行,如果在间隔内再触发会重置cd
    节流:事件执行设置cd

    对象与原型链

    对象属性有两种:值属性和访问器属性。大多数情况使用值属性。
    注意:访问对象属性的算法复杂度为JS/TS - 图5,但是数组只有JS/TS - 图6

    构造函数

    每个函数都可以作为对象的构造函数。使用new操作符将其当作构造函数使用。没有return。
    构造函数中的this是创建新对象的this
    构造函数有一个prototype属性,创建时,新对象的[[Prototype]]指向这个属性。
    构造函数的prototype属性的constructor就是指这个构造函数(两者循环引用)

    原型链

    对象的原型可以具有原型。这样构成了一套原型链。
    对象原型都会一步步到达Object.prototype,最后到达null
    在寻找对象的属性时,如果找不到就会继续从原型中搜寻。
    原型链的问题:

  5. 如果在原型链中有引用类型的属性,那么一个对象在自身无该属性且修改原型属性时,其它对象也会有这次修改的变化(实例属性要做到统一赋值)

    在ES5中实现class及继承

    有多种方式。比如RMMV使用的是原型链继承方式。缺点只是无法使用父类构造函数(而是直接在构造函数中apply调用了initialize,initialize再调用父类同名方法)

    作用域与闭包

    作用域分为两种,函数作用域和块级作用域。
    function函数的this是执行者对象。如果这样的函数被传来传去变成不同this的属性,哪一个执行this就是谁(当然,除非你用了bind等方式)
    箭头函数的this是定义时当前函数作用域的this,此外箭头函数没有arguments(但依然可以通过...args收集参数)

    iterator/generator

    生成器在一些情况下用来做异步操作。
    不过注意了,生成器只是一个可以中断和多次执行的函数,是同步的。
    实现异步功能还是要通过生成器的调用时间不同实现,
    而这个不同肯定是用回调为基础的方式实现的,包括但不限于promise

    迭代器

    一个对象是可迭代的,那它必须实现了**iterable**接口或者**iterator**接口
    这两个接口是不一样的,注意迭代器部分两个接口的区别:

  6. iterable接口:指定对象的默认迭代器(实际上指定的是工厂函数)。
    这个对象本身进行迭代时,实际迭代的是对象的[Symbol.iterator]工厂函数的返回对象。
    工厂函数的返回对象必须是可迭代的(实现了**iterable**接口或者**iterator**接口)
    生成器函数非常适合做这个工厂函数

  7. iterator接口:实现该接口的对象本身就是迭代器
    具体需要实现nextreturn两个函数。其中return是可选的

iterator接口需要实现的函数:
next: () => { value: any, done: boolean }

  • 必须要实现,迭代时每取一个值都会调用一次next
  • value是迭代输出的值,done是迭代是否结束
  • 自己写迭代器,注意不同次调用next生成不同值的相关状态需要自己维护

return: () => { value: any, done: boolean }

  • 在迭代提前退出时执行,如breakcontinuethrowreturn,解构未使用所有值
  • return 和迭代器可关闭的关系?
  • 返回值在什么情况下有用?

    生成器

    包括一个生成器函数。生成器函数有特殊的写法:

    1. function *generatorFn(param) {
    2. yield 'get this when first execute next() on genObject'
    3. // yield 后生成器函数的程序会自动暂停。在下一次 next() 执行后,执行到下一个 yield 的地方
    4. yield 'twice'
    5. // yield 可以很灵活使用,次数不固定
    6. for (let i = 0; i < 5; i++) {
    7. yield 'you can yield not fixed times'
    8. }
    9. // yield * iterable 可以在这里,一次次 yield 出迭代器的迭代值
    10. yield* [1,2,3]
    11. // 注意生成器对象也是 iterable,所以这里可以 yield* 生成器函数,比如自己,来做递归
    12. // 递归时注意别无限递归了
    13. if (param > 0) yield* generatorFn(param - 1)
    14. // 外部通过 gn.throw(error) 给生成器扔错误时,生成器可以 catch 处理。
    15. // catch 处理后,这次的 yield 值会被跳过,并返回下个yield值(无论 yield 在 catch 里面还是外面)
    16. // 如果不处理,生成器会直接关闭。
    17. try {
    18. yield 1
    19. } catch (e) {
    20. processError(e)
    21. yield 'error info'
    22. }
    23. }

    生成器函数执行后,返回一个生成器对象。生成器对象实现了iterator接口,是可迭代对象。
    每个生成器对象都有自己的作用域。不同生成器对象之间作用域不受影响。
    生成器对象可以通过throw函数向生成器的下一个yield扔错误。
    这个功能不是**iterator**接口设定的,是生成器自己的特性
    如果生成器内部,出错的那个yield未被trycatch等处理,那么这个生成器会被关闭

    Promise

    const promise = new Promise((resolve, reject) => any)
    使用一个做异步操作函数创建promise,函数传入resolve, reject两个函数作为参数,这两个函数不是自己定义的,唯一的用途就是在异步函数结束后执行并返回结果,使得promise进入解决或者出错状态。

  • 不要以promise为理由拒绝或接受,而是用then等方法再新建promise

  • 仅创建一个promise是没有完成后回调函数这一说法的,需要后面接then或者await处理

    原型方法

    .then(onFulfilled, onRejected):在promise接受或拒绝时返回执行onFulfilledonRejected函数的返回值,或者如果他们不是函数则直接返回。

  • 返回值可以是创建了新的promise,这样可以链式的去调用

  • 如果promise被拒绝,且onReject
  • 进行异步处理错误的最佳实践是,只规定onFulfilled,并在最后加一个catch,前面出错的时候会直接将错误同步地传给catch

.catch(onRejected)等同于then不传入第一个参数,.finally(onResolved)接受拒绝统一执行

生成实例方法

Promise.resolve(value)``Promise.reject(value)以value为值直接返回一个已解决已拒绝的promise
Promise.all(Promise[])创建一个promise,这个promise会在传入promise[]全部解决时,以每一个promise解决值的数组进入接受态;或者其中有一个拒绝,以第一个拒绝的promise值的理由进入拒绝态。

注意Promise.all(Promise[])只传入一个参数,这个参数是一个数组!

  1. const yibu = (times=0) => {
  2. return (resolve, reject) => {
  3. console.log(`第${times}次设置promise`)
  4. setTimeout(() => {
  5. const r = Math.random()
  6. if (r < 0.8) {
  7. console.log(`第${times}次运行成功`)
  8. resolve(times+1)
  9. } else {
  10. console.log(`第${times}次出错`)
  11. reject(times)
  12. }
  13. }, 3000)
  14. }
  15. }
  16. const returnPromise = (times=0) => {
  17. return new Promise(yibu(times))
  18. }
  19. returnPromise()
  20. .then(returnPromise)
  21. .then(returnPromise)
  22. .then(returnPromise)
  23. .then(returnPromise)
  24. .catch((times) => {console.log(`抓到第${times}次出错`)})

async/await

async函数返回值会自动被promise包装。
await会在后面执行的异步函数(返回promise)解决时返回解决值,并在此之后执行后面的代码。

  • 只能在async函数中使用
  • 如果await的promise被拒绝,会直接将拒绝理由作为async函数的输出结果

    可中断的promise

    可以通过写一个简单的函数,把中断方法暴露出来。 ```typescript export const enAbort = (p1: Promise) => { let abort const p2 = new Promise((resolve, reject) => (abort = reject)) const p = Promise.race([p1, p2]) return [p, abort] as unknown as [Promise, (reason?: any) => void] }
  1. 这样调用传出的abort函数,就可以以中断理由这个promise了。<br />一般情况下,中断的应用场景,比如是,点击一个选项加载一些东西,这个东西没加载完就跨了另一个选项,这时必须把promise中断掉,防止先拿上次加载完的结果放上然后再放这一次的结果。
  2. <a name="knCic"></a>
  3. ## 宏任务与微任务
  4. 详解:[https://juejin.cn/post/7035985703573913630](https://juejin.cn/post/7035985703573913630)<br />微任务优先于宏任务执行,同步代码优先异步代码执行。<br />宏任务包括:
  5. - `setTimeout`类以及手动触发监听器事件
  6. 微任务包括:
  7. - promise完成后的回调函数
  8. - [MutationObserver](https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver)
  9. 这一部分考一堆异步代码和0计时器一块执行然后判断哪个先输出
  10. <a name="AgAnQ"></a>
  11. ## "真正的"异步函数
  12. 真正花时间且在不同线程的执行过程,一般是给定的api。<br />也许工作者线程可以做这个?还没看到这部分
  13. <a name="Engf7"></a>
  14. # v8
  15. ```javascript
  16. let a = 0, b = 0
  17. console.time('testb')
  18. for (let i=0; i<1000000; i++) {
  19. b = 2 * i
  20. a = b + 1
  21. }
  22. console.timeEnd('testb')
  23. a = 0, b = 0
  24. console.time('testa')
  25. for (let i=0; i<1000000; i++) {
  26. b = 2 * i
  27. a = a + 1
  28. }
  29. console.timeEnd('testa')
  30. a = 0, b = 0
  31. console.time('testi')
  32. for (let i=0; i<1000000; i++) {
  33. b = 2 * i
  34. a = i + 1
  35. }
  36. console.timeEnd('testi')

工作者线程(浏览器)

基本使用方式是通过Worker传入js脚本url,创造一个新的并行线程来执行代码。
线程之间可以通过postMessage传递信息
主线程通过worker.onmessage,工作者线程通过直接的onmessage函数接收信息

Typescript

https://juejin.cn/post/6999985372440559624
https://juejin.cn/post/6988763249982308382
刷题网站:https://github.com/type-challenges/type-challenges
ts的主要作用是类型注释和自动类型推断。但是有时候类型推断会非常复杂(比如有些类型的泛型都嵌套好几次)
最佳实践:

  1. 尽量利用而不是克制自动类型推断,对于复杂的类型机制,最好能够利用好自动类型推断;尽量不要让其变成anyscript
  2. 如果类型推断满足不了需求,或者基本不需要严格的类型推断,或者对ts类型各种编译出错反感啥的,可以fallback到js strict+d.ts

    标识符速查表

? obj.maybe?.prop
?? maybeUndefined ?? {}
! 假定类型不会为undefined``null let notNull = maybeNull!
as
as const 假定类型readonly且永远不变,用于真常量对象和数组 const obj = { a: 1 } as const
[implements](https://www.typescriptlang.org/docs/handbook/2/classes.html#implements-clauses) 仅检测检测class的实例属性满足interface/type中设定的属性
is 用于类型推断函数 (cell: Cell) => cell is StackCell

函数类型

很多时候,ts中使用Function作为函数的类型声明是不合适的。
函数类型的可赋值性:

  1. 返回值类型必须符合要求的类型
  2. 每一个声明的参数都要符合要求的类型,这意味着不能比要求的参数数量多
    但是可以少。
    感性的来说,参数多的函数是一般的函数,特殊赋值给一般
  3. 使用(...args: any) => any代表任意类型的函数。

    特殊技巧

  4. 数组后面加入as const代表数组长度不可变

    隐藏小技巧

    按照js红宝书出现的前后顺序排序

  5. 模板字面量标签函数
    前面使用标签函数,返回值就是模板字面量经处理后的返回值

    1. const simpleTag = (strings, ...insertions) => {
    2. const strings = ['','+','=','']
    3. }
    4. let result = simpleTag`${a}+${b}=${c}`
  • 使用String.raw\u00A9``可以防止unicode转义
  1. wait to add

    JS性能优化整理

    关于深拷贝的一些问题

    https://juejin.cn/post/6844903692756336653#heading-2
    结论:

  2. 对于深拷贝的特殊需求:防爆栈、允许循环引用、保持内部引用

  3. 最快的方式:JSON.parse(JSON.stringify(obj))