浅记JS知识点
参考资料
- MDN Web Docs
- 阮一峰的 ES6 入门
- Runoob JavaScript 教程
- 【JS】DOM 文档操作大全
- 在线编辑:https://jsfiddle.net/ https://codepen.io/
基础知识
基本概念
JavaScript 是一门 解释型、弱类型、基于原型 的脚本语言,主要用于浏览器端(也可用于 Node.js)
它对大小写敏感,例如 var 与 Var、function 与 Function 是不同的标识符
字符串可以使用单引号或双引号
HTML 定义了网页的内容,CSS 描述了网页的布局
JavaScript 则是控制了网页的行为
Javascript 脚本代码可被放置在 HTML 页面的 <body> 和 <head> 部分中,要求位于 <script> 与 </script> 标签之间
也可以外部引用脚本,将 <script src="main.js" defer></script> 放在 <head> 内
defer 能保证脚本在文档解析完毕后执行,async 则会在下载完成后立即执行(不保证顺序)
ES6+ 表示 2015 年发版的 ECMAScript 6.0 及以后的版本
其中加入了一些新的特性,以弥补 ES5 的不足,例如块级作用域、类等
-
打印输出
1、使用console.log('hello world')写入到浏览器的控制台
2、使用window.alert('hello world')弹出警告框
3、使用document.write('hello world')方法将内容写到 HTML 文档中
4、使用innerHTML写入到 HTML 元素 -
字面量
即固定值 -
变量
用于存储数据值,可以在声明的同时赋值,也可以之后再赋值 -
关键字
JavaScript 有一些保留的关键字,例如if else float try等等-
void 关键字
不返回任何值,但是括号内的表达式还是要运行
如void(alert("Warnning!"))href="#"与href="javascript:void(0)"的区别
#包含了一个位置信息,默认的锚是#top也就是网页的上端;而javascript:void(0),仅仅表示一个 空链接
#可以用来定位页面的具体位置,格式为:# + id;而如果要定义一个无跳转的链接,用javascript:void(0)
-
-
注释
单行注释//,多行注释/* */
用于声明变量的关键字
变量提升:https://juejin.cn/post/7007224479218663455
通俗来说,变量提升是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的行为。变量被提升后,会给变量设置默认值为 undefined
-
let
是 ES6 引入的 块级作用域 声明关键字
只在声明它的代码块内有效,例如if语句块、for循环块等
不可以在同一作用域内重复声明同一个变量
不存在变量提升(hoisting),即在声明之前使用会抛出ReferenceError1 2 3for (let i = 0; i < 5; i++) { console.log(i); // 每次循环都会创建一个新的 i 变量 } -
var
var是传统的 函数作用域或全局作用域 声明关键字
变量提升意味着变量 可以在声明之前使用,但会得到undefined
可以在同一作用域内 重复声明同一个变量 -
const
const用于声明 只读的常量,一旦声明必须立即初始化赋值
同样具有 块级作用域
不能修改const声明的变量的值,对于简单数据类型来说,这意味着不能重新赋值
对于复杂数据类型(如对象或数组),const保证的是变量指向的内存地址不变,但对象内部的数据可以修改1 2 3 4 5 6 7const obj = { name: "Tom" }; obj.name = "Jerry"; // 可以修改对象内部的属性 console.log(obj); // 输出:{ name: 'Jerry' } const arr = [1, 2, 3]; arr.push(4); // 修改数组内容 console.log(arr); // 输出:[1, 2, 3, 4]
注意
简单类型和复杂类型变量 保存值的方式是不同的
简单类型的,例如数值、字符串、布尔值等,变量指向的内存地址就是值
复杂类型的,例如对象、数组、函数等,变量指向的内存地址是一个指针,这个指针指向的才是真正的值
所以,用const定义复杂类型的变量时,只能保证指针是不变的,使用const定义的 对象或者数组,可以对其内部的元素进行 修改
-
块级作用域
使用let和const声明的变量具有块级作用域
即使在块外有同名变量,块内的变量也不会影响块外的变量1 2 3 4 5 6 7 8let x = "outer"; { let x = "inner"; console.log(x); // 输出:inner } console.log(x); // 输出:outer
数据类型
分为值类型和对象类型
值类型(基本类型)
| 类型 | 说明 | 示例 |
|---|---|---|
Number |
包含整数、浮点数、NaN、Infinity |
42、3.14、NaN |
String |
字符串,使用单引号、双引号或模板字面量(`...`) |
'hello'、"world"、`Hello ${name}` |
Boolean |
布尔值,true / false |
true |
null |
表示 空值,没有指向任何内存地址。可以通过将变量的值设置为 null 来清空变量,也常用于初始化变量 |
let a = null; |
undefined |
表示未初始化的变量或不存在的属性 | let b; // undefined |
Symbol |
表示唯一且不可变的值(常用于对象属性键) | const id = Symbol('id'); |
BigInt |
任意精度整数(ES2020) | 12345678901234567890n |
对象类型(引用类型)
| 类型 | 说明 | 常用方法 |
|---|---|---|
Object |
对象,键值对集合 | Object.keys(obj)、Object.entries(obj) |
Array |
有序数组 | push / pop / map / filter / reduce |
Function |
函数,可调用的对象 | call / apply / bind |
Date |
日期时间 | new Date()、getTime() |
RegExp |
正则表达式 | test / exec / match |
Map |
任意类型键的有序集合 | set / get / has / size |
Set |
唯一值集合 | add / has / delete / size |
WeakMap / WeakSet |
键为对象且不阻止垃圾回收 | set / get / has(WeakSet 只有 add / has / delete) |
Proxy |
对目标对象的拦截层 | new Proxy(target, handler) |
Promise |
表示异步操作的最终完成或失败 | new Promise((resolve, reject) => {...}) |
-
JavaScript 对象
是 拥有属性和方法 的数据1 2 3 4 5 6 7 8 9 10 11 12 13var person = { firstName: "John", lastName: "Doe", age: 50, eyeColor: "blue", }; // 访问对象属性 person.firstName; person["lastName"]; // 可选链,防止 `null/undefined` 报错 person?.personRecords?.[0]; -
Undefined、Null
1 2 3 4 5 6 7 8var x = null; typeof x; // 输出 object var y; typeof y; // 输出 undefined null === undefined; null == undefined;===为绝对相等,即数据类型与值都必须相等typeof null 返回 ‘object’ 是一个历史遗留问题
因为在早期的 JavaScript 实现中,null 被错误地归类为对象 -
JavaScript 数组
是一种特殊的对象类型
执行typeof [1,2,3,4]返回 object
创建名为 cars 的数组1 2 3 4var cars = new Array(); cars[0] = "Saab"; //注意下标从 0 开始 cars[1] = "Volvo"; cars[2] = "BMW";或者
1var cars = new Array("Saab", "Volvo", "BMW");或者
1var cars = ["Saab", "Volvo", "BMW"];变更原数组的方法(就地) 不变更原数组 push( )
pop
shift
splice
sort
reversefilter( )
concat
slice -
对象的遍历
1 2 3 4 5 6 7 8 9 10// 1. for...in(包含原型可枚举属性) for (const key in obj) { if (obj.hasOwnProperty(key)) { // 过滤原型属性 console.log(key, obj[key]); } } // 2. Object.keys / Object.values / Object.entries Object.entries(obj).forEach(([k, v]) => console.log(k, v));
类型检测
typeof x 查看数据类型
instanceof 查看两个对象的类型是否相同
|
|
注意:判断
null应使用value === null
类型转换
JavaScript 同 Python 一样拥有 动态类型
|
|
如果使用 let 定义则类型不能动态变换,但是推荐使用 let
提高代码的稳定性
-
显式转换
1 2 3 4Number("123"); // 123 String(456); // "456" Boolean(""); // false BigInt("9007199254740991"); // 9007199254740991n -
隐式转换(注意运算符的隐式转换规则)
-
+与字符串相加会进行 字符串拼接:1 + '2' === '12' -
==会进行 抽象相等比较(会自动类型转换),推荐使用===(严格相等) -
Boolean与&&、||会进行 短路求值1 2"foo" || "bar"; // 'foo' "" && "hello"; // ''(因为左侧为 falsy,直接返回左侧)
注意:
NaN === NaN为false,应该使用Number.isNaN(value)检测 -
字符串
常见的属性和方法
获取长度、返回指定位置的字符、连接字符
正则表达式匹配、切片、大小写转换、去除空白字符等等
使用内置属性 length 来计算字符串的长度:
|
|
字符串可以是对象
|
|
注意
最好不要创建 String 对象
它会拖慢执行速度,并可能产生其他副作用
数组
数组的常用方法(ES6+)
| 方法 | 说明 |
|---|---|
push / pop |
末尾添加/删除 |
shift / unshift |
开头添加/删除 |
splice(start, deleteCount, ...items) |
任意位置增删改 |
slice(begin, end) |
返回子数组(不改变原数组) |
map(callback) |
生成新数组 |
filter(callback) |
过滤 |
reduce(callback, init) |
累积计算 |
find / findIndex |
查找第一个满足条件的元素 |
some / every |
条件检测 |
includes(value) |
判断是否包含(ES2016) |
flat(depth) / flatMap(ES2019) |
扁平化 |
sort(compareFn) |
排序(默认按 Unicode) |
forEach(callback) |
遍历(无返回值) |
注意:
sort默认会把元素转成字符串再比较,数字排序请提供比较函数:arr.sort((a, b) => a - b)
Map 与 Set
|
|
对比:
| Object | Map |
|---|---|
| 键值:字符串/Symbols | 键值:任意值 |
| 键无序 | 键有序,先进先出原则 |
| 需要手动计算键值对数 | 通过 size 属性求键值对数 |
| 键名可能冲突 | 不会冲突 |
|
|
注意
虽然 NaN !== NaN 结果为 true
但是 NaN 作为 Map 的键名却是没有区别的
二维数组可以转为 Map 对象:
|
|
克隆 Map 新Map = new Map(旧Map); 这样会产生新的对象
合并 Map:
|
|
Set 存储的值是唯一的,可以存储任何类型的值(原始值/对象引用)
用于去重、求并集、交集、差集
|
|
二维数组可以转为 Set 对象:
|
|
运算符
包括算术运算符、赋值运算符、比较运算符、逻辑运算符、条件运算符等等
| 类别 | 示例 | 说明 |
|---|---|---|
| 算术 | + - * / % ** |
** 为指数(ES2016) |
| 赋值 | = += -= *= /= %= **= |
复合赋值会返回新值 |
| 比较 | == != === !== > >= < <= |
|
| 逻辑 | && || ! |
注意短路求值 |
| 位运算 | & | ^ ~ << >> >>> |
适用于整数 |
| 三元 | cond ? a : b |
条件表达式 |
| 空值合并 | a ?? b(ES2020) |
当 a 为 null 或 undefined 时返回 b |
| 可选链 | obj?.prop?.[0](ES2020) |
当任一步骤为 null/undefined 时返回 undefined |
| 展开/剩余 | ...(ES6) |
用于函数参数、数组/对象解构、复制等 |
条件语句
-
if 语句
1 2 3 4 5 6 7if (cond1) { // ... } else if (cond2) { // ... } else { // ... } -
switch 语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18var x; var d = new Date().getDay(); // 获取今天的日期 switch (d) { case 6: x = "今天是星期六"; break; case 0: x = "今天是星期日"; break; // 如果匹配不到,则输出 default 的值 default: x = "期待周末"; } document.getElementById("demo").innerHTML = x;
循环语句
| 结构 | 示例 | 说明 |
|---|---|---|
for |
for (let i = 0; i < 10; i++) {} |
传统计数循环 |
while |
while (cond) {} |
前置条件循环 |
do...while |
do { … } while (cond); |
至少执行一次 |
for...of |
for (const item of arr) {} |
迭代 可遍历对象(Array、Map、Set、字符串等) |
for...in |
for (const key in obj) {} |
迭代 对象的可枚举属性(包括原型链上可枚举属性) |
break / continue |
break; / continue; |
跳出或继续当前循环 |
| 标签 | outer: for (...) { inner: for (...) { if (...) break outer; } } |
用于多层循环的精确控制(不推荐滥用) |
for...in 不建议 用于遍历数组,因为它会遍历所有可枚举属性(包括原型属性),而且顺序不保证
使用 for...of 或普通 for 更安全
-
数组去重(ES6+)
1const uniq = [...new Set(arr)]; -
遍历对象键值
1 2 3 4 5 6 7var person = { fname: "Bill", lname: "Gates", age: 56 }; var txt = new String(""); for (x in person) { // x 为属性名(键) txt = txt + person[x]; }1 2 3Object.entries(obj).forEach(([k, v]) => { console.log(k, v); });
函数
函数就是包裹在花括号中的代码块
使用关键词 function 且必须是 小写
|
|
作用域
作用域是 可访问变量的集合
在 JavaScript 中, 对象和函数同样也是变量
-
局部 JavaScript 变量
在 JavaScript 函数内部声明的变量 是局部变量,所以只能在函数内部访问它
只要函数运行完毕,局部变量就会被删除 -
全局 JavaScript 变量
在 函数外声明的变量 是全局变量,网页上的所有脚本和函数都能访问它 -
变量生命周期
在它声明时初始化
局部变量在 函数执行完毕后 销毁
全局变量在 页面关闭后 销毁 -
优先度
局部变量 > 全局变量 > window 变量
注意
如果给 尚未声明 的变量赋值,该变量将被 自动作为 window 的一个属性
可以通过window.属性名访问
参数默认值、剩余参数、展开运算符
|
|
-
不定参数
1 2 3 4function (...values){ 。。。 } // 不确定参数个数的时候可以使用 ...
函数声明 vs 函数表达式
|
|
-
函数声明前调用
1 2 3 4 5foo(); // 可以在声明前调用 function foo() { console.log(2); } // 输出结果:2函数声明会 整体提升(hoisting),包括函数定义,因此可以在声明前调用
-
函数表达式前调用
1 2 3 4 5 6 7 8 9 10 11 12foo(); // 尝试在赋值前调用 var foo = function () { console.log(1); }; // 输出结果:Uncaught TypeError: foo is not a function // 等价于: var foo; // 变量声明被提升 foo(); // 此时 foo 是 undefined foo = function () { console.log(1); };函数表达式(通过
var声明)只有变量名会被提升,函数赋值不会提升,因此调用时会报错
如果用 let 或 const 声明函数表达式,并且在声明前调用,会因为存在暂时性死区而直接报错(而不是 TypeError)
|
|
故推荐优先使用 const 声明函数表达式,可以避免意外覆盖导致变量污染
箭头函数
|
|
| 场景 | 推荐使用 |
|---|---|
简单回调(如 Array.map) |
箭头函数 |
需要 this 指向调用对象 |
普通函数或 function 声明 |
需要作为构造函数(new) |
普通函数(箭头函数不可用 new) |
-
箭头函数
1 2 3 4 5 6 7// 参数 => 函数体 var f = (a, b) => a + b; // 等同于 var f = function (a, b) { return a + b; }; -
箭头函数和普通函数在 JavaScript 中 this 的上下文处理
箭头函数:没有自己的 this,捕获其所在外层的 this 对象
普通函数:有自己的 this,指向调用它的上下文(对象或全局/undefined)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31var Person = { age: 18, sayHello: function () { setTimeout(function () { console.log(this.age); }, 3000); }, }; var age = 20; Person.sayHello(); // 20 // 普通函数定义的 setTimeout 函数中使用了 this // setTimeout 函数是 sayHello 方法中的函数 // 故 setTimeout 不是直接作为对象的方法被调用 // 所以 this 指向全局对象,age=20 /* ------------------------------------------- */ var Person1 = { age: 18, sayHello: function () { setTimeout(() => { console.log(this.age); }, 3000); }, }; var age = 20; Person1.sayHello(); // 18 // 箭头函数定义的 setTimeout 函数中使用了 this // 箭头函数捕获了外层函数(即 sayHello)的 this // sayHello 函数作为对象 Person1 的方法被调用 // 故 this.age 指向 Person1 对象的 age 属性,age=18对比:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25var Person = { age: 18, sayHello: () => { console.log(this.age); }, }; var age = 20; Person.sayHello(); // 20 // 箭头函数定义的 sayHello 函数中使用了 this // 箭头函数捕获其外层的 this // Person 外层即全局作用域,age=20 /* ------------------------------------------- */ var Person1 = { age: 18, sayHello: function () { console.log(this.age); }, }; var age = 20; Person1.sayHello(); // 18 // 普通函数定义的 sayHello 函数中使用了 this // sayHello 函数作为对象 Person1 的方法被调用 // 故 this.age 指向 Person1 对象的 age 属性,age=18
箭头函数的这种特性使得它们非常适合用于 回调函数
因为它们可以保留外层的 this 值,而不需要使用 bind 或其他技术来明确设置 this 的值
高阶函数与闭包
|
|
正则表达式
|
|
其中 /hello/i 是一个正则表达式
hello 是一个正则表达式 主体 (用于检索)
i 是一个 修饰符 (表示搜索不区分大小写)
test() 方法用于检测一个字符串是否匹配某个模式,如果字符串中含有匹配的文本,则返回 true,否则返回 false
exec() 方法用于匹配字符串中满足正则的内容,将内容作为数组返回,如果没有匹配则返回 null
| 修饰符 | 说明 |
|---|---|
i |
不区分大小写 |
g |
全局匹配(查找所有匹配而非在找到第一个匹配后停止),找出所有符合检索表达式的结果 |
m |
多行模式(^、$ 匹配每行的开头/结尾) |
u |
Unicode 模式(ES6) |
y |
粘性匹配(从 lastIndex 开始匹配) |
-
验证邮箱(简化版)
1 2const emailReg = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; console.log(emailReg.test("test@example.com")); // true上述的正则表达式中多次出现了
[^\s@]+
[]中的^\s@表示去除空白字符和@符号,后面跟着的+表示匹配至少一次
匹配到的内容形如:xxx@xxx.xxx
错误处理与调试
try / catch / finally
|
|
自定义错误
|
|
调试技巧
| 方法 | 说明 |
|---|---|
console.log / console.table / console.dir |
常规调试 |
console.group / console.groupEnd |
分组输出 |
debugger; |
在浏览器 DevTools 中设置断点 |
performance.now() / console.time |
性能测量 |
Error.stack |
查看调用栈 |
sourceURL(在 eval 中) |
为动态代码标记来源 |
JSON
JavaScript Object Notation,是用于存储和传输数据的格式
通常用于 服务端向网页传递数据
- JSON 语法规则
数据为 键/值对
数据由逗号分隔
字符串用双引号
大括号保存对象
方括号保存数组
|
|
JSON.parse(),用于将一个 JSON 字符串转换为 JavaScript 对象
JSON.stringify(),用于将 JavaScript 值转换为 JSON 字符串
this
在 JavaScript 中 this 不是固定不变的,它会随着 执行环境的改变 而改变
-
如果单独使用
在 全局作用域 中,this指向 全局对象
在浏览器环境中,全局对象是window1 2 3 4 5function test() { console.log(this); } test(); // 输出:window -
在方法中
当一个 函数作为对象的方法被调用 时,this指向该对象1 2 3 4 5 6 7 8const obj = { name: "Tom", greet: function () { console.log(`Hello, my name is ${this.name}`); }, }; obj.greet(); // 输出:Hello, my name is Tom对象
obj的greet是一个函数
obj.greet()将该函数作为了其方法进行调用
所以greet函数中的this指向的是obj,所以this.name即obj.name -
在函数中
-
在 非严格模式 下,
this指向全局对象 -
在 严格模式 下(
'use strict';),this在普通函数中未被显式绑定时为undefined,而不是window1 2 3 4 5 6"use strict"; function test() { console.log(this); } test(); // 输出:undefined
-
-
在事件中
this指向 触发事件的元素1 2 3 4 5 6 7 8<button id="myButton">Click me</button> <script> document.getElementById("myButton").onclick = function () { console.log(this); // 输出:<button id="myButton">Click me</button> }; </script>
此外,还有一些其他情况会影响 this 的值
例如使用 call、apply 或 bind 方法可以显式地设置 this 的值
|
|
DOM 与事件
常用 DOM 选择器
| 方法 | 说明 |
|---|---|
document.getElementById(id) |
通过 id 值获取 |
document.getElementsByName(name) |
通过 name 属性值获取 |
document.getElementsByTagName(tag) |
通过标签名获取 |
document.getElementsByClassName(class) |
通过 class 值获取 |
document.querySelector(css) |
返回第一个匹配的 Element |
document.querySelectorAll(css) |
返回 NodeList(可遍历) |
⚠️
getElements...返回的集合是 实时(live) 的,而querySelectorAll返回的NodeList是 静态 的
常用属性与方法
| 类别 | 常用属性/方法 | 示例 |
|---|---|---|
| 内容 | innerHTML / textContent / outerHTML |
el.innerHTML = '<b>Bold</b>'; |
| 属性 | getAttribute(name) / setAttribute(name, value) / removeAttribute(name) |
el.setAttribute('data-id', 123); |
| 样式 | element.style.property = value / getComputedStyle(el) |
el.style.color = 'red'; |
| 类名 | classList.add/remove/toggle/contains |
el.classList.toggle('active'); |
| 节点操作 | appendChild / insertBefore / removeChild / replaceChild |
parent.appendChild(newNode); |
| 位置尺寸 | offsetTop / offsetLeft / offsetWidth / offsetHeight / getBoundingClientRect() |
const rect = el.getBoundingClientRect(); |
offsetTop 获取的是元素上边的偏移量
offsetLeft 获取的是元素左边的偏移量
offsetWidth 获取元素内容宽 + padding 宽 + border 宽的和
offsetHeight 获取元素内容高 + padding 高 + border 高的和
注意:使用
innerHTML时要防止 XSS(跨站脚本)攻击,若仅插入文本请使用textContent或innerText。
HTML 事件
HTML 事件是发生在 HTML 元素上的事情
当在 HTML 页面中使用 JavaScript 时, JavaScript 可以触发这些事件
常见的 HTML 事件:
| 事件 | 常见用途 |
|---|---|
click |
鼠标点击 |
input / change |
表单输入变化 |
keydown / keyup / keypress |
键盘事件 |
submit |
表单提交(可阻止默认提交) |
load / DOMContentLoaded |
页面加载完成(前者等所有资源,后者仅等 DOM) |
focus / blur |
获得/失去焦点 |
mouseenter / mouseleave |
与 mouseover/mouseout 的区别在于不冒泡 |
事件委托
|
|
优势:减少监听器数量,动态添加子元素时无需重新绑定。
常用工具函数
|
|
ES6 特性
解构赋值
-
数组解构
1 2 3 4 5 6let [a, b, c] = [1, 2, 3]; let [a, [[b], c]] = [1, [[2], 3]]; let [a, , b] = [1, 2, 3]; // a=1, b=3 let [a, ...b] = [1, 2, 3]; // a=1, b=[2,3] let [a, b, c, d, e] = "hello"; // 分别等于对应的字符 // 类似 Python 的解包 -
对象解构
1 2let { foo, bar } = { foo: "aaa", bar: "bbb" }; let { a = 10, b = 5 } = { a: 3 }; // a = 3, b = 5
扩展运算符(Spread / Rest)
|
|
模板字面量
|
|
Symbol 关键字
表示独一无二的值
用来作为 对象的属性名,保证属性不重名
|
|
注意
Symbol 值作为属性名时,是公有属性,在类的外部可以访问
但是 不会出现在 for 循环遍历中
要读取一个对象的 Symbol 属性,使用Object.getOwnPropertySymbols(对象名);
例如Object.getOwnPropertySymbols(syObject).forEach((key)=>{ 。。。 });
Proxy
对目标对象进行拦截,然后进行某些处理
不直接操作对象
|
|
class
实现更像面向对象的写法
本质是 function
|
|
注意
类不会提升,访问前就要定义好
类中的方法不需要 function 关键字
方法之间不需要分号
|
|
- 封装
|
|
- 继承,extends 关键字
|
|
模块(ES6 Modules)
注意,每一个模块内声明的变量都是局部变量
-
方法一
1 2 3 4 5 6 7 8 9 10 11 12 13/*-----export [test.js]-----*/ let myName = "Tom"; let myAge = 20; let myfn = function () { return "My name is" + myName + "! I'm '" + myAge + "years old."; }; let myClass = class myClass { static a = "yeah!"; }; export { myName as name1, myAge, myfn, myClass }; // 使用大括号指定所要输出的一组变量 // 写在文档尾部,明确导出的接口1 2 3 4 5 6 7/*-----import [xxx.js]-----*/ import { name1, myAge, myfn, myClass } from "./test.js"; // 不管出现几次,只会执行一次 console.log(myfn()); // My name is Tom! I'm 20 years old. console.log(myAge); // 20 console.log(myName); // Tom console.log(myClass.a); // yeah! -
方法二
1 2 3 4 5 6 7 8 9 10 11 12 13 14// file: math.js export const PI = Math.PI; export function add(a, b) { return a + b; } export default class Calculator { /* … */ } // file: main.js import Calculator, { PI, add } from "./math.js"; console.log(PI); console.log(add(2, 3)); const calc = new Calculator();使用提示
- 在浏览器中使用
<script type="module" src="main.js"></script>。 - Node.js 中
package.json需要"type": "module",或使用.mjs扩展名。
- 在浏览器中使用
导入
每个模块可以有 一个默认导出(default),也可以有 多个命名导出
假设要导入一个 getCurTime.js 模块
它可能由以下几个方式导出
-
命名导出
1 2 3 4 5 6 7// getCurTime.js function A() { ... } const B = 1 export { A, B }1 2import { A, B } from "./getCurTime.js"; // 注意大括号 -
默认导出
1 2 3 4 5 6 7 8// getCurTime.js function A() { ... } const B = 1 export default { A, B } // 由于只能有一个默认导出,所以这里作为一个对象导出1 2 3 4import getCurTime from "./getCurTime.js"; // 导入没有大括号,但是下面要进行解构 const { A, B } = getCurTime;
高级编程
Promise 基础
|
|
| 方法 | 说明 |
|---|---|
Promise.resolve(value) |
将普通值或已有的 Promise 包装成已完成的 Promise |
Promise.reject(reason) |
返回已拒绝的 Promise |
Promise.all(iterable) |
所有 Promise 均成功才成功,返回值为数组 |
Promise.race(iterable) |
只要有一个 Promise 完成(成功或失败)就返回该结果 |
Promise.allSettled(iterable)(ES2020) |
所有 Promise 完成后返回每个的状态(fulfilled/rejected) |
Generator
异步编程
异步(Asynchronous, async)是与同步(Synchronous, sync)相对的概念
在我们学习的传统 单线程编程 中,程序的运行是同步的
(同步不意味着所有步骤同时运行,而是指步骤在一个控制流序列中 按顺序执行)
而一个异步过程的执行将不再与原有的序列有顺序关系
同步按你写的代码顺序执行,异步 不按照顺序执行
异步的执行 效率更高
异步就是 从主线程发射一个子线程 来完成任务
适用于:遇到死循环的时候,防止失去响应,或者读取什么较大的内容或者发送一个请求等等
|
|
async / await
|
|
注意:
await只能在async函数内部使用(或顶层模块的await,需在支持的环境中)。
事件循环(Event Loop)简述
看这里:https://tutorial.javascript.ac.cn/javascript-event-loop/
- 宏任务(macrotask):
setTimeout、setInterval、script、UI rendering等。 - 微任务(microtask):
Promise.then/catch/finally、queueMicrotask、MutationObserver。 - 执行顺序:一次宏任务结束后,先执行所有微任务,再进行页面渲染,随后进入下一个宏任务。
实战技巧:利用微任务可以让代码在当前调用栈结束后立即执行(但仍早于
setTimeout(..., 0))。
函数式编程
https://tech.meituan.com/2022/10/13/dive-into-functional-programming-01.html
实例
JavaScript 冒泡排序+Vue 可视化冒泡动画
- https://www.cnblogs.com/anding/p/16989579.html
- https://code.juejin.cn/pen/7131198849560870926
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Generator/next
|
|
|
|
JS 代码片段
https://blog.windstone.cc/code-snippet/js/utils/data-type.html
数据类型判断
|
|
|
|
格式化
|
|
cookie 操作
|
|
常见错误和建议
| 常见错误 | 说明 | 正确做法 |
|---|---|---|
使用 var 声明全局变量 |
可能导致意外的全局污染 | 使用 let / const,并在函数或块级作用域中声明 |
== 与 != |
隐式类型转换导致难以预料的结果 | 永远使用 === 与 !== |
innerHTML 注入 |
易受 XSS 攻击 | 使用 textContent、innerText 或安全的模板引擎 |
switch 语句忘记 break 导致穿透 |
代码意外执行多个 case | 每个 case 结束后显式 break(或 return) |
| 在循环中直接操作 DOM | 性能低下(频繁重排/重绘) | 使用 文档碎片(DocumentFragment)或一次性更新 |
| 未处理 Promise 错误 | UnhandledPromiseRejection 警告 |
为每个 Promise 加上 .catch,或在 async 中使用 try/catch |
使用 for...in 遍历数组 |
会遍历原型属性且顺序不确定 | 使用 for...of、Array.prototype.forEach、for |
忘记 await |
返回的是 Promise 而不是实际值 | 确认在 async 函数内部使用 await,或使用 .then |
在 setTimeout 中使用 var 循环变量 |
所有回调共享同一个变量 | 使用 let(块级作用域)或闭包(IIFE) |
未使用 strict mode |
隐式全局变量、this 为 window 等 |
在文件或函数顶部添加 'use strict'; |
错误的 this 绑定 |
this 为 undefined 或 window |
根据需求使用普通函数、箭头函数或 bind |
忘记 async 函数的返回值 |
async 函数默认返回 Promise |
明确返回值或使用 await 处理返回的 Promise |
不恰当的 JSON.stringify |
循环引用会抛错,undefined、函数会被忽略 |
使用 JSON.stringify(value, replacer, space) 或自定义序列化 |
-
代码风格建议
- 使用 ESLint + Prettier 自动检查并统一代码格式。
- 变量命名:使用
camelCase,常量(不变值)使用UPPER_SNAKE_CASE。 - 函数长度:保持单一职责,尽量不超过 30 行。
- 模块化:业务代码拆分为独立模块,使用
import/export