React教案
ECMAScript6
1、ES6简介
1.1、什么是ES6
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
1.2、ECMAScript和JavaScript的关系
一个常见的问题是,ECMAScript 和 JavaScript 到底是什么关系?
要讲清楚这个问题,需要回顾历史。1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。该标准从一开始就是针对 JavaScript 语言制定的,但是之所以不叫 JavaScript,有两个原因。一是商标,Java 是 Sun 公司的商标,根据授权协议,只有 Netscape 公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性。因此,ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 JScript 和 ActionScript)。日常场合,这两个词是可以互换的。
1.3、为什么要学习ES6?
这个问题可以转换一种问法,就是学完es6会给我们的开发带来什么样便利?chrome解释javascript的引擎叫做V8,有一个人把V8引擎转移到了服务器,于是服务器端也可以写javascript,这种在服务器端运行的js语言,就是Node.js。Node.js一经问世,它优越的性能就表现了出了,很多基于nodejs的web框架也应运而生,express就是之一,随之而来的就是全栈MEAN mogoDB,Express,Vue.js,Node.js开发,javaScript越来越多的使用到web领域的各个角落,js能做的事情也越来越多。Babel是一个广泛使用的ES6转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。这意味着,你可以用ES6的方式编写程序,又不用担心现有环境是否支持。nodejs是一种开发趋势,Vue.js这种前端框架是一种开发趋势,ES6被普及使用也是趋势。目前一些前端框架都在使用ES6语法,例如Vue、React、D3等等,所以ES6也是学习好前端框架的基础。
2、ES6环境搭建
由于有些低版本的浏览器还不支持ES6的语法,所以在不使用框架的情况下,需要将ES6语法转换为ES5语法。
2.1、前期准备
先创建一个项目,项目中有两个文件夹,src和dist,一个html文件
src:将编写的ES6的js文件放到此文件夹中(这里是index.js文件)
dist:将通过Babel编译成的ES5的js文件放到此文件中(这里是index.js文件)
html:注意:将dist中编译好的文件引入到HTML文件中,而不是src中的js文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="./dist/index.js"></script>
</head>
<body>
Hello ES6
</body>
</html>
2.2、ES6环境搭建
第一步:
在src目录下,新建index.js文件。这个文件很简单,我们只作一个a变量的声明,并用console.log()打印出来。
let a = 1;
console.log(a);
第二步:
在项目的根目录初始化项目并生成package.json文件(可以根据自己的需求进行修改)
cnpm init -y
{
"name": "es6",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
第三步:
安装Babel插件(将ES6语法转换为ES5)
cnpm install -g babel-cli
第四步:
当然现在还不能正常转换,还需要安装ES5所需的一个包
cnpm install --save-dev babel-preset-es2015 babel-cli 安装完成后,package.json会有所变化
{
"name": "es6",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-preset-es2015": "^6.24.1"
}
}
第五步:
在项目的根目录添加一个 .babelrc 文件,并添加内容
{
"presets":[
"es2015"
],
"plugins": []
}
在windows系统中创建.babelrc文件的方法
方法一:根目录下,创建“.babelrc.”文件名就可以了!(前后共两个点)
方法二:cmd进入根目录,输入“type null>.babelrc”,回车即可!
第六步:
安装完成后我们可以通过命令进行转换
babel src/index.js -o dist/index.js
第七步:
可以将命令进行简化(package.json进行配置)
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
修改为:
{
"name": "es6",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "babel src/index.js -o dist/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-preset-es2015": "^6.24.1"
}
}
然后我们可以通过下面命令转义代码:
npm run test
3、let与const
ES2015(ES6) 新增加了两个重要的 JavaScript 关键字: let 和 const。
let 声明的变量只在 let 命令所在的代码块内有效,const 声明一个只读的常量,一旦声明,常量的值就不能改变。
3.1、let命令
let命令有以下特点:
(1)代码块内有效
ES2015(ES6) 新增加了两个重要的 JavaScript 关键字: let 和 const。let 声明的变量只在 let 命令所在的代码块内有效,const 声明一个只读的常量,一旦声明,常量的值就不能改变。
{
let a = 1;
var b = 2;
console.log(a);//输出1
console.log(b);//输出2
}
console.log(a);//报错 ReferenceError: a is not defined
console.log(b);//输出2
(2)不能重复声明
let 只能声明一次 var 可以声明多次:
let a = 1;
let a = 2;//报错 Identifier 'a' has already been declared
var b = 3;
var b = 4;
console.log(a);
console.log(b);//输出4
for 循环计数器很适合用 let
for (var i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i);
})
}
// 输出十个 10
for (let j = 0; j < 10; j++) {
setTimeout(function(){
console.log(j);
})
}
// 输出 0123456789
变量 i 是用 var 声明的,在全局范围内有效,所以全局中只有一个变量 i, 每次循环时,setTimeout 定时器里面的 i 指的是全局变量 i ,而循环里的十个 setTimeout 是在循环结束后才执行,所以此时的 i 都是 10。
变量 j 是用 let 声明的,当前的 j 只在本轮循环中有效,每次循环的 j 其实都是一个新的变量,所以 setTimeout 定时器里面的 j 其实是不同的变量,即最后输出 12345。(若每次循环的变量 j 都是重新声明的,如何知道前一个循环的值?这是因为 JavaScript 引擎内部会记住前一个循环的值)。
(3)不存在变量提升
let 不存在变量提升,var 会变量提升:
console.log(a); //ReferenceError: a is not defined
let a = "apple";
console.log(b); //undefined
var b = "banana";
变量 b 用 var 声明存在变量提升,所以当脚本开始运行的时候,b 已经存在了,但是还没有赋值,所以会输出 undefined。变量 a 用 let 声明不存在变量提升,在声明变量 a 之前,a 不存在,所以会报错。
(4)暂时性死区
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
上面代码中,在let命令声明变量tmp之前,都属于变量tmp的“死区”。
“暂时性死区”也意味着typeof不再是一个百分之百安全的操作。
typeof x; // ReferenceError
let x;
另外,下面的代码也会报错,与var的行为不同。
// 不报错
var x = x;
// 报错
let x = x;
// ReferenceError: x is not defined
上面代码报错,也是因为暂时性死区。使用let声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行就属于这个情况,在变量x的声明语句还没有执行完成前,就去取x的值,导致报错”x 未定义“。
ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
3.2、const命令
const 声明一个只读变量,声明之后不允许改变。意味着,一旦声明必须初始化,否则会报错。
基本用法:
const PI = "3.1415926";
PI // 3.1415926
const MY_AGE; // 报错 SyntaxError: Missing initializer in const declaration
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const foo;
// 报错 SyntaxError: Missing initializer in const declaration
上面代码表示,对于const来说,只声明不赋值,就会报错。const的作用域与let命令相同:只在声明所在的块级作用域内有效。
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
上面代码在常量MAX声明之前就调用,结果报错。const声明的常量,也与let一样不可重复声明。
var message = "Hello!";
let age = 25;
// 以下两行都会报错
const message = "Goodbye!";
const age = 30;
暂时性死区:
var PI = "a";
if(true){
console.log(PI); //报错 ReferenceError: PI is not defined
const PI = "3.1415926";
}
ES6 明确规定,代码块内如果存在 let 或者 const,代码块会对这些命令声明的变量从块的开始就形成一个封闭作用域。代码块内,在声明变量 PI 之前使用它会报错。
注意要点
const 如何做到变量在声明初始化之后不允许改变的?其实 const 其实保证的不是变量的值不变,而是保证变量指向的内存地址所保存的数据不允许改动。此时,你可能已经想到,简单类型和复合类型保存值的方式是不同的。是的,对于简单类型(数值 number、字符串 string 、布尔值 boolean),值就保存在变量指向的那个内存地址,因此 const 声明的简单类型变量等同于常量。而复杂类型(对象 object,数组 array,函数 function),变量指向的内存地址其实是保存了一个指向实际数据的指针,所以 const 只能保证指针是固定的,至于指针指向的数据结构变不变就无法控制了,所以使用 const 声明复杂类型对象时要慎重。
4、ES6解构赋值
4.1、解构赋值概述
解构赋值是对赋值运算符的扩展。
它是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
4.2、解构模型
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
在解构中,有下面两部分参与:
解构的源,解构赋值表达式的右边部分;
解构目标,解构赋值表达式的左边部分;
在ES5中,为变量赋值只能直接指定变量的值:
let a = 1;
let b = 2;
在ES6中,变量赋值允许写成:
let [a,b,c] = [1,2,3];
console.log(a); // 输出1
console.log(b); // 输出2
console.log(c); // 输出3
面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
4.3、数组的解构赋值
基本用法
let [a, b, c] = [1, 2, 3];
// a = 1
// b = 2
// c = 3
可嵌套
let [a, b, c] = [1, 2, 3];
// a = 1
// b = 2
// c = 3
可忽略
let [a, , b] = [1, 2, 3];
// a = 1
// b = 3
不完全解构
let [a = 1, b] = []; // a = 1, b = undefined
如果解构不成功,变量的值就等于undefined。
let [foo] = [];
let [bar, foo] = [1];
以上两种情况都属于解构不成功,foo的值都会等于undefined。
另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
上面两个例子,都属于不完全解构,但是可以成功。
如果等号的右边不是数组(或者严格地说,不是可遍历的结构),那么将会报错。
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {
};
上面的语句都会报错,因为等号右边的值,要么转为对象以后不具备 Iterator 接口(前五个表达式),要么本身就不具备 Iterator 接口(最后一个表达式)。
剩余运算符
let [a, ...b] = [1, 2, 3];
//a = 1
//b = [2, 3]
字符串
在数组的解构中,解构的目标若为可遍历对象,皆可进行解构赋值。可遍历对象即实现 Iterator 接口的数据。
let [a, b, c, d, e] = 'hello';
// a = 'h'
// b = 'e'
// c = 'l'
// d = 'l'
// e = 'o'
解构默认值
解构赋值允许指定默认值。
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
当解构模式有匹配结果,且匹配结果是 undefined 时,会触发默认值作为返回结果。
let [a = 2] = [undefined]; // a = 2
注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
上面代码中,如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined。
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
function f() {
console.log('aaa');
}
let [x = f()] = [1];
上面代码中,因为x能取到值,所以函数f根本不会执行。上面的代码其实等价于下面的代码。
let x;
if ([1][0] === undefined) {
x = f();
} else {
x = [1][0];
}
默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
let [a = 3, b = a] = []; // a = 3, b = 3
let [a = 3, b = a] = [1];// a = 1, b = 1
let [a = 3, b = a] = [1, 2]; // a = 1, b = 2
let [a = b, b = 1] = []; // ReferenceError: y is not defined
上述代码解释:
- a 与 b 匹配结果为 undefined ,触发默认值:a = 3; b = a =3;
- a 正常解构赋值,匹配结果:a = 1,b 匹配结果 undefined ,触发默认值:b = a =1;
- a 与 b 正常解构赋值,匹配结果:a = 1,b = 2;
- 上面最后一个表达式之所以会报错,是因为x用y做默认值时,y还没有声明。
4.4、对象的解构赋值
(1)基本用法
解构不仅可以用于数组,还可以用于对象。
let {
foo, bar } = {
foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let {
bar, foo } = {
foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
let {
baz } = {
foo: 'aaa', bar: 'bbb' };
baz // undefined
上面代码的第一个例子,等号左边的两个变量的次序,与等号右边两个同名属性的次序不一致,但是对取值完全没有影响。第二个例子的变量没有对应的同名属性,导致取不到值,最后等于undefined。
如果解构失败,变量的值等于undefined。
let {
foo} = {
bar: 'baz'};
foo // undefined
上面代码中,等号右边的对象没有foo属性,所以变量foo取不到值,所以等于undefined。
对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。
// 例一
let {
log, sin, cos } = Math;
// 例二
const {
log } = console;
log('hello') // hello
上面代码的例一将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多。例二将console.log赋值到log变量。
如果变量名与属性名不一致,必须写成下面这样。
let {
foo: baz } = {
foo: 'aaa', bar: 'bbb' };
baz // "aaa"
let obj = {
first: 'hello', last: 'world' };
let {
first: f, last: l } = obj;
f // 'hello'
l // 'world'
这实际上说明,对象的解构赋值是下面形式的简写。
let {
foo: foo, bar: bar } = {
foo: 'aaa', bar: 'bbb' };
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
let {
foo: baz } = {
foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined
上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。
(2)嵌套对象的解构赋值
与数组一样,解构也可以用于嵌套结构的对象。
let obj = {
p: [
'Hello',
{
y: 'World' }
]
};
let {
p: [x, {
y }] } = obj;
x // "Hello"
y // "World"
注意,这时p是模式,不是变量,因此不会被赋值。如果p也要作为变量赋值,可以写成下面这样。
let obj = {
p: [
'Hello',
{
y: 'World' }
]
};
let {
p, p: [x, {
y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]
下面是另一个例子。
4.5、解构赋值注意事项
(1)如果要将一个已经声明的变量用于解构赋值,必须非常小心。
// 错误的写法
let x;
{
x} = {
x: 1};
// SyntaxError: syntax error
上面代码的写法会报错,因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。
// 正确的写法
let x;
({
x} = {
x: 1});
上面代码将整个解构赋值语句,放在一个圆括号里面,就可以正确执行。关于圆括号与解构赋值的关系,参见下文。
(2)解构赋值允许等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。
({
} = [true, false]);
({
} = 'abc');
({
} = []);
上面的表达式虽然毫无意义,但是语法是合法的,可以执行。
(3)由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
let arr = [1, 2, 3];
let {
0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3
上面代码对数组进行对象解构。数组arr的0键对应的值是1,[arr.length - 1]就是2键,对应的值是3。方括号这种写法,属于“属性名表达式”。
4.6、解构赋值的用途
变量的解构赋值用途很多。
(1)交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
上面代码交换变量x和y的值,这样的写法不仅简洁,而且易读,语义非常清晰。
(2)从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let {
foo, bar } = example();
(3)函数参数的定义
解构赋值可以方便地将一组参数与变量名对应起来。
// 参数是一组有次序的值
function f([x, y, z]) {
... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({
x, y, z}) {
... }
f({
z: 3, y: 2, x: 1});
(4)提取JSON数据
解构赋值对提取 JSON 对象中的数据,尤其有用。
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let {
id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
上面代码可以快速提取 JSON 数据的值。
(5)函数参数的默认值
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {
},
cache = true,
complete = function () {
},
crossDomain = false,
global = true,
// ... more config
} = {
}) {
// ... do stuff
};
指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || ‘default foo’;这样的语句。
(6)遍历Map结构
任何部署了 Iterator 接口的对象,都可以用for…of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
如果只想获取键名,或者只想获取键值,可以写成下面这样。
// 获取键名
for (let [key] of map) {
// ...
}
// 获取键值
for (let [,value] of map) {
// ...
}
(7)输入模块的指定方法
加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。
const {
SourceMapConsumer, SourceNode } = require("source-map");
5、字符串、函数、数组、对象的扩展
5.1、模板字符串
传统的 JavaScript 语言,输出模板通常是这样写的(下面使用了 jQuery 的方法)。
$('#result').append(
'There are <b>' + basket.count + '</b> ' +
'items in your basket, ' +
'<em>' + basket.onSale +
'</em> are on sale!'
);
上面这种写法相当繁琐不方便,ES6 引入了模板字符串解决这个问题。
$('#result').append(`
There are <b>${
basket.count}</b> items
in your basket, <em>${
basket.onSale}</em>
are on sale!
`);
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
// 普通字符串
`In JavaScript '\n' is a line-feed.`
// 多行字符串
`In JavaScript this is
not legal.`
console.log(`string text line 1
string text line 2`);
// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${
name}, how are you ${
time}?`
上面代码中的模板字符串,都是用反引号表示。
转义符号
如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。
let greeting = `\`Yo\` World!`;
多行字符串
如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。
$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`);
上面代码中,所有模板字符串的空格和换行,都是被保留的,比如 <ul>
标签前面会有一个换行。如果你不想要这个换行,可以使用 trim
方法消除它。
$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`.trim());
插入变量
模板字符串中嵌入变量,需要将变量名写在${}之中。
function authorize(user, action) {
if (!user.hasPrivilege(action)) {
throw new Error(
// 传统写法为
// 'User '
// + user.name
// + ' is not authorized to do '
// + action
// + '.'
`User ${
user.name} is not authorized to do ${
action}.`);
}
}
插入表达式
大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。
let x = 1;
let y = 2;
`${
x} + ${
y} = ${
x + y}`
// "1 + 2 = 3"
`${
x} + ${
y * 2} = ${
x + y * 2}`
// "1 + 4 = 5"
let obj = {
x: 1, y: 2};
`${
obj.x + obj.y}`
// "3"
调用函数
模板字符串之中还能调用函数。
function fn() {
return "Hello World";
}
`foo ${
fn()} bar`
// foo Hello World bar
如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的 toString
方法。
如果模板字符串中的变量没有声明,将报错。
// 变量place没有声明
let msg = `Hello, ${
place}`;
// 报错
由于模板字符串的大括号内部,就是执行 JavaScript 代码,因此如果大括号内部是一个字符串,将会原样输出。
`Hello ${
'World'}`
// "Hello World"
注意要点
模板字符串中的换行和空格都是会被保留的
innerHtml = `<ul>
<li>menu</li>
<li>mine</li>
</ul>
`;
console.log(innerHtml);
// 输出
<ul>
<li>menu</li>
<li>mine</li>
</ul>
5.2、字符串扩展方法
(1)子串的识别
ES6 之前判断字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的识别方法。
- includes():返回布尔值,判断是否找到参数字符串。
- startsWith():返回布尔值,判断参数字符串是否在原字符串的头部。
- endsWith():返回布尔值,判断参数字符串是否在原字符串的尾部。
以上三个方法都可以接受两个参数,需要搜索的字符串,和可选的搜索起始位置索引。
let s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
这三个方法都支持第二个参数,表示开始搜索的位置。
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
上面代码表示,使用第二个参数 n
时, endsWith
的行为与其他两个方法有所不同。它针对前 n
个字符,而其他两个方法针对从第 n
个位置直到字符串结束。
注意点:
- 这三个方法只返回布尔值,如果需要知道子串的位置,还是得用 indexOf 和 lastIndexOf 。
- 这三个方法如果传入了正则表达式而不是字符串,会抛出错误。而 indexOf 和 lastIndexOf 这两个方法,它们会将正则表达式转换为字符串并搜索它。
(2)字符串重复
repeat():返回新的字符串,表示将字符串重复指定次数返回。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
参数如果是小数,会被向下取整。
'na'.repeat(2.9) // "nana"
如果 repeat
的参数是负数或者 Infinity
,会报错。
'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError
但是,如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于 -0
, repeat
视同为 0。
'na'.repeat(-0.9) // ""
参数 NaN
等同于 0。
'na'.repeat(NaN) // ""
如果 repeat