基本数据类型
只有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,向上冒泡阶段3currentTarget是事件处理程序所在元素target是当前执行到的元素
- 通过
preventDefault阻止DOM默认在该事件执行的动作 - 通过
stopPropagation阻止继续捕获和冒泡 - 只有通过
addEventListener加入的useCapture=true的事件,才会在捕获阶段执行,其余只会在冒泡阶段执行 -
焦点事件
https://zhuanlan.zhihu.com/p/73992526
鼠标触摸事件/DOM坐标相关
[ ] pc端与移动端的事件
鼠标是一种指针设备,鼠标可以移动和点击来进行交互。
鼠标事件涉及到三个坐标
目前这块内容还很有争议,确定不下来。。
鼠标移动
移入移出的过程:enter,move,leave
鼠标从正方形中间从左向右划过:enter``over|out``over|out``over|out``leave
over和out时间顺序在夹层里面,其中的子节点区域默认不认为是自己的区域,实际上不推荐使用[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
- 在 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端和移动端都做移动响应,你需要这两个事件都定义,且处理好接口的不同(最好做一个抽象封装) - move事件总是延后一帧,在
onmousemove执行后更新光标坐标,可以看到dom的光标位置与你的移动点位置有延迟(不知是不是react导致的问题)。mousemove移动点在挂载节点的dom区域内,事件才会响应,移出后就不会。touchmove只要开始触屏在节点内,后面不论移到哪里(事件的坐标也可超出节点占据区域),事件都会持续触发,直到松开。

如果向在鼠标点击时让mousemove实现touchmove的效果,
你需要在mousedown点击时刻,在window上挂mousemove使之响应移动,挂mouseup是鼠标松开时取消上面响应,清除新加的侦听器。 - 2022/6/12 在edge移动端模拟测试,发现目前这个鼠标事件比我预想的要复杂。
在移动端,react的鼠标事件还是会触发(原生应该也一样)
如果只是点一下,会按照down,up的顺序同时触发鼠标事件,就像鼠标瞬移到那个位置然后点了一下。不知道是模拟器的问题还是真实移动端也有这个情况,没法正式测试。
不过在pc,移动端的touch事件确实是完全不触发。
应该和cocos一样的设定,全用touch事件才是正确的最佳实践 此外目前还有一种pointer事件,不知道它的浏览器支持具体如何,好像不是非常好。这个api在移动端和客户端都有比较好的效果
复制粘贴事件
https://juejin.cn/post/6868087257154289671
这里说明一下selection防抖和节流
防抖:触发后一定间隔执行,如果在间隔内再触发会重置cd
节流:事件执行设置cd对象与原型链
对象属性有两种:值属性和访问器属性。大多数情况使用值属性。
注意:访问对象属性的算法复杂度为,但是数组只有
构造函数
每个函数都可以作为对象的构造函数。使用
new操作符将其当作构造函数使用。没有return。
构造函数中的this是创建新对象的this
构造函数有一个prototype属性,创建时,新对象的[[Prototype]]指向这个属性。
构造函数的prototype属性的constructor就是指这个构造函数(两者循环引用)原型链
对象的原型可以具有原型。这样构成了一套原型链。
对象原型都会一步步到达Object.prototype,最后到达null
在寻找对象的属性时,如果找不到就会继续从原型中搜寻。
原型链的问题:如果在原型链中有引用类型的属性,那么一个对象在自身无该属性且修改原型属性时,其它对象也会有这次修改的变化(实例属性要做到统一赋值)
在ES5中实现class及继承
有多种方式。比如RMMV使用的是原型链继承方式。缺点只是无法使用父类构造函数(而是直接在构造函数中apply调用了initialize,initialize再调用父类同名方法)
作用域与闭包
作用域分为两种,函数作用域和块级作用域。
function函数的this是执行者对象。如果这样的函数被传来传去变成不同this的属性,哪一个执行this就是谁(当然,除非你用了bind等方式)
箭头函数的this是定义时当前函数作用域的this,此外箭头函数没有arguments(但依然可以通过...args收集参数)iterator/generator
生成器在一些情况下用来做异步操作。
不过注意了,生成器只是一个可以中断和多次执行的函数,是同步的。
实现异步功能还是要通过生成器的调用时间不同实现,
而这个不同肯定是用回调为基础的方式实现的,包括但不限于promise迭代器
一个对象是可迭代的,那它必须实现了
**iterable**接口或者**iterator**接口
这两个接口是不一样的,注意迭代器部分两个接口的区别:iterable接口:指定对象的默认迭代器(实际上指定的是工厂函数)。
这个对象本身进行迭代时,实际迭代的是对象的[Symbol.iterator]工厂函数的返回对象。
工厂函数的返回对象必须是可迭代的(实现了**iterable**接口或者**iterator**接口)
生成器函数非常适合做这个工厂函数iterator接口:实现该接口的对象本身就是迭代器。
具体需要实现next和return两个函数。其中return是可选的
iterator接口需要实现的函数:next: () => { value: any, done: boolean }:
- 必须要实现,迭代时每取一个值都会调用一次
next value是迭代输出的值,done是迭代是否结束- 自己写迭代器,注意不同次调用
next生成不同值的相关状态需要自己维护
return: () => { value: any, done: boolean }
- 在迭代提前退出时执行,如
break,continue,throw,return,解构未使用所有值 - return 和迭代器可关闭的关系?
-
生成器
包括一个生成器函数。生成器函数有特殊的写法:
function *generatorFn(param) {yield 'get this when first execute next() on genObject'// yield 后生成器函数的程序会自动暂停。在下一次 next() 执行后,执行到下一个 yield 的地方yield 'twice'// yield 可以很灵活使用,次数不固定for (let i = 0; i < 5; i++) {yield 'you can yield not fixed times'}// yield * iterable 可以在这里,一次次 yield 出迭代器的迭代值yield* [1,2,3]// 注意生成器对象也是 iterable,所以这里可以 yield* 生成器函数,比如自己,来做递归// 递归时注意别无限递归了if (param > 0) yield* generatorFn(param - 1)// 外部通过 gn.throw(error) 给生成器扔错误时,生成器可以 catch 处理。// catch 处理后,这次的 yield 值会被跳过,并返回下个yield值(无论 yield 在 catch 里面还是外面)// 如果不处理,生成器会直接关闭。try {yield 1} catch (e) {processError(e)yield 'error info'}}
生成器函数执行后,返回一个生成器对象。生成器对象实现了
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接受或拒绝时返回执行onFulfilled或onRejected函数的返回值,或者如果他们不是函数则直接返回。返回值可以是创建了新的promise,这样可以链式的去调用
- 如果promise被拒绝,且onReject
- 进行异步处理错误的最佳实践是,只规定
onFulfilled,并在最后加一个catch,前面出错的时候会直接将错误同步地传给catch
.catch(onRejected)等同于then不传入第一个参数,.finally(onResolved)接受拒绝统一执行
生成实例方法
Promise.resolve(value)``Promise.reject(value)以value为值直接返回一个已解决或已拒绝的promisePromise.all(Promise[])创建一个promise,这个promise会在传入promise[]全部解决时,以每一个promise解决值的数组进入接受态;或者其中有一个拒绝,以第一个拒绝的promise值的理由进入拒绝态。
注意
Promise.all(Promise[])只传入一个参数,这个参数是一个数组!
const yibu = (times=0) => {return (resolve, reject) => {console.log(`第${times}次设置promise`)setTimeout(() => {const r = Math.random()if (r < 0.8) {console.log(`第${times}次运行成功`)resolve(times+1)} else {console.log(`第${times}次出错`)reject(times)}}, 3000)}}const returnPromise = (times=0) => {return new Promise(yibu(times))}returnPromise().then(returnPromise).then(returnPromise).then(returnPromise).then(returnPromise).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] }
这样调用传出的abort函数,就可以以中断理由这个promise了。<br />一般情况下,中断的应用场景,比如是,点击一个选项加载一些东西,这个东西没加载完就跨了另一个选项,这时必须把promise中断掉,防止先拿上次加载完的结果放上然后再放这一次的结果。<a name="knCic"></a>## 宏任务与微任务详解:[https://juejin.cn/post/7035985703573913630](https://juejin.cn/post/7035985703573913630)<br />微任务优先于宏任务执行,同步代码优先异步代码执行。<br />宏任务包括:- `setTimeout`类以及手动触发监听器事件微任务包括:- promise完成后的回调函数- [MutationObserver](https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver)这一部分考一堆异步代码和0计时器一块执行然后判断哪个先输出<a name="AgAnQ"></a>## "真正的"异步函数真正花时间且在不同线程的执行过程,一般是给定的api。<br />也许工作者线程可以做这个?还没看到这部分<a name="Engf7"></a># v8```javascriptlet a = 0, b = 0console.time('testb')for (let i=0; i<1000000; i++) {b = 2 * ia = b + 1}console.timeEnd('testb')a = 0, b = 0console.time('testa')for (let i=0; i<1000000; i++) {b = 2 * ia = a + 1}console.timeEnd('testa')a = 0, b = 0console.time('testi')for (let i=0; i<1000000; i++) {b = 2 * ia = i + 1}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的主要作用是类型注释和自动类型推断。但是有时候类型推断会非常复杂(比如有些类型的泛型都嵌套好几次)
最佳实践:
- 尽量利用而不是克制自动类型推断,对于复杂的类型机制,最好能够利用好自动类型推断;尽量不要让其变成anyscript
- 如果类型推断满足不了需求,或者基本不需要严格的类型推断,或者对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作为函数的类型声明是不合适的。
函数类型的可赋值性:
- 返回值类型必须符合要求的类型
- 每一个声明的参数都要符合要求的类型,这意味着不能比要求的参数数量多。
但是可以少。
感性的来说,参数多的函数是一般的函数,特殊赋值给一般 使用
(...args: any) => any代表任意类型的函数。特殊技巧
-
隐藏小技巧
按照js红宝书出现的前后顺序排序
模板字面量标签函数
前面使用标签函数,返回值就是模板字面量经处理后的返回值const simpleTag = (strings, ...insertions) => {const strings = ['','+','=','']}let result = simpleTag`${a}+${b}=${c}`
- 使用
String.raw\u00A9``可以防止unicode转义
