1 基础
1.1 let
1.1.1 基础
let
允许你声明一个作用域被限制在块级中的变量、语句或者表达式。
重复声明报错:var
和 let
的不同之处在于 let
是在编译时才初始化,也就是在同一个块级下不能重复地声明一个变量,否则会报错(即使使用 var
去声明也是不可以的)
var声明的变量会添加到window对象作为属性,let不会添加到window上
尽量使用 let
去代替 var
来声明变量。
1.1.2 块级作用域(ES6新增)
(1)全局作用域
能通过 window 对象拿到的变量就是全局作用域下声明的变量
(2)函数作用域
在函数内部定义的变量,也就是局部作用域
对外是封闭的,从外层是无法直接访问函数内部的作用域的
(3)块级作用域
在打大括号({}
)中,使用 let
或 const
声明的变量,才会产生块级作用域。
块级作用域的产生是 let
或 const
带来的,而不是大括号,大括号的作用是限制 let
或 const
的作用域范围。当不在大括号中声明时, let
或 const
的作用域范围是全局。
块级作用域可以任意嵌套
1.1.3 不能变量提升
let
和 const
语法规定必须声明后使用,否则报错
1.1.4 暂时性死区
在代码块内,使用 let
命令声明变量之前,该变量都是不可用的。这在语法上,称为 “暂时性死区”(temporal dead zone,简称 TDZ)
处于暂存死区,不能被使用,如果引用则会引用错误
注意对于 typeof
也是会报错的。
1.1.5 重复声明报错
在 switch
语句中只有一个块级作用域,所以下面这种情况也是会报错的。
let x = 1;
switch(x) {
case 0:
let num;
break;
case 1:
let num;//重复声明了
break;
}
// 报错
把 case
后面的语句放到块作用域中(加大括号)则不会报错
1.2 const
1.2.1 基础
const
在声明时必须初始化一个值,而且这个值不能被改变
但如果使用 const
声明的变量是一个对象类型的话,我们可以改变对象里的值,这是因为 const
存的变量只是一个地址的引用,所以只要不改变引用的值,就不会报错。
特点:
- 不能进行变量提升,在声明前不能被使用,否则会抛出异常;
- 存在暂时性死区,在块中不能被重复声明。
1.2.2 ES5 模拟实现 const
在 ES6 之前是不能定义常量的,如果想定义常量的话,需要借助 ES5 中的 defineProperty
方法
function setConst(key, value, obj) {
Object.defineProperty(window, key, {
get: function(){
return value;
},
set: function(){
console.error('Uncaught TypeError: Assignment to constant variable');
},
});
}
setConst('PI', 3.1415);
console.log(PI) // 3.1415
PI = 3; // Uncaught TypeError: Assignment to constant variable.
ES5 的 Object.defineProperty
方法,这个方法允许在一个对象上定义一个新的属性
1.3 展开语法
ES6 新增了 ...
的语法糖,主要用于展开语法和剩余语法中
展开语法顾名思义可以理解为把整体展开成个体
展开语法在函数调用和构造数组时,将字符串和数组在语法层面展开;如果是对象时,将对象的表达式按照 key-value
的方式展开。
展开语法的使用主要有以下几种:
- 处理字符串、数组和字面量对象;
- 简化函数调用时传参问题;
- 代替 apply 方法。
1.3.1 拷贝
(1)数组拷贝
ES5:
var arr1 = [1, 2, 3];
1)forEach
var arr2 = [];
arr1.forEach(function(value){
arr2.push(value);
})
2)concat
var arr2 = [].concat(arr1);
3)slice
var arr3 = arr1.slice(0);
ES6:
var arr1 = [1, 2, 3];
var arr2 = [...arr1];
(2)字面量对象拷贝
字面量:不用new操作符创建实例
ES5:
1)循环
let obj = {a: 1, b: 2};
let copy1 = {};
for(let key in obj) {
copy1[key] = obj[key]
}
2)JSON.stringify
和 JSON.parse
let obj = {a: 1, b: 2};
let copy2 = JSON.parse(JSON.stringify(obj))
ES6:
let obj = {a: 1, b: 2};
let copy = {...obj};
以上的方法都是浅拷贝(只拷贝数组和对象的第一层结构)的过程,不会进行深拷贝。
1.3.2 在字符串中的使用
顾名思义可以把字符进行展开,从而得到一个每项都是单个字符串的数组
const arr = [...'imooc'];
console.log(arr); // ["i", "m", "o", "o", "c"]
ES5:使用 split
方法实现把字符串变成数组。
const arr = 'imooc'.split('');
console.log(arr); // ["i", "m", "o", "o", "c"]
1.3.3 在数组中的使用
在数组的操作中还有添加、合并等操作的时候,需要调用数组的 slice ()
、concat ()
、unshift ()
等方法,或者组合使用这些方法。
const arr1 = [1, 2];
const arr2 = ['a', ...arr1];
const arr3 = [...arr1, ...arr2];
console.log(arr2); // ['a', 1, 2]
console.log(arr3); // [1, 2, 'a', 1, 2]
把展开语法当成一个整体,直接放到想放到的位置上即可,扩展了操作数组的方式
1.3.4 在字面量对象中的使用
const obj1 = {a: 1, b: 2};
const obj2 = {...obj1, c: 30};
console.log(obj2); // {a:1, b:2, c:30}
const obj3 = {b: 20, c: 30};
const obj4 = {...obj2, ...obj3}; // 合并对象
console.log(obj4); // {a:1, b:20, c:30}
把数组或对象中的每一项展开到另一个数组或对象中去。
1.3.5 函数调用时传参问题
传递多个参数时,需要取出数组中的每一项再传进去
sum(data[0], data[1])
ES5:使用 apply()
对函数进行间接的调用
function sum(x, y, z) {
return x + y + z;
}
const data = [1,2,3];
console.log(sum.apply(null, data)); // 6
ES6:
function sum(x, y, z) {
return x + y + z;
}
const data = [1,2,3];
console.log(sum(...data)); // 6
1.4 剩余参数
剩余语法是将多个元素收集起来成为一个整体。
1.4.1 剩余参数在函数参数中都解决了哪些问题
ES5 中,函数经常会传入不定参数,在传入不定参数时,ES5 的给出的解决方案是通过 arguments
对象来获取函数调用时传递的参数。
arguments
对象是一个类数组对象,所谓类数组对象,就是指可以通过索引属性访问元素并且拥有 length 属性的对象。
var arrLike = {
0: 'name',
1: 'age',
2: 'job',
length: 3
}
在 ES5 的开发模式下,想要使用传递的参数,则需要按位置把对应的参数取出来。尽管 arguments
是一个类数组且可遍历的变量,但它终究不是数组,它不支持数组方法,需要使用一些特殊的方法转换成数组使用,如:借助 call
方法把 arguments
转化成一个真正的数组
function fn() {
var arr = [].slice.call(arguments);
console.log(arr)
}
fn('ES6');
// ["ES6"]
fn('imooc', 7, 'ES6');
// ["imooc", 7, "ES6"]
ES6:
function fn(...args) {
console.log(args)
}
fn('ES6');
// ["ES6"]
fn('imooc', 7, 'ES6');
// ["imooc", 7, "ES6"]
另外,还可以指定一个默认的参数:
function fn(name, ...args) {
console.log(name); // 基础参数
console.log(args); // 剩下的参数组成的数组
}
fn('ES6');
// 'ES6'
// []
fn('imooc', 7, 'ES6');
// "imooc"
// [7, "ES6"]
1.4.2 解构剩余参数
ES6 允许按照一定模式,从数组和对象中提取值,并对变量进行赋值,这被称为解构
let array = [1, 2, 3]
let [a, b, c] = array;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
let obj = {a:1, b:2, c:3}
let {a, b, c} = obj;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
在解构赋值时,可以使用剩余操作符。剩余操作符所操作的变量会匹配在解构赋值中所有其他变量未匹配到的属性。
let {a, b, ...others } = {a: 1, b: 2, c: 3, d: 4, e: 5}
console.log(a); // 1
console.log(b); // 2
console.log(others); // {c: 3, d: 4, e: 5}
在函数传参的时候也可以是和解构一起使用
function fun(...[a, b, c]) {
return a + b + c;
}
fun('1') // NaN (b 和 c 都是 undefined)
fun(1, 2, 3) // 6
fun(1, 2, 3, 4) // 6 多余的参数不会被获取到
1.5 解构赋值
1.5.1 什么是解构赋值
解构赋值就是分解一个数据的结构,并从这数据解构中提取数据的过程
解构的目的是为了简化提取数据的过程,增强代码的可读性。 把变量放在 []
或者 {}
中来获取目标对象上的对应的值。
1.5.2 数组的解构赋值
1)基本用法
数组的解构赋值是,通过新建一个数组,数组内的变量和目标数组是一一对应的,按照索引的方式去获取值,然后赋值给指定索引上的变量。
在 ES6 之前,想获取数组中的值,需要通过 Array[index]
的方式来获取值
ES6 的解构赋值:
var [a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20
2)默认值
在解构一个未知的数组时,需要对未能取的值的变量赋一个默认值,为了防止从数组中取出一个值为 undefined
的对象,可以在表达式的左边的数组中为任意变量预设一个默认的值。
let [a=3, b=9] = [1]; // a=3 b=9
let [a, b = 1] = [10, '']; // a=10, b=''
let [a, b = 1] = [10, undefined]; // a=10, b=1
在 ES6 中,只有当一个数组成员严格等于 undefined,默认值才会生效
3)交换变量
在 ES6 之前如果我们想交换两个变量的值时,我们需要借助一个中间的值来过渡
使用 ES6 解构赋值:
var a = 1;
var b = 4;
[a, b] = [b, a] // a=4, b=1
4)跳过某项值使用逗号隔开
在解构数组时,可以忽略不需要解构的值,可以使用逗号对解构的数组进行忽略操作
var [a, , , b] = [10, 20, 30, 40];
console.log(a); // 10
console.log(b); // 40
5)剩余参数中的使用
var [a, b, ...rest] = [10, 20, 30, 40, 50];
1.5.3 对象的解构赋值
和数组基本类似
1)重命名属性
var {a:x = 8, b:y = 3} = {a: 2};
console.log(x); // 2
console.log(y); // 3
这里把 a 和 b 的变量名重新命名为 x 和 y。
1.5.4 解构字符串
const [a, b, c, d, e] = 'imooc';
字符串也可以被当作对象来解构,但是由于字符串方法只有 length 属性,所以只能解构出 length 的值。
let {length : len} = 'hello';
console.log(len); // 5
1.5.5 解构复杂的数据结构
对于解构这种既有对象又有数组的数据解构,我们要先声明一个和目标对象有着相同的数据嵌套,才能去拆解目标对象。这里解构的是 data 中的 a 和 b 的值,而且把 a 和 b 重命名为 newA 和 newB。
var data = {
a: 1,
arr: [
{
b: 2
c: 3
}
]
}
var {
a: newA,
arr: [
{
b: newB
}
]
} = data;
console.log(newA, newB) // 1, 2
1.6 模板字符串
有了模版字符串后就不需要使用加号了,通过使用 ````(反引号)中直接定义多行字符串和变量的拼接。如果是变量就包裹在 ${}
的大括号中。另外,在模版字符串中还可以使用表达式和使用函数标签的方式来增加字符串复杂的功能,扩展了字符串的功能。
1.6.1 字符串拼接
在 ES6 中可以使用反引号来声明一个字符变量的值。
var name = '慕课网';
var lang = 'ES6';
console.log(`这是${name}的${lang}教程!`);
// 这是慕课网的ES6教程!
1.6.2 多行字符串
ES6 的模版字符串,不需要添加换行符,反引号里的内容就是最后结果的直观表达
1.6.3 逻辑运算
console.log(`小明是${age > 18 ? '成年人' : '未成年人'}`)
1.6.4 带标签的模版字符串
function isAdult(strArr, age) {
var s1 = strArr[0]; // strArr是字符串被变量分割后的数组
var str = ''
if (age > 18) {
str = age + '岁成年了';
} else {
str = age + '岁还未成年';
}
return `${s1}${str}`;
}
var test = isAdult`imooc今年${age}`
console.log(test); // imooc今年7岁还未成年
带标签的模板字符串可以把模版字符串的内容作为参数传到函数中,使用方式和函数的调用略有不同 fn${expression}
函数 fn 会接收后面表达式作为参数,第一个参数是表达式中所有字符串组成的数组,第二个以后的参数是表达式中的变量的值,和变量是一一对应的。
1.7 箭头函数
使用 “箭头” =>
来定义一个函数,它没有自己的 this
、arguments
、super
或 new.target
,箭头函数表达式更适用于那些本来需要匿名函数的地方,但它不能用作构造函数。
1.7.1 语法
(1)由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
(2)当函数体内有返回值时,ES6 的箭头函数可以省略大括号
var sum = (num1, num2) => num1 + num2;
当传递的参数只有一个时,圆括号也可以省略:
var sum = num => num + 10;
(3)在定义函数时,往往需要给参数添加默认值,ES6 中可以直接在圆括号中进行赋值
var sum = (num1, num2 = 2) => num1 + num2;
console.log(sum(1)) // 3
(4)剩余参数
函数在接收不定参数时,可以使用剩余运算符把调用函数时传入的参数聚拢起来成为一个参数数组
(类似 function
中的 arguments
对象,但 arguments
不是数组,不能直接使用)
var fun = (param1, param2, ...rest) => {
console.log(param1)
console.log(param2)
console.log(rest)
};
fun(1, 2, 3, 4, 5);
// 1
// 2
// [3, 4, 5]
1.7.2 没有this
箭头函数不会创建自己的 this
,它只会从自己的作用域链的上一层继承 this
,setTimeout
会改变 this
的指向
箭头函数的 this 永远指向的是父级作用域。
1.7.3 不绑定 arguments
在箭头函数中去取 arguments
时会报引用错误,没有定义的 arguments
arguments
的主要作用是获取所有调用函数时所需要传入的参数,在箭头函数中使用剩余参数 ...args
,在函数内可以直接使用。
function foo(...args) {
console.log(args)
}
foo(1); // [1]
foo(1, 2, 3); // [1, 2, 3]
1.7.4 notes
- 不能用作构造器
- 没有 prototype 属性
- 不能用作构造器