ECMAScript6基本 使用

  • 4.2、解构模型

  • 4.3、数组的解构赋值

  • 4.4、对象的解构赋值

  • 4.5、解构赋值注意事项

  • 4.6、解构赋值的用途

  • 5、字符串、函数、数组、对象的扩展

      • 5.1、模板字符串
  • 5.2、字符串扩展方法

  • 5.3、函数的扩展

  • 5.4、数组的扩展

  • 5.5、对象的扩展

  • 6、Class基本使用和继承

      • 6.1、类的由来
  • 6.2、constructor方法

  • 6.3、类的实例

  • 6.4、类的继承

  • 6.5、静态方法

  • 6.6、静态属性

  • 7、Set和Map数据结构

      • 7.1、Set
  • 7.2、Map

  • 8、Promise的对象

      • 8.1、Promise概述
  • 8.2、Promise状态的特点

  • 8.3、Promise的方法

  • 9、async函数

      • 9.1、概念
  • 9.2、基本用法

  • 9.3、语法

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文件

Document

Hello ES6

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 关键字: letconst

let 声明的变量只在 let 命令所在的代码块内有效,const 声明一个只读的常量,一旦声明,常量的值就不能改变。

3.1、let命令

let命令有以下特点:

(1)代码块内有效

ES2015(ES6) 新增加了两个重要的 JavaScript 关键字: letconst。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©; // 输出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 ’ + basket.count + ' ’ +

'items in your basket, ’ +

’ + basket.onSale +

‘ are on sale!’

);

上面这种写法相当繁琐不方便,ES6 引入了模板字符串解决这个问题。

$(‘#result’).append(`

There are ${basket.count} items

in your basket, ${basket.onSale}

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(`

    • first
    • second
    • `);

      上面代码中,所有模板字符串的空格和换行,都是被保留的,比如 <ul> 标签前面会有一个换行。如果你不想要这个换行,可以使用 trim 方法消除它。

      $(‘#list’).html(`

      • first
      • second
      • `.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 = `

        • menu
        • mine
        • `;

          console.log(innerHtml);

          // 输出

          • menu
          • mine
          • 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 之间的小数,取整以后等于 -0repeat 视同为 0。

            ‘na’.repeat(-0.9) // “”

            参数 NaN 等同于 0。

            ‘na’.repeat(NaN) // “”

            如果 repeat 的参数是字符串,则会先转换成数字。

            ‘na’.repeat(‘na’) // “”

            ‘na’.repeat(‘3’) // “nanana”

            (3)字符串补全

            ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。

            • padStart:返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串。

            • padEnd:返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。

            以上两个方法接受两个参数,第一个参数是指定生成的字符串的最小长度,第二个参数是用来补全的字符串。如果没有指定第二个参数,默认用空格填充。

            console.log(“h”.padStart(5,“o”)); // “ooooh”

            console.log(“h”.padEnd(5,“o”)); // “hoooo”

            console.log(“h”.padStart(5)); // " h"

            console.log(‘x’.padStart(5, ‘ab’)); // ‘ababx’

            console.log(‘x’.padStart(4, ‘ab’)); // ‘abax’

            console.log(‘x’.padEnd(5, ‘ab’)); // ‘xabab’

            console.log(‘x’.padEnd(4, ‘ab’)); // ‘xaba’

            上面代码中, padStart()padEnd() 一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。

            如果指定的长度小于或者等于原字符串的长度,则返回原字符串:

            console.log(“hello”.padStart(5,“A”)); // “hello”

            如果原字符串加上补全字符串长度大于指定长度,则截去超出位数的补全字符串:

            console.log(“hello”.padEnd(10,“,world!”)); // “hello,worl”

            如果省略第二个参数,默认使用空格补全长度。

            console.log(‘x’.padStart(4)); // ’ x’

            console.log(‘x’.padEnd(4)); // 'x ’

            padStart()的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。

            console.log(‘1’.padStart(10, ‘0’)); // “0000000001”

            console.log(‘12’.padStart(10, ‘0’)); // “0000000012”

            console.log(‘123456’.padStart(10, ‘0’)); // “0000123456”

            另一个用途是提示字符串格式。

            console.log(‘12’.padStart(10, ‘YYYY-MM-DD’)); // “YYYY-MM-12”

            console.log(‘09-12’.padStart(10, ‘YYYY-MM-DD’)); // “YYYY-09-12”

            (4)消除空格

            ES6对字符串实例新增了 trimStart()trimEnd() 这两个方法。它们的行为与 trim() 一致,trimStart() 消除字符串头部的空格,trimEnd() 消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。

            const s = ’ abc ';

            s.trim() // “abc”

            s.trimStart() // "abc "

            s.trimEnd() // " abc"

            上面代码中,trimStart() 只消除头部的空格,保留尾部的空格。trimEnd() 也是类似行为。

            除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。

            浏览器还部署了额外的两个方法,trimLeft()trimStart() 的别名,trimRight()trimEnd() 的别名。

            5.3、函数的扩展

            (1)默认值

            ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。

            function log(x, y) {

            y = y || ‘World’;

            console.log(x, y);

            }

            log(‘Hello’) // Hello World

            log(‘Hello’, ‘China’) // Hello China

            log(‘Hello’, ‘’) // Hello World

            上面代码检查函数log的参数y有没有赋值,如果没有,则指定默认值为World。这种写法的缺点在于,如果参数y赋值了,但是对应的布尔值为false,则该赋值不起作用。就像上面代码的最后一行,参数y等于空字符,结果被改为默认值。

            为了避免这个问题,通常需要先判断一下参数y是否被赋值,如果没有,再等于默认值。

            if (typeof y === ‘undefined’) {

            y = ‘World’;

            }

            ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

            function log(x, y = ‘World’) {

            console.log(x, y);

            }

            log(‘Hello’) // Hello World

            log(‘Hello’, ‘China’) // Hello China

            log(‘Hello’, ‘’) // Hello

            可以看到,ES6 的写法比 ES5 简洁许多,而且非常自然。下面是另一个例子。

            function Point(x = 0, y = 0) {

            this.x = x;

            this.y = y;

            }

            const p = new Point();

            p // { x: 0, y: 0 }

            除了简洁,ES6 的写法还有两个好处:首先,阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;其次,有利于将来的代码优化,即使未来的版本在对外接口中,彻底拿掉这个参数,也不会导致以前的代码无法运行。

            参数变量是默认声明的,所以不能用letconst再次声明。

            function foo(x = 5) {

            let x = 1; // error

            const x = 2; // error

            }

            上面代码中,参数变量x是默认声明的,在函数体中,不能用letconst再次声明,否则会报错。

            使用参数默认值时,函数不能有同名参数。

            // 不报错

            function foo(x, x, y) {

            // …

            }

            // 报错

            function foo(x, x, y = 1) {

            // …

            }

            // SyntaxError: Duplicate parameter name not allowed in this context

            另外,一个容易忽略的地方是,参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。

            let x = 99;

            function foo(p = x + 1) {

            console.log§;

            }

            foo() // 100

            x = 100;

            foo() // 101

            上面代码中,参数p的默认值是x + 1。这时,每次调用函数foo,都会重新计算x + 1,而不是默认p等于 100。

            (2)不定参数

            不定参数用来表示不确定参数个数,形如,…变量名,由…加上一个具名参数标识符组成。具名参数只能放在参数组的最后,并且有且只有一个不定参数。

            基本用法

            function f(…values){

            console.log(values.length);

            }

            f(1,2); //2

            f(1,2,3,4); //4

            (3)箭头函数

            箭头函数提供了一种更加简洁的函数书写方式。基本语法是:

            参数 => 函数体

            基本用法:

            var f = v => v;

            //等价于

            var f = function(a){

            return a;

            }

            f(1); //1

            当箭头函数没有参数或者有多个参数,要用 () 括起来。

            var f = (a,b) => a+b;

            f(6,2); //8

            当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。

            var f = (a,b) => {

            let result = a+b;

            return result;

            }

            f(6,2); // 8

            当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来

            // 报错

            var f = (id,name) => {id: id, name: name};

            f(6,2); // SyntaxError: Unexpected token :

            // 不报错

            var f = (id,name) => ({id: id, name: name});

            f(6,2); // {id: 6, name: 2}

            注意点:没有 this、super、arguments 和 new.target 绑定。

            var func = () => {

            // 箭头函数里面没有 this 对象,

            // 此时的 this 是外层的 this 对象,即 Window

            console.log(this)

            }

            func(55) // Window

            var func = () => {

            console.log(arguments)

            }

            func(55); // ReferenceError: arguments is not defined

            箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象。

            function fn(){

            setTimeout(()=>{

            // 定义时,this 绑定的是 fn 中的 this 对象

            console.log(this.a);

            },0)

            }

            var a = 20;

            // fn 的 this 对象为 {a: 19}

            fn.call({a: 18}); // 18

            不可以作为构造函数,也就是不能使用 new 命令,否则会报错

            5.4、数组的扩展

            (1)扩展运算符

            扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

            console.log(…[1, 2, 3])

            // 1 2 3

            console.log(1, …[2, 3, 4], 5)

            // 1 2 3 4 5

            […document.querySelectorAll(‘div’)]

            // [

            ,
            ,
            ]

            该运算符主要用于函数调用。

            function push(array, …items) {

            array.push(…items);

            }

            function add(x, y) {

            return x + y;

            }

            const numbers = [4, 38];

            add(…numbers) // 42

            上面代码中,array.push(...items)add(...numbers)这两行,都是函数的调用,它们都使用了扩展运算符。该运算符将一个数组,变为参数序列。

            (2)扩展运算符的应用

            复制数组

            数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。

            const a1 = [1, 2];

            const a2 = a1;

            a2[0] = 2;

            a1 // [2, 2]

            上面代码中,a2并不是a1的克隆,而是指向同一份数据的另一个指针。修改a2,会直接导致a1的变化。

            ES5 只能用变通方法来复制数组。

            const a1 = [1, 2];

            const a2 = a1.concat();

            a2[0] = 2;

            a1 // [1, 2]

            上面代码中,a1会返回原数组的克隆,再修改a2就不会对a1产生影响。

            扩展运算符提供了复制数组的简便写法。

            const a1 = [1, 2];

            // 写法一

            const a2 = […a1];

            // 写法二

            const […a2] = a1;

            最后

            自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

            深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

            因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

            img

            既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

            如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

            由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
            s.length);

            }

            f(1,2); //2

            f(1,2,3,4); //4

            (3)箭头函数

            箭头函数提供了一种更加简洁的函数书写方式。基本语法是:

            参数 => 函数体

            基本用法:

            var f = v => v;

            //等价于

            var f = function(a){

            return a;

            }

            f(1); //1

            当箭头函数没有参数或者有多个参数,要用 () 括起来。

            var f = (a,b) => a+b;

            f(6,2); //8

            当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。

            var f = (a,b) => {

            let result = a+b;

            return result;

            }

            f(6,2); // 8

            当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来

            // 报错

            var f = (id,name) => {id: id, name: name};

            f(6,2); // SyntaxError: Unexpected token :

            // 不报错

            var f = (id,name) => ({id: id, name: name});

            f(6,2); // {id: 6, name: 2}

            注意点:没有 this、super、arguments 和 new.target 绑定。

            var func = () => {

            // 箭头函数里面没有 this 对象,

            // 此时的 this 是外层的 this 对象,即 Window

            console.log(this)

            }

            func(55) // Window

            var func = () => {

            console.log(arguments)

            }

            func(55); // ReferenceError: arguments is not defined

            箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象。

            function fn(){

            setTimeout(()=>{

            // 定义时,this 绑定的是 fn 中的 this 对象

            console.log(this.a);

            },0)

            }

            var a = 20;

            // fn 的 this 对象为 {a: 19}

            fn.call({a: 18}); // 18

            不可以作为构造函数,也就是不能使用 new 命令,否则会报错

            5.4、数组的扩展

            (1)扩展运算符

            扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

            console.log(…[1, 2, 3])

            // 1 2 3

            console.log(1, …[2, 3, 4], 5)

            // 1 2 3 4 5

            […document.querySelectorAll(‘div’)]

            // [

            ,
            ,
            ]

            该运算符主要用于函数调用。

            function push(array, …items) {

            array.push(…items);

            }

            function add(x, y) {

            return x + y;

            }

            const numbers = [4, 38];

            add(…numbers) // 42

            上面代码中,array.push(...items)add(...numbers)这两行,都是函数的调用,它们都使用了扩展运算符。该运算符将一个数组,变为参数序列。

            (2)扩展运算符的应用

            复制数组

            数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。

            const a1 = [1, 2];

            const a2 = a1;

            a2[0] = 2;

            a1 // [2, 2]

            上面代码中,a2并不是a1的克隆,而是指向同一份数据的另一个指针。修改a2,会直接导致a1的变化。

            ES5 只能用变通方法来复制数组。

            const a1 = [1, 2];

            const a2 = a1.concat();

            a2[0] = 2;

            a1 // [1, 2]

            上面代码中,a1会返回原数组的克隆,再修改a2就不会对a1产生影响。

            扩展运算符提供了复制数组的简便写法。

            const a1 = [1, 2];

            // 写法一

            const a2 = […a1];

            // 写法二

            const […a2] = a1;

            最后

            自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

            深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

            因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

            [外链图片转存中…(img-7bXbLPO4-1714851997865)]

            [外链图片转存中…(img-Mcw8EhBc-1714851997866)]

            [外链图片转存中…(img-2RKeriav-1714851997866)]

            既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

            如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

            由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

          • 23
            点赞
          • 9
            收藏
            觉得还不错? 一键收藏
          • 0
            评论

          “相关推荐”对你有帮助么?

          • 非常没帮助
          • 没帮助
          • 一般
          • 有帮助
          • 非常有帮助
          提交
          评论
          添加红包

          请填写红包祝福语或标题

          红包个数最小为10个

          红包金额最低5元

          当前余额3.43前往充值 >
          需支付:10.00
          成就一亿技术人!
          领取后你会自动成为博主和红包主的粉丝 规则
          hope_wisdom
          发出的红包
          实付
          使用余额支付
          点击重新获取
          扫码支付
          钱包余额 0

          抵扣说明:

          1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
          2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

          余额充值