ES6的继续学习
早上吴老师带我们快速过了一下es6的知识,包括import/export,let/const,箭头函数,模板字符串,Promise和对象解构赋值的相关知识。
由于之前的准备不是很充分,所以需要接下来的进一步巩固和复习:
对象的拓展
有时变量多写起来真的很麻烦,很多繁琐的差不多的重复工作,es6为我们提供了多种更加便利的声明变量的形式——变量的解构赋值
es6允许按照一定的模式,从数组和对象中提取值,对变量进行赋值。本质是模式匹配。
变量的声明和赋值是一体的,使用let或者const不可以重复定义
在对象中的方法可以简写。
const o = {
hello() {
return "Hello!";
}
};
// 等同于
const o = {
hello: function() {
return "Hello!";
}
};
自己写了个实际的例子,顺便使用了刚学的模板字符串:
let birth = '1996/03/25';
const Person = {
name: 'kiri',
//等同于birth: birth
birth,
// 等同于hello: function ()...
hello() { console.log(`我的名字是${this.name}
我的生日是${this.birth}`); }
};
Person.hello();
控制台中输出为:
我的名字是kiri
我的生日是2000/01/01
对象和数组的解构
1.es6里对象可采用以下简洁的写法进行解构赋值:
let {name,age,id}={name:'cursor',age:19,id:'vc6dfuoc91vpdfoi87s'};
console.log(name);
console.log(age);
console.log(id);
当如果对象的属性值是一个变量时,调用必须使用变量名:
let {type:tipType,min:minNumber}={type:'message',min:20};
console.log(tipType);
console.log(minNumber);
console.log(type) //type is not defined
2.数组的嵌套解构:
注意:前后结构需要一致,不然会报错
let [age, [{name: fname}]] = [20, [{
name: 'qc'
}]];
console.log(age); //20
console.log(fname); //qc
3.数组的不完全解构:可以看出根据赋值结构进行匹配:
let [name,[age,[fname],[lname]],email]=['steven',[20,['qc'],[]]];
console.log(name); //steven
console.log(age); //20
console.log(fname); //qc
console.log(lname); //undefined
console.log(email); //undefined
4.解构对象的默认值.
在给对象或者与元素赋值时,可给被赋值的对象设置一个默认值,当被复制对象无法通过解构赋值取到值时会被赋予默认值。
默认值的生效条件是对象的属性值严格等于undefined。
5.字符串解构
const [a,b,c,d,e,f]="hello";
console.log(a); //h
console.log(b); //e
console.log(c); //l
console.log(d); //l
console.log(e); //o
console.log(f); //undefined
let和const的用法巩固
1.let是块级作用域,而var是函数作用域,
所以下面的代码如果使用var,最后输出的是10。
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
上面代码中,变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 10。
如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。
2.不存在变量提升
简单来说。let只支持先声明再调用,不存在像var一样的变量提升(即可以先调用后声明)。
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
3.不允许重复声明
let不允许在相同作用域内,重复声明同一个变量。
因此,不能在函数内部重新声明参数。
function func(arg) {
let arg; // 报错
}
function func(arg) {
{
let arg; // 不报错
}
}
4.const的基本用法
const声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const foo;
// SyntaxError: Missing initializer in const declaration
const的作用域与let命令相同:只在声明所在的块级作用域内有效。
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
5.const的本质
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。
简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
Promise对象**
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大
Promise对象有以下两个特点。
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
下面是一个Promise对象的简单例子。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。
编程风格
(1)let 取代 var
建议不再使用var命令,而是使用let命令取代
(2)全局常量和线程安全
在let和const之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。
const优于let有几个原因。一个是const可以提醒阅读程序的人,这个变量不应该改变;另一个是const比较符合函数式编程思想,运算不改变值,只是新建值,而且这样也有利于将来的分布式运算;最后一个原因是 JavaScript 编译器会对const进行优化,所以多使用const,有利于提高程序的运行效率,也就是说let和const的本质区别,其实是编译器内部的处理不同。
(3)字符串
静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
// good
const a = 'foobar';
const b = `foo${a}bar`;
const c = 'foobar';
(4) 解构赋值
使用数组成员对变量赋值时,优先使用解构赋值。
const arr = [1, 2, 3, 4];
// good
const [first, second] = arr;
console.log(first);
console.log(second);
(5)对象的成员
函数的参数如果是对象的成员,优先使用解构赋值。
function getFullName({firstName, lastName }) {
return `我的名字是${firstName} ${lastName}`
}
const obj=getFullName({"firstName":"卢","lastName":"基狗"});
console.log(obj)
上述代码就输出结果:我的名字是卢基狗
(6)数组的拷贝
使用扩展运算符(…)拷贝数组。
const arr=[1,2,3,4,5];
app=[...arr];
console.log(app);
(7)多使用箭头函数
const func3=(a,b) => {
return 'a'
}
const func3=() => 'a'
箭头函数取代Function.prototype.bind,不应再用 self/_this/that 绑定 this。
// bad
const self = this;
const boundMethod = function(...params) {
return method.apply(self, params);
}
// acceptable
const boundMethod = method.bind(this);
// best
const boundMethod = (...params) => method.apply(this, params);
简单的、单行的、不会复用的函数,建议采用箭头函数。如果函数体较为复杂,行数较多,还是应该采用传统的函数写法。