Contents

浅记JS知识点

Beautiful landscape

Nature's Beauty

Explore the wonders of the world

参考资料

基础知识

基本概念

JavaScript 是一门 解释型、弱类型、基于原型 的脚本语言,主要用于浏览器端(也可用于 Node.js)
它对大小写敏感,例如 varVarfunctionFunction 是不同的标识符
字符串可以使用单引号或双引号

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),即在声明之前使用会抛出 ReferenceError

    1
    2
    3
    
    for (let i = 0; i < 5; i++) {
      console.log(i); // 每次循环都会创建一个新的 i 变量
    }
    
  • var
    var 是传统的 函数作用域或全局作用域 声明关键字
    变量提升意味着变量 可以在声明之前使用,但会得到 undefined
    可以在同一作用域内 重复声明同一个变量

  • const
    const 用于声明 只读的常量,一旦声明必须立即初始化赋值
    同样具有 块级作用域
    不能修改 const 声明的变量的值,对于简单数据类型来说,这意味着不能重新赋值
    对于复杂数据类型(如对象或数组),const 保证的是变量指向的内存地址不变,但对象内部的数据可以修改

    1
    2
    3
    4
    5
    6
    7
    
    const 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 定义的 对象或者数组,可以对其内部的元素进行 修改

  • 块级作用域
    使用 letconst 声明的变量具有块级作用域
    即使在块外有同名变量,块内的变量也不会影响块外的变量

    1
    2
    3
    4
    5
    6
    7
    8
    
    let x = "outer";
    
    {
      let x = "inner";
      console.log(x); // 输出:inner
    }
    
    console.log(x); // 输出:outer
    

数据类型

分为值类型和对象类型

值类型(基本类型)

类型 说明 示例
Number 包含整数、浮点数、NaNInfinity 423.14NaN
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
    13
    
    var 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
    8
    
    var 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
    4
    
    var cars = new Array();
    cars[0] = "Saab"; //注意下标从 0 开始
    cars[1] = "Volvo";
    cars[2] = "BMW";
    

    或者

    1
    
    var cars = new Array("Saab", "Volvo", "BMW");
    

    或者

    1
    
    var cars = ["Saab", "Volvo", "BMW"];
    
    变更原数组的方法(就地) 不变更原数组
    push( )
    pop
    shift
    splice
    sort
    reverse
    filter( )
    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 查看两个对象的类型是否相同

1
2
3
typeof x; // "string"、"number"、"object"、"function"、"undefined"、"symbol"、"bigint"

x instanceof Date; // true / false

注意:判断 null 应使用 value === null

类型转换

JavaScript 同 Python 一样拥有 动态类型

1
2
3
var x; // x 为 undefined
var x = 5; // 现在 x 为数字
var x = "John"; // 现在 x 为字符串

如果使用 let 定义则类型不能动态变换,但是推荐使用 let
提高代码的稳定性

  • 显式转换

    1
    2
    3
    4
    
    Number("123"); // 123
    String(456); // "456"
    Boolean(""); // false
    BigInt("9007199254740991"); // 9007199254740991n
    
  • 隐式转换(注意运算符的隐式转换规则)

    • + 与字符串相加会进行 字符串拼接1 + '2' === '12'

    • == 会进行 抽象相等比较(会自动类型转换),推荐使用 ===(严格相等)

    • Boolean&&|| 会进行 短路求值

      1
      2
      
      "foo" || "bar"; // 'foo'
      "" && "hello"; // ''(因为左侧为 falsy,直接返回左侧)
      

    注意:NaN === NaNfalse,应该使用 Number.isNaN(value) 检测

字符串

常见的属性和方法
获取长度、返回指定位置的字符、连接字符
正则表达式匹配、切片、大小写转换、去除空白字符等等

使用内置属性 length 来计算字符串的长度:

1
2
var txt = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var sln = txt.length;

字符串可以是对象

1
2
3
4
5
6
var x = "John";
typeof x; //返回 String 类型

// 使用 new 关键字将字符串定义为一个对象
var y = new String("John");
typeof y; //返回 Object 类型

注意
最好不要创建 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)

MapSet

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Map
const map = new Map([
  ["a", 1],
  ["b", 2],
]);
map.set("c", 3);
console.log(map.get("b")); // 2
console.log([...map.entries()]); // [['a',1],['b',2],['c',3]]

// Set
const set = new Set([1, 2, 2, 3]); // 自动去重
set.add(4);
set.delete(1);
console.log(set.has(2)); // true

对比:

Object Map
键值:字符串/Symbols 键值:任意值
键无序 键有序,先进先出原则
需要手动计算键值对数 通过 size 属性求键值对数
键名可能冲突 不会冲突
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
var myMap = new Map();
myMap.set(键1, 值1);
myMap.set(键2, 值2);

// 通过键获取值
值1 = myMap.get(键1);
值2 = myMap.get(键2);

// 循环遍历
// .entries() 确保按照键值对的插入顺序进行遍历
for (var [key, value] of myMap.entries()) {
  console.log(key + "=" + value);
}

myMap.forEach(function (k, v) {
  console.log(k + "=" + v);
}, myMap);

myMap.keys();
myMap.values();

注意
虽然 NaN !== NaN 结果为 true
但是 NaN 作为 Map 的键名却是没有区别的

二维数组可以转为 Map 对象:

1
2
3
4
5
6
7
var myMap = new Map([
  [键1, 值1],
  [键2, 值2],
]);

// 逆变换
var myArray = Array.from(myMap);

克隆 Map 新Map = new Map(旧Map); 这样会产生新的对象

合并 Map:

1
2
3
4
5
var Map1 = new Map([。。。]);
var Map2 = new Map([。。。]);
var merged_map = new Map([...Map1, ...Map2]);
// 注意 ... 操作符
// 若 Map1 和 Map2 有相同的键,后出现的值会覆盖过前面的

Set 存储的值是唯一的,可以存储任何类型的值(原始值/对象引用)
用于去重、求并集、交集、差集

1
2
3
4
5
6
7
8
let mySet = new Set();

mySet.add(1);
mySet.add(2);
mySet.add("hello");
mySet.add({ a: 1, b: 2 });

mySet.has(1); // true

二维数组可以转为 Set 对象:

1
2
3
4
5
var mySet = new Set([[1, "hello", 3]]);

// 逆变换
var myArray = [...mySet];
// 注意 ... 操作符

运算符

包括算术运算符、赋值运算符、比较运算符、逻辑运算符、条件运算符等等

类别 示例 说明
算术 + - * / % ** ** 为指数(ES2016)
赋值 = += -= *= /= %= **= 复合赋值会返回新值
比较 == != === !== > >= < <=
逻辑 && || ! 注意短路求值
位运算 & | ^ ~ << >> >>> 适用于整数
三元 cond ? a : b 条件表达式
空值合并 a ?? b(ES2020) anullundefined 时返回 b
可选链 obj?.prop?.[0](ES2020) 当任一步骤为 null/undefined 时返回 undefined
展开/剩余 ...(ES6) 用于函数参数、数组/对象解构、复制等

条件语句

  • if 语句

    1
    2
    3
    4
    5
    6
    7
    
    if (cond1) {
      // ...
    } else if (cond2) {
      // ...
    } else {
      // ...
    }
    
  • switch 语句

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    var 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+)

    1
    
    const uniq = [...new Set(arr)];
    
  • 遍历对象键值

    1
    2
    3
    4
    5
    6
    7
    
    var person = { fname: "Bill", lname: "Gates", age: 56 };
    
    var txt = new String("");
    for (x in person) {
      // x 为属性名(键)
      txt = txt + person[x];
    }
    
    1
    2
    3
    
    Object.entries(obj).forEach(([k, v]) => {
      console.log(k, v);
    });
    

函数

函数就是包裹在花括号中的代码块
使用关键词 function 且必须是 小写

1
2
3
4
5
6
function myfunction(var1, var2) {
  // 代码
}

// 调用
myfunction(arg1, arg2);

作用域

作用域是 可访问变量的集合
在 JavaScript 中, 对象和函数同样也是变量

  • 局部 JavaScript 变量
    在 JavaScript 函数内部声明的变量 是局部变量,所以只能在函数内部访问它
    只要函数运行完毕,局部变量就会被删除

  • 全局 JavaScript 变量
    函数外声明的变量 是全局变量,网页上的所有脚本和函数都能访问它

  • 变量生命周期
    在它声明时初始化
    局部变量在 函数执行完毕后 销毁
    全局变量在 页面关闭后 销毁

  • 优先度
    局部变量 > 全局变量 > window 变量

注意
如果给 尚未声明 的变量赋值,该变量将被 自动作为 window 的一个属性
可以通过 window.属性名 访问

参数默认值、剩余参数、展开运算符

1
2
3
4
5
function greet(name = "Guest", ...others) {
  console.log(`Hello, ${name}!`);
  console.log("其它参数:", others);
}
greet("Alice", 1, 2, 3);
  • 不定参数

    1
    2
    3
    4
    
    function (...values){
      。。。
    }
    // 不确定参数个数的时候可以使用 ...
    

函数声明 vs 函数表达式

1
2
3
4
5
// 函数声明
function foo() {}

// 函数表达式
const bar = function () {};
  • 函数声明前调用

    1
    2
    3
    4
    5
    
    foo(); // 可以在声明前调用
    function foo() {
      console.log(2);
    }
    // 输出结果:2
    

    函数声明会 整体提升(hoisting),包括函数定义,因此可以在声明前调用

  • 函数表达式前调用

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    foo(); // 尝试在赋值前调用
    var foo = function () {
      console.log(1);
    };
    // 输出结果:Uncaught TypeError: foo is not a function
    
    // 等价于:
    var foo; // 变量声明被提升
    foo(); // 此时 foo 是 undefined
    foo = function () {
      console.log(1);
    };
    

    函数表达式(通过 var 声明)只有变量名会被提升,函数赋值不会提升,因此调用时会报错

如果用 letconst 声明函数表达式,并且在声明前调用,会因为存在暂时性死区而直接报错(而不是 TypeError)

1
2
3
4
foo(); // Uncaught ReferenceError: Cannot access 'foo' before initialization
const foo = function () {
  console.log(1);
};

故推荐优先使用 const 声明函数表达式,可以避免意外覆盖导致变量污染

箭头函数

1
2
3
const add = (a, b) => a + b;

// 箭头函数没有自己的 this、arguments、super、new.target
场景 推荐使用
简单回调(如 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
    31
    
    var 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
    25
    
    var 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 的值

高阶函数与闭包

1
2
3
4
5
6
7
8
9
function makeCounter() {
  let count = 0;
  return function () {
    return ++count;
  };
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2

正则表达式

1
2
3
4
5
6
7
const pattern = /hello/i;

// 常用方法
const str = "Hello World!";
console.log(pattern.test(str)); // true
console.log(str.search(/world/i)); // 6
console.log(str.replace(/world/i, "JS")); // "Hello JS!"

其中 /hello/i 是一个正则表达式
hello 是一个正则表达式 主体 (用于检索)
i 是一个 修饰符 (表示搜索不区分大小写)

test() 方法用于检测一个字符串是否匹配某个模式,如果字符串中含有匹配的文本,则返回 true,否则返回 false
exec() 方法用于匹配字符串中满足正则的内容,将内容作为数组返回,如果没有匹配则返回 null

修饰符 说明
i 不区分大小写
g 全局匹配(查找所有匹配而非在找到第一个匹配后停止),找出所有符合检索表达式的结果
m 多行模式(^$ 匹配每行的开头/结尾)
u Unicode 模式(ES6)
y 粘性匹配(从 lastIndex 开始匹配)
  • 验证邮箱(简化版)

    1
    2
    
    const emailReg = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    console.log(emailReg.test("test@example.com")); // true
    

    上述的正则表达式中多次出现了 [^\s@]+
    [] 中的 ^\s@ 表示去除空白字符和 @ 符号,后面跟着的 + 表示匹配至少一次
    匹配到的内容形如:xxx@xxx.xxx

错误处理与调试

try / catch / finally

1
2
3
4
5
6
7
8
9
try {
  // 可能抛出异常的代码
  riskyOperation();
} catch (e) {
  console.error("捕获错误:", e);
} finally {
  // 无论是否异常都会执行
  cleanup();
}

自定义错误

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

function validate(age) {
  if (age < 0) throw new ValidationError("年龄不能为负数");
}

调试技巧

方法 说明
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 语法规则
    数据为 键/值对
    数据由逗号分隔
    字符串用双引号
    大括号保存对象
    方括号保存数组
1
2
3
4
5
6
7
8
9
var text =
  '{ "sites" : [' +
  '{ "name":"Runoob" , "url":"www.runoob.com" },' +
  '{ "name":"Google" , "url":"www.google.com" },' +
  '{ "name":"Taobao" , "url":"www.taobao.com" } ]}';

obj = JSON.parse(text);
document.getElementById("demo").innerHTML =
  obj.sites[1].name + " " + obj.sites[1].url;

JSON.parse(),用于将一个 JSON 字符串转换为 JavaScript 对象
JSON.stringify(),用于将 JavaScript 值转换为 JSON 字符串

this

在 JavaScript 中 this 不是固定不变的,它会随着 执行环境的改变 而改变

  • 如果单独使用
    全局作用域 中,this 指向 全局对象
    在浏览器环境中,全局对象是 window

    1
    2
    3
    4
    5
    
    function test() {
      console.log(this);
    }
    
    test(); // 输出:window
    
  • 在方法中
    当一个 函数作为对象的方法被调用 时,this 指向该对象

    1
    2
    3
    4
    5
    6
    7
    8
    
    const obj = {
      name: "Tom",
      greet: function () {
        console.log(`Hello, my name is ${this.name}`);
      },
    };
    
    obj.greet(); // 输出:Hello, my name is Tom
    

    对象 objgreet 是一个函数
    obj.greet() 将该函数作为了其方法进行调用
    所以 greet 函数中的 this 指向的是 obj,所以 this.nameobj.name

  • 在函数中

    • 非严格模式 下,this 指向全局对象

    • 严格模式 下('use strict';),this 在普通函数中未被显式绑定时为 undefined,而不是 window

      1
      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 的值
例如使用 callapplybind 方法可以显式地设置 this 的值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var person1 = {
  fullName: function () {
    return this.firstName + " " + this.lastName;
  },
};

var person2 = {
  firstName: "John",
  lastName: "Doe",
};
person1.fullName.call(person2);
// 返回 'John Doe'

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(跨站脚本)攻击,若仅插入文本请使用 textContentinnerText

HTML 事件

HTML 事件是发生在 HTML 元素上的事情
当在 HTML 页面中使用 JavaScript 时, JavaScript 可以触发这些事件

常见的 HTML 事件:

事件 常见用途
click 鼠标点击
input / change 表单输入变化
keydown / keyup / keypress 键盘事件
submit 表单提交(可阻止默认提交)
load / DOMContentLoaded 页面加载完成(前者等所有资源,后者仅等 DOM)
focus / blur 获得/失去焦点
mouseenter / mouseleave mouseover/mouseout 的区别在于不冒泡

事件委托

1
2
3
4
5
6
// 父元素统一处理子元素的点击
document.getElementById("list").addEventListener("click", (e) => {
  if (e.target.matches("li")) {
    console.log("点击了", e.target.textContent);
  }
});

优势:减少监听器数量,动态添加子元素时无需重新绑定。

常用工具函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 防抖(Debounce)——在连续触发事件后仅执行最后一次
function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// 节流(Throttle)——在一定时间间隔内最多执行一次
function throttle(fn, interval) {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last >= interval) {
      last = now;
      fn.apply(this, args);
    }
  };
}

ES6 特性

解构赋值

  • 数组解构

    1
    2
    3
    4
    5
    6
    
    let [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
    2
    
    let { foo, bar } = { foo: "aaa", bar: "bbb" };
    let { a = 10, b = 5 } = { a: 3 }; // a = 3, b = 5
    

扩展运算符(Spread / Rest)

1
2
3
4
5
6
7
8
9
// 合并数组
const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = [...arr1, ...arr2]; // [1,2,3,4]

// 合并对象(ES2018)
const obj1 = { a: 1 };
const obj2 = { b: 2 };
const mergedObj = { ...obj1, ...obj2 }; // {a:1,b:2}

模板字面量

1
2
const name = "Alice";
const msg = `Hello, ${name}! 今天是 ${new Date().toLocaleDateString()}.`;

Symbol 关键字

表示独一无二的值
用来作为 对象的属性名,保证属性不重名

1
2
3
4
5
let sy = Symbol("key1");

let syObject = {};
syObject[sy] = "hello"; // 注意 Symbol 作为属性名需要【用方括号,不用点号】
console.log(syObject); // {Symbol(key1): 'hello'}

注意
Symbol 值作为属性名时,是公有属性,在类的外部可以访问
但是 不会出现在 for 循环遍历中
要读取一个对象的 Symbol 属性,使用 Object.getOwnPropertySymbols(对象名);
例如 Object.getOwnPropertySymbols(syObject).forEach((key)=>{ 。。。 });

Proxy

对目标对象进行拦截,然后进行某些处理
不直接操作对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
let target = {
  name: "Tom",
  age: 24,
};

let handler = {
  get: function (target, key, receiver) {
    console.log("getting " + key);
    return target[key]; // 不用 target.key
  },
  set: function (target, key, value, receiver) {
    console.log("setting " + key);
    target[key] = value;
  },
};

let proxy = new Proxy(target, handler);
proxy.name; // 实际执行 handler.get
proxy.age = 25; // 实际执行 handler.set

class

实现更像面向对象的写法
本质是 function

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class example {
  // constructor(构造函数)相当于 __init__ 函数
  constructor(a) {
    this.a = a;
  }

  // 静态方法
  static sum(a, b) {
    console.log(a + b);
  }

  // 原型方法 / 实例方法
  minus(a, b) {
    console.log(a - b);
  }

  // getter / setter
  get a() {
    return this.a;
  }
}
// class 也可以是匿名的

注意
类不会提升,访问前就要定义好
类中的方法不需要 function 关键字
方法之间不需要分号

1
2
3
4
5
6
7
// 类的实例化,必须 new 关键字
let myc1 = new example();
let myc2 = new example();
// 注意 myc1 和 myc2 是【共享原型方法】的

// 获取实例的原型对象
Object.getPrototypeOf(myc1);
  • 封装
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Example1 {
  constructor(a, b) {
    this._a = a;
    this.b = b;
  }

  get a() {
    console.log("getter");
    return this._a;
  }

  set a(a) {
    console.log("setter");
    this._a = a;
  }
}

let exam1 = new Example1(1, 2); // 只输出 setter , 不会调用 get 方法
console.log(exam1._a); // 1, 可以直接访问
  • 继承,extends 关键字
 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
31
32
33
class Father {
  constructor(address, age) {
    this.address = address;
    this.age = age;
  }

  test() {
    return 0;
  }
  static test1() {
    return 1;
  }
}

class Child1 extends Father {
  constructor(address, age, grade) {
    super(address, age);
    this.grade = grade;
  }
}
class Child2 extends Father {
  constructor(address, age, grade) {
    super(address, age);
    this.grade = grade;
    // 调用父类的普通方法
    console.log(super.test()); // 0
  }
  static test2() {
    // 调用父类静态方法
    return super.test1 + 2;
  }
}
Child2.test2(); // 3

模块(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
    2
    
    import { 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
    4
    
    import getCurTime from "./getCurTime.js";
    
    // 导入没有大括号,但是下面要进行解构
    const { A, B } = getCurTime;
    

高级编程

Promise 基础

1
2
3
4
5
6
function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// 使用
delay(1000).then(() => console.log("1 秒后输出"));
方法 说明
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)相对的概念
在我们学习的传统 单线程编程 中,程序的运行是同步的
(同步不意味着所有步骤同时运行,而是指步骤在一个控制流序列中 按顺序执行
而一个异步过程的执行将不再与原有的序列有顺序关系

同步按你写的代码顺序执行,异步 不按照顺序执行
异步的执行 效率更高
异步就是 从主线程发射一个子线程 来完成任务
适用于:遇到死循环的时候,防止失去响应,或者读取什么较大的内容或者发送一个请求等等

1
2
3
setTimeout(function () {
  document.getElementById("demo").innerHTML = "RUNOOB!";
}, 3000);

async / await

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
async function fetchData() {
  try {
    const response = await fetch("https://api.example.com/data");
    if (!response.ok) throw new Error("网络错误");
    const data = await response.json();
    return data;
  } catch (err) {
    console.error(err);
    // 可选:返回默认值或重新抛出
    throw err;
  }
}

// 调用
fetchData()
  .then((data) => console.log(data))
  .catch((err) => console.error("获取失败", err));

注意await 只能在 async 函数内部使用(或顶层模块的 await,需在支持的环境中)。

事件循环(Event Loop)简述

看这里:https://tutorial.javascript.ac.cn/javascript-event-loop/

  • 宏任务(macrotask)setTimeoutsetIntervalscriptUI rendering 等。
  • 微任务(microtask)Promise.then/catch/finallyqueueMicrotaskMutationObserver
  • 执行顺序:一次宏任务结束后,先执行所有微任务,再进行页面渲染,随后进入下一个宏任务。

实战技巧:利用微任务可以让代码在当前调用栈结束后立即执行(但仍早于 setTimeout(..., 0))。

函数式编程

https://tech.meituan.com/2022/10/13/dive-into-functional-programming-01.html

实例

JavaScript 冒泡排序+Vue 可视化冒泡动画

 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
<div id="app">
  <h1>冒泡排序</h1>

  <!-- 参数区域 -->
  <div class="para">
    <span>排序列表(空格隔开):</span><input type="text" v-model="sortArr" />
    <span>自动执行间隔(ms):</span><input type="number" v-model="speed" />
  </div>
  <div class="control">
    <button v-on:click="initSortItems">🔁重置</button>
    <button v-on:click="play">▶️单步执行</button>
    <button v-on:click="start">自动执行</button>
    <button v-on:click="stop">暂停</button>
  </div>

  <!-- 泡泡 -->
  <!-- 移动动画用的 Vue 的 transition-group 组件来实现 -->
  <transition-group tag="ul" name="sort" class="sort">
    <li
      v-for="item in sortItems"
      :key="item.value"
      :style="{'backgroundColor':item.color,width:item.size,height:item.size,'line-height':item.size}"
    >
      {{item.value}}
    </li>
  </transition-group>
</div>
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>
  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
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// 泡泡的颜色
const bubbleColor = {
  default: "#f6f2f2",
  inprocess: "#f1cc9f",
  completed: "mediumspringgreen",
};

// 重置颜色
// 执行排序前需要重置泡泡颜色
function resetColor(items, color = bubbleColor.default) {
  items.forEach((item) => {
    if (item.color != bubbleColor.completed) item.color = color;
  });
}

// 包装待排序对象元素
// 每个元素包括值、大小、颜色三个属性
function SortItem(n) {
  this.value = n;
  this.size = 30 + n + "px"; //泡泡大小,初试大小 30px
  this.color = bubbleColor.default;
}

// 生成排序对象集合
// 函数的输入参数为排序元素字符串
// 如 “9 100 6 17 3 1”
function generateSortItems(arrStr) {
  let arrItems = [];
  let arr = arrStr.trim().split(" ");
  for (let i = 0; i < arr.length; i++) {
    // 每个元素都进行包装,赋予其三个属性
    arrItems[i] = new SortItem(window.parseInt(arr[i]));
  }
  return arrItems;
}

// 【迭代器实现排序步骤的拆分】
// 注意生成器的定义方式 function*
function* generateSortFunc(items) {
  const len = items.length;
  for (let i = 0; i < len - 1; i++) {
    for (let j = 0; j < len - i - 1; j++) {
      // 迭代器返回的是一个(闭包)函数,为每一个排序步骤
      yield () => {
        // 执行排序前重置泡泡颜色
        resetColor(items);
        // 正在排序的泡泡元素高亮
        items[j].color = bubbleColor.inprocess;
        items[j + 1].color = bubbleColor.inprocess;
        // 冒泡排序逻辑,两两比较
        if (items[j].value > items[j + 1].value) {
          [items[j], items[j + 1]] = [items[j + 1], items[j]];
        }
      };
    }
    // 完成一轮排序,末尾泡泡元素标记为完成态颜色
    items[len - i - 1].color = bubbleColor.completed;
  }
}

let app = new Vue({
  el: "#app",
  data: {
    sortArr: "9 100 6 17 3 1",
    speed: 1000,
    isAutoPlay: false,
    sortItems: null, //排序对象集合
    sortFunc: null, //排序迭代器
  },
  methods: {
    // 初始化排序对象集合
    initSortItems: function () {
      this.stop();
      this.sortItems = generateSortItems(this.sortArr);
      this.sortFunc = generateSortFunc(this.sortItems);
    },

    // 单步执行
    play() {
      // 生成器实例的 next() 方法返回一个包含属性 done 和 value 的对象
      let next = this.sortFunc.next();
      // 如果生成器的控制流程已经结束,则 done 值为 true
      if (next.done) {
        this.sortItems.forEach((item) => (item.color = bubbleColor.completed));
        this.stop();
      }
      // 向 next 方法传入一个参数来向生成器传一个值
      // 这里没有进行传值操作
      else next.value();
    },

    // 启动自动执行
    start() {
      this.isAutoPlay = true;
      this.doAutoPlay();
    },
    // 暂停自动执行
    stop() {
      this.isAutoPlay = false;
    },
    // 自动执行,干活的
    doAutoPlay() {
      if (!this.isAutoPlay) return;
      setTimeout(() => {
        this.play();
        // 递归调用
        this.doAutoPlay();
      }, this.speed);
    },
  },

  created: function () {
    this.initSortItems();
  },
});

JS 代码片段

https://blog.windstone.cc/code-snippet/js/utils/data-type.html

数据类型判断

1
2
3
// 判断变量是否是未定义
typeof val === "undefined";
// 同理可以判断 string、number
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var toString = Object.prototype.toString;

/**
 * 判断变量是否是函数
 * @param {Object} val 是一个变量
 * @returns {boolean} 如果 val 是一个函数则返回 True,否则返回 false
 */
function isFunction(val) {
  return toString.call(val) === "[object Function]";
}
// 同理可以判断是否是 Object、Array、Date 等等

格式化

1
2
3
4
// 去除小数点后无意义的 0
parseFloat("2.00"); // 2
parseFloat("2.20"); // 2.2
parseFloat("2.02"); // 2.02
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 获取浏览器上的 cookie 并转换成键值对的形式存储在 {} 中
function getCookies() {
  const cookiesHash = {};
  const cookies = document.cookie;

  if (cookies === "") {
    return cookiesHash;
  }

  const cookieArray = cookies.split(" ");
  cookieArray.forEach((cookie) => {
    const index = cookie.indexOf("=");
    const key = cookie.substring(0, index);
    const value = cookie.substring(index + 1);
    cookiesHash[key] = decodeURIComponent(value);
  });
  return cookiesHash;
}

常见错误和建议

常见错误 说明 正确做法
使用 var 声明全局变量 可能导致意外的全局污染 使用 let / const,并在函数或块级作用域中声明
==!= 隐式类型转换导致难以预料的结果 永远使用 ===!==
innerHTML 注入 易受 XSS 攻击 使用 textContentinnerText 或安全的模板引擎
switch 语句忘记 break 导致穿透 代码意外执行多个 case 每个 case 结束后显式 break(或 return
在循环中直接操作 DOM 性能低下(频繁重排/重绘) 使用 文档碎片(DocumentFragment)或一次性更新
未处理 Promise 错误 UnhandledPromiseRejection 警告 为每个 Promise 加上 .catch,或在 async 中使用 try/catch
使用 for...in 遍历数组 会遍历原型属性且顺序不确定 使用 for...ofArray.prototype.forEachfor
忘记 await 返回的是 Promise 而不是实际值 确认在 async 函数内部使用 await,或使用 .then
setTimeout 中使用 var 循环变量 所有回调共享同一个变量 使用 let(块级作用域)或闭包(IIFE)
未使用 strict mode 隐式全局变量、thiswindow 在文件或函数顶部添加 'use strict';
错误的 this 绑定 thisundefinedwindow 根据需求使用普通函数、箭头函数或 bind
忘记 async 函数的返回值 async 函数默认返回 Promise 明确返回值或使用 await 处理返回的 Promise
不恰当的 JSON.stringify 循环引用会抛错,undefined、函数会被忽略 使用 JSON.stringify(value, replacer, space) 或自定义序列化
  • 代码风格建议

    • 使用 ESLint + Prettier 自动检查并统一代码格式。
    • 变量命名:使用 camelCase,常量(不变值)使用 UPPER_SNAKE_CASE
    • 函数长度:保持单一职责,尽量不超过 30 行。
    • 模块化:业务代码拆分为独立模块,使用 import/export