以下是本人在 js 学习过程中个人总结和积累的一些笔记,我学习或参考过 黑马js、渡一、JavaScript高级程序设计、MDN、al 交流等课程或资料,希望可以帮助到读者,欢迎读者纠偏
本篇是 JS 基础篇,包括基础的知识、语句和部分事件的知识
JavaScript是一种在运行在客户端(浏览器)的编程语言,实现人机交互效果
作用:
- 网页特效(监听用户的一些行为并让网页作出对应的反馈)
- 表单验证(针对表单数据的合法性进行判断)
- 数据交互(获取后台的数据,渲染到前端)
- 服务端编程([[1_计算机/服务端/Node.js|node.js]])
组成:
- ECMAScript——JavaScript语言基础
- Web APIs DOM——页面文档对象模型 BOM——浏览器对象模型 ECMAScript: 规定了JS基础语法核心知识:比如变量、分支语句、循环语句、对象等等
Web APIs:
- DOM 操作文档,比如对页面元素进行移动、大小、添加删除等操作
- BOM 操作浏览器,比如页面弹窗,检测窗口宽度、存储数据到浏览器等等
#JS参考网站 查资料网站:MDN Web Docs
注意: JS严格区分大小写
JS介绍
书写位置(类似于CSS):
- 行内JS
- 内部JS——一般写在
</body>前面 - 外部JS
内部JS
直接书写在html文件内,使用<script>标签包裹
规范:书写在html代码之后
<body>
<script>
alert('hello,js')
</script>
</body>
注意:
- 放在html底部是因为浏览器会按照代码在文件中的顺序加载HTML
- 如果先加载JavaScript期望修改其下方的HTML,可能由于HTML尚未被加载而失效
外部JS
代码书写在.js文件内
语法:通过script标签引入html页面中
<body>
<script src="my.js">
\\中间写的js代码不被执行
</script>
</body>
注意: 外部js标签中间的js代码会被忽略
行内JS
目前仅作了解,vue框架会用到这种模式
<body>
<button onclink="alert('逗你玩')">点击我月薪过万</button>
</body>
注释
分为单行注释//和块注释/* */,同Less
结束符
作用:使用英文的;代表语句结束
实际情况:实际开发中,可写可不写,浏览器(JavaScript引擎)可以自动推断语句的结束位置
现状:在实际开发中,越来越多人主张书写JavaScript代码时省略结束符
约定:为了统一代码风格,结束符要么每句都写,要么每句都不写
输入输出语法
语法:人和计算机打交道的规则约定
输出语法
语法1
作用:向<body>内输出内容
document.write('我是div标签')
document.write('<h1>我是h1标签</h1>')
注意:
可以在write内书写文字和html代码,写的是标签的话会被解析成网页元素
语法2
作用:页面弹出警告对话框
alert('要弹出的内容')
语法3
作用:控制台打印输出给程序员(控制台输出语法,程序员调试使用)
console.log('控制台打印')
注意: console是一个副作用函数,具有立即执行效果,在函数体中出现时,执行到这行代码后会立刻在控制台打印结果
输入语法
语法1
作用:显示一个对话框,对话框中包含一条文字信息,用来提示用户输入文字
prompt('请输入您的姓名:')
代码执行顺序
按HTML文档流顺序执行JavaScript代码
alert()和prompt()会跳过页面渲染先被执行
弹窗
alert()
弹窗只有一个选项
confirm()
弹窗有确定和取消两个选项,可以返回 true or false
字面量
在计算机科学中,字面量(literal)是描述计算机的事/物,包括数字字面量、字符串字面量、数组字面量[]、对象字面量{}
数字字面量:即数字 字符串字面量:即文字
常量
声明方式与变量一样
const PI = 3.1415926
注意:
- 为与变量区分,常量名称一般全大写
- 常量声明时必须赋值
- 常量无法被重新赋值
变量
是计算机存储数据的“容器”
注意: 变量不是数据本身,而是用于存储数据的容器,可以理解为用来装东西的纸箱子
变量的声明
即创建变量(也称为声明变量或者定义变量)
语法:
- 由声明关键字和变量名(标识)组成
- 关键字是系统提供的专门用来声明(定义)变量的词语,如l
let
let 变量名
变量声明有三个 var , let 和 const
var:排除,老写法,问题很多,可以淘汰const和let:const优先,尽量使用constconst语义化更好:很多变量在声明的后不会更改- 约定:有了变量先以
const声明,后续发现变量需要修改再改为let
注意:const声明
const声明数组元素和对象元素后,可以修改 array和 object 的值,包括增删查改,但不允许重新赋值该 array 或 object
const arr = ['red', 'pink']
//允许添加元素
arr.push('blue')
console.log(arr)
//不允许重新赋值
arr = [1, 2, 4] //报错
原因是: array 和 object 是复杂数据类型,声明后栈空间存放数据地址,堆空间存放数据值,增删查改会对堆空间数据进行操作,而栈空间的数据地址不变,重新赋值会导致栈空间的数据地址改变,指向新的堆空间数据值,而栈空间数据改变是 const 不允许的
注意:建议数组和对象使用 const来声明
var 和 let 的区别
-
在迭代上
- var 声明是函数作用域,如果循环内部含有定时器等异步操作,则会等到循环结束后,再执行,由于var是全局的变量,所有循环会共享此时 var 的值
- let 声明是块作用域, 每次迭代都会创建新作用域,循环内的异步操作有各自对应的 i 的值
-
在提升上
- var 拥有声明提升
- let 没有声明提升
-
在全局声明上
- var 在全局作用域中声明变量会变成 window 对象的属性
- let 不会
-
示例
// 10 个 10
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
// 0-9
// 解决方法1——闭包
for (var i = 0; i < 10; i++) {
(function (j){setTimeout(() => {
console.log(j);
}, 0)
})(i)
}
// 解决方法2——let
for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
// 每次循环类似于
{
i = 0
setTimeout(() => {
console.log(i);
}, 0);
}
{
i = 1
setTimeout(() => {
console.log(i);
}, 0);
}
......
变量的赋值
在声明的变量后面添加=即可赋值,=即赋值的意思,规则是将=右边的值赋值给左边,如 let age = 18
在声明的同时赋值变量,称为变量的初始化
注意:
- 通过变量名使用变量
- 同时声明多个变量时,中间用
,隔开 - 多个声明最好分开声明,具有更好的可读性
//写法1,声明和赋值分开
let age
age = 18
//写法2,声明和赋值同时进行
let age = 18
//使用变量
alert(age)
//同时声明多个变量
let age = 18, name = lcl
更新变量
赋值的变量可以重新赋值,方式如下
let age = 18
age = 19
注意:同一变量只能声明一次
交换变量的值
方法:引入临时变量 temp
步骤:
- 声明一个临时变量
temp - 把num1的值赋值给
temp - 把num2的值赋值给num1
- 把
temp的值赋值给num2
//传统写法
let num1 =10, num2 = 20
let temp
temp = num1
num1 = num2
num2 = temp
document.write(num1)
document.write(num2)
//新写法,不需要引入临时变量,本质是解构赋值
let num1 = 10, num2 = 20;
[num1, num2] = [num2, num1]
注意:
- 临时变量没有值后自动销毁
- 如果没有声明
temp,JS会自动创建一个全局变量,即widow.temp = num1
变量的本质
内存:计算机中存储数据的地方,相当于一个空间
变量的本质:程序在内存中申请一块用来存放数据的小空间
变量的规则和规范
规则:
- 不能使用关键字 关键字即有特殊含义的字符,JavaScript内置的一些英语词汇,如let、var、if、for等
- 只能由下划线、字母、数字、$组成,且不能数字开头
- 字母严格区分大小写
规范:
- 起名要有意义
- 遵守小驼峰命名法 第一个单词首字母小写,后面每个单词首字母大写,如useName
函数prompt规则
prompt('参数1', '参数2'),参数1为提示信息(必选),参数2为默认输入值(可选),多余的参数会被忽略
扩展:let和war的区别
var声明:
- 可以先使用,再声明(不合理)
- var声明过的变量可以重复声明(不合理)
- 变量提升、全局变量、没有块级作用域等
数组(array)
是一种将一组数据存储在单个变量名下的方式
术语:
- 元素:数组中保存的每个数据都叫数组元素
- 下标:数组中数据的编号
- 长度:数组中数据的个数,通过数组的
length属性获得
let 数组名 = [数据1, 数据2, 数据3, ……, 数据n]
注意:
- 数组是有序的
- 数据的编号也叫索引或下标
- 数据索引号从0开始
- 数组可以存储任意类型的数据
- 先声明变量,再将变量放入数组中,本质是将变量复制了一份放到数组里面,二者存储在独立的内存中
let i = 0
let array = [i]
array[0] += 2
//相当于数组中的第一个元素加了2,由于数组中的i和变量i的存放位置已经分开独立,所以原始变量i的值还是0
使用数组:数组名[索引号]
| 数据类型 | 是否需要引号 | 实例 |
|---|---|---|
| 字符串 | ✔️ 必加 | 'hello' |
| 数字 | ❌ 不加 | 123 |
| 布尔值 | ❌ 不加 | true |
| 对象 | ❌ 不加 | {x:1} |
| 数组 | ❌ 不加 | [1,2] |
| null | ❌ 不加 | null |
| undefined | ❌ 不加 | undefined |
| 注意:只有字符串需要引号,其他类型直接写值。如果想让非字符串类型转为字符串,才需要刻意加引号 |
数据类型
分为基本数据类型和引用数据类型(复杂数据类型)
基本数据类型:
- number 数字型
- string 字符串型
- boolean 布尔型
- undefined 未定义型
- null 空类型
- symbol 符号
- bigInt 表示大数字
引用数据类型:
- object 对象 赋值
//数字类型
let num = 2
//字符串类型
let num = 'pink'
注意: JS是弱数据语言,即只有数据赋值后才知道数据是什么类型
数字类型(number)
经常跟算术运算符(数学运算符)一起使用,主要包括加、减、乘、除、取余(取模)、平方
+:求和-:求差*:求积/:求商%:取模(取余数) 开发过程中常用于判断某个数字是否被整除**:乘方
console.log(1 + 1)
console.log(1 - 1)
console.log(1 * 1)
console.log(1 / 1)
console.log(5 % 3)
console.log(2 ** 2)
运算优先级:
同数学计算,可以使用 ( )提升优先级
NaN
- 含义 代表一个计算错误,它是一个不正确的或者一个未定义的数学操作所得到的结果
//NaN
console.log('老师' - 2)
consolr.log(NaN + 2)
[!tips] 注意
- NaN是粘性的,任何对NaN的操作都会返回NaN
- NaN 不等于包括 NaN 在内的任何值
- 如果数字超过了最大表示范围,会被转化为 Infinity(正无穷) 或 -Infinity,且不能再进一步进行计算
- 查询最大数值 Number.MAX_VALUE,最小数值同理
字符串类型(string)
通过单引号(')、双引号(")、或反引号(`)包裹的数据都叫字符串,单双引号没有本质上的区别,推荐使用单引号
注意:
- 使用反引号包裹,可以换行
- 单引号、双引号或反引号应成对使用
- 单引号/双引号可以相互嵌套,但不允许自己嵌套自己(口诀:外双内单或外单内双)
eg.
console.log('"哥们"有点意思')输出为”哥们“有点意思 - 必要时可以使用转义符
\,输出单引号或者双引号 - 单/双/反引号中没有内容叫做空字符串
+在字符串中起到拼接前后字符串的作用(只需要前后有一个是字符串就能起作用) eg.console.log('玉皇' + '大帝')输出结果为玉皇大帝 eg.document.write('我今年' + age + '岁了'),可以根据变量age的结果输出不同答案
模板字符串
使用场景:拼接字符串和变量
语法:
- 使用反引号包裹所有内容
- 变量格式为
${变量}
document.write(`我今年${age}岁了`)
去除字符串两侧空格
- 语法
// trim 方法
元素.trim()
转换为字符串类型
- toString() 可以将数值、布尔值、对象和字符串转换为字符串,字符串使用该方法时返回自身的副本(复制了一遍) null 和 undefined 没有 toString() 方法 对象是数字时可以传入一个参数,参数表示进制,会将数字转换为相应进制的字符串,该参数应大于 2,小于某个值(忘了,三十多吧)
const num = 10
num.toString() // '10'
- String()
- 与上面不同的是,null 和 undefined 会被转换为 'null' 'undefined'
String(num) // '10'
布尔类型(boolean)
表示肯定或否定时在计算机中对应的是布尔类型数据
| 取值 | 意义 |
|---|---|
true | 正确 |
false | 错误 |
| 注意:true和false是布尔型字面量 |
未定义类型(undefined)
是比较特殊的类型,只有undefined一个值
声明变量而不赋值的情况下,变量默认为undefined,一般很少直接为某个变量赋值为undefined
使用场景:开发中声明一个变量,等待数据传输。如果不知道这个数据是否被传输过来,可以通过检测此变量是否为undefined来判断用户是否传输数据
[!tips] 注意
- 对未声明的变量进行除了 typeof 之外的操作都会报错,使用 typeof 操作符返回 undefined
[!tip] undefined 转为数字是 NaN ,对 NaN 的任何比较都是 false
空类型(null)
JavaScript中的null仅仅是一个代表”无“、”空“、或者”值未知“的特殊值,指的是空对象引用
[!tips] 注意
- 在定义将来要保存对象值的变量时,如果不明确对象的具体类型(如不知道是 {} 还是 []),建议使用 null 来初始化,不要使用其他值,这样可以通过检测是否为 null 来判断该个变量是否在后来被重新赋予了一个对象的引用
null和undefined区别
| 取值 | 意义 |
|---|---|
null | 已赋值,但内容为空 |
undefined | 未赋值 |
| null的使用场景: |
- 官方解释:null作为尚未创建的对象
- 大白话:将来有个变量里面存放的是某个对象,但对象还未创建好,可以先取值null
计算区别:
//输出为NaN
console.log(undefined + 1)
//输出为1
console.log(null + 1)
‘’ , null , undefined , true 和 false的读取与报错
// 0 '空字符串的长度'
console.log(''.length, '空字符串的长度')
// undefined '空字符串不存在的属性'
console.log(''.name, '空字符串不存在的属性')
// Uncaught TypeError: Cannot read properties of null (reading 'name')
console.log(null.name, 'null不存在的属性')
// Uncaught TypeError: Cannot read properties of undefined (reading 'name')
console.log(undefined.name, 'undefined不存在的属性')
// undefined 'true不存在的属性'
console.log(true.name, 'true不存在的属性')
// undefined 'true不存在的属性'
console.log(false.name, 'false不存在的属性')
符号类型
- 描述
是 ES6 新增的数据类型,是原始值,可被 typeof 操作符检测为
symbol,符号实例是唯一、不可变的,符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险,通过Symbol(description)函数初始化符号,这可以保证每次调用Symbol(description)创建的都是新的符号实例,description 会被隐式转换为字符串
// 1. 通过 Symbol 函数创建
const symbol = Symbol()
// 2. 即使传入相同的参数,也不会相等,符号实例是唯一的
const sym2 = Symbol()
console.log(symbol === sym2) // false
const sym3 = Symbol('lcl')
const sym4 = Symbol('lcl')
console.log(sym3 === sym4) // false
符号没有字面量语法,也不能和 new 关键字实例化,这是为了防止使用对象包装符号(如包装数字等)
// 1. 示例 —— 包装字符串
const str = 'aaa'
const string = new String('aaa')
console.log(str === string) // false
console.log(typeof str) // string
console.log(typeof string) // object
// 2. 使用 new 关键字实例化报错
const sym = new Symbol() // Uncaught TypeError: Symbol is not a constructor
全局符号注册表 —— 重用符号实例
Symbol.for() —— 重用符号实例
- 描述
可以使用
Symbol.for(description)在全局符号注册表上创建符号实例,这是个幂等方法,如果注册表上没有该符号,则创建一个新符号并记录在注册表上,如果注册表上已经有这个符号,则返回这个已有的符号,从而实现了符号的共享和重用
// 1. 创建符号注册表符号
const sym1 = Symbol.for('lcl') // 创建新符号
const sym2 = Symbol.for('lcl') // 重用已有符号
console.log(sym1 === sym2) // true
// 2. 全局符号注册表上定义的符号与 Symbol() 定义的符号不同
const sym3 = Symbol('lcl')
console.log(sym2 === sym3) // false
在全局符号注册表定义符号,其符号描述必须为字符串,非字符串会转换为字符串类型
// 1. 描述符为空
const sym = Symbol.for()
console.log(sym) // Symbol(undefined)
Symbol.keyFor() —— 查询全局注册表并返回符号描述字符串
- 描述 该方法接收一个参数,必须为符号,否则会抛出 TypeError,该方法会查询全局符号注册表,如果有该符号,则返回其符号描述字符串
// 1. 创建全局注册表符号
const sym1 = Symbol.for('lcl')
// 2. 获取描述
console.log(Symbol.keyFor(sym1)) // lcl
// 3. 查询不在全局注册表的符号
const sym2 = Symbol('dhh')
console.log(Symbol.keyFor(sym2)) // undefined
使用符号作为属性
- 描述
所有能使用数字,字符串作为属性的地方,都能使用符号作为属性,包括对象字面量和
defineProperty,其中在对象字面量中使用符号属性必须使用动态属性
const sym1 = Symbol('dhh')
const obj = {
[sym1]: 'dhh'
}
const sym2 = Symbol('lcl')
obj[sym2] = 'lcl'
Object.defineProperty(obj, Symbol('666'), { value: 666 })
console.log(obj) // {Symbol(dhh): 'dhh', Symbol(lcl): 'lcl', Symbol(666): 666}
常用内置符号 —— 暴露语言内部行为
- 描述 ES6 内置了一批常用内置符号,用于暴露语言内部行为,这些常用内置符号都以 Symbol 工厂函数字符串属性的方式存在,开发者可以直接访问、重写和模拟这些行为
这些符号的最重要的用途之一是重新定义它们,从而改变原生结构的行为,例如重写一个对象的 [Symbol.iterator] 方法可以改变函数迭代时的行为
这些符号是全局函数 Symbol 的普通字符串属性,指向一个符号的实例。所有内置符号属性都是不可写、不可枚举、不可配置的
// 在规范中的符号名称跟平常使用的不太一样,遵循以下转换结构
@@iterator 指的就是 Symbol.iterator
Symbol.asyncIterator —— 返回默认异步迭代器
- 描述 调用该方法返回默认异步迭代器,for-await-of 循环会利用这个函数执行异步迭代操作
由于我暂时还没学习异步生成器和迭代器,暂不展开描述
Symbol.hasInstance —— 确认对象是否为其实例
- 描述 它主要用于确认构造器对象是否是任何这个对象是它的实例,由 instanceof 操作符调用,在 ES6 中,可以直接调用该方法来达成同样的效果
// 1. 判断一个对象是否是某构造器对象的实例
class Lcl {}
const lcl = new Lcl()
console.log(lcl instanceof Lcl) // true
console.log(Lcl[Symbol.hasInstance](lcl)) // true
// 自定义 Symbol.hasInstance
class Bar {}
class Baz extends Bar {
static [Symbol.hasInstance]() {
return false;
}
}
let b = new Baz();
console.log(Bar[Symbol.hasInstance](b)); // true
console.log(b instanceof Bar); // true
console.log(Baz[Symbol.hasInstance](b)); // false
console.log(b instanceof Baz); // false
Symbol.isConcatSpreadable —— 改变数组 concat 方法的行为
- 描述 该属性为一个布尔值,如果为 true,则打平该数组(这里的打平简单来说,就是去掉最外层的数组,该影响只对 a.concat(b...) 中的 b 都有效),如果为 false 或假值会把整个对象加追到数组末尾,为 true 或真值则把类数组对象打平并追加到数组末尾
// 1. 打平 —— 只去掉了一层外壳
const arr1 = [1, 2]
const arr2 = [[3, 4]]
console.log(arr2[Symbol.isConcatSpreadable]) // undefined
arr2[Symbol.isConcatSpreadable] = true
const arr3 = arr1.concat(arr2)
console.log(arr3) // [1, 2, [3, 4]]
// 2. 将对象追加至数组末尾
const obj = { a: 1 }
console.log(obj[Symbol.isConcatSpreadable]) // undefined
console.log(arr1.concat(obj)) // [1, 2, { a: 1 }]
// 3. 将它变成类数组对象
const o = { 0: 6, 1: 666, length: 2 }
Object.assign(obj, o)
console.log(obj) // { a: 1, 0: 6, 1: 666, length: 2 }
obj[Symbol.isConcatSpreadable] = true
console.log(arr1.concat(obj)) // [1, 2, 6, 666]
Symbol.iterator —— 默认迭代器工厂函数
- 描述 返回(默认)迭代器,详情见[[#Iterator —— 迭代器]]
Symbol.match —— 使用正则匹配字符串
- 描述
表示一个正则表达式方法,使用该方法匹配字符串,由
String.prototype.match()使用,String.prototype.match()会使用Symbol.match为键的函数对正则表达式求值,正则表达式的原型上默认有这个函数的含义,因此所有正则表达式实例都是这个String方法的有效参数
该方法目前有点不太懂,以后补吧,遇见去 JS 高级编程设计里面找哦
Symbol.replace —— 正则表达式方法,替换一个字符串中匹配的子串
- 描述
该方法替换一个字符串中匹配的子串。由
String.prototype.replace()方法使用,String.prototype.replace()方法会使用以 Symbol.replace 为键的函数来对正则表达式求值。正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个 String 方法的有效参数
该方法目前有点不太懂,以后补吧,遇见去 JS 高级编程设计里面找哦
Symbol.search —— 返回字符串中匹配正则表达式的索引
- 描述
该方法返回字符串中匹配正则表达式的索引。由
String.prototype.search()方法使用,String.prototype.search()方法会使用以 Symbol.search 为键的函数来对正则表达式求值。正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个 String 方法的有效参数
Symbol.species —— 创建派生对象的构造函数
略
Symbol.split —— 在正则表达式索引位置拆分字符串
略
Symbol.toPrimitive —— 将对象转为原始值
[!tip] 注意
- 一旦定义了
Symbol.toPrimitive,valueOf和toString在类型转换中将被完全忽略(除非你在Symbol.toPrimitive方法内部手动调用它们,在转换为原始值的场景中,该属性具有最高优先级
- 描述
该方法将对象转换为相应的原始值。由
ToPrimitive抽象操作使用,很多内置操作都会尝试强制将对象转换为原始值,包括字符串、数值和未指定的原始类型
| 转换种类 | 含义 | 触发场景 |
|---|---|---|
| number | 期望得到一个数字 | Number(target) , 数学运算 + - * / , 大小比较 , Array.prototype.sort() 等 |
| string | 期望得到一个字符串 | String(target) , 模板字符串 ${target} , 属性键 obj[target] , 字符串拼接等 |
| default | 引擎不确定期望什么类型 | 二元加法 +(可能用于字符串拼接或数字加法)、== 比较(但不包括 ===)、Date 对象与其他值相加等 |
// 需要设置三种类型,转为 string ,转为 number 或者未指定的原始类型 default
const temperature = {
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'string':
return '10℃';
case 'number':
return 10;
case 'default':
return 10;
}
}
}
// 1. 使用该方法
console.log(Number(temperature)) // 10
console.log(String(temperature)) // 10℃
console.log(6 + temperature) // 16
Symbol.toStringTag —— 设置对象字符串描述符(属性)
- 描述 该属性是一个字符串,该字符串用于创建对象的默认字符串描述,由内置方法 Object.prototype.toString()使用
通过 toString() 方法查找属性内置类型时,会检索由 Symbol.toStringTag 指定的实例标识符,默认为 Object ,所有内置类型都提供了该属性的值,自定义类型需要自己设置其值
// 属性模板
[Object xxxx]
- 详情 [[#判断类型|详情见此处]]
Symbol.unscopables —— 与 with 关键字有关,不推荐使用
- 描述 with 关键字不推荐使用了,这个属性也不推荐使用
检测数据类型
typeof 操作符 —— 检测简单类型
通过typeof关键字检测数据类型
typeof可以返回被检测的数据类型,它支持两种语法形式:
- 作为运算符:
typeof x(常用写法) - 函数形式:
typeof(x)即有无括号不影响输出结果,所以一般使用运算符写法
\\测试变量 age的类型
let age = '十八'
console.log(typeof age)
[!tips] 注意
- typeof 操作符可以检测 String , Number , Undefined , Boolean 四种数据类型,如果检测类型为对象(数组)或 null 则返回 object
- typeof 操作符检测函数时,返回 function
- 使用 typeof 操作符检测未声明的变量时,返回 undefined
instanceof 操作符 —— 检测引用类型
-
作用 检测构造函数的
prototype属性是否出现在某个实例对象的原型链上,其返回值为一个布尔值 可以用来检测引用类型数据的类型 -
语法
object instanceof 构造函数
[[1_计算机/前端/参考书/JavaScript高级程序设计(第4版) (Matt Frisbie) (Z-Library).pdf#page=111&selection=273,0,273,4|JavaScript高级程序设计(第4版) (Matt Frisbie) (Z-Library), 页面 111]]
[!tips] 注意
- 所有引用类型对 Object 构造函数使用该操作符均返回 true,因为所有引用类型都是 Object 实例(按照定义)
- 使用 instanceof 操作符检测简单数据类型永远返回 false
数据类型转换
prompt和表单提取的数据默认为字符串类型,不能直接进行加法运算
数据类型转换即把一种数据类型的变量转换成我们需要的类型,分为隐式转换和显示转换
隐式转换
某些运算符被执行时,系统内部自动将数据类型进行转换,称为隐式转换
规则:
+号两边只要有一个字符串,就会把另外一个转换为字符串- 除了
+以外的算术运算符,比如 + * /等都会把数据转换成数字类型
缺点:转换类型不明确,靠经验才能总结
注意:
+号作为正号解析可以转换成数字型- 任何数据和字符串相加结果都是字符串
- 减法
-只能用于数字,它会使空字符串""转换为0 null经过数字转换之后会变成0undefined经过数字转换之后会变成NaN
显式转换
系统内部的隐式转换是不明显的,规律并不清晰,大多是靠经验的总结,为了避免隐式转换带来的问题,通常根逻辑需要对数据进行显示转换
概念:通过代码告诉系统该转换成什么类型
转换为数字型:
Number(数据)转成数字类型 如果字符串内容里有非数字,转换失败时结果为 NaN(Not a Number)即不是一个数字 NaN也是number类型的数据,代表非数字
let num1 = prompt('请输入你的年薪')
console.log(Number(num1))
//方法2
let num2 = Number(prompt('请输入你的年薪'))
console.log(num2)
parseInt(数据)只保留整数(Int表示整数) 如果数字有字母(中文等),从左向右检索,开头是数字的只保留开头的一串数字,开头不是数字则报错为NaN 第二个参数为数字,代表进制数,被转换的数据将以该进制为标准转换为十进制 如果不传第二个参数容易遇到歧义,因此最好传参,多数情况下传参为 10
//输出为12
console.log(parseInt('12px'))
console.log(parseInt('12.34px'))
console.log(parseInt('12px34'))
console.log(parseInt('c', 16))
//报错NaN
console.log(parseInt('px12'))
parseFloat(数据)可以保留小数(Float表示浮点数) 如果数字有字母(中文等),从左向右检索,开头是数字的只保留开头的一串数字,开头不是数字则报错为NaN 只有一个参数,只解析十进制
[!tips] 注意 parseInt 和 parseFloat 从第一个非空格字符开始转换
运算符
赋值运算符
对变量进行赋值的运算符
| 运算符 | 含义 |
|---|---|
= | 将等号右边的值赋予左边,要求左边必须是一个容器 |
+= | num += 1等价于 num = num + 1 |
-= | num -= 1等价于 num = num - 1 |
/= | num /= 1等价于 num = num / 1 |
%= | num %= 1等价于 num = num % 1 |
*= | num *= 1等价于 num = num * 1 |
**= | num **= 1等价于 num = num ** 1 |
一元运算符
可以进行自增运算
JS运算符可以根据所需表达式的个数,分为一元运算符、二元运算符、三元运算符
//一元运算符
let age = +prompt('请输入你的年龄')
//二元运算符
let num = 20 + 5
一元运算符:
- 正负号
- 自增运算符(++)
- 自减运算符(--)
使用场景:计数
自增运算符的用法:
- 前置自增 每执行1次,当前变量数值加1
let num = 1
++num //让num的值加1变成2
- 后置自增 每执行1次,当前变量数值加1
let num = 1
num++ //让num的值加1变成2
注意:
- 前置自增和后置自增在单独使用时没有区别
- 参与运算后有区别: 前置自增先自增再返回值 后置自增先返回值再自增
//输出4
let i = 1
i = ++i + 2
console.log(i)
//输出3
let m = 1
m = m++ + 2
console.log(m)
后置自增时,m先返回原先的值为1,然后立即自增到2,之后返回的原先值1会参与计算,得到m=1+2,即m=3,覆盖了自增的结果2,因此最后输出为3
[!tip] 注意
- let i = 1; i ++ + i ++ 的结果为3
比较运算符
使用场景:比较两个数据的大小,是否相等
| 运算符 | 含义 |
|---|---|
> | 左边是否大于右边 |
< | 左边是否小于右边 |
>= | 左边是否大于等于右边 |
<= | 左边是否小于等于右边 |
== | 左右两边值是否相等 |
=== | 全等,左右两边是否类型和值都相等 |
!== | 左右两边是否不全等 |
!= | 左右两边的值是否不相等 |
| 注意: |
==具有隐式转换,会把其他类型转换为数字类型- 判断是否相等最好用
=== NaN === NaN会输出false,说明NaN不等于NaN,涉及到NaN都会输出false- 尽量不要比较小数,因为小数的精度有问题(可以把小数扩大成整数之后再进行比较) 算0.1+0.2可以转化为1+2=3,然后3/10=0.3
- 不同类型之间比较会发生隐式转换
对比:
=是赋值==是判断===是全等
了解:字符串也可以进行比较,比较的是字符对应的ASCII码
- 从左往右依次比较
- 如果第一位一样再比较第二位,以此类推
逻辑运算符
使用场景:解决多重条件判断 eg.判断num是否大于5且小于10
num > 5 && num < 10
| 符号 | 名称 | 日常读法 | 特点 | 口诀 |
|---|---|---|---|---|
&& | 逻辑与 | 并且 | 两边都为ture结果才为true, 若左侧条件为false则不计算右侧 | 一假则假 |
| || | 逻辑或 | 或者 | 一个为true则结果为true | 一真则真 |
! | 逻辑非 | 取反 | true变false,false变true | 真变假,假变真 |
注意:
- 逻辑与,两侧都为真时,输出右侧;逻辑或,两侧为真时,输出左侧(逻辑中断)
//输出22
console.log(11 && 22)
//输出11
console.log()
逻辑中断
短路: 只存在于&&和||中,当满足一定条件会让右边代码不执行
| 符号 | 短路条件 | 原理 |
|---|---|---|
| && | 左边为false就短路,返回左侧值,反之返回右侧值 | 与,一假则假 |
| || | 左边为true就短路,返回左侧值,反之返回右侧值 | 非,一真则真 |
原理:通过左边就能得到式子的结果,因此无需判断右边
运算符优先级
| 优先级 | 运算符 | 顺序 |
|---|---|---|
| 1 | 小括号 | ( ) |
| 2 | 一元运算符 | ++、--、! |
| 3 | 算术运算符 | 先 * / %后+ - |
| 4 | 关系运算符 | > >= < <= |
| 5 | 相等运算符 | == != === !== |
| 6 | 逻辑运算符 | 先 &&后 || |
| 7 | 赋值运算符 | = |
| 8 | 逗号运算符 | , |
语句
表达式和语句
表达式:可以被求值的代码,JavaScript引擎会将其计算出一个结果
语句: 一段可以执行的代码 eg. prompt( ) 弹出一个输入框;if语句、for 循环语句等等
区别:
- 表达式可以被求值,所以它可以写在赋值语句的右侧
- 语句不一定有值,如 alert( ) for 和break 等语句无法用于赋值
分支语句
- 从上往下线性执行的代码结构叫做顺序结构
- 根据条件选择性执行代码叫做分支结构
- 某段代码被重复执行,叫做循环结构
分支语句包括:
- if 分支语句
- 三元运算符
- switch语句
if语句
三种使用:
- 单分支
- 双分支
- 多分支
单分支
if (条件) {
满足条件要执行的代码
}
注意:
- 括号内条件为true时,执行大括号内代码
- 小括号内的结果不是布尔型时,会发生隐式转换为布尔型
- 如果大括号只有一个语句,大括号可以省略(不提倡)
- 条件为数字时,除0外所有数字都为真
- 条件为字符串时,除了空字符串以外都为真
if为false的情况:
false0(包括0,0n,0.0)""(空字符串)nullundefinedNaN
双分支
if (条件) {
满足条件要执行的代码
} else {
不满足条件执行的代码
}
多分支
使用场景:适合于有多个结果的时候,比如学习成绩可以分为:优、良、中、差
if (条件1) {
代码1
} else if (条件2) {
代码2
} else if (条件3) {
代码3
} else {
代码n
}
释义:
- 先判断条件1,若满足则执行代码1,其他不执行
- 若不满足则向下判断条件2,满足则执行代码2,其他不执行
- 若依然不满足则继续往下判断
- 以上条件均不满足则执行最后一个else的代码n
三元运算符
使用场景: 比if双分支更简单的写法,可以使用三元表达式,一般用于取值
语法:
条件 ? 满足条件执行的代码 : 不满足条件执行的代码
技巧:
- 使用三元运算符补0
//时分秒案例
h = h < 10 ? '0' + h : h
m = m < 10 ? '0' + m : m
s = s < 10 ? '0' + s : s
注意:
- 如果连续出现的三元运算符报错,有可能是因为JS的解析面临歧义从而导致出现
expression expected的错误,此时可以把判断语句用()包裹或者利用;将三元运算符分隔
switch语句
能利用switch执行满足条件的语句
switch (数据) {
case 值1:
代码1
break
case 值2 :
代码2
break
default:
代码n
break
}
释义:
- 找到跟小括号里数据全等的case值,并执行里面对应的代码
- 若没有全等
===则执行default里的代码 - 若数据跟值2全等,则执行代码2
- 数据即为某个变量,如
switch(x) {},不能是布尔表达式,否则会导致条件永远不匹配,值则为变量x的取值
注意:
- switch case 语句一般用于等值判断,不适合区间判断
- switch case 一般需要配合break关键字使用,没有break会造成case穿透
- switch case语句是二分法,有五种情况的话会从中间开始查找,如1-5共5种情况,数据值为4,会先从3开始查,发现3<4,再往下查,而不是从1查到4
switch语句和if...else...语句的区别:
- 前者处理确定值,后者用于范围判断
- 前者判断句后直接执行程序语句,效率更高,后者有几种情况就要判断几次
- 前者的数据必须为
===全等,同时要加break,否则会有穿透效果 - 当分支比较少的时候,后者效率高
- 分支较多时,前者效率高,且代码结构更清晰
循环语句
学习路径:
- 断点调试
- while循环
断点调试
作用:学习时可以帮助更好的理解代码运行,工作时可以更快找到bug
步骤:
- 浏览器打开调试界面
- F12进入开发者工具
- 找到source一栏
- 选择代码文件 被选中的那行代码会及其之后会停止运行
| 功能 | 快捷键 | 说明 |
|---|---|---|
| 恢复执行/跳到下一个断点 | F8 | |
| 单步跳过(不进入函数) | F10 | |
| 单步进入(进入函数) | F11 | 遇到条件判断等有函数时使用它可以进入函数 |
| 跳出当前函数 | Shift + F11 | 回退到进入该函数的前一行代码 |
具体停止的情况
1. 函数调用的返回点
let result = myFunction(); // F10会停在这里(执行完myFunction后)
console.log(result); // 然后停在这里
为什么停:函数执行完毕,返回到调用点,这是一个天然的语句边界。
2. 异步操作的等待点
let data = await fetch('/api'); // F10会停在这里(等待fetch完成)
console.log(data); // fetch完成后停在这里
为什么停:await 会暂停函数执行,等待Promise完成,这是强制的停止点。
3. 代码块的边界
if (condition) {
console.log('true'); // F10进入if后会停在这里
}
console.log('after'); // 执行完if块后停在这里
为什么停:进入新的代码块是语句边界。
4. 循环的每次迭代
for (let i = 0; i < 3; i++) {
console.log(i); // F10会在每次循环迭代时停在这里
}
console.log('done'); // 循环结束后停在这里
为什么停:每次循环迭代都是一个新的执行周期。
5. 遇到断点
let a = 1;
let b = 2; // 如果这里有断点,F10必定停在这里
let c = 3;
为什么停:断点是强制停止指令。
不会停下来的情况
1. 连续的同步语句(没有断点)
let a = 1; // 你在这里按F10
let b = 2; // 不会停,继续执行
let c = a + b; // 不会停,继续执行
console.log(c); // 不会停,继续执行
router.push('/'); // 执行完这里,页面跳转,调试结束
为什么不停:这些都是普通的同步操作,JavaScript引擎会一口气执行完,调试器跟随执行流。
2. 数组方法的内部执行
arr.forEach(item => { // F10在这里会执行完整个forEach
console.log(item); // 不会逐个停在每次回调
});
console.log('done'); // forEach完成后停在这里
为什么不停:F10是"跳过",不会进入forEach的内部实现。
F10 vs F11 的区别
F10(单步跳过)
-
遇到函数调用:执行完整个函数,停在函数调用的下一行
-
遇到方法调用:执行完整个方法,停在方法调用的下一行
F11(单步进入)
-
遇到函数调用:进入函数内部,停在函数的第一行
-
遇到方法调用:如果是用户代码,进入方法内部;如果是原生方法,行为和F10相同
while循环
满足条件期间,重复执行某些操作,本质是以某个变量为起始值,然后不断产生变化量,慢慢靠近终止条件的过程
基本语法:
while (循环条件) {
重复执行的代码(循环体)
}
释义:
- 循环条件为ture时才会进入循环体执行代码
- 代码执行完毕后不会跳出,而是继续判断循环条件是否满足,若满足则再次执行循环体,直到循环条件不满足,跳出循环
- 循环条件直接写
true的话会一直循环
三要素:
- 变量起始值
- 终止条件(没有终止条件,循环会一直执行,造成死循环)
- 变量变化量(用于自增或自减)
let i = 1
while (i <= 3) {
document.write ('我会循环三次<br>')
i++
}
循环退出
循环结束:
- break:结束整个循环
let x = 1
while (x <= 5) {
if (x === 3) {
break
}
document.write(`这是第${x}个包子<br>`)
x++
}
- continue:结束本次循环,继续下次循环
let x = 1
while (x <= 5) {
if (x === 3) {
x++
continue
}
document.write(`这是第${x}个包子<br>`)
}
ATM案例
错误写法: let y = 0写在while循环内部,导致每次循环都会重新生成y = 0,存取款操作之后余额任然为0
while (true) {
let x = +prompt(`请选择你的操作:
1.取钱
2.存钱
3.查看余额
4.退出`)
let y = 0
switch (x) {
case 1:
y -= +prompt('请输入取款金额')
break
case 2:
y += Number(prompt('请输入存入金额'))
break
case 3:
alert(`您的余额为${y}`)
break
default:
break
}
if (x === 4) {
break
}
}
正确写法:let y = 0移出来,变为全局变量
let y = 0
while (true) {
let x = +prompt(`请选择你的操作:
1.取钱
2.存钱
3.查看余额
4.退出`)
switch (x) {
case 1:
y -= +prompt('请输入取款金额')
break
case 2:
y += Number(prompt('请输入存入金额'))
break
case 3:
alert(`您的余额为${y}`)
break
default:
break
}
if (x === 4) {
break
}
}
for循环
作用:重复执行代码
好处: 把声明起始值、循环条件、变化值写到一起,让人一目了然,是最常用的循环方式
for (变量起始值; 循环条件; 变量变化量) {
//循环体
}
利用for循环打印遍历数组:
- 遍历数组:从第一个循环到最后一个
let arr = ['詹伟', '陈中彧', '廖成林', '丁键']
//方法1:推荐使用
for (let c = 0; c < arr.length; c++) {
console.log(arr[c])
}
//方法2:既可以遍历数组,也可以遍历对象,缺点是,遍历出来的索引号是字符串型,容易出问题
for (let k in arr) {
console.log(k)//k是字符串型
console.log(arr[k])
}
退出循环机制与while一样
注意:
while(true)构造“无限循环”,需要使用break退出循环if(;;)构造无限循环,需要使用break来退出循环
区别:
- 如果明确循环次数推荐使用for
- 不明确循环次数推荐使用while
for循环嵌套
一个循环里再套另一个循环,称为循环的嵌套,一般用在for循环里
for (外部声明记录循环次数的变量; 循环条件; 变化值) {
for (内部声明记录循环次数的变量; 循环条件; 变化值) {
循环体
}
}
注意:
- 变化值在结束一次循环后执行
for...in 循环
- 作用 枚举对象中的非符号键属性
[!tips] 注意
- 数组也是对象,因此可以用来遍历数组,缺点是遍历的属性是字符串,即数组的索引会被转换为相应的字符串
- 使用 for...in 循环时,应该使用 const 来定义,而不是 let 以防止在循环体内部意外修改变量的值从而导致意料之外的结果
数组
[!tip] 注意
- 使用 delete 方法删除数组元素时,length 不会变化,需要手动调整
数组:(array)是一种可以按顺序保存数据的数据类型
场景:如果有多个数据(数据也可以是数组,从而实现数组的嵌套)可以使用数组进行保存,放到一个变量中,管理方便
let 数组名 = [数据1, 数据2, 数据3]
//使用构造函数声明 new Array
let 数组名 = new Array(数据1, 数据2, 数据3)
遍历数组
for (let i = 0; i < arr.length; i++) {
循环体
}
求数组的最大值和最小值
冒泡排序,以最大值为例:
let arr = [2, 6, 1, 7, 4]
let max = arr[0]
for (let i = 1; i < arr.length; i++) {
max > arr[i] ? max === max : max = arr[i]
}
操作数组
包括增、删、查、改:
- 增:为数组添加新的数据类型
数组.push(数据1, 数据2, 数据3)
数组.unshift(数据1, 数据2, 数据3)
- 删:删除数组中的数据
数组.pop()
数组.shift()
数组.splice(元素的下标, 删除的个数)
- 查:查询数组数据(访问数组数据)
数组[下标]
- 改:重新赋值
数组[下标] = 新值
注意:
- 若数组未赋值,查询整个数组的结果为
[],查询某个数据的结果为undefined - 若要在数组每一项数据前后添加一样的内容,可以使用for循环遍历数组修改
数组——增
数组.push(数据1, 数据2, 数据3):将一个或多个元素添加到数组的末尾,并返回该数组的新长度(即打印该函数时显示的值是新数组的长度)
unshift(数据1, 数据2, 数据3) :将指定元素添加到数组的开头,并返回数组的新长度
array.splice(startIndex, deleteCount, item1, item2, ...):在指定未知插入/删除数据
参数:
startIndex:插入/删除的起始索引。deleteCount:要删除的元素数量(设为0表示仅插入,不删除)item1, item2...:要插入的元素。
数组——筛选
步骤:
- 声明新数组
newArray用于存放数据 - 遍历旧数组,找出大于等于10的元素
- 一次追加给新数组
newArray
let arr = [1,3,15,16,84,223,6,0,3], newArray = []
for (let 0 =1; i<arr.length; i++) {
if (arr[i] >10) {
newArray.push(arr[i])
}
}
数组——删除
数组.pop()(括号内留白):从数组中删除最后一个元素,并返回该元素的值
数组.shift()(括号内留白):从数组中删除第一个元素,并返回该元素的值
数组.splice(startIndex, deleteCount):从数组某一位置删除开始删除n个元素
startIndex:指定修改的起始位置(元素下标)deleteCount:表示要移除的元素个数,若省略则默认从起始位置删除到最后
冒泡排序
一种简单的排序算法
原理:重复走访要排序的数列,一次比较两个元素,如果他们的顺序错误就进行交换,直到没有元素需要交换为止,此时排序完成
let arr = [5, 4, 3, 2, 1]
let newArr = []
for (let m = 0; m < arr.length - 1; m++) {
for (let n = 0; n < arr.length - m - 1; n++) {
if (arr[n] > arr[n + 1]) {
[arr[n], arr[n+1]] = [arr[n+1], arr[n]]
}
}
}
实际:使用arr.sort()进行排序(默认为升序排列)
let arr = [5, 4, 3, 2, 1]
arr.sort()
//完整写法——升序
arr.sort(function (a, b) {
return b - a
})
//完整写法——降序
arr.sort(function (a, b) {
return a - b
})
函数基础
function:被设计成执行特定任务的代码块,从而实现代码复用,分为声明和调用两部分
说明:
- 函数可以把具有相同或相似逻辑的代码“包裹“起来,通过函数调用执行这些被”包裹“的代码逻辑,有利于精简代码方便使用
- 如之前使用的
alert()、prompt()和console.log()都是被封装好的js函数,可以直接使用
函数的声明:
function 函数名() {
函数体
}
函数的命名规范:
- 和变量命名基本一致
- 尽量使用小驼峰式命名法
- 前缀应该为动词
- 命名建议:常用动词约定
| 动词 | 含义 |
|---|---|
can | 判断是否可执行某个动作 |
has | 判断是否含有某个值 |
is | 判断是否为某个值 |
get | 获取某个值 |
set | 设置某个值 |
load | 加载某些数据 |
| 函数的调用: |
函数名()
注意:
- 声明(定义)的函数必须调用才会真正被执行,使用( )调用函数
- 之前使用的如
alert()中的括号本质是调用函数
函数体:是函数的构成部分,负责将相同或相似的代码”包裹“起来,直到调用时其体内的代码才会被执行。函数的功能代码都要写在函数体中
函数传参
把要计算的数字传到函数内部,从而实现函数的灵活化
- 若函数完成功能需要调用者传入数据,就需要用有参数的函数
- 可以极大提高函数的灵活性
声明语法
function 函数名(参数列表) {
函数体
}
参数列表:
- 传入数据列表
- 声明需要传入数据的数量
- 多个数据用逗号隔开
- 声明时的参数叫做形参(形式上的参数),可以理解为在此函数内声明的变量
function 函数名(形参1, 形参2, 形参3) {
函数体
}
调用及传参
意味着:形参1 = 实参1、形参2 = 实参2以此类推,调用时的参数称之为实参(实际的参数),可以理解为给形参赋值
function 函数名(实参1, 实参2, 实参3)
eg.求任意两整数的累加和
//声明函数
function getSum(start = 0, end = 0) {
let sum = 0
for (let i = start; i <= end; i++) {
sum += i
}
document.write(getSum)
}
//调用函数,求50-100的累加和
getSum(50, 100)
注意:
- 应尽量保持形参和实参的个数一致,若不同
- 形参过多,会自动填上
undefined^37dca5 - 实参过多,多余的实参会被忽略(即不参与运算,函数内部有一个arguments,里面装着所有实参)
- 形参过多,会自动填上
//形参赋零
function getSum(start = 0, end = 0) {
}
//逻辑中断
function getSum(x, y) {
x = x || 0
y = y || 0
}
- 有实参时,优先执行实参,没有实参则执行形参默认值
- 实参也可以是变量
- 函数的嵌套是合法的,但内部函数需在其父级函数调用才能实现只调用外层函数而实际内部函数也运行
函数返回值
返回函数值:当调用某个函数后,此函数会返回一个结果,可以让其他程序使用这个结果
语法:相当于 函数名() = 数据
function 函数名() {
return 数据 //返回单个数据
return [数据1, 数据2] //返回多个数据
}
注意:
return后面的代码在调用时不会执行,只有用到函数返回值时才会执行- 没有设置返回值时,函数返回
undefined - 用数组可以返回多个值
- 在函数体中使用return关键字能将内部的执行结果交给函数外部使用
- return后面代码不会被执行,会立即结束当前函数,所以returen后面的数据不能换行写(即return应在函数最下部分)
- 存在多个函数的函数名相同,后面的函数会覆盖前面的函数
- 函数默认隐式返回undefined,设置了
return之后会返回return的值,函数的返回值是否被“看到”,取决于如何调用函数,函数名()的方式会执行函数,但忽略返回值(本质是该语法没有使用返回值) 调用流程: ![[z_attachments/deepseek_mermaid_20250603_0abe32.svg]] 调用的三种方式:
| 方式 | 语法 | 结果 | 备注 |
|---|---|---|---|
| 独立调用 | 函数名() | 返回值被丢弃 | 返回值不可见 |
| 赋值语句 | let 变量名 = 函数名() | 返回值赋给变量 | 返回值通过变量可见 |
| 参数使用 | console.log(函数名()) | 返回值传给外层函数 | 返回值通过参数可见 |
匿名函数
函数分为具名函数和匿名函数
注意:
- 具名函数可以先调用再声明
- 匿名函数必须先声明再调用
匿名函数:没有名字的函数,无法直接使用
使用方式:
- 函数表达式:将匿名函数赋值给一个变量,并且通过变量名称进行调用,我们称之为函数表达式
let fn = function() {
// 函数体
}
- 立即执行函数
//语法1
(function(x, y){
函数体
})(1, 2);
//语法2
(function(s, y) {
函数体
}(1, 2));
// 语法3
!function () {}
注意:
- 立即执行函数后必须使用分号
;隔开,否则会报错
检测是否为数字
isNaN()函数与 !isNaN()函数
isNaN()函数:检测一个值是否是 NaN(Not-a-Number)或无法被转换为数字的值,本质是在问,这个数是否是一个非数字
注意:会发生隐式转换,强制将参数类型转化为数字
返回值:
- true:值为NaN或无法被转化为数字
- false:值是有效数字或可以转化为数字
| 情况 | 返回值 | 备注 |
|---|---|---|
isNaN(NaN) | true | |
isNaN('abc') | true | |
isNaN(undefined) | true | |
isNaN({}) | true | |
isNaN("123px") | true | 部分值为数字无效 |
isNaN(123) | false | |
isNaN('123') | false | 字符串可转数字 |
isNaN('') | false | 空字符串转0 |
isNaN(null) | false | null 转 0 |
isNaN(true) | false | true 转 1 |
isNaN(" 123 ") | false | 空格会被忽略 |
用途:
- 用
isNaN():检测一个值是否无效(如用户输入非法数字) - 用
!isNaN():检测一个值是否有效(如验证表单输入是否为数字)
替代方案
-
Number.isNaN()(ES6+):严格检测NaN不会对检测值进行类型转换 -
typeof+isNaN
function isNumber(x) {
return typeof x === 'number' && !isNaN(x);
}
⭐转换为Boolean(布尔)型——逻辑真假⭐
有六种值在布尔上下文中被判断为 false ,在逻辑运算、if语句、三元表达式中,会被隐式转换为 false
| 值 | 类型 | 返回值 | 备注 |
|---|---|---|---|
false | Boolean | false | |
0, -0 | Number | false | |
"", '' | String | false | 空字符串 |
null | Null | false | |
undefined | Undefined | false | |
NaN | Number | false | |
0n | BigInt | falsy | BigInt 类型的零 |
[], {} | Object | true | |
"0", "false" | String | true | |
" " | String | true | 空格字符串 |
关于空对象和空数组的特殊情况
- 示例
// 永远输出 true
p !== {}
p !== []
- 原因
JavaScript 中对象是通过引用(内存地址)比较的,而不是比较内容。每次使用
{}或[]都会创建一个全新的空对象,其内存地址与原对象params不同
对象
对象是JavaScript中的一种数据类型,由属性和方法组成
可以理解为一种无序的数据集合(数组是有序的数据集合),可以用来描述具体事务
声明:实际开发中,多用花括号进行声明,{}是对象字面量
//方法1
let 对象名 = {}
//方法2
let 对象名 = new Object()
⭐注意⭐
对象赋值的特殊情况
- 示例
// 现有两个对象 a 和 b ,二者都有属性 x 和 y
const a = {
x: 1,
y: 2
}
const b = {
x: 11,
y: 22
}
// 写下 a = b
a = b
console.log(a) // 显示 { x: 11, y: 22 }
- 说明
对于复杂数据类型来说,赋值运算符
=代表的是引用传递,即变量 a 由指向{ x: 1, y: 2 }的内存地址变为指向{ x: 11, y: 22 }的内存地址,此时 a 和 b 指向的内存地址相同
属性
[!tip] 注意
- JS 对象只支持 整数、字符串、符号 作为 键
数据描述的信息称为属性,如人的姓名、身高、年龄、性别等,一般是名词性
let 对象名 = {
属性名: 属性值,
方法名: 函数
}
注意:
- 属性包括属性名和值,二者成对存在,用英文
:分隔 - 多个属性之间用英文
,分隔 - 属性是依附在对象上的变量(外面是变量,对象内是属性)
- 属性名可以使用
""或'',一般情况下省略,除非遇到名称或者特殊符号,如空格、中横线等 - 对象和数组之间的关系有些类似于HTML里面的ol和ul
null表示不存在的对象,类似于空对象,let obj = null跟let obj = {}差不多- 属性名和属性值相同时,可以只写属性名
descripter —— 属性描述符
- 属性的特征 JS 通过 6 个内部特性来描述属性的类型,六个内部特性(使用 2 个中括号括起来表示内部特性)分别为
[[Configurable]]
[[Enumerable]]
[[Writable]]
[[Value]]
[[Set]]
[[Get]]
- 组成 包括数据描述符和存取描述符,二者是互斥的,调用一次 defineProperty 定义一个属性只能使用其中一个描述符
[!tips] 注意 以下 6 个属性描述符跟上面 6 个内部特性一一对应
数据描述符(数据属性)
| 数据属性 | 类型 | 默认值 | 含义 |
|---|---|---|---|
value | any | undefined | 属性的值 |
writable | boolean | true | 值是否可以被修改 |
enumerable | boolean | true | 是否可枚举(能否在 for...in 或 Object.keys() 中出现) |
configurable | boolean | true | 是否能通过 delete 删除并重新定义,是否可以修改其特性, 是否能将它修改成访问器属性 |
[!tip] 注意
- 这里的不可枚举,除了上述中方法无法枚举之外,打印属性所在的对象时,不可枚举属性也不会被打印出来
访问器描述符(访问器属性)
[!tip] 注意
- 设置了访问器时,读取该属性时不会再去内存中寻找它的属性值,而是运行 get 函数,读取其返回值,写入属性同理
- 利用 get 和 set 两个访问器,可以制作出会报错的只读属性,将 set 设置为 抛出错误,并提示后来者不可赋值即可
| 访问器属性 | 类型 | 默认值 | 含义 |
|---|---|---|---|
get | function | undefined | 获取函数,在读取属性时调用,未设置时默认为 undefined |
set | function | undefined | 设置函数,在写入属性时调用,未设置时默认为 undefind |
enumerable | boolean | true | 是否可枚举 |
configurable | boolean | true | 是否可配置 |
writable 和 configurable 的关键区别
-
configurable 为 false 时,不能删除属性,也不能使用 defineProperty 重新配置,它的不可配置是指,不能修改 attributes 配置
-
writable 为 false 时,不能通过赋值运算符重新赋值
-
示例
const obj = {}
// 定义一个不可配置属性
Reflect.defineProperty(obj, 'lockedData', {
value: '锁定数据',
configurable: false
})
// 不可配置的属性在重新使用 defineProperty 配置时失败
Reflect.defineProperty(obj, 'lockedData', {
value: '尝试修改'
}) // ❌ 失败
// 在删除不可配置属性时会失败
Reflect.deleteProperty(obj, 'lockedData') // ❌ 失败
对象的使用
对象本质是无序数据的集合,操作数据包括增删查改语法
查
声明对象并添加若干属性后,可以使用 . 获得对象中属性对应的值,称之为属性访问,即获得对象里面的属性值
//语法1
对象名.属性名
//语法2
对象名['属性名']
| 特性 | 点表示法 obj.property | 方括号表示法 obj["property"] |
|---|---|---|
| 属性名要求 | 必须是有效的标识符(如:name、age_1) | 任意字符串(如:"first name"、"123abc") |
| 动态性 | 属性名需硬编码在代码中 | 支持变量/表达式(如:obj[key]) |
| 特殊字符支持 | ❌ 不支持含空格、连字符等属性名(如:obj.my-prop 报错) | ✅ 支持(如:obj["my-prop"]) |
| 数字开头属性名 | ❌ 不支持(如:obj.1key 报错) | ✅ 支持(如:obj["1key"]) |
注意:
.方法实际上等价于[]方法中直接使用字符串字面量情况
对象名.属性名
//等价于
对象名['属性名']
.方法更加简洁,适合静态、有效标识符的属性名- 出现需要动态绑定、处理特殊属性名或计算属性的情况时,只能使用
[]方法 - 一般情况下,直接在
{}内书写的属性名,在使用[]方法查询时,需要添加引号,否则会被视作变量,从而出现变量未定义的错误 []方法何时需要添加''或"":
| 场景 | 实例 | 是否加引号 |
|---|---|---|
| 直接使用字符串字面量 | obj["name"] | ✅ 必须加 |
| 使用变量作为属性名 | obj[key] | ❌ 不加 |
| 使用表达式作为属性名 | obj["key" + idx] | ✅ 内嵌字符串需加 |
| 访问数字属性 | obj[123] | ❌ 不加(自动转换为'123') |
| 访问 Symbol 类型属性 | obj[symbolVar] | ❌ 不加 |
改
对象名.属性名 = 新值
增
对象名.新属性名 = 新值
删(了解)
delete 对象名.属性名
对象中的方法
数据行为性的信息称为方法,如跑步、唱歌等,一般是动词性的,其本质是函数
let person = {
//属性
name: 'andy',
//方法
sayHi: function() {
document.write('hi~~')
}
// 简写方式,上述方法等价于
sayHi () {
document.write('hi~~')
}
}
方法语法:
方法名: 匿名函数
//例子
sayHi: function(形参) {
document.write('hi~~')
}
注意:
- 多个方法之间用英文
,隔开,方法和属性之间使用英文,分隔 - 方法是依附在对象中的函数
- 方法名可以使用
""或'',一般情况下省略,除非遇到特殊符号(空格、中横线等)
方法的调用
对象名.方法名(实参)
//例子
person.sayHi(实参)
对象名类似于一个储存空间,对象名.意思是打开箱子, 方法名(实参)是只在这个箱子里的工具
可计算属性(动态属性)
-
作用 实现了动态命名属性名,即前面所谓的 [] 方法
-
示例
let a = 'num'
const obj = {
[a]: 1
}
// { num: 1 }
console.log(obj)
[!tip] 注意
- 必须在有动态属性的对象创建之前传入动态属性的值(
[]内的内容被当作一个 JS 表达式来求值),如果未声明会报错,只声明作为动态属性的变量却不初始化会导致动态属性变成 undefined
# —— 私有属性
- 描述 对象可以为属性添加 # 前缀,表示这个属性是一个私有属性,只能在对象内部访问
遍历对象
- 对象没有像数组一样的length属性,无法确定长度
- 对象里面是无序的键值对,没有规律,不像数组里面有规律的下标
遍历对象语法:
for (let k in 对象名) { //k其实是一个变量名,常用k或key,表示键值对,是一个习惯
console.log(k) //打印属性名
console.log(对象名.[k])//用[]方法打印属性值(k是变量,只能使用 []方法)
}
注意:
- for in 语法中的 k 是一个变量,在循环的过程中一次代表对象的属性名(字符串类型)
- 由于k是变量,必须使用
[]方法解析 k是获得对象的属性名,对象名[k]是获得属性值
数组对象
数组对象:把对象作为元素放置在数组中
//定义数组
let students = [
{name: '小明', age: 18, gender: '男', hometown: '河北省'},
{name: '小红', age: 19, gender: '女', hometown: '河南省'},
{name: '小刚', age: 17, gender: '男', hometown: '山西省'},
{name: '小丽', age: 18, gender: '女', hometown: '山东省'}
]
//遍历数组
for (let i = 0; i < students.length; i++) {
console.log(i)
//查询对象属性
console.log(students[i].name)
}
注意:
- 此时的对象名为数组元素,表示为
数组名[]
内置对象
JavaScript内部提供的对象,包含各种属性和方法给开发者调用
document.write()
console.log()
内置对象——Math
Math 对象是 JavaScript提供的一个“数学”对象,包含属性和方法,Math在线文档 | MDN
作用:提供了一系列做数学运算的方法
常用属性和方法:
| 属性 | 含义 | 备注 |
|---|---|---|
Math.PI | 约为3.141 | |
Math.E | 约为2.718 | |
Math.LN10 | 约为2.302 | |
Math.LN2 | 约为0.693 | |
Math.LOG10E | 约为0.434 | |
Math.LOG2E | 约为0.693 | |
Math.SORT1_2 | 约为0.707 | |
Math.SORT2 | 约为1.414 |
| 方法 | 含义 | 备注 |
|---|---|---|
Math.random() | 生成一个随机数,范围为 [0,1) | 括号内不用填值 |
Math.ceil() | 向上取整 | |
Math.floor() | 向下取整 | |
Math.max() | 找最大数 | |
Math.min() | 找最小数 | |
Math.pow() | 幂运算 | |
Math.abs() | 绝对值 |
内置对象——生成任意范围随机数
Math.random()随机数方法,返回一个0-1之间,包括0而不包括1的随机小数
//生成0-10的随机小数
Math.random() * 10
//生成0-10的随机整数
Math.floor(random() * (10 + 1))
//生成5-10的随机小数
Math.random() * (10 - 5) + 5
//生成 n-m 的随机小数
Math.random() * (m - n) + n
//生成 n-m 的随机整数
Math.floor(random() * (m - n + 1) + n)
//随机整数抽数组元素
let arr = ['red', 'green', 'blue']
let random = Math.floor(Math.random() * arr.length)
//封装随机数函数
function getRandom(n, m) {
return Math.floor(Math.random() * (m - n + 1) + n)
}
随机生成颜色
function getRandomColor(input) {
// 参数传递false,输出随机rgb颜色
if (input === false) {
const r = Math.floor(Math.random() * 256)
const g = Math.floor(Math.random() * 256)
const b = Math.floor(Math.random() * 256)
return `rgb(${r}, ${g}, ${b})`
} else{
// 输出16进制颜色
//方法1
const hexChars = ['#']
for (let i = 0; i < 6; i++) {
hexChars.push(Math.floor(Math.random() * 16).toString(16))
}
return hexChars.join('')
}
//方法2
let str = '#'
for (let i = 0; i < 6; i++) {
let str += Math.floor(Math.random() * 16).toString(16)
}
return str
//方法3
let string = '#'
let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
for (let i = 0; i < 6, i++) {
let random = Math.floor(Math.random() * 16)
string += arr[random]
}
return string
}
注意:
参数.toString()方法可以将数字转换为字符串,参数.toString(16)能将数字转换为16进制字符串
let a = 0
let a = 0
扩展
扩展——术语解释
| 术语 | 解释 | 举例 |
|---|---|---|
| 关键字 | 在 JavaScript中有特殊意义的词汇 | let, var, function, if, else, switch, case, break |
| 保留字 | 在目前的 JavaScript中没有意义,但未来可能会具有特殊意义的词汇 | int, short, long, char |
| 标识(标识符) | 变量名、函数名的另一种叫法 | 无 |
| 表达式 | 能产生值得代码,一般配合运算符出现 | 10 + 3, age >= 18 |
| 语句 | 一段可执行得代码 | if(), for() |
扩展——基本数据类型和引用数据类型
简单类型又叫基本数据类型或者值类型,复杂数据类型又叫引用类型
-
值类型:简单数据类型/基本数据类型,在存储时变量中存储的是值本身,因此也叫值类型 eg.
string,number,boolean,undefined,null -
引用类型:复杂数据类型,在存储时变量中存储的仅仅是地址(引用),因此叫做引用数据类型 eg.通过new关键字创建的对象(系统对象、自定义对象),如
Object,Array,Date等
扩展——堆栈(zhan)空间
内存分为栈空间(用矩形表示)和堆空间(用椭圆表示)
- 栈(操作系统):由操作系统自动分配释放存放函数的参数值、局部变量的值等。其操作方式类似于数据结构中的栈;简单数据类型存放在栈里面,具体机制为,简单数据类型的值直接存放在栈空间中
//简单数据类型
let num1 = 10
let num2 = num1
num2 = 20
console.log(num1)//打印结果是10
原因是,这两个变量存储的是简单数据类型,值存放在栈空间中,所以 let num2 = num1,相当于在栈空间中单独开辟了一个小空间 num2 里面存放的是 10,之后赋值 num2 = 20 ,则小空间 num2 的值变成了20,但 num1不受影响
- 堆(操作系统):存储复杂数据类型(对象),一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收;引用数据类型存放在堆里面,具体机制为,引用数据类型在栈空间内存放地址,通过地址去堆空间查找值
let obj1 = {
age: 18
}
let obj2 = obj1
//修改属性
obj2.age = 20
console.log(obj1.age)//打印结果为20
原因如下,对于复杂数据类型来说,数据存放在堆空间中,栈空间存放的是地址,所以, let obj2 = obj1实际上是吧栈空间中 obj1的地址赋值给了 obj2 ,此时二者拥有同一个地址,类似于堆空间的数据是房间里的物品,而地址是打开房门的钥匙,此时修改了 obj2 的属性,相当于把房间里的物品修改了,因此 obj1的值也会发生变化
![[z_attachments/07a44bb18de00df90ac67ed1cdcbdc0.jpg]]
Console —— 调试语句
- 展开符号
如下图中的三角形展开符号,只有第一次使用它展开这个对象/数组时,dev 会获取这个数据的实时快照并固定,因此你打印的对象并不是你书写
console.log()语句时的对象快照
![[z_attachments/93b1263326dd977031669a90c14bb473.png|400]]
在 console.log() 中使用 css
- 描述 可以使用 %c 为 console 日志添加 CSS 样式,第二个参数为 css 样式字符串
console.log( "%c🚨 CRITICAL ERROR", "color: red; font-size: 20px; font-weight: bold; background: yellow; padding: 10px;");
使用 ES6 简写对象属性
- 错误写法 文字标识喝对象之间的关系打印出来不清楚,不方便调试
const user = { name: "Alice", age: 30 };
const product = { id: 123, price: 49.99 };
console.log("user", user);console.log("product", product);
- 正确写法 对象喝变量名的打印关系十分清晰,图片示例如上图
console.log({ user, product })
console.table —— 打印数组
-
描述 使用
console.log()打印数组结构不清晰,可以使用console.table以表形式打印 -
示例
const users = [
{ name: "Alice", age: 30, role: "Admin" },
{ name: "Bob", age: 25, role: "User" },
{ name: "Charlie", age: 35, role: "Moderator" }
];
console.table(users);
效果图如下
![[z_attachments/7714c88fa4896f0a194b7219c2b2fc61.png|500]]
console.trace() —— 打印函数调用栈
- 示例
function processPayment(amount) {
function innerFn() {
console.trace("Payment processing started");
}
innerFn()
}
processPayment(20)
![[z_attachments/82864c2ea185813e0a8c002b022892bc.png|500]]
console.assert() —— 条件断点
-
描述 如果断言为 false,则将一个错误消息写入控制台。如果断言是
true,没有任何反应 -
语法
console.assert(assertion, obj1 [, obj2, ..., objN]);
console.assert(assertion, msg [, subst1, ..., substN]); // c-like message formatting
| 参数 | 含义 |
|---|---|
| assertion | 条件判断,只有为 false 时才会打印 |
| msg | 一个包含零个或多个子串的 Javascript 字符串 |
| obj1...objN | 被用来输出的 Javascript 对象列表,最后输出的字符串是各个对象依次拼接的结果 |
| subst1...substN | 各个消息作为字串的 Javascript 对象 |
- 示例
console.assert(user.age >= 18, "Underage user detected!", user);
![[z_attachments/c98e8087de101c2e5a307c834f6de6eb.png|500]]
console.time() —— 时间打印
- 描述
被以下 console.time...console.timeEnd 包裹的代码,其运行时间将会被打印出来,打印格式为
flag: 时间
// 计时开始
console.time(flag)
// 代码
// 计时结束
console.timeEnd(flag)
// 打印示例
// API Call: 342.87ms
console.dir() —— 以对象形式查看
- 描述 其最常用的功能就是打印 html ,会以对象的形式打印
const div = document.createElement('div')
conso.dir(div)
![[z_attachments/Pasted image 20260305164129.png|500]]
Web API 基本认知
作用和分类
作用:使用JS去操作html和浏览器
分类:DOM(文档对象模型)、BOM(浏览器对象模型)
DOM( Document Object Model——文档对象模型 ):用来呈现以及与任意HTML或XML文档交互的API,即是浏览器提供的一套专门用来操作网页内容的功能
- 开发网页内容特效和实现用户交互
DOM树
将HTML文档以树状结构直观的表现出来,我们称之为文档树或DOM树
作用:文档树直观的体现了标签与标签之间的关系
DOM对象(重要)
DOM对象:浏览器根据html标签生成的JS对象,即html的标签(如div,p,h1)等等,在JS里会变成对象,标签的属性会变成对象的属性,修改对象属性的同时也会映射到标签属性上
- 所有的标签属性都能在这个对象上面找到
- 修改这个对象的属性会自动映射到标签身上
核心思想:把网页内容当作对象来处理
document对象
- 是 DOM 里提供的一个对象,也是整个网页最大的对象
- 它提供的属性和方法都是用来访问和操作网页内容的
eg.
document.write() - 网页所有内容都在 document 里面
获取DOM元素
查找DOM元素就是利用JS选择页面中的标签元素
方法:
- 根据CSS选择器来获取DOM元素(重点)
- 其他获取DOM元素的方法(了解)
获取 html 元素
- 语法
document.documentElement
CSS方法
选择匹配的第一个元素
- query:查询
- selector:选择
//括号内可以写 #id名 .类名等等
document.querySelector('css选择器')
注意:
- 小括号里面要加引号
- 参数:包含一个或多个有效的css选择器字符串
- 返回值:CSS选择器匹配的第一个元素,一个HTMLElement元素
选择所有匹配元素
document.querySelectorAll('css选择器')
注意:
- 参数:包含一个或多个有效的css选择器字符串
- 返回值:css选择器匹配的NodeList 对象集合(伪数组)
- 即使只有一个元素,通过
document.querySelectorAll()获取的也是一个伪数组,里面只有一个元素
伪数组:
- 有长度有索引号的数组
- 但是没有pop( ), push( )等数组方法
- 想要得到里面的每一个对象,需要通过遍历(for) 的方式获得
其他方法
//根据id获取第一个元素
document.getElementById('')
//根据标签获取一类元素 获取页面所有的div 等价于标签选择器
document.getElementsByTagName('div')
//根据类名获取元素 获取页面所有类名为 w 的元素 等价于类名选择器
document.getElementsByClassName('w')
注意:后面两个获取的结果都是伪数组
查看获取的DOM元素属性
显示指定 JavaScript 对象的属性列表,并以交互式的形式展现。输出结果呈现为分层列表,包含展开/折叠的三角形图标,可用于查看子对象的内容。
语法:
console.dir()
操作元素内容
DOM对象都是根据标签生成的,所以操作标签本质上就是操作DOM对象
修改标签元素内容,可以使用如下方式:
-
对象.innerText = 值属性 将文本内容添加/更新到任意标签位置 显示纯文本,不解析标签 -
对象.innerHTML = 值属性 将文本内容添加/更新到任意标签位置 会解析标签,多标签建议使用模板字符串 -
对象.textContent = 值属性 将原始文本内容添加/更新到任意标签位置
注意: 只要是双标签,都可以用 innerText 和 innerHTML 获取内容
单标签元素/表单元素
1. 查询元素内容: 对象.value
获取文本框字符串长度
对象.value = 值
innerText 渲染特性
使用 innerText 设置文本内容时,浏览器会自动进行以下处理:
-
合并连续空格:多个连续空格会被压缩成单个空格
-
忽略首尾空白:元素开头和结尾的空格会被完全移除
-
转换换行符:
\n会被转换为<br>但首尾换行会被忽略 -
一定触发回流:
- 计算 CSS 样式(隐藏元素的内容不会被包含)
- 生成文本布局(考虑换行、空白处理)
- 重新计算几何信息
innerHTML 渲染特性
-
作用: 获取或设置元素的 HTML 内容(包括标签)
-
特性
- 会解析 HTML 标签
- 保留所有空白字符(包括首尾空格)
- 设置时会替换元素内所有内容
- 可能触发回流: 如果插入的内容改变了元素尺寸
textcontent 渲染特性
-
作用: 获取或设置元素的原始文本内容
-
特性
- 不解析 HTML 标签(显示为纯文本)
- 保留所有空白(包括首尾空格、换行符)
- 不考虑 CSS 样式(会获取隐藏元素的内容)
- 性能最好(不触发回流)
- 绕过布局计算:
- 直接操作文本节点,不涉及元素尺寸计算
- 不影响文档流中其他元素的位置
- 避免样式计算:
- 不关心元素是否可见(
display: none的内容也会被更新) - 不处理 CSS 文本渲染规则(如空白处理)
- 不关心元素是否可见(
- 无 HTML 解析:
- 纯文本操作,无需构建 DOM 树
- 不会触发脚本执行或资源加载
- 绕过布局计算:
三者对比
| 特性 | textContent | innerText | innerHTML |
|---|---|---|---|
| 性能 | ⭐⭐⭐⭐ (最佳) | ⭐ (最差) | ⭐⭐ |
| 是否触发回流 | 极少 | 总是 | 可能 |
| HTML解析 | 否 | 否 | 是 |
| 保留空白 | 是 | 否 | 是 |
| 考虑样式 | 否 | 是 | 否 |
| 安全风险 | 无 | 无 | 有(XSS) |
操作元素属性
通过JS设置/修改标签元素属性,比如通过src更换图片
最常见的属性比如: href, title, src等
对象.属性 = 值
//举例
<body>
<img src:"">
<script>
const img = document.querySelector('img')
img.src = '值'
</script>
</body>
操作元素样式属性
可以通过JS设置/修改标签元素的样式属性
注意:修改body的属性不用抓取
document.body.style.样式属性 = 值
通过style属性⭐操作css⭐
对象.style.样式属性 = 值
//举例
<body>
<div></div>
<script>
const div = document.querySelector('div')
div.style.width = '300px'
</script>
</body>
[!tips] 注意
- 通过
style属性添加的样式是行内样式,权重较高- 值为字符串类型,需要引号包裹
- 如果样式属性含有
-,如background-color,采用小驼峰命名法,为backgroundColor- 赋值的时候需要添加css单位
读改 style 属性中的 css 属性
- 方法
// 该对象包含所有的 css 属性,但只能读取到 style 属性中的内联属性,无法读取到 clss 或 id 中设置的属性,未读到值得属性为空字符串,修改时相当于直接修改了该 css 属性得内联属性,权重高
element.style
读取计算后的 css 属性
- 方法
// 读取经过 4 步 css 属性计算后的所有 css 样式,它是只读的
window.getComputedStyle(element)
操作类名{className}操作css
修改的样式较多时,直接通过 style 属性修改比较繁琐,可以借助css类名的形式,修改标签类名为下列值
//注意,此处指定为类名了,所以引号内不需要加 .
元素.className = '类名'
// 移除所有类
元素.className = ''
使用步骤:
- 抓取需要修改的标签
- 在CSS样式中添加一个类名,里面放置修改后的样式
- 使用
{classNme}修改样式
<head>
<style>
div {
width: 100px;
height: 100px;
background-color: pink;
}
.box {
width: 300px;
height: 300px;
background-color: tomato;
margin: 100px auto;
border: 1px solid #000;
}
</style>
</head>
<body>
<div></div>
<script>
const box = document.querySelector('div')
box.className = 'box'
</script>
</body>
注意:
class是关键字,所以用className代替- 修改后检查被修改的html样式,会发现其类名变成了
className的值 className使用新值替换旧值,如果需要添加一个类,需要保留之前的类名- 多个类名都包裹在引号中,中间用空格隔开
通过 classList 操作控制css
通过 classList追加和删除类名,已解决 className 容易覆盖已有类名的问题
//追加一个类
元素.classList.add('类名')
//追加多个类
元素.classList.add('类名', '类名')
//删除一个类
元素.classList.remove('类名')
//切换一个类,即如果类名存在,则移除它,否则添加它,第二个参数是判断条件,如果符合判断条件,就添加该类,否则移除该类,添加时发现已有该类名,不做操作,删除时发现无该类名,不做操作
元素.classList.toggle('类名', 判断条件)
// 查询是否有某个类,有则返回 true ,没有则返回 false
元素.classList.contains('类名')
注意:
- 由于已经指定了是类,所以类名不用添加
. - 操作多个类名,不同类名分别使用
''包裹,并用,隔开 - 用
classList添加的是类,需要注意优先级
操作表单元素属性
抓取表单元素
//括号内为input,选择第一个表单标签
document.querySelector('input')
//根据表单的 type 属性,如checkbox, button,可以选择第一个相应元素
document.querySelector('input[type ="checkbox"]')
操作表单属性
//获取
DOM对象.属性名
//设置
DOM对象.属性名 = 新值
注意:
- 获取单标签表单元素内容不能使用
innerText,innerHTML,应该使用DOM对象.value来获取 button标签是双标签,不能使用value,应使用innerText,innerHTML
表单属性中添加则有效果,移除无效果的,一律使用布尔值表示,true 为添加了该属性, false 为移除该属性
eg. 属性包括 disabled ——禁用按钮, checked ——勾选, selected——选择,作用于<option>标签
在HTML中,这些属性的完整写法应该为 checked ="checked" ,由于属性值和属性名相同,因此可以简写为 checked
//无checked,无勾选效果
<input type="checkbox" name="" id="">
//添加勾选效果
const ipt = documen.querySelector('input')
ipt.checked = true
注意:
- 如案例中代码,改成
ipt.checked = 'true'也能够触发勾选,因为此处会发生隐式转换,字符串中除了空字符串都会转换为true
自定义属性
标准属性: 标签自带的属性,如 class , id , titile 等;可以直接使用点语法操作的属性,比如 disabled , checked , selected
自定义属性:
- 在html5中推出来了专门的
data-自定义属性 - 在标签上一律以
data-开头 - 在DOM对象上以
dataset对象方式获取 data 数据; set 集合 语法:元素对象.dataset.自定义元素名
<div data-id="1">1</div>
打印自定义属性
const one = document.querySelector('div')
//打印自定义属性集合
console.log(one.dataset)
//打印单个自定义属性
console.log(one.dataset.自定义属性名)
注意:
- 自定义属性全部都是字符串型
- 自定义属性名为
data-之后的部分 - 打印自定义属性集合用的方法是
console.log(变量.dataset),打印标准属性集合用的是console.dir()
计时器——间歇函数
使用场景: 每隔一段时间需要自动执行一段代码,不需要手动触发,如网页中的倒计时
定时器函数可以开启和关闭定时器
开启计时器
// Interval 间歇
setInterval(函数, 间隔时间)
// 匿名函数
setInterval(function() {
函数体
}, 间隔时间)
// 具名函数
function 函数名() {
函数体
}
// 方法1,不能加小括号
setInterval(函数名, 间隔时间)
// 方法2,加引号和小括号
setInterval('函数名()', 间隔时间)
作用:
- 每隔一段时间调用这个函数
- 间隔时间单位是毫秒
注意:
- 具名函数时,小括号内只写函数名,不能加小括号
函数名(),否则表示的是调用函数,显现的是函数返回值,一定需要加小括号的话,详见代码块内方法2 - 执行到
setInterval(函数, 间隔时间)时,会先执行间隔时间,再执行函数 - 定时器返回的是一个id数字
- 定时器的间歇时间是最小间歇时间,实际上受到其他因素的影响可能会更大,因此定时器实际上是不准的
查询计时器id
获取定时器id时必须使用 let ,因为定时器的关闭和开启会导致重新赋值,使用const会报错
let n = setInterval(function() {}, 1000)
console.log(n)
关闭计时器及重新开启
// 关闭定时器
let 变量名 = setInterval(函数, 间隔时间)
clearInterval(变量名)
//重新开启定时器(变量名跟上面保持一致)
变量名 = setInterval(函数, 间隔时间)
关闭计时器与回调函数
- 注意:
- 显式使用
clearInterval('变量名')才能清除定时器,return只会终止正在运行的回调函数,如果仅仅使用return,定时器会继续按照时间间隔反复触发回调函数 - 页面被卸载后(如页面跳转等),定时器会被浏览器自动回收
- clearInterval 只能终止正在运行的计时器,如果有变量引用了计时器(即 let 变量 = setInterval()),则变量仍会保留计时器 id 的数值
- 显式使用
事件监听
事件: 编程时系统内发生的动作或者发生的事情,如用户在网页上单击一个按钮
事件监听: 让程序检测是否有事件产生,若有事件触发,立即调用函数做出回应,也称为绑定事件或注册事件,如鼠标经过显示下拉菜单,点击播放轮播图等
事件监听三要素:
-
事件源: 哪个dom元素被事件触发了,要获取dom元素
-
事件类型: 用什么方式触发事件
-
事件调用的函数: 要做什么事情
元素对象.addEventListener('事件类型', 执行函数)
注意:
- 事件类型要加引号
- 函数是事件类型触发后再执行,每次执行一次
- 执行函数写法和定时器一样
事件监听完整写法
- 语法
addEventListener(type, listener)
// [] 中的部分表示可以省略
addEventListener(type, listener[, options])
addEventListener(type, listener[, useCapture])
useCapture
-
含义 一个简单的布尔值标志,用于指定监听器是在捕获阶段还是冒泡阶段被调用
-
取值
true监听器在事件捕获阶段触发false: 默认值,可省略 监听器在事件冒泡阶段触发
-
实例
element.addEventListener('click', handleClick, true); // 捕获阶段触发
element.addEventListener('click', handleClick, false); // 冒泡阶段触发 (等同于省略)
element.addEventListener('click', handleClick); // 冒泡阶段触发 (false 被省略)
options
-
内容 一个配置对象 ,提供了
capture,once,passive,signal四种属性 -
语法
// 语法
元素对象.addEventListener('事件类型', function () {}, {
once: 取值,
passive: 取值,
signal: controller.signal
}
-
capture同useCapture -
oncetrue监听器在触发一次后会自动被移除,适用于一次性事件false: 默认值 监听器一直存在,直到被显式移除
-
passive浏览器需要等待监听器执行完毕,再进行滚动,此属性控制浏览器是否等待监听器执行完毕true向浏览器承诺此监听器不会调用event.preventDefault()false: 默认值 监听器可能会调用preventDefault()(阻止浏览器滚动)
-
signal与AbortController配合使用,可以集中控制移除监听器- 取值:
signal: controller.signal
- 取值:
-
注意
options需要使用大括号包裹
-
实例
button.addEventListener('click', function(event) {
console.log('Button clicked! This will only happen once.');
// 这里不能调用 event.preventDefault(),因为设置了 passive: true
}, {
once: true,
passive: true, // 虽然对click通常不需要passive,但语法演示
signal: controller.signal
});
⭐JS调用事件⭐
元素对象.事件类型()
理解: 事件监听老写法是 元素对象.on事件类型 = function() {} ,可以理解为将函数赋值给元素对象,所以添加小括号就能调用函数
实例
// 会调用btn所有的click监听器
btn.click()
⭐实例方法⭐
| 实例方法 | 含义 |
|---|---|
| HTMLElement.click() | 模拟鼠标点击该元素,触发其鼠标点击事件 |
事件监听老版本
元素对象.on事件类型 = function() {
函数体
}
// 存在覆盖情况
元素对象.on事件类型 = function() {
alert('111')
}
元素对象.on事件类型 = function() {
alert('222')
}
//最后只弹出222
注意:
on方式会被覆盖(可通俗理解为等号赋值,重复赋值会覆盖),addEventListener方式可以绑定多次,拥有事件更多特性on方式只能做冒泡,addEventLister既可以冒泡,也可以捕获
表单元素
-
获得焦点
focus, 鼠标点击表单元素 -
失去焦点
blur,鼠标点击表单元素之外的任意地方 -
输入值变化
input,<input>,<select>,<textarea>的值发生变化 (输入、粘贴、JS修改等)
// focus
元素对象.addEventListner('focus', function () {})
// blur
元素对象.addEventListner('blur', function () {})
// input
元素对象.addEventListner('input', function () {})
键盘事件
-
触发方式 通过键盘触发
-
适用对象 所有表单元素,以及
contenteditable="true"的元素 -
键盘事件
keydown: 键盘按下触发,键盘不抬起会一直触发keyup: 键盘抬起触发 -
语法
// keydown
元素对象.addEventListner('keydown', function () {})
// keyup
元素对象.addEventListner('keyup', function () {})
事件类型表
#事件类型表
表1:用户交互事件 (User Interaction Events)
| 事件类型 | 事件解释 | 经典应用 |
|---|---|---|
click | 元素上按下并释放主鼠标按钮或轻点 | 按钮点击、导航、选择 |
dblclick | 元素上快速连续点击两次 | 进入编辑、快速操作 |
mousedown | 在元素上按下鼠标按钮 | 拖拽开始、自定义按下效果 |
mouseup | 在元素上释放按下的鼠标按钮 | 拖拽结束、点击完成 |
mousemove | 在元素上移动鼠标指针 | 拖拽、绘图、鼠标跟踪 |
mouseover | 鼠标指针移入元素或其子元素 | 显示悬停菜单/提示、高亮 |
mouseout | 鼠标指针移出元素或其子元素 | 隐藏悬停菜单/提示、取消高亮 |
mouseenter | 鼠标指针移入元素本身 (不冒泡, 不因子元素触发) | 精确控制元素自身悬停效果 |
mouseleave | 鼠标指针移出元素本身 (不冒泡, 不因子元素触发) | 精确控制元素自身离开效果 |
keydown | 按下键盘任意键 (按住会持续触发) | 快捷键、游戏控制、实时搜索 |
keyup | 释放键盘按键 | 完成按键操作、停止持续动作 |
focus | 元素获得焦点 (如点击输入框, Tab切换) | 显示提示、改变获得焦点样式 |
blur | 元素失去焦点 | 表单验证、保存输入、隐藏提示 |
touchstart | 手指触摸屏幕 | 移动端手势(滑动/缩放)、自定义触摸交互 |
touchmove | 手指在屏幕上移动 | |
touchend | 手指离开屏幕 | |
touchcancel | 触摸被系统事件打断 | |
scroll | 元素内的滚动条被滚动 | 无限滚动、导航栏显隐、滚动动画 |
contextmenu | 用户尝试打开上下文菜单 (如右键点击) | 阻止默认菜单、显示自定义菜单 |
select | 用户在文本输入框中选择文本 | 提供文本操作(复制/加粗) |
表2:表单事件 (Form Events)
| 事件类型 | 事件解释 | 经典应用 |
|---|---|---|
submit | 用户尝试提交表单 (点击提交按钮或输入框按Enter) 监听在<form>元素上 | 最终验证、阻止默认提交(AJAX)、收集数据 |
input | <input>, <select>, <textarea>的值发生变化 (输入、粘贴、JS修改等) | 实时搜索建议、即时验证、字符计数 |
change | 元素值改变且用户提交更改 (通常失焦后触发; <select>选择即触发,上传文件时,用户选择文件后触发) | 失焦后验证、根据选择加载内容 |
focus | 表单元素获得焦点 | 显示输入提示、激活样式 |
blur | 表单元素失去焦点 | 离开时验证、保存输入 |
表3:文档/窗口事件 (Document/Window Events)
| 事件类型 | 事件解释 | 经典应用 |
|---|---|---|
DOMContentLoaded | 初始HTML文档完全加载和解析完成 (DOM树就绪),不等待样式/图片等资源 | 最常用初始化 - 操作DOM、绑定事件监听器 |
load | 整个页面及所有依赖资源(图片/样式/脚本)加载完成 (监听在window或元素) | 资源就绪后初始化、显示“加载完成” |
resize | 浏览器窗口大小被调整 (监听在window) | 响应式布局调整、图表重绘 |
beforeunload | 窗口/文档即将卸载 (关闭、刷新、导航) | 谨慎使用提示用户保存未保存更改 |
unload | 窗口/文档正在卸载 | 清理操作(保存状态、关闭连接) |
error (资源) | 资源(<img>, <script>, <link>)加载失败 | 显示占位图、错误处理 |
error (全局 window) | 未捕获的JavaScript运行时错误 (监听在window.onerror) | 错误捕获与报告 |
表4:视频事件
| 事件名称 | 类别 | 触发时机 | 常用属性 |
|---|---|---|---|
play | 播放控制 | 视频开始播放时(包括暂停后恢复) | currentTime |
pause | 播放控制 | 视频暂停时 | paused |
ended | 播放控制 | 视频播放结束时 | ended |
seeking | 播放控制 | 用户开始拖动进度条时 | seeking |
seeked | 播放控制 | 进度条跳转完成时 | currentTime |
timeupdate | 播放控制 | 播放位置变化时(每秒4-66次) | currentTime, duration |
ratechange | 播放控制 | 播放速度改变时 | playbackRate |
loadstart | 加载状态 | 开始加载视频资源时 | networkState |
progress | 加载状态 | 正在下载视频数据时(周期性触发) | buffered |
loadedmetadata | 加载状态 | 元数据加载完成(时长/尺寸等) | duration, videoWidth, videoHeight |
loadeddata | 加载状态 | 当前帧数据加载完成时 | readyState |
canplay | 加载状态 | 有足够数据开始播放时 | readyState |
canplaythrough | 加载状态 | 预计可不缓冲播放完成时 | readyState |
waiting | 加载状态 | 因缓冲不足暂停时 | readyState |
stalled | 加载状态 | 尝试加载但无数据返回时 | networkState |
suspend | 加载状态 | 主动暂停加载时 | networkState |
abort | 加载状态 | 加载被终止时(非错误) | error |
error | 加载状态 | 加载发生错误时 | error.code |
emptied | 加载状态 | 媒体资源变为空时(如调用load()后) | networkState |
volumechange | 其他 | 音量或静音状态改变时 | volume, muted |
durationchange | 其他 | 视频时长变化时(如切换源) | duration |
resize | 其他 | 视频尺寸变化时(实验性) | videoWidth, videoHeight |
enterpictureinpicture | 其他 | 进入画中画模式时 | document.pictureInPictureElement |
leavepictureinpicture** | 其他 | 退出画中画模式时 | document.pictureInPictureElement |
表5:其他重要事件 (Other Important Events)
| 事件类型 | 事件解释 | 经典应用 |
|---|---|---|
error | (见文档/窗口事件表) | (见文档/窗口事件表) |
contextmenu | (见用户交互事件表) | (见用户交互事件表) |
select | (见用户交互事件表) | (见用户交互事件表) |
表格使用提示:
-
事件委托: 对于动态生成或大量的子元素,优先在父元素上监听事件 (如
click),利用事件冒泡,通过event.target判断实际触发元素,提高性能。 -
addEventListener: 始终优先使用element.addEventListener('eventname', handler)来绑定事件,避免覆盖已有处理程序,且能控制捕获阶段。 -
事件对象 (
event): 事件处理函数接收的事件对象包含丰富信息,如触发元素(target/currentTarget)、鼠标位置(clientX,clientY)、按键(key)、表单值等,是处理事件的关键。 -
性能注意: 高频事件 (如
scroll,mousemove,resize) 的处理函数应尽量轻量,或使用防抖(debounce)/节流(throttle)技术优化
补充事件
transitionend
- 作用 CSS 过渡完成时触发触发
表单事件
重置表单
- 语法
const form = document.querySelector('form')
form.addEventListener('submit', function () {
// 重置表单
this.reset()
})
键盘事件补充
KeyboardEvent: code 与 key
- 描述
在 Web 开发中,键盘事件(KeyboardEvent)的code和key是两个最常用的属性,它们的核心区别在于:code代表物理按键位置,key代表按键产生的字符值。
核心区别对比
| 属性 | 含义 | 特点 | 示例 (按下 'A' 键) | 示例 (按下 Shift + 'A') |
|---|---|---|---|---|
| code | 物理键码 | 固定不变。代表键盘上的物理位置,不受键盘布局、系统语言、Shift/Caps Lock 的影响。 | "KeyA" | "KeyA" |
| key | 键值 | 动态变化。代表实际产生的字符或功能。受键盘布局、Shift、输入法等影响。 | "a" | "A" |
使用场景分析
场景一:判断功能键(使用 code)
// 这里的意图是:只要用户按下了那个物理位置的"回车键",就执行逻辑
if (code === 'Enter' || code === 'NumpadEnter') {
// ...
}
- 为什么要用
code? 因为你想捕获的是"按下回车"这个动作code可以区分主键盘的回车 (Enter) 和小键盘的回车 (NumpadEnter) 对于功能键(ArrowUp, Space, Delete),使用code通常更稳定,因为它不会因为用户切换了键盘布局而改变
场景二:处理文本输入(使用 key)
// 这里的意图是:获取用户实际想输入的字符,赋值给单元格
const isPrintable = key.length === 1 && ...
if (isPrintable ...) {
row[column.field] = key // <--- 关键点:这里需要 'a' 或 'A',而不是 'KeyA'
}
- 为什么要用
key? 因为你需要知道用户到底输入了什么 如果按 Shift + A,code还是'KeyA',把code赋值进去,单元格就会显示KeyA,这显然不对 而key会自动处理大小写,返回'A',这才是用户想要的输入内容
极端示例(帮助理解)
假设使用 AZERTY(法语)键盘布局(Q 和 A 的位置是互换的):
// 当你按下键盘左上角那个字母键时:
// (在 QWERTY 键盘上是 Q,在 AZERTY 上是 A)
// code: 永远是 "KeyQ"
// (因为它的物理位置就是 QWERTY 键盘的 Q 键位置)
// key: 会变成 "a"
// (因为在法语模式下,这个键输出 a)
总结
-
做快捷键、游戏控制、功能导航时:优先用
code例如:WASD 移动,不管什么语言布局,物理位置不变 -
处理文本输入、搜索框、编辑器时:必须用
key因为你需要用户输入的真实字符
[!tip] 记忆口诀
code= 物理位置(哪里按的)
key= 实际字符(输入什么)
事件对象
-
含义 是一个对象,记录事件触发时的相关信息,如鼠标点击事件中,事件对象储存鼠标点击的位置等信息
-
使用场景 判断用户按下的按键,从而做出反应,如按回车发布新闻 判断用户点击的元素,从而做出反应
-
语法 事件绑定的回调函数中,第一个参数就是事件对象 一般命名为
event,ev,e
元素对象.addEventListener('click', function (e) {})
-
注意 一般在回调函数体内使用事件对象 事件对象为触发事件时鼠标下的最具体元素,如果元素有堆叠则指向最上层元素,如不想让上层元素影响事件执行,可以给它设置
pointer-events: "none";,不触发鼠标事件,从而实现事件穿透 -
事件对象获取鼠标的页面坐标
e.pageX
e.pageY
- 示例
<style>
* {
padding: 0;
margin: 0;
}
div {
width: 300px;
height: 200px;
}
.middle,
.brother {
width: 400px;
height: 400px;
}
.middle {
position: relative;
background-color: lightblue;
}
.layer {
width: 200px;
height: 200px;
position: absolute;
left: 0;
top: 0;
background-color: pink;
}
.brother {
position: absolute;
left: 0;
top: 0;
background-color: tomato;
/* display: none; */
/* pointer-events: none; */
}
</style>
<script>
// brother未添加 display: none; 时,位于最上层,挡住了middle,阻止了事件发生,此时 e.target时.brother,但middle事件未触发
// brother添加 display: none; 或 pointer-events: none; 时,middle位于最上层,可以触发事件, e.target指向此时鼠标下的最具体元素
document.querySelector('.middle').addEventListener('mousemove', function (e) {
console.log(e.target);
})
</script>
获取事件对象
- 部分常用属性
| 属性 | 含义 |
|---|---|
type | 获取当前事件类型 |
clientX , clientY | 获取光标相对于浏览器可见窗口左上角的位置 |
offsetX , offsetY | 获取光标相对于当前DOM元素左上角的位置 |
key | 用户按下的键盘键的值(现在不提倡使用 keyCode ) |
| touches, changedtouches | 前者是所有触摸点的信息,后者是发生变化的触摸点信息,通常用于touch相关事件中 |
| timeStamp | 时间戳,相对于页面开始加载的时间戳 |
环境对象
-
环境对象 函数内部特殊的变量
this(也是对象) ,代表当前函数运行时所处的环境 -
特性
-
函数的调用方式不同,
this指代的对象不同 -
谁调用,
this就指向谁 普通函数: 每个函数里面都有环境对象,指向window事件: 指向调用者 案例:btn.addEventListener('click', function () {}的调用者是btn箭头函数: 没有this
-
-
注意
- 直接调用函数,实际上相当于
window.函数,所以this指向window - 在定时器中,
this指向window
- 直接调用函数,实际上相当于
回调函数
-
回调函数 如果将函数A做为参数传递给函数B时,我们称函数A为回调函数 ,回调即,回头调用
-
常见回调函数
setInterval(function () {}, 间隔时间)中的function元素对象.addEventListener('事件类型', function () {})中的function -
特性 不会立即执行,而是等待执行条件触发,如定时器定时,事件触发
-
例子
function fn() {
console.log('我是回调函数')
}
// fn传递给了 setInterval ,fn是回调函数
setInterval(fn, 1000)
- 注意 回调函数本质是函数,用的时候当作参数 使用匿名函数作为回调函数比较常见
事件流
-
事件流 事件完整执行过程中的流动路径 ![[z_attachments/105be48ac3a2d6ba2580c64f00bf85f.jpg|400]]
-
完整过程 事件捕获→事件目标阶段→事件冒泡
事件捕获
-
事件捕获 从DOM的根元素开始去执行对应的事件(从外到里)
-
代码
元素对象.addEventListener(事件类型, 事件处理函数, 是否使用捕获机制)
- 说明
addEventListener第三个参数传入true代表是捕获阶段触发 传入false代表冒泡阶段触发,默认值为false
// 值为 true ,触发事件捕获,依次弹出我是爷爷,我是爸爸,我是儿子
<div class="father">
<div class="son"></div>
<div>
<script>
const father = document.querySelector('.father')
const son = document.querySelector('.son')
document.addEventLsistener('click', function () {
alert('我是爷爷')
}, true)
father.addEventLsistener('click', function () {
alert('我是爸爸')
}, true)
son.addEventLsistener('click', function () {
alert('我是儿子')
}, true)
</script>
事件冒泡
-
事件冒泡 当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中一次被触发,这一过程被称为事件冒泡(当一个事件触发后,会依次向上调用所有父级元素的同名事件)
-
说明
// 默认值为false ,可省略,触发事件捕获,依次弹出我是儿子,我是爸爸,我是爷爷
<div class="father">
<div class="son"></div>
<div>
<script>
const father = document.querySelector('.father')
const son = document.querySelector('.son')
document.addEventLsistener('click', function () {
alert('我是爷爷')
})
father.addEventLsistener('click', function () {
alert('我是爸爸')
})
son.addEventLsistener('click', function () {
alert('我是儿子')
})
</script>
阻止冒泡
-
目的 将事件限制在当前元素内
-
语法 propagation : 流动,传播
事件对象.stopPropagation()
-
注意 此方法可以阻断事件流动传播,对事件捕获和事件冒泡都有效
-
例子
<div class="son"></div>
<script>
const son = document.querySelector('.son')
son.addEventLsistener('click', function (e) {
alert('我是儿子')
// 阻止事件流动传播
e.stopPropagation()
})
</script>
解绑事件
- 传统
on事件 直接使用null覆盖即可实现事件解绑
// 绑定事件
btn.onclick = function () {
alert('点击了')
}
// 解绑事件
btn.onclick = null
addEventListener事件监听
// 绑定事件
function fn() {
alert('点击了')
}
btn.addEventListener('click', fn)
// 解绑事件
btn.removeEventListener('click', fn)
- 注意
对于
addEventListener来说,匿名函数无法被解绑
区别: mouseover 和 mouseenter
- 区别
| 属性 | 是否冒泡 | 备注 |
|---|---|---|
mouseover , mouseout | 是 | 多次触发事件,影响性能 |
mouseenter , mouseleave | 否 | 推荐 |
- 实例
- 解读
- 鼠标从外面进入 dad,触发事件监听
mouseover,打印 dad 进入 - 鼠标从 dad 进入 son,触发事件监听
mouseout,打印 dad 离开 - 触发事件 son 的
mouseover,但此事件没有设置监听器,所以没有反应,开始向父级冒泡 - 父级 dad 接受 son 的冒泡,再次触发事件监听
mouseover,再次打印 dad 进入
- 鼠标从外面进入 dad,触发事件监听
- 控制台结果 dad 进入 dad 离开 dad 进入
- 解读
// mouseover
<div class="dad">
<div class="son"></div>
</div>
<script>
const dad = document.querySelector('.dad')
const son = document.querySelector('.son')
dad.addEventListener('mouseover', function () {
console.log('dad进入')
})
dad.addEventListener('mouseout', function () {
console.log('dad离开')
})
</script>
区别:传统 on 注册和事件监听注册
-
传统
on注册- 同一个对象,后面注册的事件会覆盖前面注册的事件(同一类事件)
- 直接使用null覆盖偶可以实现事件解绑
- 都是冒泡阶段执行
-
事件监听注册
- 语法
元素对象.addEventListener(事件类型, 事件处理函数, 获取捕获或冒泡阶段) - 后注册事件不会覆盖前注册事件(同一类事件)
- 可以通过第三个参数确定是否在冒泡阶段执行
- 必须使用
元素对象.removeEventListener(事件类型, 事件处理函数, 获取捕获或冒泡阶段) - 匿名函数无法被解绑
- 语法
事件委托
-
概念 利用事件流的特征(事件冒泡)解决一些开发需求的知识技巧
-
优点 减少注册次数(使用for循环遍历需要注册多次),可以提高程序性能
-
原理
- 给父元素注册事件,当触发子元素时,会冒泡到父元素身上,从而触发父元素的事件
- 通过环境对象
e,定位触发父级冒泡事件的子元素,语法为e.target,从而实现对子级元素的事件 - 添加判断条件
e.target.tagName可以实现对特定标签的子元素的事件控制,其他元素同理
-
实例
// 利用事件委托,让触发 click 的小li变成红色
<body>
<ul>
<li>第1个孩子</li>
<li>第2个孩子</li>
<li>第3个孩子</li>
<li>第4个孩子</li>
<li>第5个孩子</li>
<p>我不需要变颜色</p>
</ul>
<script>
// 点击每个小li,当前li变为红色
const ul = document.querySelector('ul')
ul.addEventListener('click', function (e) {
// 判断对象是否为 li
if(e.target.tagName === 'LI') {
e.target.style.color = 'red'
}
})
</script>
</body>
- 注意
tagName的值为全大写- 可以通过自定义属性
data-定义序号通过序号定位其他元素从而形成元素之间的联动效果
阻止元素默认行为
-
含义 某些情况下需要阻止默认行为的发生,如链接的跳转、表单域跳转等等
-
语法
e.preventDefault()
[!tips] 注意
- 使用 keyup 事件时,e.preventDefault() 会失效,因为 keyup 事件发生在默认行为发生后,需要使用 keydown
其他事件
页面加载事件
load 事件
-
含义 外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件
-
应用场景
- 有时候需要等待页面资源加载完成才能处理某些事情
- 老代码喜欢把 script 写在 head中,此时直接找 dom 元素会找不到
-
语法
// 等待页面所有资源加载完毕,再执行回调函数
window.addEventListener('load', function () {})
- 注意
- 不光可以监听整个页面资源加载完毕,也可以针对某个资源绑定
load事件
- 不光可以监听整个页面资源加载完毕,也可以针对某个资源绑定
DOMCcontentLoaded 事件
-
含义 当初始的 HTML 文档被全部加载和解析完成之后,
DOMContentloaded事件被触发,无需等待样式表、图像等完全加载 -
作用 监听页面 DOM 加载完毕
-
语法
// DOM 树加载完成时
document.addEventListener('DOMContentLoaded', function () {})
元素滚动事件
-
含义 滚动条在滚动时持续触发的事件
-
应用场景 网页检测用户滚动页面至某一区域后做一些处理,如固定导航栏,返回底部等 元素内部,检测用户滚动页面至某一区域后做一些处理
-
事件名
scroll -
语法
// 页面滚动事件
window.addEventListener('scroll', function () {})
获取位置—— scrollLeft 和 scrollTop 属性
![[z_attachments/Pasted image 20250612095208.png|400]]
-
作用
- 获取被卷去的大小
- 获取元素内容往左往上滚出去看不到的距离
- 这两个值是可读写的,不带单位
-
可读写
// 可读
// 获取 html 元素(页面)滚动距离
document.documentElement.scrollLeft
document.documentElement.scrollTop
//获取 div 元素滚动距离
const div = document.querySelector('div')
div.scrollTop
// 可写
document.documentElement.scrollTop = 800
滚动到指定坐标
-
实现方式
scrollTo()方法 -
语法
//元素为页面时写window, x,y分别为距离左侧、顶部的距离,不加单位
元素.scrollTo(x, y)
页面尺寸事件
-
含义 改变窗口尺寸时会触发的事件,事件名为
resize -
语法
window.addEventListener('resize', function () {})
获取元素宽高
- 获取宽高
- 获取元素可见部分宽高,包括内容区域和内边距(不包含border,margin,滚动条等)
clientWidth和clientHeight属性
元素尺寸与位置
[[#读取元素尺寸集合|返回读取元素尺寸集合]]
-
作用 通过js,得到元素在页面中的尺寸与位置,从而省去计算
-
获取宽高 ——
offsetWidth和offsetHeight- 获取元素自身宽高,包括元素自身设置的宽高、padding、border
- 获取出来的是数值,方便计算
-
获取位置 ——
offsetLeft和offsetTop- 获取元素距离自己定位父级元素(即添加了
position属性的父级元素)的左、上距离 - 这两个属性是只读的
- 获取元素距离自己定位父级元素(即添加了
-
获取位置 ——
element.getBoundingClientRect()- 作用 方法返回元素的大小及其相对于视口的位置
- 语法
对象元素.getBoundingClientRect()
![[z_attachments/f2f00f2c2b1d83ee310f30af49f6bdc.jpg|400]]
总结
| 属性/方法 | 作用 | 说明 |
|---|---|---|
scrollLeft 和 scrollTop | 被卷去的头部和左侧 | 配合页面滚动来用,可读写 |
clientWidth 和 clientHeight | 获得元素宽度和高度 | 不包含 border , margin , 滚动条,用于js获取元素大小,只读 |
offsetWidth 和 offsetHeight | 获得元素宽度和高度 | 包括 border , padding , 滚动条等,只读 |
offsetLeft 和 offsetTop | 获取元素距离自己定位父级元素的左、上距离 | 获取元素位置的时候使用,只读 |
element.getBoundingClientRect() | 获取元素的尺寸和相对于视口的位置 |
Date —— 日期对象
-
日期对象 用来表示时间的对象
-
作用 获取当前系统时间
实例化
-
实例化 使用
new关键字调用函数行为被称为实例化 -
使用实例化获得当前时间
// 获得当前时间
const date = new Date()
// 获得指定时间 格式:年-月-日 时:分:秒
const date = new Date('2022-5-1 08:30:00')
// 隐式调用 Data.UTC() ,月份从 0 开始算,一月为 0 ,具体时间格式为 年月日时分秒毫秒,中间使用 , 分隔,不填则为 0
const date = new Date(2022,4,1,8,33,0)
日期对象方法
-
使用场景 日期对象返回的数据不能直接使用,需要转换为实际开发中常用的格式
-
属性 年月日星期时分秒
| 方法 | 作用 | 说明 |
|---|---|---|
getFullYear() | 获得年份 | 获取四位年份 |
getMonth() | 获取月份 | 取值为 0-11 |
getDate() | 获取月份中的日期(获取日期对象为某月几号中的几号) | 不同月份取值不同 |
getDay() | 获取星期 | 取值为 0-6 |
getHours() | 获取小时 | 取值为 0-23 |
getMinutes() | 获取分钟 | 取值为 0-59 |
getSeconds() | 获取秒 | 取值为 0-59 |
toLocalString() | 返回日期对象的字符串 | 2025/6/15 12:36:03 |
toLocalDateString() | 返回日期对象的日期字符串 | 2025/6/15 |
toLocalTimeString() | 返回日期对象的时间字符串 | 12:36:03 |
toISOString() | 返回YYYY-MM-DDTHH:mm:ss.sssZ 或 ±YYYYY-MM-DDTHH:mm:ss.sssZ)。时区始终为 UTC,由后缀 Z 表示 | 2025-10-29T08:20:10.721Z |
-
注意
getDate()获取的是几号,getDay()获取的是星期- 表示当前月份需要加1
- 星期取值为0,代表的是星期天
-
语法
// 已年份为例
日期对象.getFullYear()
new Date().getFullYear()
时间戳
-
时间戳 时间戳(Timestamp) 是指从特定时间点(称为"纪元")开始计算的毫秒数。理解时间戳是处理日期和时间操作的基础 纪元时间为 1970年1月1日 00:00:00 UTC
-
使用场景 添加倒计时
-
算法
- 将来的时间戳 - 现在的时间戳 = 剩余时间毫秒数
- 剩余时间毫秒数转换为剩余时间的 年月日时分秒 就是倒计时时间
-
获取当前时间戳
- 简写
+new Date()必须实例化 - 使用
getTime()方法 必须实例化 - 使用
Date.now()不需要实例化,但只能得到当前时间戳
- 简写
-
计算今日已过时间(ms)
// 1. 获取当前时间
const now = new Date();
// 2. 创建当前时间的副本(避免修改原始对象)
const startOfDay = new Date(now);
// 3. 将副本时间设置为午夜 00:00:00.000
startOfDay.setHours(0, 0, 0, 0);
// 4. 计算时间差(今日已过毫秒数)
const millisecondsPassed = now.getTime() - startOfDay.getTime();
- 注意
-
不能直接使用时间戳获取当天已过去时间(因为时间戳是格林尼治时间,跟北京时间有8小时时差)
-
日期对象.sethours(hoursValue,minutesValue,secondsValue,msValue)- 根据本地时间为一个日期对象设置小时数,返回从 1970-01-01 00:00:00 UTC 到更新后的日期对象实例所表示时间的毫秒数
- 会直接修改调用它的日期对象
-
节点操作
DOM节点
-
DOM节点 DOM树里的每个内容都称之为DOM节点
-
节点类型
- 元素节点
- 所有的标签 比如div , body
- html 是根节点
- 属性节点 所有的属性,比如 href
- 文本节点 所有的文本
- 其他 ![[z_attachments/79d52a0feba326e31868049fd200f06.jpg|600]]
- 元素节点
查找节点
-
方式 通过节点关系查找目标节点
-
节点关系 针对的找亲戚返回的是对象
- 父节点
- 子节点
- 兄弟节点
-
父节点查找
- 含义 查找最近父级
- 语法(找不到返回
null)子元素.parentNode
-
子节点查找
- 查找最近子级
childNodes属性 获得所有子节点,包括文本节点(空格、换行)、注释节点等children属性- 仅获取的所有元素节点
- 返回伪数组
- 语法
// 所有子节点
父元素对象.childNodes
//所有元素节点
父元素对象.children
- 兄弟节点查找
nextElementSibling属性 下一个兄弟节点previousElementSibling属性 上一个兄弟节点- 语法
// 下一个兄弟节点
元素对象.nextElementSibling
// 上一个兄弟节点
元素对象.previousElementSibling
element.closest(selectors) —— 查找符合某选择器的最近祖先元素或本身
-
作用 作用匹配特定选择器且离当前元素最近的祖先元素(也可以是当前元素本身),如果不匹配则返回 null
-
语法
// 选择器可以包含多个,就像 css 选择器复合使用那样,例如 "p:hover, .toto + q"
element.closest(selectors)
增加节点
-
步骤
-
创建节点
-
追加节点(把新节点放到指定元素内部)
-
-
创建节点
// 创建新元素节点
document.createElement('标签名')
- 追加节点
-
插入到父元素的最后一个子元素
父元素.appendChild(newNode)父元素.append()两者差异 若该节点已在父元素内则将其调换到最后一个位置 -
插入到父元素中的某个子元素之前
父元素.insertBefore(newNode, referenceNode) -
插入为父元素的第一个子元素
-
const ul = document.querySelector('ul')
const li = document.querySelector('li')
ul.insertBefore(li, ul.children[0])
克隆节点
-
步骤
- 复制一个原有节点
- 把复制的节点放入指定元素内部
-
语法
// 克隆一个已有的节点
元素.cloneNode(布尔值)
- 布尔值
true:克隆当前节点及后代节点false(默认值):仅克隆当前节点(仅克隆HTML标签,不克隆内容)
删除节点
- 语法
// removeChild方法
父元素.removeChild(要删除的元素)
// remove 方法
要删除的元素.remove()
- 例子
<body>
<ul>
<li>没用了</li>
</ul>
<script>
ul.removeChild(ul.children[0])
</script>
</body>
- 注意
- 在 JS 原生 DOM 操作中,必须通过父元素删除节点
- 删除节点和隐藏节点(
display:none)的区别:隐藏节点仍然存在,删除节点后对应节点会从 HTML 中消失
M 端事件
触屏事件
-
别称:触摸事件
-
事件名称:
touch -
touch对象 代表一个触摸点,触摸点可以是一根手指或者一根触摸笔 -
作用 响应用户手指(或触控笔)对屏幕或者触控板操作
-
常见触屏事件
| 触屏事件 | 说明 |
|---|---|
touchstart | 手指触摸到一个 DOM 元素时触发 |
touchmove | 手指在一个 DOM 元素上滑动时触发 |
touchend | 手指从一个 DOM 元素上移开时触发 |
[!tips] 注意
- touchmove 提供的位移是累计位移,例如第一次触发向右2,第二次向右3,那它会显示向右5
- 在触屏事件中,touches 数组,记录了所有屏幕上触摸点的信息,changedtouches 数据记录了屏幕上有变化的手指的信息
- 在 touchend 中,如果本来只有一根手指,那么此时手指已经离开,touches 没有触摸点,是一个空数组,但 changedtouches 会记录刚刚离开的手指的信息