文章目录

  • 第 1 章  什么是JavaScript
    • 1.2JavaScript
      • 1.2.1 ECMAScript
      • 1.2.2 DOM
      • 1.2.3 BOM
  • 第 2 章 HTML中的JavaScript
    • 2.1 script元素
      • 2.1.1 标签占位符
      • 2.1.2 推迟执行脚本
      • 2.1.3 异步执行脚本
      • 2.1.4 动态加载脚本
      • 2.1.5 XHTML中的变化
    • 2.2 noscript元素
  • 第 3 章 语言基础
    • 3.1 语法
      • 3.1.1 标识符(采用驼峰大小写形式)
      • 3.1.2 注释
      • 3.1.3 严格模式(use strict)
      • 3.1.4 区分大小写
      • 3.1.5 区分大小写
    • 3.1 关键字与保留字
      • 关键字
      • 保留字
    • 3.3 变量
      • 3.3.1 var
      • 3.3.2 let
      • 3.3.3 const
    • 3.4 数据类型
      • 3.4.1 typeof操作符
      • 3.4.2 Undefined类型
      • 3.4.3 Null类型
      • 3.4.4 Boolean类型
      • 3.4.5 Number类型
        • NaN
        • isNaN
        • 数值转换
          • Number()
          • parseInt
          • parseFloat()
      • 3.4.6 String类型
        • 1.字符字面量
        • 转换为字符串
          • toString()
          • String
        • 模板字面量
        • 字符串插值
        • 模板字面量标签函数
        • 原始字符串
      • 3.4.7 Symbol类型
      • 3.4.8 Object类型
    • 3.5 操作符
      • 3.5.1一元操作符
      • 3.5.2 位操作符
        • 1、按位非
        • 2、按位与
        • 3、按位或
        • 4、按位异或
        • 5、左移(<<)
        • 6、有符号右移(>>)
        • 7、无符号右移(>>>)
      • 3.5.3 布尔操作符
        • 1.逻辑非(!)
        • 2.逻辑与(&&)
        • 3.逻辑或(||)
      • 3.5.4 乘性操作符
        • 1.乘法操作符(*)
        • 2.除法操作符(/)
        • 3.取模操作符
      • 3.5.5 指数操作符
      • 3.5.6 加性操作符
        • 1.加法操作符(+)
        • 2.减法操作符(-)
      • 3.5.7 关系操作符(>,>=,<,<=)
        • 1.等于和不等于
        • 2.全等和不全等
      • 3.5.9 条件操作符(三目)
      • 3.5.10 赋值操作符
      • 3.5.11 逗号操作符
    • 3.6 语句
      • 3.6.1 if 语句
      • 3.6.2 do-while 语句
      • 3.6.3 while 语句
      • 3.6.4 for 语句
      • 3.6.5 for-in 语句
      • 3.6.6 for-of 语句
      • 3.6.7 标签语句
      • 3.6.8 break 和 continue 语句
      • 3.6.9 with 语句
      • 3.6.10 switch 语句
      • 3.7 函数
  • 第 4 章 变量、作用域与内存
    • 4.1 原始值与引用值
      • 4.1.1 动态属性
      • 4.1.2 复制值
      • 4.1.3 传递参数
      • 4.1.4 确定类型
    • 4.2 执行上下文与作用域
      • 4.2.1 作用域链增强
      • 4.2.2 变量声明
        • 1. 使用 var 的函数作用域声明
        • 2. 使用 let 的块级作用域声明
        • 3. 使用 const 的常量声明
    • 4.3 垃圾回收
      • 4.3.1 标记清理
      • 4.3.2 引用计数
      • 4.3.4 内存管理
    • 5.1 Date
      • 5.1.2 日期格式化方法
      • 5.1.3 日期/时间组件方法
    • 5.2 RegExp
      • 5.2.1 RegExp 实例属性
      • 5.2.2 RegExp 实例方法
      • 5.2.3 RegExp 构造函数属性
    • 5.3 原始值包装类型
      • 5.3.1 Boolean
      • 5.3.2 Number
      • 5.3.3 String
        • 1、JavaScript 字符
        • 2、字符串操作方法
          • 1、concat (不改变原数据)
          • 2、slice([ )) N
          • 3、substring([)) N
          • 4、substr()N
        • 3、字符串位置方法
          • 1、indexOf()
          • 2、lastIndexOf()
        • 4、字符串包含方法
          • 1、startsWith()
          • 2、endsWith()
          • 3、includes()
        • 5、trim()、trimLeft()、trimRight()方法
        • 6、repeat()方法
        • 7、padStart()和 padEnd()方法
        • 8、字符串迭代与解构
          • 1、迭代
          • 2、解构([...])
        • 9、字符串大小写转换
        • 10、字符串模式匹配方法
          • 1、match
          • 2、search
          • 3、replace
        • 11、localeCompare()方法
        • 12、HTML 方法
    • 5.4 单例内置对象
      • 5.4.1 Global
        • 1、URL 编码方法
        • 2、eval()方法
        • 3、window 对象
      • 5.4.2 Math
        • 1、Math 对象属性
        • 2、min()和 max()方法
        • 3、舍入方法
        • 4、random()方法
        • 5、其他方法
  • 第 6 章集合引用类型
    • 6.1 Object
    • 6.2 Array
      • 6.2.1 创建数组
      • 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 操作方法
        • 1、concat()
        • 2、slice()
      • 6.2.12 搜索和位置方法
        • 1、严格相等
        • 2、断言函数
      • 6.2.13 迭代方法
      • 6.2.14 归并方法
    • 6.4 Map
      • 6.4.1 基本 API
      • 6.4.2 顺序与迭代
      • 6.4.3 选择 Object 还是 Map
        • 1. 内存占用
        • 2、插入性能
        • 3、查找速度
        • 4、删除性能
    • 6.5 WeakMap
      • 6.5.1 基本 API
      • 6.5.2 弱键
      • 6.5.3 不可迭代键
      • 6.5.4 使用弱映射
    • 6.6 Set
      • 6.6.1 基本 API
      • 6.6.2 顺序与迭代
    • 6.7 WeakSet
      • 6.7.1 基本 API
      • 6.7.2 弱值
  • 第7 章 迭代器与生成器
    • 7.1 理解迭代
    • 7.2迭代器
      • 7.2.1 可迭代协议
  • 第 8 章 对象、类与面向对象编程
    • 8.1 理解对象
      • 8.1.1 属性的类型
      • 8.1.2 定义多个属性
      • 8.1.3 读取属性的特性
      • 8.1.4 合并对象
      • 8.1.5 对象标识及相等判定
      • 8.1.6 增强的对象语法
      • 8.1.7 对象解构
    • 8.2 创建对象
      • 8.2.1、工厂模式
      • 8.2.2、 构造函数模式
      • 8.2.4 原型模式
      • 8.2.5 对象迭代
    • 8.3 继承
      • 8.3.1 原型链
      • 8.3.2 盗用构造函数
      • 8.3.3 组合继承
      • 8.3.4 原型式继承
      • 8.3.5 寄生式继承
      • 8.3.6 寄生式组合继承
    • 8.4 类
      • 8.4.1 类定义
      • 8.4.2 类构造函数
      • 8.4.4 继承
  • 第九章 代理与反射
    • 9.1代理基础
      • 9.1.1创建空代理
      • 9.1.2 定义捕获器
      • 9.1.3 捕获器参数和反射 API
      • 9.1.4 捕获器不变式
      • 9.1.5 可撤销代理
      • 9.1.6 实用反射 API
      • 9.1.7 代理另一个代理
      • 9.1.8 代理的问题与不足
    • 9.2 代理捕获器与反射方法
      • 9.2.1 get()
      • 9.2.2 set()
      • 9.2.3 has()
      • 9.2.4 defineProperty()
      • 9.2.5 getOwnPropertyDescriptor()
      • 9.2.6 deleteProperty()
      • 9.2.7 ownKeys()
      • 9.2.8 getPrototypeOf()
      • 9.2.9 setPrototypeOf()
      • 9.2.10 isExtensible()
      • 9.2.11 preventExtensions()
      • 9.2.12 apply()
      • 9.2.13 construct()
    • 9.3 代理模式
      • 9.3.1 跟踪属性访问
      • 9.3.2 隐藏属性
      • 9.3.3 属性验证
      • 9.3.4 函数与构造函数参数验证
      • 9.3.5 数据绑定与可观察对象
  • 第 10 章 函 数
    • 10.1 箭头函数
    • 10.2 函数名
    • 10.4 没有重载
    • 10.5 默认参数值
    • 10.6 参数扩展与收集
    • 10.7函数声明与函数表达式
    • 10.9 函数内部
      • 10.9.1 arguments
      • 10.9.2 this
      • 10.9.3 caller
      • 10.9.4 new.target
    • 10.10 函数属性和方法
    • 10.11 函数表达式
    • 10.12 递归
    • 10.13 尾调用优化
      • 10.13.1 尾调用优化的条件
      • 10.13.2 尾调用优化代码
    • 10.14 闭包
    • 10.15 立即调用的函数表达式
  • 第 11 章 期约与异步函数
    • 11.1 异步编程
      • 11.1.1 同步与异步
      • 11.1.2 以往的异步编程模式(回调地狱)
    • 11.2 期约(promise)
    • 11.3 异步函数(async/await)
  • 第12章 BOM
      • 12.1 window对象
      • 12.1.1 global作用域
      • 12.1.2 浏览器窗口的尺寸
      • 12.1.3 window方法
      • 12.1.4 系统对话框
    • 12.2 location对象
    • 12.4 Screen
    • 12.5 History
      • 12.5.1 导航
      • 12.5.2历史状态管理
  • 第14章 DOM
    • 14.1 节点层级
      • 14.1.1 Node类型
      • 14.1.2 Document类型
      • 14.1.3 Element 类型
      • 14.1.4 Text类型
      • 14.1.5 Comment 类型
      • 14.1.6 CDATASection 类型
      • 14.1.7 DocumentType 类型
      • 14.1.8 DocumentFragment 类型
      • 14.1.9 Attr 类型
    • 14.2 DOM变成
      • 14.2.1动态脚本
      • 14.2.2 动态样式
      • 14.2.3 操作表格
  • 第15章 DOM扩展
  • 第16章 DOM2和DOM3
  • 第17章 事件
    • 17.1 事件流
      • 17.1.1 事件冒泡
      • 17.1.2 事件捕获
      • 17.2事件处理程序
    • 17.3 事件对象
    • 17.4 事件类型
      • 17.4.1 用户界面事件
      • 17.4.2 焦点事件
      • 17.4.3 鼠标和滚轮事件
      • 17.4.4 键盘与输入事件
      • 17.4.5 合成事件
      • 17.4.6 变化事件
      • 17.4.7 HTML5事件
      • 17.4.9 触摸及手势事件
    • 17.5 内存与性能
      • 17.5.1 事件委托
      • 17.5.2 删除事件处理程序
    • 17.6 模拟事件
    • 17.4 事件类型
      • 17.4.1 用户界面事件
      • 17.4.2 焦点事件
      • 17.4.3 鼠标和滚轮事件
      • 17.4.4 键盘与输入事件
      • 17.4.5 合成事件
      • 17.4.6 变化事件
      • 17.4.7 HTML5事件
      • 17.4.9 触摸及手势事件
    • 17.5 内存与性能
      • 17.5.1 事件委托
      • 17.5.2 删除事件处理程序
    • 17.6 模拟事件

第 1 章  什么是JavaScript

1.2JavaScript

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

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

1.2.1 ECMAScript

ECMAScript,即ECMA-262定义的语言,并不局限于Web浏览器。事实上,这门语言没有输入和输出之类的方法。Web浏览器只是ECMAScript实现可能存在的一种宿主环境(host environment)。宿主环境提供ECMAScript的基准实现和与环境自身交互必需的扩展。扩展(比如DOM使用ECMAScript核心类型和语法,提供特定于环境的额外功能。其他宿主环境还有服务器端JavaScript平台Node.js和即将被淘汰的Adobe Flash。
如果不涉及浏览器的话, 在基本的层面,它描述这门语言的如下部分:

  • 语法
  • 类型
  • 语句
  • 关键字
  • 保留字
  • 操作符
  • 全局对象

1.2.2 DOM

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

1.2.3 BOM

浏览器对象模型(BOM) API,用于支持访问和操作浏览器的窗口。

  • 弹出新浏览器窗口的能力;
  • 移动、缩放和关闭浏览器窗口的能力;
  • navigator对象,提供关于浏览器的详尽信息;
  • location对象,提供浏览器加载页面的详尽信息;
  • screen对象,提供关于用户屏幕分辨率的详尽信息;
  • performance对象,提供浏览器内存占用、导航行为和时间统计的详尽信息;
  • 对cookie的支持;
  • 其他自定义对象,如XMLHttpRequest和IE的ActiveXObject。

第 2 章 HTML中的JavaScript

2.1 script元素

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

2.1.1 标签占位符

过去,所有

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

缺点:把所有JavaScript文件都放在head里,也就意味着必须把所有JavaScript代码都下载、解析和解释完成后,才能开始渲染页面(页面在浏览器解析到body的起始标签时开始渲染)。对于需要很多JavaScript的页面,这会导致页面渲染的明显延迟,在此期间浏览器窗口完全空白。
解决方案:现代Web应用程序通常将所有JavaScript引用放在body元素中的页面内容后面,页面会在处理JavaScript代码之前完全渲染页面

<!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 推迟执行脚本

defer
脚本会被延迟到整个页面都解析完毕后再运行,只对外部脚本文件才有效
注意对于XHTML文档,指定defer属性时应该写成defer=“defer”。
<script defer src="example1.js"></script>

2.1.3 异步执行脚本

async
async属性与defer类似。不同的是,标记为async的脚本并不保证能按照它们出现的次序执行

2.1.4 动态加载脚本

通过向DOM中动态添加script元素同样可以加载指定的脚本

let script = document.createElement('script');
script.src = 'gibberish.js';
document.head.appendChild(script);

2.1.5 XHTML中的变化

可扩展超文本标记语言(XHTML,Extensible HyperText Markup Language)是将HTML作为XML的应用重新包装的结果。与HTML不同,在XHTML中使用JavaScript必须指定type属性且值为text/javascript,在XHTML中编写代码的规则比HTML中严格,

<script type="text/javascript">
//<![CDATA[function compare(a, b) {if (a < b) {console.log("A is less than B");}}
//]]>
</script>

2.2 noscript元素

针对早期浏览器不支持JavaScript的问题,需要一个页面优雅降级的处理方案。最终,元素出现,被用于给不支持JavaScript的浏览器提供替代内容。

<!DOCTYPE html>
<html><head><title>Example HTML Page</title><script defer="defer" src="example1.js"></script><script defer="defer" src="example2.js"></script></head><body><noscript><p>This page requires a JavaScript-enabled browser.</p></noscript></body>
</html>

这个例子是在脚本不可用时让浏览器显示一段话。如果浏览器支持脚本,则用户永远不会看到它。

第 3 章 语言基础

3.1 语法

3.1.1 标识符(采用驼峰大小写形式)

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

3.1.2 注释

// 单行注释
/* 这是多行
注释 */

3.1.3 严格模式(use strict)

3.1.4 区分大小写

3.1.5 区分大小写

3.1 关键字与保留字

关键字

break do in typeof case else instanceof var catch export new void class extends return whileconst finally super with continue for switch yield debugger function this default if throw delete import try

保留字

始终保留: enum
严格模式下保留: implements package public interface protected static let private
模块代码中保留:await

3.3 变量

3.3.1 var

不初始化的情况下,变量会保存一个特殊值undefined
可以改变保存的值,也可以改变值的类型
全局作用于中会成为window对象的属性(window.age)
在函数退出时被销毁
声明提升,自动提升到函数作用域的顶部

3.3.2 let

块作用域
不能重复声明同一个变量
暂时性死区
不会成为window对象的属性
没有变量提升

val和let另一个区别:

for (var i = 0; i < 5; ++i) {setTimeout(() => console.log(i), 0)
}
// 实际上会输出5、5、5、5、5for (let i = 0; i < 5; ++i) {setTimeout(() => console.log(i), 0)
}
// 会输出0、1、2、3、4

3.3.3 const

声明变量时必须同时初始化变量,基础类型数据无法修改

3.4 数据类型

Undefined、Null、Boolean、Number、String和Symbol。还有一种复杂数据类型叫Object(对象)。

3.4.1 typeof操作符

对一个值使用typeof操作符会返回下列字符串之一:
•"undefined"表示值未定义;
•"boolean"表示值为布尔值;
•"string"表示值为字符串;
•"number"表示值为数值;
•"object"表示值为对象(而不是函数)或null(null被认为是一个对空对象的引用);
•"function"表示值为函数;
•"symbol"表示值为符号。

3.4.2 Undefined类型

Undefined类型只有一个值,就是特殊值undefined。
一部分代码实例

let message;
console.log(message == undefined); // true
let message1 = undefined;
console.log(message1 == undefined); // truelet message2;    // 这个变量被声明了, 只是值为undefined
// 确保没有声明过这个变量
// let age
console.log(message2); // "undefined"
console.log(age);     // 报错let message3; // 这个变量被声明了, 只是值为undefined
// 确保没有声明过这个变量
// let age
console.log(typeof message3); // "undefined"
console.log(typeof age);     // "undefined"

3.4.3 Null类型

Null类型同样只有一个值,即特殊值null
代码实例:

let car = null;
console.log(typeof car);  // "object"console.log(null == undefined);  // true

3.4.4 Boolean类型

true和false。

3.4.5 Number类型

八进制字面量在严格模式下是无效的,会导致JavaScript引擎抛出语法错误。

NaN

console.log(0/0);    // NaN
console.log(-0/+0);  // NaNconsole.log(5/0);   // Infinity
console.log(5/-0);  // -Infinityconsole.log(NaN == NaN); // false

isNaN

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

数值转换

Number()
let num1 = Number("Hello world!");  // NaN
let num2 = Number("");              // 0
let num3 = Number("000011");        // 11
let num4 = Number(true);            // 1
console.log( Number("1234blue"))    // NaN
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()也接收第二个参数,用于指定底数(进制数)
let num = parseInt("0xAF", 16); // 175
//提供了十六进制参数,那么字符串前面的"0x"可以省掉
let num1 = parseInt("AF", 16);  // 175
let num2 = parseInt("AF");      // NaN
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.6 String类型

使用双引号(")、单引号(')或反引号(`)标示,首尾必须相同

1.字符字面量

转换为字符串
toString()
let num = 10;
console.log(num.toString());     // "10"
console.log(num.toString(2));    // "1010"
console.log(num.toString(8));    // "12"
console.log(num.toString(10));   // "10"
console.log(num.toString(16));   // "a"
String
let value1 = 10;
let value2 = true;
let value3 = null;
let value4;console.log(String(value1));  // "10"
console.log(String(value2));  // "true"
console.log(String(value3));  // "null"
console.log(String(value4));  // "undefined"
模板字面量

保留换行字符,可以跨行定义字符串

let myMultiLineString = 'first line\nsecond line';
let myMultiLineTemplateLiteral = `first line
second line`;console.log(myMultiLineString);
// first line
// second line"
字符串插值

一种特殊的JavaScript句法表达式,只不过求值后得到的是字符串
利用 ${}插值

模板字面量标签函数

标签函数本身是一个常规函数,通过前缀到模板字面量来应用自定义行为,如下例所示。标签函数接收到的参数依次是原始字符串数组和对每个表达式求值的结果。这个函数的返回值是对模板字面量求值得到的字符串。

let a = 6;
let b = 9;function simpleTag(strings, aValExpression, bValExpression, sumExpression) {console.log(strings);console.log(aValExpression);console.log(bValExpression);console.log(sumExpression);return 'foobar';
}let untaggedResult = `${ a } + ${ b } = ${ a + b }`;
let taggedResult = simpleTag`${ a } + ${ b } = ${ a + b }`;
// ["", " + ", " = ", ""]
// 6
// 9
// 15console.log(untaggedResult);   // "6 + 9 = 15"
console.log(taggedResult);     // "foobar"
原始字符串

可以直接获取原始的模板字面量内容(如换行符或Unicode字符),而不是被转换后的字符表示(String.raw标签函数)

// \u00A9是版权符号
console.log(`\u00A9`);            // ©
console.log(String.raw`\u00A9`);  // \u00A9

3.4.7 Symbol类型

是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。

let sym = Symbol();
console.log(typeof sym); // symbollet genericSymbol = Symbol();
let otherGenericSymbol = Symbol();let fooSymbol = Symbol('foo');
let otherFooSymbol = Symbol('foo');console.log(genericSymbol == otherGenericSymbol);  // false
console.log(fooSymbol == otherFooSymbol);      // false

只要创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性
Symbol()函数不能用作构造函数,与new关键字一起使用

3.4.8 Object类型

// 创建
let o = new Object();
let o = new Object;  // 合法, 但不推荐

每个Object实例都有如下属性和方法。

  • constructor:用于创建当前对象的函数。在前面的例子中,这个属性的值就是Object()函数。
  • hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如o.hasOwnProperty(“name”))或符号。
  • isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型
  • propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用for-in语句枚举。与hasOwnProperty()一样,属性名必须是字符串。
  • toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境。
  • toString():返回对象的字符串表示。
  • valueOf():返回对象对应的字符串、数值或布尔值表示。通常与toString()的返回值相同。

3.5 操作符

3.5.1一元操作符

前缀版:
变量的值会在语句被求值前改变
后缀版:
递增递减在语句被求值后才改变

let age = 29;
let anotherAge = --age + 2;console.log(age);         // 28
console.log(anotherAge);  // 30

规则:
不限于整数
 对于字符串,如果是有效的数值形式,则转换为数值再应用改变。变量类型从字符串变成数值。
 对于字符串,如果不是有效的数值形式,则将变量的值设置为NaN 。变量类型从字符串变成
数值。
 对于布尔值,如果是false,则转换为0 再应用改变。变量类型从布尔值变成数值。
 对于布尔值,如果是true,则转换为1 再应用改变。变量类型从布尔值变成数值。
 对于浮点值,加1 或减1。
 如果是对象,则调用其(第5 章会详细介绍的)valueOf()方法取得可以操作的值。对得到的
值应用上述规则。如果是NaN,则调用toString()并再次应用其他规则。变量类型从对象变成
数值。

3.5.2 位操作符

1、按位非

let num1 = 25;      // 二进制00000000000000000000000000011001
let num2 = ~num1;   // 二进制11111111111111111111111111100110
console.log(num2);  // -26

2、按位与

按位与操作在两个位都是1时返回1,在任何一位是0时返回0。

let result = 25 & 3;
console.log(result); // 125 = 0000 0000 0000 0000 0000 0000 0001 10013 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
AND = 0000 0000 0000 0000 0000 0000 0000 0001

3、按位或

按位或操作在至少一位是1时返回1,两位都是0时返回0。

let result = 25 | 3;
console.log(result); // 2725 = 0000 0000 0000 0000 0000 0000 0001 10013 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------OR = 0000 0000 0000 0000 0000 0000 0001 1011

4、按位异或

按位异或与按位或的区别是,它只在一位上是1的时候返回1(两位都是1或0,则返回0)

let result = 25 ^ 3;
console.log(result); // 2625 = 0000 0000 0000 0000 0000 0000 0001 10013 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
XOR = 0000 0000 0000 0000 0000 0000 0001 1010

5、左移(<<)

按照指定的位数将数值的所有位向左移动

let oldValue = 2;              // 等于二进制10
let newValue = oldValue << 5;  // 等于二进制1000000, 即十进制64

左移会保留它所操作数值的符号。比如,如果-2左移5位,将得到-64,而不是正64。

6、有符号右移(>>)

将数值的所有32位都向右移,同时保留符号(正或负)。有符号右移实际上是左移的逆运算。

let oldValue = 64;             // 等于二进制1000000
let newValue = oldValue >> 5;  // 等于二进制10, 即十进制2

7、无符号右移(>>>)

对正数来说,这跟有符号右移效果相同。但对负数来说,结果就差太多了。无符号右移操作符将负数的二进制表示当成正数的二进制表示来处理。因为负数是其绝对值的二补数,所以右移之后结果变得非常之大,

let oldValue = -64;              // 等于二进制11111111111111111111111111000000
let newValue = oldValue >>> 5;   // 等于十进制134217726

3.5.3 布尔操作符

1.逻辑非(!)

规则:

  • 如果操作数是对象,则返回false。
  • 如果操作数是空字符串,则返回true。
  • 如果操作数是非空字符串,则返回false。
  • 如果操作数是数值0,则返回true。
  • 如果操作数是非0数值(包括Infinity),则返回false。
  • 如果操作数是null,则返回true。
  • 如果操作数是NaN,则返回true。
  • 如果操作数是undefined,则返回true。
console.log(!false);   // true
console.log(!"blue");  // false
console.log(!0);       // true
console.log(!NaN);     // true
console.log(!"");      // true
console.log(!12345);   // false

逻辑非操作符也可以用于把任意值转换为布尔值。同时使用两个叹号(!!),相当于调用了转型函数Boolean()。无论操作数是什么类型,第一个叹号总会返回布尔值。第二个叹号对该布尔值取反,从而给出变量真正对应的布尔值。结果与对同一个值使用Boolean()函数是一样的

console.log(!!"blue"); // true
console.log(!!0);      // false
console.log(!!NaN);    // false
console.log(!!"");     // false
console.log(!!12345);  // true

2.逻辑与(&&)

规则:

  • 如果第一个操作数是对象,则返回第二个操作数。
  • 如果第二个操作数是对象,则只有第一个操作数求值为true才会返回该对象。
  • 如果两个操作数都是对象,则返回第二个操作数。
  • 如果有一个操作数是null,则返回null。
  • 如果有一个操作数是NaN,则返回NaN。
  • 如果有一个操作数是undefined,则返回undefined。

逻辑与操作符是一种短路操作符,意思就是如果第一个操作数决定了结果,那么永远不会对第二个操作数求值。对逻辑与操作符来说,如果第一个操作数是false,那么无论第二个操作数是什么值,结果也不可能等于true。

3.逻辑或(||)

规则:

  • 如果第一个操作数是对象,则返回第一个操作数。
  • 如果第一个操作数求值为false,则返回第二个操作数。
  • 如果两个操作数都是对象,则返回第一个操作数。
  • 如果两个操作数都是null,则返回null。
  • 如果两个操作数都是NaN,则返回NaN。
  • 如果两个操作数都是undefined,则返回undefined。

逻辑或操作符也具有短路的特性。只不过对逻辑或而言,第一个操作数求值为true,第二个操作数就不会再被求值了。

3.5.4 乘性操作符

1.乘法操作符(*)

特殊的行为:

  • 如果操作数都是数值,则执行常规的乘法运算,即两个正值相乘是正值,两个负值相乘也是正值,正负符号不同的值相乘得到负值。如果ECMAScript不能表示乘积,则返回Infinity或-Infinity。
  • 如果有任一操作数是NaN,则返回NaN。
  • 如果是Infinity乘以0,则返回NaN。
  • 如果是Infinity乘以非0的有限数值,则根据第二个操作数的符号返回Infinity或-Infinity。
  • 如果是Infinity乘以Infinity,则返回Infinity。
  • 如果有不是数值的操作数,则先在后台用Number()将其转换为数值,然后再应用上述规则。

2.除法操作符(/)

特殊的行为。

  • 如果操作数都是数值,则执行常规的除法运算,即两个正值相除是正值,两个负值相除也是正值,符号不同的值相除得到负值。如果ECMAScript不能表示商,则返回Infinity或-Infinity。
  • 如果有任一操作数是NaN,则返回NaN。 •如果是Infinity除以Infinity,则返回NaN。 •如果是0除以0,则返回NaN。
  • 如果是非0的有限值除以0,则根据第一个操作数的符号返回Infinity或-Infinity。
  • 如果是Infinity除以任何数值,则根据第二个操作数的符号返回Infinity或-Infinity。
  • 如果有不是数值的操作数,则先在后台用Number()函数将其转换为数值,然后再应用上述规则。

3.取模操作符

特殊的行为。
•如果操作数是数值,则执行常规除法运算,返回余数。
•如果被除数是无限值,除数是有限值,则返回NaN。
•如果被除数是有限值,除数是0,则返回NaN。
•如果是Infinity除以Infinity,则返回NaN。
•如果被除数是有限值,除数是无限值,则返回被除数。
•如果被除数是0,除数不是0,则返回0。
•如果有不是数值的操作数,则先在后台用Number()函数将其转换为数值,然后再应用上述规则。

3.5.5 指数操作符

ECMAScript 7新增了指数操作符,Math.pow()现在有了自己的操作符**,结果是一样的:

console.log(Math.pow(3, 2);    // 9
console.log(3 ** 2);           // 9console.log(Math.pow(16, 0.5); // 4
console.log(16** 0.5);         // 4

不仅如此,指数操作符也有自己的指数赋值操作符**=,该操作符执行指数运算和结果的赋值操作

let squared = 3;
squared **= 2;
console.log(squared); // 9let sqrt = 16;
sqrt **= 0.5;
console.log(sqrt); // 4

3.5.6 加性操作符

1.加法操作符(+)

返回结果:
•如果有任一操作数是NaN,则返回NaN;
•如果是Infinity加Infinity,则返回Infinity;
•如果是-Infinity加-Infinity,则返回-Infinity;
•如果是Infinity加-Infinity,则返回NaN;
•如果是+0加+0,则返回+0;
•如果是-0加+0,则返回+0;
•如果是-0加-0,则返回-0。
不过,如果有一个操作数是字符串,则要应用如下规则:
•如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面;
•如果只有一个操作数是字符串,则将另一个操作数转换为字符串,再将两个字符串拼接在一起。
如果有任一操作数是对象、数值或布尔值,则调用它们的toString()方法以获取字符串,然后再应用前面的关于字符串的规则。对于undefined和null,则调用String()函数,分别获取"undefined"和"null"。

2.减法操作符(-)

返回规则:
•如果两个操作数都是数值,则执行数学减法运算并返回结果。
•如果有任一操作数是NaN,则返回NaN。
•如果是Infinity减Infinity,则返回NaN。
•如果是-Infinity减-Infinity,则返回NaN。
•如果是Infinity减-Infinity,则返回Infinity。
•如果是-Infinity减Infinity,则返回-Infinity。
•如果是+0减+0,则返回+0。
•如果是+0减-0,则返回-0。
•如果是-0减-0,则返回+0。
•如果有任一操作数是字符串、布尔值、null或undefined,则先在后台使用Number()将其转换为数值,然后再根据前面的规则执行数学运算。如果转换结果是NaN,则减法计算的结果是NaN。
•如果有任一操作数是对象,则调用其valueOf()方法取得表示它的数值。如果该值是NaN,则减法计算的结果是NaN。如果对象没有valueOf()方法,则调用其toString()方法,然后再将得到的字符串转换为数值。

let result1 = 5 - true; // true被转换为1, 所以结果是4
let result2 = NaN - 1;  // NaN
let result3 = 5 - 3;    // 2
let result4 = 5 - "";   // ""被转换为0, 所以结果是5
let result5 = 5 - "2";  // "2"被转换为2, 所以结果是3
let result6 = 5 - null; // null被转换为0, 所以结果是5

3.5.7 关系操作符(>,>=,<,<=)

规则:
•如果操作数都是数值,则执行数值比较。
•如果操作数都是字符串,则逐个比较字符串中对应字符的编码。
•如果有任一操作数是数值,则将另一个操作数转换为数值,执行数值比较。
•如果有任一操作数是对象,则调用其valueOf()方法,取得结果后再根据前面的规则执行比较。如果没有valueOf()操作符,则调用toString()方法,取得结果后再根据前面的规则执行比较。
•如果有任一操作数是布尔值,则将其转换为数值再执行比较。

1.等于和不等于

2.全等和不全等

会比较数据类型

3.5.9 条件操作符(三目)

let max = (num1 > num2) ? num1 : num2;

3.5.10 赋值操作符

•乘后赋值(*=)
•除后赋值(/=)
•取模后赋值(%=)
•加后赋值(+=)
•减后赋值(-=)
•左移后赋值(<<=)
•右移后赋值(>>=)
•无符号右移后赋值(>>>=)

3.5.11 逗号操作符

逗号操作符可以用来在一条语句中执行多个操作,如下所示:

let num1 = 1, num2 = 2, num3 = 3;

在一条语句中同时声明多个变量是逗号操作符最常用的场景。不过,也可以使用逗号操作符来辅助赋值。在赋值时使用逗号操作符分隔值,最终会返回表达式中最后一个值:

let num = (5, 1, 4, 8, 0); // num的值为0

在这个例子中,num将被赋值为0,因为0是表达式中最后一项。逗号操作符的这种使用场景并不多见,但这种行为的确存在。

3.6 语句

3.6.1 if 语句

if (condition){//条件成立执行此处
} else {//条件不成立执行此处
}

3.6.2 do-while 语句

循环体内的代码至少执行一次。

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

3.6.3 while 语句

while(expression) statement

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

3.6.4 for 语句

for (initialization; expression; post-loop-expression) statement

实例:

let count = 10;
//是使用 let 声明迭代器变量,这样就可以将这个变量的作用域限定在循环中。
for (let i = 0; i < count; i++) { console.log(i);
}//等同于下面while代码
let i = 0;
while (i < count) { console.log(i); i++;
}//for循环中不写条件就变成了无穷循环
for (;;) {doSomething();
}
// 如果只包含条件表达式,那么 for 循环实际上就变成了 while 循环:
let count = 10;
let i = 0;
for (; i < count; ) { console.log(i); i++;
}

3.6.5 for-in 语句

for-in 语句是一种严格的迭代语句,用于枚举对象中的非符号键属性,语法如下:
for (property in expression) statement
这里控制语句中的 const 也不是必需的。但为了确保这个局部变量不被修改,推荐使用 const
对象的属性是无序的,因此 for-in 语句不能保证返回对象属性的顺序,返回的顺序以浏览器而定
如果 for-in 循环要迭代的变量是 null 或 undefined,则不执行循环体。

3.6.6 for-of 语句

for-of 语句是一种严格的迭代语句,用于遍历可迭代对象的元素,语法如下: for (property of expression)
statement

3.6.7 标签语句

标签语句用于给语句加标签,语法如下:

label: statement

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

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

let num = 0;
outermost:
for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { if (i == 5 && j == 5) { //break 结果为95 //指定退出i层循环break outermost; } num++; }
}
console.log(num); // 55

3.6.8 break 和 continue 语句

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

let num = 0;
outermost:
for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { if (i == 5 && j == 5) { //当 i 和 j 都等于 5 时,会执行 continue,跳到外部循环继续执行,从而导致内部循环少执行 5 次,结果 num 等于 95。continue outermost; } num++; }
}
console.log(num); // 95

3.6.9 with 语句

with 语句的用途是将代码作用域设置为特定的对象,其语法是:

with (expression) statement;

let hostName = location.hostname;
let url = location.href;
let qs = location.search.substring(1);
//上面代码中的每一行都用到了 location 对象。如果使用 with 语句,就可以少写一些代码:
with(location) { let qs = search.substring(1); let hostName = hostname; let url = href;
}

严格模式不允许使用 with 语句,否则会抛出错误。

由于 with 语句影响性能且难于调试其中的代码,通常不推荐在产品代码中使用 with语句。

3.6.10 switch 语句

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

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.");
}

3.7 函数

语法:function functionName(arg0, arg1,…,argN) { 执行内容}
严格模式对函数也有一些限制:
 函数不能以 eval 或 arguments 作为名称;
 函数的参数不能叫 eval 或 arguments;
 两个命名参数不能拥有同一个名称。
如果违反上述规则,则会导致语法错误,代码也不会执行。

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

4.1 原始值与引用值

1、原始值:基础数据类型
2、引用值:对象

4.1.1 动态属性

只有引用值类型,可以随时添加、修改和删除其属性和方法。

let person = new Object();
person.name = "Nicholas";
console.log(person.name); // "Nicholas"
// 原始值不能有属性,尽管尝试给原始值添加属性不会报错。比如:
let name = "Nicholas";
name.age = 27;
console.log(name.age); // undefined

4.1.2 复制值

原始值:存储是独立的,不会改变原来变量的值
引用值:复制的值实际上是一个指针,它指向存储在堆内存中的对象,会改变原变量的值

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

4.1.3 传递参数

在按值传递参数时,值会被复制到一个局部变量。
在按引用传递参数时,值在内存中的位置会被保存在一个局部变量,这意味着对本地变量的修改会反映到函数外部。

 num += 10; return num;
}
let count = 20;
let result = addTen(count);
console.log(count); // 20,没有变化
console.log(result); // 30obj.name = "Nicholas";
}
let person = new Object();
setName(person);
console.log(person.name); // "Nicholas"obj.name = "Nicholas"; //当 obj 在函数内部被重写时,它变成了一个指向本地对象的指针。而那个本地对象在函数执行结束时就被销毁了。obj = new Object(); obj.name = "Greg";
}
let person = new Object();
setName(person);
console.log(person.name); // "Nicholas"

4.1.4 确定类型

基础类型:typeof
对象:instanceof
语法:variable instanceof constructor

4.2 执行上下文与作用域

4.2.1 作用域链增强

 try/catch 语句的 catch 块
 with 语句

function buildUrl() { let qs = "?debug=true"; with(location){ let url = href + qs; } return url;
}

4.2.2 变量声明

1. 使用 var 的函数作用域声明

在使用 var 声明变量时,变量会被自动添加到最接近的上下文

function add(num1, num2) { var sum = num1 + num2; return sum;
}
let result = add(10, 20); // 30
console.log(sum); // 报错:sum 在这里不是有效变量

var省略默认是全局变量

 sum = num1 + num2; return sum;
}
let result = add(10, 20); // 30
console.log(sum); // 30

2. 使用 let 的块级作用域声明

1、级作用域由最近的一对包含花括号{}界定
2、在同一作用域内不能声明两次,而重复的 let 声明会抛出 SyntaxError。

3. 使用 const 的常量声明

1、在其生命周期的任何时候都不能再重新赋予新值
2、赋值为对象的 const 变量不能再被重新赋值为其他引用值,但对象的键则不受限制。
3、如果想让整个对象都不能修改,可以使用 Object.freeze(),这样再给属性赋值时虽然不会报错,但会静默失败:

4.3 垃圾回收

4.3.1 标记清理

先给当前不使用的值加上标记,再回来回收它们的内存

4.3.2 引用计数

对每个值都记录它被
引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为 1。如果同一个值又被赋给另一个变
量,那么引用数加 1。类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减 1。当一
个值的引用数为 0 时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。垃圾回收程序
下次运行的时候就会释放引用数为 0 的值的内存。

4.3.4 内存管理

  1. 通过 const 和 let 声明提升性能
  2. 隐藏类和删除操作
  3. 内存泄漏
  4. 静态分配与对象池

5.1 Date

表示1970年一月一日之后的时间

不传参数的时候,默认当前日期和时间。创建其他日期和时间要传入其毫秒数,es提供了2个辅助函数Date.parse()和 Date.UTC()。

1、Date.parse()方法接收一个表示日期的字符串参数,尝试将这个字符串转换为表示该日期的毫秒数。传入的日期格式不正确的话会返回NaN

2、Date.UTC()方法也返回日期的毫秒表示,但使用的是跟 Date.parse()不同的信息来生成这个值。

传给 Date.UTC()的参数是年、零起点月数(1 月是 0,2 月是 1,以此类推)、日(131)、时(023)、

分、秒和毫秒。这些参数中,只有前两个(年和月)是必需的。如果不提供日,那么默认为 1 日。其他

参数的默认值都是 0

let now = new Date();
new Date(Date.parse("May 23, 2019"))==  new Date("May 23, 2019")
new Date(2005, 4, 5, 17, 55, 55)== new Date(Date.UTC(2005, 4, 5, 17, 55, 55))

5.1.1 继承的方法

Date 类型重写了 toLocaleString()、toString()和 valueOf()方法。

1、toLocaleString():返回与浏览器运行的本地环境一致的日期和时间,包含AM和PM,不包含时区信息

2、toString():24小时制,带时区和时间

3、valueOf(),不返回字符串,返回的日期的毫

5.1.2 日期格式化方法

 toDateString()显示日期中的周几、月、日、年(格式特定于实现);

 toTimeString()显示日期中的时、分、秒和时区(格式特定于实现);

 toLocaleDateString()显示日期中的周几、月、日、年(格式特定于实现和地区);

 toLocaleTimeString()显示日期中的时、分、秒(格式特定于实现和地区);

 toUTCString()显示完整的 UTC 日期(格式特定于实现)。

5.1.3 日期/时间组件方法

方法 说明
getTime() 返回日期的毫秒数,与valueOf()相同
setTime() 设置日期的毫秒数
getFullYear() 返回 4 位数年
getUTCFullYear() 返回 UTC 日期的 4 位数年
setFullYear(year) 设置日期的年(year 必须是 4 位数)
setUTCFullYear(year) 设置 UTC 日期的年(year 必须是 4 位数)
getMonth() 返回日期的月(0 表示 1 月,11 表示 12 月)
getUTCMonth() 返回 UTC 日期的月(0 表示 1 月,11 表示 12 月)
setMonth(month) 设置日期的月(month 为大于 0 的数值,大于 11 加年)
setUTCMonth(month) 设置 UTC 日期的月(month 为大于 0 的数值,大于 11 加年)
getDate() 返回日期中的日(1~31)
getUTCDate() 返回 UTC 日期中的日(1~31)
getHours() 返回日期中的时(0~23)
getUTCHours() 返回 UTC 日期中的时(0~23)
setHours(hours) 设置日期中的时(如果 hours 大于 23,则加日)
setUTCHours(hours) 设置 UTC 日期中的时(如果 hours 大于 23,则加日)
getMinutes() 返回日期中的分(0~59)
getUTCMinutes() 返回 UTC 日期中的分(0~59)
setMinutes(minutes)
setUTCMinutes(minutes) 设置日期中的分(如果 minutes 大于 59,则加时)
getSeconds() 返回日期中的秒(0~59)
getUTCSeconds() 返回 UTC 日期中的秒(0~59
setSeconds(seconds) 设置日期中的秒(如果 seconds 大于 59,则加分)
setUTCSeconds(seconds) 设置 UTC 日期中的秒(如果 seconds 大于 59,则加分)
getMilliseconds() 返回日期中的毫秒
getUTCMilliseconds() 返回 UTC 日期中的毫秒
setMilliseconds(milliseconds) 设置日期中的毫秒

5.2 RegExp

语法:let expression = /pattern/flags;

flags标记:

 g:全局模式,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束。

 i:不区分大小写,表示在查找匹配时忽略 pattern 和字符串的大小写。

 m:多行模式,表示查找到一行文本末尾时会继续查找。

 y:粘附模式,表示只查找从 lastIndex 开始及之后的字符串。

 u:Unicode 模式,启用 Unicode 匹配。

 s:dotAll 模式,表示元字符.匹配任何字符(包括\n 或\r)。

// 匹配字符串中的所有"at"
let pattern1 = /at/g;
// 匹配第一个"bat"或"cat",忽略大小写
let pattern2 = /[bc]at/i;
// 匹配所有以"at"结尾的三字符组合,忽略大小写
let pattern3 = /.at/gi;
// 匹配第一个"bat"或"cat",忽略大小写
let pattern1 = /[bc]at/i;
// 匹配第一个"[bc]at",忽略大小写
let pattern2 = /\[bc\]at/i;
// 匹配所有以"at"结尾的三字符组合,忽略大小写
let pattern3 = /.at/gi;
// 匹配所有".at",忽略大小写
let pattern4 = /\.at/gi;

5.2.1 RegExp 实例属性

 global:布尔值,表示是否设置了 g 标记。

 ignoreCase:布尔值,表示是否设置了 i 标记。

 unicode:布尔值,表示是否设置了 u 标记。

 sticky:布尔值,表示是否设置了 y 标记。

 lastIndex:整数,表示在源字符串中下一次搜索的开始位置,始终从 0 开始。

 multiline:布尔值,表示是否设置了 m 标记。

 dotAll:布尔值,表示是否设置了 s 标记。

 source:正则表达式的字面量字符串(不是传给构造函数的模式字符串),没有开头和结尾的

斜杠。

 flags:正则表达式的标记字符串。始终以字面量而非传入构造函数的字符串模式形式返回(没

有前后斜杠)。

5.2.2 RegExp 实例方法

是 exec(),主要用于配合捕获组使用。这个方法只接收一个参数,即要应

用模式的字符串。如果找到了匹配项,则返回包含第一个匹配信息的数组;如果没找到匹配项,则返回

null。返回的数组虽然是 Array 的实例,但包含两个额外的属性:index 和 input。index 是字符串

中匹配模式的起始位置,input 是要查找的字符串

let pattern = /mom( and dad( and baby)?)?/gi;
let matches = pattern.exec(text);
console.log(matches.index); // 0
console.log(matches.input); // "mom and dad and baby"
console.log(matches[0]); // "mom and dad and baby"
console.log(matches[1]); // " and dad and baby"
console.log(matches[2]); // " and baby"

5.2.3 RegExp 构造函数属性

5.3 原始值包装类型

5.3.1 Boolean

let booleanObject = new Boolean(true);  //返回字符串"true"或"false"。
let falseObject = new Boolean(false);
let result = falseObject && true;
console.log(result); // true let falseValue = false;
result = falseValue && true;
console.log(result); // false

区别:typeof 操作符对原始值返回"boolean",

但对引用值返回"object"。

Boolean 对象是 Boolean 类型的实例,在使用instaceof 操作符时返回 true,但对原始值则返回 false

console.log(typeof falseObject); // object
console.log(typeof falseValue); // boolean console.log(falseObject instanceof Boolean); // true
console.log(falseValue instanceof Boolean); // false

5.3.2 Number

let numberObject = new Number(10); //返回字符串
let num = 10;
console.log(num.toString()); // "10"
console.log(num.toString(2)); // "1010"
console.log(num.toString(8)); // "12"
console.log(num.toString(10)); // "10"
console.log(num.toString(16)); // "a"

其他将数值转换为字符串的方法:

1、toFixed()

let num = 10;
console.log(num.toFixed(2)); // "10.00"let num = 10.005;
console.log(num.toFixed(2)); // "10.01"  四舍五入

2、toExponential():科学计数法

let num = 10;
console.log(num.toExponential(1)); // "1.0e+1" 科学计数法

3、toPrecision():可以固定长度,也可能是科学计数法,接收一个参数,表示结果中数字的总位数(不包含指数)

let num = 99;
console.log(num.toPrecision(1)); // "1e+2"
console.log(num.toPrecision(2)); // "99"
console.log(num.toPrecision(3)); // "99.0"

typeof的结果

let numberObject = new Number(10);
let numberValue = 10;
console.log(typeof numberObject); // "object"
console.log(typeof numberValue); // "number"
console.log(numberObject instanceof Number); // true
console.log(numberValue instanceof Number); // false

isInteger()方法与安全整数

判断一个数值是否为整数

console.log(Number.isInteger(1)); // true
console.log(Number.isInteger(1.00)); // true
console.log(Number.isInteger(1.01)); // false

5.3.3 String

let stringObject = new String("hello world");

1、JavaScript 字符

属性:

1、length

let message = "abcde";
console.log(message.length); // 5

2、charAt() :返回索引位置的字符

let message = "abcde";
console.log(message.charAt(2)); // "c"

3、charCodeAt()码元的字符编码

let message = "abcde";
console.log(message.charCodeAt(2)); // 99
// 十进制 99 等于十六进制 63
console.log(99 === 0x63); // true

4、fromCharCode() 根据给定的 UTF-16 码元创建字符串中的字符

console.log(String.fromCharCode(0x61, 0x62, 0x63, 0x64, 0x65)); // "abcde"
console.log(String.fromCharCode(97, 98, 99, 100, 101)); // "abcde"

2、字符串操作方法

1、concat (不改变原数据)

字符串拼接

let stringValue = "hello ";
let result = stringValue.concat("world","!");
console.log(result); // "hello world!"
console.log(stringValue); // "hello"
2、slice([ )) N
3、substring([)) N
4、substr()N

正数

let stringValue = "hello world";
console.log(stringValue.slice(3)); // "lo world"
console.log(stringValue.substring(3)); // "lo world"
console.log(stringValue.substr(3)); // "lo world"
console.log(stringValue.slice(3, 7)); // "lo w"
console.log(stringValue.substring(3,7)); // "lo w"
console.log(stringValue.substr(3, 7)); // "lo worl"  //截取7个字符串数量

负数:在给 slice()和 substr()传入负参数时,它们的返回结果相同。

这是因为-3 会被转换为 8(长度加上负参数),实际上调用的是 slice(8)和 substr(8)。

substring()方法返回整个字符串,因为-3 会转换为 0。

当第二个参数为负值时,

slice()方法将第二个参数转换为 7,实际上相当于调用 slice(3, 7),因此返回"lo w"。

而 substring()方法会将第二个参数转换为 0,相当于调用substring(3, 0),等价于 substring(0, 3),这是因为这个方法会将较小的参数作为起点,将较大的参数作为终点。

对 substr()来说,第二个参数会被转换为 0,意味着返回的字符串包含零个字符,因而会返回一个空字符串

let stringValue = "hello world";
console.log(stringValue.slice(-3)); // "rld"
console.log(stringValue.substring(-3)); // "hello world"
console.log(stringValue.substr(-3)); // "rld" console.log(stringValue.slice(3, -4)); // "lo w"
console.log(stringValue.substring(3, -4)); // "hel"
console.log(stringValue.substr(3, -4)); // "" (empty string)

3、字符串位置方法

1、indexOf()
2、lastIndexOf()

查找不对返回-1

let stringValue = "hello world";
// 查找第一个
console.log(stringValue.indexOf("o")); // 4
//查找最后一个
console.log(stringValue.lastIndexOf("o")); // 7
let stringValue = "hello world";
//从第六个开始查找
console.log(stringValue.indexOf("o", 6)); // 7
//它从位置 6 开始反向搜索至字符串开头
console.log(stringValue.lastIndexOf("o", 6)); // 4

4、字符串包含方法

1、startsWith()
2、endsWith()
3、includes()

一个参数:

startsWith()检查开始于索引 0 的匹配项,

endsWith()检查开始于索引最末尾的匹配项,

而 includes()检查整个字符串

let message = "foobarbaz";
console.log(message.startsWith("foo")); // true
console.log(message.startsWith("bar")); // false
console.log(message.endsWith("baz")); // true
console.log(message.endsWith("bar")); // false
console.log(message.includes("bar")); // true
console.log(message.includes("qux")); // false

2个参数:

startsWith()和 includes()方法接收可选的第二个参数,表示开始搜索的位置。如果传入第二个参数,则意味着这两个方法会从指定位置向着字符串末尾搜索,忽略该位置之前的所有字符。

let message = "foobarbaz";
console.log(message.startsWith("foo")); // true
console.log(message.startsWith("foo", 1)); // false
console.log(message.startsWith("bar", 3)); // true
console.log(message.includes("bar")); // true
console.log(message.includes("bar", 4)); // false

endsWith()方法接收可选的第二个参数,表示应该当作字符串末尾的位置。

如果不提供这个参数,那么默认就是字符串长度。

如果提供这个参数,那么就好像字符串只有那么多字符一样:

let message = "foobarbaz";
console.log(message.endsWith("bar")); // false
console.log(message.endsWith("bar", 6)); // true5

5、trim()、trimLeft()、trimRight()方法

删除前、后所有空格符

let stringValue = " hello world ";
let trimmedStringValue = stringValue.trim();
console.log(stringValue); // " hello world "
console.log(trimmedStringValue); // "hello world"
console.log(stringValue.trimRight()); // "hello world "
console.log(stringValue.trimLeft()); // " hello world"

6、repeat()方法

将字符串复制的次数

let stringValue = "na ";
console.log(stringValue.repeat(16) + "batman");
// na na na na na na na na na na na na na na na na batman

7、padStart()和 padEnd()方法

复制字符串,如果小于指定长度,则在相应一边填充字符(默认空格),直至满足长度条件。

let stringValue = "foo";
console.log(stringValue.padStart(6)); // " foo"
console.log(stringValue.padStart(9, ".")); // "......foo"
console.log(stringValue.padEnd(6)); // "foo "
console.log(stringValue.padEnd(9, ".")); // "foo......"

如果提供了多个字符的字符串,则会将其拼接并截断以匹配指定长度。此外,如果长度小于或等于字符串长度,则会返回原始字符串

let stringValue = "foo";
console.log(stringValue.padStart(8, "bar")); // "barbafoo"
console.log(stringValue.padStart(2)); // "foo"
console.log(stringValue.padEnd(8, "bar")); // "foobarba"
console.log(stringValue.padEnd(2)); // "foo"

8、字符串迭代与解构

1、迭代

字符串的原型上暴露了一个@@iterator 方法,表示可以迭代字符串的每个字符。

let message = "abc";
let stringIterator = message[Symbol.iterator]();
console.log(stringIterator.next()); // {value: "a", done: false}
console.log(stringIterator.next()); // {value: "b", done: false}
console.log(stringIterator.next()); // {value: "c", done: false}
console.log(stringIterator.next()); // {value: undefined, done: true}
在 for-of 循环中可以通过这个迭代器按序访问每个字符:
for (const c of "abcde") { console.log(c);
}
// a
// b
// c
// d
// e
2、解构([…])
let message = "abcde";
console.log([...message]); // ["a", "b", "c", "d", "e"]

9、字符串大小写转换

let stringValue = "hello world";
//在少数语言中(如土耳其语)
console.log(stringValue.toLocaleUpperCase()); // "HELLO WORLD"
console.log(stringValue.toUpperCase()); // "HELLO WORLD"
console.log(stringValue.toLocaleLowerCase()); // "hello world"
console.log(stringValue.toLowerCase()); // "hello world"

10、字符串模式匹配方法

1、match

RegExp 对象的 exec()方法相同

let text = "cat, bat, sat, fat";
let pattern = /.at/;
// 等价于 pattern.exec(text)
let matches = text.match(pattern);
console.log(matches.index); // 0
console.log(matches[0]); // "cat"
console.log(pattern.lastIndex); // 0
2、search

返回第一个匹配的位置索引,如果没找到则返回-1

let text = "cat, bat, sat, fat";
let pos = text.search(/at/);
console.log(pos); // 1
3、replace
let text = "cat, bat, sat, fat";
//匹配第一个
let result = text.replace("at", "ond");
console.log(result); // "cond, bat, sat, fat"
//全匹配
result = text.replace(/at/g, "ond");
console.log(result); // "cond, bond, sond, fond"

11、localeCompare()方法

比较两个字符串

 如果按照字母表顺序,字符串应该排在字符串参数前头,则返回负值。(通常是-1,具体还要看

与实际值相关的实现。)

 如果字符串与字符串参数相等,则返回 0。

 如果按照字母表顺序,字符串应该排在字符串参数后头,则返回正值。(通常是 1,具体还要看

与实际值相关的实现。)

let stringValue = "yellow";
console.log(stringValue.localeCompare("brick")); // 1
console.log(stringValue.localeCompare("yellow")); // 0
console.log(stringValue.localeCompare("zoo")); // -1

12、HTML 方法

早期浏览器扩展了规范,增加了辅助生成 HTML 标签的方法。

5.4 单例内置对象

5.4.1 Global

1、URL 编码方法

encodeURI()和 encodeURIComponent()方法用于编码统一资源标识符(URI),以便传给浏览器。

区别

encodeURI()不会编码属于 URL 组件的特殊字符,比如冒号、斜杠、问号、井号,

encodeURIComponent()会编码它发现的所有非标准字符。

let url = "http://www.wrox.com/illegal value.js#start";
// "http://www.wrox.com/illegal%20value.js#start"
console.log(encodeURI(url));
// "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.js%23start"
console.log(encodeURIComponent(url));

decodeURI()和 decodeURIComponent()解码

let uri = "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.js%23start";
// http%3A%2F%2Fwww.wrox.com%2Fillegal value.js%23start
console.log(decodeURI(uri));
// http:// www.wrox.com/illegal value.js#start
console.log(decodeURIComponent(uri));

2、eval()方法

这个方法就是一个完整的 ECMAScript 解释器,它接收一个参数,即一个要执行的 ECMAScript(JavaScript)字符串。

eval("console.log('hi')");
//上面这行代码的功能与下一行等价:
console.log("hi");

3、window 对象

所有全局作用域中声明的变量和函数都变成了 window 的属性

var color = "red";
function sayColor() { console.log(window.color);
}
window.sayColor(); // "red"

5.4.2 Math

1、Math 对象属性

2、min()和 max()方法

min()和 max()方法用于确定一组数值中的最小值和最大值

let max = Math.max(3, 54, 32, 16);
console.log(max); // 54
let min = Math.min(3, 54, 32, 16);
console.log(min); // 3let values = [1, 2, 3, 4, 5, 6, 7, 8];
let max = Math.max(...val);

3、舍入方法

 Math.ceil()方法始终向上舍入为最接近的整数。

 Math.floor()方法始终向下舍入为最接近的整数。

 Math.round()方法执行四舍五入。

 Math.fround()方法返回数值最接近的单精度(32 位)浮点值表示

console.log(Math.ceil(25.9)); // 26
console.log(Math.ceil(-25.5)); // -25
console.log(Math.ceil(25.1)); // 26
console.log(Math.round(25.9)); // 26
console.log(Math.round(-25.5)); // -25
console.log(Math.round(25.1)); // 25
console.log(Math.fround(0.4)); // 0.4000000059604645
console.log(Math.fround(0.5)); // 0.5
console.log(Math.fround(25.9)); // 25.899999618530273
console.log(Math.floor(25.9)); // 25
console.log(Math.floor(-25.5)); // 26
console.log(Math.floor(25.1)); // 25

4、random()方法

Math.random()方法返回一个 0~1 范围内的随机数,其中包含 0 但不包含 1

a~b: Math.floor(Math.random() *(b-a+1) +a)

//1~9
Math.floor(Math.random() * 10 + 1)
//2~10
Math.floor(Math.random() * 9 + 2)

5、其他方法

第 6 章集合引用类型

6.1 Object

创建方式:

1、new 操作符和 Object 构造函数

let person = new Object();
person.name = "Nicholas";
person.age = 29;

2、对面字面量

let person = { name: "Nicholas", age: 29
};

6.2 Array

6.2.1 创建数组

1、构造函数

let colors = new Array();
//长度为20的数组
let colors = new Array(20);
//包含 3 个字符串值的数组
let colors = new Array("red", "blue", "green");
//也可以省略操作符
let colors = Array(3); // 创建一个包含 3 个元素的数组
let names = Array("Greg"); // 创建一个只包含一个元素,即字符串"Greg"的数组

2、数组字面量

let colors = ["red", "blue", "green"]; // 创建一个包含 3 个元素的数组
let names = []; // 创建一个空数组
let values = [1,2,]; // 创建一个包含 2 个元素的数组

3、 ES6 新增了两个用于创建数组的静态方法:from()和 of()

3.1、Array.from()的第一个参数是一个类数组对象,即任何可迭代的结构,或者有一个 length 属性和可索引元素的结构。

// 字符串会被拆分为单字符数组
console.log(Array.from("Matt")); // ["M", "a", "t", "t"]
// 可以使用 from()将集合和映射转换为一个新数组
const m = new Map().set(1, 2).set(3, 4);
const s = new Set().add(1).add(2).add(3).add(4);
console.log(Array.from(m)); // [[1, 2], [3, 4]]
console.log(Array.from(s)); // [1, 2, 3, 4]
// Array.from()对现有数组执行浅复制
const a1 = [1, 2, 3, 4];
const a2 = Array.from(a1);
console.log(a1); // [1, 2, 3, 4]
alert(a1 === a2); // false
// 可以使用任何可迭代对象
const iter = { *[Symbol.iterator]() { yield 1; yield 2; yield 3; yield 4; }
};
console.log(Array.from(iter)); // [1, 2, 3, 4]// arguments 对象可以被轻松地转换为数组
function getArgsArray() { return Array.from(arguments);
}
console.log(getArgsArray(1, 2, 3, 4)); // [1, 2, 3, 4]
// from()也能转换带有必要属性的自定义对象
const arrayLikeObject = { 0: 1, 1: 2, 2: 3, 3: 4, length: 4
};
console.log(Array.from(arrayLikeObject)); // [1, 2, 3, 4]

Array.from()还接收第二个可选的映射函数参数。这个函数可以直接增强新数组的值,而无须像调用 Array.from().map()那样先创建一个中间数组。还可以接收第三个可选参数,用于指定映射函数中 this 的值。但这个重写的 this 值在箭头函数中不适用。

const a1 = [1, 2, 3, 4];
const a2 = Array.from(a1, x => x**2);
const a3 = Array.from(a1, function(x) {return x**this.exponent}, {exponent: 2});
console.log(a2); // [1, 4, 9, 16]
console.log(a3); // [1, 4, 9, 16]
Array.of()可以把一组参数转换为数组。这个方法用于替代在 ES6之前常用的 Array.prototype.
slice.call(arguments),一种异常笨拙的将 arguments 对象转换为数组的写法:
console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
console.log(Array.of(undefined)); // [undefined]

6.2.2 数组空位

使用数组字面量初始化数组时,可以使用一串逗号来创建空位。值为undefined

const options = [,,,,,]; // 创建包含 5 个元素的数组
console.log(options.length); // 5
console.log(options); // [,,,,,]
const options = [1,,,,5];
for (const option of options) { console.log(option === undefined);
}
// false
// true
// true
// true
// falseconst a = Array.from([,,,]); // 使用 ES6 的 Array.from()创建的包含 3 个空位的数组
for (const val of a) { alert(val === undefined);
}
// true
// true
// true
alert(Array.of(...[,,,])); // [undefined, undefined, undefined]
for (const [index, value] of options.entries()) { alert(value);
}
// 1
// undefined
// undefined
// undefined
// 5

ES6 之前的方法则会忽略这个空位,但具体的行为也会因方法而异:

const options = [1,,,,5];
// map()会跳过空位置
console.log(options.map(() => 6)); // [6, undefined, undefined, undefined, 6]
// join()视空位置为空字符串
console.log(options.join('-')); // "1----5"

6.2.3 数组索引

要取得或设置数组的值,需要使用中括号并提供相应值的数字索引,如下所示:

let colors = ["red", "blue", "green"]; // 定义一个字符串数组
alert(colors[0]); // 显示第一项
colors[2] = "black"; // 修改第三项
colors[3] = "brown"; // 添加第四项

数组 length 属性不是只读的。通过修改 length 属性,可以从数组末尾删除或者添加元素

let colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组
colors.length = 2;
alert(colors[2]); // undefined

如果将 length 设置为大于数组元素数的值,则新添加的元素都将以undefined 填充

let colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组
colors.length = 4;
alert(colors[3]); // undefined
//向数据末尾添加元素
let colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组
colors[colors.length] = "black"; // 添加一种颜色(位置 3)
colors[colors.length] = "brown"; // 再添加一种颜色(位置 4)

6.2.4 检测数组

1、instanceof

if (value instanceof Array){ // 操作数组
}

2、Array.isArray()

if (Array.isArray(value)){ // 操作数组
}

6.2.5 迭代器方法

keys()、values()和entries()

keys()返回数组索引的迭代器,values()返回数组元素的迭代器,而 entries()返回索引/值对

const a = ["foo", "bar", "baz", "qux"];
// 因为这些方法都返回迭代器,所以可以将它们的内容
// 通过 Array.from()直接转换为数组实例
const aKeys = Array.from(a.keys());
const aValues = Array.from(a.values());
const aEntries = Array.from(a.entries());
console.log(aKeys); // [0, 1, 2, 3]
console.log(aValues); // ["foo", "bar", "baz", "qux"]
console.log(aEntries); // [[0, "foo"], [1, "bar"], [2, "baz"], [3, "qux"]]
使用 ES6 的解构可以非常容易地在循环中拆分键/值对:
const a = ["foo", "bar", "baz", "qux"];
for (const [idx, element] of a.entries()) { alert(idx); alert(element);
}
// 0
// foo
// 1
// bar
// 2
// baz
// 3
// qux

6.2.6 复制和填充方法

1、批量复制copyWithin()

会按照指定范围浅复制数组中的部分内容,然后将它们插入到指定索引开始的位置

let ints, reset = () => ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 从 ints 中复制索引 0 开始的内容,插入到索引 5 开始的位置
// 在源索引或目标索引到达数组边界时停止
ints.copyWithin(5);
console.log(ints); // [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
reset();
// 从 ints 中复制索引 5 开始的内容,插入到索引 0 开始的位置
ints.copyWithin(0, 5);
console.log(ints); // [5, 6, 7, 8, 9, 5, 6, 7, 8, 9]
reset();
// 从 ints 中复制索引 0 开始到索引 3 结束的内容
// 插入到索引 4 开始的位置
ints.copyWithin(4, 0, 3);
alert(ints); // [0, 1, 2, 3, 0, 1, 2, 7, 8, 9]
reset();
// JavaScript 引擎在插值前会完整复制范围内的值
// 因此复制期间不存在重写的风险
ints.copyWithin(2, 0, 6);
alert(ints); // [0, 1, 0, 1, 2, 3, 4, 5, 8, 9]
reset();
// 支持负索引值,与 fill()相对于数组末尾计算正向索引的过程是一样的
ints.copyWithin(-4, -7, -3);
alert(ints); // [0, 1, 2, 3, 4, 5, 3, 4, 5, 6]
copyWithin()静默忽略超出数组边界、零长度及方向相反的索引范围:
let ints, reset = () => ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 索引过低,忽略
ints.copyWithin(1, -15, -12);
alert(ints); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset()
// 索引过高,忽略
ints.copyWithin(1, 12, 15);
alert(ints); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 索引反向,忽略
ints.copyWithin(2, 4, 2);
alert(ints); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 索引部分可用,复制、填充可用部分
ints.copyWithin(4, 7, 10)
alert(ints); // [0, 1, 2, 3, 7, 8, 9, 7, 8, 9];

2、及填充数组fill()

使用 fill()方法可以向一个已有的数组中插入全部或部分相同的值。

开始索引用于指定开始填充的位置,它是可选的。如果不提供结束索引,则一直填充到数组末尾。负值索引从数组末尾开始计算。也可以将负索引想象成数组长度加上它得到的一个正索引:

const zeroes = [0, 0, 0, 0, 0];
// 用 5 填充整个数组
zeroes.fill(5);
console.log(zeroes); // [5, 5, 5, 5, 5]
zeroes.fill(0); // 重置
// 用 6 填充索引大于等于 3 的元素
zeroes.fill(6, 3);
console.log(zeroes); // [0, 0, 0, 6, 6]
zeroes.fill(0); // 重置
// 用 7 填充索引大于等于 1 且小于 3 的元素
zeroes.fill(7, 1, 3);
console.log(zeroes); // [0, 7, 7, 0, 0];
zeroes.fill(0); // 重置
// 用 8 填充索引大于等于 1 且小于 4 的元素
// (-4 + zeroes.length = 1)
// (-1 + zeroes.length = 4)
zeroes.fill(8, -4, -1);
console.log(zeroes); // [0, 8, 8, 8, 0];

fill()静默忽略超出数组边界、零长度及方向相反的索引范围:

const zeroes = [0, 0, 0, 0, 0];
// 索引过低,忽略
zeroes.fill(1, -10, -6);
console.log(zeroes); // [0, 0, 0, 0, 0]
// 索引过高,忽略
zeroes.fill(1, 10, 15);
console.log(zeroes); // [0, 0, 0, 0, 0]
// 索引反向,忽略
zeroes.fill(2, 4, 2);
console.log(zeroes); // [0, 0, 0, 0, 0]
// 索引部分可用,填充可用部分
zeroes.fill(4, 3, 10)
console.log(zeroes); // [0, 0, 0, 4, 4]

6.2.7 转换方法

1、toString()

返回由数组中每个值的等效字符串拼接而成的一个逗号分隔的字符串。对数组的每个值都会调用其 toString()方法

let colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组
console.log(colors.toString()); // red,blue,green

2、toLocaleString()

会得到一个逗号分隔的数组值的字符串,会调用数组每个值的 toLocaleString()方法

let person1 = { toLocaleString() { return "Nikolaos"; }, toString() { return "Nicholas"; }
};
let person2 = { toLocaleString() { return "Grigorios"; }, toString() { return "Greg"; }
};
let people = [person1, person2];
alert(people); // Nicholas,Greg  //会调用每个值的toString()方法
alert(people.toString()); // Nicholas,Greg
alert(people.toLocaleString()); // Nikolaos,Grigorios

3、join()

可以接收一个参数,作为分隔符,不传参数或传undefined时,会默认","分割

let colors = ["red", "green", "blue"];
alert(colors.join(",")); // red,green,blue
alert(colors.join("||")); // red||green||blue

6.2.8 栈方法

栈是一种后进先出的结构,也就是最近添加的项先被删除。数据项的插入(称为推入,push)和删除(称为弹出,pop)只在栈的一个地方发生,即栈顶。

1、push()

添加到数组尾部,改变原数组,返回数组的长度

2、pop()

删除数组的最后一项,改变原数组,返回被删除的项

let colors = new Array(); // 创建一个数组
let count = colors.push("red", "green"); // 推入两项
console.log(count); // 2
count = colors.push("black"); // 再推入一项
console.log(count); // 3
let item = colors.pop(); // 取得最后一项
console.log(item); // black
console.log(colors.length); // 2

6.2.9 队列方法

先进先出,在末尾添加数据,在开头获取数据

1、shift()

删除第一项 返回删除的项,修改原数组

2、unshift()

在头部添加一项,返回长度 修改原数组


let arr =[20, 30];
count = arr.shift(); //删除第一项
console.log(count);  //返回删除的项 20
console.log(arr)  //[30]
let num =[40,50]; //
count = num.unshift(5); //
console.log(count); // 3 返回长度
console.log(num) // [5,40,50]

6.2.10 排序方法

1、reverse()

反向排序

let values = [1, 2, 3, 4, 5];
values.reverse();
alert(values); // 5,4,3,2,1

2、sort()

sort会按照升序重新排列数据元素,会在每一项上调用string()转型函数,即使是整数,也是通过字符串来决定顺序,

let values = [0, 1, 5, 10, 15];
values.sort();
alert(values); // 0,1,10,15,5     字符串"10"在字符串"5"的前头

以上情况在大多数情况下都不是合适的,所以sort方法接收一个比较函数,用来判断哪个值排在前面

比较函数接收两个参数,

如果第一个参数应该排在第二个参数前面,就返回负值;

如果两个参数相等,就返回 0;

如果第一个参数应该排在第二个参数后面,就返回正值。

//升序
function compare(value1, value2) { if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; }
}
let values = [0, 1, 5, 10, 15];
values.sort(compare);
alert(values); // 0,1,5,10,15//降序
function compare(value1, value2) { if (value1 < value2) { return 1; } else if (value1 > value2) { return -1; } else { return 0; }
}
let values = [0, 1, 5, 10, 15];
values.sort(compare);
alert(values); // 15,10,5,1,0
//简写
let values = [0, 1, 5, 10, 15];
values.sort((a, b) => a < b ? 1 : a > b ? -1 : 0);
//values.sort((a, b) => a-b);
alert(values); // 15,10,5,1,0

6.2.11 操作方法

1、concat()

let colors = ["red", "green", "blue"];
let colors2 = colors.concat("yellow", ["black", "brown"]);
console.log(colors); // ["red", "green","blue"]
console.log(colors2); // ["red", "green", "blue", "yellow", "black", "brown"]

2、slice()

用于创建一个包含原有数组中一个或多个元素的新数组。

如果只有一个参数,则 slice()会返回该索引到数组末尾的所有元素。

如果有两个参数,则 slice()返回从开始索引到结束索引对应的所有元素,其中不包含结束索引对应的元素。

不影响原数组。

let colors = ["red", "green", "blue", "yellow", "purple"];
console.log(colors.slice(0))  // ['red', 'green', 'blue', 'yellow', 'purple']
console.log(colors.slice(1)); // green,blue,yellow,purple
console.log(colors.slice(1, 4)); // green,blue,yellow

3、splice()

 删除。需要给 splice()传 2 个参数:要删除的第一个元素的位置和要删除的元素数量。可以从数组中删除任意多个元素,比如 splice(0, 2)会删除前两个元素。

 插入。需要给 splice()传 3 个参数:开始位置、0(要删除的元素数量)和要插入的元素,可以在数组中指定的位置插入元素。第三个参数之后还可以传第四个、第五个参数,乃至任意多个要插入的元素。比如,splice(2, 0, “red”, “green”)会从数组位置 2 开始插入字符串"red"和"green"。

 替换。splice()在删除元素的同时可以在指定位置插入新元素,同样要传入 3 个参数:开始位置、要删除元素的数量和要插入的任意多个元素。要插入的元素数量不一定跟删除的元素数量一致。比如,splice(2, 1, “red”, “green”)会在位置 2 删除一个元素,然后从该位置开始向数组中插入"red"和"green"。

splice()返回被删除的元素,如果没有删除元素,则返回空数组

let colors = ["red", "green", "blue"];
let removed = colors.splice(0,1); // 删除第一项
console.log(colors); // green,blue
console.log(removed); // red,只有一个元素的数组
removed = colors.splice(1, 0, "yellow", "orange"); // 在位置 1 插入两个元素
console.log(colors); // green,yellow,orange,blue
console.log(removed); // 空数组
removed = colors.splice(1, 1, "red", "purple"); // 插入两个值,删除一个元素
console.log(colors); // green,red,purple,orange,blue
console.log(removed); // yellow,只有一个元素的数组

6.2.12 搜索和位置方法

1、严格相等

1、indexOf()

返回要查找的元素在数组中的位置,如果没找到则返回-1

2、lastIndexOf()

返回要查找的元素在数组中的位置,如果没找到则返回-1

3、includes()

返回布尔值

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
console.log(numbers.indexOf(4)); // 3
console.log(numbers.lastIndexOf(4)); // 5
console.log(numbers.includes(4)); // true
//会进行全等(====)比较
console.log(numbers.indexOf("4")); // -1
console.log(numbers.lastIndexOf("4")); // -1
console.log(numbers.includes("4")); // falseconsole.log(numbers.indexOf(4, 4)); // 5
console.log(numbers.lastIndexOf(4, 4)); // 3
console.log(numbers.includes(4, 7)); // false let person = { name: "Nicholas" };
let people = [{ name: "Nicholas" }];
let morePeople = [person];
console.log(people.indexOf(person)); // -1
console.log(morePeople.indexOf(person)); // 0
console.log(people.includes(person)); // false
console.log(morePeople.includes(person)); // true

2、断言函数

1、find()

返回第一个匹配的元素

2、findIndex()

返回第一个匹配元素的索引。

找到匹配项后,这两个方法都不再继续搜索。

const people = [{ name: "Matt", age: 27 }, { name: "Nicholas", age: 29 }
];
console.log(people.find((element, index, array) => element.age < 28));
// {name: "Matt", age: 27}
console.log(people.findIndex((element, index, array) => element.age < 28));
//0

6.2.13 迭代方法

 every():对数组每一项都运行传入的函数,如果对每一项函数都返回 true,则这个方法返回 true。

 filter():对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回。

 forEach():对数组每一项都运行传入的函数,没有返回值。

 map():对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组。

 some():对数组每一项都运行传入的函数,如果有一项函数返回 true,则这个方法返回 true。

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let everyResult = numbers.every((item, index, array) => item > 2);
console.log(everyResult); // false let someResult = numbers.some((item, index, array) => item > 2);
console.log(someResult); // truelet filterResult = numbers.filter((item, index, array) => item > 2);
console.log(filterResult); // [3, 4, 5, 4, 3]//没有返回值
let mapResult = numbers.map((item, index, array) => item * 2);
console.log(mapResult); //  [2, 4, 6, 8, 10, 8, 6, 4, 2]
numbers.forEach((item, index, array) => {// 执行某些操作
})

6.2.14 归并方法

接收 4 个参数:上一个归并值、当前项、当前项的索引和数组本身。这个函数返回的任何值都会作为下一次调用同一个函数的第一个参数。

1、reduce()

可以使用 reduce()函数执行累加数组中所有数值的操作,比如:
let values = [1, 2, 3, 4, 5];
let sum = values.reduce((prev, cur, index, array) => prev + cur);
alert(sum); // 15

法从数组第一项开始遍历到最后一项。

2、reduceRight()

与reduce相反,从最后一项开始遍历至第一项

6.4 Map

6.4.1 基本 API

const m = new Map();

// 使用嵌套数组初始化映射
const m1 = new Map([ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"]
]);
console.log(m1.size); // 3
// 使用自定义迭代器初始化映射
const m2 = new Map({ [Symbol.iterator]: function*() { yield ["key1", "val1"]; yield ["key2", "val2"]; yield ["key3", "val3"]; }
});
console.log(m2.size); // 3
// 映射期待的键/值对,无论是否提供
const m3 = new Map([[]]);
console.log(m3.has(undefined)); // true
console.log(m3.get(undefined)); // undefined

1、set

添加键值对

2、get()

查询

3、has()

查询

4、delete()

只删除一个

5、clear()

删除所有键值对

const m = new Map();
console.log(m.has("firstName")); // false
console.log(m.get("firstName")); // undefined
console.log(m.size); // 0
// 可以把多个操作连缀起来
m.set("firstName", "Matt") .set("lastName", "Frisbie");
console.log(m.has("firstName")); // true
console.log(m.get("firstName")); // Matt
console.log(m.size); // 2
m.delete("firstName"); // 只删除这一个键/值对
console.log(m.has("firstName")); // false
console.log(m.has("lastName")); // true
console.log(m.size); // 1
m.clear(); // 清除这个映射实例中的所有键/值对
console.log(m.has("firstName")); // false
console.log(m.has("lastName")); // false
console.log(m.size); // 0

Object 只能使用数值、字符串或符号作为键,而Map 可以使用任何 JavaScript 数据类型作为键。

const m = new Map();
const functionKey = function() {};
const symbolKey = Symbol();
const objectKey = new Object();
m.set(functionKey, "functionValue");
m.set(symbolKey, "symbolValue");
m.set(objectKey, "objectValue");
console.log(m.get(functionKey)); // functionValue
console.log(m.get(symbolKey)); // symbolValue
console.log(m.get(objectKey)); // objectValue
// SameValueZero 比较意味着独立实例不冲突
console.log(m.get(function() {})); // undefined

与严格相等一样,在映射中用作键和值的对象及其他"集合"类型,在自己的内容和属性被修改时仍然保持不变:

const m = new Map();
const objKey = {}, objVal = {}, arrKey = [], arrVal = [];
m.set(objKey, objVal);
m.set(arrKey, arrVal);
objKey.foo = "foo";
objVal.bar = "bar";
arrKey.push("foo");
arrVal.push("bar");
console.log(m.get(objKey)); // {bar: "bar"}
console.log(m.get(arrKey)); // ["bar"]

6.4.2 顺序与迭代

map是有序的

映射实例可以提供一个迭代器(Iterator),能以插入顺序生成[key, value]形式的数组。可以通过 entries()方法(或者 Symbol.iterator 属性,它引用 entries())取得这个迭代器:

const m = new Map([ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"]
]);
alert(m.entries === m[Symbol.iterator]); // true
for (let pair of m.entries()) { alert(pair);
}
// [key1,val1]
// [key2,val2]
// [key3,val3]
for (let pair of m[Symbol.iterator]()) { alert(pair);
}
// [key1,val1]
// [key2,val2]
// [key3,val3]
因为 entries()是默认迭代器,所以可以直接对映射实例使用扩展操作,把映射转换为数组:
const m = new Map([ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"]
]);
console.log([...m]); // [[key1,val1],[key2,val2],[key3,val3]]
const m = new Map([ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"]
]);
m.forEach((val, key) => alert(`${key} -> ${val}`));
// key1 -> val1
// key2 -> val2
// key3 -> val3
keys()和 values()分别返回以插入顺序生成键和值的迭代器:
const m = new Map([ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"]
]);
for (let key of m.keys()) { alert(key);
}
// key1
// key2
// key3
for (let key of m.values()) { alert(key);
}
// value1
// value2
// value3

键和值在迭代器遍历时是可以修改的,但映射内部的引用则无法修改。

const m1 = new Map([ ["key1", "val1"]
]);
// 作为键的字符串原始值是不能修改的
for (let key of m1.keys()) { key = "newKey"; alert(key); // newKey alert(m1.get("key1")); // val1
}
const keyObj = {id: 1};
const m = new Map([ [keyObj, "val1"]
]);
// 修改了作为键的对象的属性,但对象在映射内部仍然引用相同的值
for (let key of m.keys()) { key.id = "newKey"; alert(key); // {id: "newKey"} alert(m.get(keyObj)); // val1
}
alert(keyObj); // {id: "newKey"}

6.4.3 选择 Object 还是 Map

1. 内存占用

Object 和 Map 的工程级实现在不同浏览器间存在明显差异,但存储单个键/值对所占用的内存数量都会随键的数量线性增加。批量添加或删除键/值对则取决于各浏览器对该类型内存分配的工程实现。

不同浏览器的情况不同,但给定固定大小的内存,Map 大约可以比 Object 多存储 50%的键/值对。

2、插入性能

向 Object 和 Map 中插入新键/值对的消耗大致相当,不过插入 Map 在所有浏览器中一般会稍微快一点儿。对这两个类型来说,插入速度并不会随着键/值对数量而线性增加。如果代码涉及大量插入操作,那么显然 Map 的性能更佳。

3、查找速度

与插入不同,从大型 Object 和 Map 中查找键/值对的性能差异极小,但如果只包含少量键/值对,

则 Object 有时候速度更快。在把 Object 当成数组使用的情况下(比如使用连续整数作为属性),浏览器引擎可以进行优化,在内存中使用更高效的布局。这对 Map 来说是不可能的。对这两个类型而言,查找速度不会随着键/值对数量增加而线性增加。如果代码涉及大量查找操作,那么某些情况下可能选择Object 更好一些。

4、删除性能

使用 delete 删除 Object 属性的性能一直以来饱受诟病,目前在很多浏览器中仍然如此。为此,出现了一些伪删除对象属性的操作,包括把属性值设置为 undefined 或 null。但很多时候,这都是一种讨厌的或不适宜的折中。而对大多数浏览器引擎来说,Map 的 delete()操作都比插入和查找更快。如果代码涉及大量删除操作,那么毫无疑问应该选择 Map。

6.5 WeakMap

ECMAScript 6 新增的“弱映射”(WeakMap)是一种新的集合类型,为这门语言带来了增强的键/值对存储机制。WeakMap 是 Map 的“兄弟”类型,其 API 也是 Map 的子集。WeakMap 中的“weak”(弱),描述的是 JavaScript 垃圾回收程序对待“弱映射”中键的方式。

6.5.1 基本 API

const wm = new WeakMap();

弱映射中的键只能是 Object 或者继承自 Object 的类型,尝试使用非对象设置键会抛出TypeError。值的类型没有限制。

// 初始化是全有或全无的操作
// 只要有一个键无效就会抛出错误,导致整个初始化失败
const wm2 = new WeakMap([ [key1, "val1"], ["BADKEY", "val2"], [key3, "val3"]
]);
// TypeError: Invalid value used as WeakMap key
typeof wm2;
// ReferenceError: wm2 is not defined

初始化之后可以使用 set()再添加键/值对,可以使用 get()和 has()查询,还可以使用 delete()删除,版本28之后不在使用clear()方法

6.5.2 弱键

这些键不属于正式的引用,不会阻止垃圾回收。但要注意的是,弱映射中值的引用可不是“弱弱地拿着”的。只要键存在,键/值对就会存在于映射中,并被当作对值的引用,因此就不会被当作垃圾回收。

const wm = new WeakMap();
wm.set({}, "val");

set()方法初始化了一个新对象并将它用作一个字符串的键。因为没有指向这个对象的其他引用,所以当这行代码执行完成后,这个对象键就会被当作垃圾回收。然后,这个键/值对就从弱映射中消失了,使其成为一个空映射。在这个例子中,因为值也没有被引用,所以这对键/值被破坏以后,值本身也会成为垃圾回收的目标。

const wm = new WeakMap();
const container = { key: {}
};
wm.set(container.key, "val");
function removeReference() { container.key = null;
}

container 对象维护着一个对弱映射键的引用,因此这个对象键不会成为垃圾回收的目标。不过,如果调用了 removeReference(),就会摧毁键对象的最后一个引用,垃圾回收程序就可以把这个键/值对清理掉。

6.5.3 不可迭代键

因为 WeakMap 中的键/值对任何时候都可能被销毁,所以没必要提供迭代其键/值对的能力。当然,也用不着像 clear()这样一次性销毁所有键/值的方法。

6.5.4 使用弱映射

  1. 私有变量

    弱映射造就了在 JavaScript 中实现真正私有变量的一种新方式。前提很明确:私有变量会存储在弱映射中,以对象实例为键,以私有成员的字典为值。

    const wm = new WeakMap();
    class User { constructor(id) { this.idProperty = Symbol('id'); this.setId(id); } setPrivate(property, value) { const privateMembers = wm.get(this) || {}; privateMembers[property] = value; wm.set(this, privateMembers); } getPrivate(property) { return wm.get(this)[property]; } setId(id) { this.setPrivate(this.idProperty, id); } getId() { return this.getPrivate(this.idProperty); }
    }
    const user = new User(123);
    alert(user.getId()); // 123
    user.setId(456);
    alert(user.getId()); // 456
    // 并不是真正私有的
    alert(wm.get(user)[user.idProperty]); // 456
    

    对于上面的实现,外部代码只需要拿到对象实例的引用和弱映射,就可以

    取得“私有”变量了。为了避免这种访问,可以用一个闭包把 WeakMap 包装起来,这样就可以把弱映射与外界完全隔离开了:

    const User = (() => { const wm = new WeakMap(); class User { constructor(id) { this.idProperty = Symbol('id');this.setId(id); } setPrivate(property, value) { const privateMembers = wm.get(this) || {}; privateMembers[property] = value; wm.set(this, privateMembers); } getPrivate(property) { return wm.get(this)[property]; } setId(id) { this.setPrivate(this.idProperty, id); } getId(id) { return this.getPrivate(this.idProperty); } } return User;
    })();
    const user = new User(123);
    alert(user.getId()); // 123
    user.setId(456);
    alert(user.getId()); // 456
    
  2. DOM 节点元数据

    因为 WeakMap 实例不会妨碍垃圾回收,所以非常适合保存关联元数据。

    当节点从 DOM 树中被删除后,垃圾回收程序就可以立即释放其内存(假设没有其他地方引用这个对象):

    const wm = new WeakMap();
    const loginButton = document.querySelector('#login');
    // 给这个节点关联一些元数据
    wm.set(loginButton, {disabled: true})
    

    6.6 Set

    Set 在很多方面都像是加强的 Map,这是因为它们的大多数 API 和行为都是共有的。

    6.6.1 基本 API

    const m = new Set();

    // 使用数组初始化集合
    const s1 = new Set(["val1", "val2", "val3"]);
    alert(s1.size); // 3
    // 使用自定义迭代器初始化集合
    const s2 = new Set({ [Symbol.iterator]: function*() { yield "val1"; yield "val2"; yield "val3"; }
    });
    alert(s2.size); // 3
    

    初始化之后,可以使用 add()增加值,使用 has()查询,通过 size 取得元素数量,以及使用 delete()和 clear()删除元素:

    const s = new Set();
    alert(s.has("Matt")); // false
    alert(s.size); // 0
    s.add("Matt") .add("Frisbie");
    alert(s.has("Matt")); // true
    alert(s.size); // 2
    s.delete("Matt");
    alert(s.has("Matt")); // false
    alert(s.has("Frisbie")); // true
    alert(s.size); // 1
    s.clear(); // 销毁集合实例中的所有值
    alert(s.has("Matt")); // false
    alert(s.has("Frisbie")); // false
    alert(s.size); // 0
    

    与 Map 类似,Set 可以包含任何 JavaScript 数据类型作为值。

    const s = new Set();
    const functionVal = function() {};
    const symbolVal = Symbol();
    const objectVal = new Object();
    s.add(functionVal);
    s.add(symbolVal);
    s.add(objectVal);
    alert(s.has(functionVal)); // true
    alert(s.has(symbolVal)); // true
    alert(s.has(objectVal)); // true
    // SameValueZero 检查意味着独立的实例不会冲突
    alert(s.has(function() {})); // false
    

    与严格相等一样,用作值的对象和其他“集合”类型在自己的内容或属性被修改时也不会改变:

    const s = new Set();
    const objVal = {}, arrVal = [];
    s.add(objVal);
    s.add(arrVal);
    objVal.bar = "bar";
    arrVal.push("bar");
    alert(s.has(objVal)); // true
    alert(s.has(arrVal)); // true
    add()和 delete()操作是幂等的。delete()返回一个布尔值,表示集合中是否存在要删除的值:
    const s = new Set();
    s.add('foo');
    alert(s.size); // 1
    s.add('foo');
    alert(s.size); // 1
    // 集合里有这个值
    alert(s.delete('foo')); // true
    // 集合里没有这个值
    alert(s.delete('foo')); // false
    

    6.6.2 顺序与迭代

    Set 会维护值插入时的顺序,因此支持按顺序迭代。

    可以通过 values()方法及其别名方法 keys()(或者 Symbol.iterator 属性,它引用 values())取得这个迭代器:

    const s = new Set(["val1", "val2", "val3"]);
    alert(s.values === s[Symbol.iterator]); // true
    alert(s.keys === s[Symbol.iterator]); // true
    for (let value of s.values()) { alert(value);
    }
    // val1
    // val2
    // val3
    for (let value of s[Symbol.iterator]()) { alert(value);
    }
    // val1
    // val2
    // val3
    

因为 values()是默认迭代器,所以可以直接对集合实例使用扩展操作,把集合转换为数组

const s = new Set(["val1", "val2", "val3"]);
console.log([...s]); // ["val1", "val2", "val3"]

集合的 entries()方法返回一个迭代器,可以按照插入顺序产生包含两个元素的数组,这两个元素是集合中每个值的重复出现:

const s = new Set(["val1", "val2", "val3"]);
for (let pair of s.entries()) { console.log(pair);
}
// ["val1", "val1"]
// ["val2", "val2"]
// ["val3", "val3"]

如果不使用迭代器,而是使用回调方式,则可以调用集合的 forEach()方法并传入回调,依次迭代每个键/值对。传入的回调接收可选的第二个参数,这个参数用于重写回调内部 this 的值

const s = new Set(["val1", "val2", "val3"]);
s.forEach((val, dupVal) => alert(`${val} -> ${dupVal}`));
// val1 -> val1
// val2 -> val2
// val3 -> val3
修改集合中值的属性不会影响其作为集合值的身份:
const s1 = new Set(["val1"]);
// 字符串原始值作为值不会被修改
for (let value of s1.values()) {value = "newVal"; alert(value); // newVal alert(s1.has("val1")); // true
}
const valObj = {id: 1};
const s2 = new Set([valObj]);
// 修改值对象的属性,但对象仍然存在于集合中
for (let value of s2.values()) { value.id = "newVal"; alert(value); // {id: "newVal"} alert(s2.has(valObj)); // true
}
alert(valObj); // {id: "newVal"}

6.7 WeakSet

弱集合

6.7.1 基本 API

const ws = new WeakSet();

const val1 = {id: 1}, val2 = {id: 2}, val3 = {id: 3};
// 使用数组初始化弱集合
const ws1 = new WeakSet([val1, val2, val3]);
alert(ws1.has(val1)); // true
alert(ws1.has(val2)); // true
alert(ws1.has(val3)); // true
// 初始化是全有或全无的操作
// 只要有一个值无效就会抛出错误,导致整个初始化失败
const ws2 = new WeakSet([val1, "BADVAL", val3]);
// TypeError: Invalid value used in WeakSet
typeof ws2;
// ReferenceError: ws2 is not defined
// 原始值可以先包装成对象再用作值
const stringVal = new String("val1");
const ws3 = new WeakSet([stringVal]);
alert(ws3.has(stringVal)); // true

初始化之后可以使用 add()再添加新值,可以使用 has()查询,还可以使用 delete()删除

6.7.2 弱值

这些值不属于正式的引用,不会阻止垃圾回收。

第7 章 迭代器与生成器

7.1 理解迭代

循环是迭代机制的基础,可以指定迭代的次数,以及每次迭代要执行的操作

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

迭代会在一个有序集合上进行,数组就是最典型的例子,数组有已知的长度,且数组每一项都可以通过索引获取

let collection = ['foo', 'bar', 'baz'];
for (let index = 0; index < collection.length; ++index) { console.log(collection[index]);
}

由于如下原因,通过这种循环来执行例程并不理想。

 迭代之前需要事先知道如何使用数据结构。数组中的每一项都只能先通过引用取得数组对象,

然后再通过[]操作符取得特定索引位置上的项。这种情况并不适用于所有数据结构。

 遍历顺序并不是数据结构固有的。通过递增索引来访问数据是特定于数组类型的方式,并不适用于其他具有隐式顺序的数据结构

es5新增了forEach()方法,解决了单独记录索引和通过数组对象取得值的问题,不过这种没有办法标识迭代何时终止。

7.2迭代器

就是重复既定的任务,直到完成

7.2.1 可迭代协议

实现 Iterable 接口(可迭代协议)要求同时具备两种能力:支持迭代的自我识别能力和创建实现Iterator 接口的对象的能力。

第 8 章 对象、类与面向对象编程

8.1 理解对象

创建自定义对象的通常方式是创建 Object 的一个新实例,然后再给它添加属性和方法,如下例所示:

let person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function() { console.log(this.name);
};// 字面量写法
let person = { name: "Nicholas", age: 29, job: "Software Engineer", sayName() { console.log(this.name); }
};

8.1.1 属性的类型

属性分两种:数据属性和访问器属性。

1、数据属性

包含一个保存数据值的位置,值会从这个位置读取,也会写入到这个位置,数据属性有4个特性描述它们的行为:

 [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是 true,如前面的例子所示。

 [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是 true,如前面的例子所示。

 [[Writable]]:表示属性的值是否可以被修改。默认情况下,所有直接定义在对象上的属性的这个特性都是 true,如前面的例子所示。

 [[Value]]:包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性的默认值为 undefined。

let person = { name: "Nicholas"
};

这里,我们创建了一个名为 name 的属性,并给它赋予了一个值"Nicholas"。这意味着[[Value]]特性会被设置为"Nicholas",之后对这个值的任何修改都会保存这个位置。

要修改属性的默认特性,就必须使用 Object.defineProperty()方法。这个方法接收 3 个参数:要给其添加属性的对象、属性的名称和一个描述符对象

let person = {};
Object.defineProperty(person, "name", { writable: false, value: "Nicholas"
});
console.log(person.name); // "Nicholas"
person.name = "Greg";
console.log(person.name); // "Nicholas"

以上例子创建了一个名为name的属性并给它赋予了一个只读的值“Nicholas”,这个属性就不能再修改了,严格模式下,修改属性的值会抛出错误。

let person = {};
Object.defineProperty(person, "name", { //不能从对象上删除configurable: false, value: "Nicholas"
});
console.log(person.name); // "Nicholas"
delete person.name;
console.log(person.name); // "Nicholas"

2、访问器属性

不包含数据值。他们包含一个获取(getter)函数和一个设置(setter)函数,在读取访问器属性时,会调用获取函数,返回一个有效的值,在写入访问器属性时,会调用设置函数并传入新值,有以下四个特性:

 [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性。默认情况下,所有直接定义在对象上的属性的这个特性都是 true。

 [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是 true。

 [[Get]]:获取函数,在读取属性时调用。默认值为 undefined。

 [[Set]]:设置函数,在写入属性时调用。默认值为 undefined。

访问器属性是不能直接定义的,必须使用 Object.defineProperty()。

// 定义一个对象,包含伪私有成员 year_和公共成员 edition
let book = { year_: 2017, edition: 1};
Object.defineProperty(book, "year", { get() { return this.year_; }, set(newValue) { if (newValue > 2017) { this.year_ = newValue; this.edition += newValue - 2017; } }
});
book.year = 2018;
console.log(book.edition); // 2

8.1.2 定义多个属性

Object.defineProperties()

 let book = {};Object.defineProperties(book, {year_: {value: 2017,},edition: {value: 1,},year: {get() {return this.year_;},set(newValue) {if (newValue > 2017) {this.year_ = newValue;this.edition += newValue - 2017;}},},});

8.1.3 读取属性的特性

使用 Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。

  let book = {};Object.defineProperties(book, {year_: {value: 2017},edition: {value: 1},year: {get: function () {return this.year_;},set: function (newValue) {if (newValue > 2017) {this.year_ = newValue;this.edition += newValue - 2017;}}}});let descriptor = Object.getOwnPropertyDescriptor(book, "year_"); console.log(descriptor.value); // 2017 console.log(descriptor.configurable); // false console.log(typeof descriptor.get); // "undefined" let yearDescriptor = Object.getOwnPropertyDescriptor(book, "year"); console.log(yearDescriptor.value); // undefined console.log(yearDescriptor.enumerable); // false console.log(typeof yearDescriptor.get); // "function"

ECMAScript 2017 新增了 Object.getOwnPropertyDescriptors()静态方法,实际上是在每个自有属性上调用Object.getOwnPropertyDescriptor()并在一个新对象中返回它们。

 let book = {};Object.defineProperties(book, {year_: {value: 2017,},edition: {value: 1,},year: {get: function () {return this.year_;},set: function (newValue) {if (newValue > 2017) {this.year_ = newValue;this.edition += newValue - 2017;}},},});console.log(Object.getOwnPropertyDescriptors(book));

8.1.4 合并对象

Object.assign()方法

let dest, src, result;/** * 简单复制*/dest = {};src = {id: 'src'};result = Object.assign(dest, src);// Object.assign 修改目标对象// 也会返回修改后的目标对象console.log(dest === result); // true console.log(dest !== src); // true console.log(result); // { id: src } console.log(dest); // { id: src } /** * 多个源对象*/dest = {};result = Object.assign(dest, {a: 'foo'}, {b: 'bar'});console.log(result); // { a: foo, b: bar } /** * 获取函数与设置函数*/dest = {set a(val) {console.log(`Invoked dest setter with param ${val}`);}};src = {get a() {console.log('Invoked src getter');return 'foo';}};Object.assign(dest, src);// 调用 src 的获取方法// 调用 dest 的设置方法并传入参数"foo" // 因为这里的设置函数不执行赋值操作// 所以实际上并没有把值转移过来console.log("=============>", dest); // { set a(val) {...} }

Object.assign()实际上对每个源对象执行的是浅复制。如果多个源对象都有相同的属性,则使用最后一个复制的值。此外,从源对象访问器属性取得的值,比如获取函数,会作为一个静态值赋给目标对象。换句话说,不能在两个对象间转移获取函数和设置函数。

let dest, src, result;
/** * 覆盖属性*/
dest = { id: 'dest' };
result = Object.assign(dest, { id: 'src1', a: 'foo' }, { id: 'src2', b: 'bar' });
// Object.assign 会覆盖重复的属性
console.log(result); // { id: src2, a: foo, b: bar }
// 可以通过目标对象上的设置函数观察到覆盖的过程:
dest = { set id(x) { console.log(x); }
};
Object.assign(dest, { id: 'first' }, { id: 'second' }, { id: 'third' });
// first
// second
// third
/** * 对象引用*/
dest = {};
src = { a: {} };
Object.assign(dest, src);
// 浅复制意味着只会复制对象的引用
console.log(dest); // { a :{} }
console.log(dest.a === src.a); // true

8.1.5 对象标识及相等判定

在es6之前有些特殊情况是===操作符无法正确判断的

如:

// 这些情况在不同 JavaScript 引擎中表现不同,但仍被认为相等
console.log(+0 === -0); // true
console.log(+0 === 0); // true
console.log(-0 === 0); // true
// 要确定 NaN 的相等性,必须使用极为讨厌的 isNaN()
console.log(NaN === NaN); // false
console.log(isNaN(NaN)); // true

为此es6新增了Object.is()

// 正确的 0、-0、+0 相等/不等判定
console.log(Object.is(+0, -0)); // false
console.log(Object.is(+0, 0)); // true
console.log(Object.is(-0, 0)); // false
// 正确的 NaN 相等判定
console.log(Object.is(NaN, NaN)); // true

8.1.6 增强的对象语法

1、属性值简写

let name = 'Matt';
let person = { name: name
};
console.log(person); // { name: 'Matt' }
// 等同于:
let name = 'Matt';
let person = { name
};
console.log(person); // { name: 'Matt' }

2、可计算属性

可以在对象字面量中完成动态属性复制

const nameKey = 'name';
const ageKey = 'age';
const jobKey = 'job';
let person = { [nameKey]: 'Matt', [ageKey]: 27, [jobKey]: 'Software engineer'
};
console.log(person); // { name: 'Matt', age: 27, job: 'Software engineer' }

3、简写方法名

let person = { sayName: function(name) { console.log(`My name is ${name}`); }
};
person.sayName('Matt'); // My name is Matt
//等同于
let person = { sayName(name) { console.log(`My name is ${name}`); }
};
person.sayName('Matt'); // My name is Matt//简写方法名对获取函数和设置函数也是适用的:name_: '', get name() { return this.name_; }, set name(name) { this.name_ = name; }, sayName() { console.log(`My name is ${this.name_}`); }
};
person.name = 'Matt';
person.sayName(); // My name is Matt//简写方法名与可计算属性键相互兼容:
const methodKey = 'sayName';
let person = { [methodKey](name) { console.log(`My name is ${name}`); }
} 

8.1.7 对象解构

// 使用对象解构
let person = { name: 'Matt', age: 27
};
//另命名
let { name: personName, age: personAge } = person;
console.log(personName); // Matt
console.log(personAge); // 27
//如果给事先声明的变量赋值,必须包含在一对括号中
let personName, personAge;
let person = { name: 'Matt', age: 27
};
({name: personName, age: personAge} = person);
console.log(personName, personAge); // Matt, 27//直接解构
let { name, age } = person;
console.log(name); // Matt
console.log(age); // 27
//如果引用的属性 不存在,报undefined
console.log(job); // undefined
//也可以在解构赋值的同时定义默认值
let { name, job='Software engineer' } = person;
console.log(job); // Software engineer

解构在内部使用函数 ToObject()(不能在运行时环境中直接访问)把源数据结构转换为对象。这意味着在对象解构的上下文中,原始值会被当成对象。这也意味着(根据 ToObject()的定义),null和 undefined 不能被解构,否则会抛出错误。

let { length } = 'foobar';
console.log(length); // 6
let { constructor: c } = 4;
console.log(c === Number); // true
let { _ } = null; // TypeError
let { _ } = undefined; // TypeError

1、嵌套解构

 let person = {name: 'Matt',age: 27,job: {title: 'Software engineer'}};let personCopy = {};({name: personCopy.name,age: personCopy.age,job: personCopy.job} = person);// 因为一个对象的引用被赋值给 personCopy,所以修改 person.job 对象的属性也会影响 personCopy person.job.title = 'Hacker'console.log(person);// { name: 'Matt', age: 27, job: { title: 'Hacker' } } console.log(personCopy);// { name: 'Matt', age: 27, job: { title: 'Hacker' } }
//嵌套结构
let person = { name: 'Matt', age: 27, job: { title: 'Software engineer' }
};
// 声明 title 变量并将 person.job.title 的值赋给它
let { job: { title } } = person;
console.log(title); // Software engineer

在外层属性没有定义的情况下不能使用嵌套解构

let person = { job: { title: 'Software engineer' }
};
let personCopy = {};
// foo 在源对象上是 undefined
({ foo: { bar: personCopy.bar }
} = person);
// TypeError: Cannot destructure property 'bar' of 'undefined' or 'null'.
// job 在目标对象上是 undefined
({ job: { title: personCopy.job.title }
} = person);
// TypeError: Cannot set property 'title' of undefined

2、部分解构

涉及多个属性的解构赋值是一个输出无关的顺序化操作。如果一个解构表达式涉及多个赋值,开始的赋值成功而后面的赋值出错,则整个解构赋值只会完成一部分:

let person = { name: 'Matt', age: 27
};
let personName, personBar, personAge;
try { // person.foo 是 undefined,因此会抛出错误({name: personName, foo: { bar: personBar }, age: personAge} = person);
} catch(e) {}
console.log(personName, personBar, personAge);
// Matt, undefined, undefined
  1. 参数上下文匹配

对参数的解构赋值不会影响 arguments 对象

8.2 创建对象

8.2.1、工厂模式

function createPerson(name, age, job) { let o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function() { console.log(this.name); }; return o;
}
let person1 = createPerson("Nicholas", 29, "Software Engineer");
let person2 = createPerson("Greg", 27, "Doctor");

8.2.2、 构造函数模式

function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = function() { console.log(this.name); };
}
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Gregconsole.log(person1.constructor == Person); // true
console.log(person2.constructor == Person); // true

和工厂模式的区别

 没有显式地创建对象。

 属性和方法直接赋值给了 this。

 没有 return。

创建实例,使用了new操作符:

(1) 在内存中创建一个新对象。

(2) 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。

(3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。

(4) 执行构造函数内部的代码(给新对象添加属性)。

(5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。

1、构造函数也是函数

构造函数与普通函数唯一的区别就是调用方式不同。。任何函数只要使用 new 操作符调用就是构造函数,而不使用 new 操作符调用的函数就是普通函数。

 function Person(name, age, job) {this.name = name;this.age = age;this.job = job;this.sayName = function () {console.log(this.name);};}// 作为构造函数let person = new Person("Nicholas", 29, "Software Engineer");person.sayName(); // "Nicholas"// 作为函数调用Person("Greg", 27, "Doctor"); // 添加到 window 对象window.sayName(); // "Greg"// 在另一个对象的作用域中调用let o = new Object();Person.call(o, "Kristen", 25, "Nurse");o.sayName(); // "Kristen"

2、构造函数的问题

每次定义函数时,都会初始化一个对象

此对前面的例子而言,person1 和 person2 都有名为 sayName()的方法,但这两个方法不是同一个 Function 实例。

要解决这个问题,可以把函数定义转移到构造函数外部

function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName;
}
function sayName() { console.log(this.name);
}
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg

这样虽然解决了相同逻辑的函数重复定义的问题,但全局作用域也因此被搞乱了,因为那个函数实际上只能在一个对象上调用。如果这个对象需要多个方法,那么就要在全局作用域中定义多个函数。这会导致自定义类型引用的代码不能很好地聚集一起。这个新问题可以通过原型模式来解决。

8.2.4 原型模式

每个函数都会创建一个 prototype 属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型

好处:在原型对象上定义的属性和方法可以被对象实例共享

​ 原来在构造函数中直接赋给对象实例的值,可以直接赋值给他们的原型

    function Person() {}// let Person = function() {};  //函数表达式也可以Person.prototype.name = "js";Person.prototype.sayName = function () {console.log(this.name);};let person1 = new Person();person1.sayName(); // "js"let person2 = new Person();person2.sayName(); // "js"console.log(person1.sayName == person2.sayName); // true

1、理解原型

只要创建一个函数,就会给函数创建一个prototype属性(指向原型对象),所有的原型对象自动获得一个名为constructor 的属性,指回与之关联的构造函数。

Person.prototype.constructor == Person //true

用代码理解实例与构造函数的关系:

   function Person() {}// 声明之后,构造函数就有了一个与之关联的原型对象:console.log(typeof Person.prototype);console.log(Person.prototype);// {// constructor: f Person(),// [[Prototype]]: Object// }// 如前所述,构造函数有一个 prototype 属性 引用其原型对象,而这个原型对象也有一个 constructor 属性,引用这个构造函数换句话说,两者循环引用:console.log(Person.prototype.constructor === Person); // true// 正常的原型链都会终止于 Object 的原型对象 Object 原型的原型是 nullconsole.log(Person.prototype.__proto__ === Object.prototype); // trueconsole.log(Person.prototype.__proto__.constructor === Object); // trueconsole.log(Person.prototype.__proto__.__proto__ === null); // trueconsole.log(Person.prototype.__proto__);// {// constructor: f Object(),// toString: ...// hasOwnProperty: ...// isPrototypeOf: ...// ...// }let person1 = new Person(),person2 = new Person();//构造函数、原型对象和实例 是 3 个完全不同的对象:console.log(person1 !== Person); // trueconsole.log(person1 !== Person.prototype); // trueconsole.log(Person.prototype !== Person); // true/*** 实例通过__proto__链接到原型对象,* 它实际上指向隐藏特性[[Prototype]]* 构造函数通过 prototype 属性链接到原型对象* 实例与构造函数没有直接联系,与原型对象有直接联系*/console.log(person1.__proto__ === Person.prototype); // trueconsole.log(person1.__proto__.constructor === Person); // true// 同一个构造函数创建的两个实例 共享同一个原型对象:console.log(person1.__proto__ === person2.__proto__); // true//instanceof 检查实例的原型链中 是否包含指定构造函数的原型:console.log(person1 instanceof Person); // trueconsole.log(person1 instanceof Object); // trueconsole.log(Person.prototype instanceof Object); // true

下图展示了各个对象之间的关系:

Person.prototype 指向原型对象,而 Person.prototype.contructor 指回 Person 构造函数。原型对象包含 constructor 属性和其他后来添加的属性。Person 的两个实例 person1 和 person2 都只有一个内部属性指回 Person.prototype,而且两者都与构造函数没有直接联系。另

判断两个对象之间的关系:

//两个例子内部都有链接指向Person.prototype
console.log(Person.prototype.isPrototypeOf(person1)); // true
console.log(Person.prototype.isPrototypeOf(person2)); // true

ECMAScript 的 Object 类型有一个方法叫 Object.getPrototypeOf(),返回参数的内部特性

[[Prototype]]的值。

console.log(Object.getPrototypeOf(person1) == Person.prototype); // true
console.log(Object.getPrototypeOf(person1).name); // "Nicholas"

Object 类型还有一个 setPrototypeOf()方法,可以向实例的私有特性[[Prototype]]写入一

个新值。这样就可以重写一个对象的原型继承关系:

 let biped = {numLegs: 2,
};
let person = {name: "Matt",
};
Object.setPrototypeOf(person, biped);
console.log(person.name); // Matt
console.log(person.numLegs); // 2
console.log(Object.getPrototypeOf(person) === biped); // true

Object.setPrototypeOf会严重影响代码性能,可以通过Object.create()来创建一个新对象,并且为其指定原型:

    let biped = {numLegs: 2,};let person = Object.create(biped);person.name = "Matt";console.log(person.name); // Mattconsole.log(person.numLegs); // 2console.log(Object.getPrototypeOf(person) === biped); // true

2、 原型层级

在通过对象访问属性时,会按照这个属性的名称开始搜索,现在对象实例本身搜索,如果找到了就返回,找不到就在原型对象上搜索。

索然可以通过实例读取原型对象上的值,但不可能通过实例重写这些值,如果在实例上添加了一个与原型对象中同名的属性,那么会在实例上创建这个属性,并且会盖住原型对象上的属性,不会修改它,但是会屏蔽对他的访问。

使用delete可以完全删除实例上的这个属性,从而让标识符解析过程能够继续搜索原型对象

function Person() {}Person.prototype.name = "Nicholas";Person.prototype.age = 29;Person.prototype.job = "Software Engineer";Person.prototype.sayName = function () {console.log(this.name);};let person1 = new Person();let person2 = new Person();person1.name = "Greg";console.log(person1.name); // "Greg",来自实例console.log(person2.name); // "Nicholas",来自原型

hasOwnProperty()方法用于确定某个属性是在实例上还是在原型对象上。

 function Person() {}Person.prototype.name = "Nicholas";Person.prototype.age = 29;Person.prototype.job = "Software Engineer";Person.prototype.sayName = function () {console.log(this.name);};let person1 = new Person();let person2 = new Person();console.log(person1.hasOwnProperty("name")); // falseperson1.name = "Greg";console.log(person1.name); // "Greg",来自实例console.log(person1.hasOwnProperty("name")); // trueconsole.log(person2.name); // "Nicholas",来自原型console.log(person2.hasOwnProperty("name")); // falsedelete person1.name;console.log(person1.name); // "Nicholas",来自原型console.log(person1.hasOwnProperty("name")); // false

3、原型和in操作符

单独使用时,in操作符会在可以通过对象访问指定属性时返回true,无论在实例上还是原型上

 function Person() {}Person.prototype.name = "Nicholas";let person1 = new Person();let person2 = new Person();console.log(person1.name); // "Greg",来自原型console.log(person1.hasOwnProperty("name")); // falseconsole.log("name" in person1); // trueperson1.name = "Greg";console.log(person1.name); // "Greg",来自实例console.log(person1.hasOwnProperty("name")); // trueconsole.log("name" in person1); // truedelete person1.name;console.log(person1.name); // "Nicholas",来自原型console.log(person1.hasOwnProperty("name")); // falseconsole.log("name" in person1); // true

如果要确定某个属性是否存在于原型上,则可以同时使用hasOwnProperty()和in

function hasPrototypeProperty(object, name){ return !object.hasOwnProperty(name) && (name in object);
}

获得对象所有可枚举的实例属性:Object.keys()

 function Person() {}Person.prototype.name = "Nicholas";Person.prototype.age = 29;Person.prototype.job = "Software Engineer";Person.prototype.sayName = function () {};console.log(Object.keys(Person.prototype)); // "name,age,job,sayName"let p1 = new Person();p1.name = "Rob";p1.age = 31;console.log(Object.keys(p1)); // "[name,age]"console.log(Object.keys(p1.__proto__)); // " ['name', 'age', 'job', 'sayName']"

如果想列出所有实例属性,无论是否可以枚举,都可以使用 Object.getOwnPropertyNames():

let keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys); // "[constructor,name,age,job,sayName]"

4、属性枚举顺序

1、不确定:for-in 循环和 Object.keys()

2、确定:Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()和 Object.assign()

8.2.5 对象迭代

Object.values()返回对象值的数组;

Object.entries()返回键/值对的数组

注意:非字符串属性会被转换成字符串输出,这2个方法执行对象的浅复制

const sym = Symbol();
const o = { foo: 'bar', baz: 1, qux: {},[sym]: 'foo' //符号属性会被忽略
};
console.log(Object.values(o)); // ["bar", 1, {}]
console.log(Object.entries((o))); // [["foo", "bar"], ["baz", 1], ["qux", {}]]

1、其他原型语法

字面量写法

function Person() {}
Person.prototype = {name: "Nicholas", age: 29, job: "Software Engineer", sayName() { console.log(this.name); }
};

这样重写之后,Person.prototype 的 constructor 属性就不指向 Person了。在创建函数时,也会创建它的 prototype 对象,同时会自动给这个原型的 constructor 属性赋值。而上面的写法完全重写了默认的 prototype 对象,因此其 constructor 属性也指向了完全不同的新对象(Object 构造函数),不再指向原来的构造函数。虽然 instanceof 操作符还能可靠地返回值,但我们不能再依靠 constructor 属性来识别类型了。

let friend = new Person();
console.log(friend instanceof Object); // true
console.log(friend instanceof Person); // true
console.log(friend.constructor == Person); // false
console.log(friend.constructor == Object); // true

如果 constructor 的值很重要,则可以像下面这样在重写原型对象时专门设置一下它的值:

function Person() {
}
Person.prototype = { constructor: Person, name: "Nicholas", age: 29, job: "Software Engineer", sayName() { console.log(this.name); }
};

但这种方式会创建一个个[[Enumerable]]为 true 的属性。而原生 constructor 属性默认是不可枚举的。因此,如果你使用的是兼容 ECMAScript 的 JavaScript 引擎,那可能会改为使用 Object.defineProperty()方法来定义 constructor 属性:

function Person() {}
Person.prototype = { name: "Nicholas", age: 29, job: "Software Engineer", sayName() { console.log(this.name); }
};
// 恢复 constructor 属性
Object.defineProperty(Person.prototype, "constructor", { enumerable: false, value: Person
});

8.3 继承

8.3.1 原型链

function SuperType() {this.property = true;}SuperType.prototype.getSuperValue = function () {return this.property;};function SubType() {this.subproperty = false;}// 继承 SuperTypeSubType.prototype = new SuperType();let instance = new SubType();console.log(instance.getSuperValue()); // true

2、原型与继承关系

console.log(instance instanceof Object); // true
console.log(instance instanceof SuperType); // true
console.log(instance instanceof SubType); // trueconsole.log(Object.prototype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // true
console.log(SubType.prototype.isPrototypeOf(instance)); // true

3、关于方法

子类会覆盖父类的方法:

 this.property = true;
}
SuperType.prototype.getSuperValue = function() { return this.property;
};
function SubType() { this.subproperty = false;
}
// 继承 SuperType
SubType.prototype = new SuperType();
// 新方法
SubType.prototype.getSubValue = function () { return this.subproperty;
};
// 覆盖已有的方法
SubType.prototype.getSuperValue = function () { return false;
};
let instance = new SubType();
console.log(instance.getSuperValue()); // false

以对象字面量方式创建原型方法会破坏之前的原型链,因为这相当于重写了原型链。

 this.property = true;
}
SuperType.prototype.getSuperValue = function() { return this.property;
};
function SubType() { this.subproperty = false;
}
// 继承 SuperType
SubType.prototype = new SuperType();
// 通过对象字面量添加新方法,这会导致上一行无效
SubType.prototype = { getSubValue() { return this.subproperty; }, someOtherMethod() { return false; }
};
let instance = new SubType();
console.log(instance.getSuperValue()); // 出错!

4、原型链的问题

1、原型中包含的引用值会在所有实例间共享

2、子类型在实例化时不能给父类型的构造函数传参

8.3.2 盗用构造函数

为了解决原型包含引用值导致的继承问题而出现。

思路:在子类构造函数中调用父类构造函数,通过apply()和 call()方法

function SuperType() { this.colors = ["red", "blue", "green"];
}
function SubType() { // 继承 SuperType SuperType.call(this);
}
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
let instance2 = new SubType();
console.log(instance2.colors); // "red,blue,green"

1、传递参数

function SuperType(name){ this.name = name;
}
function SubType() { // 继承 SuperType 并传参SuperType.call(this, "Nicholas"); // 实例属性this.age = 29;
}
let instance = new SubType();
console.log(instance.name); // "Nicholas";
console.log(instance.age); // 29

2、盗用构造函数的问题

使用构造函数模式自定义类型的问题:必须在构造函数中定义方法,因此函数不能重用。

8.3.3 组合继承

综合了原型链和盗用构造函数,将两者的优点集中了起来。基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性

function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() { console.log(this.name);
};
function SubType(name, age){ // 继承属性SuperType.call(this, name); this.age = age;
}
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function() { console.log(this.age);
};
let instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
instance1.sayName(); // "Nicholas";
instance1.sayAge(); // 29
let instance2 = new SubType("Greg", 27);
console.log(instance2.colors); // "red,blue,green"
instance2.sayName(); // "Greg";
instance2.sayAge(); // 27

8.3.4 原型式继承

创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时类型的一个实例。

function object(o) { function F() {} F.prototype = o; return new F();
}

8.3.5 寄生式继承

创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。

function createAnother(original){ let clone = object(original); // 通过调用函数创建一个新对象clone.sayHi = function() { // 以某种方式增强这个对象console.log("hi"); }; return clone; // 返回这个对象
}

8.3.6 寄生式组合继承

 this.name = name; this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() { console.log(this.name);
};
function SubType(name, age){ SuperType.call(this, name); // 第二次调用 SuperType() this.age = age;
}
SubType.prototype = new SuperType(); // 第一次调用 SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() { console.log(this.age);
};

8.4 类

8.4.1 类定义

// 类声明
class Person {}
// 类表达式
const Animal = class {};

1、与函数定义不同的是,虽然函数声明可以提升,但类定义不能

2、函数受函数作用域限制,而类受块作用域限制

类可以包含构造函数方法、实例方法、获取函数、设置函数和静态类方法.

// 空类定义,有效
class Foo {}
// 有构造函数的类,有效
class Bar { constructor() {}
}
// 有获取函数的类,有效
class Baz { get myBaz() {}
}
// 有静态方法的类,有效
class Qux { static myQux() {}
}

8.4.2 类构造函数

方法名 constructor 会告诉解释器在使用 new 操作符创建类的新实例时,应该调用这个函数

1、实例化

(1)在内存中创建一个空对象

(2)这个新对象内部的[[Prototype]]指针被赋值为构造函数的prototype属性

(3)构造函数内部的this被赋值为这个新对象

(4)执行构造函数内部的代码(给新对象添加属性)

(5)如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。

类实例化时传入的参数会用作构造函数的参数。如果不需要参数,则类名后面的括号也是可选的:

class Person { constructor(name) { console.log(arguments.length); this.name = name || null; }
}
let p1 = new Person; // 0
console.log(p1.name); // null
let p2 = new Person(); // 0
console.log(p2.name); // null
let p3 = new Person('Jake'); // 1
console.log(p3.name); // Jake

8.4.4 继承

1、继承基础:extends

class Vehicle {}
// 继承类
class Bus extends Vehicle {}
let b = new Bus();
console.log(b instanceof Bus); // true
console.log(b instanceof Vehicle); // true
function Person() {}
// 继承普通构造函数
class Engineer extends Person {}
let e = new Engineer();
console.log(e instanceof Engineer); // true
console.log(e instanceof Person); // true

2、构造函数、HomeObject 和 super()

派生类的方法可以通过 super 关键字引用它们的原型。这个关键字只能在派生类中使用,而且仅限于类构造函数、实例方法和静态方法内部。在类构造函数中使用 super 可以调用父类构造函数。

class Vehicle { constructor() { this.hasEngine = true; }
}
class Bus extends Vehicle { constructor() { // 不要在调用 super()之前引用 this,否则会抛出 ReferenceError super(); // 相当于 super.constructor() console.log(this instanceof Vehicle); // true console.log(this); // Bus { hasEngine: true } }
}
new Bus();
在静态方法中可以通过 super 调用继承的类上定义的静态方法:
class Vehicle { static identify() { console.log('vehicle'); }
}
class Bus extends Vehicle { static identify() { super.identify(); }
}
Bus.identify(); // vehicle

在使用 super 时要注意几个问题。

 super 只能在派生类构造函数和静态方法中使用。

class Vehicle { constructor() { super(); // SyntaxError: 'super' keyword unexpected }
}

 不能单独引用 super 关键字,要么用它调用构造函数,要么用它引用静态方法。

class Vehicle {}
class Bus extends Vehicle { constructor() { console.log(super); // SyntaxError: 'super' keyword unexpected here }
}

 调用 super()会调用父类构造函数,并将返回的实例赋值给 this。

class Vehicle {}
class Bus extends Vehicle { constructor() { super(); console.log(this instanceof Vehicle); }
}
new Bus(); // true

 super()的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入。

class Vehicle { constructor(licensePlate) { this.licensePlate = licensePlate; }
}
class Bus extends Vehicle { constructor(licensePlate) { super(licensePlate); }
}
console.log(new Bus('1337H4X')); // Bus { licensePlate: '1337H4X' }

 如果没有定义类构造函数,在实例化派生类时会调用 super(),而且会传入所有传给派生类的

参数。

class Vehicle { constructor(licensePlate) { this.licensePlate = licensePlate; }
}
class Bus extends Vehicle {}
console.log(new Bus('1337H4X')); // Bus { licensePlate: '1337H4X' }

 在类构造函数中,不能在调用 super()之前引用 this。

class Vehicle {}
class Bus extends Vehicle { constructor() { console.log(this); }
}
new Bus();
// ReferenceError: Must call super constructor in derived class
// before accessing 'this' or returning from derived constructor

 如果在派生类中显式定义了构造函数,则要么必须在其中调用 super(),要么必须在其中返回

一个对象。

class Vehicle {}
class Car extends Vehicle {}
class Bus extends Vehicle { constructor() { super(); }
}
class Van extends Vehicle { constructor() { return {}; }
}
console.log(new Car()); // Car {}
console.log(new Bus()); // Bus {}
console.log(new Van()); // {}

第九章 代理与反射

9.1代理基础

9.1.1创建空代理

代理是使用 Proxy 构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象。缺

少其中任何一个参数都会抛出 TypeError。要创建空代理,可以传一个简单的对象字面量作为处理程序对象。如下面的代码,在代理对象上执行的任何操作都会应用到目标对象

const target = { id: 'target'
};
const handler = {};
const proxy = new Proxy(target, handler);
// id 属性会访问同一个值
console.log(target.id); // target
console.log(proxy.id); // target
// 给目标属性赋值会反映在两个对象上
// 因为两个对象访问的是同一个值
target.id = 'foo';
console.log(target.id); // foo
console.log(proxy.id); // foo
// 给代理属性赋值会反映在两个对象上
// 因为这个赋值会转移到目标对象
proxy.id = 'bar';
console.log(target.id); // bar
console.log(proxy.id); // bar
// hasOwnProperty()方法在两个地方
// 都会应用到目标对象
console.log(target.hasOwnProperty('id')); // true
console.log(proxy.hasOwnProperty('id')); // true
// Proxy.prototype 是 undefined
// 因此不能使用 instanceof 操作符
console.log(target instanceof Proxy); // TypeError: Function has non-object prototype
'undefined' in instanceof check
console.log(proxy instanceof Proxy); // TypeError: Function has non-object prototype
'undefined' in instanceof check
// 严格相等可以用来区分代理和目标
console.log(target === proxy); // false

9.1.2 定义捕获器

使用代理的主要目的是可以定义捕获器,就是在处理程序对象中定义的“基本操作的拦截器”

const target = { foo: 'bar'
};
const handler = { // 捕获器在处理程序对象中以方法名为键get() { return 'handler override'; }
};
const proxy = new Proxy(target, handler);
console.log(target.foo); // bar
console.log(proxy.foo); // handler override
console.log(target['foo']); // bar
console.log(proxy['foo']); // handler override
console.log(Object.create(target)['foo']); // bar
console.log(Object.create(proxy)['foo']); // handler override

当代理对象执行get()操作时,会触发定义的get()捕获器。proxy[property]、proxy.property 或 Object.create(proxy)[property]等操作都会触发基本的 get()操作以获取属性。因此所有这些操作只要发生在代理对象上,就会触发 get()捕获器,只有在代理对象上执行这些操作才会触发捕获器。

9.1.3 捕获器参数和反射 API

1、手写代码

const target = {foo: 'bar',};const handler = {get(trapTarget, property, receiver) {console.log(trapTarget === target);console.log(property);console.log(receiver === proxy);}};const proxy = new Proxy(target, handler);proxy.foo;// true // foo // true // 有了这些参数, 就可以重建被捕获方法的原始行为:const target = {foo: 'bar'};const handler = {get(trapTarget, property, receiver) {return trapTarget[property];}};const proxy = new Proxy(target, handler);console.log(proxy.foo); // bar console.log(target.foo); // bar

2、通过调用全局 Reflect 对象重建

const target = { foo: 'bar'
};
const handler = { get() { return Reflect.get(...arguments); }
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar
console.log(target.foo); // bar

3、更简洁写法

const target = { foo: 'bar'
};
const handler = { get: Reflect.get
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar
console.log(target.foo); // bar

4、进阶

const target = { foo: 'bar'
};
const proxy = new Proxy(target, Reflect);
console.log(proxy.foo); // bar
console.log(target.foo); // bar

5、反射api,可以回返回值进行修饰

const target = { foo: 'bar', baz: 'qux'
};
const handler = { get(trapTarget, property, receiver) { let decoration = ''; if (property === 'foo') { decoration = '!!!'; } return Reflect.get(...arguments) + decoration; }
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar!!!
console.log(target.foo); // bar
console.log(proxy.baz); // qux
console.log(target.baz); // qux

9.1.4 捕获器不变式

如果目标对象有一个不可配置且不可写的数据属性,那么在捕获器返回一个与该属性不同的值时,会抛出 TypeError:

const target = {};
Object.defineProperty(target, 'foo', { configurable: false, writable: false, value: 'bar'
});
const handler = { get() { return 'qux'; }
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo);
// TypeError

9.1.5 可撤销代理

revocable()支持撤销代理对象与目标对象的关联

撤销函数(revoke())是幂等的,调用多少次的结果都一样。撤销代理之后再调用代理会抛出 TypeError

 foo: 'bar'
};
const handler = { get() { return 'intercepted'; }
};
const { proxy, revoke } = Proxy.revocable(target, handler);
console.log(proxy.foo); // intercepted
console.log(target.foo); // bar
revoke();
console.log(proxy.foo); // TypeError

9.1.6 实用反射 API

某些情况优先使用反射Api

  1. 反射 API 与对象 API
  2. 状态标记
  3. 用一等函数替代操作符
  4. 安全地应用函数

9.1.7 代理另一个代理

一个代理去代理另一个代理,可以在一个目标对象上构建多层拦截网

const target = { foo: 'bar'
};
const firstProxy = new Proxy(target, { get() { console.log('first proxy'); return Reflect.get(...arguments); }
});
const secondProxy = new Proxy(firstProxy, { get() { console.log('second proxy'); return Reflect.get(...arguments); }
});
console.log(secondProxy.foo);
// second proxy
// first proxy
// bar

9.1.8 代理的问题与不足

  1. 代理中的 this
  2. 代理与内部槽位

9.2 代理捕获器与反射方法

代理可以捕获13种不同的基本操作

9.2.1 get()

get()捕获器会在获取属性值的操作中被调用。对应的反射 API 方法为 Reflect.get()

const myTarget = {};
const proxy = new Proxy(myTarget, { get(target, property, receiver) { console.log('get()'); return Reflect.get(...arguments) }
});
proxy.foo;
// get()
  1. 返回值

返回值无限制。

  1. 拦截的操作

 proxy.property

 proxy[property]

 Object.create(proxy)[property]

 Reflect.get(proxy, property, receiver)

  1. 捕获器处理程序参数

 target:目标对象。

 property:引用的目标对象上的字符串键属性。①

 receiver:代理对象或继承代理对象的对象。

  1. 捕获器不变式

如果 target.property 不可写且不可配置,则处理程序返回的值必须与 target.property 匹配。

如果 target.property 不可配置且[[Get]]特性为 undefined,处理程序的返回值也必须是 undefined。

9.2.2 set()

set()捕获器会在设置属性值的操作中被调用。对应的反射 API 方法为 Reflect.set()。

const myTarget = {};
const proxy = new Proxy(myTarget, { set(target, property, value, receiver) { console.log('set()'); return Reflect.set(...arguments) }
});
proxy.foo = 'bar';
// set()
  1. 返回值

返回 true 表示成功;返回 false 表示失败,严格模式下会抛出 TypeError。

  1. 拦截的操作

 proxy.property = value

 proxy[property] = value

 Object.create(proxy)[property] = value

 Reflect.set(proxy, property, value, receiver)

  1. 捕获器处理程序参数

 target:目标对象。

 property:引用的目标对象上的字符串键属性。

 value:要赋给属性的值。

 receiver:接收最初赋值的对象。

  1. 捕获器不变式

如果 target.property 不可写且不可配置,则不能修改目标属性的值。

如果 target.property 不可配置且[[Set]]特性为 undefined,则不能修改目标属性的值。

在严格模式下,处理程序中返回 false 会抛出 TypeError。

9.2.3 has()

has()捕获器会在 in 操作符中被调用。对应的反射 API 方法为 Reflect.has()。

const myTarget = {};
const proxy = new Proxy(myTarget, { has(target, property) { console.log('has()'); return Reflect.has(...arguments) }
});
'foo' in proxy;
// has()
  1. 返回值

has()必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值。

  1. 拦截的操作

 property in proxy

 property in Object.create(proxy)

 with(proxy) {(property);}

 Reflect.has(proxy, property)

  1. 捕获器处理程序参数

 target:目标对象。

 property:引用的目标对象上的字符串键属性。

  1. 捕获器不变式

如果 target.property 存在且不可配置,则处理程序必须返回 true。

如果 target.property 存在且目标对象不可扩展,则处理程序必须返回 true。

9.2.4 defineProperty()

defineProperty()捕获器会在 Object.defineProperty()中被调用。对应的反射 API 方法Reflect.defineProperty()。

const myTarget = {};
const proxy = new Proxy(myTarget, { defineProperty(target, property, descriptor) { console.log('defineProperty()'); return Reflect.defineProperty(...arguments) }
});
Object.defineProperty(proxy, 'foo', { value: 'bar' });
// defineProperty()
  1. 返回值

defineProperty()必须返回布尔值,表示属性是否成功定义。返回非布尔值会被转型为布尔值。

  1. 拦截的操作

 Object.defineProperty(proxy, property, descriptor)

 Reflect.defineProperty(proxy, property, descriptor)

  1. 捕获器处理程序参数

 target:目标对象。

 property:引用的目标对象上的字符串键属性。

 descriptor:包含可选的 enumerable、configurable、writable、value、get 和 set

定义的对象。

  1. 捕获器不变式

如果目标对象不可扩展,则无法定义属性。

如果目标对象有一个可配置的属性,则不能添加同名的不可配置属性。

如果目标对象有一个不可配置的属性,则不能添加同名的可配置属性。

9.2.5 getOwnPropertyDescriptor()

getOwnPropertyDescriptor()捕获器会在 Object.getOwnPropertyDescriptor()中被调用。对应的反射 API 方法为 Reflect.getOwnPropertyDescriptor()。

const myTarget = {};
const proxy = new Proxy(myTarget, { getOwnPropertyDescriptor(target, property) { console.log('getOwnPropertyDescriptor()'); return Reflect.getOwnPropertyDescriptor(...arguments) }
});
Object.getOwnPropertyDescriptor(proxy, 'foo');
// getOwnPropertyDescriptor()
  1. 返回值

getOwnPropertyDescriptor()必须返回对象,或者在属性不存在时返回 undefined。

  1. 拦截的操作

 Object.getOwnPropertyDescriptor(proxy, property)

 Reflect.getOwnPropertyDescriptor(proxy, property)

  1. 捕获器处理程序参数

 target:目标对象。

 property:引用的目标对象上的字符串键属性。

  1. 捕获器不变式

如果自有的 target.property 存在且不可配置,则处理程序必须返回一个表示该属性存在的

对象。

如果自有的 target.property 存在且可配置,则处理程序必须返回表示该属性可配置的对象。

如果自有的 target.property 存在且 target 不可扩展,则处理程序必须返回一个表示该属性存

在的对象。

如果 target.property 不存在且 target 不可扩展,则处理程序必须返回 undefined 表示该属

性不存在。

如果 target.property 不存在,则处理程序不能返回表示该属性可配置的对象。

9.2.6 deleteProperty()

deleteProperty()捕获器会在 delete 操作符中被调用。对应的反射 API 方法为 Reflect. deleteProperty()。

const myTarget = {}; const proxy = new Proxy(myTarget, { deleteProperty(target, property) { console.log('deleteProperty()'); return Reflect.deleteProperty(...arguments) } }); delete proxy.foo // deleteProperty()
  1. 返回值

deleteProperty()必须返回布尔值,表示删除属性是否成功。返回非布尔值会被转型为布尔值。

  1. 拦截的操作

 delete proxy.property

 delete proxy[property]

 Reflect.deleteProperty(proxy, property)

  1. 捕获器处理程序参数

 target:目标对象。

 property:引用的目标对象上的字符串键属性。

  1. 捕获器不变式

如果自有的 target.property 存在且不可配置,则处理程序不能删除这个属性。

9.2.7 ownKeys()

ownKeys()捕获器会在 Object.keys()及类似方法中被调用。对应的反射 API 方法为 Reflect. ownKeys()。

const myTarget = {};
const proxy = new Proxy(myTarget, { ownKeys(target) { console.log('ownKeys()'); return Reflect.ownKeys(...arguments) }
});
Object.keys(proxy);
// ownKeys()
  1. 返回值

ownKeys()必须返回包含字符串或符号的可枚举对象。

  1. 拦截的操作

 Object.getOwnPropertyNames(proxy)

 Object.getOwnPropertySymbols(proxy)

 Object.keys(proxy)

 Reflect.ownKeys(proxy)

  1. 捕获器处理程序参数

 target:目标对象。

  1. 捕获器不变式

返回的可枚举对象必须包含 target 的所有不可配置的自有属性。

如果 target 不可扩展,则返回可枚举对象必须准确地包含自有属性键。

9.2.8 getPrototypeOf()

getPrototypeOf()捕获器会在 Object.getPrototypeOf()中被调用。对应的反射 API 方法为Reflect.getPrototypeOf()。

const myTarget = {};
const proxy = new Proxy(myTarget, { getPrototypeOf(target) { console.log('getPrototypeOf()'); return Reflect.getPrototypeOf(...arguments) }
}); Object.getPrototypeOf(proxy); // getPrototypeOf()
  1. 返回值

getPrototypeOf()必须返回对象或 null。

  1. 拦截的操作

 Object.getPrototypeOf(proxy)

 Reflect.getPrototypeOf(proxy)

 proxy.proto

 Object.prototype.isPrototypeOf(proxy)

 proxy instanceof Object

  1. 捕获器处理程序参数

 target:目标对象。

  1. 捕获器不变式

如果 target 不可扩展,则 Object.getPrototypeOf(proxy)唯一有效的返回值就是 Object.getPrototypeOf(target)的返回值。

9.2.9 setPrototypeOf()

setPrototypeOf()捕获器会在 Object.setPrototypeOf()中被调用。对应的反射 API 方法为Reflect.setPrototypeOf()。

const myTarget = {}; const proxy = new Proxy(myTarget, { setPrototypeOf(target, prototype) { console.log('setPrototypeOf()'); return Reflect.setPrototypeOf(...arguments) } }); Object.setPrototypeOf(proxy, Object); // setPrototypeOf()
  1. 返回值

setPrototypeOf()必须返回布尔值,表示原型赋值是否成功。返回非布尔值会被转型为布尔值。

  1. 拦截的操作

 Object.setPrototypeOf(proxy)

 Reflect.setPrototypeOf(proxy)

  1. 捕获器处理程序参数

 target:目标对象。

 prototype:target 的替代原型,如果是顶级原型则为 null。

  1. 捕获器不变式

如果 target 不可扩展,则唯一有效的 prototype 参数就是 Object.getPrototypeOf(target)的返回值。

9.2.10 isExtensible()

isExtensible()捕获器会在 Object.isExtensible()中被调用。对应的反射 API 方法为Reflect.isExtensible()。

const myTarget = {}; const proxy = new Proxy(myTarget, { isExtensible(target) { console.log('isExtensible()'); return Reflect.isExtensible(...arguments) } }); Object.isExtensible(proxy); // isExtensible()
  1. 返回值

isExtensible()必须返回布尔值,表示 target 是否可扩展。返回非布尔值会被转型为布尔值。

  1. 拦截的操作

 Object.isExtensible(proxy)

 Reflect.isExtensible(proxy)

  1. 捕获器处理程序参数

 target:目标对象。

  1. 捕获器不变式

如果 target 可扩展,则处理程序必须返回 true。

如果 target 不可扩展,则处理程序必须返回 false。

9.2.11 preventExtensions()

preventExtensions()捕获器会在 Object.preventExtensions()中被调用。对应的反射 API方法为 Reflect.preventExtensions()。

const myTarget = {}; const proxy = new Proxy(myTarget, { preventExtensions(target) { console.log('preventExtensions()'); return Reflect.preventExtensions(...arguments) } }); Object.preventExtensions(proxy); // preventExtensions()
  1. 返回值

preventExtensions()必须返回布尔值,表示 target 是否已经不可扩展。返回非布尔值会被转

型为布尔值。

  1. 拦截的操作

 Object.preventExtensions(proxy)

 Reflect.preventExtensions(proxy)

  1. 捕获器处理程序参数

 target:目标对象。

  1. 捕获器不变式

如果 Object.isExtensible(proxy)是 false,则处理程序必须返回 true。

9.2.12 apply()

apply()捕获器会在调用函数时中被调用。对应的反射 API 方法为 Reflect.apply()

const myTarget = () => {}; const proxy = new Proxy(myTarget, { apply(target, thisArg, ...argumentsList) { console.log('apply()'); return Reflect.apply(...arguments) } }); proxy(); // apply()
  1. 返回值

返回值无限制。

  1. 拦截的操作

 proxy(…argumentsList)

 Function.prototype.apply(thisArg, argumentsList)

 Function.prototype.call(thisArg, …argumentsList)

 Reflect.apply(target, thisArgument, argumentsList)

  1. 捕获器处理程序参数

 target:目标对象。

 thisArg:调用函数时的 this 参数。

 argumentsList:调用函数时的参数列表

  1. 捕获器不变式

target 必须是一个函数对象。

9.2.13 construct()

construct()捕获器会在 new 操作符中被调用。对应的反射 API 方法为 Reflect.construct()。

const myTarget = function() {}; const proxy = new Proxy(myTarget, { construct(target, argumentsList, newTarget) { console.log('construct()'); return Reflect.construct(...arguments) } }); new proxy; // construct()
  1. 返回值

construct()必须返回一个对象。

  1. 拦截的操作

 new proxy(…argumentsList)

 Reflect.construct(target, argumentsList, newTarget)

  1. 捕获器处理程序参数

 target:目标构造函数。

 argumentsList:传给目标构造函数的参数列表。

 newTarget:最初被调用的构造函数。

  1. 捕获器不变式

target 必须可以用作构造函数。

9.3 代理模式

9.3.1 跟踪属性访问

通过捕获get、set和has等,监控对象何时被访问过

const user = { name: 'Jake'
};
const proxy = new Proxy(user, { get(target, property, receiver) { console.log(`Getting ${property}`); return Reflect.get(...arguments); }, set(target, property, value, receiver) { console.log(`Setting ${property}=${value}`); return Reflect.set(...arguments); }
});
proxy.name; // Getting name
proxy.age = 27; // Setting age=27

9.3.2 隐藏属性

代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举。比如:

const hiddenProperties = ['foo', 'bar'];
const targetObject = { foo: 1, bar: 2, baz: 3
};
const proxy = new Proxy(targetObject, { get(target, property) { if (hiddenProperties.includes(property)) { return undefined; } else { return Reflect.get(...arguments); } }, has(target, property) {if (hiddenProperties.includes(property)) { return false; } else { return Reflect.has(...arguments); } }
});
// get()
console.log(proxy.foo); // undefined
console.log(proxy.bar); // undefined
console.log(proxy.baz); // 3
// has()
console.log('foo' in proxy); // false
console.log('bar' in proxy); // false
console.log('baz' in proxy); // true

9.3.3 属性验证

因为所有赋值操作都会触发 set()捕获器,所以可以根据所赋的值决定是允许还是拒绝赋值:

const target = { onlyNumbersGoHere: 0
};
const proxy = new Proxy(target, { set(target, property, value) { if (typeof value !== 'number') { return false; } else { return Reflect.set(...arguments); } }
});
proxy.onlyNumbersGoHere = 1;
console.log(proxy.onlyNumbersGoHere); // 1
proxy.onlyNumbersGoHere = '2';
console.log(proxy.onlyNumbersGoHere); // 1

9.3.4 函数与构造函数参数验证

对函数和构造函数进行审查,可以让函数只接收某种类型的值

function median(...nums) { return nums.sort()[Math.floor(nums.length / 2)];
}
const proxy = new Proxy(median, { apply(target, thisArg, argumentsList) { for (const arg of argumentsList) { if (typeof arg !== 'number') { throw 'Non-number argument provided'; } }return Reflect.apply(...arguments); }
});
console.log(proxy(4, 7, 1)); // 4
console.log(proxy(4, '7', 1));
// Error: Non-number argument provided
class User { constructor(id) { this.id_ = id; }
}
const proxy = new Proxy(User, { construct(target, argumentsList, newTarget) { if (argumentsList[0] === undefined) { throw 'User cannot be instantiated without id'; } else { return Reflect.construct(...arguments); } }
});
new proxy(1);
new proxy();
// Error: User cannot be instantiated without id

9.3.5 数据绑定与可观察对象

通过代理可以把运行时中原本不相关的部分联系到一起。这样就可以实现各种模式,从而让不同的代码互操作。

比如,可以将被代理的类绑定到一个全局实例集合,让所有创建的实例都被添加到这个集合中:

const userList = [];
class User { constructor(name) { this.name_ = name; }
}
const proxy = new Proxy(User, { construct() { const newUser = Reflect.construct(...arguments); userList.push(newUser); return newUser; }
});
new proxy('John');
new proxy('Jacob');
new proxy('Jingleheimerschmidt');
console.log(userList); // [User {}, User {}, User{}]

另外,还可以把集合绑定到一个事件分派程序,每次插入新实例时都会发送消息:const userList = [];

const userList = [];
function emit(newValue) { console.log(newValue);
}
const proxy = new Proxy(userList, { set(target, property, value, receiver) { const result = Reflect.set(...arguments); if (result) { emit(Reflect.get(target, property, receiver)); } return result; }
});
proxy.push('John');
// John
proxy.push('Jacob');
// Jacob

第 10 章 函 数

函数创建(以下三个函数等价)

//函数声明
function sum (num1, num2) { return num1 + num2;
}//函数表达式
let sum = function(num1, num2) { return num1 + num2;
};//匿名函数
let sum = (num1, num2) => { return num1 + num2;
};

10.1 箭头函数

// 以下两种写法都有效
let double = (x) => { return 2 * x; };
let triple = x => { return 3 * x; };
// 没有参数需要括号
let getRandom = () => { return Math.random(); };
// 多个参数需要括号
let sum = (a, b) => { return a + b; };
//只有一条返回语句可省略{}
let triple = (x) => 3 * x;

注意:箭头函数不能使用 arguments、super 和new.target,也不能用作构造函数。

此外,箭头函数也没有 prototype 属性,this指向离他最近的上层非箭头函数的this

10.2 函数名

函数名就是指向函数的指针。使用不带括号的函数名会访问函数指针,而不会执行函数

function sum(num1, num2) {return num1 + num2;
}
console.log(sum(10, 10)); // 20
let anotherSum = sum;
console.log(anotherSum(10, 10)); // 20
sum = null;
console.log(anotherSum(10, 10)); // 20

10.3参数

可以在函数内部访问arguments(类数组对象)对象,从中取得传进来的每个参数值。

箭头函数中没有arguments对象,但是可以在包装函数中把它提供给箭头函数

function foo() { let bar = () => { console.log(arguments[0]); // 5 }; bar();
}
foo(5);

10.4 没有重载

在js中函数没有重载,如果定义两个同名函数,则后定义的会覆盖先定义的

function addSomeNumber(num) { return num + 100;
}
function addSomeNumber(num) { return num + 200;
}
let result = addSomeNumber(100); // 300

10.5 默认参数值

在es5.1之前,实现默认参数的一种常用方式就是检测某个参数是否等于undefined,如果是则意味着没有传这个参数,那就给他一个赋值

function makeKing(name) { name = (typeof name !== 'undefined') ? name : 'Henry'; return `King ${name} VIII`;
}
console.log(makeKing()); // 'King Henry VIII'
console.log(makeKing('Louis')); // 'King Louis VIII'

es6之后支持显式定义默认参数

function makeKing(name = 'Henry') { return `King ${name} VIII`;
}
console.log(makeKing('Louis')); // 'King Louis VIII'
console.log(makeKing()); // 'King Henry VIII'

在使用默认参数时,arguments 对象的值不反映参数的默认值,只反应传给函数的参数

function makeKing(name = 'Henry') { name = 'Louis'; return `King ${arguments[0]}`;
}
console.log(makeKing()); // 'King undefined'
console.log(makeKing('Louis')); // 'King Louis'

10.6 参数扩展与收集

通过arguments

let values = [1, 2, 3, 4];
function getSum() { let sum = 0; for (let i = 0; i < arguments.length; ++i) { sum += arguments[i]; } return sum;
}

通过apply()方法

console.log(getSum.apply(null, values)); // 10

通过扩展运算符

console.log(getSum(...values)); // 10

10.7函数声明与函数表达式

js在加载数据时对它们是有区别的,

js在任何代码执行之前,会先读取函数声明,并在执行上下文中生成函数定义,所以会存在函数声明提升

而函数表达式必须等到代码执行到它那一行,才会在执行上下文中生成函数定义

// 没问题
console.log(sum(10, 10));
function sum(num1, num2) { return num1 + num2;
}
// 会出错
console.log(sum(10, 10));
let sum = function(num1, num2) { return num1 + num2;
};

10.8 函数作为值

因为函数名在es中就是变量,所以函数可以用到任何可以使用变量的地方,这就意味着不仅可以把函数作为参数传给另一个函数,还可以在一个函数中返回另一个函数

function callSomeFunction(someFunction, someArgument) { return someFunction(someArgument);
}

10.9 函数内部

在 ECMAScript 5 中,函数内部存在两个特殊的对象:arguments 和 this。ECMAScript 6 又新增了 new.target 属性

10.9.1 arguments

这个对象只有在function关键字定义函数时才会有,此对象有一个callee属性,指向arguments对象所在函数的指针

function factorial(num) { if (num <= 1) { return 1; } else { //这种方法,必须保证函数名是factorialreturn num * factorial(num - 1); }
}

使用arguments。callee可以让函数逻辑与函数名解耦:

function factorial(num) { if (num <= 1) { return 1; } else { //无论函数叫什么名称,都可以引用正确的函数return num * arguments.callee(num - 1); }
}

10.9.2 this

在标准函数中,this指向Windows

在构造函数中,指向创建出来的实例

在对象的方法里调用,指向调用方法的对象

在严格模式下,this为undefined

在箭头函数中,指向上下文中离他最近的非箭头函数中的this

10.9.3 caller

这个属性引用的是调用当前函数的函数,如果是在全局作用域中调用则为null

function outer() { inner();
}
function inner() { console.log(inner.caller);   //ƒ outer() { inner(); }
}
outer();

如果要降低耦合度,则可以通过 arguments.callee.caller 来引用同样的值:

function outer() { inner();
}
function inner() { console.log(arguments.callee.caller);
}
outer();

10.9.4 new.target

如果函数时正常调用的,则new.target的值是undefined,如果是使用 new 关键字调用的,则 new.target 将引用被调用的构造函数。

function King() { if (!new.target) { throw 'King must be instantiated using "new"' } console.log('King instantiated using "new"');
}
new King(); // King instantiated using "new"
King(); // Error: King must be instantiated using "new"

10.10 函数属性和方法

由于函数也是对象,因此有属性和方法

每个函数有2个属性,.length和prototype,其中length属性保存函数定义的命名参数的个数

function sayName(name) { console.log(name);
}
function sum(num1, num2) { return num1 + num2;
}
function sayHi() { console.log("hi");
}
console.log(sayName.length); // 1
console.log(sum.length); // 2
console.log(sayHi.length); // 0

prototype 是保存引用类型所有实例方法的地方,这意味着 toString()、valueOf()等方法实际上都保存在 prototype 上,进而由所有实例共享。

函数还有2个方法:apply()、call(),可以改变this指向

apply()接收2个参数:函数内this的值和一个参数数组

function sum(num1, num2) { return num1 + num2;
}
function callSum1(num1, num2) { return sum.apply(this, arguments); // 传入 arguments 对象
}
function callSum2(num1, num2) { return sum.apply(this, [num1, num2]); // 传入数组
}
console.log(callSum1(10, 10)); // 20
console.log(callSum2(10, 10)); // 20

call()方法与apply()类似,第一个参数this的值,剩下的参数逐个传递

es5除了一个bind()方法,会创建一个新的函数实例,其 this 值会被绑定到传给 bind()的对象

10.11 函数表达式

没有变量提升会报错:

let functionName = function(arg0, arg1, arg2) { // 函数体
};

10.12 递归

递归函数:一个函数通过名称调用自己

如下阶乘

function factorial(num) { if (num <= 1) { return 1; } else { return num * factorial(num - 1); }
}

10.13 尾调用优化

即外部函数的返回值是一个内部函数的返回值

ECMAScript 6 规范新增了一项内存管理优化机制,让 JavaScript 引擎在满足条件时可以重用栈帧。

function outerFunction() { return innerFunction(); // 尾调用
}

在 ES6 优化之前,执行这个例子会在内存中发生如下操作。

(1) 执行到 outerFunction 函数体,第一个栈帧被推到栈上。

(2) 执行 outerFunction 函数体,到 return 语句。计算返回值必须先计算 innerFunction。

(3) 执行到 innerFunction 函数体,第二个栈帧被推到栈上。

(4) 执行 innerFunction 函数体,计算其返回值。

(5) 将返回值传回 outerFunction,然后 outerFunction 再返回值。

(6) 将栈帧弹出栈外。

在 ES6 优化之后,执行这个例子会在内存中发生如下操作。

(1) 执行到 outerFunction 函数体,第一个栈帧被推到栈上。

(2) 执行 outerFunction 函数体,到达 return 语句。为求值返回语句,必须先求值 innerFunction。

(3) 引擎发现把第一个栈帧弹出栈外也没问题,因为 innerFunction 的返回值也是 outerFunction

的返回值。

(4) 弹出 outerFunction 的栈帧。

(5) 执行到 innerFunction 函数体,栈帧被推到栈上。

(6) 执行 innerFunction 函数体,计算其返回值。

(7) 将 innerFunction 的栈帧弹出栈外。

10.13.1 尾调用优化的条件

 代码在严格模式下执行;

 外部函数的返回值是对尾调用函数的调用;

 尾调用函数返回后不需要执行额外的逻辑;

 尾调用函数不是引用外部函数作用域中自由变量的闭包。

"use strict";
// 有优化:栈帧销毁前执行参数计算
function outerFunction(a, b) { return innerFunction(a + b);
}
// 有优化:初始返回值不涉及栈帧
function outerFunction(a, b) { if (a < b) { return a; } return innerFunction(a + b);
}
// 有优化:两个内部函数都在尾部
function outerFunction(condition) { return condition ? innerFunctionA() : innerFunctionB();
}

10.13.2 尾调用优化代码

举个递归的例子:斐波纳契数列的函数

function fib(n) { if (n < 2) { return n; } return fib(n - 1) + fib(n - 2);
}
console.log(fib(0)); // 0
console.log(fib(1)); // 1
console.log(fib(2)); // 1
console.log(fib(3)); // 2
console.log(fib(4)); // 3
console.log(fib(5)); // 5
console.log(fib(6)); // 8

显然这个函数不符合尾调用优化的条件,因为返回语句中有一个相加的操作fib(n)的栈帧数的内存复杂度是 O(2n)。

为此可以使用两个嵌套的函数,外部函数作为基础框架,内部函数执行递归:

"use strict";
// 基础框架
function fib(n) { return fibImpl(0, 1, n);
}
// 执行递归
function fibImpl(a, b, n) { if (n === 0) { return a; } return fibImpl(b, a + b, n - 1);
}

10.14 闭包

指引用了而另一个函数作用域中的变量,通常在嵌套函数中

function createComparisonFunction(propertyName) { return function(object1, object2) { let value1 = object1[propertyName]; let value2 = object2[propertyName]; if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } };
}

函数内部的代码在访问变量时,就会使用给定的名称从作用域链中查找变量。函数执行完毕后,局部活动对象会被销毁,内存中就只剩下全局作用域。而闭包会保存到内存中,直到函数被销毁后才会被销毁;

10.14.1 内存泄漏

由于 IE 在 IE9 之前对 JScript 对象和 COM 对象使用了不同的垃圾回收机制,所以闭包在这些旧版本 IE 中可能会导致问题。在这些版本的 IE 中,把 HTML 元素保存在某个闭包的作用域中,就相当于宣布该元素不能被销毁。来看下面的例子:

function assignHandler() { let element = document.getElementById('someElement'); element.onclick = () => console.log(element.id);
}

以上代码创建了一个闭包,即 element 元素的事件处理程序。而这个处理程序又创建了一个循环引用。匿名函数引用着 assignHandler()的活动对象,阻止了对element 的引用计数归零。只要这个匿名函数存在,element 的引用计数就至少等于 1。也就是说,内存不会被回收。其实只要这个例子稍加修改,就可以避免这种情况,比如:

function assignHandler() { let element = document.getElementById('someElement'); let id = element.id; element.onclick = () => console.log(id);element = null;
}

在这个修改后的版本中,闭包改为引用一个保存着 element.id 的变量 id,从而消除了循环引用。不过,光有这一步还不足以解决内存问题。因为闭包还是会引用包含函数的活动对象,而其中包含element。即使闭包没有直接引用 element,包含函数的活动对象上还是保存着对它的引用。因此,必须再把 element 设置为 null。这样就解除了对这个 COM 对象的引用,其引用计数也会减少,从而确保其内存可以在适当的时候被回收。

10.15 立即调用的函数表达式

(function() { // 块级作用域
})();

第 11 章 期约与异步函数

11.1 异步编程

11.1.1 同步与异步

同步行为对应内存中顺序执行的处理器指令

异步行为类似于系统中断,即当前进程外部的实体可以触发代码执行。

11.1.2 以往的异步编程模式(回调地狱)

function double(value) { setTimeout(() => setTimeout(console.log, 0, value * 2), 1000);
}
double(3);
// 6(大约 1000 毫秒之后)

11.2 期约(promise)

let p = new Promise(() => {});
setTimeout(console.log, 0, p); // Promise <pending>

1、三种状态:

 待定(pending)

 兑现(fulfilled,有时候也称为“解决”,resolved)

 拒绝(rejected)

2、实例方法:

.then()

.catch()

.finally()

.all()

.race()

3、期约扩展

期约取消: 第三方库:Bluebird

期约进度通知: notify()方法

11.3 异步函数(async/await)

1、async

async 关键字用于声明异步函数。这个关键字可以用在函数声明、函数表达式、箭头函数和方法上

async function foo() {}
let bar = async function() {};
let baz = async () => {};
class Qux { async qux() {}
}

2、await

关键字可以暂停异步函数代码的执行,等待期约解决

await 关键字会暂停执行异步函数后面的代码,让出 JavaScript 运行时的执行线程。

async function foo() { let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3)); console.log(await p);
}
foo();
// 3

第12章 BOM

12.1 window对象

12.1.1 global作用域

var age = 29;
var sayAge = () => alert(this.age);
alert(window.age); // 29
sayAge(); // 29
window.sayAge(); // 29

12.1.2 浏览器窗口的尺寸

对于Internet Explorer、Chrome、Firefox、Opera 以及 Safari:

  • window.innerHeight - 浏览器窗口的内部高度(包括滚动条)
  • window.innerWidth - 浏览器窗口的内部宽度(包括滚动条)

对于 Internet Explorer 8、7、6、5:

  • document.documentElement.clientHeight
  • document.documentElement.clientWidth

或者

  • document.body.clientHeight
  • document.body.clientWidth

12.1.3 window方法

  • window.open() - 打开新窗口
  • window.close() - 关闭当前窗口
  • window.moveTo() - 移动当前窗口
  • window.resizeTo() - 调整当前窗口的尺寸

12.1.4 系统对话框

使用alert()、confirm()、prompt()

12.2 location对象

  • location.hostname 返回 web 主机的域名
  • location.pathname 返回当前页面的路径和文件名
  • location.port 返回 web 主机的端口 (80 或 443)
  • location.protocol 返回所使用的 web 协议(http: 或 https:)

12.3 navigator对象

appName 浏览器名称
appVersion 浏览器版本
cookieEnable 启用cookies
platform 硬件平台
userAgent 用户代理
language 用户代理语言
appCodeName 浏览器代号

12.4 Screen

  • screen.availWidth - 可用的屏幕宽度
  • screen.availHeight - 可用的屏幕高度

12.5 History

12.5.1 导航

// 后退一页
history.go(-1);
// 前进一页
history.go(1);
// 前进两页
history.go(2);
// 导航到最近的 wrox.com 页面
history.go("wrox.com");
// 后退一页
history.back();
// 前进一页
history.forward();
//history 对象还有一个 length 属性,表示历史记录中有多个条目

12.5.2历史状态管理

hashchange 会在页面 URL 的散列变化时被触发,开发者可以在此时执行某些操作。而状态管理API 则可以让开发者改变浏览器 URL 而不会加载新页面。为此,可以使用 history.pushState()方法。这个方法接收 3 个参数:一个 state 对象、一个新状态的标题和一个(可选的)相对 URL。例如:

let stateObject = {foo:"bar"};
history.pushState(stateObject, "My title", "baz.html");

pushState()方法执行后,状态信息就会被推到历史记录中,浏览器地址栏也会改变以反映新的相对 URL。除了这些变化之外,即使 location.href 返回的是地址栏中的内容,浏览器页不会向服务器发送请求。第二个参数并未被当前实现所使用,因此既可以传一个空字符串也可以传一个短标题。第一个参数应该包含正确初始化页面状态所必需的信息。为防止滥用,这个状态的对象大小是有限制的,通常在 500KB~1MB 以内。

因为 pushState()会创建新的历史记录,所以也会相应地启用“后退”按钮。此时单击“后退”按钮,就会触发 window 对象上的 popstate 事件。popstate 事件的事件对象有一个 state 属性,其中包含通过 pushState()第一个参数传入的 state 对象:

window.addEventListener("popstate", (event) => { let state = event.state; if (state) { // 第一个页面加载时状态是 null processState(state); }
});

基于这个状态,应该把页面重置为状态对象所表示的状态(因为浏览器不会自动为你做这些)。记住,页面初次加载时没有状态。因此点击“后退”按钮直到返回最初页面时,event.state 会为 null。可以通过 history.state 获取当前的状态对象,也可以使用 replaceState()并传入与pushState()同样的前两个参数来更新状态。更新状态不会创建新历史记录,只会覆盖当前状态:history.replaceState({newFoo: “newBar”}, “New title”); 传给 pushState()和 replaceState()的 state 对象应该只包含可以被序列化的信息。因此,DOM 元素之类并不适合放到状态对象里保存。

第14章 DOM

14.1 节点层级

14.1.1 Node类型

appendChild(),添加节点

insertBefore()

// 作为最后一个子节点插入
returnedNode = someNode.insertBefore(newNode, null);
alert(newNode == someNode.lastChild); // true
// 作为新的第一个子节点插入
returnedNode = someNode.insertBefore(newNode, someNode.firstChild);
alert(returnedNode == newNode); // true
alert(newNode == someNode.firstChild); // true
// 插入最后一个子节点前面
returnedNode = someNode.insertBefore(newNode, someNode.lastChild);
alert(newNode == someNode.childNodes[someNode.childNodes.length - 2]); // true

appendChild() 和 insertBefore() 在插入节点时不会删除任何已有节点。相对地,replaceChild()方法接收两个参数:要插入的节点和要替换的节点。

// 替换第一个子节点
let returnedNode = someNode.replaceChild(newNode, someNode.firstChild);
// 替换最后一个子节点
returnedNode = someNode.replaceChild(newNode, someNode.lastChild);

removeChild删除节点

// 删除第一个子节点
let formerFirstChild = someNode.removeChild(someNode.firstChild);
// 删除最后一个子节点
let formerLastChild = someNode.removeChild(someNode.lastChild);

所有节点类型还共享了两个方法。

第一个是 cloneNode(),会返回与调用它的节点一模一样的节点。cloneNode()方法接收一个布尔值参数,表示是否深复制。在传入 true 参数时,会进行深复制,即复制节点及其整个子 DOM 树。如果传入 false,则只会复制调用该方法的节点。

第二个是normalize ,是处理文档子树中的文本节点。

14.1.2 Document类型

 nodeType 等于 9;

 nodeName 值为"#document";

 nodeValue 值为 null;

 parentNode 值为 null;

 ownerDocument 值为 null;

 子节点可以是 DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction

或 Comment 类型。

所有主流浏览器都支持 document.documentElement 和 document.body。

documentElement、firstChild 和 childNodes[0]都指向同一个值,即<html>元素
// 读取文档标题
let originalTitle = document.title;
// 修改文档标题
document.title = "New page title";
// 取得完整的 URL
let url = document.URL;
// 取得域名
let domain = document.domain;
// 取得来源
let referrer = document.referrer;

方法:getElementById()、 getElementsByTagName()、getElementsByName()

文档写入:write()、writeln()、open()、 close()

14.1.3 Element 类型

 nodeType 等于 1;

 nodeName 值为元素的标签名;

 nodeValue 值为 null;

 parentNode 值为 Document 或 Element 对象;

 子节点可以是 Element、Text、Comment、ProcessingInstruction、CDATASection、EntityReference 类型。

所有的html都有标准属性:id、title、lang、dir、className

方法:getAttribute()、setAttribute()和 removeAttribute()

创建元素:createElement()

元素后代:childNodes()

14.1.4 Text类型

 nodeType 等于 3;

 nodeName 值为"#text";

 nodeValue 值为节点中包含的文本;

 parentNode 值为 Element 对象;

 不支持子节点。

Text 节点中包含的文本可以通过 nodeValue 属性访问,也可以通过 data 属性访问,这两个属性

包含相同的值。修改 nodeValue 或 data 的值,也会在另一个属性反映出来。文本节点暴露了以下操

作文本的方法:

 appendData(text),向节点末尾添加文本 text;

 deleteData(offset, count),从位置 offset 开始删除 count 个字符;

 insertData(offset, text),在位置 offset 插入 text;

 replaceData(offset, count, text),用 text 替换从位置 offset 到 offset + count 的

文本;

 splitText(offset),在位置 offset 将当前文本节点拆分为两个文本节点;

 substringData(offset, count),提取从位置 offset 到 offset + count 的文本。

除了这些方法,还可以通过 length 属性获取文本节点中包含的字符数量。这个值等于 nodeValue. length 和 data.length。

14.1.5 Comment 类型

DOM 中的注释通过 Comment 类型表示。Comment 类型的节点具有以下特征:

 nodeType 等于 8;

 nodeName 值为"#comment";

 nodeValue 值为注释的内容;

 parentNode 值为 Document 或 Element 对象;

 不支持子节点。

14.1.6 CDATASection 类型

CDATASection 类型表示 XML 中特有的 CDATA 区块。CDATASection 类型继承 Text 类型,因

此拥有包括 splitText()在内的所有字符串操作方法。CDATASection 类型的节点具有以下特征:

 nodeType 等于 4;

 nodeName 值为"#cdata-section";

 nodeValue 值为 CDATA 区块的内容;

 parentNode 值为 Document 或 Element 对象;

 不支持子节点。

CDATA 区块只在 XML 文档中有效,因此某些浏览器比较陈旧的版本会错误地将 CDATA 区块解析为 Comment 或 Element。

14.1.7 DocumentType 类型

DocumentType 类型的节点包含文档的文档类型(doctype)信息,具有以下特征:

 nodeType 等于 10;

 nodeName 值为文档类型的名称;

 nodeValue 值为 null;

 parentNode 值为 Document 对象;

 不支持子节点。

14.1.8 DocumentFragment 类型

在所有节点类型中,DocumentFragment 类型是唯一一个在标记中没有对应表示的类型。DOM 将

文档片段定义为“轻量级”文档,能够包含和操作节点,却没有完整文档那样额外的消耗。

DocumentFragment 节点具有以下特征:

 nodeType 等于 11;

 nodeName 值为"#document-fragment";

 nodeValue 值为 null;

 parentNode 值为 null;

 子节点可以是 Element、ProcessingInstruction、Comment、Text、CDATASection 或EntityReference。

14.1.9 Attr 类型

元素数据在 DOM 中通过 Attr 类型表示。Attr 类型构造函数和原型在所有浏览器中都可以直接访

问。技术上讲,属性是存在于元素 attributes 属性中的节点。Attr 节点具有以下特征:

 nodeType 等于 2;

 nodeName 值为属性名;

 nodeValue 值为属性值;

 parentNode 值为 null;

 在 HTML 中不支持子节点;

 在 XML 中子节点可以是 Text 或 EntityReference。

14.2 DOM变成

14.2.1动态脚本

//动态加载外部文件很容易实现,比如下面的<script>元素:<script src="foo.js"></script> //可以像这样通过 DOM 编程创建这个节点:let script = document.createElement("script"); script.src = "foo.js"; document.body.appendChild(script);

14.2.2 动态样式

let link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = "styles.css";
let head = document.getElementsByTagName("head")[0];
head.appendChild(link);

14.2.3 操作表格

元素添加了以下属性和方法:

 caption,指向元素的指针(如果存在);

 tBodies,包含元素的 HTMLCollection;

 tFoot,指向元素(如果存在);

 tHead,指向元素(如果存在);

 rows,包含表示所有行的 HTMLCollection;

 createTHead(),创建元素,放到表格中,返回引用;

 createTFoot(),创建元素,放到表格中,返回引用;

 createCaption(),创建元素,放到表格中,返回引用;

 deleteTHead(),删除元素;

 deleteTFoot(),删除元素;

 deleteCaption(),删除元素;

 deleteRow(pos),删除给定位置的行;

 insertRow(pos),在行集合中给定位置插入一行。

元素添加了以下属性和方法:

 rows,包含元素中所有行的 HTMLCollection;

第15章 DOM扩展

Selectors API Level 1 的核心是两个方法:querySelector()和 querySelectorAll()。在兼容浏

览器中,Document 类型和 Element 类型的实例上都会暴露这两个方法。

Selectors API Level 2 规范在 Element 类型上新增了更多方法,比如 matches()、find()和

findAll()。不过,目前还没有浏览器实现或宣称实现 find()和 findAll()。

getElementsByClassName()

第16章 DOM2和DOM3

第17章 事件

17.1 事件流

17.1.1 事件冒泡

由内向外触发

17.1.2 事件捕获

从外向内触发

17.2事件处理程序

// html
<input type="button" value="Click Me" onclick="console.log('Clicked')"/>
//domo
let btn = document.getElementById("myBtn");
btn.onclick = function() { console.log("Clicked");
};
//DOM2
//addEventListener()和 removeEventListener(),它们接收 3 个参数:事件名、事件处理函
//数和一个布尔值,true 表示在捕获阶段调用事件处理程序,false(默认值)表示在冒泡阶段调用事
//件处理程序。
let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => { console.log(this.id);
}, false);//IE 事件处理程序  即 attachEvent()和 detachEvent()
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function() { console.log("Clicked");
});

17.3 事件对象

在 DOM 中发生事件时,所有相关信息都会被收集并存储在一个名为 event 的对象中。这个对象包含了一些基本信息,比如导致事件的元素、发生的事件类型,以及可能与特定事件相关的任何其他数据。例如,鼠标操作导致的事件会生成鼠标位置信息,而键盘操作导致的事件会生成与被按下的键有关的信息。所有浏览器都支持这个 event 对象,

17.4 事件类型

17.4.1 用户界面事件

  • load:在window上当页面(包括所有外部资源)加载完成后触发
  • unload:在window上当页面完全卸载后触发
  • abort:在元素上当相应对象加载完成前被用户提前终止下载时触发
  • error:在js报错时触发
  • select:在文本框上当用户选择一个或多个字符时触发
  • resize:在窗口或窗格被缩放时触发
  • scroll:滚动时触发

17.4.2 焦点事件

  • blur:当元素失去焦点时
  • focus:获得焦点时触发
  • focusin:当元素获得焦点时触发。这个事件是 focus 的冒泡版。
  • focusout:当元素失去焦点时触发。这个事件是 blur 的通用版。

17.4.3 鼠标和滚轮事件

  • click:单击,通常是左键或按键盘回车键时触发
  • dbclick:双击
  • mousedown:在用户按下任意鼠标键时触发
  • mouseenter:在用户把鼠标光标从元素外部移到元素内部时触发,不会冒泡,也不会在光标经过后代元素时触发
  • mouseleave:在用户把鼠标光标从元素内部移到元素外部时触发,不冒泡
  • mousemove:在鼠标光标在元素上移动时反复触发
  • mouseout:在用户把鼠标光标从一个元素移到另一个元素上时触发
  • mouseover:在用户把鼠标光标从元素外部移到元素内部时触发。
  • mouseup:在用户释放鼠标键时触发

17.4.4 键盘与输入事件

  • keydown,用户按下键盘上某个键时触发,而且持续按住会重复触发。
  • keypress,用户按下键盘上某个键并产生字符时触发,而且持续按住会重复触发。Esc 键也会触发这个事件。DOM3 Events 废弃了 keypress 事件,而推荐 textInput 事件。
  • keyup:用户释放键盘上某个键时触发

17.4.5 合成事件

合成事件是在DOM3 Events中新增的,用于处理通常使用IMS输入时的复杂输入序列。IME 可以让用户输入物理键盘上没有的字符。例如,使用拉丁字母键盘的用户还可以使用 IME 输入日文。IME 通常需要同时按下多个键才能输入一个字符。合成事件用于检测和控制这种输入。合成事件有以下 3 种:

  • compositionstart,在 IME 的文本合成系统打开时触发,表示输入即将开始;
  • compositionupdate,在新字符插入输入字段时触发;
  • compositionend,在 IME 的文本合成系统关闭时触发,表示恢复正常键盘输入。

17.4.6 变化事件

已被废弃

17.4.7 HTML5事件

  1. contextmenu:鼠标右键事件

    事件冒泡,因此只要给document指定一个事件处理程序就可以处理页面上所有同类事件。

    可用event.preventDefault()取消冒泡 ,IE8 及更早版本中将 event.returnValue 设置为 false

  2. beforeunload事件

    在页面即将从浏览器中卸载时触发

  3. DOMContentLoaded事件

    在页面完全加载后触发,不用等待图片、js文件、css文件等外部资源加载完成

  4. readystatechange

    提供文档或元素加载状态的信息

  5. pageshow 与 pagehide 事件

    无论是否来自往返缓存,在新加载的页面上都会触发

  6. hashchange 事件

    用于在url发生变化时通知开发者

17.4.8 设备事件

  1. orientationchange

    判断用户的设备是处于垂直模式还是水平模式

  2. deviceorientation

    设备在空间中的朝向

  3. devicemotion

    这个事件用于提示设备实际上在移动,而不仅仅是改变了朝向。例如,devicemotion 事件可以用来确定设备正在掉落或者正拿在一个行走的人手里

17.4.9 触摸及手势事件

  1. touchstart:手指放到屏幕上时触发(即使有一个手指已经放在了屏幕上)。
  2. touchmove:手指在屏幕上滑动时连续触发。在这个事件中调用 preventDefault()可以阻止滚动
  3. touchend:手指从屏幕上移开时触发。
  4. touchcancel:系统停止跟踪触摸时触发。文档中并未明确什么情况下停止跟踪。
  5. gesturestart:一个手指已经放在屏幕上,再把另一个手指放到屏幕上时触发。
  6. gesturechange:任何一个手指在屏幕上的位置发生变化时触发。
  7. gestureend:其中一个手指离开屏幕时触发。

17.5 内存与性能

17.5.1 事件委托

使用一个事件处理程序来管理一种类型的事件

17.5.2 删除事件处理程序

及时删除不用的事件处理程序,如:btn.onclick = null;

17.6 模拟事件

通过document.createEvent()方法创建一个 event 对象。

17.4 事件类型

17.4.1 用户界面事件

  • load:在window上当页面(包括所有外部资源)加载完成后触发
  • unload:在window上当页面完全卸载后触发
  • abort:在元素上当相应对象加载完成前被用户提前终止下载时触发
  • error:在js报错时触发
  • select:在文本框上当用户选择一个或多个字符时触发
  • resize:在窗口或窗格被缩放时触发
  • scroll:滚动时触发

17.4.2 焦点事件

  • blur:当元素失去焦点时
  • focus:获得焦点时触发
  • focusin:当元素获得焦点时触发。这个事件是 focus 的冒泡版。
  • focusout:当元素失去焦点时触发。这个事件是 blur 的通用版。

17.4.3 鼠标和滚轮事件

  • click:单击,通常是左键或按键盘回车键时触发
  • dbclick:双击
  • mousedown:在用户按下任意鼠标键时触发
  • mouseenter:在用户把鼠标光标从元素外部移到元素内部时触发,不会冒泡,也不会在光标经过后代元素时触发
  • mouseleave:在用户把鼠标光标从元素内部移到元素外部时触发,不冒泡
  • mousemove:在鼠标光标在元素上移动时反复触发
  • mouseout:在用户把鼠标光标从一个元素移到另一个元素上时触发
  • mouseover:在用户把鼠标光标从元素外部移到元素内部时触发。
  • mouseup:在用户释放鼠标键时触发

17.4.4 键盘与输入事件

  • keydown,用户按下键盘上某个键时触发,而且持续按住会重复触发。
  • keypress,用户按下键盘上某个键并产生字符时触发,而且持续按住会重复触发。Esc 键也会触发这个事件。DOM3 Events 废弃了 keypress 事件,而推荐 textInput 事件。
  • keyup:用户释放键盘上某个键时触发

17.4.5 合成事件

合成事件是在DOM3 Events中新增的,用于处理通常使用IMS输入时的复杂输入序列。IME 可以让用户输入物理键盘上没有的字符。例如,使用拉丁字母键盘的用户还可以使用 IME 输入日文。IME 通常需要同时按下多个键才能输入一个字符。合成事件用于检测和控制这种输入。合成事件有以下 3 种:

  • compositionstart,在 IME 的文本合成系统打开时触发,表示输入即将开始;
  • compositionupdate,在新字符插入输入字段时触发;
  • compositionend,在 IME 的文本合成系统关闭时触发,表示恢复正常键盘输入。

17.4.6 变化事件

已被废弃

17.4.7 HTML5事件

  1. contextmenu:鼠标右键事件

    事件冒泡,因此只要给document指定一个事件处理程序就可以处理页面上所有同类事件。

    可用event.preventDefault()取消冒泡 ,IE8 及更早版本中将 event.returnValue 设置为 false

  2. beforeunload事件

    在页面即将从浏览器中卸载时触发

  3. DOMContentLoaded事件

    在页面完全加载后触发,不用等待图片、js文件、css文件等外部资源加载完成

  4. readystatechange

    提供文档或元素加载状态的信息

  5. pageshow 与 pagehide 事件

    无论是否来自往返缓存,在新加载的页面上都会触发

  6. hashchange 事件

    用于在url发生变化时通知开发者

17.4.8 设备事件

  1. orientationchange

    判断用户的设备是处于垂直模式还是水平模式

  2. deviceorientation

    设备在空间中的朝向

  3. devicemotion

    这个事件用于提示设备实际上在移动,而不仅仅是改变了朝向。例如,devicemotion 事件可以用来确定设备正在掉落或者正拿在一个行走的人手里

17.4.9 触摸及手势事件

  1. touchstart:手指放到屏幕上时触发(即使有一个手指已经放在了屏幕上)。
  2. touchmove:手指在屏幕上滑动时连续触发。在这个事件中调用 preventDefault()可以阻止滚动
  3. touchend:手指从屏幕上移开时触发。
  4. touchcancel:系统停止跟踪触摸时触发。文档中并未明确什么情况下停止跟踪。
  5. gesturestart:一个手指已经放在屏幕上,再把另一个手指放到屏幕上时触发。
  6. gesturechange:任何一个手指在屏幕上的位置发生变化时触发。
  7. gestureend:其中一个手指离开屏幕时触发。

17.5 内存与性能

17.5.1 事件委托

使用一个事件处理程序来管理一种类型的事件

17.5.2 删除事件处理程序

及时删除不用的事件处理程序,如:btn.onclick = null;

17.6 模拟事件

通过document.createEvent()方法创建一个 event 对象。

JavaScript高级程序设计 第4版相关推荐

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

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

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

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

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

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

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

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

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

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

  6. javascript高级程序设计 第三版

    网盘地址 提取码:vh81 笔记 第二章 2.1script标签 <script>元素属性:async.charset.defer.language.src.type async和defe ...

  7. 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 ...

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

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

  9. 《JavaScript高级程序设计 第3版》-学习笔记-1

    P1-P30页 1.<script>标签的属性 async:async(html)  | async="async"(xhtml),表示立即下载脚本,但不马上执行(执行 ...

  10. javascript高级程序设计第3版——第6章 面向对象的程序设计

    第六章--面向对象的程序设计 这一章主要讲述了:面向对象的语言由于没有类/接口情况下工作的几种模式以及面向对象语言的继承: 模式:工厂模式,构造函数模式,原型模式 继承:原型式继承,寄生式继承,以及寄 ...

最新文章

  1. 来看看企业如何拥抱混合云?
  2. 落地华东总部、上线创新云、签约AIoT产业基金……京东云南京“新动作”
  3. 全球及中国工业金刚石微米粉行业十四五规划研究与发展战略分析报告2021年版
  4. python基础教程:函数作用域
  5. python3函数中lambda/filter/map/reduce的用法
  6. jsp可以使用iframe_使用 JavaScript object URLs,可以处理图像、音频和视频
  7. 2 了解MyBatis配置文件
  8. REVERSE-PRACTICE-BUUCTF-14
  9. mySQL 建表约束
  10. 2019/5/10开始咯
  11. 完全二叉树的判断java,判断二叉树是否为完全二叉树的实例
  12. django 一个项目多个App项目搭建
  13. jquery中的ajax写法
  14. ofo的智能锁初代方案:声波频率识别开锁
  15. 201871010134-周英杰《面向对象程序设计(java)》第二周学习总结
  16. Python 网络爬虫从0到1 (2):网络爬虫的特性、问题与规范
  17. 是时候回答【我为什么要学习 Go 语言(golang)】这个问题了
  18. python的多线程使用setDaemon有什么意义?
  19. 【Nav2中文网】五、普通教程(三)用实体Turtlebot3机器人导航
  20. 保罗兰德作品赏析_每周一书:保罗·兰德《设计的意义:保罗·兰德谈设计、形式与混沌》...

热门文章

  1. Python渗透测试之网络嗅探与欺骗
  2. Android动画分类与总结
  3. 计算机专业小米笔记本推荐,小米笔记本哪款好
  4. pyspark 空值填充
  5. Linux系统蓝牙WiFi抓包命令
  6. OPPO A57怎么刷机 OPPO A57的刷机教程 OPPO A57完美解除账号锁
  7. 解析数论导轮中的数学实验(python)
  8. 防病毒服务器维护记录表,机房巡检记录表.doc
  9. android+自动拨打电话,自动拨打电话 - 好玩的代码
  10. 2020 工业机器人行业研究报告