JavaScript
JavaScript(JS)是一种具有函数优先特性的轻量级、解释型或者说即时编译型的编程语言。虽然作为 Web 页面中的脚本语言被人所熟知,但是它也被用到了很多非浏览器环境中,例如 Node.js、Apache CouchDB、Adobe Acrobat 等。进一步说,JavaScript 是一种基于原型、多范式、单线程的动态语言,并且支持面向对象、命令式和声明式(如函数式编程)风格。
JavaScript 的动态特性包括运行时对象的构造、变量参数列表、函数变量、动态脚本创建(通过 eval
)、对象内枚举(通过 for...in
和 Object
工具方法)和源代码恢复(JavaScript 函数会存储其源代码文本,可以使用 toString()
进行检索)。
语法
值、变量
一个变量,就是一个用于存放数值的容器。这个数值可能是一个用于累加计算的数字,或者是一个句子中的字符串。变量的独特之处在于它存放的数值是可以改变的。
声明变量:
要想使用变量,首先需要创建变量(也称为声明变量或者定义变量)。
语法:
let 变量名
声明变量有两部分构成:声明关键字、变量名(标识)
let即关键字(let:允许、许可、让、要),所谓关键字是系统提供专门用来声明(定义)变量的词语。
变量名也叫标识符。
变量赋值
定义了一个变量,就能够初始化它(赋值)。在变量名后面跟上一个“=”,然后是数值。
注意:是通过变量名来获取变量里面的数据。
更新变量
变量赋值后,还可以通过简单地给它一个不同的值来更新它。
let abc = 18
abc = 19
console.log(abc)
// 19
注意:let不允许多次声明一个变量。
声明多个变量
语法:
let age = 18,
uname = '周杰伦'
多个变量中间用逗号隔开。
说明:看上去代码长度更短,单不提倡这样。为了更好的可读性,请一行只声明一个变量。
变量声明的特殊情况
只声明不赋值,结果是undefined,程序也不知道值是什么,所以输出的结果为undefined(未定义的)。
let sex;
console.log(sex);
// sex
不声明,不赋值的变量,直接使用会报错。
console.log(tel);
// tel is not defined
变量的本质
内存:计算机中存储数据的地方,相当于一个空间。
变量本质:是程序再内存中申请的一块用来存放数据的小空间。
变量命名规则与规范
- 规则:必须遵守,不遵守报错
- 不能使用关键字:关键字:有特殊含义的字符,Javascript内置的一些英语词汇。例如:let、var、if、for等;
- 只能用下划线、字母、数字、$组成,且数字不能开头;
- 字母严格区分大小写,如Age和age是不同的变量。
- 规范:建议,不遵守不会报错,但不符合行业内通识
- 起名要有意义;
- 遵守小驼峰命名法:第一个单词首字母小写,后面每个单词首字母大写。例:userName。
数据类型
数据类型可以分为两大类
- 基本数据类型(简单数据类型):数字型(number)、字符串型(string)、布尔型(boolean)、未定义型(undefined)、空类型(null)、BigInt、Symbol
- 引用数据类型(复杂数据类型):对象(object)、数组(array)
数字类型:Javascript中的正数、负数、小数等统一称为数字类型。
注意事项:
- JS是弱数据类型,变量到底属于哪种类型,只有赋值之后,才能确定。
- Java是强数据类型,例如:int a = 3,必须是整数。
数学运算符也叫算术运算符,主要包括加(+)、减(-)、乘(*)、除(/)、取余(%求模、取余数:开发中经常作为某个数字是否被整除)
NaN代表一个计算错误。它是一个不正确的或者一个未定义的数学操作所得到的结果,NaN是粘性的,任何对NaN的操作都会返回NaN。
数字类型
Number 类型是一种基于 IEEE 754 标准的双精度 64 位二进制格式的值。它能够存储 2-1074(Number.MIN_VALUE)和 21024(Number.MAX_VALUE)之间的正浮点数,以及 -2-1074 和 -21024 之间的负浮点数,但是它仅能安全地存储在 -(253 − 1)(Number.MIN_SAFE_INTEGER)到 253 − 1(Number.MAX_SAFE_INTEGER)范围内的整数。超出这个范围,JavaScript 将不能安全地表示整数;相反,它们将由双精度浮点近似表示。你可以使用 Number.isSafeInteger() 检查一个数是否在安全的整数范围内。
±(2-1074 到 21024) 范围之外的值会自动转换:
- 大于
Number.MAX_VALUE
的正值被转换为+Infinity
。 - 小于
Number.MIN_VALUE
的正值被转换为+0
。 - 小于 -
Number.MAX_VALUE
的负值被转换为-Infinity
。 - 大于 -
Number.MIN_VALUE
的负值被转换为-0
。
Number 类型仅有一个具有多个表现形式的值:0
同时表示为 -0
和 +0
(其中 0
是 +0
的别名)。实际上,这两者之间几乎没有区别;例如,+0 === -0
是 true
。然而,当你除以 0 的时候,你要注意到这一点:
console.log(42 / +0); // Infinity
console.log(42 / -0); // -Infinity
字符串类型
通过单引号(’’)、双引号(””)、反引号(``)包裹的数据都叫字符串,单引号和双引号没有本质上的区别,推荐使用单引号。
注意事项:
- 无论单引号或者是双引号必须成对出现。
- 单引号/双引号可以互相嵌套,但是不可以嵌套自己(口诀:外双内单,或者外单内双)。
- 必要时可以使用转义符\,输出单引号或双引号。
字符串拼接:
场景:+运算符可以实现字符串拼接。
口诀:数字相加,字符相连。
模板字符串:
使用场景:拼接字符串和变量。
语法:
``(反引号)
内容拼接变量时,用${}包住变量。
let age = 21
document.write(`我今年${age}岁了`)
布尔类型:
表示肯定或否定在计算机中对应的是布尔类型数据。
他有两个固定的值true和false,表示肯定的数据用true(真),表示否定的数据用false(假)。
let isCool = false
console.log(isCool)
未定义类型:
未定义是比较特殊的类型,只有一个值undefined。
什么情况出现未定义类型?
只声明变量,不赋值的情况下,变量的默认值为undefined,一般很少直接为某个变量赋值为undefined。
工作中的使用场景:
开发中经常声明一个变量,等待传送过来的数据。
如果我们不知道这个数据是否传递过来,此时我们可以通过检测这个变量是不是undefined,就判断用户是否有数据传递过来。
空类型
Javascript中的null仅仅是一个代表“无”、“空”或“值未知”的特殊值
let age = null
console.log(age)
null和undefined区别:
-
undefined表示没有赋值。
-
null表示赋值了,但是内容为空。
null开发中的使用场景:
-
官方解释:把null作为尚未创建的对象。
-
大白话:将来有个变量里面存放的是一个对象,但是对象还没创建好,可以先给个null。
BigInt类型
BigInt
类型在 Javascript 中是一个数字的原始值,它可以表示任意大小的整数。使用 BigInt,你可以安全地存储和操作巨大的整数,甚至超过 Number 的安全整数限制(Number.MAX_SAFE_INTEGER
)。
BigInt 是通过将 n
附加到整数末尾或调用 BigInt()
函数来创建的。
此示例演示了增加 Number.MAX_SAFE_INTEGER
返回预期结果的位置:
// BigInt
const x = BigInt(Number.MAX_SAFE_INTEGER); // 9007199254740991n
x + 1n === x + 2n; // false because 9007199254740992n and 9007199254740993n are unequal
// Number
Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2; // true because both are 9007199254740992
BigInt 值并不总是更精确的,也不总是比 number 精确,因为 BigInt 不能表示分数,但是可以表示更准确的大整数。这两种类型都包含各自的类型,并且它们不能相互替代。如果 BigInt 值在算术表达式中与正则数值混合,或者它们相互隐式转换,则抛出 TypeError
。
Symbol 类型
Symbol
是唯一并且不可变的原始值并且可以用来作为对象属性的键(如下)。在某些程序语言当中,Symbol 也被称作“原子类型”(atom)。symbol 的目的是去创建一个唯一属性键,保证不会与其他代码中的键产生冲突。
类型判断
typeof:
- 返回一个变量的数据类型。
- 可以准确判断基础数据类型,而且判断null、数组和对象结果返回都为object。
instanceof:
- 返回一个布尔值。
- 用于检测构造函数是否处于某个实例对象的原型链上。
- 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型。
Object.prototype.toString.call():
- 返回的结果是一个字符串。
- 可以准确的判断基础数据类型和复杂引用数据类型。
类型转换
隐式转换
某些运算被执行时,系统内部自动将数据进行 转换,这种转换称为隐式转换。
规则:
- +号两边只要有一个是字符串,都会把另一个转成字符串。
- 除了+以外的算术运算符,比如 - * / 都会把数据转成数字类型。
缺点:
- 转换类型不明确,靠经验才能总结。
技巧:
- +号作为正好解析可以转换成数字型。
- 任何数字和字符串相加结果都是字符串。
转换为数字型
Number(数据)
- 转换数字型。
- 如果字符串内容里有非数字,转换失败时结果为NaN(Not a Number)即不是一个数字。
- NaN也是Number类型的数据,代表非数字。
parseInt(数据):只保留整数,可以过滤掉字符,但是字符开头必须是数字。
parseFloat(数据):可以保留小数,可以过滤掉字符,但是字符开头必须是数字。
转换为字符型
String(数据)
变量.toString(进制)
转换为布尔型
-
代表空、否定的值会被转换为 false,如 ‘’、0、NaN、null、undefined
-
其余值都会被转换为true
console.log(Boolean('')); // false
console.log(Boolean(0)); // false
console.log(Boolean(NaN)); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean('小白')); // true
console.log(Boolean(12)); // true
7.运算符
流程控制
JavaScript 提供一套灵活的语句集,特别是控制流语句,你可以用它在你的应用程序中实现大量的交互性功能。
控制流语句包含:
-
if语句
-
三元运算符
-
switch语句
if语句
当一个逻辑条件为真,用 if 语句执行一个语句。当这个条件为假,使用可选择的 else 从句来执行这个语句。
if语句有三种使用:单分支、双分支、多分枝
单分支使用语法:
if (条件) {
满足条件要执行的代码
}
括号内的条件为true时,进入大括号里面执行代码。
小括号内的结果若不是布尔类型时,会发生隐式转换为布尔类型(除了0,所有的数字都为真、除了’’(空字符串),所有的字符串都为真、undefined、NaN、false为假)。
如果大括号只有一个语句,大括号可以省略不写,但是不提倡。
双分支语句使用语法:
if (条件) {
满足条件要执行的代码
} else {
不满足条件要执行的代码
}
多分支语句使用语法:
if (条件1) {
代码1
} else if (条件2) {
代码2
} else if (条件3) {
代码3
} else {
代码n
}
释义:
-
先判断条件1,若满足条件1就执行代码1,其他不执行。
-
若不满足则向下判断条件2,满足条件执行代码2,其他不执行。
-
若依然不满足继续往下判断,依次类推。
-
若以上代码都不满足,执行else里的代码n。
-
注:可以写N个条件。
三元运算符
使用场景:其实是比if双分支更简单的写法,可以使用条件执行的代码
符号:?与:配合使用
语法:
条件?满足条件执行的代码:不满足条件执行的代码
一般用来取值。
switch语句
语法:
switch (数据) {
case 值1:
代码1
break
case 值2:
代码2
break
default:
代码n
break
}
释义:
-
找到跟小括号里面全等的case值,并且执行里面对应的代码。
-
若没有全等(===)则执行default里面的代码。
注意:
-
switch case语句一般用于等值判断,不适合用于区间判断。
-
switch case一般需要配合break关键字使用,没有break会造成case穿透。
运算(表达式、运算符)
表达式
JavaScript 中的基本关键字和常用表达式。
this
:this
关键字指向函数的执行上下文。function
:function
关键字定义了函数表达式。class
:class
关键字定义了类表达式。function*
:function*
关键字定义了一个 generator 函数表达式。yield
:暂停和恢复 generator 函数。yield*
:委派给另外一个 generator 函数或可迭代的对象。async function
:async function
定义一个异步函数表达式。await
:暂停或恢复执行异步函数,并等待 promise 的 resolve/reject 回调。async function*
:async function*
定义了一个异步生成器函数表达式。[]
:数组初始化/字面量语法。{}
:对象初始化/字面量语法。/ab+c/i
:正则表达式字面量语法。( )
:分组操作符。
运算符
赋值运算符
赋值运算符:对变量进行赋值的运算符
= 运算符:将等号右边的值赋予给左边,要求左边必须是一个容器。
其他运算符:+=、-=、*=、/=、%=
使用这些运算符可以对变量赋值时进行快速操作。
num += 1 //num = num + 1
一元运算符
自增:
符号:++
作用:让变量的值+1
自减:
符号:–
作用:让变量的值-1
使用场景:经常用于计数使用。
前置自增:先自加再使用(记忆口诀:++在前 先加)
let num = 1
++num
每执行1次,当前变量数值加1
其作用相当于num+=1
后置自增:先使用再自加(记忆口诀:++在后 后加)
let num = 1
num++
每执行1次,当前变量数值加1
其作用相当于num+=1
比较运算符
比较运算符:
>:左边是否大于右边
<:左边是否小于右边
>=:左边是否大于等于右边
<=:左边是否小于等于右边
==(相等运算符):左右两边值是否相等
!=(不等运算符):左右两边值是否不相等
===(严格相等运算符):左右两边是否类型和值都相等
!== (严格不等运算符) :左右两边是否不全等
比较结果为boolean类型,即只会得到true或false
对比:
= 单等是赋值
== 双等是判断
=== 三等是全等
开发中判断是否相等,强烈推荐使用===
字符串比较,是比较的字符中对应的ASCII码。
- 从左到右依次比较。
- 如果第一位一样再比较第二位,以此类推。
NaNc不等于任何值,包括它本身(涉及到“NaN”的比较,都是false)。
尽量不要比较小数,因为小数有精度问题。
不同类型之间比较会发生隐式转换。
- 最终把数据隐式转换成number类型再比较。
- 所以在开发中,如果进行准确的比较我们更喜欢=或者!。
逻辑运算符
符号 | 名称 | 读法 | 特点 | 口诀 |
---|---|---|---|---|
&& | 逻辑与 | 并且 | 符号两边都为true 结果才为true | 一假则假 |
|| | 逻辑或 | 或者 | 符号两边有一个 true就为true | 一真则真 |
! | 逻辑非 | 取反 | true变false false变true | 真变假,假变真 |
运算符优先级
优先级 | 运算符 | 顺序 |
---|---|---|
1 | 小括号 | () |
2 | 一元运算符 | ++ – ! |
3 | 算数运算符 | 先*/%后± |
4 | 关系运算符 | > >= < <= |
5 | 相等运算符 | == != === !== |
6 | 逻辑运算符 | 先&&后|| |
7 | 赋值运算符 | = |
8 | 逗号运算符 | , |
一元运算符里面的逻辑非优先级很高。
逻辑与比逻辑或优先级高。
逻辑中断
数字为真,两个数字相与,则会输出后面为真的数值。一假则假,要两个数值都判断完的情况下输出数值,因为两个数值都为真,所以会输出后面的数值。
注:逻辑与 找假 只要碰到假,就逻辑中断。
console.log(11 && 22)
数字为真,两个数字相或,则会输出前面为真的数值。一真则真,前面的数值为真的情况下,则不会去判断后面的数值,直接输出前面的数值。
注:逻辑或 找真 只要碰到真,就逻辑中断。
console.log(33 || 44)
注:0 ' ' null undefined NaN false为假。
函数
函数声明
一个函数定义(也称为函数声明,或函数语句)由一系列的function
关键字组成,依次为:
- 函数的名称。
- 函数参数列表,包围在括号中并由逗号分隔。
- 定义函数的 JavaScript 语句,用大括号
{}
括起来。
function 函数名() {
函数体
}
函数的命名规范:
-
function是声明函数的关键字,必须小写。
-
和变量命名基本一致。
-
尽量小驼峰式命名法。
-
前缀应该为动词。
动词 | 含义 |
---|---|
can | 判断是否可以执行某个动作 |
has | 判断是否含义某个值 |
is | 判断是否为某个值 |
get | 获取某个值 |
set | 设置某个值 |
load | 加载某些数据 |
handle | 处理某些数据 |
verify | 验证某些数据 |
函数体:
函数体是函数的构成部分,它负责将相同或相似代码“包裹”起来,直到函数调用体内的代码才会被执行。函数的功能都要写在函数体当中。
函数的调用语法:
// 函数调用,这些函数体内的代码逻辑会被执行
函数名()
函数的封装:
函数的封装是把一个或者多个功能通过函数的方式
封装起来,对外只提供一个简单的函数接口。
函数传参
在声明函数
时,可以在函数名称后面的小括号中添加一些参数
,这些参数被称为形参(形式上的参数)。
在调用函数
时,同样也需要传递相应的参数,这些参数被称为实参
(实际上的参数)。
形参:函数定义时设置接受调用时传入的实际参数值,类似于一个变量。
实参:函数调用时传入小括号内的真实数据。
函数的作用:在函数内部某些值不能固定,我们可以通过参数在调用函数时传递不同的值进去。
语法:
function 函数名(参数列表) {
函数体
}
函数名(传递的参数列表)
</script>
-
调用的时候实参值是传递给形参的。
-
形参简单理解为:不用声明的变量。
-
实参和形参的多个参数之间用逗号(,)分隔。
参数默认值;
形参:可以看作变量,如果一个变量不给值,默认为0。
如果函数内部让两个数相加时,不输入实参,则会出现undefined+ undefined结果为NaN。
说明:这个默认值只会在缺少实参参数传递时才会被执行,所以有参数会优先执行传递的实参,否则默认为undefined。
函数返回值
为什么要让函数有返回值
-
函数执行后得到结果,结果是调用者想要拿到的(函数内部不需要输出结果,而是返回结果)。
-
对执行结果的扩展性更高,可以让其他的程序使用这个结果。
有返回值函数的概念:
-
当调用某个函数,这个函数会返回一个结果出来。
-
这就是有返回值的函数。
当函数需要返回数据出去时,用return关键字。
语法:
return 数据
细节:
-
Ø 在函数体中使用return关键字能将内部的执行结果交给函数外部使用。
-
Ø return后面代码不会再被执行,会立即结束当前函数,所以return后面的数据不要换行写。
-
Ø return函数可以没有return,这种情况函数默认返回值为undefined。
-
Ø return函数返回多个数据时,return函数后面填写数组,数据填写再数组中。
-
Ø 两个相同的函数后面的会覆盖前面的函数。
-
Ø 再JavaScript中,实参的个数和形参的个数可以不一致。
- n 如果新参过多,会自动填上undefined。
- n 如果实参过多,那么多余的实参会被忽略(函数内部有一个arguments,里面装着所有的实参)。
作用域
通常来说,一段程序代码中所使用的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突。
全局作用域:
-
作用于所有代码执行的环境(整个script标签内部)或者一个独立的js文件。
-
处于全局作用域内的变量,称为全局变量,局部变量再任何区域都可以访问和修改。
局部作用域:
-
作用于函数内的代码环境,就是局部作用域。因为跟函数有关系,所有也被称为函数作用域。
-
处于局部作用域内的变量称为局部变量,局部变量只能在当前函数内部访问和修改。
特殊情况:
-
如果函数内部,变量没有声明,直接赋值,也当全局变量,强烈不推荐。
-
函数内部的形参可以看作是局部变量。
-
作用域链:内部函数访问变量,现在自身作用域找声明,如果没有,往外层找,直到找到全局,如果有,采用就近原则。
扩展:
-
全局变量:页面关闭了,浏览器关闭,才会把数据销毁。
-
局部变量:函数调用完之后,就销毁。
匿名函数
函数分为两种:具名函数和匿名函数。
匿名函数:没有名字的函数,无法直接使用。
使用方法:
-
函数表达式
-
立即执行函数
函数表达式:先声明函数表达式,在调用函数。
// 声明
let fn = function () {
console.log('函数表达式')
}
// 调用
fn()
函数表达式和具名函数的不同:
-
具名函数的调用可以写到任何位置(函数声明提升,会把函数提升到最上面)。
-
函数表达式,必须先声明函数表达式,然后再调用。
立即执行函数:
(function () { })();
(function () { }());
作用:防止变量污染。
注意:
-
无需调用,立即执行,其实本质上已经调用了。
-
多个立即执行函数之间用分号隔开。
函数表达式
function
关键字可以用来在一个表达式中定义一个函数。
你也可以使用 Function
构造函数和一个函数声明来定义函数。
let function_expression = function [name]([param1[, param2[, ..., paramN]]]) {
statements
}
回调函数
被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数,称为回调函数。
function greeting(name) {
alert('Hello ' + name);
}
function processUserInput(callback) {
var name = prompt('Please enter your name.');
callback(name);
}
processUserInput(greeting);
以上示例为同步回调,它是立即执行的。
然而需要注意的是,回调函数经常被用于在一个异步操作完成后执行代码,它们被称为异步回调。一个常见的例子是在 promise 末尾添加的 .then
内执行回调函数(在 promise 被兑现或拒绝时执行)。这个结构常用于许多现代的 web API,例如 fetch()
。
数据类型深入
数字方法
字符串方法
数组方法
Iterable object(可迭代对象)
可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for…of 结构中,哪些值可以被遍历到。一些内置类型同时是内置的可迭代对象,并且有默认的迭代行为,比如 Array 或者 Map,而其他内置类型则不是(比如 Object)。
要成为可迭代对象,该对象必须实现 @@iterator 方法,这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iterator 访问该属性:
[Symbol.iterator],一个无参数的函数,其返回值为一个符合迭代器协议的对象。
当一个对象需要被迭代的时候(比如被置入一个 for…of 循环时),首先,会不带参数调用它的 @@iterator 方法,然后使用此方法返回的迭代器获得要迭代的值。
值得注意的是调用此无参数函数时,它将作为对可迭代对象的方法进行调用。因此,在函数内部,this 关键字可用于访问可迭代对象的属性,以决定在迭代过程中提供什么。
此函数可以是普通函数,也可以是生成器函数,以便在调用时返回迭代器对象。在此生成器函数的内部,可以使用 yield 提供每个条目。
String
、Array
、TypedArray
、Map
、Set
以及 Intl.Segments
都是内置的可迭代对象,因为它们的每个 prototype
对象都实现了 @@iterator
方法。此外,arguments 对象和一些 DOM 集合类型,如 NodeList 也是可迭代的。目前,没有内置的异步可迭代对象.
生成器函数返回生成器对象,它们是可迭代的迭代器。异步生成器函数返回异步生成器对象,它们是异步可迭代的迭代器。
从内置迭代返回的迭代器实际上都继承了一个公共类(目前尚未暴露),该类实现了上述 [Symbol.iterator]() { return this; }
方法,使它们都是可迭代的迭代器。将来,除了迭代器协议要求的 next() 方法外,这些内置迭代器可能还有其他辅助方法。你可以通过在图形控制台中记录迭代器的原型链来检查它。
Map and Set(映射和集合)
Map
Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值。
const map1 = new Map();
map1.set('a', 1);
map1.set('b', 2);
map1.set('c', 3);
console.log(map1.get('a'));
// Expected output: 1
map1.set('a', 97);
console.log(map1.get('a'));
// Expected output: 97
console.log(map1.size);
// Expected output: 3
map1.delete('b');
console.log(map1.size);
// Expected output: 2
Map 对象是键值对的集合。Map 中的一个键只能出现一次;它在 Map 的集合中是独一无二的。Map 对象按键值对迭代——一个 for…of 循环在每次迭代后会返回一个形式为 [key,value] 的数组。迭代按插入顺序进行,即键值对按 set() 方法首次插入到集合中的顺序(也就是说,当调用 set() 时,map 中没有具有相同值的键)进行迭代。
Set
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
Set对象是值的集合,你可以按照插入的顺序迭代它的元素。Set 中的元素只会出现一次,即 Set 中的元素是唯一的。
WeakMap and WeakSet(弱映射和弱集合)
WeakMap
WeakMap
对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。
WeakMap 的 key 只能是 Object 类型。 原始数据类型 是不能作为 key 的(比如 Symbol)。
Why WeakMap ?
在 JavaScript 里,map API 可以 通过使其四个 API 方法共用两个数组(一个存放键,一个存放值)来实现。给这种 map 设置值时会同时将键和值添加到这两个数组的末尾。从而使得键和值的索引在两个数组中相对应。当从该 map 取值的时候,需要遍历所有的键,然后使用索引从存储值的数组中检索出相应的值。
但这样的实现会有两个很大的缺点:
- 首先赋值和搜索操作都是 O(n) 的时间复杂度(n 是键值对的个数),因为这两个操作都需要遍历全部整个数组来进行匹配。
- 另外一个缺点是可能会导致内存泄漏,因为数组会一直引用着每个键和值。这种引用使得垃圾回收算法不能回收处理他们,即使没有其他任何引用存在了。
相比之下,原生的 WeakMap 持有的是每个键对象的“弱引用”,这意味着在没有其他引用存在时垃圾回收能正确进行。原生 WeakMap 的结构是特殊且有效的,其用于映射的 key _只有_在其没有被回收时才是有效的。
正由于这样的弱引用,WeakMap 的 key 是不可枚举的(没有方法能给出所有的 key)。如果 key 是可枚举的话,其列表将会受垃圾回收机制的影响,从而得到不确定的结果。因此,如果你想要这种类型对象的 key 值的列表,你应该使用 Map。
WeakSet
WeakSet
对象允许你将弱保持对象存储在一个集合中。
WeakSet 对象是一些对象值的集合。且其与 Set 类似,WeakSet 中的每个对象值都只能出现一次。在 WeakSet 的集合中,所有对象都是唯一的。
它和 Set 对象的主要区别有:
- WeakSet 只能是对象的集合,而不能像 Set 那样,可以是任何类型的任意值。
- WeakSet 持弱引用:集合中对象的引用为弱引用。如果没有其他的对 WeakSet 中对象的引用,那么这些对象会被当成垃圾回收掉。
Object.keys、values、entries
Object.keys()
Object.keys()
方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致。
const object1 = {
a: 'somestring',
b: 42,
c: false
};
console.log(Object.keys(object1));
// Expected output: Array ["a", "b", "c"]
Object.keys()
返回一个所有元素为字符串的数组,其元素来自给定的 object
上面可直接枚举的属性。这些属性的顺序与手动遍历该对象属性时的一致。
Object.values()
Object.values()
方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用 for...in
循环的顺序相同(区别在于 for-in 循环枚举原型链中的属性)。
var obj = { foo: 'bar', baz: 42 };
console.log(Object.values(obj)); // ['bar', 42]
// array like object
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.values(obj)); // ['a', 'b', 'c']
// array like object with random key ordering
// when we use numeric keys, the value returned in a numerical order according to the keys
var an_obj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.values(an_obj)); // ['b', 'c', 'a']
// getFoo is property which isn't enumerable
var my_obj = Object.create({}, { getFoo: { value: function() { return this.foo; } } });
my_obj.foo = 'bar';
console.log(Object.values(my_obj)); // ['bar']
// non-object argument will be coerced to an object
console.log(Object.values('foo')); // ['f', 'o', 'o']
Object.values()
返回一个数组,其元素是在对象上找到的可枚举属性值。属性的顺序与通过手动循环对象的属性值所给出的顺序相同。
Object.entries()
Object.entries()
方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in
循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。
const object1 = {
a: 'somestring',
b: 42
};
for (const [key, value] of Object.entries(object1)) {
console.log(`${key}: ${value}`);
}
// Expected output:
// "a: somestring"
// "b: 42"
解构赋值
解构赋值语法是一种 Javascript 表达式。可以将数组中的值或对象的属性取出,赋值给其他变量。
let a, b, rest;
[a, b] = [10, 20];
console.log(a);
// Expected output: 10
console.log(b);
// Expected output: 20
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(rest);
// Expected output: Array [30, 40, 50]
语法:
const [a, b] = array;
const [a, , b] = array;
const [a = aDefault, b] = array;
const [a, b, ...rest] = array;
const [a, , b, ...rest] = array;
const [a, b, ...{ pop, push }] = array;
const [a, b, ...[c, d]] = array;
const { a, b } = obj;
const { a: a1, b: b1 } = obj;
const { a: a1 = aDefault, b = bDefault } = obj;
const { a, b, ...rest } = obj;
const { a: a1, b: b1, ...rest } = obj;
const { [key]: a } = obj;
let a, b, a1, b1, c, d, rest, pop, push;
[a, b] = array;
[a, , b] = array;
[a = aDefault, b] = array;
[a, b, ...rest] = array;
[a, , b, ...rest] = array;
[a, b, ...{ pop, push }] = array;
[a, b, ...[c, d]] = array;
({ a, b } = obj); // brackets are required
({ a: a1, b: b1 } = obj);
({ a: a1 = aDefault, b = bDefault } = obj);
({ a, b, ...rest } = obj);
({ a: a1, b: b1, ...rest } = obj);
对象和数组字面量表达式提供了一种简单的方法来创建特别的数据包。
解构赋值使用类似的语法,但在赋值的左侧定义了要从原变量中取出哪些值。
const x = [1, 2, 3, 4, 5];
const [y, z] = x;
console.log(y); // 1
console.log(z); // 2
同样,你可以在赋值语句的左侧解构对象。
const obj = { a: 1, b: 2 };
const { a, b } = obj;
// is equivalent to:
// const a = obj.a;
// const b = obj.b;
绑定与赋值
对于对象和数组的解构,有两种解构模式:绑定模式和赋值模式,它们的语法略有不同。
在绑定模式中,模式以声明关键字(var
、let
或 const
)开始。然后,每个单独的属性必须绑定到一个变量或进一步解构。
const obj = { a: 1, b: { c: 2 } };
const { a, b: { c: d } } = obj;
// Two variables are bound: `a` and `d`
所有变量共享相同的声明,因此,如果你希望某些变量可重新分配,而其他变量是只读的,则可能需要解构两次——一次使用 let
,一次使用 const
。
const obj = { a: 1, b: { c: 2 } };
const { a } = obj; // a is constant
let { b: { c: d } } = obj; // d is re-assignable
在赋值模式中,模式不以关键字开头。每个解构属性都被赋值给一个赋值目标——这个赋值目标可以事先用 var
或 let
声明,也可以是另一个对象的属性——一般来说,可以是任何可以出现在赋值表达式左侧的东西。
const numbers = [];
const obj = { a: 1, b: 2 };
({ a: numbers[0], b: numbers[1] } = obj);
// The properties `a` and `b` are assigned to properties of `numbers`
默认值
每个解构属性都可以有一个默认值。当属性不存在或值为 undefined
时,将使用默认值。如果属性的值为 null
,则不使用它。
const [a = 1] = []; // a is 1
const { b = 2 } = { b: undefined }; // b is 2
const { c = 2 } = { c: null }; // c is null
默认值可以是任何表达式。仅在必要时对其进行评估。
const { b = console.log("hey") } = { b: 2 };
// Does not log anything, because `b` is defined and there's no need
// to evaluate the default value.
剩余属性
你可以使用剩余属性(...rest
)结束解构模式。此模式会将对象或数组的所有剩余属性存储到新的对象或数组中。
const { a, ...others } = { a: 1, b: 2, c: 3 };
console.log(others); // { b: 2, c: 3 }
const [first, ...others2] = [1, 2, 3];
console.log(others2); // [2, 3]
剩余属性必须是模式中的最后一个,并且不能有尾随逗号。
解构数组
基本赋值
const foo = ['one', 'two', 'three'];
const [red, yellow, green] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // "three"
解构比源更多的元素
在从赋值语句右侧指定的长度为 N 的数组解构的数组中,如果赋值语句左侧指定的变量数量大于 N,则只有前 N 个变量被赋值。其余变量的值将是未定义。
const foo = ['one', 'two'];
const [red, yellow, green, blue] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // undefined
console.log(blue); //undefined
交换变量
可以在一个解构表达式中交换两个变量的值。
没有解构赋值的情况下,交换两个变量需要一个临时变量(或者用低级语言中的异或交换技巧)。
let a = 1;
let b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1
const arr = [1, 2, 3];
[arr[2], arr[1]] = [arr[1], arr[2]];
console.log(arr); // [1, 3, 2]
解析一个从函数返回的数组
从一个函数返回一个数组是十分常见的情况。解构使得处理返回值为数组时更加方便。
在下面例子中,要让 f()
返回值 [1, 2]
作为其输出,可以使用解构在一行内完成解析。
function f() {
return [1, 2];
}
const [a, b] = f();
console.log(a); // 1
console.log(b); // 2
忽略某些返回值
你可以忽略你不感兴趣的返回值:
function f() {
return [1, 2, 3];
}
const [a, , b] = f();
console.log(a); // 1
console.log(b); // 3
const [c] = f();
console.log(c); // 1
你也可以忽略全部返回值:
[,,] = f();
使用绑定模式作为剩余属性
数组解构赋值的剩余属性可以是另一个数组或对象绑定模式。这允许你同时提取数组的属性和索引。
const [a, b, ...{ pop, push }] = [1, 2];
console.log(a, b); // 1 2
console.log(pop, push); // [Function pop] [Function push]
const [a, b, ...[c, d]] = [1, 2, 3, 4];
console.log(a, b, c, d); // 1 2 3 4
这些绑定模式甚至可以嵌套,只要每个剩余属性都在列表的最后。
解构对象
基本赋值
const user = {
id: 42,
isVerified: true,
};
const { id, isVerified } = user;
console.log(id); // 42
console.log(isVerified); // true
赋值给新的变量名
可以从对象中提取属性,并将其赋值给名称与对象属性不同的变量。
const o = { p: 42, q: true };
const { p: foo, q: bar } = o;
console.log(foo); // 42
console.log(bar); // true
举个例子,const { p: foo } = o
从对象 o
中获取名为 p
的属性,并将其赋值给名为 foo
的局部变量。
赋值到新的变量名并提供默认值
一个属性可以同时是两者:
- 从对象提取并分配给具有不同名称的变量。
- 指定一个默认值,以防获取的值为
undefined
。
const { a: aa = 10, b: bb = 5 } = { a: 3 };
console.log(aa); // 3
console.log(bb); // 5
从作为函数参数传递的对象中提取属性
传递给函数参数的对象也可以提取到变量中,然后可以在函数体内访问这些变量。至于对象赋值,解构语法允许新变量具有与原始属性相同或不同的名称,并为原始对象未定义属性的情况分配默认值。
请考虑此对象,其中包含有关用户的信息。
const user = {
id: 42,
displayName: 'jdoe',
fullName: {
firstName: 'Jane',
lastName: 'Doe',
},
};
在这里,我们展示了如何将传递对象的属性提取到具有相同名称的变量。参数值 { id }
表示传递给函数的对象的 id
属性应该被提取到一个同名变量中,然后可以在函数中使用。
function userId({ id }) {
return id;
}
console.log(userId(user)); // 42
你可以定义提取变量的名称。在这里,我们提取名为 displayName
的属性,并将其重命名为 dname
,以便在函数体内使用。
function userDisplayName({ displayName: dname }) {
return dname;
}
console.log(userDisplayName(user)); // `jdoe`
嵌套对象也可以提取。下面的示例展示了属性 fullname.firstName
被提取到名为 name
的变量中。
function whois({ displayName, fullName: { firstName: name } }) {
return `${displayName} is ${name}`;
}
console.log(whois(user)); // "jdoe is Jane"
设置函数参数的默认值
默认值可以使用 = 指定,如果指定的属性在传递的对象中不存在,则将其用作变量值。
下面我们展示了一个默认大小为 big的函数,默认坐标为 x: 0, y: 0,默认半径为 25。
function drawChart({ size = 'big', coords = { x: 0, y: 0 }, radius = 25 } = {}) {
console.log(size, coords, radius);
// do some chart drawing
}
drawChart({
coords: { x: 18, y: 30 },
radius: 30,
});
在上面 drawChart
的函数签名中,解构的左侧具有空对象 = {}
的默认值。
你也可以在没有该默认值的情况下编写该函数。但是,如果你省略该默认值,该函数将在调用时寻找至少一个参数来提供,而在当前形式下,你可以在不提供任何参数的情况下调用 drawChart()
。否则,你至少需要提供一个空对象字面量。
解构嵌套对象和数组
const metadata = {
title: 'Scratchpad',
translations: [
{
locale: 'de',
localization_tags: [],
last_edit: '2014-04-14T08:43:37',
url: '/de/docs/Tools/Scratchpad',
title: 'JavaScript-Umgebung'
}
],
url: '/en-US/docs/Tools/Scratchpad'
};
let {
title: englishTitle, // rename
translations: [
{
title: localeTitle, // rename
},
],
} = metadata;
console.log(englishTitle); // "Scratchpad"
console.log(localeTitle); // "JavaScript-Umgebung"
For of 迭代和解构
const people = [
{
name: 'Mike Smith',
family: {
mother: 'Jane Smith',
father: 'Harry Smith',
sister: 'Samantha Smith',
},
age: 35,
},
{
name: 'Tom Jones',
family: {
mother: 'Norah Jones',
father: 'Richard Jones',
brother: 'Howard Jones',
},
age: 25,
}
];
for (const { name: n, family: { father: f } } of people) {
console.log(`Name: ${n}, Father: ${f}`);
}
// "Name: Mike Smith, Father: Harry Smith"
// "Name: Tom Jones, Father: Richard Jones"
对象属性计算名和解构
计算属性名,如对象字面量,可以被解构。
const key = 'z';
const { [key]: foo } = { z: 'bar' };
console.log(foo); // "bar"
日期和时间
JSON序列化、反序列化
JSON
对象包含两个方法:用于解析 JavaScript Object Notation(JSON)的 parse()
方法,以及将对象/值转换为 JSON 字符串的 stringify()
方法。除了这两个方法,JSON 这个对象本身并没有其他作用,也不能被调用或者作为构造函数调用。
JSON 是一种语法,用来序列化对象、数组、数值、字符串、布尔值和 null
。它基于 JavaScript 语法,但与之不同:JavaScript 不是 JSON,JSON 也不是 JavaScript。
JSON = null
or true or false
or JSONNumber
or JSONString
or JSONObject
or JSONArray
JSONNumber = - PositiveNumber
or PositiveNumber
PositiveNumber = DecimalNumber
or DecimalNumber . Digits
or DecimalNumber . Digits ExponentPart
or DecimalNumber ExponentPart
DecimalNumber = 0
or OneToNine Digits
ExponentPart = e Exponent
or E Exponent
Exponent = Digits
or + Digits
or - Digits
Digits = Digit
or Digits Digit
Digit = 0 through 9
OneToNine = 1 through 9
JSONString = ""
or " StringCharacters "
StringCharacters = StringCharacter
or StringCharacters StringCharacter
StringCharacter = any character
except " or \ or U+0000 through U+001F
or EscapeSequence
EscapeSequence = \" or \/ or \\ or \b or \f or \n or \r or \t
or \u HexDigit HexDigit HexDigit HexDigit
HexDigit = 0 through 9
or A through F
or a through f
JSONObject = { }
or { Members }
Members = JSONString : JSON
or Members , JSONString : JSON
JSONArray = [ ]
or [ ArrayElements ]
ArrayElements = JSON
or ArrayElements , JSON
JSON.parse()
JSON.parse()
方法用来解析 JSON 字符串,构造由字符串描述的 JavaScript 值或对象。提供可选的 reviver 函数用以在返回之前对所得到的对象执行变换 (操作)。
const json = '{"result":true, "count":42}';
const obj = JSON.parse(json);
console.log(obj.count);
// Expected output: 42
console.log(obj.result);
// Expected output: true
JSON.stringify()
JSON.stringify()
方法将一个 JavaScript 对象或值转换为 JSON 字符串,如果指定了一个 replacer 函数,则可以选择性地替换值,或者指定的 replacer 是数组,则可选择性地仅包含数组指定的属性。
console.log(JSON.stringify({ x: 5, y: 6 }));
// Expected output: "{"x":5,"y":6}"
console.log(JSON.stringify([new Number(3), new String('false'), new Boolean(false)]));
// Expected output: "[3,"false",false]"
console.log(JSON.stringify({ x: [10, undefined, function(){}, Symbol('')] }));
// Expected output: "{"x":[10,null,null,null]}"
console.log(JSON.stringify(new Date(2006, 0, 2, 15, 4, 5)));
// Expected output: ""2006-01-02T15:04:05.000Z""
对象
函数
原型
原型对象
-
构造函数通过原型分配的函数是所有对象所
共享的
。 -
JavaScript 规定,
每一个构造函数都有一个prototype属性
,指向另一个对象,所以我们也称为原型对象。 -
这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存。
-
我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。
-
构造函数和原型对象中的this 都指向实例化的对象。
function Star(uname, age) {
this.uname = uname
this.age = age
}
console.log(Star.prototype) //返回一个对象成为原型对象
Star.prototype.sing = function () {
console.log('我会唱歌')
}
let ldh = new Star('刘德华', 18)
let zxy = new Star('张学友', 18)
console.log(ldh.sing === zxy.sing) //结果是 true 说明俩函数一样,共享
//构造函数this指向
let that
function Star(uname) {
this.uname = uname
that = this
}
const o = new Star()
console.log(that === o) // true
//原型对象this指向
let that
function Star(uname) {
this.uname = uname
}
Star.prototype.sing = function () {
that = this
}
const o = new Star()
o.sing()
console.log(that === o) // true
constructor属性
每个原型对象里面都有个constructor属性(constructor构造函数)。
作用:该属性指向
该原型对象的构造函数, 简单理解,就是指向我的爸爸,我是有爸爸的孩子
。
**使用场景: **
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值。
但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象constructor就不再指向当前构造函数了。
此时,我们可以在修改后的原型对象中,添加一个constructor指向原来的构造函数。
// 手动修改前
function Star(name) {
this.uname = uname
}
Star.prototype = {
sing: function () {
console.log('唱歌')
},
}
console.log(Star.prototype.constructor) // 指向 Object
// 手动修改后
function Star(name) {
this.uname = uname
}
Star.prototype = {
constructor: Star,
sing: function () {
console.log('唱歌')
},
}
console.log(Star.prototype.constructor) // 指向 Star
对象原型
对象都会有一个属性__proto__
指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__
原型的存在。
注意:
-
__proto__
是JS非标准属性。 -
[[prototype]]和
__proto__
意义相同。 -
用来表明当前实例对象指向哪个原型对象prototype。
-
__proto__
对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数
。
对象都会有一个属性 __proto__
指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__
原型的存在。
对象都会有一个属性 __proto__.constructor
指向建该实例对象的构造函数。
原型链
原型链:基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构。
查找规则:
- 当访问一个对象的属性(方法)时,首先查找这个
对象自身
有没有该属性。 - 如果没有就查找它的原型(也就是
__proto__
指向的prototype原型对象
)。 - 如果还没有就查找原型对象的原型(
Object的原型对象
)。 - 依此类推一直找到 Object 为止(
null
)。 __proto__
对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。- 可以使用instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。
类
类是用于创建对象的模板。他们用代码封装数据以处理该数据。JS 中的类建立在原型上,但也具有某些语法和语义未与 ES5 类相似语义共享。
定义类
实际上,类是“特殊的函数”,就像你能够定义的函数表达式和函数声明一样,类语法有两个组成部分:类表达式和类声明。
类声明
定义类的一种方法是使用类声明。要声明一个类,你可以使用带有class
关键字的类名(这里是“Rectangle”)。
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
函数声明和类声明之间的一个重要区别在于,函数声明会提升,类声明不会。你首先需要声明你的类,然后再访问它,否则类似以下的代码将抛出ReferenceError
:
let p = new Rectangle(); // ReferenceError
class Rectangle {}
类表达式
类表达式是定义类的另一种方法。类表达式可以命名或不命名。命名类表达式的名称是该类体的局部名称。(不过,可以通过类的 (而不是一个实例的) name
属性来检索它)。
// 未命名/匿名类
let Rectangle = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Rectangle.name);
// output: "Rectangle"
// 命名类
let Rectangle = class Rectangle2 {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Rectangle.name);
// 输出:"Rectangle2"
类体和方法定义
一个类的类体是一对花括号/大括号 {} 中的部分。这是你定义类成员的位置,如方法或构造函数。
严格模式
类声明和类表达式的主体都执行在严格模式下。比如,构造函数,静态方法,原型方法,getter 和 setter 都在严格模式下执行。
构造函数
constructor方法是一个特殊的方法,这种方法用于创建和初始化一个由 class
创建的对象。一个类只能拥有一个名为“constructor”的特殊方法。如果类包含多个 constructor的方法
,则将抛出 一个SyntaxError 。
一个构造函数可以使用 super
关键字来调用一个父类的构造函数。
原型方法
参见方法定义。
class Rectangle {
// constructor
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea()
}
// Method
calcArea() {
return this.height * this.width;
}
}
const square = new Rectangle(10, 10);
console.log(square.area);
// 100
静态方法
static
关键字用来定义一个类的一个静态方法。调用静态方法不需要实例化 (en-US) 该类,但不能通过一个类实例调用静态方法。静态方法通常用于为一个应用程序创建工具函数。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static displayName = "Point";
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
}
}
const p1 = new Point(5, 5);
const p2 = new Point(10,10);
p1.displayName;
// undefined
p1.distance;
// undefined
console.log(Point.displayName);
// "Point"
console.log(Point.distance(p1, p2));
// 7.0710678118654755
用原型和静态方法绑定 this
当调用静态或原型方法时没有指定 this 的值,那么方法内的 this 值将被置为 undefined
。即使你未设置 "use strict"
,因为 class
体内部的代码总是在严格模式下执行。
class Animal {
speak() {
return this;
}
static eat() {
return this;
}
}
let obj = new Animal();
obj.speak(); // Animal {}
let speak = obj.speak;
speak(); // undefined
Animal.eat() // class Animal
let eat = Animal.eat;
eat(); // undefined
如果上述代码通过传统的基于函数的语法来实现,那么依据初始的 this 值,在非严格模式下方法调用会发生自动装箱。若初始值是 undefined
,this 值会被设为全局对象。
严格模式下不会发生自动装箱,this 值将保留传入状态。
function Animal() { }
Animal.prototype.speak = function() {
return this;
}
Animal.eat = function() {
return this;
}
let obj = new Animal();
let speak = obj.speak;
speak(); // global object
let eat = Animal.eat;
eat(); // global object
实例属性
实例的属性必须定义在类的方法里:
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
静态的或原型的数据属性必须定义在类定义的外面。
Rectangle.staticWidth = 20;
Rectangle.prototype.prototypeWidth = 25;
公有字段声明
使用 JavaScript 字段声明语法,上面的示例可以写成:
class Rectangle {
height = 0;
width;
constructor(height, width) {
this.height = height;
this.width = width;
}
}
通过预先声明字段,类定义变得更加自我记录,并且字段始终存在。
在声明字段时,我们不需要像 let
、const
和 var
这样的关键字。
正如上面看到的,这个字段可以用也可以不用默认值来声明。
私有字段声明
使用私有字段,可以按以下方式细化定义。
class Rectangle {
#height = 0;
#width;
constructor(height, width) {
this.#height = height;
this.#width = width;
}
}
从类外部引用私有字段是错误的。它们只能在类里面中读取或写入。通过定义在类外部不可见的内容,可以确保类的用户不会依赖于内部,因为内部可能在不同版本之间发生变化。
备注: 私有字段仅能在字段声明中预先定义。
私有字段不能通过在之后赋值来创建它们,这种方式只适用普通属性。
extends
extends
关键字用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。
extends
关键字用来创建一个普通类或者内建对象的子类,子类可以继承父类的一些属性和方法。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name) {
super(name); // 调用超类构造函数并传入 name 参数
}
speak() {
console.log(`${this.name} barks.`);
}
}
var d = new Dog('Mitzie');
d.speak();// 'Mitzie barks.'
super
super
关键字用于调用对象的父对象上的函数。
class Cat {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Lion extends Cat {
speak() {
super.speak();
console.log(this.name + ' roars.');
}
}
Mix-ins / 混入
抽象子类或者 mix-ins 是类的模板。一个 ECMAScript 类只能有一个单超类,所以想要从工具类来多重继承的行为是不可能的。子类继承的只能是父类提供的功能性。因此,例如,从工具类的多重继承是不可能的。该功能必须由超类提供。
一个以超类作为输入的函数和一个继承该超类的子类作为输出可以用于在 ECMAScript 中实现混合:
var calculatorMixin = Base => class extends Base {
calc() { }
};
var randomizerMixin = Base => class extends Base {
randomize() { }
};
使用 mix-ins 的类可以像下面这样写:
class Foo { }
class Bar extends calculatorMixin(randomizerMixin(Foo)) { }
异步流程控制
模块化
JavaScript 程序本来很小——在早期,它们大多被用来执行独立的脚本任务,在你的 web 页面需要的地方提供一定交互,所以一般不需要多大的脚本。过了几年,我们现在有了运行大量 JavaScript 脚本的复杂程序,还有一些被用在其他环境(例如 Node.js)。
因此,近年来,有必要开始考虑提供一种将 JavaScript 程序拆分为可按需导入的单独模块的机制。Node.js 已经提供这个能力很长时间了,还有很多的 JavaScript 库和框架已经开始了模块的使用(例如,CommonJS 和基于 AMD 的其他模块系统 如 RequireJS,以及最新的 Webpack 和 Babel)。
好消息是,最新的浏览器开始原生支持模块功能了,这是本文要重点讲述的。这会是一个好事情 —- 浏览器能够最优化加载模块,使它比使用库更有效率:使用库通常需要做额外的客户端处理。
模块功能主要由两个命令构成:export
和import
。export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
或者
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
import { firstName, lastName, year } from 'aa.js'
export default 命令
===> 为模块指定默认输出
// export-default.js
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'