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的值
undefined
null
0
NaN
''
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
2
setTimeout(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的查漏补缺(下)》