JS与ES6的查漏补缺(上)
本文最后更新于 2024年6月17日 凌晨
JavaScript部分
1 | 类型判断
可以用:
- 运算符:
typeof,instanceof - 方法:
Object.prototype.toString
确定一个值的类型
typeof
typeof的种类: number, string, boolean, undefined, function, object |(少见: symbol, bigint)
注意typeof null === "object"
这是由于历史原因造成的。1995年的 JavaScript 语言第一版,只设计了五种数据类型(对象、整数、浮点数、字符串和布尔值),没考虑
null,只把它当作object的一种特殊值。后来null独立出来,作为一种单独的数据类型,为了兼容以前的代码,typeof null返回object就没法改变了。
instanceof
instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
instanceof只能用于对象:
1 | |
不能用于原始的值
1 | |
任何对象, 除了null都是instancef Object:
对于null和undefined:
1 | |
Object.prototype.toString
Object.prototype.toString方法返回对象的类型字符串,因此可以用来判断一个值的类型。
- 数值:返回
[object Number]。 - 字符串:返回
[object String]。 - 布尔值:返回
[object Boolean]。 - undefined:返回
[object Undefined]。 - null:返回
[object Null]。 - 数组:返回
[object Array]。 - arguments 对象:返回
[object Arguments]。 - 函数:返回
[object Function]。 - Error 对象:返回
[object Error]。 - Date 对象:返回
[object Date]。 - RegExp 对象:返回
[object RegExp]。 - 其他对象:返回
[object Object]
1 | |
2 | 比较
字符串的比较
按照字典序依次比较字符的Unicode码点大小
原始类型且有非字符串的比较
先Number()转化成数值再比较
1 | |
NaN与谁比较都是false
和对象的比较
会先将对象转换为valueOf()对应的值再进行比较, 若valuOf返回的还是对象则调用toString方法
注意: 这个规则在对象的"加法"中仍适用
1 | |
===比较
- 对象比较地址
undefined === undefined,null === null- 原始类型只要保证类型相等 + 值相等即可, 如
+0 === -0,1 === 0x1 - NaN和自身不相等
1 | |
==比较
相同类型数据等价于===
不同类型数据会转化为Number后进行比较
1 | |
- 对于对象和原始类型的比较, 现将对象转化为原始类型的值再进行比较
- 对象和对象的比较先调用对象的
valueOf()方法, 比不成, 再调用toString()
3 | 一些特殊运算符
>>>: 逻辑右移(左边补0),>>是算数右移, 需要考虑符号位,: 逗号运算符, 对两个表达式求值并返回后一个的值
1 | |
-
void:void运算符的作用是执行一个表达式, 返回undefined。-
这个运算符的主要用途是浏览器的书签工具(Bookmarklet),以及在超级链接中插入代码防止网页跳转。
-
1 | |
4 | parseInt()和Number()
Number函数将字符串转为数值,要比parseInt函数严格很多。基本上,只要有一个字符无法转成数值,整个字符串就会被转为NaN。
1 | |
原因在于Number还是经历了valueOf和toString的过程
5 | 为false的值
undefinednull0NaN''
6 | console
console可以通过log, info, debug, warn, error, table, count, dir, assert, time&timeEnd打印信息
console.debug需要开启显示级别verbose(详细)才会显示
console.warn 黄色显示
console.error红色打印错误信息并打印错误发生的堆栈
console.table可以将复合类型数据转为表格显示
console.count(label)可以以label为标签, 打印调用次数
1 | |
console.dir以更易于阅读的方式打印信息
1 | |
console.assert(expr, message)条件判断
console.time(label)和console.timeEnd(label)记录两次调用的时间
1 | |
7 | Object静态方法
Object.keys(): 返回一个对象自身属性名的数组 (可枚举属性)
Object.getOwnPropertyNames(): 返回对象自身可枚举和不可枚举的属性名数组
1 | |
属性描述对象
JavaScript 提供了一个内部数据结构,用来描述对象的属性,控制它的行为,比如该属性是否可写、可遍历等等。这个内部数据结构称为“属性描述对象”(attributes object)。每个属性都有自己对应的属性描述对象,保存该属性的一些元信息。
一个属性描述对象的例子:
1 | |
Object.getOwnPropertyDescriptor(object, attr)可以获取属性attr的属性描述对象, (只能用于自身属性, 不能用于继承的属性)
1 | |
Object.defineProperty()和Object.defineProperties()可以用于修改属性描述对象
1 | |
8 | Object实例方法
Object实例对象的方法,主要有以下六个。
Object.prototype.valueOf():返回当前对象对应的值。Object.prototype.toString():返回当前对象对应的字符串形式。Object.prototype.toLocaleString():返回当前对象对应的本地字符串形式。Object.prototype.hasOwnProperty():判断某个属性是否为当前对象自身的属性,还是继承自原型对象的属性。 同时,in这个运算符检查属性是否存在, 不区分是对象自身的还是继承的.Object.prototype.isPrototypeOf():判断当前对象是否为另一个对象的原型。Object.prototype.propertyIsEnumerable():判断某个属性是否可枚举。
9 | Array
Array构造函数的漏洞
Array构造函数的漏洞: 不同的参数个数会导致行为不一致
1 | |
为弥补这一缺陷, 在ES6中引入了Array.of()解决了这个问题
一些方法
- 静态方法
Array.isArray可以弥补typeof运算符的不足, 识别数组 (Object.prototype.toString.call也可以)
1 | |
push()和pop()返回的是操作后数组的长度, 将改变原数组shift()将删除数组的第一个元素, 并返回该元素, 将改变原数组unshift()将在数组的首部添加元素, 并返回添加元素后数组的长度, 将改变原数组join():- 默认以逗号为分隔符
- 如果应用于undefined或null, ]将之视为空字符串
- 可以通过
call应用于字符串或类数组对象
join的例子:
1 | |
concat(): 不改变原数组, 返回值为concat后的新数组reverse(): 改变原数组slice(start, end): 不改变原数组- 左闭右开
- 支持负数, 表示倒数计算的位置
- 如果不指定end, 或
end >= length则表示一直到数组末尾
slice的例子:
1 | |
splice(start, count, addElement1, addElement2, ...):- 改变原数组
- 返回值为删除的元素组成的数组
1 | |
sort(): 默认字典序升序, 将改变原数组
1 | |
map(): 最多接受三个参数
1 | |
forEach(): 和map一样也是最多三个参数filter(): 返回新数组, 不改变原数组some()和every()reduce()和reduceRight(): 一个从左到右一个从右到左, 可以接收四个参数(至少两个)
1 | |
indexOf()和lastIndexOf(): 没有出现均返回-1
10 | Number
静态属性
Number.POSITIVE_INFINITY:正的无限,指向Infinity。Number.NEGATIVE_INFINITY:负的无限,指向-Infinity。Number.NaN:表示非数值,指向NaN。Number.MIN_VALUE:表示最小的正数(即最接近0的正数,在64位浮点数体系中为5e-324),相应的,最接近0的负数为-Number.MIN_VALUE。Number.MAX_SAFE_INTEGER:表示能够精确表示的最大整数,即9007199254740991。Number.MIN_SAFE_INTEGER:表示能够精确表示的最小整数,即-9007199254740991。
实例方法
toFixed(): 转换小数位数, 返回字符串, 有效范围为0~100, 且四舍五入不确定
1 | |
toExponential(): 转为科学计数法, 返回字符串
1 | |
toPrecision(): 转为指定位数的有效数字, 同样的, 四舍五入不确定
1 | |
11 | String
fromCharCode: 不支持码点>0xFFFF的字符, 大于0xFFFF的Unicode必须使用四字节表示法即UTF-16编码确定slice和substring和substr- slice(start, end)表示[start, end)的子字符串, 支持负数, 和数组类似
- substring别用, 设计很蠢
- substr(start, length)表示start开始长度位length的子字符串
match可以匹配子字符串, 可以用正则表达式search类似match, 返回匹配第一个位置split可以接受两个参数, 第二个参数表示返回的最大成员数'a|b|c'.split('|', 2) // ["a", "b"]
12 | Math
一些静态属性(数学常数)
1 | |
静态方法
Math.abs():绝对值Math.ceil():向上取整Math.floor():向下取整Math.max():最大值Math.min():最小值Math.pow():幂运算Math.sqrt():平方根Math.log():自然对数Math.exp():e的指数Math.round():四舍五入Math.random():随机数
**注意: **
-
Math.max和Math.min可以接收多个参数 -
由于浮点数精度问题
0.1 + 0.2 == 0.3 // false, 如果要在js比较浮点数
1 | |
13 | Date
直接调用, 返回一个当前时间字符串:
1 | |
Date实例有一个独特的地方。其他对象求值的时候,都是默认调用.valueOf()方法,但是Date实例求值的时候,默认调用的是toString()方法。这导致对Date实例求值,返回的是一个字符串,代表该实例对应的时间。
静态方法
1 | |
各种转换方法
1 | |
还有各种set, get方法, 这里不祥说, 建议时间库使用更丰富的: dayjs
14 | 正则表达式
两种创建方法:
var regex = /xyz/;var regex = new RegExp('xyz');
实例方法
-
test()=> boolean -
exec()=> 匹配成功的子字符串-
exec()方法的返回数组还包含以下两个属性:-
input:整个原字符串。 -
index:模式匹配成功的开始位置(从0开始计数)。
-
-
1 | |
字符串和正则表达式相关的实例方法
String.prototype.match():返回一个数组,成员是所有匹配的子字符串。String.prototype.search():按照给定的正则表达式进行搜索,返回一个整数,表示匹配开始的位置。String.prototype.replace():按照给定的正则表达式进行替换,返回替换后的字符串。String.prototype.split():按照给定规则进行字符串分割,返回一个数组,包含分割后的各个成员。
修饰符
g全局匹配- 如果带有
g修饰符,test,exec每次都将从上次匹配结束的位置向后匹配 - 带有
g, replace等操作会完成全部替换, 如果不带g则只进行操作
- 如果带有
i忽略大小写m多行模式: 默认情况下(即不加m修饰符时),^和$匹配字符串的开始处和结尾处,加上m修饰符以后,^和$还会匹配行首和行尾,即^和$会识别换行符
1 | |
15 | setTimeout和setInterval的运行机制
setTimeout和setInterval的运行机制,是将指定的代码移出本轮事件循环,等到下一轮事件循环,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就继续等待。这意味着,
setTimeout和setInterval指定的回调函数,必须等到本轮事件循环的所有同步任务都执行完,才会开始执行。由于前面的任务到底需要多少时间执行完,是不确定的,所以没有办法保证,setTimeout和setInterval指定的任务,一定会按照预定时间执行。
1
2setTimeout(someTask, 100);
veryLongTask();上面代码的
setTimeout,指定100毫秒以后运行一个任务。但是,如果后面的veryLongTask函数(同步任务)运行时间非常长,过了100毫秒还无法结束,那么被推迟运行的someTask就只有等着,等到veryLongTask运行结束,才轮到它执行。
16 | document和element
这部分内容太多了, 只记录一部分想记的内容, 详细请参考
元素的相关属性
Element.accessKey: 读写用于分配给当前元素的快捷键
1 | |
Element.tabIndex: 表示当前元素在Tab键遍历时的顺序, 可读写, -1表示不会Tab键不会遍历到该元素
Element.hidden: 当前元素是否可见(与css的设置是互相独立的, css的display和visibility优先级高于该属性)
innerHTML和outerHTML: 前者表示不包含该元素, 后者表示包含该元素, 注意outerHTML必须保证该元素有父元素
Element.clientHeight和Element.clientWidth: 返回元素节点的css高度和宽度, 只对块级元素有效, 包括padding部分, 但是不包括border, margin部分, 值始终是整数
Element.clientLeft和Element.clientTop: 表示边框border的宽度, 不包括padding和margin
Element.scrollHeight和Element.scrollWidth: 返回一个整数值, 表示当前元素的总高度, 包括padding不包括border和margin, 注意包括伪元素::before, ::after的高度, 如果元素内容溢出, 即使溢出部分隐藏, 仍然返回总高度(包括溢出).
1 | |
上面代码中,即使myDiv元素的 CSS 高度只有200像素,且溢出部分不可见,但是scrollHeight仍然会返回该元素的原始高度。
offsetHeight和offsetWidth: 与clientHeight和clientWidth相比多了边框的宽高
17 | 事件
事件处理
1 | |
事件传播的逻辑
事件的传播分为三个阶段, 以这段代码为例:
1 | |
1 | |
当点击p时, 三个阶段的过程如下:
- 捕获阶段:事件从
<div>向<p>传播时,触发<div>的click事件; - 目标阶段:事件从
<div>到达<p>时,触发<p>的click事件; - 冒泡阶段:事件从
<p>传回<div>时,再次触发<div>的click事件。
事件的代理
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。
1 | |
上面代码中,click事件的监听函数定义在<ul>节点,但是实际上,它处理的是子节点<li>的click事件。这样做的好处是,只要定义一个监听函数,就能处理多个子节点的事件,而不用在每个<li>节点上定义监听函数。而且以后再添加子节点,监听函数依然有效。
阻止事件传播
可使用stopPropagation方法阻止事件的传播
1 | |
注意, 如果一个元素绑定了多个事件, stopPropagation只会取消这个事件的传播, 而不会取消这个事件, 即:
1 | |
如果要彻底取消这个事件, 可以使用stopImmediatePropagation方法
Event对象
1 | |
事件的属性:
Event.eventPhase: 只读, 返回值有四种可能。- 0,事件目前没有发生。
- 1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中。
- 2,事件到达目标节点,即
Event.target属性指向的那个节点。 - 3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。
Event.bubbles: 只读, 事件是否会冒泡Event.cancelable: 只读Event.cancelBubble: 设为true则等效于stopPropagation, 用于阻止事件传播Event.defaultPrevented: 只读, 示该事件是否调用过Event.preventDefault方法Event.currentTarget: 返回事件当前所在的节点,即事件当前正在通过的节点,也就是当前正在执行的监听函数所在的那个节点。随着事件的传播,这个属性的值会变。Event.target: 返回原始触发事件的那个节点,即事件最初发生的节点。这个属性不会随着事件的传播而改变。Event.type: 事件类型
1 | |
Event.isTrusted: 返回一个布尔值,表示该事件是否由真实的用户行为产生。比如,用户点击链接会产生一个click事件,该事件是用户产生的;Event构造函数生成的事件,则是脚本产生的。
事件的方法:
Event.preventDefault()取消浏览器对该事件的默认行为, 比如点击链接后,浏览器默认会跳转到另一个页面,使用这个方法以后,就不会跳转了;该方法生效的前提是,事件对象的cancelable属性为true,如果为false,调用该方法没有任何效果。Event.stopPropagation()方法阻止事件在 DOM 中继续传播,防止再触发定义在别的节点上的监听函数,但是不包括在当前节点上其他的事件监听函数。Event.stopImmediatePropagation(): 方法阻止同一个事件的其他监听函数被调用Event.composedPath(): 返回一个数组,成员是事件的最底层节点和依次冒泡经过的所有上层节点。
常见的事件
鼠标相关: https://wangdoc.com/javascript/events/mouse.html
click:按下鼠标(通常是按下主按钮)时触发。dblclick:在同一个元素上双击鼠标时触发。mousedown:按下鼠标键时触发。mouseup:释放按下的鼠标键时触发。mousemove:当鼠标在一个节点内部移动时触发。当鼠标持续移动时,该事件会连续触发。为了避免性能问题,建议对该事件的监听函数做一些限定,比如限定一段时间内只能运行一次。mouseenter:鼠标进入一个节点时触发,进入子节点不会触发这个事件mouseover:鼠标进入一个节点时触发,进入子节点会再一次触发这个事件mouseout:鼠标离开一个节点时触发,离开父节点也会触发这个事件mouseleave:鼠标离开一个节点时触发,离开父节点不会触发这个事件contextmenu:按下鼠标右键时(上下文菜单出现前)触发,或者按下“上下文”菜单键时触发。wheel:滚动鼠标的滚轮时触发,该事件继承的是WheelEvent接口。
键盘相关: https://wangdoc.com/javascript/events/keyboard.html
keydown:按下键盘时触发。keypress:按下有值的键时触发,即按下 Ctrl、Alt、Shift、Meta 这样无值的键,这个事件不会触发。对于有值的键,按下时先触发keydown事件,再触发这个事件。keyup:松开键盘时触发该事件。
进度相关: https://wangdoc.com/javascript/events/progress.html
abort:外部资源中止加载时(比如用户取消)触发。如果发生错误导致中止,不会触发该事件。error:由于错误导致外部资源无法加载时触发。load:外部资源加载成功时触发。loadstart:外部资源开始加载时触发。loadend:外部资源停止加载时触发,发生顺序排在error、abort、load等事件的后面。progress:外部资源加载过程中不断触发。timeout:加载超时时触发。
表单相关: https://wangdoc.com/javascript/events/form.html
input和change: 当<input>、<select>、<textarea>的值发生变化时触发。该事件跟change事件很像,不同之处在于input事件在元素的值发生变化后立即发生,而change在元素失去焦点时发生,而内容此时可能已经变化多次。也就是说,如果有连续变化,input事件会触发多次,而change事件只在失去焦点时触发一次。select:select事件当在<input>、<textarea>里面选中文本时触发。invalid: 用户提交表单时,如果表单元素的值不满足校验条件,就会触发invalid事件。reset事件当表单重置(所有表单成员变回默认值)时触发。submit事件当表单数据向服务器提交时触发。注意,submit事件的发生对象是<form>元素,而不是<button>元素,因为提交的是表单,而不是按钮。
拖拽相关: https://wangdoc.com/javascript/events/drag.html
当元素节点或选中的文本被拖拉时,就会持续触发拖拉事件,包括以下一些事件。
drag:拖拉过程中,在被拖拉的节点上持续触发(相隔几百毫秒)。dragstart:用户开始拖拉时,在被拖拉的节点上触发,该事件的target属性是被拖拉的节点。通常应该在这个事件的监听函数中,指定拖拉的数据。dragend:拖拉结束时(释放鼠标键或按下 ESC 键)在被拖拉的节点上触发,该事件的target属性是被拖拉的节点。它与dragstart事件,在同一个节点上触发。不管拖拉是否跨窗口,或者中途被取消,dragend事件总是会触发的。dragenter:拖拉进入当前节点时,在当前节点上触发一次,该事件的target属性是当前节点。通常应该在这个事件的监听函数中,指定是否允许在当前节点放下(drop)拖拉的数据。如果当前节点没有该事件的监听函数,或者监听函数不执行任何操作,就意味着不允许在当前节点放下数据。在视觉上显示拖拉进入当前节点,也是在这个事件的监听函数中设置。dragover:拖拉到当前节点上方时,在当前节点上持续触发(相隔几百毫秒),该事件的target属性是当前节点。该事件与dragenter事件的区别是,dragenter事件在进入该节点时触发,然后只要没有离开这个节点,dragover事件会持续触发。dragleave:拖拉操作离开当前节点范围时,在当前节点上触发,该事件的target属性是当前节点。如果要在视觉上显示拖拉离开操作当前节点,就在这个事件的监听函数中设置。drop:被拖拉的节点或选中的文本,释放到目标节点时,在目标节点上触发。注意,如果当前节点不允许drop,即使在该节点上方松开鼠标键,也不会触发该事件。如果用户按下 ESC 键,取消这个操作,也不会触发该事件。该事件的监听函数负责取出拖拉数据,并进行相关处理。
其他:
resize: 事件在改变浏览器窗口大小时触发,主要发生在window对象上面。storage: Storage 接口储存的数据发生变化时,会触发 storage 事件,可以指定这个事件的监听函数。StorageEvent.key:字符串,表示发生变动的键名。如果 storage 事件是由clear()方法引起,该属性返回null。StorageEvent.newValue:字符串,表示新的键值。如果 storage 事件是由clear()方法或删除该键值对引发的,该属性返回null。StorageEvent.oldValue:字符串,表示旧的键值。如果该键值对是新增的,该属性返回null。StorageEvent.storageArea:对象,返回键值对所在的整个对象。也说是说,可以从这个属性上面拿到当前域名储存的所有键值对。StorageEvent.url:字符串,表示原始触发 storage 事件的那个网页的网址。
18 | 关于同源
次级域名不同, 如何共享Cookie
举例来说,A 网页的网址是http://w1.example.com/a.html,B 网页的网址是http://w2.example.com/b.html,那么只要设置相同的document.domain,两个网页就可以共享 Cookie。因为浏览器通过document.domain属性来检查是否同源。
1 | |
注意,A 和 B 两个网页都需要设置document.domain属性,才能达到同源的目的。因为设置document.domain的同时,会把端口重置为null,因此如果只设置一个网页的document.domain,会导致两个网址的端口不同,还是达不到同源的目的。
CORS
CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能。
所以CORS主要还是服务器部分需要配置实现。
19 | URL的编码和解码
编码和解码规则
网页的 URL 只能包含合法的字符。合法字符分成两类。
- URL 元字符:分号(
;),逗号(,),斜杠(/),问号(?),冒号(:),at(@),&,等号(=),加号(+),美元符号($),井号(#) - 语义字符:
a-z,A-Z,0-9,连词号(-),下划线(_),点(.),感叹号(!),波浪线(~),星号(*),单引号('),圆括号(())
除了以上字符,其他字符出现在 URL 之中都必须转义,规则是根据操作系统的默认编码,将每个字节转为百分号(%)加上两个大写的十六进制字母。
比如,UTF-8 的操作系统上,http://www.example.com/q=春节这个 URL 之中,汉字“春节”不是 URL 的合法字符,所以被浏览器自动转成http://www.example.com/q=%E6%98%A5%E8%8A%82。其中,“春”转成了%E6%98%A5,“节”转成了%E8%8A%82。这是因为“春”和“节”的 UTF-8 编码分别是E6 98 A5和E8 8A 82,将每个字节前面加上百分号,就构成了 URL 编码。
encodeURI和encodeURIComponent
前者只转码除元字符和语义字符外的字符, 后者除了语义字符所有字符都被转码
故后者不能用来转码整个URL, 只能用来转码组成部分
1 | |
与这两个方法对应的有解码方法decodeURI和decodeURIComponent
20 | 表单
<form>
内置验证
1 | |
如果一个控件通过验证,它就会匹配:valid的 CSS 伪类,浏览器会继续进行表单提交的流程。如果没有通过验证,该控件就会匹配:invalid的 CSS 伪类,浏览器会终止表单提交,并显示一个错误信息
:valid和:invalid示意:
1 | |
可以通过form.checkValidity() // 返回值boolean手动触发表单校验
由于记完JS, 篇幅已经很长了, 于是打算将此博客分篇, 下篇见《JS与ES6的查漏补缺(下)》