JavaScript 历史
JavaScript 作为 Netscape Navigator 浏览器的一部分首次出现在 1996 年, 它最初的设计目标是改善网页的用户体验
期初 JavaScript 被命名为 LiveScript, 后因和 Sun 公司合作, 因市场宣传需要改名 JavaScript, 后来 Sun 公司被 Oracle 收购, JavaScript 版权归 Oracle 所有。
注: JavaScript 简称 JS, 以下将使用简称
浏览器组成部分
- shell 部分
- 内核部分
- 渲染引擎(语法规则和渲染)
- JS 引擎
- 其他模块
JS 引擎
- 2001 年发布 IE6, 首次实现对 JS 引擎的优化
- 2008 年 Google 发布最新浏览器 Chrome, 它是采用优化后的 JS 引擎, 引擎代号 V8, 因能把 JS 代码直接转化为机械码来执行, 进而以速度快而闻名
- 后 Firefox 也推出了具备强大功能的 JS 引擎
- Firefox 3.5 TraceMonkey(对频繁执行的代码做了路径优化)
- Firefox 4.0 JeagerMonkey
JS 的逼格
- 解释性语言 --> 跨平台(不需要编译成文件)
- 单线程
- ECMA 标准 --> 为了取得技术优势, 微软推出了 JScript, CEnvi 推出 ScriptEase, 与 JavaScript 同样可在浏览器上运行, 为了统一规格 JavaScript 兼容于 ECMA 标准, 因此也称为 ECMAScript
ECMAScript
数据类型
-
基本数据类型(值类型)
- Number
- String
- Boolean
- Null
- Undefined
- Symbol (ES 6 新增)
-
复杂数据类型(引用数据类型、引用值、堆数据)
- Array
- Function
- Object
typeof 六种数据类型
- number (包括 NaN, 非数也是数!!)
- string
- boolean
- undefined
- function
- object (包括 Array、Null、Date、RegExp 等)
注意:
- typeof(null) == 'object' 是因为历史遗留问题, 早期 Null 是为对象占位使用的, 所以当时认为 Null 也是一个 Object 类型
- typeof 是 ES 5 中唯一一个使用未声明变量不会报错的运算符
类型转换
显示类型转换
- Number(mix)
- parseInt(string, radix)
- parseFloat(string)
- toString(radix)
- String(mix)
- Boolean()
隐式类型转换
- isNaN ()
- ++/— +/-(一元正负)
- +
- */%
- && || !
- < > <= >=
- == !=
不发生类型转换
- ===
- !==
let 和 const
let 是 var 更严谨的升级版
- var 的作用域为函数, 并且会变量提升
- let 的作用域是最近的块级作用域(被
{}
包裹的区域) 中, 且不会变量提升
const: 声明一个只读的常量, 一旦声明, 常量的值就不能改变, 且必须在声明时初始化变量
通过 let、const 声明的变量, 在同一作用域内就不再允许重复声明
注意: const 并非完全意义上的常量, 如果 const 的值是一个引用类型, 只要不修改该变量所指向的引用地址, 对该引用地址内的属性做修改是允许的
const foo = {
name: 'zjh',
age: 18,
wife: ['a', 'b', 'c']
}
foo.age = 20 // 允许
foo.wife = ['a'] // 允许
foo = { // 不允许
name: 'zhangsan',
age: 18
}
预编译
预编译前奏
- 暗示全局变量(imply global)
- 即任何变量, 如果有变量未经声明就赋值, 此变量就为全局对象(window)所有
- 一切声明的全局变量, 全是 window 的属性
预编译四部曲
- 创建 AO 对象
- 找形参和变量声明, 将变量声明作为 AO 属性名, 值为 undefined
- 将实参和形参统一
- 在函数体里面找函数声明, 将函数名作为 AO 属性名, 值为该函数的函数体(如果函数名与形参或变量重复, 则覆盖)
function fn(a) {
var a = 123
function a() {}
var b = function () {}
function d() {}
}
fn(1)
// 1. 创建 AO 对象 AO : {}
/* 2. 形参、变量声明
AO: {
a: undefined,
b: undefined
}
*/
/* 3. 实参和形参相统一
AO: {
a: 1,
b: undefined
}
*/
/* 4. 函数声明提升
AO: {
a: function () {},
b: undefined,
d: function () {}
}
*/
/* 函数体代码执行完毕
AO: {
a : 123
b: function () {},
d: function () {}
}
*/
作用域
作用域定义: 变量(变量作用域又称上下文)和函数生效(能被访问)的区域
作用域分为: 全局作用域和局部作用域
作用域的访问顺序为就近原则, 即访问变量时, 优先寻找离自身最近的作用域
作用域链
[[scope]]: 指的就是我们所说的作用域, 其中存储了运行期上下文的集合
- 每个 JS 函数都是一个对象, 对象中有些属性我们可以访问, 但有些不可以, 这些属性仅供 JS 引擎存取, [[scope]] 就是其中一个
作用域链: [[scope]] 中所存储的执行期上下文对象的集合, 这个集合呈链式链接, 我们把这种链式链接叫做作用域链
运行期上下文: 当函数执行时, 会创建一个称为执行期上下文的内部对象
- 一个执行期上下文定义了一个函数执行时的环境, 函数每次执行时对应的执行上下文都是独一无二的, 所以多次调用一个函数会导致创建多个执行期上下文
- 当函数执行完毕, 执行上下文被销毁
查找变量: 从作用域链的顶端依次向下查找(就近原则)
function a() {
function b() {
var b = 234
}
var a = 123
b()
}
var glob = 100
a()
闭包
当内部函数被保存到外部时, 将会产生闭包
function a() {
function b() {
var bbb = 234
document.write(aaa) // 123
}
var aaa = 123
return b
}
var glob = 100
var demo = a()
demo()
闭包的用途
- 实现公有变量
- 缓存
- 实现封装, 属性私有化
闭包的缺点
- 闭包会导致原有作用域链不释放, 造成内存泄漏
闭包的防范
闭包会导致多个执行函数公用一个公有变量, 如果不是特殊需要, 应尽量防止这种情况发生
立即执行函数
定义: 此类函数没有声明, 在一次执行过后释放, 适合做初始化工作
(function () {
console.log('这就是立即执行函数, W3C 更推荐我这种写法')
}())
(function () {
console.log('这也是立即执行函数')
})()
如果一个函数以表达式的形势出现, 那么该函数就具备通过函数定义 + ()
执行的能力, 即立即执行函数
/*
() 是运算符
给函数加上 () 之后就变成了函数表达式
此时函数表达式就可以通过 () 直接调用
*/
// (function () {})
// () 调用
// 最终为 (function () {}()) 或 (function () {})()
// ! + - ~ = 都可以
!function () { console.log('!') }()
+function () { console.log('+') }()
-function () { console.log('-') }()
~function () { console.log('~') }()
let a = function () { console.log('=')} ()
// 巧妙的方法: void、new
void function () { console.log('void') }()
new function () { console.log('new') }()
立即执行函数的作用
- 不必为函数命名, 避免了污染全局变量
- 立即执行函数内部形成了一个单独的作用域, 可以封装一些外部无法读取的私有变量
- 封装变量
总而言之: 立即执行函数会形成一个单独的作用域, 我们可以封装一些临时变量或者局部变量, 避免污染全局变量
应用场景
- 1
- 2
- 3
对象
构造函数内部原理
- 在函数体最前面隐式的加上
this = {}
- 执行
this.xxx = xxx
- 隐式的返回 this
return this
包装类
- String()
- Boolean()
- Number()
var a = 123
// new Number(a).toString()
console.log(a.toString()) // '123'
// new Number(a).name = 'foo'
// 接着便销毁该对象
console.log(a.name = 'foo') // 'foo'
// new Number(a).name ==> undefined
console.log(a.name) // 'undefined'
原型
- 定义: 原型是 function 对象的一个属性, 它定义了构造函数制造出的对象的公共祖先, 通过该构造函数产生的对象, 可以继承该原型的属性和方法, 原型也是对象
- 利用原型的特点和概念, 可以提取共有属性
- 对象属性的增删改只能作用在对象本身, 但是可以查到原型上的属性, 要增删改原型的属性, 需要通过原型对象进行修改
- 对象查看原型 --> 隐式属性
__proto__
- 对象查看构造函数 -->
constructor
原型链
每个对象都可以有一个原型__proto__
, 这个原型还可以有它自己的原型, 以此类推, 形成一个原型链。
查找属性的时候, 先去这个对象里去找, 如果没有的话就去它的原型对象里面去找, 如果还是没有的话再去向原型对象的原型对象里去寻找...
这个操作被委托在整个原型链上, 这就是原型链
- 原型链中的某个方法被实例调用了, 那么 this 指向谁?
- 谁调用方法, 内部 this 就是谁
- 对象最终都会继承自
Object.prototype
hasOwnProperty
判断属性是否为实例对象所有(非原型属性)
Person.prototype.age = 18
function Person() {
this.name = 'zjh'
}
let person = new Person()
console.log(person.hasOwnProperty('name')) // true
console.log(person.hasOwnProperty('age')) // false
instanceof
判断 a 的原型链是否存在 b
function Father() {}
Son.prototype = new Father()
function Son() {}
let son = new Son()
console.log(son instanceof Father) // true
this
- 函数预编译过程 this 指向 window
- 全局作用域中的 this 指向 window
- 通过 new 关键字执行的函数, this 指向该自身实例
- obj.func() func 里面的 this 指向 obj
- 严格模式下, 函数预编译过程 this 指向 undefined
ES 6 箭头函数
this 指向定义该函数时的父级 this
const obj = {
name: 'zjh',
say1: () => {
console.log('this 指向 obj')
console.log(this.name)
},
say2: function () {
console.log('this 指向 window')
console.log('当外界通过 obj.say2 调用该函数时 this 指向 obj')
}
}
- 当参数只有一个时, 可以省略括号
a => { console.log(a) }
- 当函数体只有一条语句时(将这一条语句的结果当作返回值 return), 可以省略花括号
() => console.log('ok')
- 没有 arguments
call / apply / bind
作用: 改变 this 指向
call 和 apply 的区别: 后面传的参数形式不同
- call 传参为逐个传递, 多个实参用逗号分割
- apply 传参为数组传递, 将参数放在数组中, 并将数组作为实参传递
bind 和 call、apply 的区别: bind 返回函数体, call、apply 自动执行函数
bind 传参同 call (也可以调用时当作普通函数形式传参)
let obj = {
name: 'zjh',
say: function (age, height) {
console.log(this.name, age, height)
}
}
let me = {
name: 'zhangsan'
}
obj.say.call(me, 18, 1.88)
obj.say.apply(me, [19, 1.86])
obj.say.bind(me, 20, 1.84)()
obj.say.bind(me)(21, 1.82)
圣杯模式
const inherit = (function () {
const F = function () {}
return function (Target, Origin) {
F.prototype = Origin.prototype
Target.prototype = new F()
Target.prototype.constuctor = Target
Target.prototype.uber = Origin.prototype
}
}())
数组
数组定义
new Array(length/content)
- 字面量
数组的读与写
-
arr[num]
不可以溢出读, 返回 undefined -
arr[num] = xxx
可以溢出写
改变原数组的方法
- push
- pop
- unshift
- shift
- sort
- splice
- reverse
不改变原数组的方法
- forEach
- filter
- map
- reduce
- reduceRight
- slice
- concat
- join
- split
- toString
filter 过滤函数
return true
在新数组中 push 此次的 value
return false
函数过滤掉此次的 value
let arr1 = [1, 2, 3, 4, 5]
let arr2 = arr1.filter((value, index) => value >= 3)
console.log(arr2) // [3, 4, 5]
map
在新数组中 push 此次的返回值
let arr1 = [1, 2, 3, 4, 5]
let arr2 = arr1.map((value, index) => value >= 3 ? value : 0)
console.log(arr2) // [0, 0, 3, 4, 5]
reduce
- firstPreValue: 第一次循环时, preValue 的值
- 如果省略该参数, 则默认值为
Array[0]
, 且循环从Array[1]
开始 - 如果填入该参数, 则循环从
Array[0]
开始
- 如果省略该参数, 则默认值为
- lastPreValue: 最后一次循环时, return 的值
let lastPreValue = Array.reduce((preValue, curValue, index) => {
// preValue: 上次return的值
// curValue: 当前Array[index]的值
// return: 作为下一次循环, preValue的值
}, firstPreValue)
// 例子:
let arr1 = [1, 2, 3, 4, 5]
let lpv1 = arr1.reduce((preValue, curValue, index) => {
/*
1 2
2 3
3 4
4 5
*/
console.log(preValue, curValue)
return curValue
})
console.log(lpv1) // 5
let lpv2 = arr1.reduce((preValue, curValue, index) => {
/*
0 1
1 2
2 3
3 4
4 5
*/
console.log(preValue, curValue)
return curValue
}, 0)
console.log(lpv2) // 5
类数组
- 可以利用属性名模拟数组的特性
- 可以动态曾长 length 属性
- 如果强行让类数组调用 push 方法, 则会根据 length 属性值的位置进行属性的扩充
try...catch
try {
setTimeout(() => {
console.lo('你以为我写错了? 其实我是故意的')
}, 1000)
} catch(err) {
console.log(err)
} finally {
console.log('不管成不成功, 我都会被执行')
}
Error.name
的六种值对应信息
- EvalError: eval() 的使用与定义不一致
- RangeError: 数组越界
- ReferenceError: 非法或不能识别的引用数值
- SyntaxError: 发生语法解析错误
- TypeError: 操作数类型错误
- URLError: URL 处理函数使用不当
ES 5 严格模式
"use strict"
- 不再兼容 ES 3 的一些不规则语法, 使用全新的 ES 5 规范
- 两种用法
- 全局严格模式(在代码最前书写
"use strict"
) - 局部函数内严格模式(推荐)(在函数体最前书写
"use strict"
)
- 全局严格模式(在代码最前书写
- 就是一行字符串, 不会对不兼容严格模式的浏览器产生影响
- 不支持 with, arguments.callee, func.caller
- 变量赋值前必须声明, 局部 this 必须被赋值(Person.call(null/undefined) 赋值什么就是什么), 拒绝重复属性和参数
DOM (Document Object Model)
DOM 定义了表示和修改文档所需的对象、这些对象的行为和属性以及这些对象之间的关系
DOM对象即为宿主对象, 由浏览器厂商定义, 用来操作 HTML 和 CSS 功能的一类对象的集合
也有人称 DOM 是对 HTML 以及 XML 的标准编程接口
DOM 基本操作
查找元素节点
document 代表整个文档
-
document.getElementById()
通过 id 查找元素, 在 IE 8 以下的浏览器, 不区分 id 大小写, 而且也返回匹配 name 属性的元素 -
document.getElementsByTagName()
通过标签名查找元素 -
document.getElementsByName()
IE不支持需注意, 只有部分标签 name 可生效(表单, 表单元素, img, iframe) -
document.getElementsByClassName()
通过 class 查找元素, IE 8 和 IE 8 以下的 IE 版本中没有, 可以多个 class 一起 -
document.querySelector()
CSS选择器, 在 IE 7 和 IE 7 以下的版本中没有 -
document.querySelectorAll()
CSS选择器, 在 IE 7 和 IE 7 以下的版本中没有
遍历节点树
- parentNode: 父节点(最顶端的 parentNode 为 #document)
- childNodes: 子节点们
- firstChild: 第一个子节点
- lastChild: 最后一个子节点
- nextSibling: 后一个兄弟节点
- previousSibling: 前一个兄弟节点
基于元素节点树的遍历
- parentElement: 返回当前元素的父元素节点 (IE 不兼容)
- children: 只返回当前元素的元素子节点
-
node.childElementCount === node.children.length
当前元素节点的子元素节点个数(IE 不兼容) - firstElementChild: 返回的是第一个元素节点(IE 不兼容)
- lastElementChild: 返回的是最后一个元素节点(IE 不兼容)
- nextElementSibling / previousElementSibling: 返回后一个/前一个兄弟元素节点(IE 不兼容)
节点类型
- 元素节点: 1
- 属性节点: 2
- 文本节点: 3
- 注释节点: 8
- document: 9
- DocumentFragment: 11
获取节点类型nodeType
节点的四个属性一个方法
-
nodeName
- 元素的标签名, 以大写形式表示, 只读
-
nodeValue
- Text 节点或 Comment 节点的文本内容, 可读写
-
nodeType
- 该节点的类型, 只读
-
attributes
- Element 节点的属性集合
-
Node.hasChildNodes()
- 方法返回一个布尔值, 表明当前节点是否包含有子节点
创建 DOM 元素
-
document.createElement()
创建元素节点 -
document.createTextNode()
创建文本节点 -
document.createComment()
创建注释节点 -
document.createDocumentFragment()
创建文档碎片节点
插入 DOM 元素
-
PARENTNODE.appendChild()
往父元素 PARENTNODE 最后的位置插入节点 -
PARENTNODE.insertBefore(a, b)
往父元素 PARENTNODE 中 b 节点前插入 a 节点
删除 DOM 元素
-
PARENTNODE.removeChild()
删除父元素 PARENTNODE 下的某个子节点
替换 DOM 元素
PARENTNODE.replaceChild(new, origin)
Element 节点的属性和方法
- 属性
innerHTML
-
innerText
(Firefox 不兼容) -
textContent
(老版本 IE 不兼容)
- 方法
-
Element.setAttribute()
设置元素行间属性 -
Element.getAttribute()
获得元素行间属性
-
DOM 接口
-
getElementById
方法定义在Document.prototype
上, 即 Element 节点上不能使用 -
getElementsByName
方法定义在HTMLDocument.prototype
上, 即非 HTML 中的 document 以外不能使用(xml document, Element) -
getElementsByTagName
方法定义在Document.prototype
和Element.prototype
上 -
HTMLDocument.prototype
定义了一些常用的属性, body、head 分别指代 HTML 文档中的、
标签 -
Document.prototype
上定义了documentElement
属性, 指代文档的根元素, 在 HTML 文档中, 它总是指代元素
-
getElementsByClassName、querySelectAll、querySelector
在 Document、Element 类中均有定义
定时器
-
setInterval(function, timeout)
- 按照指定的周期(以毫秒计)来调用函数或计算表达式
- 方法会不停地调用函数, 直到
clearInterval()
被调用或窗口被关闭
-
setTimeout(function, timeout)
- 在指定的毫秒数后调用函数或计算表达式
- 只执行一次
-
clearInterval(定时器对象)
清除 setInterval 定时器 -
clearTimeout(定时器对象)
清除 setTimeout 定时器 - 参数 function 也可以是用引号包裹的代码段, 一般使用函数
- 以上方法均属于 window 上的方法, 参数 function 的 this 指向 window
setTimeout() 时间参数设置为 0 的探讨
var arr = [1, 2, 3]
for(var i in arr){
setTimeout(function() {
console.log(arr[i])
}, 0)
console.log(arr[i])
}
chrome 打印结果如下
虽然 setTimeout 函数在每次循环的开始就调用了, 但是却被放到循环结束才执行, 循环结束, i = 3, 接连打印了3次3
这里涉及到 JS 单线程执行的问题: JS 在浏览器中是单线程执行的, 必须在完成当前任务后才执行队列中的下一个任务
另外, 对于 JS 还维护着一个setTimeout 队列, 未执行的setTimeout 任务就按出现的顺序放到 setTimeout 队列, 等待普通的任务队列中的任务执行完才开始按顺序执行积累在 setTimeout 中的任务
所以在这个问题里, 会先打印1 2 3, 而将 setTimeout 任务放到 setTimeout 任务队列, 等循环中的打印任务执行完了, 才开始执行setTimeout队列中的函数, 所以在最后会接着打印 3 次 3
事件
绑定事件的方法
-
Element.onXXX = function (event) {}
- 兼容性好, 但是一个事件只能绑定一个处理函数
- 基本等同于写在 HTML 行间上
-
Element.addEventListener(type, fn, false)
- 事件类型, 回调函数, true: 捕获阶段执行 | false: 冒泡阶段执行
- IE 9 以下不兼容, 可以为一个事件绑定多个处理函数
-
Element.attachEvent('on' + type, fn)
- IE 独有, 一个事件同样可以绑定多个处理函数
事件处理函数的运行环境
-
Element.onXXX = function (event) {}
- 回调函数 this 指向 DOM 元素本身
-
Element.addEventListener(type, fn, false)
- 回调函数 this 指向 DOM 元素本身
-
Element.attachEvent('on' + type, fn)
- 回调函数 this 指向 window
解除事件处理函数
Element.onclick = false / '' / null
-
Element.removeEventListener(type, fn, false)
- fn 为回调函数的引用地址
-
Element.detachEvent('on' + type, fn)
- fn 为回调函数的引用地址
注意: 若绑定匿名函数, 则除了第一种绑定方式外, 其他绑定方式无法解除
事件处理模型 - 事件冒泡、捕获
事件冒泡
结构上(非视觉上)嵌套关系的元素, 会存在事件冒泡的功能, 即同一事件, 自子元素冒泡向父元素(自底向上)
child
事件捕获
结构上(非视觉上)嵌套关系的元素, 会存在事件捕获的功能, 即同一事件, 自父元素捕获至子元素(事件源元素)(自顶向下)
child
冒泡和捕获的触发顺序
触发顺序为: 先捕获后冒泡
哪些事件不冒泡
- focus
- blur
- change
- submit
- reset
- select
取消冒泡和阻止默认事件
取消冒泡
-
event.stopPropagation()
- W3C 标准
- IE 9 以下不兼容
-
event.cancelBubble = true
- IE 独有
阻止默认事件
默认事件: 表单提交、a 标签跳转、右键菜单等
-
return false
- 以对象属性的方式注册的事件才生效
-
event.preventDefault()
- W3C 标准
- IE 9 以下不兼容
-
event.returnValue = false
- 兼容 IE
事件对象
event
-
window.event
用于 IE
事件源对象
-
event.target
firefox 独有 -
event.srcElement
IE 独有 - 以上 chrome 都有
事件委托(事件代理)
事件委托原理: 利用事件冒泡和事件源对象处理子元素事件
- 1
- 2
- 3
优点
- 性能
- 不需要循环所有子元素一个个绑定事件
- 灵活
- 当有新的子元素插入时, 不需要重新为子元素绑定事件
事件分类
鼠标事件
- click
- (mousedown + mouseup 鼠标左键点击事件)
- mousedown
- 鼠标按下事件
-
event.button
0 左键, 1 滚轮, 2 右键
- mouseup
- 鼠标左键抬起事件
-
event.button
0 左键, 1 滚轮, 2 右键
- contextmenu
- 右键产生菜单事件
- mouseover / mouseenter(HTML 5新增)
- 鼠标进入该区域事件
- mouseout / mouseleave(HTML 5新增)
- 鼠标离开该区域事件
- mousemove
- 鼠标经过事件
click 的触发比 mousedown、mouseup 慢, 它是两个事件的集合
DOM 3 标准规定: click 事件只能监听左键, 只能通过 mousedown 和 mouseup 来判断鼠标健
键盘事件
- keyup
- 键盘按键抬起
- keydown
- 键盘按键按下并一直触发(可以响应任意键盘按键, 但检测不准,不区分大小写)
- keypers
- 键盘按键按下并一直触发(只可以响应 ASCII 码按键, 根据 ASCII 码区分大小写)
执行顺序: keydown 快于 keyperss 快于 keyup
事件源上的 whice 属性表示按键的 unicode 编码
文本操作事件
- input
- 根据输入文本内容变化触发
- change
- 获得焦点和失去焦点的过程中文本内容发生变化则触发
- focus
- 获得焦点后触发
- blur
- 失去焦点后触发
window 事件
- scroll
- 滚动条事件
- load
- W3C标准
- 网页加载完毕事件
- 效率低(它要等所有 HTML, CSS 加载完毕后才触发)
JSON
JSON 是一种传输数据的格式(以对象为样板, 本质上就是对象, 但用途有区别, 对象就是本地用的, JSON 是用来传输的)
往后端发送数据的格式(构建一个对象, 属性名用双引号包裹)
-
JSON.parse()
- 将 string 转换为 json 格式
-
JSON.stringify()
- 将 json 格式转换为 string
异步加载 JS
JS 加载的缺点: 加载工具方法没必要阻塞文档, 过多 JS 加载会影响页面效率, 一旦网速不好, 那么整个网站将等待 JS 加载而不进行后续渲染等工作
JS 异步加载的三种方案
defer 异步加载, 但要等到 DOM 文档全部解析完(DOMTree 构建完成)才会被执行, 只能 IE 用, 也可以将 JS 代码写在
中
-
async 异步加载, 加载完就执行, async 只能加载外部脚本, 不能把 JS 代码写在
中(W3C标准)
- 单独写在标签行间属性中, 执行时不会阻塞页面
-
动态创建
, 插入到 DOM 中, 加载完毕后执行 callBack(回调函数)
loadScript('a.js', () => { console.log('a.js 加载完毕') }) function loadScript(url, callback) { const oScript = document.createElement('script') oScript.src = url oScript.onload = callback document.head.appendChild(oScript) }
JS 加载时间线
- 创建 Document 对象, 开始解析 web 页面, 这个阶段
document.readyState = 'loading'
- 遇到 link 外部 CSS, 创建线程加载, 并继续解析文档
- 遇到 script 外部 JS, 并且没有设置 async、defer, 浏览器加载, 并阻塞, 等待 JS 加载完成并执行该脚本, 然后继续解析文档
- 遇到 script 外部 JS, 并且设有 async、defer, 浏览器创建线程加载, 并继续解析文档, 对于 async 属性的脚本, 脚本加载完后立即执行(异步禁止使用
document.write()
, 它会把页面上的所有文档流清空, 并执行document.write()
中的内容) - 遇到 img 等, 先正常解析 DOM 结构, 然后浏览器异步加载 src, 并继续解析文档
- 当文档解析完成,
document.readyState = 'interactive'
- 当文档解析完成后, 所有设置有 defer 的脚本会按照顺序执行(注意与 async 的不同, 但同样禁止使用
document.write()
) - document 对象触发
DOMContentLoaded
事件(通过addEventListener
绑定事件触发), 这也标志着程序执行从同步脚本执行阶段, 转化为事件驱动阶段 - 当所有 async 的脚本加载完成并执行、img 等加载完成后,
document.readyState = 'complete'
, window 对象触发 load 事件 - 从此, 以异步响应方式处理用户输入、网络事件等
正则表达式 (RegExp)
正则表达式的作用: 匹配特殊字符或有特殊搭配原则的字符的最佳选择
创建方法
- 字面量
let a = /规则/修饰符
new RegExp("规则", "修饰符")
修饰符
- i : 忽略大小写
- g : 全局匹配(查找所有匹配而非在找到第一个匹配后停止)
- m : 多行匹配
使用字符串方法 .match("规则")
获得结果
表达式
- ^ : 从前往后匹配
- $ : 从后往前匹配
- [abc] : 查找方括号之间的任何字符
- [^abc] : 查找任何不在方括号之间的字符
- [0-9] : 查找任何从 0 至 9 的数字
- [a-z] : 查找任何从小写 a 至小写 z 的字符
- [A-Z] : 查找任何从大写 A 至大写 Z 的字符
- [A-z] : 查找任何从大写 A 至小写 z 的字符
- (red|blue|green) : 查找任何指定的选项
一个括号代表一位
元字符
- . : 查找单个字符, 除了换行和行结束符, [^\r\n]
- \w : 查找单词字符, [0-9A-z_]
- \W : 查找非单词字符, [^\w]
- \d : 查找数字, [0-9]
- \D : 查找非数字字符, [^\d]
- \s : 查找空白字符, (空格、换行、制表等)
- \S : 查找非空白字符, (^\s)
- \b : 匹配单词边界
- \B : 匹配非单词边界
- \n : 查找换行符
- \xxx : 查找以八进制数 xxx 规定的字符
- \xdd : 查找以十六进制数 dd 规定的字符
- \uxxxx : 查找以十六进制数 xxxx 规定的 Unicode 字符
量词(贪婪匹配)
- n+ : 匹配任何包含至少一个 n 的字符串
- n* : 匹配任何包含零个或多个 n 的字符串
- n? : 匹配任何包含零个或一个 n 的字符串
- n{X} : 匹配包含 X 个 n 的序列的字符串
- n{X,Y} : 匹配包含 X 至 Y 个 n 的序列的字符串
- n{X,} : 匹配包含至少 X 个 n 的序列的字符串
- n$ : 匹配任何结尾为 n 的字符串
- ^n : 匹配任何开头为 n 的字符串
- ?=n : 匹配任何其后紧接指定字符串 n 的字符串
- ?!n : 匹配任何其后没有紧接指定字符串 n 的字符串
非贪婪匹配则在表达式后面加一个 ?
var str = "aaaaaaa"
var reg = /a+?/g
子表达式
- (\w) : 子表达式 \w
- \n : 反向引用第 n 个表达式的内容
- 例如:
(\w)\1 == \w\w
- 例如:
RegExp对象属性
lastIndex
: 游标(加修饰符 g, 则每次匹配后游标都会变化)
RegExp对象方法
-
exec(字符串)
: 根据游标作为开始的位置进行匹配
支持正则表达式的 String 对象的方法
-
match(正则表达式)
: 找到一个或多个正则表达式的匹配 -
search(正则表达式)
: 返回第一次匹配成功的位置, -1 为匹配失败 -
split(正则表达式)
: 根据正则表达式把字符串分割成字符串数组 -
replace(正则表达式, "b")
: 把匹配正则表达式的字符串更改成 b, 如果不加 g 则只改第一个匹配到的, 加了 g 则更改所有- 第二个参数可以用 $1 来表示第一个子表达式的内容, $2 依次类推
正向预查和方向预查
// 正向预查
var str = "abaaaaa"
var reg = /a(?=b)/g
// 结果为第一个a
// 反向预查
var str = "abaaaaa"
var reg = /a(?!b)/g
// 结果为从 a 的结尾不是 b 的所有 a
Doctype
渲染模式
在多年以前(IE6诞生以前), 各浏览器都处于各自比较封闭的发展中(基本没有兼容性可谈)
随着 WEB 的发展, 兼容性问题的解决越来越显得迫切, 随即, 各浏览器厂商发布了按照标准模式(遵循各厂商制定的统一标准)工作的浏览器, 比如 IE6 就是其中之一
但是考虑到以前建设的网站并不支持标准模式, 所以各浏览器在加入标准模式的同时也保留了混杂模式(即以前那种未按照统一标准工作的模式, 也叫怪异模式)
三种标准模式的写法
写法一
写法二
写法三