JS拾遗笔记
本文主要记录了 JS 基础以及 ES6 的相关笔记,随缘更新。
JS 基础
基本概念
JS 中 label 标签用来终止或者跳过外层循环
在 ES5 之前,JS 有六种基本对象
- number
- string
- boolean
- object
- undefined
- null
typeof null
的结果是object
,这是由于 JS 历史遗留问题造成的。JS 中所有数字都是以 64 位浮点数表示的,而有些小数用二进制不能精确的表示,例如
要理解这个问题,我们需要知道十进制和二进制的区别。十进制是以 10 为基数的数制,也就是说,每个位上的数字都是 10 的幂次方的系数。比如,123.45 可以表示为:
二进制是以 2 为基数的数制,也就是说,每个位上的数字都是 2 的幂次方的系数。比如,101.11 可以表示为:
当我们把一个十进制小数转换成二进制小数时,我们需要不断地用 2 去 x 这个小数的小数部分,并把余数作为二进制位。比如,0.1 转换成二进制的过程如下:
可以看到,当我们遇到了之前出现过的余数 0.2 时,就会出现循环。所以,0.1 的二进制表示就是:
其中$\overline{0011}$表示 0011 这个部分是无限重复的。
如果一个十进制小数能被 2 整除,那么它就能用有限的二进制位来表示。比如,0.5 可以表示为$(0.1)_2$,因为$0.5 \div 2 = 0 … 1$。但如果一个十进制小数不能被 2 整除,那么它就可能用无限的二进制位来表示。比如,0.3 可以表示为$(0.\overline{010011})_2$³。
数值转换函数。
parseInt
:将一个字符串转化为整数。parseFloat
:将一个字符串转化为浮点数。isFinite
:判断是否是正常的数值。先转化成数值再进行判断。isNaN
:判断是否是NaN
。先转换成数值再进行判断。
JS 在圆括号里面只能是表达式,在遇到花括号的时候一律解释为代码块。
delete
用于删除对象的属性,但有注意点,一是不能通过delete
语句的结果来判断删除属性是否成功,因为删除一个不存在的属性delete
也会返回true
,只有当对象属性设置为不可配置的时候返回false
。而且只能删除自身的属性,不能删除继承的属性。in
运算符可以判断对象属性是否含有某个key
,注意会沿着对象的原型链进行查找。你可以传递任意数量的参数给
Function
构造函数,只有最后一个参数会被当做函数体,如果只有一个参数,该参数就是函数体。var
提升只会把声明提升,不会提升赋值语句。- 函数的
name
属性会显示函数的名字,函数的length
会显示函数预期传入的参数的个数。这个属性可用于函数的方法的重载。为何内置函数的
toString
方法返回的是function (){[native code]}
,这是因为JS
内部方法的函数不是通过原生JS
实现的,而是由C++
等代码实现的。 - 闭包的最大用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。闭包的另一个用处,是封装对象的私有属性和私有方法。
为了避免解析的歧义,
JavaScript
规定,如果function
关键字出现在行首,一律解释成语句。因此,引擎看到行首是function
关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。
对象 Object
数组的
length
是一个动态属性,只要是数组,就一定有length
属性。该属性是一个动态的值,等于键名中的最大整数加上1
,没有显示的元素填空值。更改length
属性会导致,数组长度增大,添加空值。或减小,直接裁剪元素。注意一些方法对于数组空位的处理
Object.keys()
与Object.getOwnPropertyNames
二者返回结果在大多数情况下都是相同的,但前者只会返回可枚举的属性,后者可以返回不可枚举的属性。二者都不能获取原型链上面的属性。valueOf
方法的主要用途是,JavaScript
自动类型转换时会默认调用这个方法。Object.defineProperty
方法的详细解释Object.defineProperty
方法是一个JavaScript
的静态方法,它可以用来在一个对象上直接定义一个新属性,或者修改一个已经存在的属性,并返回该对象。这个方法的作用是可以精确地控制对象属性的特征,比如是否可写、是否可枚举、是否可配置等。Object.defineProperty
方法的语法如下:其中,
obj
是要定义或修改属性的对象,prop
是要定义或修改的属性名,descriptor
是一个对象,用来描述该属性的特征。descriptor
对象可以包含以下几个键:value
:该属性的值,可以是任何有效的JavaScript
值,默认为undefined
。writable
:该属性是否可写,如果为true
,则可以通过赋值运算符修改该属性的值,否则不能,默认为false
。enumerable
:该属性是否可枚举,如果为true
,则可以通过for...in
循环或Object.keys
方法遍历该属性,否则不能,默认为false
。configurable
:configurable
是一个布尔值,表示属性的可配置性,默认为false
。如果设为false
,将阻止某些操作改写属性描述对象,比如无法删除该属性,也不得改变各种元属性(value
属性除外)。也就是说,configurable
属性控制了属性描述对象的可写性。get
:该属性的getter
函数,用来返回该属性的值,如果没有getter
函数,则为undefined
。当访问该属性时,会调用这个函数,并把this
绑定到所属对象上。默认为undefined
。set
:该属性的setter
函数,用来设置该属性的值,如果没有setter
函数,则为undefined
。当给该属性赋值时,会调用这个函数,并把this
绑定到所属对象上。默认为undefined
。
注意:
descriptor
对象中不能同时出现value
或writable
和get
或set
这两组键,否则会抛出异常。也就是说,一个属性要么是数据描述符(有value
和writable
),要么是访问器描述符(有get
和set
)。使用
Object.defineProperty
方法可以创建或修改对象的属性,并且可以精细地控制其特征。例如:Object.getOwnPropertyDescriptor()
方法可以获取属性描述对象。它的第一个参数是目标对象,第二个参数是一个字符串,对应目标对象的某个属性名。不能用于获取继承属性。Object.getOwnPropertyNames
方法返回一个数组,成员是参数对象自身的全部属性的属性名,不管该属性是否可遍历。实例对象的
propertyIsEnumerable()
方法返回一个布尔值,用来判断某个属性是否可遍历。注意,这个方法只能用于判断对象自身的属性,对于继承的属性一律返回false
。如果原型对象的某个属性的
writable
为false
,那么子对象将无法自定义这个属性。具体来说,如果一个属性的
enumerable
为false
,下面三个操作不会取到该属性。for..in
循环Object.keys
方法JSON.stringify
方法
当一个对象的属性的
configurable
设置为false
的时候,writable
属性只有在false
改为true
时会报错,true
改为false
是允许的。value
属性的情况比较特殊。只要writable
和configurable
有一个为true
,就允许改动value
。get
与set
关键字的使用上面两种写法,虽然属性 p 的读取和赋值行为是一样的,但是有一些细微的区别。第一种写法,属性 p 的 configurable 和 enumerable 都为 false,从而导致属性 p 是不可遍历的;第二种写法,属性 p 的 configurable 和 enumerable 都为 true,因此属性 p 是可遍历的。实际开发中,写法二更常用。
内置对象
利用
Object.prototype.toString
方法来写一个能够判断所有数据类型的函数数组的
toString
方法,注意数组的shift
与pop
方法会返回当前弹出的值。数组的join
方法,如果数组成员是undefined
或null
或空位,会被转成空字符串。数组的splice
方法也能使用负数索引,只使用一个参数就会把从当前索引往后全部删除掉。大部分遍历类型的数组原生函数都会跟三个对象element, index, arr
。注意,对于空数组,
some
方法返回false
,every
方法返回true
,回调函数都不会执行。toFixed()
方法先将一个数转为指定位数的小数,然后返回这个小数对应的字符串。由于浮点数的原因,小数 5 的四舍五入是不确定的,使用的时候必须小心。
Number.prototype.toPrecision
方法用于将一个数转为指定位数的有效数字。由于浮点数的原因,该方法四舍五入依然不可靠。
toLocaleString
方法的使用。Math
对象的相关方法。Math.abs()
:绝对值Math.ceil()
:向上取整Math.floor()
:向下取整Math.max()
:最大值Math.min()
:最小值Math.pow()
:幂运算Math.sqrt()
:平方根Math.log()
:自然对数Math.exp()
:e 的指数Math.round()
:四舍五入Math.random()
:随机数
Date
对象可以作为普通函数直接调用,返回一个代表当前时间的字符串。Date
实例求值默认调用的是toString
方法而不是valueOf
方法。JSON
的格式规定- 复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
- 原始类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和
null
(不能使用NaN
,Infinity
,-Infinity
和undefined
)。 - 字符串必须使用双引号表示,不能使用单引号。
- 对象的键名必须放在双引号里面。
- 数组或对象最后一个成员的后面,不能加逗号。
JSON.stringfy
可以接受三个参数自定义行为。toJSON
用于自定义返回值作为参数。
通过
defineProperty
实现对象的深拷贝。
API
MutationObserver
options
配置选项。childList
:子节点的变动(指新增,删除或者更改)。attributes
:属性的变动。characterData
:节点内容或节点文本的变动。subtree
:布尔值,表示是否将该观察器应用于该节点的所有后代节点。attributeOldValue
:布尔值,表示观察attributes
变动时,是否需要记录变动前的属性值。characterDataOldValue
:布尔值,表示观察characterData
变动时,是否需要记录变动前的值。attributeFilter
:数组,表示需要观察的特定属性(比如['class','src']
)MutationRecord
对象MutationRecord
对象包含了DOM
的相关信息,有如下属性:type
:观察的变动类型(attributes
、characterData
或者childList
)。target
:发生变动的DOM
节点。addedNodes
:新增的DOM
节点。removedNodes
:删除的DOM
节点。previousSibling
:前一个同级节点,如果没有则返回null
。nextSibling
:下一个同级节点,如果没有则返回null
。attributeName
:发生变动的属性。如果设置了attributeFilter
,则只返回预先指定的属性。oldValue
:变动前的值。这个属性只对attribute
和characterData
变动有效,如果发生childList
变动,则返回null
。
addEventListener
第三个参数配置。
其次,第三个参数除了布尔值useCapture
,还可以是一个监听器配置对象,定制事件监听行为。该对象有以下属性。capture
:布尔值,如果设为true
,表示监听函数在捕获阶段触发,默认为false
,在冒泡阶段触发。once
:布尔值,如果设为true
,表示监听函数执行一次就会自动移除,后面将不再监听该事件。该属性默认值为false
。passive
:布尔值,设为true
时,表示禁止监听函数调用preventDefault()
方法。如果调用了,浏览器将忽略这个要求,并在控制台输出一条警告。该属性默认值为false
。signal
:该属性的值为一个AbortSignal
对象,为监听器设置了一个信号通道,用来在需要时发出信号,移除监听函数。JS 中的
dispatchEvent
是一个用于在代码中生成和触发事件的方法。它可以用来模拟用户的操作,如点击或按键,或者自定义一些事件,如动画结束或数据更新。使用dispatchEvent
的步骤如下:- 首先,使用
Event
构造函数创建一个新的Event
对象,指定事件的类型和一些可选的属性,如是否可冒泡或是否可取消。 - 然后,使用
element.dispatchEvent()
方法将事件派发到一个指定的事件目标,如一个元素或一个文档。这个方法会同步地调用所有监听该事件的事件处理函数,并返回一个布尔值,表示事件是否被取消了。 - 最后,如果需要,可以在事件处理函数中使用
event.preventDefault()
方法来阻止事件的默认行为,或者使用event.stopPropagation()
方法来阻止事件的进一步传播。
下面是一个简单的例子,演示了如何使用
dispatchEvent
来触发一个自定义的build
事件:Event.stopImmediatePropagation
方法阻止同一个事件的其他监听函数被调用,不管监听函数定义在当前节点还是其他节点。也就是说,该方法阻止事件的传播,比Event.stopPropagation()
更彻底。- 首先,使用
鼠标事件的种类
- 点击事件
click
:按下鼠标(通常是按下主按钮)时触发。dblclick
:在同一个元素上双击鼠标时触发。mousedown
:按下鼠标键时触发。mouseup
:释放按下的鼠标键时触发。
- 移动事件
mousemove
:当鼠标在一个节点内部移动时触发。当鼠标持续移动时,该事件会连续触发。为了避免性能问题,建议对该事件的监听函数做一些限定,比如限定一段时间内只能运行一次。mouseenter
:鼠标进入一个节点时触发,进入子节点不会触发这个事件。mouseover
:鼠标进入一个节点时触发,进入子节点会再一次触发这个事件。mouseout
:鼠标离开一个节点时触发,离开父节点也会触发这个事件。mouseleave
:鼠标离开一个节点时触发,离开父节点不会触发这个事件。
- 点击事件
键盘事件的种类
keydown
:按下键盘时触发。keypress
:按下有值的键时触发,即按下Ctrl
、Alt
、Shift
、Meta
这样无值的键,这个事件不会触发。对于有值的键,按下时先触发keydown
事件,再触发这个事件。keyup
:松开键盘时触发该事件。
进度事件的种类
进度事件用来描述资源加载的进度,主要由AJAX
请求、<img>、<audio>、<video>、<style>、<link>
等外部资源的加载触发,继承了ProgressEvent
接口。它主要包含以下几种事件。abort
:外部资源中止加载时(比如用户取消)触发。如果发生错误导致中止,不会触发该事件。error
:由于错误导致外部资源无法加载时触发。load
:外部资源加载成功时触发。loadstart
:外部资源开始加载时触发。loadend
:外部资源停止加载时触发,发生顺序排在error、abort、load
等事件的后面。progress
:外部资源加载过程中不断触发。timeout
:加载超时时触发。
表单事件
input
:input
事件当<input>、<select>、<textarea>
的值发生变化时触发。对于复选框(<input type=checkbox>
)或单选框(<input type=radio>
),用户改变选项时,也会触发这个事件。另外,对于打开contenteditable
属性的元素,只要值发生变化,也会触发input
事件。select
:select
事件当在<input>、<textarea>
里面选中文本时触发。change
事件当<input>、<select>、<textarea>
的值发生变化时触发。它与input
事件的最大不同,就是不会连续触发,只有当全部修改完成时才会触发,另一方面input
事件必然伴随change
事件。具体来说,分成以下几种情况。invalid
:用户提交表单时,如果表单元素的值不满足校验条件,就会触发invalid
事件。
拖拉事件
- 事件:
drag
、dragstart
DataTransfer
接口:
- 事件:
动态加载脚本防止浏览器假死
popstate
事件在浏览器的history
对象的当前记录发生显式切换时触发。注意,调用history.pushState()
或history.replaceState()
,并不会触发popstate
事件。该事件只在用户在history
记录之间显式切换时触发,比如鼠标点击“后退/前进”按钮,或者在脚本中调用history.back()
、history.forward()
、history.go()
时触发。
该事件对象有一个state
属性,保存history.pushState
方法和history.replaceState
方法为当前记录添加的state
对象。hashchange
事件在URL
的hash
部分(即#
号后面的部分,包括#
号)发生变化时触发。该事件一般在window
对象上监听。hashchange
的事件实例具有两个特有属性:oldURL
属性和newURL
属性,分别表示变化前后的完整URL
。浏览器的弹窗
alert
:方法弹出的对话框,只有一个“确定”按钮,往往用来通知用户某些信息。用户只有点击“确定”按钮,对话框才会消失。对话框弹出期间,浏览器窗口处于冻结状态,如果不点“确定”按钮,用户什么也干不了。window.alert()
方法的参数只能是字符串,没法使用CSS
样式,但是可以用\n
指定换行。prompt
:window.prompt()
方法弹出的对话框,提示文字的下方,还有一个输入框,要求用户输入信息,并有“确定”和“取消”两个按钮。它往往用来获取用户输入的数据。window.prompt()
的返回值有两种情况,可能是字符串(有可能是空字符串),也有可能是null
。具体分成三种情况。- 用户输入信息,并点击“确定”,则用户输入的信息就是返回值。
- 用户没有输入信息,直接点击“确定”,则输入框的默认值就是返回值。
- 用户点击了“取消”(或者按了
ESC
按钮),则返回值是null
。
confirm
:window.confirm()
方法弹出的对话框,除了提示信息之外,只有“确定”和“取消”两个按钮,往往用来征询用户是否同意。
window
窗口的相关的方法open
:新建一个浏览器窗口close
:关闭当前的窗口stop
:停止资源的加载
window.navigator
属性指向一个包含浏览器和系统信息的Navigator
对象。脚本通过这个属性了解用户的环境信息。ajax
的基本使用
浏览器的相关知识
同源策略(
same-origin policy
)
要求:- 协议相同
- 域名相同
- 端口相同
但是,浏览器对于
cookie
的处理并没有完全遵守同源策略的规定。实际上,浏览器允许同一个域名下的不同端口之间共享cookie
。也就是说,如果一个网址是http://www.example.com:8000
,它可以读取和写入http://www.example.com:8001的cookie
,反之亦然。这是因为浏览器实现来说,“cookie
区分域,而不区分端口”。JSONP
的使用CORS
的使用- 满足简单请求的条件:
- 请求方法是以下三种方法之一
GET
POST
HEAD
HTTP
的头信息不超出以下几种字段。Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
:只限于三个值application/x-www-form-urlencoded
、multipart/form-data
、text/plain
- 请求方法是以下三种方法之一
- 对于简单请求,浏览器直接发出
CORS
请求。具体来说,就是在头信息之中,增加一个Origin
字段。
- 相应预检请求的
node.js
- 满足简单请求的条件:
读取生成
URL
ES6
块级作用域
let
与const
都不会发生变量提升,并且都是块级作用域
这是因为 var 和 let/const 在变量声明时有不同的机制。var 声明的变量会发生变量提升,也就是说,它们的声明会被提升到作用域的顶部,但是赋值操作不会被提升。所以在声明之前使用 var 变量,不会报错,但是变量的值是 undefined,而不是赋值后的值。例如:
上面的代码相当于:
而 let/const 声明的变量不会发生变量提升,也就是说,它们的声明和赋值都不会被提升。而且,在声明之前使用 let/const 变量会报错,因为 let/const 声明的变量在初始化之前存在一个暂时性死区(Temporal Dead Zone),在这个区域内,变量不能被访问或者赋值。例如:
寄生式继承
手写一个 new 函数
关键在于如果函数返回了一个对象则以这个对象为
new
的返回结果。
数组的新增方法
空值运算符
它的行为类似||
,但是只有运算符左侧的值为 null
或 undefined
时,才会返回右侧的值。
Symbol
基本使用
获取
Symbol
对象属性名Symbol
值的重用有时,我们希望重新使用同一个
Symbol
值,Symbol.for()
方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol
值。如果有,就返回这个Symbol
值,否则就新建一个以该字符串为名称的Symbol
值,并将其注册到全局。
Proxy
- get(target, propKey, receiver):拦截对象属性的读取,比如
proxy.foo
和proxy['foo']
。 - set(target, propKey, value, receiver):拦截对象属性的设置,比如
proxy.foo = v
或proxy['foo'] = v
,返回一个布尔值。 - has(target, propKey):拦截
propKey in proxy
的操作,返回一个布尔值。 - deleteProperty(target, propKey):拦截
delete proxy[propKey]
的操作,返回一个布尔值。 - ownKeys(target):拦截
Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。 - getOwnPropertyDescriptor(target, propKey):拦截
Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象。 - defineProperty(target, propKey, propDesc):拦截
Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一个布尔值。 - preventExtensions(target):拦截
Object.preventExtensions(proxy)
,返回一个布尔值。 - getPrototypeOf(target):拦截
Object.getPrototypeOf(proxy)
,返回一个对象。 - isExtensible(target):拦截
Object.isExtensible(proxy)
,返回一个布尔值。 - setPrototypeOf(target, proto):拦截
Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。 - apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如
proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。 construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如
new proxy(...args)
。Reflect
配合Proxy
实现观察者模式。Proxy.revocable()
方法返回一个可取消的Proxy
实例。
Reflect
Reflect
对象与Proxy
对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect
对象的设计目的有这样几个。
- 将
Object
对象的一些明显属于语言内部的方法(比如Object.defineProperty
),放到Reflect
对象上。现阶段,某些方法同时在Object
和Reflect
对象上部署,未来的新方法将只部署在Reflect
对象上。也就是说,从Reflect
对象上可以拿到语言内部的方法。- 修改某些
Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。- 让
Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy
对象可以方便地调用对应的Reflect
方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy
怎么修改默认行为,你总可以在Reflect
上获取默认行为。
iterator
- 简单遍历器实现
return
方法的使用场合是,如果for...of
循环提前退出(通常是因为出错,或者有break
语句),就会调用return
方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return
方法。
generator
基本实现
throw
方法和return
方法。
async 函数的实现原理
super
关键字的注意点
由于
this
指向子类实例,所以如果通过super
对某个属性赋值,这时super
就是this
,赋值的属性会变成子类实例的属性。
ES6 属性的遍历
ES6 一共有 5 种方法可以遍历对象的属性。
for...in
:循环遍历对象自身的和继承的可枚举属性(不含Symbol
属性)Object.keys(obj)
:返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol
属性)的键名Object.getOwnPropertyNames(obj)
:回一个数组,包含对象自身的所有属性(不含Symbol
属性,但是包括不可枚举属性)的键名Object.getOwnPropertySymbols(obj)
:返回一个数组,包含对象自身的所有Symbol
属性的键名Reflect.ownKeys(obj)
:返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是Symbol
或字符串,也不管是否可枚举
上述遍历,都遵守同样的属性遍历的次序规则:
- 首先遍历所有数值键,按照数值升序排列
- 其次遍历所有字符串键,按照加入时间升序排列
- 最后遍历所有
Symbol
键,按照加入时间升序排