1.let和const
在ES6之前都是用var进行变量定义,但是var定义在程序编写的时候会有很多潜在的问题。
- 它可重复定义。
var a = 10; var a = 20;
- 它不可控,意为可随意修改变量值。不像C和Java可以定义不可变量,甚至可以宏定义。
- var变量没有块级作用域。为函数级作用域。
紧接着ES6进行更新了,变量定义的新语法,let和const,二者的出现共同解决了var定义变量的三个大问题。
1. 变量重复声明问题; 2. 变量不可控问题
let a= 12;
let a= 15; //重复定义错误
const data = 10;
console.log(data); //10
//data = 100; //执行错误,常量不可重复定义
但是若用常量去定义一个数组,可操作常量数组中的值,但是无法为常量数组赋值。对象操作类似。
const list = [1, 2, 3];
console.log(list); //[ 1, 2, 3 ]
list[0] = 10;
console.log(list); //[ 10, 2, 3 ]
list.push(20);
console.log(list); //[ 10, 2, 3, 20 ]
//list = [4,5,6]; //错误 常量不可重复赋值
3.块级作用域问题
var arr = [];
for (var i = 0; i < 10; i++) {
arr.push(function(){
console.log(i);
})
}
arr.forEach(item=> item()) // 10 10 10 10 10 10 10 10 10 10
按照for循环的执行机制来说, 应该打印从0到9的数字,但是i的声明方式是var,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 10。
解决办法: 1. 利用闭包概念,将i包在函数身上。2. 利用let进行变量声明。
var arr = [];
for (var i = 0; i < 10; i++) {
arr.push((function () {
console.log(i);
})(i))
}
arr.forEach(item => item()) // 0 1 2 3 4 5 6 7 8 9
闭包之后可以实现预期输出,但是程序编写略显麻烦,下面用let进行变量声明
var arr = [];
for (let i = 0; i < 10; i++) {
arr.push(function () {
console.log(i);
})
}
arr.forEach(item => item()) // 0 1 2 3 4 5 6 7 8 9
分析:变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以
最后按顺序输出。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而
计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在
上一轮循环的基础上进行计算。
总结一下:var变量可重复声明,可以被修改,函数级作用域;let 不能重复声明,可以被修改,块级作用域;
const 不能重复声明,不可以被修改,块级作用域; 放弃var用let,const使用看情况。
但是let有暂时性死区问题
var a = 10;
function name(params) {
console.log(a);
let a = 20;
};
name();
输出a,浏览器会报错,这就是let的暂时性死区。只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
2.解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。
- 两边的结构必须一样,不能等式一边是数组一边是JSON之类不对等的解构。
- 等式右边必须是正确的结构,类似
{12, 15}; [ "name":"不良菜", “age”: 18]等
奇奇怪怪的表达式无法进行解构。
数组解构赋值
let arr = [1, 2, 3, 4, 5]
let [a, b, c] = arr;
console.log(a, b, c); // 1 2 3 4 5
通过剩余运算符
let [x, y, ...other] = arr;
console.log(x, y, other); //1 2 [ 3, 4, 5 ]
对象解构赋值
let json= { name: '不良菜', age: 20 };
let {name, age} = json;
console.log(name, age); // 不良菜 20
数值和布尔值的解构赋值
其中原始类型和实例对象的自动转换,大家可以看看包装对象,网道写的非常详细。
解构在内部使用函数ToObject()把元数据结构转换为对象。意味着在对象解构的上下文中,原始值会被当成对象。根据ToObject()函数的定义,null和undefined不能被解构,否则会抛出错误。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。
const { prop: x } = undefined; // TypeError
const { prop: y } = null; // TypeError
可以使用空对象模型{}来检查一个值是否被强制转换成了一个对象。正如前面提到的规则,
undefined和null将会抛出错误
字符串的解构赋值
其中原始类型和实例对象的自动转换,大家可以看看包装对象,网道写的非常详细。
let { length } = "abc" // 字符串abc的包装对象提供了多个属性,length只是其中之一
// 相当于现在"abc"变成了{0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}
// 我们用其中的length,自然输出就为3
console.log(length); // 3
console.log({length}); // { length: 3 }
字符串被转换成了一个类似数组的对象。
const [a,b,c,d,e]="hello"
console.log(a,b,c,d,e); // h e l l o
函数参数的解构赋值
函数参数的解构可以使用默认值也可以利用传递的参数进行解构
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move(); // [0, 0] 无传参,直接使用默认值
用途
对JSON格式的值提取起来尤为方便,再进行数据传输的时候,假如后台返回一串JSON格式的值,通过遍历之后,再通过解构就能轻松将里面的值取出来。
let jsonData = {
id: 01,
name: 'buliangc',
status: "OK",
data: [[{},{},{}]
};
let { id, name, status, data: sourceData} = jsonData;
console.log(id, name, status, sourceData);
// 1 buliangc OK [ {}, {}, {} ]
3.块级作用域
ES5之前js的作用域只有全局作用域和函数作用域,ES6之后,新增了块级作用域,解决了以前诸多不合理的问题。比如:内层变量可能会覆盖外层变量。
var name = '外层变量';
function fn() {
console.log(name);
if (false) {
var name = '内层变量';
}
}
fn();//undefined
通过var变量提升,内部的name变量提升到函数作用域的最前面,var name;导致外层的变量被覆盖。从而输出undefined。
let 实际上为 JavaScript 新增了块级作用域。
var name = '外层变量';
function fn() {
console.log(name);
if (false) {
let name = '内层变量';
}
}
fn(); //'外层变量'
4.箭头函数
在ES6中,新增箭头函数,函数的一种简写形式。
- 箭头函数不需要使用function关键字,使用括号包裹参数,跟随一个 =>,紧接着是函数体。
- 箭头函数内部没有this,而是继承父级作用域的this,任何方法都改变不了,包括显示绑定的call,apply,bind。
- 箭头函数内部没有arguments。
- 箭头函数不能作为构造函数,不能使用new关键字。
定时器中普通函数的this指向window,除非进行绑定或者用变量保存this,不然指向Person。而用箭头函数就很轻松的解决了这个问题,因为他指向父级,也就是构造函数本身。
function Person() {
this.age = 0;
setTimeout(() => {
this.age++;
console.log(this.age);
}, 2000);
}
var person = new Person();
5. for…of vs for…in
for…of:遍历数组的值(value)
let arr = ["a", "b", "c"];
for (let item of arr) {
console.log(item); // a b c
}
for…in:遍历对象中的属性,数组的键名(key)
let arr = ["a", "b", "c"];
for (let item in arr) {
console.log(item); // 0 1 2
}
let obj = { a: 1, b: 2, c: 3 };
for (let item in obj) {
console.log(item); // a b c
console.log(obj.item) // 1 2 3
}
6. 类
子类本身是没有自己的 this ,子类构造器中, this 是由 super () 调用所产生的(即所谓「父类的 this 」)。 在 super () 调用之前,你是不能在子类构造器中使用 this 的,如果访问之,会产报错 ReferenceError :Must call super constructor in derived class before accessing ‘this’ or returning from derived constructor。
super作为函数调用时,代表父类的构造函数,不过this指向的子类实例对象。所以如果你在子类Porsche的constructor中执行super(),就相当于执行A.prototype.constructor.call(this)。