引言
最近抽空学习了下 JavaScript 语言,当然还是入门级别的那种。学习过程中也做了些笔记。总的来说,ES5 学完后,对 JavaScript 本身了解了大概,语言本身设计上也存在一些坑,所以搭配《JavaScript 精粹》这本书看看,可以进一步了解有哪些比较好的写法避免踩坑。
基础篇
判断是否为未定义的变量:
1
2
3
4
5
6
7
8
9// 错误的写法
if (v) {
// ...
}
// ReferenceError: v is not defined
// 正确的写法
if (typeof v === "undefined") {
// ...
}typeof null
也是object
- 使用
instanceof
可以判断是否为数组:v instanceof Array
null
和undefined
非常类似,甚至null == undefined
都成立,但是它们在 JavaScript 中还是有区别:null
表示空对象,可转换为数值 0:Number(null) //0
undefined
表示没有定义的原始值,转换为数值为 NaN
JS 中所有数字都是以 64 位浮点数存储的,对于某些运算,只能用整数,JS 会将数字转换为 32 位整数再做运算
- 数值计算比较需要注意,因为浮点数有精度丢失的问题。比如:
0.3-0.2 !== 0.2-0.
。 - JavaScript 对 15 位的十进制数都可以精确处理。
- 对于码点在 U+10000 到 U+10FFFF 之间的字符,JavaScript 总是认为它们是两个字符(length 属性为 2)
- base64 转换:
btoa
,atob
属性删除
delete
:- 不能删除对象本身
- 无论属性是否存在,删除总会返回
true
- 继承的属性不能删除,虽然也会返回
true
- 使用
defineProperty
定义的标记为不可删除的属性,delete
返回false
对象属性存在新判断:
in
可以判断,但是不能区分是继承的还是自身拥有的hasOwnProperty
可以弥补上述缺点
for...in
用于遍历对象的属性:- 对象属性必须是可遍历的(enumerable)
- 对于继承的属性也会被遍历到,如果不想要,可以搭配
hasOwnProperty
过滤掉
一般也可以使用
Object.keys(o).map(k => { /*do stuff*/ }
- 函数声明几种方式:
function func_name(params) {}
let say = function (word) {}
,匿名函数let say = function f(word) { console.log(f) }
,注意,这里的函数名f
只可以在该函数内部访问到,有两个好处:- 遍历在函数内部调用自己
- 方便调试,排错时看到具体的函数名,而不再是匿名函数
- 使用
Function
构造函数,注意,返回的函数的type
的确也是function
(不推荐使用)
- JS 引擎将函数名视为变量名,所以也存在「函数名提升」的效果,因此,在函数声明位置的上方调用也不会出错
- 函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域
arguments
对象可以获取到函数传入的参数值:- 该对象不是数组,所以像
slice
,forEach
方法就不存在 - 如果想要转换成数组,一种方式就是:
let args = Array.prototype.slice.call(arguments)
- 在严格模式下,不能通过
arguments.callee
调用函数自身;对arguments
的修改不会对原始传入的参数产生影响
- 该对象不是数组,所以像
立即调用函数的两种方式:
(function () {console.log(10)})()
(function () {console.log(10)}())
数组是特殊的对象,其键名就是一组数字,可以通过
Object.keys
返回所有的键名- 数组的
length
属性不过滤空位。所以,使用 length 属性进行数组遍历,一定要非常小心。如delete arr[3]
之后,依然不会影响length
,只是产生了一个空位;所以可以使用forEach
替代,可以过滤空位(但空位不等同于undefined
)。也就是说var a = [, , ,]
表示的每个位置都是空位;var a = [undefined, undefined]
则是可遍历到的,只是值为undefined
而已 - 类似数组的数组(Array like object):
- 对象拥有类似下面的定义:
1
2
3
4
5
6
7
8// 所有的键都是数字
// 带有 length 属性
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
- 对象拥有类似下面的定义:
函数的
arguments
,字符串,还有多数的 DOM 元素都是 array-like 的对象。可以使用Array.prototype.slice.call(xx)
转换成数组void
运算符:作用是执行一个表达式,不返回任何值(返回 undefined):- 推荐用法:
void (x = 5)
- 使用场景,防止网页跳转:
<a href="javascript: void f()">文字</a>
- 推荐用法:
逗号运算符返回最后一个值
'a', 'b' // "b"
语法专题
Number
用于强制转换成数值类型,与parseInt
相比,转换要严格得多,只要遇到不可转换为数值的字符,转换就失败Boolean
所有对象(含空对象)的转换结果都是true
,如Boolean(new Boolean(false)) // true
JS 中所有抛出的错误都是
new Error(xx)
出来的,Error 的基本属性:message
:错误提示信息(必须)name
:错误名称(非标准属性,看 JS 引擎)stack
:错误的堆栈(非标准属性,看 JS 引擎)
派生错误对象:
SyntaxError
ReferenceError
RangeError
:数组长度为负数;Number
对象的方法参数超出范围;函数堆栈超过最大值TypeError
URIError
throw
可以抛出任意类型的值,并且在throw
的位置就中断了执行,JS 引擎会接收到抛出的信息console
一些重要的方法:info
error
debug
warn
table
log
dir
:类似inspect
可以在需要的地方加
debugger
作为断点,用于调试
标准库
Object()
函数可以将任意值转换成对象,如果传入的参数是原始类型值,则会转换成其包装对象的实例;如果入参已经是对象了,则不会转换- 可以利用上述特性判断变量是否为对象:
value === Object(value)
Object.keys()
静态方法可以返回对象的所有属性名(非继承,且可枚举),该遍历方法优先Object.getOwnPropertyNames()
静态方法则会返回对象的所有属性名(包括不可枚举的)Object.prototype.xxx
实例方法:valueOf()
:返回对象的值,默认为自身toString()
:返回对象的字符串表示Object.prototype.toString.call(value)
返回[object <TypeName>]
,第二个元素可以得到具体的值类型
JS 提供了内部数据结构,描述对象的属性,控制其行为,示例如下:
1
2
3
4
5
6
7
8{
value: 123,
writeable: false,
enumerable: true,
configurable: false,
get: undefined,
set: undefined
}Object.getOwnPropertyDescriptor(obj, 'a')
可以获取属性描述对象- 默认情况下,如果属性被设置为
writeable=false
后,继承者将无法重新定义同名属性;除非可以通过覆盖属性描述对象来绕过限制 如果属性的
enumerable
为false
,则有三种操作不能取到该属性:for...in
Object.keys
:无法获取对象继承的属性,此时可以用getOwnPropertyNames
JSON.stringify
configurable
:控制是否可以修改描述符属性,并且当configurable=fasle
时,无法删除- 判断对象是否为数组:
Array.isArray
面向对象
- JavaScript 中的对象体系,是基于构造函数
constructor
和原型链prototype
的。构造函数就是对象生成的「模板」 构造函数特点:
- 内部使用
this
,指向生成的对象实例 - 需要配合
new
关键字
- 内部使用
new
干了什么:- 新建空对象,作为要返回的对象
- 将空对象的
__proto__
指向构造函数的prototype
- 将该空对象赋值给函数内部的
this
关键字 - 执行构造函数中的代码
函数内部可使用
new.target
判断是否使用了new
调用构造函数this
在函数体内部,指向当前的运行环境- 如果
this
所在的方法不在对象的第一层,这时this
只是指向当前一层的对象,而不会继承更上面的层 - 函数实例的
call
方法可以指定this
指向的环境,此外可以跳过同名函数覆盖,直接使用原始的函数;appy
和call
类似,只是接收一个列表参数 bind
也可以绑定对象,变换this
指向- JS 继承机制的核心:原型中所有属性和方法都可以被多个实例共享
instanceof
运算符:- 只能用于非原始类型对象
- 对象的原型
prototype
均为 null 时失效 undefined instanceof Object
和null instanceof Object
均为false
异步
- Promise 的回调函数不是正常的异步任务,而是微任务。前者会追加到下一轮事件循环,而微任务则追加到本轮事件循环。所以微任务执行要早于常规异步任务。
setTimeout(f, 0)
可以让任务在下轮事件循环中执行
DOM
- 元素的
hidden
属性只有在 CSS 没有明确设定当前元素的可见性时才会生效,也就是 CSS 的display
用于可见性控制的优先级高于元素自身的hidden
属性 - 网页元素可以自定义
data-*
属性,添加数据,可以使用.dataset
返回数据对象 - 为防止遭受攻击,对于文本内容,建议使用
textContent
替代innerHTML
设置文本
CSS
- CSS 负责视觉效果;JavaScript 负责与用户的行为互动
- 可以通过
style
属性设置 CSS 样式 CSSStyleDeclaration
对象用于操作元素的样式,包括:- 元素的
style
属性 CSSStyle
实例的style
属性window.getComputedStyle()
返回值(元素的全部样式需要它获取到)
- 元素的
需要注意的是,
CSSStyleDeclaration
的属性名和原生 CSS 还是有点区别,命名需要修改,如backgroud-color
->backgroudColor
;如果是 JS 的保留字,则需要加上css
前缀- 属性操作:
setProperty
removeProperty
DOM 变动监控
- Mutation Observer API:DOM 的变动(节点增减、属性变动、文本内容变动),都可以通过该 API 获得通知
- 与事件的区别:
- MO 事件是在 DOM 变动时异步触发,并且是等到当前所有 DOM 操作都结束才触发
- 事件则是同步触发,DOM 变动立刻触发
总结来说:
- 等待所有脚本任务完成后,才会触发
- 把 DOM 变动记录封装成一个数组处理,而非每条都分别处理
- 既可以观察 DOM 所有类型变动,也可以指定观察某类变动
用法:
1
2
3
4
5const observer = new MutationObserver((mutations, observer) => {})
// 指定监听的元素和类型
const article = document.querySelector("article")
observer.observe(article, {'childList': true, 'attritubes': true})
事件
DOM 的事件操作(监听、触发),都是
EventTarget
中定义的。主要接口如下:addEventListener
removeEventListener
dispatchEvent
HTML 中可以直接在元素属性中,定义某些监听代码。如
onload
,这类监听代码只在冒泡阶段触发- 元素的
onclick
属性,也可以指定监听函数,并且在冒泡阶段触发 - 事件传播:
- 第一阶段:从
window
对象传导到目标节点(上层传到底层),即为「捕获阶段」(capture phase) - 第二阶段:从目标节点上触发,叫做「目标阶段」(target phase)
- 第三阶段:从目标节点传导回
window
对象(从底层传回上层),叫做「冒泡阶段」(bubbling phase)
- 第一阶段:从
e.StopPropagation
则是会阻止事件传播,但不会处理其它的监听事件e.stopImmediatePropagation
会彻底取消同名的时间- 网页中除了元素节点默认不可以拖拉,其它(图片、链接、选中的文字)都是可以拖拉的,可通过将元素节点的
draggable
属性设置为true
实现 - 一旦某个元素节点被设置为可拖拽后,就无法通过鼠标选中节点内部的文字或子节点了
浏览器对象
- 嵌入脚本时,
<script>
元素的type
属性默认为text/javascript
,新浏览器可以写成application/javascript
。当然,也可以不填写。另外,integrity
属性可以设置脚本的 SHA256 签名,对于非法脚本浏览器会拒绝加载 async
和defer
同时存在,则后者不起作用window.navigator
指向包含浏览器和系统信息的Navigator
对象,可以了解用户的环境信息- Cookie 是服务器保存在浏览器的一小段文本信息,每个 Cookie 的大小一般不超过 4KB,每次请求服务器,都会附带该信息
Cookie 包括的信息:
- 名字
- 值
- 到期时间
- 所属域名(默认当前域名)
- 生效的路径(默认当前网站)
浏览器的安全基础是「同源政策」(same-origin policy)
同源的含义:
- 相同的协议
- 相同的域名
- 相同的端口
非同源的限制:
- 无法读取非同源网页的 Cookie, LocalStorage 和 IndexedDB
- 无法接触非同源网页的 DOM
- 无法向非同源地址发送 AJAX 请求(即便发送,浏览器也会拒绝接受响应)
document.domain
设置可共享 Cookie,适用于 ifame 和 Cookie,对于 LocalStorage 和 IndexedDB 无效;此外,服务器可以在设置 Cookie 时就指定好所属一级域名:.example.com
,也可以对子域名达到共享 Cookie 的目的对于完全不同源的窗口,想要通信有两种方式:
fragment identifier
:片段标识符。http://example.com/x.html#fragment
只是改变#fragment
不会导致网页刷新,属于破解无法通信之法- H5 提供了新的
postMessage
方法发送消息,监听方可以监听message
事件获取消息
AJAX 突破同源限制的方法:
- JSONP
- WebSocket
- CORS (Cross-Origin Resource Sharing)
CORS 通信:
- W3C 标准,允许浏览器跨域请求
- 详细介绍
- 区分简单请求和非简单请求,流程不同
- 简单请求中,Origin Header 很重要
- 非简单请求,先进行
/OPTIONS
预先检查,然后后续流程类似简单请求
Storage 接口:
sessionStorage
:保存的数据用于浏览器一次会话,结束后会被清理localStorage
保存数据长期存在- 只有同域名下的网页才可以读取,跨域会报错
length
返回数据项个数setItem
保存 key + valuegetItem
返回保存的 key 对应的 valueclear
key
返回对应位置的键值