JavaScript 笔记

JavaScript 历史

  • JavaScript 作为 Netscape Navigator 浏览器的一部分首次出现在 1996 年, 它最初的设计目标是改善网页的用户体验

  • 期初 JavaScript 被命名为 LiveScript, 后因和 Sun 公司合作, 因市场宣传需要改名 JavaScript, 后来 Sun 公司被 Oracle 收购, JavaScript 版权归 Oracle 所有。

注: JavaScript 简称 JS, 以下将使用简称

浏览器组成部分

  1. shell 部分
  2. 内核部分
    • 渲染引擎(语法规则和渲染)
    • 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 等)

注意:

  1. typeof(null) == 'object' 是因为历史遗留问题, 早期 Null 是为对象占位使用的, 所以当时认为 Null 也是一个 Object 类型
  2. 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
}

预编译

预编译前奏

  1. 暗示全局变量(imply global)
    • 即任何变量, 如果有变量未经声明就赋值, 此变量就为全局对象(window)所有
  2. 一切声明的全局变量, 全是 window 的属性

预编译四部曲

  1. 创建 AO 对象
  2. 找形参和变量声明, 将变量声明作为 AO 属性名, 值为 undefined
  3. 将实参和形参统一
  4. 在函数体里面找函数声明, 将函数名作为 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()
image

image

image

image

闭包

当内部函数被保存到外部时, 将会产生闭包

function a() {
  function b() {
    var bbb = 234
    document.write(aaa) // 123
  }
  var aaa = 123
  return b
}

var glob = 100
var demo = a()
demo()
image

闭包的用途

  • 实现公有变量
  • 缓存
  • 实现封装, 属性私有化

闭包的缺点

  • 闭包会导致原有作用域链不释放, 造成内存泄漏

闭包的防范

闭包会导致多个执行函数公用一个公有变量, 如果不是特殊需要, 应尽量防止这种情况发生

立即执行函数

定义: 此类函数没有声明, 在一次执行过后释放, 适合做初始化工作

(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. 封装变量

总而言之: 立即执行函数会形成一个单独的作用域, 我们可以封装一些临时变量或者局部变量, 避免污染全局变量

应用场景

  • 1
  • 2
  • 3

对象

构造函数内部原理

  1. 在函数体最前面隐式的加上this = {}
  2. 执行this.xxx = xxx
  3. 隐式的返回 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'

原型

  1. 定义: 原型是 function 对象的一个属性, 它定义了构造函数制造出的对象的公共祖先, 通过该构造函数产生的对象, 可以继承该原型的属性和方法, 原型也是对象
  2. 利用原型的特点和概念, 可以提取共有属性
  3. 对象属性的增删改只能作用在对象本身, 但是可以查到原型上的属性, 要增删改原型的属性, 需要通过原型对象进行修改
  4. 对象查看原型 --> 隐式属性__proto__
  5. 对象查看构造函数 --> 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

  1. 函数预编译过程 this 指向 window
  2. 全局作用域中的 this 指向 window
  3. 通过 new 关键字执行的函数, this 指向该自身实例
  4. obj.func() func 里面的 this 指向 obj
  5. 严格模式下, 函数预编译过程 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

类数组

  1. 可以利用属性名模拟数组的特性
  2. 可以动态曾长 length 属性
  3. 如果强行让类数组调用 push 方法, 则会根据 length 属性值的位置进行属性的扩充

try...catch

try {
  setTimeout(() => {
    console.lo('你以为我写错了? 其实我是故意的')
  }, 1000)
} catch(err) {
  console.log(err)
} finally {
  console.log('不管成不成功, 我都会被执行')
}

Error.name 的六种值对应信息

  1. EvalError: eval() 的使用与定义不一致
  2. RangeError: 数组越界
  3. ReferenceError: 非法或不能识别的引用数值
  4. SyntaxError: 发生语法解析错误
  5. TypeError: 操作数类型错误
  6. 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 接口

image
  1. getElementById方法定义在Document.prototype上, 即 Element 节点上不能使用
  2. getElementsByName方法定义在HTMLDocument.prototype上, 即非 HTML 中的 document 以外不能使用(xml document, Element)
  3. getElementsByTagName方法定义在Document.prototypeElement.prototype
  4. HTMLDocument.prototype定义了一些常用的属性, body、head 分别指代 HTML 文档中的标签
  5. Document.prototype上定义了documentElement属性, 指代文档的根元素, 在 HTML 文档中, 它总是指代元素
  6. 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 打印结果如下

image

虽然 setTimeout 函数在每次循环的开始就调用了, 但是却被放到循环结束才执行, 循环结束, i = 3, 接连打印了3次3

这里涉及到 JS 单线程执行的问题: JS 在浏览器中是单线程执行的, 必须在完成当前任务后才执行队列中的下一个任务

另外, 对于 JS 还维护着一个setTimeout 队列, 未执行的setTimeout 任务就按出现的顺序放到 setTimeout 队列, 等待普通的任务队列中的任务执行完才开始按顺序执行积累在 setTimeout 中的任务

所以在这个问题里, 会先打印1 2 3, 而将 setTimeout 任务放到 setTimeout 任务队列, 等循环中的打印任务执行完了, 才开始执行setTimeout队列中的函数, 所以在最后会接着打印 3 次 3

事件

绑定事件的方法

  1. Element.onXXX = function (event) {}
    • 兼容性好, 但是一个事件只能绑定一个处理函数
    • 基本等同于写在 HTML 行间上
  2. Element.addEventListener(type, fn, false)
    • 事件类型, 回调函数, true: 捕获阶段执行 | false: 冒泡阶段执行
    • IE 9 以下不兼容, 可以为一个事件绑定多个处理函数
  3. Element.attachEvent('on' + type, fn)
    • IE 独有, 一个事件同样可以绑定多个处理函数

事件处理函数的运行环境

  1. Element.onXXX = function (event) {}
    • 回调函数 this 指向 DOM 元素本身
  2. Element.addEventListener(type, fn, false)
    • 回调函数 this 指向 DOM 元素本身
  3. Element.attachEvent('on' + type, fn)
    • 回调函数 this 指向 window

解除事件处理函数

  1. Element.onclick = false / '' / null
  2. Element.removeEventListener(type, fn, false)
    • fn 为回调函数的引用地址
  3. Element.detachEvent('on' + type, fn)
    • fn 为回调函数的引用地址

注意: 若绑定匿名函数, 则除了第一种绑定方式外, 其他绑定方式无法解除

事件处理模型 - 事件冒泡、捕获

事件冒泡

结构上(非视觉上)嵌套关系的元素, 会存在事件冒泡的功能, 即同一事件, 自子元素冒泡向父元素(自底向上)

child

事件捕获

结构上(非视觉上)嵌套关系的元素, 会存在事件捕获的功能, 即同一事件, 自父元素捕获至子元素(事件源元素)(自顶向下)

child

冒泡和捕获的触发顺序

image

触发顺序为: 先捕获后冒泡

哪些事件不冒泡

  • 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

优点

  1. 性能
    • 不需要循环所有子元素一个个绑定事件
  2. 灵活
    • 当有新的子元素插入时, 不需要重新为子元素绑定事件

事件分类

鼠标事件

  • 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 异步加载的三种方案

  1. defer 异步加载, 但要等到 DOM 文档全部解析完(DOMTree 构建完成)才会被执行, 只能 IE 用, 也可以将 JS 代码写在

  2. async 异步加载, 加载完就执行, async 只能加载外部脚本, 不能把 JS 代码写在中(W3C标准)

    • 单独写在标签行间属性中, 执行时不会阻塞页面
  3. 动态创建, 插入到 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 加载时间线

  1. 创建 Document 对象, 开始解析 web 页面, 这个阶段 document.readyState = 'loading'
  2. 遇到 link 外部 CSS, 创建线程加载, 并继续解析文档
  3. 遇到 script 外部 JS, 并且没有设置 async、defer, 浏览器加载, 并阻塞, 等待 JS 加载完成并执行该脚本, 然后继续解析文档
  4. 遇到 script 外部 JS, 并且设有 async、defer, 浏览器创建线程加载, 并继续解析文档, 对于 async 属性的脚本, 脚本加载完后立即执行(异步禁止使用document.write(), 它会把页面上的所有文档流清空, 并执行document.write()中的内容)
  5. 遇到 img 等, 先正常解析 DOM 结构, 然后浏览器异步加载 src, 并继续解析文档
  6. 当文档解析完成, document.readyState = 'interactive'
  7. 当文档解析完成后, 所有设置有 defer 的脚本会按照顺序执行(注意与 async 的不同, 但同样禁止使用document.write())
  8. document 对象触发DOMContentLoaded事件(通过addEventListener绑定事件触发), 这也标志着程序执行从同步脚本执行阶段, 转化为事件驱动阶段
  9. 当所有 async 的脚本加载完成并执行、img 等加载完成后, document.readyState = 'complete', window 对象触发 load 事件
  10. 从此, 以异步响应方式处理用户输入、网络事件等

正则表达式 (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 就是其中之一
但是考虑到以前建设的网站并不支持标准模式, 所以各浏览器在加入标准模式的同时也保留了混杂模式(即以前那种未按照统一标准工作的模式, 也叫怪异模式)

三种标准模式的写法

写法一


写法二


写法三


你可能感兴趣的:(JavaScript 笔记)