首页 阅读DAY6 JavaScript高级程序设计 5章下 基本引用类型
文章
取消

阅读DAY6 JavaScript高级程序设计 5章下 基本引用类型

开始阅读JavaScript高级程序设计(第5版)学习JS,总共有1000+页,非常全面,短期看完不太现实,找到了一篇博客,花些时间跟着这篇博客过一下红宝书。

JavaScript高级程序设计(第5版)微信读书

红宝书《JavaScript高级程序设计(第5版)》学习大纲 - 大前端全栈开发 - SegmentFault 思否

单例内置对象:

ECMA-262对内置对象的定义是“任何由ECMAScript实现提供、与宿主环境无关,并在ECMAScript程序开始执行时就存在的对象”。这就意味着,开发者不用显式地实例化内置对象,因为它们已经实例化好了。前面我们已经接触了大部分内置对象,包括Object、Array和String。

本节介绍ECMA-262定义的另外两个单例内置对象:Global和Math

Global:

Global对象是ECMAScript中最特别的对象,因为代码不会显式地访问它。ECMA-262规定Global对象为一种兜底对象,它所针对的是不属于任何对象的属性和方法。

事实上,不存在全局变量或全局函数这种东西。在全局作用域中定义的变量和函数都会变成Global对象的属性。本书前面介绍的函数,包括isNaN()、isFinite()、parseInt()和parseFloat(),实际上都是Global对象的方法。除了这些,Global对象上还有另外一些方法。

URI编码方法:

encodeURI() 与 encodeURIComponent():

两个方法都用于对 URI 进行编码,用 UTF-8 编码替换无效字符,使浏览器能正确处理。区别在于编码的范围不同:

方法用途编码范围
encodeURI()编码整个 URI不编码 URI 保留字符(:/?# 等)
encodeURIComponent()编码 URI 中的某个组件编码所有非字母数字字符
1
2
3
4
5
6
7
let uri = "http://www.wiley.com/illegal value.js#start";

encodeURI(uri); // "http://www.wiley.com/illegal%20value.js#start"
 // 只有空格被编码为 %20,其余保留字符不变

encodeURIComponent(uri); // "http%3A%2F%2Fwww.wiley.com%2Fillegal%20value.js%23start"
 // 所有非字母字符都被编码

encodeURI() 适合处理完整的 URL,而 encodeURIComponent() 适合拼接查询参数等场景:

1
2
let query = "illegal value.js";
let url = "http://www.wiley.com/" + encodeURIComponent(query); // 只编码参数部分

实际开发中 encodeURIComponent() 使用频率更高,因为编码查询参数的场景远多于编码整个 URL。

decodeURI() 与 decodeURIComponent():

与编码方法对应,用于还原被编码的字符串。它们和编码方法一一对应:

  • decodeURI() 只解码 encodeURI() 编码过的字符(不会解码 :/# 等)
  • decodeURIComponent() 解码 encodeURIComponent() 编码过的所有字符
1
2
3
4
5
6
7
let uri = "http%3A%2F%2Fwww.wiley.com%2Fillegal%20value.js%23start";

decodeURI(uri); // "http%3A%2F%2Fwww.wiley.com%2Fillegal value.js%23start"
 // 只还原了 %20 → 空格

decodeURIComponent(uri); // "http://www.wiley.com/illegal value.js#start"
 // 全部还原

废弃方法escape() 与 unescape():

escape()unescape() 在 ECMAScript 第 3 版就已经被废弃,被 URI 方法取代。原因在于 URI 方法能正确编码所有 Unicode 字符,而 escape() 只能正确处理 ASCII 字符

不要在生产代码中使用 escape()unescape(),始终使用 URI 编码方法。

eval()方法:

eval() 是 ECMAScript 中最强大的方法——它本身就是一个完整的 JavaScript 解释器,接收一个字符串参数,将其解释为实际代码并执行:

1
eval("console.log('hi')");

上面这行代码的功能与下面这行完全等价:

1
console.log("hi");

当解释器遇到 eval() 调用时,会把参数字符串当作真正的 JavaScript 语句插入到那个位置,然后立即执行

作用域与上下文:

eval() 中执行的代码与调用所在的上下文拥有相同的作用域链。这意味着外部定义的变量可以在 eval() 内部被引用

1
2
let msg = "hello world";
eval("console.log(msg)"); // "hello world"

变量 msg 是在 eval() 外部定义的,但 console.log() 能访问它,因为第二行代码会被替换成真正的函数调用代码。

反过来,eval() 内部定义的变量和函数也可以在外部使用

1
2
3
4
5
eval("function sayHi() { console.log('hi'); }");
sayHi(); // "hi"

eval("let msg = 'hello world';");
console.log(msg); // "hello world"

这是因为 eval() 会把字符串内容直接插入到调用位置,就像在那行手写入一段代码一样。

通过 eval() 创建的变量和函数不会被提升,因为解析阶段它们还只是字符串,直到 eval() 真正执行时才会被创建

1
2
3
// 下面两行等价:
eval("let msg = 'hello'");
let msg = 'hello';

严格模式下的限制:

1
2
3
4
5
6
"use strict";

eval("let msg = 'hello'");
console.log(msg); // ReferenceError ← eval 内部定义的变量无法从外部访问

eval = "hi"; // SyntaxError ← 严格模式下禁止给 eval 赋值

严格模式下,eval() 内部创建的变量和函数被隔离在它自己的作用域中,无法影响外部。同样,在严格模式下,赋值给eval也会导致错误。

eval() 的能力非常强大,但也非常危险。执行用户输入的内容时尤其危险,恶意用户可能注入代码导致网站崩溃或引发 XSS 攻击。

实际开发中几乎所有 eval() 的场景都有更安全的替代方案,应尽量避免使用。

Global对象属性:

Global对象有很多属性。像undefined、NaN和Infinity等特殊值都是Global对象的属性。此外,所有原生引用类型构造函数,比如Object和Function,也都是Global对象的属性。下表列出了所有这些属性。

image-20260422140731876

window对象:

ECMA-262 没有规定直接访问 Global 对象的方式,但浏览器通过 window 对象作为 Global 对象的代理,所有全局变量和函数都会成为 window 的属性:

1
2
3
4
5
6
7
var color = "red";

function sayColor() {
 console.log(window.color);
}

window.sayColor(); // "red"

注意:letconst 声明的全局变量不会成为 window 的属性,只有 var 声明的才会。

另一种获取 Global 对象的方式是通过立即调用函数表达式(IIFE)返回 this

1
2
3
let global = function() {
 return this;
}();

原理:当函数在没有明确指定 this 值的情况下执行(既不是某个对象的方法,也没有通过 call() / apply() 调用),this 就指向 Global 对象。所以一个简单返回 this 的 IIFE 可以在任何执行上下文中获取 Global 对象。

IIFE = 把函数包在括号里 + 末尾加 (),定义即执行,常用来创建独立作用域。

在严格模式下,this 不再默认指向 Global 对象,此时返回 undefined

Math:

ECMAScript提供了Math对象作为保存数学公式、信息和计算的地方。Math对象提供了一些辅助计算的属性和方法。

Math对象上提供的计算要比直接在JavaScript中实现的快得多,因为Math对象上的计算使用了JavaScript引擎中更高效的实现和处理器指令。但使用Math计算的问题是精度会因浏览器、操作系统、指令集和硬件而异。

Math对象属性:

Math对象有一些属性,主要用于保存数学中的一些特殊值。

image-20260422142756693

这些值的含义和用法超出了本书的范畴,但都是ECMAScript规范定义的,并可以在需要时使用。

min()和max()方法:

min()和max()方法用于确定一组数值中的最小值和最大值。这两个方法都接收任意多个参数,如下面的例子所示:

1
2
3
4
5
let max = Math.max(3, 54, 32, 16);
console.log(max);  // 54

let min = Math.min(3, 54, 32, 16);
console.log(min);  // 3

要知道数组中的最大值或最小值,可以像下面这样使用扩展操作符:

1
2
let values = [1, 2, 3, 4, 5, 6, 7, 8];
let max = Math.max(...values);

舍入方法:

4 个方法用于将小数值舍入为整数:

方法舍入方式
Math.ceil()始终向上舍入
Math.floor()始终向下舍入
Math.round()四舍五入
Math.fround()返回最接近的单精度(32 位)浮点值
1
2
3
4
5
6
7
8
9
10
11
Math.ceil(25.1); // 26 ← 25.x 一律向上
Math.ceil(25.5); // 26
Math.ceil(25.9); // 26

Math.floor(25.1); // 25 ← 25.x 一律向下
Math.floor(25.5); // 25
Math.floor(25.9); // 25

Math.round(25.1); // 25 ← < 25.5 舍去
Math.round(25.5); // 26 ← ≥ 25.5 进位
Math.round(25.9); // 26

Math.fround() 比较特殊,它不做整数舍入,而是将数值转换为 32 位单精度浮点数,精度比 JavaScript 默认的 64 位双精度低,因此可能出现精度偏差:

1
2
Math.fround(0.4); // 0.4000000059604645 ← 精度丢失
Math.fround(0.5); // 0.5 ← 恰好能精确表示

Math.round() 最常用,因为日常开发中大部分场景都是做普通的四舍五入,比如分页、金额、展示数量等。

Math.floor() 排第二,常用于取索引(下标从 0 开始)、向下取整的场景。

Math.ceil() 排第三,用于”向上取整”的场景,比如分页计算(Math.ceil(total / pageSize))。

Math.fround() 基本用不到,那是 WebGL 等底层场景才会用的。

random()方法:

返回一个 0~1 之间的随机小数,包含 0 但不包含 1。

从整数范围中随机选一个数的公式:

1
Math.floor(Math.random() * 可选总数 + 最小值)

因为 Math.random() 返回的是小数,乘以任何数都还是小数,所以要配合 Math.floor() 向下取整。

1
2
3
4
5
// 从 1~10 中选一个
let num = Math.floor(Math.random() * 10 + 1); // 10是可选总数,1是最小值

// 从 2~10 中选一个
let num = Math.floor(Math.random() * 9 + 2); // 9是可选总数(10-2+1),2是最小值

此外,也可以把方法封装成函数,每次都要算可选总数容易出错,封装一下更方便:

1
2
3
4
5
6
function selectFrom(lowerValue, upperValue) {
 let choices = upperValue - lowerValue + 1;
 return Math.floor(Math.random() * choices + lowerValue);
}

let num = selectFrom(2, 10); // 2~10,包含2和10

配合 selectFrom 从数组中随机挑选一个数组元素:

1
2
let colors = ["red", "green", "blue", "yellow", "black", "purple", "brown"];
let color = colors[selectFrom(0, colors.length - 1)];

Math.random() 不适合用于加密场景,因为它产生的随机数可预测。如果需要真正的密码学安全随机数,应使用 window.crypto.getRandomValues()

其他方法:

Math对象还有很多涉及各种简单或高阶数运算的方法。讨论每种方法的具体细节或者它们的适用场景超出了本书的范畴。仅在下表简单总结。

image-20260422184345219

即便这些方法都是由ECMA-262定义的,对正弦、余弦、正切等计算的实现仍然取决于浏览器,因为计算这些值的方式有很多种。结果,这些方法的精度可能因实现而异。

本文由作者按照 CC BY 4.0 进行授权

重启DAY5 快慢指针

阅读DAY6 JavaScript高级程序设计 6章上 高级引用类型