一. JavaScript
1.1 <script>元素
-
async: 可选。表示立即开始下载脚本,但不能阻止其他页面动作,比如下载资源或等待其他脚本加载。只对外部脚本文件有效。
-
charset:可选。使用scr属性指定代码字符集。很少使用因为多数浏览器不在乎它的值。
-
crossorigin:可选。配置相关请求的CORS( 跨源资源共享 )设置。默认不使用CORS。
-
defer:可选。表示脚本延迟到界面完全解析和显示后再执行。仅对外部脚本有效。
-
integrity:可选。用于确保内容分发网络不会提供恶意内容,但并不是所有浏览器都支持。
-
src:可选。表示要执行的代码的外部文件。
-
language:废弃。
-
type:可选。代替language,表示代码块中脚本语言的内容类型。
ps:外部JavaScript文件的拓展名可以不为.js,但要确保服务器能返回正确的MIME类型。
1.2 标签位置
现代web应用程序通常将所有Javascript引用放在<body>元素中的页面内容后面,如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 页面内容 -->
...
···
<script src="./myjs.js"></script>
</body>
</html>
页面会在处理JavaScript代码之前完全渲染页面,提高用户体验;
1.3 推迟执行脚本
HTML 4.01为<script>元素定义了defer属性。表示脚本在执行的时候不会改变页面结构,相当于立即下载,但延迟执行:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
...
<script defer src="./myjs.js"></script>
<script defer src="./yourjs.js"></script>
...
<title>Document</title>
</head>
<body>
<!-- 页面内容 -->
</body>
</html>
1.4 异步执行脚本
HTML 5为<script>元素定义了async属性。从改变脚本处理方式上看,async属性与defer类似。区别于async不保证能按照他们出现的顺序执行。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
...
<script async src="./myjs.js"></script>
<script async src="./yourjs.js"></script>
...
<title>Document</title>
</head>
<body>
<!-- 页面内容 -->
</body>
</html>
在上面这个例子中,脚本二可能先于脚本一执行,因此异步脚本不应该在加载期间修改DOM。异步脚本会保证在野蛮load事件前执行,但可能会在DOMContentLoaded(17章)之前或之后。不推荐使用
1.5 动态加载脚本
除了<script>标签以外,还可以通过DOM总动态添加script元素同样可以加载指定的脚本。
let script = document.createElement('script')
script.src = 'myjs.js'
document.head.appendChild(script)
ps:let作为es6新增命令,用法类似var用来声明变量,不同于var是全局变量,let只在当前代码块有效。详细介绍移步2.3 变量。
以上这种方式是以异步方式加载,相当于添加了async属性。同时因为所有浏览器都支持createElement()方法,但不是所有浏览器都支持async属性,因此存在一些问题,需要明确设置为同步加载:
let script = document.createElement('script')
script.src = 'myjs.js'
script.async = false
document.head.appendChild(script)
但以上这种方式获取的资源获取的资源对浏览器的预加载器来说是不可见的。会严重影响性能,可通过在文档头部显示声明他们:
<link rel = "preload" href = "myjs.js">
1.6 行内代码与外部文件
推荐使用外部文件:
-
可维护性:一个文件保存所有JS文件更容易维护。
-
缓存。两个文件共同使用同一个JS文件仅会下载一次,页面加载更快。
-
适应未来。
二.语言基础
2.1 语言基础
ECMAScript借鉴于C语言和其他C类语言,如Java、Perl。
2.1.1 区分大小写
ECMAScript中一切都区分大小写,包括变量、函数名、操作符。比如typeof作为函数名并不能用来当做函数名,但Typeof可以。
2.1.2 标识符
变量、函数、属性、或函数参数的名称。第一个字符必须是字母、下划线 ' _ ' 或美元符号 ' $ ' ;剩下字符可以包含数字。
按照惯例ECMAScript标识符使用驼峰命名法。
ps:关键字、保留字、true、false、null不能作为标识符;
2.1.3 注释
ECMAScript注释采用C语言风格注释: // 单行注释
/* 多行注释 */
2.1.4 严格模式
ECMAScript 5 增加了严格模式( strict mode ),这种模式下会处理ECMAScript 3的一些不符合规范的写法,对不安全的活动将会抛出错误。启用该模式需在脚本开头添加:
"use strict"
也可以单独为一段函数指定严格模式:
function myjs() {
"use strict"
// 函数体
}
所有现代浏览器都支持严格模式。
2.1.5 语句
ECMAScript 中的语句以分号结尾。省略分号意味着由解释器决定语句在哪里结尾。推荐使用分号,即使语句末尾的分号不是必须的,这样可以避免输入内容不完整,也便于开发者通过删除空行来压缩代码,同时也助于在某些情况下提升性能,因为浏览器会尝试在合适的位置补上分号以纠正语法错误。
let sum = a + b //没有分号也有效,但不推荐。
let diff = a - b //有分号,推荐。
多条语句可以合并到C语言风格的代码块中。代码块由一个左花括号 ( { ) 标识开始,一个右花括号 ( } ) 标识结束:
if (test) {
test = false;
console.log(test);
}
if 之类的控制语句只在执行多条语句时要求必须有代码块。不过,推荐始终使用代码块,即使要执行的语句只有一条:
if (test)
console.log(test);
// 有效,但容易导致错误,不推荐。
if (test) {
console.log(test);
}
// 有效,推荐
使用代码块可以让文字更清晰,更方便后续修改,减少出错的可能。
2.2 关键字与保留字
ECMA-262 描述了一组保留的关键字,不能用作标识符或属性名。其中也描述了一组保留字同样不能用作标识符或属性名,用来在以后作为关键字使用。
2.3 变量
ECMAScript 变量是松散类型的,意思是可以用于保存任何类型的数据。声明变量的关键字:var 、let、const。其中var在 ECMAScript 的所有版本中都可以使用,let 和 const 只能在 ECMAScript 6 及更晚版本中使用。
2.3.1 var 关键字
用于定义变量,后跟变量名:
var myName;
该行代码定义了名为myName的变量,可以保存任意类型的值 ( 在不进行初始化的情况下,变量默认保存一个特殊值 undefined ) ECMAScript 实现变量初始化,因此可以同时定义变量并设置它的值:
var myName = '张三';
在这里,myName被定义为保存字符串 ' 张三 ' 的变量,但并不会将myName改变为字符串类型。随后不仅可以改变保存的值,也可以改变值的类型:
var myName = '张三';
myName = 2022; //合法,但不推荐
在这个例子中变量先被定义为保存字符串的变量,但随后又被重写为保存数值的变量。虽然不推荐改变变量保存值的类型,但在 ECMAScript 中这是有效的。
1.var 声明作用域
使用var定义的变量会成为包含它的函数的局部变量。例如,在一个函数内定义一个变量,就意味着该变量在函数退出时被销毁:
function new () {
var myVar = 'hi'; // 局部变量
}
console.log(myVar); // 出错!
这里变量声明在函数 new 之中,因此在函数外调用 myVar 变量会报错,不过在函数内定义变量时省略var,可以创建一个全局变量:
function new () {
myVar = 'hi'; // 局部变量
}
new();
console.log(myVar); // 'hi'
去掉之前的var之后,myVar就变成了全局变量。只要调用一次new( )函数,就会定义这个变量,并可以在函数外部访问到。
ps:虽然可以通过省略var来声明全局变量,但不推荐。在局部作用域中声明全局变量很难维护,也会造成困惑。在严格模式下会导致 ReferenceError ( 没有定义 )
如需声明多个变量,可在一条语句中用逗号分隔两个变量 ( 及可选的初始化 ):
var myName = '张三',
age = 18,
address = '中国';
这里定义并初始化三个变量。因为 ECMAScript 是松散类型的,因此可以使用不同数据类型的变量在一条语句中声明。
2.var 声明提升
使用var时,下面代码不会报错。这是因为使用这个关键字声明的变量会自动提升到函数作用域顶部:
function new() {
console.log(age);
var age = 18;
}
new(); // undefined
// 之所以不会报错,是因为 ECMAScript 运行时把它等价于如下代码:
function new() {
var age;
console.log(age);
age = 26;
}
new(); // undefined
这就是所谓的 ' 提升 ' ( hoist ),也就是将所有的变量声明都拉到函数作用域的顶部。此外,反复多次的使用var声明同一个变量也没有问题:
function new() {
var age = 16;
var age = 17;
var age = 18;
console.log(age);
}
new(); // 18;
2.3.2 let 声明
let 跟 var 的作用差不多,但有着非常重要的区别。最明显的区别是,let 声明的范围是块作用域,而 var 声明的范围是函数作用域。
if (true) {
var myName = '张三';
console.log(myName); // '张三'
}
console.log(myName); // '张三'
if (true) {
let myName = '张三';
console.log(myName); // '张三'
}
console.log(myName); // ReferenceError:myName ( 没有定义 )
在这里,变量之所以不能在 if 块外部引用,是因为它的作用域仅限于该块内部。块作用域是函数作用域的子集,因此适用于 var 的作用域限制同样适用于 let。
let 也不允许同一个块作用域中出现冗余声明。会导致报错:
var myName;
var myName;
let age;
let age; //SyntaxError; 标识符age已经被声明。
var myName = '张三';
console.log(myName); // '张三'
if (true) {
var myName = '李四';
console.log(myName); // '李四'
}
let age = 18;
console.log(age); // 18
if (true) {
let age = 20;
console.log(myName); // 20
}
以上两个 let 不在同一作用域,因此可以重复声明。
var myName;
let myName; // SyntaxError;
let age;
var age; // SyntaxError;
以上两种情况,不会因为混用而影响报错,因为声明的是同一变量。
1.暂时性死区
let 和 var 的一个重要区别,let不会提升:
console.log(myName); // undefined
var myName = '张三';
// myName 会被提升
console.log(age); // ReferenceError age; 没有定义age
let age = 18;
// age 不会提升
虽然在解析代码时,JavaScript引擎也会先注意到后面的 let 声明,只不过在此之前不能以任何方式引用未声明的变量。在 let 声明之前的执行瞬间被称为 ' 暂时性死区 ' ( temporal dead zone ) ,在此阶段引用任何后面才声明的变量都会抛出 ReferenceError。
2.全局声明
与 var 关键字不同,使用 let 在全局作用域中声明的变量不会成为 window 对象的属性:
var myName = '张三';
console.log(window.myName); // '张三'
let age = 18;
console.log(window.age); // undefined
虽然不能成为 window 的对象属性,但此时 let 的声明仍在全局作用域发生,因此为了避免 SyntaxError ,必须确保页面不会重复声明同一个变量。
3.条件声明
太多懒得写,牢记 let 只得作用域只在当前块声明即可;
4.for循环中的 let 声明
在 let 出现之前,for 循环定义的迭代变量会渗透到循环外部:
for (var i = 0; i < 5; ++i) {
// 循环逻辑
}
console.log(i); // 5
// 而改为let之后,这个问题消失了,因为迭代变量的作用域仅限于for循环块内部:
for (let i = 0; i < 5; ++i) {
// 循环逻辑
}
console.log(i); // ReferenceError:i 没有定义
在使用var时,最常见的问题就是对迭代变量的奇特声明和修改:
for (var i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 5、5、5、5、5 而不是 0、1、2、3、4
// 出现上面这种情况是因为退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行的超时逻辑时,所有的i都是同一个变量,因而输出的都是同一个值。
// 而将var换成let时,JavaScript引擎在后台会为每个新的迭代循环声明一个新的迭代变量。这样每一个setTimeout引用的都是不同的变量实例,所以console.log()会输出我们期望的值。
for (let i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 0、1、2、3、4
这种每次迭代声明一个独立变量实例的行为适用于所有风格的 for 循环,包括 for-in 、 for-of 循环。
2.3.3 const 声明
const 的行为基本与 let 相同,区别在于声明 const 变量的同时需要为其赋值,并且为已经声明的 const 变量进行修改时会导致运行错误。
const age = 18;
age = 20; // TypeError: 给常量赋值
// const 也不允许重复声明
const myName = '张三';
const myName = '李四'; // SyntaxError
// const 声明的作用域也是块
const myName = '张三';
if (true) {
const myName = '李四';
}
console.log(myName); // '张三'
const 声明的限制只适用于他指向的变量的引用。换句话说,如果 const 变量引用的是一个对象,那么修改这个对象内部的属性并不违反 const 的限制。
const new = {};
new.name = '张三'; // ok
虽然 const 与 let 很相似,但不能用 const 来声明迭代变量 ( 因为迭代变量会自增 ):
for (const i = 0; i < 5; ++i) {} // TypeError: 给常量赋值
当然 const 声明一个不会被修改的 for 循环变量时可以的,也就是说每次迭代只是创建一个新的变量。这对 for-in 、 for-of 循环特别有意义。
let i = 0;
for (const j = 7; i < 5; ++i) {
console.log(j);
} // 7、7、7、7、7
for (const key in {a: 1,b: 2}) {
console.log(key);
} // a,b
for (const value of [1,2,3,4,5]) {
console.log(value);
} // 1,2,3,4,5
2.3.4 声明风格及最佳实践
本书推荐不使用 var ,优先使用 const,let 次之。
2.4 数据类型
ECMAScript 有六种简单数据类型:Undefined、Null、Boolean、Number、String 和 Symbol。Symbol ( 符号 ) ECMAScript 6 新增类型。复杂数据类型:Object ( 对象 ) 是一种无序值对的集合。ECMAScript 的数据类型很灵活,一种数据类型可以当做多种数据类型来使用。
2.4.1 typeof 操作符
因为 ECMAScript 的类型系统是松散的,所以需要一种手段来确定任意变量的数据类型。typeof 操作符就是为此而生的。对一个值使用 typeof 操作符会返回下列字符串之一:
-
" undefined " 表示值未定义;
-
" boolean " 表示值为布尔值;
-
" string " 表示值为字符串;
-
" number" 表示值为数值;
-
" object " 表示值为对象 ( 而不是函数 ) 或 null;
-
" function " 表示值为函数;
-
" symbol " 表示值为符号;
2.4.2 undefind 类型
undefind 类型只有一个值,就是 undefind 。当使用 var 或 let 声明了变量但没有初始化时,就相当于给其赋值 undefind:
let text;
console.log(text == undefind); // true
// 等同于
let text = undefind;
console.log(text == undefind); // true
但包含 undefind 的变量和未定义的变量还是有区别的:
let text = undefind;
console.log(text); // "undefind"
console.log(age); // 报错
在对未初始化的变量调用 typeof 时,返回的结果时 undefind ,但对未声明的变量调用它时返回的结果还是 undefind :
let text = undefind;
console.log(typeof text); // "undefind"
console.log(typeof age); // 报错
undefind 是一个假值。因此,如果需要,可以使用更简洁的方式来检查它,所以要明确想要检测的是 undefind 的字面值,而不是假值。
let text;
if (text) {
// 不会执行
}
if (!text) {
// 会执行
}
if (age) {
// 报错
}
2.4.3 Null 类型
Null 类型同样只有一个值,即特殊值 null 。逻辑上讲,null 值表示一个空对象指针,这也是给 typeof 传一个 null 会返回 " object " 的原因:
let car = null;
console.log(typeof car); // "object"
undefind 的值是由 null 的值派生而来,因此在 ES 中将它们定义为表面上相等:
console.log(null == undefined); // true
// 用等于操作符比较二者始终返回true,但这个操作符会为了比较而转换它的操作数
即使 undefind 和 null 有关系,但它们的用途完全不一样。与 undefind 不同任何时候,只要变量要保存对象,而当时有没有对象可以保存,就需要用 null 来填充变量,来保持 null 是空对象指针的语义,并进一步将其与 undefind 区分开来。
null是一个假值。因此,如果需要,可以使用更简洁的方式来检查它,所以要明确想要检测的是 null 的字面值,而不是假值。
let text = null;
let age;
if (text) {
// 不会执行
}
if (!text) {
// 会执行
}
if (age) {
// 不会执行
}
if (!age) {
// 会执行
}
2.4.4 Boolean 类型
Boolean( 布尔值 ) 类型是 ES 中使用最频繁的类型之一,有两个字面值:true 和 false 。这两个布尔值不同于数值,因此 true 不等于 1,false 不等于 0 。
let found = true;
let lost = false;
布尔值字面量 true 和 false 是区分大小写的,True 和 False 是有效的标识符,而不是布尔值。
虽然布尔值只有两个,但所有其他 ES 类型的值都有相应布尔值等价形式。要将其他类型的值转换为布尔值,可以调用特定的 Boolean( ) 转型函数:
let text = 'Hello world';
let textBoolean = Boolean(text)
在这个例子中将字符串转换成布尔值,Boolean( ) 转型函数可以在任意类型的数据上调用,而且始终返回一个布尔值。转换规则取决于数据类型和实际的值:
数据类型 | TRUE | FALSE |
---|---|---|
Boolean | true | false |
String | 非空字符串 | 空字符串 |
Number | 非零数值 | 0、NaN |
Object | 任意对象 | null |
Undefined | N/A ( 不存在 ) | undefined |
2.4.5 Number 类型
最基本的数值字面量格式是十进制整数:
let intNum = 10; // 整数
8 进制字面量第一个数必须为 0 ,后面接相应的 8 进制数值 ( 0 ~ 7 ) 。如果字面量中包含的数字超过范围,就会忽略前面的 0 ,后面的数字序列被当为十进制:
let octalNum1 = 070; // 有效的8进制 56
let octalNum2 = 079; // 无效8进制,当做79处理
let octalNum3 = 08; // 无效8进制,当做8处理
以上这种定义 8 进制的写法在严格模式下是不生效的,会引起语法错误,应该改为在 0 后面跟o:
let let octalNum1 = 0o70; // 有效8进制
16 进制,前缀 0x 严格区分大小写,后跟 16 进制数值 ( 大小写均可 ):
let hexNum1 = 0x1A;
let hexNum2 = 0x9f;
1.浮点值
定义浮点值,数值中必须包含小数,并且小数点后面必须跟上数值。小数点前可以不跟数值,但不推荐:
let floatNum1 = 1.1;
let floatNum2 = 0.1;
let floatNum3 = .1; // 有效但不推荐(目前严格模式不会报错)
因为浮点值所占内存是整数的两倍,所以 ES 总是想将其转换为整数,因此浮点值在小数点后没有数值或数值为0 的情况下都会自动转换为整数:
let floatNum1 = 1. ;
let floatNum2 = 1.0; // 结果都会当做1处理
对于数值过大或过小的数值会采用科学计数法。
因为浮点值的精准度最高可达 17 位小数,但在算数计算中远不如整数精准:
let floatNum1 = 0.1;
let floatNum2 = 0.2;
console.log(floatNum1 + floatNum2); // 答案并不是0.3,而是0.3000...04
之所以存在这种错误,是因为使用了 IEEE754 数值,这种错误并非 ES 独有。其它使用该格式的语言也存在这个问题。因此永远不要测试某个特定的浮点值。
2.值的范围
当数值超出表示范围后负数会以 —Infinity ( 负无穷大 ) 表示,正数以 Infinity ( 无穷大 ) 表示,并且该值无法进行进一步运算,要确定一个数是否超出了范围可以用 isFinite ( ) 函数:
let result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(isFinite (result)); // 超出 false
3.NaN
特殊的数值NaN,意思是“不是数值”( Not a Number ),用来表示本来应返回数值的操作失败了( 而不是抛出错误 )。例如 0 除任何数在其他语言通常会引起错误,从而导致代码终止。但在 ES 中,0、+0、-0 相除都会返回 NaN:
console.log(0/0);
console.log(-0/+0); // NaN
如果分子是非零,分母是有无符号0,则会返回—Infinity 或 Infinity :
console.log(5/0); // Infinity
console.log(5/+0); // —Infinity
NaN 有几个独特的属性。首先任何涉及 NaN 的操作都会返回 NaN 如 ( NaN / 10 ),其次 NaN 不等于包括它本身在内的任何数:
console.log(NaN == NaN); // false
isNaN ( ) 函数用来接收一个可以是任何数据类型的参数,并判断这个参数是否“不是数值”。但首先它会尝试将它转换为数值:
console.log(isNaN(NaN)); // true
console.log(isNaN(10)); // false
console.log(isNaN('10')); // false '10'可以转换
console.log(isNaN("blue")); // true
console.log(isNaN(true)); // false true可以转换为 1
4.数值转换
有三个函数可以将非数值转换为数值:Number ( )、parseInt ( )、parseFloat ( )。Number ( ) 是转型函数,可用于任何数据类型。而后两个函数主要用于将字符串转换为数值。对于同样的参数这三个函数执行的操作也不同: Number ( ) 函数基于如下规则执行转换:
-
布尔值,true 转换为1,false 转换为0;
-
数值,直接返回;
-
null,返回 0;
-
undefined,返回 NaN;
-
对象,调用 valueof ( ) 方法,并按其他规则转换返回的值。如果转换结果为 NaN,则会调用 toString ( ) 方法,再按照字符串规则转换。
-
字符串,应用以下规则:
-
如果字符串包含数值字符,包括数值字符前面带加减号的情况,则转换为一个十进制数值。因此,Number ('1') 返回1,Number ('123') 返回123,Number ('0123') 返回123 ( 忽略0 )。
-
如果字符串包含有效的浮点格式,则会转换为相应的浮点值,同样忽略前面的 0。
-
如果字符串包含十六进制格式如'0x',则会转换为相应的十六进制数值。
-
空字符串,返回 0。
-
包含除以上以外的字符,返回 NaN。
-
let num1 = Number('Hello world'); // NaN
let num2 = Number(' '); // 0
let num3 = Number('00011'); // 11
let num4 = Number(true); // 1
let num1 = Number('1234Hello world'); // NaN
let num1 = Number('Hello world'1234); // NaN
通常在需要得到整数时可以优先使用 parseInt ( ) 函数。parseInt ( ) 函数更专注于字符串是否包含数值模式。字符串最前面的空格会被忽略,从低一个非空格字符开始转换。如果第一个非空格字符不为数值或加减号,parseInt ( ) 立即返回NaN。这就意味着空字符也会返回 NaN,而在 Number ( ) 中返回的是 0。如果第一个非空格字符是数值或加减号,则会依次检测每一个字符直到结尾,或是碰到非数值字符。其中 '22.5' 会被转换为 22,因为小数点不是有效的整数字符。同时 parseInt ( ) 函数 也能识别不同的整数格式:
let num1 = parseInt('123blue'); // 123
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
// 通过第二个参数,可以极大拓展转换后获得的结果类型
let num1 = parseInt('10', 2); // 2进制解析
let num2 = parseInt('10', 8); // 8进制解析
let num1 = parseInt('10', 10); // 10进制解析
let num2 = parseInt('10', 16); // 16进制解析
因为不传底数参数相当于让 parseInt ( ) 自己决定如何解析,所以为了避免解析出错,建议始终传给它第二个参数。
parseFloat ( ) 跟 parseInt ( ) 的工作方式类似,但不同的是 parseFloat ( ) 解析到一个无效的浮点数值字符为止,这意味着第一个小数点是有效的,而第二个小数点就会失效,此时剩余字符都会忽略。因此 ' 22.3.5 ' 会转唤成 22.3。
parseFloat ( ) 的另一个不同之处在于,他始终忽略字符串开头的零,它能够识别前面讨论的所有浮点格式,以及十进制格式。十六进制数值始终会返回 0。因为 parseFloat ( ) 函数只解析十进制,因此不能指定底数。最后字符串如果是整数则返回整数:
let num1 = parseFloat('1234blue'); // 1234
let num2 = parseFloat('0xA'); // 0
let num3 = parseFloat('22.5'); // 22.5
let num4 = parseFloat('22.5.5'); // 22.5
let num5 = parseFloat('0980.5'); // 980.5
let num6 = parseFloat('3.125e7'); // 31250000
2.4.6 String 类型
1.转换为字符串
toString ( ) 返回当前值的字符串等价物:
let age = 11;
let ageString = age.toString(); // '11'
let found = true;
let found = found.toString(); // 'true'
toString ( ) 用于数值、布尔值、对象、字符串值 ( 返回一个自身的副本 )。null 和 undefined 没有toString ( ) 方法。
多数情况下,toString ( ) 不接收任何参数。不过对于数值调用这个方法时,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'
如果不确定一个值是否为 null 或 nudefined,可以使用 Sting ( ) 转型函数,它始终会返回表示相应类型值的字符串。null 或 nudefined 会返回它们本身,其他情况会调用 toString ( ) 函数。
2.模板字面量
ES6 新增了使用模板字面量定义字符串的能力。与使用单引号和双引号不同,模板字面量保留换行字符,可以跨行定义字符串:
let template = 'first line\nsecond line';
let template2 = `first line
nsecond line`;
console.log(template);
// first line
// second line
console.log(template2);
// first line
// second line
console.log(template === template2); // true
3.字符串穿插值
// 在此之前字符串穿插值是这么实现的
let year = 2022;
console.log('今年是' + year + '年')
// 现在可以这样实现
let year = 2022;
console.log(`今年是${year}年`)
// ${}嵌套内容
所有插入的值都默认会被 toString ( ) 强制转型为字符串,而且任何JavaScript表达式都可以用于插值。嵌套的模板字符串无需转义:
console.log(`Hello, ${'World'}!`); // Hello World!
// 将表达式转换为字符串时会调用 toString():
let foo = { toString: () => 'World' };
// '=>' ES6新增箭头函数,等于function(){ return 'World' } 详细在后续会讲
// toString: 将对象作为字符串返回,在这里去除后会导致结果变为---Hello,[object Object]!
// 因为foo是一个对象--object
console.log(`Hello,${ foo }!`);
// 在插值表达式中可以调用函数和方法:
function cap(word) {
return `${ word[0].toUpperCase() }${ word.slice(1) }`;
// toUpperCase() 表示将字符串转换为大写,在这里表示word传入的实参仅显示首字符并且大写,也就是H和W;
// slice() 表示提取字符串,由于索引从0开始,所以在这里表示提取除首字符外的字符,也就是ello和orld;
}
console.log(`${ cap('Hello') },${ cap('World') }!`); // Hello World!
// 此外,模板也可以插入自己之前的值:
let value = '';
function append() {
value = `${ value }abc`;
console.log(value);
}
append(); // abc
append(); // abcabc
append(); // abcabcabc
4.模板字面量标签函数
模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为。标签函数会接受插值记号分隔后的模板和对每个表达式求值的结果。
标签函数本身是一个常规函数,通过前缀到模板字面量来应用自定义行为,如下列表示。标签函数接收到的参数依次是原始字符串数组和对每个表达式求值的结果。这个函数的返回值是对模板字面量求值得到的字符串:
let a = 6;
let b = 9;
function sim(str,ava,bva,sum) {
console.log(str);
// [' ',' + ',' ' = ' '] 第一个形参用来接收除${}外的字符串组成的数组,${}在这里相当于分隔符。
// 想要接收${}中的内容需要额外添加形参,如下:
console.log(ava); // 6
console.log(bva); // 9
console.log(sum); // 15
return 'fo';
}
let unt = `${a} + ${b} = ${ a + b }`; // 单纯的赋值
let tag = sim `${a} + ${b} = ${ a + b }`; // 用来传参
console.log(unt); // '6 + 9 = 15'
console.log(tag); // 'fo'