JavaScript 是一种运行在客户端(浏览器)的编程语言,实现人机互动效果:
- 网页特效(监听用户的某些行为并令网页进行反馈)
- 表单验证(针对表单数据的合法性进行判断)
- 数据交互(获取后台数据并渲染到前端)
JavaScript 组成
- ECMAScript:基础语法核心
- Web APIs:DOM(页面文档对象模型)、BOM(浏览器对象模型)
JavaScript 引入方式
- 内部 JavaScript:代码写在 html 文件中,用 script 标签包裹,规范 script 标签写在 上方
- 外部 JavaScript:代码写在 JavaScript 文件中,通过 script 标签引入到 html 页面
- 内联 JavaScript:代码写在标签内部
JavaScript 注释
// 单行注释/*
多行
注释
*/
JavaScript 执行顺序
自上而下,但对话框相关语句会优先于页面渲染语句
- 1输入输出
- 2变量
- 3常量
- 4数据类型
- 5运算符
- 6分支结构
- 7循环结构
- 8数组
- 9函数
- 10匿名函数
- 11对象-“类”
- 12Web APIs-DOM获取元素
- 13Web APIs-DOM事件基础
- 14Web APIs-DOM事件进阶
- 15Web APIs-DOM节点操作
- 16Web APIs-BOM操作浏览器
- 17Web APIs-正则表达式
- 18作用域
- 19函数进阶
- 20深入对象
- 21构造函数
- 22内置构造函数
- 23编程思想
- 24原型prototype
- 25深浅拷贝
- 26异常处理
- 27this
- 28性能优化
1输入输出
输出
- 文档输出语句:向 body 内输出内容,若输出内容为标签,则会被解析成网页元素
document.write('文档输出语句')
- 对话框输出语句:页面弹出对话框输出内容
alert('对话框输出语句')
- 控制台输出语句:向控制台输出内容,调试使用
console.log('控制台输出语句')
输入
- 对话框输入语句:显示对话框,其中存在输入框
prompt('对话框输入语句')
2变量
变量本质是程序在内存中申请的一块用以存储数据的空间
变量组成:下划线、字母、数字、$,不能以数字开头,区分大小写
let age // 声明变量,声明关键字 变量名
age = 22 // 变量赋值,赋值号 =let height = 180 // 变量声明同时赋值,即变量初始化
height = 181 // 变量更新let age = 18, height = 180 // 多个变量可以同时声明
var 声明的问题:可以先使用后声明、可以重复声明等,let 声明的出现解决 var 声明的问题
作用域:
- 全局作用域:作用于所有代码执行的环境
- 局部作用域:作用于某一代码块环境
注意:函数内部无声明直接赋值的变量可被当作全局变量,形参视作局部变量,局部变量优先作用
3常量
永远不会改变的变量,不允许再赋值
const G = 9.8 // 常量声明,声明时必须赋值
变量声明,const 优先,需修改则变 let,引用数据类型建议使用 const
4数据类型
- 基本数据类型:number(数字型)、string(字符串型)、boolean(布尔型)、undefined(未定义型)、null(空类型),存储数据值,存放至栈
- 引用数据类型:object(对象),存储数据地址,存放至堆
数据类型 | 值 | 说明 |
---|---|---|
number(数字型) | 整数、小数、NAN 等数据 | NAN,代表计算错误,任何针对 NAN 的操作都会返回 NAN |
string(字符串型) | 单引号、双引号、反引号包裹的数据 | 拼接运算符(+),反引号模板字符串实现拼接(`我今年${age}岁`) |
boolean(布尔型) | 固定值:true、false | true 代表真,false 代表假 |
undefined(未定义型) | 固定值:undefined | 变量声明不赋值默认 undefined |
null(空类型) | 固定值:null | 代表值为空,可代表未创建的对象 |
数据类型检测
typeof x // typeof 关键字
typeof(x) // typeof 函数
数据转换
- 隐式转换(自动):加号两边都有数据,若存在字符串则整体为字符串类型,否则为数字类型,其余算术运算符会将数据自动转换为数字类型
console.log(+'123') // 字符串类型隐式转换为数字类型
- 显式转换(强制)
Number('123') // 字符串类型显式转换为数字类型,非数字字符串转成 NAN
parseInt('1.23px') // 仅保留整数,开头非数字转成 NAN
parseFloat('1.23px') // 小数同样保留,开头非数字转成 NAN
Boolean('pink') // 字符串类型转换成布尔类型,''、0、undefined、null、false、NAN 转换为 false,其余为 true
5运算符
赋值运算符
符号 | = | += | -= | *= | /= | %= |
---|---|---|---|---|---|---|
说明 | 赋值运算符 | 加且赋值运算符 | 减且赋值运算符 | 乘且赋值运算符 | 除且赋值运算符 | 取模且赋值运算符 |
一元运算符
符号 | ++ | -- |
---|---|---|
说明 | 自增 | 自减 |
前置自增、后置自增一般情况下显示效果相同,但参与运算存在区别,前置自增先自增再参与运算,后置自增初始值参与运算最后自增
比较运算符
符号 | > | < | >= | <= | == | === | != | !== |
---|---|---|---|---|---|---|---|---|
说明 | 大于 | 小于 | 大于等于 | 小于等于 | 值相等 | 值和类型全相等 | 值不相等 | 值和类型不全等 |
console.log(2 == '2') // 字符串类型隐式转换为数字类型,true
console.log(2 === '2') // 类型不等,false
console.log(undefined == null) // true
console.log(undefined === null) // false
console.log(NAN === NAN) // false
字符串比较,实际比较 ASCII 码,顺序比较,长串大
小数比较会存在精度问题
算术运算符
符号 | + | - | * | / | % |
---|---|---|---|---|---|
说明 | 求和 | 求差 | 求积 | 求商 | 取模(取余数) |
运算顺序:从左至右,先乘除取模后加减,括号最优先
逻辑运算符
符号 | && | || | ! |
---|---|---|---|
说明 | 逻辑与 | 逻辑或 | 逻辑非 |
逻辑中断:对于 && 和 ||,当满足一定条件时,右侧代码禁止执行
符号 | 短路条件 | 特殊 |
---|---|---|
&& | 左侧为 false 则短路 | 两侧都为真,返回最后一个真值 |
|| | 左侧为 true 则短路 | 两侧存在真值,返回第一个真值 |
运算符优先级
小括号 -> 一元运算符 -> 算术运算符 -> 关系运算符 -> 相等运算符 -> 逻辑运算符 -> 赋值运算符 -> 逗号运算符
6分支结构
三大流程控制:顺序结构、分支结构、循环结构
- if 分支语句:单分支、双分支、多分支
if (score >= 80) {document.write('优秀')
} else if (score >= 60) {document.write('及格')
} else {document.write('不及格')
}
- 三元运算符:if 双分支的简化
age > 18 ? alert('成年') : alert('未成年')
- switch 分支语句
switch (isTrue) {case true:console.log('成功')breakcase false:console.log('失败')breakdefault:console.log('未知')break
}
注意:值和类型全相等才能执行 case
7循环结构
- while 循环语句
while (i < 10) {if (i === 1) {i++continue // 退出本次循环}if (i === 2) {break // 退出整个循环}i++
}
- for 循环语句
for (let i = 0; i < arr.length; i++) {console.log(arr[i]) // 遍历数组if (arr[i] === 4) {break}
}// 嵌套循环
for (let i = 1; i < 10; i++) {for (let j = 1; j <= i; j++) {document.write(`${i} * ${j} = ${i * j} `)}document.write('<br>')
}
8数组
数组可以存储所有类型的数据,下标从零开始
// 数组声明
let age = [1, 2, 3, 4]
let ages = new Array(1, 2, 3, 4)console.log(age[0]) // 数组使用console.log(age.length) // 数组长度属性age[0] = 5 // 数组修改// 遍历数组
for (let i = 0; i < arr.length; i++) {console.log(arr[i])
}// 数组添加元素
arr.push(6) // push() 方法将元素添加至数组末尾,并返回数组新长度
arr.unshift(0) // unshift() 方法将元素添加至数组开头,并返回数组新长度// 数组删除元素
arr.pop() // pop() 方法删除数组末尾元素,并返回删除元素值
arr.shift() // shift() 方法删除数组开头元素,并返回删除元素值
arr.splice(0, 2) // splice(起始位置, 删除个数) 方法从起始位置开始删除指定个数元素,起始位置必须指定,删除个数未指定则删除至末尾// forEach 方法遍历数组,无返回值
arr.forEach(function (item, index) {console.log(item) // 数组元素console.log(index) // 数组下标
})// map 方法:遍历数组并处理数据,返回新数组
const arr = ['red', 'blue', 'green']
const newArr = arr.map(function (item, index) {console.log(item) // 数组元素console.log(index) // 数组下标return ele + '颜色'
})// join 方法:将数组所有元素转换成为一个字符串
const arr = ['red', 'blue', 'green']
console.log(arr.join('')) // 无符号拼接,默认逗号分割 -> arr.join()
9函数
// 函数声明
function getSum(x = 0, y = 0) { // 设置形参默认值,不设置则为 undefinedz = x + yreturn z // 返回值,默认为 undefined
}let sum = getSum() // 函数调用
10匿名函数
- 匿名函数-函数表达式使用法
let getSum = function (x, y) {return x + y
}getSum(1, 2)
函数表达式与具名函数区别:具名函数调用可在任何位置,函数表达式必须先声明后调用
- 匿名函数-立即执行法
;(function(){document.write('hello')
})() // 语法一;(function(x, y){document.write(x + y)
} (1, 2)) // 语法二
注意:立即执行法需添加分号,分号可开头也可末尾,末尾小括号相当于函数调用
11对象-“类”
对象由属性和方法组成,属性可以简单理解为基本数据类型,方法可以简单理解为函数
// 对象声明
let student = {'stu-name': 'Tom', // 中划线属性声明age: 18,gender: '男',sayHi: function () {document.write('Hi')},sayGoodbye: function () {document.write('Goodbye')}
}
let obj = new Object() // 不常用// 使用对象属性
console.log(student.gender)
console.log(student['stu-name'])student.age = 19 // 修改对象属性student.height = 180 // 新增对象属性delete student.height // 删除对象属性student.sayHi() // 对象方法调用// 对象遍历
for (let key in student) {console.log(key) // 输出对象属性,数组则为下标,输出类型为字符串类型console.log(student[key]) // 输出对象属性值
}
数学内置对象
调用 | 说明 |
---|---|
Math.PI | 圆周率 |
Math.ceil() | 向上取整 |
Math.floor() | 向下取整 |
Math.round() | 四舍五入取近值、大值 |
Math.max() | 最大值 |
Math.min() | 最小值 |
Math.abs() | 绝对值 |
Math.random() | 生成 [0,1) 的随机数,注意随机区间 |
Math.pow() | 幂运算 |
12Web APIs-DOM获取元素
-
DOM 是用来呈现以及与任意 HTML 或 XML 文档交互的 API,将网页内容视作对象进行处理,可操作网页内容以开发网页内容特效和实现用户交互
-
DOM 树将 HTML 文档以树状结构直观表现,体现标签间关系
-
DOM 对象是浏览器根据 HTML 标签生成的 JavaScript 对象
-
DOM 节点即 DOM 树中每一内容,包含元素节点(所有标签,html 为根节点)、属性节点(所有属性)、文本节点(所有文本)、其他节点
- 获取 DOM 元素:根据 CSS 选择器获取 DOM 元素
document.querySelector('div') // 获取第一个 div(选择器)元素,返回一个 HTML 对象或 null
document.querySelector('ul li:first-child') // 获取第一个 ul 元素中的第一个 li 元素document.querySelectorAll('sapn') // 获取所有 span 元素,返回一个 HTML 对象集合,伪数组,有长度有索引号,但没有 push()、pop() 等方法
除去根据 CSS 选择器获取 DOM 元素,还存在一些过去的旧方法
document.getElementById('nav') // 根据 id 获取元素
document.getElementByTagName('div') // 获取所有 div 元素
document.getElementByClassName('pink') // 获取所有类名为 pink 的元素
- 操作元素内容
const box = document.querySelector('.box') // 获取元素console.log(box.innerText) // 获取元素文字内容,不解析标签
console.log(box.innerHTML) // 获取元素内容,包含标签解析box.innerText = 'hello' // 修改元素文字内容
box.innerHTML = '<strong>hello</strong>' // 修改元素内容
- 操作元素属性
// 操作常用属性
const pic = document.querySelector('img')
pic.src = './images/1.png'
pic.title = '测试1'// 操作样式属性
const box = document.querySelector('.box')
box.style.width = '400px' // 通过 style 修改属性
box.style.backgroundColor = 'black' // 中划线变小驼峰命名
box.style.border = '2px solid while' // 通过 style 添加属性
document.body.style.backgroundImage = './test.png' // body 元素唯一可直接调用而不获取// 通过类名修改属性
.box {...
} // 定义类修改样式
const div = document.querySelector('div')
div.className = 'box' // 为元素添加或修改类名以更行样式,可同时添加多个类// 通过 classList 修改样式
div.classList.add('box') // 追加类名
div.classList.remove('box') // 删除类名
div.classList.toggle('box') // 有则删除,无则追加// 操作表单元素属性
const val = document.querySelector('input') // 文本框
console.log(val.value) // 获取表单元素值
val.value = 'hello' // 修改表单元素值
console.log(val.type) // 获取表单元素类型
val.type = 'password' // 修改表单元素类型const ipt = document.querySelector('input') // 下拉菜单
ipt.checked = true // 勾选const button = document.querySelector('button') // 按钮
ipt.diabled = true // 禁用
- 自定义属性:以 data- 开头
<div data-id="1" data-address="home">1</div> // 设置自定义属性
const one = document.querySelector('div')
console.log(one.dataset.id) // 获取自定义属性
- 间歇函数
setInterval(function () {
console.log('hello')
}, 1000) // 开启定时器,每 1000ms 执行一次匿名函数,返回一个 id 数字setInterval(fn, 1000) // 有名函数基本形式
setInterval('fn()', 1000) // 有名函数特殊形式let n = setInterval(fn, 1000) // 获取 id
clearInterval(n)
13Web APIs-DOM事件基础
- 事件监听:事件源(who)、事件类型(what)、执行函数(how)
const btn = document.querySelector('button')
btn.addEventListener('click', function () {alert('hello')
}) // 点击事件// 旧版本
btn.onclick = function () { // 该方法的原理为赋值,故可能被覆盖,但上面方法是追加,并且该方法都在冒泡阶段执行,无法实现捕获alert('Hi')
}
- 事件类型
|事件|说明|
|-|-|
|click|鼠标点击|
|mouseenter|鼠标经过|
|mouseleave|鼠标离开|
|focus|获得焦点(表单)|
|blur|失去焦点(表单)|
|Keydown|键盘按下触发|
|Keyup|键盘抬起触发|
|input|用户输入事件(表单)|
注意:mouseover,mouseout 的效果同 mouseenter、mouseleave 的效果相同,但前者会存在事件冒泡,可能影响父级元素
-
事件对象:事件监听中执行函数的第一个参数
|属性|说明|
|-|-|
|type|获取当前事件的类型|
|clientX、clientY|获取光标相对于浏览器可见窗口左上角的位置|
|offsetX、offsetY|获取光标相对于当前 DOM 元素左上角的位置|
|key|用户按下的键盘值| -
环境对象:this,常规情况下 this 指向调用者
-
回调函数:如果将函数 A 作为参数传递给函数 B,那么函数 A 称为回调函数
14Web APIs-DOM事件进阶
-
事件流:事件完整执行过程中的流动路径
-
事件捕获:从 DOM 的根元素开始执行对应事件
box.addEventListener('click', function () {alert('hello')
}, true) // true 代表开启事件捕获机制,默认为 false
- 事件冒泡:当一个元素的事件触发时,同样的事件将会在该元素的所有祖先元素中依次被触发
box.addEventListener('click', function () {alert('hello')
}) // 默认为 false,开启事件冒泡机制
默认开启事件冒泡机制,容易影响父级元素,则需要组织事件冒泡
box.addEventListener('click', function (e) {alert('hello')e.stopPropagation() // 阻止事件流动,既可以阻止事件捕获,也可阻止事件冒泡
})
- 解绑事件
const btn = document.querySelector('button')
btn.onclick = function () {alert('Hi')
}
btn.onclick = null // 旧版本解绑事件function fn () { // 匿名函数无法解绑alert('Hi')
}
btn.addEventListener('click', fn)
btn.removeEventListener('click', fn)
- 事件委托:利用事件冒泡特点为父级元素注册事件,进而减少事件注册次数,提高程序性能
const ul = document.querySelector('ul') // 为父级元素注册事件
ul.addEventListener('click', function (e) {if (e.target.tagName === 'LI') { // 检验标签类型e.target.style.color = 'red' // 获取点击元素并改变样式}
})
- 阻止元素默认行为
const a = document.querySelector('a')
a.addEventListener('click', function (e) {e.preventDefault()
})
- 页面加载事件:外部资源加载完毕时触发的事件
window.addEventListener('load', function () { // 监听页面所有资源加载完毕alert('Hi')
})document.addEventListener('DOMContentLoaded', function () { // 无需等待样式表、图像等资源加载alert('Hi')
})
注意:load 不仅可以对所有资源,也可以针对某个资源
- 页面滚动事件:滚动条滚动时触发的事件
window.addEventListener('scroll', function () { // 页面滚动console.log('document.documentElement.scrollTop') // 获取 html 元素比较特殊
})const div = document.querySelector('div')
div.addEventListener('scroll', function () { // 针对某个页面if (div.scrollTop > 4 && div.scrollLeft > 2) { // 检测滚动范围alert('hello') }
})
注意:scroll 不仅可以对整个页面,也可以针对某个页面
- 页面尺寸事件:窗口尺寸改变时触发的事件
window.addEventListener('resize', function () { // 页面尺寸let w = document.documentElement.clientWidth // 获取 html 元素宽度let h = document.documentElement.clientHeight // 获取 html 元素高度console.log(w, h)
})
注意:clientWidth、clientHeight 不包含边框 border
- 元素尺寸与位置
window.addEventListener('resize', function () { // 页面尺寸let w = document.documentElement.offsetWidth // 获取 html 元素宽度let h = document.documentElement.offsetHeight // 获取 html 元素高度console.log(w, h)
})
注意:offsetWidth、offsetHeight 包含元素自身设置的宽高、padding、border,隐藏返回 0
const div = document.querySelector('div')
console.log(div.offsetLeft) // 获取 div 元素距离父级元素的左距离
console.log(div.offsetTop) // 获取 div 元素距离父级元素的上距离
注意:offsetLeft、offsetTop 是只读属性
15Web APIs-DOM节点操作
- 日期对象:获得当前系统时间
const date = new Date() // 实例化,获得当前时间
const dates = new Date('2025-9-1 00:00:00') // 实例化,获得指定时间
console.log(date.getFullYear()) // 调用日期对象方法
console.log(date.getMonth() + 1) // 注意加一
方法 | 作用 | 说明 |
---|---|---|
getFullYear() | 获得年份 | 获取四位年份 |
getMonth() | 获得月份 | 取值为0~11 |
getDate() | 获取月份中的每一天 | 不同月份取值不同 |
getDay() | 获取星期 | 取值为0~6,星期天为0 |
getHours() | 获取小时 | 取值为0~23 |
getMinutes() | 获取分钟 | 取值为0~59 |
getSeconds() | 获取秒 | 取值为0~59 |
toLocalString() | 获取年月日时分秒 | |
toLocalDateString() | 获取年月日 | |
toLocalTimeString() | 获取时分秒 |
- 时间戳:利用毫秒数进行时间计算,实现倒计时
// 方法一
const date = new Date()
console.log(date.getTime()) // 获取时间戳// 方法二
+new Date('2024-1-2 12:34:00') // 获取指定时间戳// 方法三
Date.now() // 无需实例化,但仅能得到当前时间戳
- 查找节点:关系查找,返回对象
const ul = document.querySelector('ul')
const li = document.querySelector('ul li:nth-child(2)')console.log(li.parentNode) // 返回最近父级节点对象,无则返回 nullconsole.log(ul.children) // 返回最近子级节点伪数组,仅返回元素节点
console.log(ul.childNodes) // 获得所有子级节点,包括文本节点(空格、换行)等console.log(li.previousElementSibling) // 返回上一个兄弟节点,仅返回元素节点
console.log(li.nextElementSibling) // 返回下一个兄弟节点
- 增加节点
const newLi = document.createElement('li') // 创建标签
newLi.innerHTML = 'li 标签' // 标签文本元素编辑const ul = document.querySelector('ul') // 获取父级节点ul.appendChild(newLi) // 父级节点末尾增加节点ul.insertBefore(newLi, ul.children[0]) // 父级节点开头增加节点,后面子级节点伪数组可控制插入位置
- 克隆节点:增加节点的特殊形式
const ul = document.querySelector('ul')
const newLi = ul.children[0].cloneNode(true) // true 代表克隆包含所有节点(文本节点、属性节点等),默认 false,仅包含标签节点
ul.appendChild(newLi) // 末尾追加
- 删除节点
const ul = document.querySelector('ul') // 获取父级节点
ul.removeChild(ul.children[0]) // 删除子级节点
注意:不存在父子关系,删除失败,删除后节点不复存在
- M 端事件:移动端事件
const div = document.querySelector('div')
div.addEventListener('touchstart', function () {console.log('开始触屏')
})
触屏 touch 事件 | 说明 |
---|---|
touchstart | 手指触摸至 DOM 元素触发 |
touchend | 手指从 DOM 元素移开时触发 |
touchmove | 手指在 DOM 元素移动时触发 |
- swiper 插件:实习移动端滑动效果
https://swiper.com.cn/
16Web APIs-BOM操作浏览器
-
Window 对象:全局对象,基本 BOM 的属性和方法都属于 window,所有 var 声明的全局变量、函数都会成为 window 对象的属性和方法,window 对象下的属性和方法可以省略 window 调用
-
延时函数
setTimeout(function () {console.log('Hi')
}, 2000) // 延时函数,2s 后执行函数,但不重复执行,返回一个 id 数字let timer = setTimeout(function () {console.log('Hi')
}, 2000)
clearTimeout(timer) // 清除延时函数,递归必须清除
注意:延时函数 setTimeout 内部无法删除定时器,因为定时器还在运作
- JavaScript 执行机制
-
JavaScript 是单线程语言,但 HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,于是 JavaScript 出现同步和异步机制
-
同步任务:皆在主线程执行,形成执行栈
-
异步任务:JavaScript 的异步通过回调函数实现,包含普通事件(click、resize 等)、资源加载(load、error 等)、定时器(setInterval、setTimeout 等),异步任务添加至任务队列(消息队列)
-
执行机制:先执行执行线中的同步任务,然后异步任务放入任务队列,最后依次执行消息队列中的异步任务
-
事件循环(event loop):主线程不断从任务队列中重复获得任务、执行任务、再获取任务、再执行的机制
- location 对象
location.href = 'www.baidu.com' // 属性获取完整 URL,但赋值操作实现自动跳转至网站
console.log(location.search) // 返回 URL 问号后面的内容,包含问号
console.log(location.hash) // 返回 URL 井号后面内容,包含井号
location.reload() // 刷新当前页面
location.reload(true) // 强制刷新当前页面
- navigator 对象
console.log(navigator.userAgent) // 检测浏览器版本及平台
- history 对象
history.back() // 后退
方法 | 作用 |
---|---|
back() | 实现后退功能 |
forward() | 实现前进功能 |
go(1)、go(-1) | 前者代表前进一个页面,后者代表后退一个页面 |
- 本地存储
localStorage 可以将数据永久存储至本地直至手动删除,可以多窗口共享,以键值对的形式存储使用(仅能存储字符串类型)
localStorage.setItem('uname', 'Tom') // 键值对存储
localStorage.setItem('uname', 'Mary') // 键值修改数据
console.log(localStorage.getItem('uname')) // 键值获取数据
localStorage.removeItem('uname') // 键值删除数据// 存储复杂数据类型
const obj = {uname: 'Tom',age: 18
}
localStrage.setItem('obj', JSON.stringify(obj)) // JSON 字符串 {"uname":"Tom", "age":"18"}// 获取复杂数据类型
console.log(JSON.parse(localStorage.getItem('obj'))) // JSON 字符串转换对象
sessionStorage 以关闭浏览器窗口为生命周期,再同一窗口下数据可以共享,以键值对的形式存储使用,用法同 localStorage 基本相同
17Web APIs-正则表达式
const str = 'you are a real man'
const reg = /real/ // 检测模式
reg.test(str) // 用模式 reg 检测 str 中是否有 real,匹配则返回 true,否则返回 false
reg.exec(str) // 匹配则返回数组,否则返回 null
元字符:具有特殊含义的字符
- 边界符:表示位置,开头、结尾
const regHead = /^H/ // 开头为 H
const regTail = /T$/ // 末尾为 T
const reg = /^Y$/ // 精确匹配,字符串仅含 Y 时才返回 true
- 量词:表示重复位置
const regOne = /^H*$/ // 以 H 为开头结尾,H 出现零次或更多次
const regSnd = /^H{2}$/ // 以 H 为开头结尾,H 出现两次
const regTrd = /^H{4,6}$/ // 以 H 为开头结尾,H 出现四次到六次
- 字符类:表示某一类字符
console reg = /[abc]/ // 包含其中一个就返回 true
console reg = /[a-z]/ // 小写字母,- 为连字符(表示一个范围)
console reg = /[0-9]/ // 数字
console reg = /[^a-z]/ // 除去小写字母,此处 ^ 为取反符
console reg = /./ // 匹配除换行符外的任何单个字符
console reg = /a/i // 不区分大小写
console reg = /a/g // 匹配所有满足正则表达式的结果
元字符 | 说明 |
---|---|
^ | 表示匹配行首的文本 |
$ | 表示匹配行尾的文本 |
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
重复 n 次 | |
重复 n 次或更多次,无空格 | |
重复 n 到 m 次,无空格 | |
[] | 匹配字符集合 |
\d | 匹配 0-9 间的任一数字 |
\D | 匹配除 0-9 的字符 |
\w | 匹配任意字母、数字和下划线 |
\W | 匹配除字母、数字和下划线的字符 |
\s | 匹配空格(包括换行符、制表符、空格符等) |
\S | 匹配非空格的字符 |
替换
const str = 'java'
const reg = /java/i // 逻辑或写法 /java|JAVA/
const re = 'python'
str.replace(reg, re) // 用 reg 模式匹配后用 re 替换匹配结果
18作用域
- 局部作用域
- 函数作用域:在函数内部声明的变量只能在函数内部被访问,外部无法直接访问
- 块作用域:大括号包裹的内容被称为代码块,代码块内部声明的变量外部将有可能无法访问(var 声明无法被块作用域约束)
- 全局作用域
- 为 window 对象动态添加的属性默认为全局变量
- 函数中未使用任何关键字声明的变量视作全局变量
-
作用域链
本质是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域,若当前作用域查找无果则依次逐级查找父级作用域直到全局作用域 -
垃圾回收机制
- 内存分配:声明变量、函数、对象时,系统自动分配内存
- 内存使用:读写内存,即使用变量、调用函数等
- 内存回收:使用完毕后,垃圾回收器自动回收不再使用的内存
注意:全局变量一般不被回收直至关闭页面,局部变量一般会被自动回收
内存泄漏:程序中分配的内存由于某种原因未释放或无法释放
- JavaScript 闭包
一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域(闭包 = 内层函数 + 外层函数的变量),可能会产生内存泄漏问题
闭包主要用以封闭数据,提供操作,外部可以访问使用函数内部的变量
function outer () {let a = 10function inner () {console,log(a) }return fn
}
const fun = outer()
fun()
- 变量提升
var 声明的变量会被提升至当前作用域最开始,但仅仅提升变量声明,不提升变量赋值
19函数进阶
-
函数提升
函数的声明会被提升至当前作用域最开始,但仅仅提升函数声明,不提升函数调用,但函数表达式不存在函数提升 -
函数参数
- 动态参数:arguments 是函数内部内置的伪数组变量,包含调用函数时传入的所有实参
function getSum () {let sum = 0for (let i = 0; i < arguments.length; i++) {sum += arguments[i]}return sum
}
- 剩余参数:将一个不定量的参数表示为一个数组(真数组)
function getSum (a, b, ...arr) {if (arr.length === 0) { // 没有剩余参数return a + b}else {let sum = a + bfor (let i = 0; i < arr.length; i++) {sum += arr[i]}return sum}
}
- 展开运算符(...):将数组进行展开
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
console.log(Math.max(...arr)) // 配合 Math 方法使用求最大最小值
const arr = [...arr1, ...arr2] // 合并数组
- 箭头函数:替代匿名函数,多用于函数表达式
const fn = () => { // 箭头函数声明console.log('Hi')
}
fn() // 箭头函数调用const fn = x => { // 单一参数简洁形式console.log(x)
}const fn = x => console.log(x) // 单一语句简洁形式const fn = (x, y) => x + y // return 简洁形式const fn = (uname) => ({name: uname}) // 返回对象形式const fn = (...arr) => { // 箭头函数配合剩余函数,无动态参数let sum = 0for (let i = 0; i < arr.length; i++) {sum += arr[i]}return sum
}
注意:箭头函数不会创建 this,会沿用上层作用域的 this,回调函数不推荐使用箭头函数
- 数组解构:将数组的单元值快速批量赋值给一系列变量的简洁语法
const = [100, 60, 80]
const [max, min, avg] = arrlet a = 1
let b = 2
;[b, a] = [a, b] // 用以变量交换,注意分号,数组开头前必须加分号const [a, b, c, d] = [1, 2, 3] // d 为 undefined
const [a = 0, b = 0, c = 0, d = 0] = [1, 2, 3] // 默认值防止 undefined
const [a, b, c] = [1, 2, 3, 4] // 4 不赋值
const [a, b, ...c] = [1, 2, 3, 4] // 剩余参数接收
const [a, b, , c] = [1, 2, 3, 4] // 按需赋值
const [a, b, [c, d]] = [1, 2, [3, 4]] // 多维数组支持
- 对象解构:将对象的属性和方法快速批量赋值给一系列变量的简洁语法
const obj = {uname: 'Tom',age: 18
}
const {uname, age} = obj // 一般情况下声明变量名与属性名必须相同
const {uname: name, age} = obj // 改名解决变量名冲突// 数组对象解构
const person = [{name: 'Mary', age: 18}]
const [{name, age}] = person// 多级对象解构
const pig = {name: '佩奇',family: {mother: '猪妈妈',father: '猪爸爸',brother: '乔治'},age: 6
}
const {name, family: {mother, father, brother}, age} = pig// 数组多级对象解构
const arr = [{name: '佩奇',family: {mother: '猪妈妈',father: '猪爸爸',brother: '乔治'},age: 6
}]
const [{name, family: {mother, father, brother}, age}] = arr
20深入对象
// 创建对象语法一
const obj = {uname: 'Tom',age: 18
}// 创建对象语法二
const obj = new Object()
obj.uname = 'Tom'
注意:还可以通过构造函数创建对象
21构造函数
function Person (name, age, gender) { // 构造函数大写字母开头this.name = namethis.age = agethis.gender = gender
} // 无需返回值
const student = new Person('Tom', 18, '男') // 构造函数通过 new 执行,实例化
实例化过程:
- 创建新的空对象
- 构造函数 this 指向新对象
- 执行构造函数代码,修改 this,添加新的属性
- 返回新对象
实例成员:通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员
实例成员为构造函数传入参数,创建结构相同但值不同的对象,构造函数创建的实例对象彼此独立互不影响
静态成员:构造函数的属性和方法称为静态成员
静态成员只能通过构造函数访问,静态方法中的 this 指向构造函数
22内置构造函数
数据类型在底层存在构造函数
- Object
const user = new Object({name: 'Tom', age: 18}) // 实例化对象console.log(Object.keys(user)) // 静态方法获取对象中的所有属性名,返回数组
console.log(Object.values(user)) // 静态方法获取对象中的所有属性名,返回数组const obj = {}
Object.assign(obj, user) // 将 user 拷贝至 obj,常用来添加属性(以对象拷贝的形式)
- Array
const arr = new Array(1, 2, 3)arr.reduce(function (prev, current) { // 匿名函数可用箭头函数简化return prev + current
}, 10) // 初始值为 10,计算结果为数组和 + 10console.log(arr.every(item => item > 1))const grand = [{name: '小米'},{name: '华为'},{name: '苹果'}
]
arr.find(item => item.name === '小米') // find 方法常用于查找数组对象
方法 | 作用 | 说明 |
---|---|---|
forEach | 遍历数组 | 不返回数组,经常用于查找遍历数组元素 |
filter | 过滤数组 | 返回新数组,返回结果为满足筛选条件的数组元素 |
map | 迭代数组 | 返回新数组,返回结果为处理后的数组元素 |
reduce | 累计器 | 返回累计处理的结果,经常用于数组求和 |
join | 拼接数组 | 返回字符串 |
find | 查找元素 | 返回符合条件的第一个元素,否则返回 undefined |
every | 检测数组 | 所有元素符合条件返回 true,否则为 false |
some | 检测数组 | 存在元素符合条件返回 true,否则为 false |
concat | 合并数组 | 返回新数组 |
sort | 数组排序 | 对数组元素进行排序 |
splice | 修改数组 | 删除或替换数组元素 |
reverse | 翻转数组 | 返回新数组 |
findIndex | 查找元素的索引值 | 返回索引 |
- String
const str = 'hello,world'
const arr = str.split(',') // split 方法分割拆分成数组,以 , 为分割符console.log(str.substring(1, 4)) // substring 方法截取下标 1 至 3 部分为子串(左闭右开),省略 end 参数则取完console.log(str.startwith('llo', 2)) // startwith 方法从下标为 2 的位置开始判断是否以某字符开头console.log(str.includes('world', 4)) // includes 方法从下标为 4 的位置开始判断是否包含某字符
方法 | 说明 |
---|---|
length | 获取字符串长度 |
split | 将字符串拆分成数组 |
substring | 字符串截取 |
startwith | 检测是否以某字符开头 |
includes | 判断是否包含,返回布尔值 |
toUpperCase | 字母转大写 |
toLowerCase | 字母转小写 |
indexOf | 检测是否包含某字符 |
endwith | 检测是否以某字符结尾 |
replace | 替换字符串,支持正则表达式 |
match | 查找字符串,支持正则表达式 |
23编程思想
- 面向过程:分析解决问题的步骤,接着用函数实现步骤,使用时依次调用函数(性能较高)
- 面向对象:将事务分解成对象,由对象分工合作实现(易维护、易复用、易扩展)
面向对象特性:封装性、继承性、多态性
构造函数实现 JavaScript 的封装性,但存在浪费内存的问题,this 指向实例对象
原型实现 JavaScript 的继承性,this 指向实例对象
24原型prototype
JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向一个对象,该对象称为原型对象,构造函数通过原型分配的函数由所有对象共享,因此可以利用原型对象实现属性或方法的共享,节省内存
function Star (uname, age) { // 公共属性写入构造函数this.uname = unamethis.age = age
}Star.prototype.sing = function () { // 公共方法写入原型对象console.log('sing')
}
constructor 属性:指向该原型对象的构造函数
function Star () {}Star.prototype = { // 以对象赋值方式添加公共方法,会导致 constructor 属性丢失constructor: Star // 找回 constructor 属性sing: function () { // 添加公共方法console.log('sing')}
}
对象原型 proto([[prototype]]):对象都会存在属性 proto,指向构造函数的原型对象 prototype,这使得对象可以使用原型对象的属性和方法,proto 是 JavaScript 非标准属性,只读属性,对象原型 proto 中同样存在 constructor 属性,指向创建该实例对象的构造函数
function Star () {}
const s = new Star()console.log(s.__proto__ === Star.prototype) // true
console.log(s.__proto__.constructor === Star) // true
原型继承
function Person () {this.ear = 2this.head = 1
}function Woman () {}
Woman.prototype = new Person() // 继承的同时为独有方法的添加开辟空间
Woman.prototype.constructor = Woman // prototype 改变导致 constructor 指向改变,要恢复指向
Woman.prototype.baby = function () { // 添加独有方法console.log('baby')
}
原型链:针对对象原型 proto 指向
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,这种链状结构关系被称为原型链
查找规则:当访问一个对象的属性或方法是,首先查找这个对象自身有没有该属性,如果没有就查找该对象的原型,如果还没有就查找原型对象的原型,依次类推查找到 Object 为止,proto 对象原型的意义在于为对象成员查找机制提供方向
instanceof:检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
console.log(Tom instanceof Person) // true or false
25深浅拷贝
- 浅拷贝(简单数据类型拷贝值,引用数据类型拷贝地址)
// 对象拷贝
const obj = {}const o1 = {...obj} // 展开运算符拷贝
const o2 = {}
Object.assign(o2, obj) // assign 方法拷贝// 数组拷贝:Array.prototype.concat() 或者 [...arr]
- 深拷贝(简单数据类型拷贝值,引用数据类型创建新对象赋值旧内容)
// 递归函数实现
const obj = {}
const o = {}
function deepCopy (newOb, oldObj) {for (let k in oldObj) {// 处理引用数据类型if(oldObj[k] instanceof Array) { // 判断是否为数组newObj[k] = []deepCopy(newObj[k], oldObj[k])} else if(oldObj[k] instanceof Object) { // 判断是否为对象newObj[k] = {}deepCopy(newObj[k], oldObj[k])} else {newObj[k] = oldObj[k]}}
}
deepCopy(o, obj)// lodash 实现(需要导入 JavaScript 库)
const o = _.cloneDeep(obj)// JSON 实现
const o = JSON.parse(JSON.stringify(obj))
26异常处理
// throw 方式
function fn (x, y) {if (!x || !y) {throw new Error('传递参数为空') // throw 会自动中止程序}return x + y
}// try catch finally 方式
function fn () {try {const p = document.querySelector('p')p.style.color = 'red'} catch (err) {console.log(err.message)return // 中断程序}finally {alert('警报')}
}
27this
-
普通函数:一般情况下,this 指向普通函数的调用者,若没有明确调用者则指向 window,严格模式下没有调用者 this 指向 undefined
-
箭头函数:箭头函数会默认绑定外层 this 的值,其引用最近作用域的 this,若没有则会依次向外层作用域查找 this(构造函数、原型、DOM 事件函数等需要利用 this 时不使用箭头函数)
改变 this 指向
const obj = {}
function fn (x, y) {} // this 指向 window// call 方法
fn.call(obj, 1, 2) // 改变 this 指向 obj,1、2 为传入参数,返回值为函数返回值// apply 方法
fn.apply(obj, [1, 2]) // 改变 this 指向 obj,数组传递参数,返回值为函数返回值
console.log(Math.max.apply(Math, [1, 2, 4])) // 指向可为 Math 或者 null,求最大值// bind 方法
fn.bind(obj) // 改变 this 指向 obj,返回值为新函数(this 指向改变,其余拷贝旧函数)
区别:
call 和 apply 会调用函数,并且改变函数内部 this 指向
call 和 apply 传递的参数形式不同
bind 不会调用函数,可以改变函数内部 this 指向
28性能优化
- 防抖:单位时间内,频繁触发事件,只执行最后一次
const box = document.querySelector('.box')
let i = 1
function mouseMove () {box.innerHTML = i++
}// lodash 实现防抖
box.addEventListener('mouseMove', _.debounce(mouseMove, 500)) // 500ms 间隔// 手动实现
function debounce (fn, t) {let timerreturn function () {if (timer) clearTimeout(timer)timer = setTimeout(function () {fn()}, t)}
}box.addEventListener('mouseMove', _.debounce(mouseMove, 500))
- 节流:单位时间内,频繁触发事件,只执行一次
// lodash 实现节流
box.addEventListener('mouseMove', _.throttle(mouseMove, 500)) // 500ms 间隔// 手动实现
function throttle (fn, t) {let timer = nullreturn function () {if (!timer) {timer = setTimeout(function () {fn()timer = null}, t)}}
}box.addEventListener('mouseMove', _.throttle(mouseMove, 500))
防抖强调最后一次的执行,如搜索框输入等场景,而节流强调原子性操作(不被中断),如鼠标移动、页面尺寸缩放、滚动条滚动等高频事件