目录

  • 前言
  • 1、第1章 什么是JavaScript
    • 1.1 ECMAScript
    • 1.2 DOM
    • 1.3 BOM
    • 小结
  • 第2章 HTML 中的JavaScript
    • 2.1 < script >元素
      • 2.1.1 标签位置
      • 2.1.2 推迟执行脚本
      • 2.1.3 异步执行脚本
    • 2.2 行内代码与外部文件
    • 小结
  • 第3章 语言基础
    • 3.1 语法
      • 3.1.1 区分大小写
      • 3.1.2标识符
      • 3.1.3 严格模式
      • 3.1.4 语句
    • 3.2 变量
      • 3.2.1 var关键字
      • 3.2.2 let关键字
        • for 循环中的let声明
      • 3.3.3 const 声明
    • 3.4 数据类型
      • 3.4.1 typeof操作符
      • 3.4.2 Number类型
        • 1. isNaN()函数
        • 2. 数值转换
      • 3.4.3 String 类型
        • 1. 把一个值转换为字符串的两种方式:
        • 2. 模板字面量
      • 3.4.4 相等操作符
    • 3.5 语句
      • 3.5.1 if 语句
      • 3.5.2 do-while 语句
      • 3.5.3 while 语句
      • 3.5.4 for 语句
      • 3.5.5 for-in 语句
      • 3.5.6 for-of 语句
      • 3.5.7 标签语句
      • 3.5.8 break 和continue 语句
      • 3.5.9 with 语句
      • 3.5.10 switch 语句
    • 3.6 函数
    • 小结
  • 第4章 变量、作用域与内存
    • 4.1 原始值与引用值
      • 4.1.1 动态属性
      • 4.1.2 传递参数
      • 4.1.3 确定类型
    • 4.2 执行上下文与作用域
      • 4.2.1 作用域链增强
      • 4.2.2 变量声明
    • 4.3 垃圾回收
      • 4.3.1 标记清理
      • 4.3.2 引用计数
      • 4.3.3 内存管理
  • 第 5 章 基本引用类型
    • 5.1 Date
    • 5.2 RegExp
    • 5.3 原始值包装类型
      • 5.3.1 Number
      • 5.3.2 String
        • String 类型解析和操作字符串的方法
        • 字符串操作方法
        • 字符串包含方法
        • `trim()`方法
        • `repeat()`方法
        • `padStart()`和`padEnd()`方法
        • 字符串迭代与解构
        • 字符串大小写转换
        • 字符串模式匹配方法
    • 5.4 单例内置对象
      • 5.4.1 Global
        • URL编码方法
        • `eval()`方法
        • Global 对象属性
        • window 对象
      • 5.4.2 Math
        • 1)Math对象属性
        • 2)min() 和 max() 方法
        • 3)Math 舍入方法
        • 4)Math.random()方法
    • 小节
  • 第6章 集合引用类型
    • 6.1 Object
    • 6.2 Array
      • 6.2.1 创建数组
        • 1)使用Array构造函数
        • 2)数组字面量(array literal)表示法
      • 6.2.2 数组空位
      • 6.2.3 数组索引
      • 6.2.4 检测数组
      • 6.2.5 迭代器方法
      • 6.2.6 复制和填充方法
      • 6.2.7 转换方法
      • 6.2.8 栈方法
      • 6.2.9 队列方法
      • 6.2.10 排序方法
      • 6.2.11 数组方法
      • 6.2.12 搜索和位置方法
        • 6.2.12.1 严格相等
        • 6.2.12.2 断言函数
      • 6.2.13 迭代方法
      • 6.2.14 归并方法
    • 6.3 定型数组
      • 6.3.1 ArrayBuffer
  • ~~待续

前言

1998 年,国际标准化组织(ISO)和国际电工委员会(IEC)将ECMAScript 采纳为标准(ISO/IEC-16262)。自此以后,各家浏览器均以ECMAScript 作为自己JavaScript 实现的依据,虽然具体实现各有不同。

第四版全面深入地介绍了JavaScript 开发者必须掌握的前端开发技术,涉及JavaScript 的基础特性和高级特性。

本笔记中的各小节序号,未依照原书中的章节序号。

关于电子版PDF的【分享说明】1


1、第1章 什么是JavaScript

完整的JavaScript 实现包含以下几个部分:

  • 核心(ECMAScript);
  • 文档对象模型(DOM);
  • 浏览器对象模型(BOM)

1.1 ECMAScript

ECMAScript,即ECMA-262 定义的语言,并不局限于Web 浏览器。这门语言没有输入和输出之类的方法。Web 浏览器是ECMAScript 实现的一种宿主环境(host environment)

JavaScript 实现了 ECMAScript。

1.2 DOM

DOM 文档对象模型(DOM,Document Object Model)是一个应用编程接口(API),用于在HTML 中使用扩展的XML。DOM 将整个页面抽象为一组分层节点。HTML 或XML 页面的每个组成部分都是一种节点,包含不同的数据。

比如下面的HTML 页面:

<html><head><title>Sample Page</title></head><body><p> Hello World!</p></body>
</html>

这些代码通过DOM 可以表示为一组分层节点:


DOM 通过创建表示文档的树,让开发者可以随心所欲地控制网页的内容和结构。使用DOM API,可以轻松地删除、添加、替换、修改节点。

注意:DOM 并非只能通过JavaScript 访问,而且确实被其他很多语言实现了。不过对于浏览器来说,DOM 就是使用 ECMAScript 实现的,如今已经成为 JavaScript 语言的一大组成部分。

1.3 BOM

IE3 和 Netscape Navigator 3 提供了浏览器对象模型(BOM) API,用于支持访问和操作浏览器的窗口。使用BOM,开发者可以操控浏览器显示页面之外的部分。

总体来说,BOM 主要针对浏览器窗口和子窗口(frame),不过人们通常会把任何特定于浏览器的扩展都归在BOM 的范畴内。


小结

JavaScript 是一门用来与网页交互的脚本语言,包含以下 三个组成部分。

  1. ECMAScript:由ECMA-262 定义并提供核心功能;
  2. 文档对象模型(DOM):提供与网页内容交互的方法和接口;
  3. 浏览器对象模型(BOM):提供与浏览器交互的方法和接口。

第2章 HTML 中的JavaScript

本章内容:

  • 使用< script>元素;
  • 行内脚本与外部脚本的比较;
  • 文档模式对JavaScript 有什么影响;
  • 确保JavaScript 不可用时的用户体验

2.1 < script >元素

<script>元素有下列8 个属性:

属性 描述
async 可选。表示应该立即开始下载脚本,但不能阻止其他页面动作,比如下载资源或等待其他脚本加载(异步执行脚本)。只对外部脚本文件有效。
charset 可选。使用 src 属性指定的代码字符集(规定在外部脚本文件中使用的字符编码。)。这个属性很少使用,因为大多数浏览器不在乎它的值。
crossorigin 可选。配置相关请求的CORS(跨源资源共享)设置。默认不使用CORS。
crossorigin="anonymous"配置文件请求不必设置凭据标志;
crossorigin="use-credentials"设置凭据标志,意味着出站请求会包含凭据。
defer 可选。表示立即下载,但延迟执行(直到文档完全被解析和显示为止)。只对外部脚本文件有效。在IE7 及更早的版本中,对行内脚本也可以指定这个属性。
integrity 可选。允许比对接收到的资源和指定的加密签名以验证子资源完整性(SRI,Subresource Integrity)。如果接收到的资源的签名与这个属性指定的签名不匹配,则页面会报错,脚本不会执行。这个属性可以用于确保内容分发网络(CDN)不会提供恶意内容。
language 废弃。最初用于表示代码块中的脚本语言(如"JavaScript"、“JavaScript 1.2"或"VBScript”)。大多数浏览器都会忽略这个属性,不应该再使用它。
src 可选。表示包含要执行的代码的外部文件。
type 可选。代替language,表示代码块中脚本语言的内容类型。这个值始终都是"text/javascript",尽管"text/javascript"和"text/ecmascript"都已经废弃了。JavaScript 文件的MIME 类型通常是"application/x-javascript",不过给type 属性这个值有可能导致脚本被忽略。在非IE 的浏览器中有效的其他值还有"application/javascript"和"application/ecmascript"。如果这个值是module,则代码会被当成ES6 模块,而且只有这时候代码中才能出现import 和export 关键字。

使用<script>的两种方式:

  1. 直接在网页中嵌入 JavaScript 代码;
  2. 在网页中导入外部 JavaScript 文件。

要嵌入行内 JavaScript 代码,直接把代码放在<script>元素中就行:

<script>function sayHi() {console.log("Hi!");}
</script>

包含在<script>内的代码会被从上到下解释执行。在上面的例子中,被解释的是一个函数定义,并且该函数会被保存在解释器环境中。在<script>元素中的代码被计算执行完成之前,页面的其余内容不会被加载,也不会被显示。

在使用行内JavaScript 代码时,要注意代码中不能出现字符串</script>。比如,下面的代码会导致浏览器报错:

<script>function sayScript() {console.log("</script>");}
</script>

只需在标签内添加转义字符”\“即可解决

console.log("<\/script>");

这样修改之后,代码就可以被浏览器完全解释,不会导致任何错误。

此处的转义字符指在 JavaScript 中使用反斜杠“\”来向文本字符串添加特殊字符。

要包含外部文件中的JavaScript,就必须使用`src`属性。这个属性的值是一个URL,指向包含 JavaScript 代码的文件,比如:

<script src="example.js"></script>

这个例子在页面中加载了一个名为 example.js 的外部文件。与解释行内JavaScript 一样,在解释外部 JavaScript 文件时,页面也会阻塞(阻塞时间也包含下载文件的时间。)

注意:
按照惯例,外部 JavaScript 文件的扩展名是.js。但这不是必须的,因为浏览器不会检查所包含JavaScript 文件的扩展名。这就为使用服务器端脚本语言动态生成 JavaScript 代码,或者在浏览器中将JavaScript 扩展语言(如TypeScript,或React 的JSX)转译为JavaScript提供了可能性。但请注意,服务器经常会根据文件扩展来确定响应的正确MIME 类型。如果不打算使用.js 扩展名,一定要确保服务器能返回正确的MIME 类型。

另外,使用了src 属性的<script>元素不应该再在<script>和</script>标签中再包含其他 JavaScript 代码。

如果两者都提供的话,则浏览器只会下载并执行脚本文件,从而忽略行内代码。

<script>元素的一个最为强大、同时也备受争议的特性是,它可以包含来自外部的JavaScript 文件。跟<img>元素很像,<script>元素的 src 属性可以是一个完整的URL,而且这个URL 指向的资源可以跟包含它的HTML 页面不在同一个域中,比如:

<script src="http://www.somewhere.com/afile.js"></script>

浏览器在解析这个资源时,会向src 属性指定的路径发送一个GET 请求,以取得相应资源,假定是一个JavaScript 文件。这个初始的请求不受浏览器同源策略限制,但返回并被执行的JavaScript 则受限制。当然,这个请求仍然受父页面HTTP/HTTPS 协议的限制。

如果是引用别人服务器上的JavaScript 文件时要格外小心,因为恶意的程序员随时可能替换这个文件。

要确保该域是自己所有的,或者该域是一个可信的来源。也可用。<script>标签的 integrity 属性来防范这种问题(但此属性于不同浏览器存在兼容性问题)。

2.1.1 标签位置

过去,所有<script>元素都被放在页面的标签内,如下所示:

<!DOCTYPE html>
<html><head><title>Example HTML Page</title><script src="example1.js"></script><script src="example2.js"></script></head><body><!-- 这里是页面内容 --></body>
</html>

但是,为了解决js 代码下载、解析、解释期间导致的页面渲染延迟问题(打开网页时一段时间的浏览器窗口空白),现代Web 应用程序通常将所有JavaScript 引用放在<body>元素中的页面内容后面,如下所示:

<!DOCTYPE html>
<html><head><title>Example HTML Page</title></head><body><!-- 这里是页面内容 --><script src="example1.js"></script><script src="example2.js"></script></body>
</html>

2.1.2 推迟执行脚本

HTML 4.01 为<script>元素定义了一个叫 defer 的属性。添加了这个属性,脚本会被延迟到整个页面都解析完毕后再运行。在<script>元素上设置defer 属性,相当于告诉浏览器立即下载,但延迟执行。

<!DOCTYPE html>
<html><head><title>Example HTML Page</title><script defer src="example1.js"></script><script defer src="example2.js"></script></head><body><!-- 这里是页面内容 --></body>
</html>

本示例中,由于指定了 defer 属性,即使<script>元素被放在了页面的<head>标签中,但它们仍会在浏览器解析到结束的</html>标签后才会执行。

defer 属性只对外部脚本文件才有效。这是HTML5 中明确规定的,因此支持 HTML5 的浏览器会忽略行内脚本的 defer 属性。

2.1.3 异步执行脚本

HTML5 为<script>元素定义了async 属性。从改变脚本处理方式上看,async 属性与defer 类似。且都只适用于外部脚本,都会告诉浏览器立即开始下载。不过,与defer 不同的是,标记为 async 的脚本并不保证能按照它们出现的次序执行,比如:

<!DOCTYPE html>
<html><head><title>Example HTML Page</title><script async src="example1.js"></script><script async src="example2.js"></script></head><body><!-- 这里是页面内容 --></body>
</html>

本例中,第二个脚本可能先于第一个脚本执行。因此,重点在于它们之间没有依赖关系。

给脚本添加 async 属性的目的是告诉浏览器,不必等脚本下载和执行完后再加载页面,同样也不必等到该异步脚本下载和执行后再加载其他脚本。

不过好的 Web 开发实践根本就不推荐使用这个方法。

2.2 行内代码与外部文件

虽然可以直接在HTML 文件中嵌入JavaScript 代码,但通常认为最佳实践是尽可能将JavaScript 代码放在外部文件中。推荐使用外部文件的理由如下:

  • 可维护性;
  • 缓存;
  • 适应未来。

小结

JavaScript 是通过<script>元素插入到HTML 页面中的。这个元素可用于把JavaScript 代码嵌入到 HTML 页面中,跟其他标记混合在一起,也可用于引入保存在外部文件中的JavaScript。小结如下:

  • 要包含外部JavaScript 文件,必须将src 属性设置为要包含文件的URL。文件可以跟网页在同一台服务器上,也可以位于完全不同的域。
  • 所有<script>元素会依照它们在网页中出现的次序被解释。在不使用 deferasync 属性的情况下,包含在<script>元素中的代码必须严格按次序解释。
  • 对不推迟执行的脚本,浏览器必须解释完位于<script>元素中的代码,才能继续渲染页面的剩余部分。应把<script>元素放到页面末尾,介于主内容之后及</body>标签之前。
  • 可以使用defer 属性把脚本推迟到文档渲染完毕后再执行。推迟的脚本原则上按照它们被列出的次序执行。
  • 可使用async 属性表示脚本不需要等待其他脚本,同时也不阻塞文档渲染,即异步加载。异步脚本不能保证按照它们在页面中出现的次序执行。
  • 通过使用<noscript>元素,可以指定在浏览器不支持脚本时显示的内容。如果浏览器支持并启用脚本,则<noscript>元素中的任何内容都不会被渲染。

第3章 语言基础

3.1 语法

ECMAScript 的语法很大程度上借鉴了C 语言和其他类C语言,如Java 和 Perl。熟悉这些语言的开发者,应该很容易理解ECMAScript 宽松的语法。

3.1.1 区分大小写

ECMAScript 中一切都区分大小写。无论是变量、函数名还是操作符,都区分大小写。

3.1.2标识符

标识符:变量、函数、属性或函数参数的名称。
标识符可以由一或多个下列字符组成:

  • 第一个字符必须是一个字母、下划线(_)或美元符号($);
  • 剩下的其他字符可以是字母、下划线、美元符号或数字。

推荐驼峰命名法,首字母小写,后面每个单词的首字母大写。

3.1.3 严格模式

严格模式是一种不同的 JavaScript 解析和执行模型,不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误。如果要对整个脚本启用严格模式,在脚本开头加上这一行:

"use strict";

它其实是一个预处理指令。任何支持的 JavaScript 引擎看到它都会切换到严格模式。选择这种语法形式的目的是不破坏ECMAScript 3 语法。

也可以单独指定一个函数在严格模式下执行,只要把这个预处理指令放到函数体开头即可:

function doSomething() {"use strict";// 函数体
}

严格模式会影响JavaScript 执行的很多方面,因此本书在用到它时会明确指出来(所有现代浏览器都支持严格模式)。

3.1.4 语句

ECMAScript 中的语句以分号结尾。省略分号意味着由解析器确定语句在哪里结尾,如下面的例子所示:

let sum = a + b   // 没有分号也有效 —— 不推荐
let diff = a - b; // 加分号有效    —— 推荐!

即使语句末尾的分号不是必需的,也应该加上。如果没有结尾的分号,删除空行会导致语法错误。

加分号也有助于在某些情况下提升性能,因为解析器会尝试在合适的位置补上分号以纠正语法错误。

3.2 变量

CMAScript 变量是松散类型的,意思是变量可以用于保存任何类型的数据,仅相当于一个点位符,可以保存任何类型的值。

有3 个关键字可以声明变量:varconstlet。其中,var 在 ECMAScript 的所有版本中都可以使用,而 constlet 只能在ECMAScript 6 及更晚的版本中使用。

3.2.1 var关键字

使用var 操作符定义的变量会成为包含它的函数的局部变量。比如,使用var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁:

function test() {var message = "hi"; // 局部变量
}
test();
console.log(message); // 出错!

但是,如果在 函数内 定义变量时 省略var 操作符,可以创建一个全局变量:

function test() {message = "hi"; // 全局变量
}
test();
console.log(message); // 输出"hi"

这样,只要调用一次函数test(),就会定义这个变量,并且可以在函数外部访问到。

~~注意~~
虽然可以通过省略 var 操作符定义全局变量,但不推荐这么做。在局部作用域中定义的全局变量很难维护,也会造成困惑。这是因为不能一下子断定省略var 是不是有意而为之。在严格模式下,如果像这样给未声明的变量赋值,则会导致抛出 ReferenceError

在一条语句中,连续声明多个变量(用逗号分隔,换行、空格、缩进非必须):

var message = "hi",found = false,age = 29;

var 存在变量提升,重复声明也不会报错。
严格模式下,不能定义名为evalarguments 的变量,否则会导致语法错误。

3.2.2 let关键字

letvar 的作用差不多,但有着非常重要的区别。最明显的区别是,let 声明的范围是块作用域,而var 声明的范围是函数作用域。块作用域函数作用域子集

if (true) {var name = 'Matt';console.log(name); // Matt
}console.log(name); // Mattif (true) {let age = 26;console.log(age); // 26
}console.log(age); // ReferenceError: age 没有定义

1、let 没有变量提升,重复声明会报错。
2、let 在全局作用域中声明的变量不会成为window 对象的属性。不过,let 声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续(因此,需确保同一页面不会重复声明)

for 循环中的let声明

在let 出现之前,for 循环定义的迭代变量会渗透到循环体外部:

for (var i = 0; i < 5; ++i) {// 循环逻辑
}console.log(i); // 5

改成使用let 之后,这个问题就消失了,因为迭代变量的作用域仅限于for 循环块内部:

for (let i = 0; i < 5; ++i) {// 循环逻辑
}
console.log(i); // ReferenceError: i 没有定义

3.3.3 const 声明

const 的行为与let 基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改const 声明的变量会导致运行时错误。

它也不允许重复声明,声明的作用域也是块。

const 声明的限制只适用于它指向的变量的引用。换句话说,如果 const 变量引用的是一个对象,那么修改这个对象内部的属性并不违反const 的限制。

const age = 26;
age = 36; // TypeError: 给常量赋值
// const 也不允许重复声明
const name = 'Matt';
const name = 'Nicholas'; // SyntaxError
// const 声明的作用域也是块
const name = 'Matt';if (true) {const name = 'Nicholas';
}console.log(name); // Mattconst person = {};person.name = 'Matt'; // ok

不使用 var :限制自己只使用 let 和 const 有助于提升代码质量,因为变量有了明确的作用域、声明位置,以及不变的值。
const 优先,let 次之

3.4 数据类型

ECMAScript 有 6 种简单数据类型和 1 种复杂数据类型(Object)。

数据类型 类型描述
Undefined Undefined 值未定义( 是一个假值)
null null 表示一个空对象指针,这也是给 typeof 传一个null 会返回"object"的原因
boolean true
false
布尔值 (区分大、小写,True 和False是标识符而不是布尔值)
string 字符串
number 不同数值类型有不同的数值字面量 数值
object 表示值为对象(而非函数)或null
function 函数
symbol 符号

虽然布尔值只有两个,但所有其他 ECMAScript 类型的值都有相应布尔值的等价形式。要将一个其他类型的值转换为布尔值,可以调用特定的 Boolean() 转型函数

let message = "Hello world!";
let messageAsBoolean = Boolean(message);

Boolean()转型函数可以在任意类型的数据上调用,而且始终返回一个布尔值。什么值能转换为 truefalse 的规则取决于数据类型和实际的值。下表总结了不同类型与布尔值之间的转换规则。

数据类型 转换为 true 的值 转换为 false 的值
boolean true false
String 非空字符串 “”(空字符串)
Number 非零数值(包括无穷值) 0、NaN(参见后面的相关内容)
Object 任意对象 null
Undefined N/A(不存在) undefined

理解以上转换非常重要,因为像if 等流控制语句会自动执行其他类型值到布尔值的转换,例如:

let message = "Hello world!";
if (message) {console.log("Value is true");
}

在这个例子中,console.log 会输出字符串"Value is true",因为字符串message 会被自动转换为等价的布尔值true。

由于存在这种自动转换,理解流控制语句中使用的是什么变量就非常重要。错误地使用对象而不是布尔值会明显改变应用程序的执行流。

3.4.1 typeof操作符

对任一个使用typeof 操作符会返回上表中所列类型之一(字符串)。

let message = "some string";
console.log(typeof message);  // "string"
console.log(typeof(message)); // "string"
console.log(typeof 95);       // "number"

typeof 是一个操作符而不是函数,不需要参数(但可以使用参数)。

严格来讲,函数在ECMAScript 中被认为是对象,并不代表一种数据类型。可是,
函数也有自己特殊的属性。为此,就有必要通过typeof 操作符来区分函数和其他对象。

var 或let 声明了变量但没有初始化时,就相当于给变量赋予了undefined 值:

let message;
console.log(message == undefined); // true

即使未初始化的变量会被自动赋予 undefined 值,但我们仍然建议在声明变量的同时进行初始化。这样,当 typeof 返回"undefined"时,你就会知道那是因为给定的变 量尚未声明,而不是声明了但未初始化。

3.4.2 Number类型

有一个特殊的数值叫NaN,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。

console.log(0/0); // NaN
console.log(-0/+0); // NaN

如果分子是非0 值,分母是有符号0 或无符号0,则会返回Infinity 或-Infinity:

console.log(5/0); // Infinity
console.log(5/-0); // -Infinity

1. isNaN()函数

该函数会尝试把它转换为数值。某些非数值的值可以直接转换成数值,如字符串 “10” 或布尔值。任何不能转换为数值的值都会导致这个函数返回 true。举例如下:

console.log(isNaN(NaN)); // true
console.log(isNaN(10)); // false,10 是数值
console.log(isNaN("10")); // false,可以转换为数值10
console.log(isNaN("blue")); // true,不可以转换为数值
console.log(isNaN(true)); // false,可以转换为数值1

虽然不常见,但 isNaN() 可以用于测试对象。此时,首先会调用对象的valueOf() 方法,然后再确定返回的值是否可以转换为数值。如果不能,再调用toString()方法,并测试其返回值

2. 数值转换

有 3 个函数可以将非数值转换为数值:

  • Number()
  • parseInt()
  • parseFloat()

Number()是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。

let num1 = Number("Hello world!"); // NaN
let num2 = Number("");             // 0
let num3 = Number("000011");       // 11
let num4 = Number(true);           // 1

通常在需要得到整数时可以优先使用parseInt()函数。parseInt()函数更专注于字符串是否包含数值模式。

let num1 = parseInt("1234blue"); // 1234
let num2 = parseInt("");         // NaN
let num3 = parseInt("0xA");      // 10,解释为十六进制整数
let num4 = parseInt(22.5);       // 22
let num5 = parseInt("70");       // 70,解释为十进制值
let num6 = parseInt("0xf");      // 15,解释为十六进制整数

不同的数值格式很容易混淆,因此parseInt()也接收第二个参数,用于指定底数(进制数)。如果知道要解析的值是十六进制,那么可以传入16 作为第二个参数,以便正确解析:

let num = parseInt("0xAF", 16); // 175

事实上,如果提供了十六进制参数,那么字符串前面的"0x"可以省掉:

let num1 = parseInt("AF", 16); // 175
let num1 = parseInt("AF");    // 没有传入进制数,出错了

因为不传底数参数相当于让parseInt()自己决定如何解析,所以为避免解析出错,建议始终传给它第二个参数。

多数情况下解析的应该都是十进制数,此时第二个参数就要传入10。

parseFloat()

parseFloat()只解析十进制值,因此不能指定底数。如果字符串表示整数(没有小数点或者小数点后面只有一个零),则parseFloat()返回整数。

let num1 = parseFloat("1234blue"); // 1234,按整数解析
let num2 = parseFloat("0xA"); // 0
let num3 = parseFloat("22.5"); // 22.5
let num4 = parseFloat("22.34.5"); // 22.34
let num5 = parseFloat("0908.5"); // 908.5
let num6 = parseFloat("3.125e7"); // 31250000

3.4.3 String 类型

字符串可以使用双引号(")、单引号(')或反引号(`)标示,因此下面的代码都是合法的:

let firstName = "John";
let lastName = 'Jacob';
let lastName = `Jingleheimerschmidt`

ECMAScript 中的字符串是不可变的(immutable),意思是一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量,如下所示:

let lang = "Java";
lang = lang + "Script";

后台过程
首先会分配一个足够容纳10 个字符的空间,然后填充上 “Java” 和 “Script”。最后销毁原始的字符串 “Java” 和字符串 “Script”

1. 把一个值转换为字符串的两种方式:

  • toString()方法:可见于数值、布尔值、对象和字符串值

    • 如果值有toString()方法,则调用该方法(不传参数)并返回结果;
    • 如果值是null,返回"null";
    • 如果值是undefined,返回"undefined
  • 用加号操作符+给一个值加上空字符串""

2. 模板字面量

模板字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值。

技术上讲,模板字面量不是字符串,而是一种特殊的JavaScript 句法表达式。

字符串插值通过在${}中使用一个 JavaScript 表达式 实现:

let interpolatedTemplateLiteral =
`${ value } to the ${ exponent } power is ${ value * value }`;

所有插入的值都会使用toString()强制转型为字符串,而且任何JavaScript 表达式都可以用于插值。嵌套的模板字符串无须转义:

console.log(`Hello, ${ `World` }!`); // Hello, World!

3.4.4 相等操作符

如果有任一操作数是NaN,则相等操作符返回false,不相等操作符返回true。

记住:即使两个操作数都是NaN,相等操作符也返回false,因为按照规则,NaN 不等于NaN。

let a = NaN == NaN;
console.log(a);   // false

3.5 语句

3.5.1 if 语句

求值结果不一定是boolean值。ECMAScript 会自动调用Boolean()函数将这个表达式的值转换为boolean值。

3.5.2 do-while 语句

后测试循环语句

1)do-while 语法示例:

// 只要i 小于10,循环就会重复执行。i 从 0 开始,每次循环递增 2。
let i = 0;
do {i += 2;
} while (i < 10);

2)使用场景:

需要循环体内的代码在退出前至少执行 1 次。

3.5.3 while 语句

先测试循环语句

while循环语法示例:

// 变量i 从0 开始,每次循环递增2。只要i 小于10,循环就会继续
let i = 0;
while (i < 10) {i += 2;
}

3.5.4 for 语句

也是先测试语句,只不过增加了进入循环之前的初始化代码,以及循环执行后要执行的表达式

for 语句示例:

let count = 10;
for (let i = 0; i < count; i++) {console.log(i);
}

与下面 while 效果相同:

let count = 10;
let i = 0;
while (i < count) {console.log(i);i++;
}

无法通过while 循环实现的逻辑,同样也无法使用for 循环实现。因此for 循环只是将循环相关的代码封装在了一起而已。

在for 循环的初始化代码中,其实是可以不使用变量声明关键字的。不过,初始化定义的迭代器变量在循环执行完成后几乎不可能再用到了。因此,最清晰的写法是使用let 声明迭代器变量,这样就可以将这个变量的作用域限定在循环中。

初始化、条件表达式和循环后表达式都不是必需的。因此,下面这种写法可以创建一个无穷循环:

for (;;) { // 无穷循环doSomething();
}

如果只包含条件表达式,那么for 循环实际上就变成了while 循环:

let count = 10;
let i = 0;
for (; i < count; ) {console.log(i);i++;
}

这种多功能性使得for 语句在这门语言中使用非常广泛。

3.5.5 for-in 语句

for-in 语句是一种严格的迭代语句,用于枚举对象中的非符号键属性。

for (const propName in window) {// 循环显示 BOM 对象 window 的所有属性document.write(propName);
}

ECMAScript 中对象的属性是无序的,因此 for-in 语句不能保证返回对象属性的顺序,返回的顺序可能会因浏览器而异。

3.5.6 for-of 语句

for-of 语句是一种严格的迭代语句,用于遍历可迭代对象的元素。

示例:

for (const el of [2,4,6,8]) {document.write(el);
}

ES2018 对for-of 语句进行了扩展,增加了for-await-of 循环,以支持生成期约(promise)的异步可迭代对象

3.5.7 标签语句

标签语句用于给语句加标签。

示例:

start: for (let i = 0; i < count; i++) {console.log(i);
}

在这个例子中,start 是一个标签,可以在后面通过breakcontinue 语句引用。标签语句的典型应用场景是嵌套循环

3.5.8 break 和continue 语句

breakcontinue语句为执行循环代码提供了更严格的控制手段。其中,

  • break 语句用于立即退出循环,强制执行循环后的下一条语句。
  • continue 语句也用于立即退出循环,但会再次从循环顶部开始执行。

示例:

let num = 0;
for (let i = 1; i < 10; i++) {if (i % 5 == 0) {break;}num++;
}
console.log(num);   // 4

之所以循环执行了4 次,是因为当i 等于5 时,break 语句会导致循环退出,该次循环不会执行递增num 的代码。

如果将 break 换成 continue,则会出现不同的效果:

let num = 0;
for (let i = 1; i < 10; i++) {if (i % 5 == 0) {continue;}num++;
}
console.log(num);  //  8

当 i =5 时,循环会在递增 num 之前退出,但会执行下一次迭代,此时 i 是6。然后,循环会一直执行到自然结束,即 i 等于10。最终num 的值是8 而不是9,是因为continue 语句导致它少递增了一次。

在嵌套循环中,breakcontinue都可以与标签语句一起使用,返回代码中特定的位置。示例如下:

let num = 0;
outermost:
for (let i = 0; i < 10; i++) {for (let j = 0; j < 10; j++) {if (i == 5 && j == 5) {break outermost;}num++;}
}
console.log(num); // 55

本例中,outermost标签标识的是第一个 for 语句。组合使用 标签语句breakcontinue 能实现复杂的逻辑,但也容易出错。注意标签要使用描述性强的文本,而嵌套也不要太深。

3.5.9 with 语句

用途:将代码作用域设置为特定的对象。

使用的主要场景:是针对一个对象反复操作,这时候将代码作用域设置为该对象能提供便利,如下所示:

let qs = location.search.substring(1);
let hostName = location.hostname;
let url = location.href;

上述代码中每一行都用到了location 对象。如果使用with 语句,就可以少写一些代码:

with(location) {let qs = search.substring(1);let hostName = hostname;let url = href;
}

with 语句用于连接 location 对象。在该语句内部,每个变量首先会被认为是一个局部变量。如果没有找到该局部变量,则会搜索location对象,看它是否有一个同名的属性。如果有,则该变量会被求值为location对象的属性。

⚠️ 警告!
1、严格模式不允许使用with语句,否则会抛出错误。
2、由于with 语句影响性能且难于调试其中的代码,通常不推荐在产品代码中使用with语句。

3.5.10 switch 语句

switch语句是与 if 语句紧密相关的一种流控制语句,从其他语言借鉴而来。ECMAScript 中 switch 语句跟 C 语言中 switch 语句的语法非常相似,如下所示:

switch (expression) {case value1:statementbreak;case value2:statementbreak;case value3:statementbreak;case value4:statementbreak;default:statement
}

每个case(条件/分支)相当于:“如果表达式等于后面的值,则执行下面的语句。”,其中:

  • break关键字:跳出switch 语句。如果没有break,则代码会继续匹配下一个条件。
  • default关键字:不满足任何条件时,指定默认执行的语句(相当于else 语句)。

如果确实需要连续匹配几个条件,那么写个注释表明是有意忽略break,如下所示:

switch (i) {case 25:/*跳过*/case 35:console.log("25 or 35");break;case 45:console.log("45");break;default:console.log("Other");
}

虽然switch 语句是从其他语言借鉴过来的,但ECMAScript 为它赋予了一些独有的特性。

  • switch 语句可以用于所有数据类型(在一些语言中,它只能用于数值);
  • 条件的值不需要是常量,也可以是变量或表达式。

示例如下:

switch ("hello world") {case "hello" + " world":console.log("Greeting was found.");break;case "goodbye":console.log("Closing was found.");break;default:console.log("Unexpected message was found.");
}

第1个条件使用的是表达式,求值为两个字符串拼接后的结果。因为拼接后的结果等于switch 的参数,所以会输出"Greeting wasfound."。

既然能够在条件判断中使用表达式,那么就可以在判断中加入更多逻辑:

let num = 25;
switch (true) {case num < 0:console.log("Less than 0.");break;case num >= 0 && num <= 10:console.log("Between 0 and 10.");break;case num > 10 && num <= 20:console.log("Between 10 and 20.");break;default:console.log("More than 20.");
}

switch 语句在比较每个条件的值时,会使用全等操作符,因此不会强制转换数据类型(比如,字符串"10"不等于数值10)。

3.6 函数

语法示例:

// 定义函数sayHi()
function sayHi(name, message) {console.log("Hello " + name + ", " + message);
}// 调用函数sayHi()
sayHi("Nicholas", "how are you today?");

1)ECMAScript 中的函数不需要指定是否返回值。

任何函数在任何时间都可以使用return 语句来返回函数的值,用法是后跟要返回的值。例如:

function sum(num1, num2) {return num1 + num2;
}// 调用 sum()
const result = sum(5, 10);

2)只要碰到 return 语句,函数就会立即停止执行并退出。

示例:

function sum(num1, num2) {return num1 + num2;console.log("Hello world"); // 不会执行
}

3)一个函数里也可以有多个return 语句

示例代码:

function diff(num1, num2) {if (num1 < num2) {return num2 - num1;} else {return num1 - num2;}
}

3)return 语句不带返回值。

函数会立即停止执行并返回undefined

这种用法常用于提前终止函数执行,并不是为了返回值。

示例:

function sayHi(name, message) {return;// 不会执行console.log("Hello " + name + ", " + message);
}

最佳实践:
函数要么返回值,要么不返回值。只在某个条件下返回值的函数会带来麻烦,尤其是调试时。

4)严格模式对函数的一些限制

  • 函数不能以evalarguments 作为名称;
  • 函数的参数不能叫evalarguments
  • 两个命名参数不能拥有同一个名称。

如果违反上述规则,则会导致语法错误,代码也不会执行。


小结

JavaScript 的核心语言特性在ECMA-262 中以伪语言ECMAScript 的形式来定义。

ECMAScript 包含所有基本语法、操作符、数据类型和对象,能完成基本的计算任务,但没有提供获得输入和产生输出的机制。理解ECMAScript 及其复杂的细节是完全理解浏览器中JavaScript 的关键。

1)ECMAScript 中的基本元素。

  • ECMAScript 中的基本数据类型包括UndefinedNullBooleanNumberStringSymbol
  • 与其他语言不同,ECMAScript 不区分整数和浮点值,只有Number 一种数值数据类型。
  • Object 是一种复杂数据类型,它是这门语言中所有对象的基类。
  • 严格模式为这门语言中某些容易出错的部分施加了限制。
  • ECMAScript 提供了C 语言和类C 语言中常见的很多基本操作符,包括数学操作符、布尔操作符、关系操作符、相等操作符和赋值操作符等。
  • 流控制语句大多是从其他语言中借鉴而来(如if 语句、for 语句和switch语句等)。

2) ECMAScript 函数与其他语言中的函数的区别

  • 不需指定返回值,函数可以随时返回任何值。
  • 不指定返回值的函数,实际上会返回特殊值undefined

第4章 变量、作用域与内存

主要内容:

  • 通过变量使用原始值与引用值;
  • 理解执行上下文;
  • 理解垃圾回收。

4.1 原始值与引用值

ECMAScript 变量可以包含两种不同类型的数据:原始值引用值

  • 原始值(primitive value):最简单的数据;
  • 引用值(reference value):由多个值构成的对象。

在给一个变量赋值时,JavaScript 引擎必须确定这个值是原始值还是引用值

  • 保存原始值的变量是按值访问的,因为我们操作的就是存储在变量中的实际值;
  • 保存引用值的变量是按引用访问的。

JavaScript 不允许直接访问内存位置,因此也就不能直接操作对象所在的内存空间。在操作对象时,实际上操作的是对该对象的引用而非实际的对象本身

4.1.1 动态属性

原始值和引用值的定义方式类似。但是,在变量保存这个值之后,可以对这个值做什么,则大有不同。对于引用值而言,可以随时添加、修改和删除其属性和方法。

如下所示:

let person = new Object();
person.name = "Nicholas";
console.log(person.name);     // "Nicholas"

此代码中,首先创建了一个对象,并把它保存在变量person 中。然后,给这个对象添加了一个名为name 的属性,并给这个属性赋值字符串 “Nicholas”。就可以访问这个新属性,直到对象被销毁或属性被显式地删除。

原始值不能有属性,尽管尝试给原始值添加属性不会报错。比如:

let name = "Nicholas";
name.age = 27;
console.log(name.age); // undefined

前端的葵花宝典 - 红宝书《JavaScript高级程序设计(第4版)》学习笔记相关推荐

  1. JavaScript高级程序设计第四版学习--第二十四章

    title: JavaScript高级程序设计第四版学习–第二十四章 date: 2021-5-31 10:46:01 author: Xilong88 tags: JavaScript 本章内容: ...

  2. 阅读JavaScript高级程序设计(第二版)笔记

    第一章js简介 JavaScript诞生在1995年,当时负责进行输入型验证. JavaScript是一种专为与网页交互而设计的脚本语言,分为 : 1. ECMAScript核心语言功能. 2.文档对 ...

  3. javascript 高级程序设计(第4版)阅读笔记(三)

    第3章,内容很长,所以更得慢,主要讲的是ECMAScript   es的语言基础:语法.数据类型.基本操作符.流控制语句.理解函数,ECMAScript 的语法很大程度上借鉴了 C 语言和其他类 C  ...

  4. JavaScript高级程序设计第三版.CHM【带实例】

    从驱动全球商业.贸易及管理领域不计其数的复杂应用程序的角度来看,说 JavaScript 已经成为当今世界上最流行的编程语言一点儿都不为过. JavaScript 是一种非常松散的面向对象语言,也是 ...

  5. JavaScript高级程序设计[第3版]

    JavaScript高级程序设计[第3版] package xyz.huning.toolkit.pdf;import java.io.FileOutputStream; import java.io ...

  6. JavaScript高级程序设计 第4版----String

    JavaScript高级程序设计 第4版----String 文章目录 JavaScript高级程序设计 第4版----String 1.JavaScript 字符 2.字符串操作方法 1.conca ...

  7. Js高级程序设计第三版学习(十二章)

                                  Js高级程序设计第三版学习(十二章) 第十二章 DOM2和DOM3   1.样式: 访问样式属性 任何支持style特性的HTML元素都有一 ...

  8. 新书-JavaScript高级程序设计:第2版(预订中,估价)

    http://www.china-pub.com/196857 JavaScript的应用在广度和深度上日益扩大和加深,前端开发亟待掌握的JavaScript技能也越来越具有挑战性. 这个新版本几乎全 ...

  9. JavaScript高级程序设计第三版 第3章 基本概念

    第3章 基本概念 3.1 语法 3.1.1 区分大小写 3.1.2 标识符 3.1.3 注释 3.1.4 严格模式 3.1.5 语句 3.2 关键字和保留字 3.3 变量 3.4 数据类型 3.4.1 ...

最新文章

  1. SUMO 设置车辆的换道模型
  2. .NET Standard 2.0 特性介绍和使用指南
  3. python中{%%}在HTML中的用法
  4. 【开发必知】基本缓存概念
  5. 查询今天、昨天、本周、上周、本月、上月数据
  6. linux console 下载 jdk
  7. FFmpeg下载编译好的WINDOWS库头文件
  8. Python + ElasticSearch:有了这个超级武器,你也可以报名参加诗词大会了!
  9. 计算机pdf转换word,PDF怎么转换成Word?解决PDF转Word的小妙招
  10. JAVA 实现《坦克大战联机版》游戏
  11. 计算机排查方法,电脑开不了机问题排查方法图解(硬件排查)
  12. 这样处理,Java中的注释代码也会执行
  13. apicloud缓存
  14. 通过驱动断链来隐藏驱动
  15. html中图片亮度调节,HTML+CSS+JS 模仿 Win10 亮度调节效果
  16. python基础学习 1
  17. 辨析:分段函数是不是初等函数?
  18. 用qt做网易云音乐--01标题栏实现
  19. Http的多线程下载
  20. LockSupport的park和unpark的原理

热门文章

  1. 【转载保存】推荐ApacheCN开源的一个机器学习路线图
  2. 【链接保存】十分钟上手sklearn:安装,获取数据,数据预处理
  3. 【转载保存】HtmlUnit的使用
  4. windows 安装tensorflow2.0
  5. 持续定义Saas模式云数据仓库+BI
  6. 阿里云机器学习怎么玩?这本新手入门指南揭秘了!
  7. “阿里巴巴小程序繁星计划”:20亿扶持200万小程序开发者和100万商家
  8. 极狐(GitLab)发布首款“GitNative”DevOps云一体化解决方案
  9. 云计算到底是谁发明的?
  10. AI行业真实现状:做芯片没工作,做视觉、语音血赚