一、let 和 const 命令
-
let
命令: 用于声明对象,用法类似于var
,但是let
所声明的变量只在let
命令所在的代码块内有效。
{
var a = 1;
let b = 2;
}
console.log(a); // 1
console.log(b); // 报错 Uncaught ReferenceError: b is not defined
-
const
命令: 声明一个只读的常量,一旦声明,常量的值就不能改变,这也意味着,const
一旦声明变量,就必须立即初始化,不能以后再赋值。且const
和let
的作用域相同,只在声明所在的块级作用域内有效。
const c = 10;
c; // 10
c= 100; // 报错 Uncaught TypeError: Assignment to constant variable.
if(true) {
const d = 5;
}
console.log(d); // 报错 ReferenceError: d is not defined
- 特点:
1.let
和const
命令声明的变量都不存在变量提升,即其声明的变量一定要在声明后使用,否则报错;
2.都存在暂时性死区,即只要块级作用域内存在let
或const
命令,它所声明的变量就“绑定”这个区域,不再受外部的影响。
var tmp = 111;
if (true) {
tmp = 'abc'; // 报错 ReferenceError: tmp is not defined
let tmp = 5;
}
二、解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
1.数组的解构赋值
- 模式匹配:只要等号两边的模式相同,左边的变量就会被赋予对应的值。
let [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3
- 使用嵌套数组进行解构
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
x // 1
y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
如果解构不成功,变量的值就等于 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
2.对象的解构赋值
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
// 等号左边的两个变量的次序,与等号右边两个同名属性的次序不一致,但是对取值完全没有影响
let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined 变量没有对应的同名属性,导致取不到值,最后等于undefined
如果变量名与属性名不一致,必须写成下面这样:
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。上面第一个例子,foo是匹配的模式,baz才是变量,真正被赋值的是变量baz,而不是模式foo。
3.字符串的解构赋值
字符串也可以解构赋值,此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
类似数组的对象都有一个length
属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5
三、Iterator(遍历器) 和 for...of 循环
遍历器(Iterator)是一种用来统一处理所有不同数据结构的接口机制,为各种不同的数据结构提供统一的访问机制。遍历器有三个作用,一是为各种数据结构提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令for...of
循环,遍历器Iterator 接口主要供for...of
消费。
var a = ['A', 'B', 'C'];
var b = new Set(['A', 'B', 'C']);
var c = new Map([[1, 'A'], [2, 'B'], [3, 'C']]);
for (var x of a) { // 遍历Array
console.log(x); // A B C
}
for (var x of b) { // 遍历Set
console.log(x); // A B C
}
for (var x of c) { // 遍历Map
console.log(x[0] + '=' + x[1]); // 1=A 2=B 3=C
}
四、箭头函数
ES6 允许使用“箭头”(=>)定义函数。如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
var f = () => 5; // 函数不需要参数
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2; // 函数需要多个参数
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
使用注意点
- 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
说明:箭头函数没有自己的this
,导致内部的this
就是外层代码块的this
。正是因为它没有this
,所以也就不能用作构造函数。
五、模板字符串
模板字符串(template string)是增强版的字符串,用反引号(``)
标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中用${变量名}
的方式嵌入变量。
使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。比如<ul>标签前面会有一个换行。如果你不想要这个换行,可以使用trim方法消除它。
$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`.trim());
六、Promise对象
Promise
是异步编程的一种解决方案,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise
是一个对象,从它可以获取异步操作的消息。Promise
提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise
状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)
Promise
特点:
- 对象的状态不受外界影响,
Promise
对象代表一个异步操作,只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态;- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为resolved
(已定型)。
Promise
优缺点:
- 有了
Promise
对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise
对象提供统一的接口,使得控制异步操作更加容易。Promise
也有一些缺点。首先,无法取消Promise
,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise
内部抛出的错误,不会反应到外部。第三,当处于pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
Promise
新建后立即执行,所以首先输出的是 Promise
。然后,then
方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved
最后输出。
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// 依次打印:Promise Hi resolved
下面用Promise
对象实现Ajax操作:
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
七、Class 类
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法。
// js 中生成实例对象的传统方法是通过构造函数
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
// ES6 的class改写上面代码:定义一个“类”,里面一个constructor方法,这就是构造方法,而this关键字则代表实例对象
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
生成类的实例的写法,与 ES5 完全一样,也是使用new命令,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var point = new Point(2, 3);
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
Class 可以通过extends
关键字实现继承。
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用了父类的toString()
}
}
上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正确
}
}
上面代码中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的。
网友评论