现代 JavaScript 教程——学习记录
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了现代 JavaScript 教程——学习记录,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含29343字,纯文字阅读大概需要42分钟。
内容图文
JavaScript基础知识
现代模式,“use strict”
begin from ES5, 为了标识ES5的新特性
变量
我们可以使用 var、let 或 const 声明变量来存储数据。
- let — 现代的变量声明方式。
- var — 老旧的变量声明方式。一般情况下,我们不会再使用它。
- const — 类似于 let,但是变量的值无法被修改。
用const
声明的对象的值 能 被修改。
数据类型
JavaScript 中有八种基本的数据类型(译注:前七种为基本数据类型,也称为原始类型,而 object
为复杂数据类型)。
number
用于任何类型的数字:整数或浮点数,在±(2?53-1)
范围内的整数。bigint
用于任意长度的整数。string
用于字符串:一个字符串可以包含 0 个或多个字符。boolean
用于true
和false
。null
用于未知的值 —— 只有一个null
值的独立类型。undefined
用于未定义的值 —— 只有一个undefined
值的独立类型。symbol
用于唯一的标识符。object
用于更复杂的数据结构。
我们可以通过 typeof
运算符查看存储在变量中的数据类型。
- 两种形式:
typeof x
或者typeof(x)
。 - 以字符串的形式返回类型名称,例如
"string"
。 typeof null
会返回"object"
—— 这是 JavaScript 编程语言的一个错误,实际上它并不是一个object
。
typeof null == "object" // JavaScript 编程语言的设计错误
typeof function(){} == "function" // 函数被特殊对待
交互:alert、prompt 和 confirm
与用户交互的 3 个浏览器的特定函数:
-
alert(message)
显示信息。
-
prompt(question[, default])
显示信息要求用户输入文本。点击确定返回文本,点击取消或按下 Esc 键返回
null
。 -
confirm(question)
显示信息等待用户点击确定或取消。点击确定返回
true
,点击取消或按下 Esc 键返回false
。
这些方法都是模态的:它们暂停脚本的执行,并且不允许用户与该页面的其余部分进行交互,直到窗口被解除。
类型转换
有三种常用的类型转换:转换为 string 类型、转换为 number 类型和转换为 boolean 类型。
字符串转换 —— 转换发生在输出内容的时候,也可以通过 String(value)
进行显式转换。原始类型值的 string 类型转换通常是很明显的。
数字型转换 —— 转换发生在进行算术操作时,也可以通过 Number(value)
进行显式转换。
数字型转换遵循以下规则:
值 | 变成…… |
---|---|
undefined | NaN |
null | 0 |
true / false | 1 / 0 |
string | “按原样读取”字符串,两端的空白会被忽略。空字符串变成 0 。转换出错则输出 NaN 。 |
布尔型转换 —— 转换发生在进行逻辑操作时,也可以通过 Boolean(value)
进行显式转换。
布尔型转换遵循以下规则:
值 | 变成…… |
---|---|
0 , null , undefined , NaN , "" | false |
其他值 | true |
上述的大多数规则都容易理解和记忆。人们通常会犯错误的值得注意的例子有以下几个:
- 对
undefined
进行数字型转换时,输出结果为NaN
,而非0
。 - 对
"0"
和只有空格的字符串(比如:" "
)进行布尔型转换时,输出结果为true
。
基础运算符,数学
-
二元运算符"+"链接字符串
-
一元运算符"+"转成数字类型
-
自增++/自减–
记住返回值是 新(前)旧(后):符号在前返回新值,符号在后返回旧值
值的比较
-
比较运算符始终返回布尔值。
-
字符串的比较,会按照“词典”顺序逐字符地比较大小。
-
当对不同类型的值进行比较时,它们会先被转化为数字(不包括严格相等检查)再进行比较。
-
普通的相等性检查
==
存在一个问题,它不能区分出0
和false
,无法区分空字符串和false
-
相等性检查
==
和普通比较符> < >= <=
的代码逻辑是相互独立的alert( null > 0 ); // (1) false alert( null == 0 ); // (2) false alert( null >= 0 ); // (3) true
-
在非严格相等
==
下,null
和undefined
相等且各自不等于任何其他的值。 -
在使用
>
或<
进行比较时,需要注意变量可能为null/undefined
的情况。比较好的方法是单独检查变量是否等于null/undefined
。 -
另外,number类型中的值 “NaN” 是独一无二的,它不等于任何东西,包括它自身:
alert( NaN === NaN ); // false
注意:
- 除了严格相等
===
外,其他但凡是有undefined/null
参与的比较,我们都需要格外小心。 - 除非你非常清楚自己在做什么,否则永远不要使用
>= > < <=
去比较一个可能为null/undefined
的变量。对于取值可能是null/undefined
的变量,请按需要分别检查它的取值情况。
条件分支:if 和 ‘?’
略
逻辑运算符
JavaScript 里有三个逻辑运算符:||
(或),&&
(与),!
(非)。
- 一个或运算
"||"
的链,将返回第一个真值,如果不存在真值,就返回该链的最后一个值。- 获取变量列表或者表达式的第一个真值。
- 短路求值(Short-circuit evaluation)。
- 与运算符
"&&"
的链返回第一个假值,如果没有假值就返回最后一个值。 - 优先级:
!
>&&
>||
空值合并运算符 ‘??’
(新特性,老的浏览器可能不支持)
空值合并运算符 ??
提供了一种简短的语法,用来获取列表中第一个“已定义”的变量(译注:即值不是 null
或 undefined
的变量)。
a ?? b
的结果是:
a
,如果a
不是null
或undefined
,b
,其他情况。- 区别
"||"
:
let height = 0;
alert(height || 100); // 100
alert(height ?? 100); // 0
??
运算符的优先级非常低,只略高于?
和=
。- 如果没有明确添加括号,不能将其与
||
或&&
一起使用。
循环:while 和 for
三种循环:
while
—— 每次迭代之前都要检查条件。do..while
—— 每次迭代后都要检查条件。for (;;)
—— 每次迭代之前都要检查条件,可以使用其他设置。
通常使用 while(true)
来构造“无限”循环。这样的循环和其他循环一样,都可以通过 break
指令来终止。
如果我们不想在当前迭代中做任何事,并且想要转移至下一次迭代,那么可以使用 continue
指令。
break/continue
支持循环前的标签。标签是 break/continue
跳出嵌套循环以转到外部的唯一方法。
“switch” 语句
-
如果没有
break
,程序将不经过任何检查就会继续执行下一个case
。 -
共享同一段代码的几个
case
分支可以被分为一组: -
强调一下,"switch"的相等是严格相等。被比较的值必须是相同的类型才能进行匹配。
函数
函数对外部变量拥有全部的访问权限。函数也可以修改外部变量。只有在没有局部变量的情况下才会使用外部变量。如果在函数内部声明了同名变量,那么函数会 遮蔽 外部变量。
默认值、后备的默认参数:
-
如果调用时未提供参数,那么其默认值则是
undefined
。 -
后备:跟
undefined
作比较、使用||
、使用??
//1. function showMessage(text) { if (text === undefined) { text = 'empty message'; } alert(text); } //2. function showMessage(text) { text = text || 'empty'; ... } //3. // 如果没有传入 "count" 参数,则显示 "unknown" function showCount(count) { alert(count ?? "unknown"); } showCount(0); // 0 showCount(null); // unknown showCount(); // unknown
空值的 return
或没有 return
的函数返回值为 undefined
函数表达式
//函数声明
function sayHi() {
alert( "Hello" );
}
//函数表达式
let sayHi = function() {
alert( "Hello" );
};
函数表达式结尾有一个分号 ;
,而函数声明没有。
回调函数、匿名函数
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
function showOk() {
alert( "You agreed." );
}
function showCancel() {
alert( "You canceled the execution." );
}
// 用法:函数 showOk 和 showCancel 被作为参数传入到 ask
ask("Do you agree?", showOk, showCancel);
ask
的两个参数值 showOk
和 showCancel
可以被称为 回调函数 或简称 回调。主要思想是我们传递一个函数,并期望在稍后必要时将其“回调”。在我们的例子中,showOk
是回答 “yes” 的回调,showCancel
是回答 “no” 的回调。
区别函数声明:
-
语法
-
函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用。
函数声明则不同。在函数声明被定义之前,它就可以被调用。
-
**严格模式下,当一个函数声明在一个代码块内时,它在该代码块内的任何位置都是可见的。但在代码块外不可见。**要使其在代码块外可见(例如if外),正确的做法是使用函数表达式。
仅当函数声明不适合对应的任务时,才应使用函数表达式。
箭头函数,基础知识
对于一行代码的函数来说,箭头函数是相当方便的。它具体有两种:
- 不带花括号:
(...args) => expression
— 右侧是一个表达式:函数计算表达式并返回其结果。 - 带花括号:
(...args) => { body }
— 花括号允许我们在函数中编写多个语句,但是我们需要显式地return
来返回一些内容。
代码质量
这一章节有很多干货,包括调试、代码风格、注释、自动化测试BDD、transpiler和Polyfill等等,需要的可以再阅读。
Object(对象):基础知识
对象
-
方括号
let user = {}; // 设置 user["likes birds"] = true; // 读取 alert(user["likes birds"]); // true // 删除 delete user["likes birds"]; //变量作属性名 let key = "likes birds"; // 跟 user["likes birds"] = true; 一样 user[key] = true;
-
计算属性
let fruit = 'apple'; let bag = { [fruit + 'Computers']: 5 // bag.appleComputers = 5 };
它们存储属性(键值对),其中:
- 属性的键必须是字符串或者 symbol(通常是字符串)。没有保留字
- 值可以是任何类型。
- 整数属性会被进行排序,“整数属性”指的是一个可以在不做任何更改的情况下与一个整数进行相互转换的字符串。
我们可以用下面的方法访问属性:
- 点符号:
obj.property
。 - 方括号
obj["property"]
,方括号允许从变量中获取键,例如obj[varWithKey]
。
其他操作:
- 删除属性:
delete obj.prop
。 - 检查是否存在给定键的属性:
"key" in obj
。 - 遍历对象:
for(let key in obj)
循环。
对象的引用和复制
对象通过引用被赋值和拷贝。换句话说,一个变量存储的不是“对象的值”,而是一个对值的“引用”(内存地址)。因此,拷贝此类变量或将其作为函数参数传递时,所拷贝的是引用,而不是对象本身。
所有通过被拷贝的引用的操作(如添加、删除属性)都作用在同一个对象上。
为了创建“真正的拷贝”(一个克隆),我们可以使用 Object.assign
来做所谓的“浅拷贝”(嵌套对象被通过引用进行拷贝)
Object.assign(dest, [src1, src2, src3...])
- 第一个参数
dest
是指目标对象。 - 更后面的参数
src1, ..., srcN
(可按需传递多个参数)是源对象。 - 该方法将所有源对象的属性拷贝到目标对象
dest
中。换句话说,从第二个开始的所有参数的属性都被拷贝到第一个参数的对象中。 - 调用结果返回
dest
。
或者使用“深拷贝”函数,例如 _.cloneDeep(obj),对于对象属性中嵌套了对象的情况。
垃圾回收
- 垃圾回收是自动完成的,我们不能强制执行或是阻止执行。
- 当对象是可达状态时,它一定是存在于内存中的。
- 被引用与可访问(从一个根)不同:一组相互连接的对象可能整体都不可达。
对象方法、“this”
this
的值是在程序运行时得到的。
- 一个函数在声明时,可能就使用了
this
,但是这个this
只有在函数被调用时才会有值。 - 可以在对象之间复制函数。
- 以“方法”的语法调用函数时:
object.method()
,调用过程中的this
值是object
。
请注意箭头函数有些特别:它们没有 this
。在箭头函数内部访问到的 this
都是从外部获取的。
注意:
function makeUser() {
return {
name: "John",
ref: this
};
}
let user = makeUser();
alert( user.ref.name ); // 报错
这里 makeUser()
中的 this
的值是 undefined
,因为它是被作为函数调用的,而不是通过点符号被作为方法调用。this
的值是对于整个函数的,代码段和对象字面量对它都没有影响。所以 ref: this
实际上取的是当前函数的 this
,值为undefined
。所以,必须是通过点符号作为方法被调用是,this
返回的才是当前方法所在的对象
构造器和操作符"new"
当一个函数被使用 new
操作符执行时,它按照以下步骤:
- 一个新的空对象被创建并分配给
this
。 - 函数体执行。通常它会修改
this
,为其添加新的属性。 - 返回
this
的值。
如果没有参数,我们可以省略 new
后的括号。
构造器模式测试:new.target
这种方法有时被用在库中以使语法更加灵活:
function User(name) {
if (!new.target) { // 如果你没有通过 new 运行我
return new User(name); // ……我会给你添加 new
}
this.name = name;
}
let john = User("John"); // 将调用重定向到新用户
alert(john.name); // John
构造器的return
如果这有一个 return
语句,那么规则就简单了:
- 如果
return
返回的是一个对象,则返回这个对象,而不是this
。 - 如果
return
返回的是一个原始类型,则忽略。
换句话说,带有对象的 return
返回该对象,在所有其他情况下返回 this
。
通常构造器没有 return
语句。这里我们主要为了完整性而提及返回对象的特殊行为。
JavaScript 为许多内置的对象提供了构造函数:比如日期 Date
、集合 Set
以及其他我们计划学习的内容。
*思考:*是否可以创建像 new A()==new B()
这样的函数 A
和 B
?
可选链"?."
(新特性,就浏览器可能不支持)可选链 ?.
语法有三种形式:
obj?.prop
—— 如果obj
存在则返回obj.prop
,否则返回undefined
。obj?.[prop]
—— 如果obj
存在则返回obj[prop]
,否则返回undefined
。obj.method?.()
—— 如果obj.method
存在则调用obj.method()
,否则返回undefined
。
我们可以使用 ?.
来安全地读取或删除,但不能写入
正如我们所看到的,这些语法形式用起来都很简单直接。?.
检查左边部分是否为 null/undefined
,如果不是则继续运算。?.
链使我们能够安全地访问嵌套属性。
但是,我们应该谨慎地使用 ?.
,仅在当左边部分不存在也没问题的情况下使用为宜。以保证在代码中有编程上的错误出现时,也不会对我们隐藏。
Symbol类型
用的少,温故而知新。
对象 — 原始值转换
对象到原始值的转换,是由许多期望以原始值作为值的内建函数和运算符自动调用的。
这里有三种类型(hint):
"string"
(对于alert
和其他需要字符串的操作)"number"
(对于数学运算)"default"
(少数运算符)
规范明确描述了哪个运算符使用哪个 hint。很少有运算符“不知道期望什么”并使用 "default"
hint。通常对于内建对象,"default"
hint 的处理方式与 "number"
相同,因此在实践中,最后两个 hint 常常合并在一起。
转换算法是:
-
调用
obj[Symbol.toPrimitive](hint)
如果这个方法存在, -
否则,如果 hint 是
"string"
- 尝试
obj.toString()
和obj.valueOf()
,无论哪个存在。
- 尝试
-
否则,如果 hint 是
"number"
或者"default"
- 尝试
obj.valueOf()
和obj.toString()
,无论哪个存在。
- 尝试
在实践中,为了便于进行日志记录或调试,对于所有能够返回一种“可读性好”的对象的表达形式的转换,只实现以 obj.toString()
作为全能转换的方法就够了。
数据类型
原始类型的方法
“对象包装器”对于每种原始类型都是不同的,它们被称为 String
、Number
、Boolean
和 Symbol
。因此,它们提供了不同的方法。
以下是 str.toUpperCase()
中实际发生的情况:
- 字符串
str
是一个原始值。因此,在访问其属性时,会创建一个包含字符串字面值的特殊对象,并且具有有用的方法,例如toUpperCase()
。 - 该方法运行并返回一个新的字符串(由
alert
显示)。 - 特殊对象被销毁,只留下原始值
str
。
所以原始类型可以提供方法,但它们依然是轻量级的。
数字类型
要写有很多零的数字:
- 将
"e"
和 0 的数量附加到数字后。就像:123e6
与123
后面接 6 个 0 相同。 "e"
后面的负数将使数字除以 1 后面接着给定数量的零的数字。例如123e-6
表示0.000123
(123
的百万分之一)。
对于不同的数字系统:
- 可以直接在十六进制(
0x
),八进制(0o
)和二进制(0b
)系统中写入数字。 parseInt(str,base)
将字符串str
解析为在给定的base
数字系统中的整数,2 ≤ base ≤ 36
。num.toString(base)
将数字转换为在给定的base
数字系统中的字符串。
要将 12pt
和 100px
之类的值转换为数字:
- 使用
parseInt/parseFloat
进行“软”转换,它从字符串中读取数字,然后返回在发生 error 前可以读取到的值。(使用加号+
或Number()
的数字转换是严格的。如果一个值不完全是一个数字,就会失败)
小数:
- 使用
Math.floor
,Math.ceil
,Math.trunc
,Math.round
或num.toFixed(precision)
进行舍入。 - 请确保记住使用小数时会损失精度。
测试:isFinite 和 isNaN、Object.is
-
isNaN(value)
将其参数转换为数字,然后测试它是否为NaN
-
isFinite(value)
将其参数转换为数字,如果是常规数字,则返回true
,而不是NaN/Infinity/-Infinity
:let num = +prompt("Enter a number", ''); // 结果会是 true,除非你输入的是 Infinity、-Infinity 或不是数字 alert( isFinite(num) );
请注意,在所有数字函数中,包括
isFinite
,空字符串或仅有空格的字符串均被视为0
。 -
有一个特殊的内建方法 Object.is,它类似于
===
一样对值进行比较,但它对于两种边缘情况更可靠:- 它适用于
NaN
:Object.is(NaN,NaN)=== true
,这是件好事。 - 值
0
和-0
是不同的:Object.is(0,-0)=== false
,从技术上讲这是对的,因为在内部,数字的符号位可能会不同,即使其他所有位均为零。
在所有其他情况下,
Object.is(a,b)
与a === b
相同。这种比较方式经常被用在 JavaScript 规范中。当内部算法需要比较两个值是否完全相同时,它使用
Object.is
(内部称为 SameValue)。 - 它适用于
字符串
反引号的使用:模板字符串
-
有 3 种类型的引号。反引号允许字符串跨越多行并可以使用
${…}
在字符串中嵌入表达式。 -
JavaScript 中的字符串使用的是 UTF-16 编码。
-
我们可以使用像
\n
这样的特殊字符或通过使用\u...
来操作它们的 unicode 进行字符插入。 -
获取字符时,使用
[]
。方括号是获取字符的一种现代化方法,而charAt
是历史原因才存在的。它们之间的唯一区别是,如果没有找到字符,[]
返回undefined
,而charAt
返回一个空字符串 -
获取子字符串,使用
slice
或substring
。方法 选择方式…… 负值参数 slice(start, end)
从 start
到end
(不含end
)允许 substring(start, end)
start
与end
之间(包括start
,但不包括end
)负值代表 0
substr(start, length)
从 start
开始获取长为length
的字符串允许 start
为负数 -
字符串的大/小写转换,使用:
toLowerCase/toUpperCase
。 -
查找子字符串时,使用
indexOf
或includes/startsWith/endsWith
进行简单检查。 -
根据语言比较字符串时使用
localeCompare
,否则将按字符代码进行比较。
还有其他几种有用的字符串方法:
str.trim()
—— 删除字符串前后的空格 (“trims”)。str.repeat(n)
—— 重复字符串n
次。- ……更多内容细节请参见 手册。
数组
数组是一种特殊的对象,适用于存储和管理有序的数据项。
-
声明:
// 方括号 (常见用法) let arr = [item1, item2...]; // new Array (极其少见) let arr = new Array(item1, item2...);
调用
new Array(number)
会创建一个给定长度的数组,但不含有任何项。 -
length
属性是数组的长度,准确地说,它是数组最后一个数字索引值加一。它由数组方法自动调整。 -
如果我们手动缩短
length
,那么数组就会被截断。
我们可以通过下列操作以双端队列的方式使用数组:
push(...items)
在末端添加items
项。pop()
从末端移除并返回该元素。shift()
从首端移除并返回该元素。unshift(...items)
从首端添加items
项。
遍历数组的元素:
for (let i=0; i<arr.length; i++)
— 运行得最快,可兼容旧版本浏览器。for (let item of arr)
— 现代语法,只能访问 items。for (let i in arr)
— 永远不要用这个。
比较数组时,不要使用 ==
运算符(当然也不要使用 >
和 <
等运算符),因为它们不会对数组进行特殊处理。它们通常会像处理任意对象那样处理数组,这通常不是我们想要的。
但是,我们可以使用 for..of
循环来逐项比较数组。
数组的方法
数组方法备忘单:
- 添加/删除元素:
push(...items)
—— 向尾端添加元素,pop()
—— 从尾端提取一个元素,shift()
—— 从首端提取一个元素,unshift(...items)
—— 向首端添加元素,splice(pos, deleteCount, ...items)
—— 从pos
开始删除deleteCount
个元素,并插入items
。slice(start, end)
—— 创建一个新数组,将从索引start
到索引end
(但不包括end
)的元素复制进去。concat(...items)
—— 返回一个新数组:复制当前数组的所有元素,并向其中添加items
。如果items
中的任意一项是一个数组,那么就取其元素。
- 搜索元素:
indexOf/lastIndexOf(item, pos)
—— 从索引pos
开始搜索item
,搜索到则返回该项的索引,否则返回-1
。includes(value)
—— 如果数组有value
,则返回true
,否则返回false
。find/filter(func)
—— 通过func
过滤元素,返回使func
返回true
的第一个值/所有值。findIndex
和find
类似,但返回索引而不是值。
- 遍历元素:
forEach(func)
—— 对每个元素都调用func
,不返回任何内容。
- 转换数组:
map(func)
—— 根据对每个元素调用func
的结果创建一个新数组。sort(func)
—— 对数组进行原位(in-place)排序,然后返回它。reverse()
—— 原位(in-place)反转数组,然后返回它。split/join
—— 将字符串转换为数组并返回。reduce/reduceRight(func, initial)
—— 通过对每个元素调用func
计算数组上的单个值,并在调用之间传递中间结果。
- 其他:
Array.isArray(arr)
检查arr
是否是一个数组。
请注意,sort
,reverse
和 splice
方法修改的是数组本身。
这些是最常用的方法,它们覆盖 99% 的用例。
Iterable object (可迭代对象)
可以应用 for..of
的对象被称为 可迭代的。
-
技术上来说,可迭代对象必须实现
Symbol.iterator
方法。obj[Symbol.iterator]()
的结果被称为 迭代器(iterator)。由它处理进一步的迭代过程。- 一个迭代器必须有
next()
方法,它返回一个{done: Boolean, value: any}
对象,这里done:true
表明迭代结束,否则value
就是下一个值。
-
Symbol.iterator
方法会被for..of
自动调用,但我们也可以直接调用它。 -
内置的可迭代对象例如字符串和数组,都实现了
Symbol.iterator
。 -
字符串迭代器能够识别代理对(surrogate pair)。(译注:代理对也就是 UTF-16 扩展字符。)
可迭代(iterable)和类数组(array-like):
- Iterable 如上所述,是实现了
Symbol.iterator
方法的对象。 - Array-like 是有索引和
length
属性的对象,所以它们看起来很像数组。 Array.from(obj[, mapFn, thisArg])
将可迭代对象或类数组对象obj
转化为真正的数组Array
,然后我们就可以对它应用数组的方法。可选参数mapFn
和thisArg
允许我们将函数应用到每个元素。
/*
可迭代对象
*/
let range = {
from: 1,
to: 5
};
// 1. for..of 调用首先会调用这个:
range[Symbol.iterator] = function() {
// ……它返回迭代器对象(iterator object):
// 2. 接下来,for..of 仅与此迭代器一起工作,要求它提供下一个值
return {
current: this.from,
last: this.to,
// 3. next() 在 for..of 的每一轮循环迭代中被调用
next() {
// 4. 它将会返回 {done:.., value :...} 格式的对象
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};
// 现在它可以运行了!
for (let num of range) {
alert(num); // 1, 然后是 2, 3, 4, 5
}
// 用Array.from()方法转化成数组
let arr = Array.from(range, num => num * num);//(*)
alert(arr); // 1,4,9,16,25
/*
类数组对象
*/
let arrayLike = {
0: "Hello",
1: "World",
length: 2
};
let arr = Array.from(arrayLike); // (*)
//在 (*) 行的 Array.from 方法接受对象,检查它是一个可迭代对象或类数组对象,然后创建一个新数组,并将该对象的所有元素复制到这个新数组。
Map and Set (映射和集合)
Map 是一个带键的数据项的集合,就像一个 Object
一样。 但是它们最大的差别是 Map
允许任何类型的键(key)。
Map
—— 是一个带键的数据项的集合。
方法和属性如下:
-
new Map([iterable])
—— 创建 map,可选择带有[key,value]
对的iterable
(例如数组)来进行初始化。 -
map.set(key, value)
—— 根据键存储值。 -
map.get(key)
—— 根据键来返回值,如果map
中不存在对应的key
,则返回undefined
。 -
map.has(key)
—— 如果key
存在则返回true
,否则返回false
。 -
map.delete(key)
—— 删除指定键的值。 -
map.clear()
—— 清空 map 。 -
map.size
—— 返回当前元素个数。 -
Object.entries:从对象创建 Map
// 键值对 [key, value] 数组 let map = new Map([ ['1', 'str1'], [1, 'num1'], [true, 'bool1'] ]); let obj = { name: "John", age: 30 }; let map = new Map(Object.entries(obj));
-
Object.fromEntries:从 Map 创建对象
let prices = Object.fromEntries([ ['banana', 1], ['orange', 2], ['meat', 4] ]); // 现在 prices = { banana: 1, orange: 2, meat: 4 } let map = new Map(); map.set('banana', 1); map.set('orange', 2); map.set('meat', 4); let obj = Object.fromEntries(map.entries()); // obj = { banana: 1, orange: 2, meat: 4 } // 或是 let obj = Object.fromEntries(map); /*因为 Object.fromEntries 期望得到一个可迭代对象作为参数,而不一定是数组。并且 map 的标准迭代会返回跟 map.entries() 一样的键/值对。*/
与普通对象 Object
的不同点:
- 任何键、对象都可以作为键。
- 迭代的顺序与插入值的顺序相同。与普通的
Object
不同,Map
保留了此顺序。 - 有其他的便捷方法,如
size
属性。
Set
—— 是一组唯一值的集合。
方法和属性:
new Set([iterable])
—— 创建 set,可选择带有iterable
(例如数组)来进行初始化。set.add(value)
—— 添加一个值(如果value
存在则不做任何修改),返回 set 本身。set.delete(value)
—— 删除值,如果value
在这个方法调用的时候存在则返回true
,否则返回false
。set.has(value)
—— 如果value
在 set 中,返回true
,否则返回false
。set.clear()
—— 清空 set。set.size
—— 元素的个数。
优点:
Set
的替代方法可以是一个用户数组,用 arr.find 在每次插入值时检查是否重复。但是这样性能会很差,因为这个方法会遍历整个数组来检查每个元素。Set
内部对唯一性检查进行了更好的优化。
在 Map
和 Set
中迭代总是按照值插入的顺序进行的,所以我们不能说这些集合是无序的,但是我们不能对元素进行重新排序,也不能直接按其编号来获取元素。
WeakMap和WeakSet
WeakMap
和 Map
的第一个不同点就是,WeakMap
的键必须是对象,不能是原始值。不支持迭代以及 keys()
,values()
和 entries()
方法。所以没有办法获取 WeakMap
的所有键或值。只有以下的方法:
weakMap.get(key)
weakMap.set(key, value)
weakMap.delete(key)
weakMap.has(key)
如果我们在 weakMap 中使用一个对象作为键,并且没有其他对这个对象的引用 —— 该对象将会被从内存(和map)中自动清除。
WeakSet
的表现类似:
- 与
Set
类似,但是我们只能向WeakSet
添加对象(而不能是原始值)。 - 对象只有在其它某个(些)地方能被访问的时候,才能留在 set 中。
- 跟
Set
一样,WeakSet
支持add
,has
和delete
方法,但不支持size
和keys()
,并且不可迭代。
WeakMap
和 WeakSet
被用作“主要”对象存储之外的“辅助”数据结构。一旦将对象从主存储器中删除,如果该对象仅被用作 WeakMap
或 WeakSet
的键,那么它将被自动清除。
Object.keys(), Objects.values(), Object.entries()
对于普通对象,下列这些方法是可用的:
- Object.keys(obj) —— 返回一个包含该对象所有的键的数组。
- Object.values(obj) —— 返回一个包含该对象所有的值的数组。
- Object.entries(obj) —— 返回一个包含该对象所有 [key, value] 键值对的数组。
区别:
Map | Object | |
---|---|---|
调用语法 | map.keys() | Object.keys(obj) ,而不是 obj.keys() |
返回值 | 可迭代项 | “真正的”数组,而不只是一个可迭代项。 |
Object.keys/values/entries 会忽略 symbol 属性
(如果我们也想要 Symbol 类型的键,那么这儿有一个单独的方法 Object.getOwnPropertySymbols,它会返回一个只包含 Symbol 类型的键的数组。另外,还有一种方法 Reflect.ownKeys(obj),它会返回 所有 键。)
对象缺少数组存在的许多方法,例如 map
和 filter
等。如果我们想应用它们,那么我们可以使用 Object.entries
,然后使用 Object.fromEntries
:
- 使用
Object.entries(obj)
从obj
获取由键/值对组成的数组。 - 对该数组使用数组方法,例如
map
。 - 对结果数组使用
Object.fromEntries(array)
方法,将结果转回成对象。
解构赋值
直接赋值:
let arr = ["Ilya", "Kantor"]
//1.
let [firstName, surname] = arr;
//2.
let [firstName, surname] = "Ilya Kantor".split(' ');
//3.不需要第二个元素
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
//4.等号右侧可以是任何可迭代对象
let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]);
//5.左侧使用任何“可以被赋值的”东西
let user = {};
[user.name, user.surname] = "Ilya Kantor".split(' ');
//6.与 .entries() 方法进行循环操作,例如循环遍历键—值对
for (let [key, value] of Object.entries(user)) {
alert(`${key}:${value}`); // name:John, then age:30
}
//7.遍历map对象
let user = new Map();
user.set("name", "John");
user.set("age", "30");
for (let [key, value] of user) {
alert(`${key}:${value}`); // name:John, then age:30
}
//8.用于交换变量值的典型技巧
let guest = "Jane";
let admin = "Pete";
[guest, admin] = [admin, guest];
剩余的’…'
let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
// 请注意,`rest` 的类型是数组
alert(rest[0]); // Consul
默认值
未成功赋值的变量被认为是 undefined
,也可以设置默认值:
let [name = "Guest", surname = "Anonymous"] = ["Julius"];
//默认值可以是更加复杂的表达式甚至可以是函数调用,这些表达式或函数只会在这个变量未被赋值的时候才会被计算。
let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"];
对象解构
//变量的顺序并不重要:
let options = {
title: "Menu",
width: 100,
height: 200
};
let {height, width, title} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
//只取所需
let { title } = options;
alert(title); // Menu
// { 什么值(sourceProperty): 赋值给谁(targetVariable) }
let {width: w, height: h, title} = options;
alert(title); // Menu
alert(w); // 100
alert(h); // 200
//默认值
let options = {
title: "Menu"
};
let {width = prompt("width?"), title = prompt("title?")} = options;
let {width: w = 100, height: h = 200, title} = options;
剩余模式 pattern "…"
let options = {
title: "Menu",
height: 200,
width: 100
};
let {title, ...rest} = options;
// 现在 title="Menu", rest={height: 200, width: 100}
alert(rest.height); // 200
alert(rest.width); // 100
不使用 let 时的陷阱:
let title, width, height;
// 这一行发生了错误,因为 JavaScript 把主代码流(即不在其他表达式中)的 {...} 当做一个代码块。
{title, width, height} = {title: "Menu", width: 200, height: 100};
// 现在就可以了,可以把整个赋值表达式用括号 (...) 包起来
({title, width, height} = {title: "Menu", width: 200, height: 100});
嵌套解构
let options = {
size: {
width: 100,
height: 200
},
items: ["Cake", "Donut"],
extra: true
};
// 为了清晰起见,解构赋值语句被写成多行的形式
let {
size: { // 把 size 赋值到这里
width,
height
},
items: [item1, item2], // 把 items 赋值到这里
title = "Menu" // 在对象中不存在(使用默认值)
} = options;
智能函数参数:通过对象传递参数
// 我们传递一个对象给函数
let options = {
title: "My menu",
items: ["Item1", "Item2"]
};
// ……然后函数马上把对象展开成变量
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
// title, items – 提取于 options,
// width, height – 使用默认值
alert( `${title} ${width} ${height}` ); // My Menu 200 100
alert( items ); // Item1, Item2
}
showMenu(options);
日期和时间
创建一个Date对象:
传入的整数参数代表的是自 1970-01-01 00:00:00 以来经过的毫秒数,该整数被称为 时间戳。
/*1.不传参*/
let now = new Date();
/*2.传入时间戳*/
// 0 表示 01.01.1970 UTC+0
let Jan01_1970 = new Date(0);
// 现在增加 24 小时,得到 02.01.1970 UTC+0
let Jan02_1970 = new Date(24 * 3600 * 1000);
/*3.传入字符串*/
let date = new Date("2017-01-26"); // 该时间未被设定,因此被假定为格林尼治标准时间(GMT)的午夜(midnight)
/*4.分别传入年、月、日、时、分、秒、毫秒等*/
new Date(2011, 0, 1, 0, 0, 0, 0); // 1 Jan 2011, 00:00:00
new Date(2011, 0, 1); // 同样,时分秒等均为默认值 0
其中4.new Date(year, month, date, hours, minutes, seconds, ms)
使用当前时区中的给定组件创建日期。只有前两个参数是必须的。
year
必须是四位数:2013
是合法的,98
是不合法的。month
计数从0
(一月)开始,到11
(十二月)结束。date
是当月的具体某一天,如果缺失,则为默认值1
。- 如果
hours/minutes/seconds/ms
缺失,则均为默认值0
。
Date对象方法
- getFullYear():获取年份(4 位数),不要写成getYear
- getMonth():获取月份,从 0 到 11。
- getDate():具体日期,从 1 到 31
- getHours(),getMinutes(),getSeconds(),getMilliseconds():
- getDay():获取一周中的第几天,从
0
(星期日)到6
(星期六) - getTime():返回日期的时间戳
- …
下列方法可以设置日期组件:
- setFullYear(year,[month], [date])
- setMonth(month,[date])
- setDate(date)
- setHours(hour, [min], [sec], [ms])
- setMinutes(min, [sec], [ms])
- setSeconds(sec, [ms])
- setMillieseconds(ms)
- setTime(milliseconds)
let today = new Date();
today.setHours(0);// 日期依然是今天,但是小时数被改为了 0
自动校准 是 Date对象的一个非常方便的特性。我们可以设置超范围的数值,它会自动校准。
let date = new Date(2016, 1, 28); // 28 Feb 2016
date.setDate(date.getDate() + 2); // 1 Mar 2016
/*可以设置0或负值*/
let date = new Date(2016, 0, 2); // 2016 年 1 月 2 日
date.setDate(1); // 设置为当月的第一天
date.setDate(0); // 天数最小可以设置为 1,所以这里设置的是上一月的最后一天
Date.now()
返回当前时间戳而不创建Date对象,它相当于 new Date().getTime()
,但它更快。
计算日期差值时,使用getTime()方法会比直接日期相减进行类型转换快
-
Date.parse(str) 方法可以从一个字符串中读取日期。
字符串的格式应该为:
YYYY-MM-DDTHH:mm:ss.sssZ
,其中:YYYY-MM-DD
—— 日期:年-月-日。- 字符
"T"
是一个分隔符。 HH:mm:ss.sss
—— 时间:小时,分钟,秒,毫秒。- 可选字符
'Z'
为+-hh:mm
格式的时区。单个字符Z
代表 UTC+0 时区。
JSON方法, toJSON
-
JSON.stringify(value[, replacer, space])
将对象转换为 JSON。value
: 要编码的值。replacer
: 要编码的属性数组或映射函数function(key, value)
。space: 用于格式化的空格数量
-
JSON.parse(str, [reviver])
将 JSON 转换回对象。str
: 要解析的 JSON 字符串。reviver
:可选的函数 function(key,value),该函数将为每个(key, value)
对调用,并可以对值进行转换。
let student = {
name: 'John',
age: 30,
isAdmin: false,
courses: ['html', 'css', 'js'],
wife: null
};
let json = JSON.stringify(student);
alert(typeof json); // string
/* JSON 编码的对象:
{
"name": "John",
"age": 30,
"isAdmin": false,
"courses": ["html", "css", "js"],
"wife": null
}
*/
支持嵌套对象转换,并且可以自动对其进行转换。
- JSON 是一种数据格式,具有自己的独立标准和大多数编程语言的库。
- JSON 支持 object,array,string,number,boolean 和
null
。 - JavaScript 提供序列化(serialize)成 JSON 的方法 JSON.stringify 和解析 JSON 的方法 JSON.parse。
- 这两种方法都支持用于智能读/写的转换函数。
- 如果一个对象具有
toJSON
,那么它会被JSON.stringify
调用。
内容总结
以上是互联网集市为您收集整理的现代 JavaScript 教程——学习记录全部内容,希望文章能够帮你解决现代 JavaScript 教程——学习记录所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。