2.1 let、const 和 block 作用域
2.1.1 let命令 :
- 基本用法:
{
let a = 0;
a // 0
}
a // 报错 ReferenceError: a is not defined
- 代码块内有效(let 是在代码块内有效,var 是在全局范围内有效)
{
let a = 0;
var b = 1;
}
a // ReferenceError: a is not defined
b // 1
- 不能重复声明 (let 只能声明一次 var 可以声明多次)
let a = 1;
let a = 2;
var b = 3;
var b = 4;
a // Identifier 'a' has already been declared
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 引擎内部会记住前一个循环的值)。
- 不存在变量提升:(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 不存在,所以会报错。
2.1.2 const命令
ES6 中,const 声明的常量类似于指针,它指向某个引用,也就是说这个「常量」并非一成不变的。
const 声明一个只读变量,声明之后不允许改变。意味着,一旦声明必须初始化,否则会报错。
- const 在声明时必须被赋值
- 当使用常量 const 声明时,请使用大写变量,如:CAPITAL_CASING
- let 和 const 声明只在最靠近的一个块中(花括号内)有效
注意要点:
const 如何做到变量在声明初始化之后不允许改变的?其实 const 其实保证的不是变量的值不变,而是保证变量指向的内存地址所保存的数据不允许改动。此时,你可能已经想到,简单类型和复合类型保存值的方式是不同的。是的,对于简单类型(数值 number、字符串 string 、布尔值 boolean),值就保存在变量指向的那个内存地址,因此 const 声明的简单类型变量等同于常量。而复杂类型(对象 object,数组 array,函数 function),变量指向的内存地址其实是保存了一个指向实际数据的指针,所以 const 只能保证指针是固定的,至于指针指向的数据结构变不变就无法控制了,所以使用 const 声明复杂类型对象时要慎重。
2. 箭头函数(Arrow Functions)
ES6 中,箭头函数就是函数的一种简写形式,使用括号包裹参数,跟随一个 =>,紧接着是函数体
getPrice 箭头函数采用了简洁函数体,它不需要 return 语句,正常函数体:
箭头函数不仅仅是让代码变得简洁,函数中 this 总是绑定总是指向对象自身,经常需要使用一个变量来保存 this,然后在 growUp 函数中引用,而使用箭头函数可以省却这个麻烦:
3. 函数参数默认值
ES6 中允许你对函数参数设置默认值
4. Spread / Rest(剩余运算符) 操作符
- spread运算符:
- spread运算符常常用于数组的解析和构造:
const arr1 = ['a','b','c'] const arr2 = ['aa','bb','cc'] //构造数组 const arradd = [...arr1, ...arr2] console.log(arradd); // ['a','b','c','aa','bb','cc'] //解析数组 let son1,son2 [son1, ...son2] = arr1 console.log(son1) // 'a' console.log(son2) //['b','c']
运行结果如图所示:
2.在一些特定函数调用中,参数只能接受数列不接受数组。这是spread
运算符也派上用场:
- rest操作符:(传参过程中)
1.rest
操作符让函数的所有参数可由一个变量统一接收 ,帮助我们创建更加灵活的函数
2.2 ES6解构赋值
2.2.1 概述:
解构赋值是对赋值运算符的扩展。
他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取
2.2.2 解构模型:
解构的源=解构赋值表达式右边部分
解构的目标=解构赋值表达式左边的部分
- 解构赋值之对象分配变量
在简单对象中的赋值作用:
也可以将值赋予异名变量a
,b
,c
:
在嵌套式对象中的解构赋值操作:
(相当于将a的值映射给:后面的参数,一一对应关系,将x的值1赋值给x1,y的值2赋值给y2)
- 解构赋值之数组分配变量
解构赋值也可以用于数组中,
它会按顺序自动获取对应位置的值。再看看如何获取指定位置的值:
- 解构赋值之函数对象参数
此处存疑:不知道什么原因,我这边运行之后只能显示num,尝试过很多次,只能显示括号里最后一个参数。
- 数组的深度copy
深度拷贝与浅度拷贝的区别在于新变量是否受源变量的影响而发生变化!
es5出现的情况:
而es6的spread
运算符促使数组解析构造,形成深度copy,arr2
复制而来的值不受源arr1
的变化而影响:
- 字符串转数组
- 变量值交换: 以往交换变量值需要一个中间变量,而es6之后语法变得简单:
var x = 1, y = 2;
[x, y] = [y, x];
console.log(x); // 2
console.log(y); //1
2.3 Symbol
2.3.1概述
ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
ES6 数据类型除了 Number 、 String 、 Boolean 、 Object、 null 和 undefined ,还新增了 Symbol 。
2.3.2 基本用法
Symbol 函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。可以接受一个字符串作为参数,为新创建的 Symbol 提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。
let sy = Symbol("KK");
console.log(sy); // Symbol(KK)
typeof(sy); // "symbol"
// 相同参数 Symbol() 返回的值不相等
let sy1 = Symbol("kk");
sy === sy1; // false
2.3.3 使用场景
1.作为属性名
用法 :
由于每一个 Symbol 的值都是不相等的,所以 Symbol 作为对象的属性名,可以保证属性不重名。
let sy = Symbol("key1");
// 写法1
let syObject = {};
syObject[sy] = "kk";
console.log(syObject); // {Symbol(key1): "kk"}
// 写法2
let syObject = {
[sy]: "kk"
};
console.log(syObject); // {Symbol(key1): "kk"}
// 写法3
let syObject = {};
Object.defineProperty(syObject, sy, {value: "kk"});
console.log(syObject); // {Symbol(key1): "kk"}
Symbol 作为对象属性名时不能用.运算符,要用方括号。因为.运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性。
let syObject = {};
syObject[sy] = "kk";
syObject[sy]; // "kk"
syObject.sy; // undefined
注意点:
Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for...in 、 for...of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。
let syObject = {};
syObject[sy] = "kk";
console.log(syObject);
for (let i in syObject) {
console.log(i);
} // 无输出
Object.keys(syObject); // []
Object.getOwnPropertySymbols(syObject); // [Symbol(key1)]
Reflect.ownKeys(syObject); // [Symbol(key1)]
2.定义常量
用字符串不能保证常量是独特的,这样会引起一些问题,但是使用 Symbol 定义常量,这样就可以保证这一组常量的值都不相等。Symbol 的值是唯一的,所以不会出现相同值得常量,即可以保证 switch 按照代码预想的方式执行。
Symbol.for()
Symbol.for() 类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。
let yellow = Symbol("Yellow");
let yellow1 = Symbol.for("Yellow");
yellow === yellow1; // false
let yellow2 = Symbol.for("Yellow");
yellow1 === yellow2; // true
Symbol.keyFor()
Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。
let yellow1 = Symbol.for("Yellow");
Symbol.keyFor(yellow1); // "Yellow"