一、变量的声明:
1.let(限制在当前作用域使用)
ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效
例1:下面的代码如果使用var,最后输出的是10
//es5
var a=[];
for(var i=0;i<10;i++){
a[i]=function(){
console.log(i); //10
}
}
a[6]();
说明:变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 10。
例2:若使用let,声明的变量仅在块级作用域内有效,最后输出的是 6
//es6
let a=[];
for(let i=0;i<10;i++){
a[i]=function(){
console.log(i); //6
}
}
a[6]();
说明:变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。
Q:如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?
A:因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。
例3:for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for(let i=0;i<3;i++){
let i='abc';
console.log(i);
}
//abc
//abc
//abc
说明:输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。
2.const(引入了常量,常量即只读、不可修改的值)
var a=10;
console.log(a); //10
a=20;
console.log(a); //20
const PI=3.14;
console.log(PI); //3.14
PI=3.15;
console.log(PI); //Uncaught TypeError: Assignment to constant variable.
补充:
代码块:相当于函数,自动执行,将同类的代码利用代码块封装
{
}
二、变量的解构赋值
1.数组的解构赋值
本质:“模式匹配”,即只要等号两边的模式相同,左边的变量就会被赋予对应的值
- 基本用法:
(1)es6中的写法
let [a, b, c] = [1, 2, 3];
说明:可以从数组中提取值,按照对应位置,对变量赋值。
(2)使用嵌套数组进行解构
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 // []
(3)解构不成功,变量的值就等于undefined
let [foo] = [];
let [bar, foo] = [1];
说明:以上两种情况都属于解构不成功,foo的值都会等于undefined。
(4)不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功
let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
说明:上面两个例子,都属于不完全解构,但是可以成功。
(5)若等号右边不是数组,则将会报错
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
- 默认值:
(6)解构赋值允许指定默认值
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
注:ES6 内部使用严格相等运算符(===),判断一个位置是否有值
(7)只有当一个数组成员严格等于undefined,默认值才会生效
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
说明:若一个数组成员是null,默认值就不会生效,因为null不严格等于undefined。
(8)默认值可以引用解构赋值的其他变量,但该变量必须已经声明
let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError: y is not defined
说明:最后一个表达式之所以会报错,是因为x用y做默认值时,y还没有声明。
三、字符串的扩展
es5:给字符串绑值 直接是拼接(++)
es6:使用模板字符串来动态绑值 ( ``)
let a=10;
let str=`我的年龄:${a}岁!`;
console.log(str);
四、函数的扩展
1.函数的默认值
es5:
function log(x, y) {
y = y || 'World';
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World
说明:检查函数log的参数y有没有赋值,如果没有,则指定默认值为World。这种写法的缺点是若参数y赋值了,但对应的布尔值为false,则该赋值不起作用。就像上面代码的最后一行,参数y等于空字符,结果被改为默认值。
为了避免这个问题,通常需要先判断一下参数y是否被赋值,如果没有,再等于默认值
if (typeof y === 'undefined') {
y = 'World';
}
es6:
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
说明:允许为函数的参数设置默认值,即直接写在参数定义的后面。
注:
(1)参数变量是默认声明的,所以不能用let或const再次声明
function foo(x = 5) {
let x = 1; // error
const x = 2; // error
}
说明:参数变量x是默认声明的,在函数体中,不能用let或const再次声明,否则会报错。
(2)使用参数默认值时,函数不能有同名参数
// 不报错
function foo(x, x, y) {
// ...
}
// 报错
function foo(x, x, y = 1) {
// ...
}
// SyntaxError: Duplicate parameter name not allowed in this context
(3)参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的
let x = 99;
function foo(p = x + 1) {
console.log(p);
}
foo() // 100
x = 100;
foo() // 101
说明:参数p的默认值是x + 1。这时,每次调用函数foo,都会重新计算x + 1,而不是默认p等于 100。
2.箭头函数
- 基本用法:
(1)ES6 允许使用“箭头”(=>)定义函数
var f = v => v;
// 等同于
var f = function (v) {
return v;
};
(2)若箭头函数不需要或需要多个参数时,则使用一个圆括号代表参数部分
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
(3)若箭头函数的代码块部分多于一条语句,就要用大括号将它们括起来,并且使用return语句返回
var sum = (num1, num2) => { return num1 + num2; }
(4)由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错
// 报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
(5)特殊情况,虽然可以运行,但会得到错误的结果
let foo = () => { a: 1 };
foo() // undefined
说明:原始意图是返回一个对象{ a: 1 },但是由于引擎认为大括号是代码块,所以执行了一行语句a: 1。这时,a可以被解释为语句的标签,因此实际执行的语句是1;,然后函数就结束了,没有返回值。
解决方法:见(4)
(6)若箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了
let fn = () => void doesNotReturn();
(7)箭头函数可以与变量解构结合使用
const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {
return person.first + ' ' + person.last;
}
- 使用注意点:
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象;
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误;
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替;
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数
- 关于this对象:指向是可变的,但是在箭头函数中,它是固定的
例1:计时器中的this
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });
// id: 42
说明:setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 42}),所以输出的是42。
例2:计时器中的this
箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数
setInterval(() => this.s1++, 1000);
// 普通函数
setInterval(function () {
this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
说明:Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(即Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100 毫秒之后,timer.s1被更新了 3 次,而timer.s2一次都没更新。
例3:构造函数中的this
let a=[1,2,5,7,3,8,4];
Array.prototype.mysort=function(){
console.log(this);
}
var n=new Array(1,2,3,4,5);
n.mysort();
说明:此时的this指当前函数前面的对象Array,输出了整个数组集合。
以防在方法里使用外面的对象:
法一:箭头函数保持上下文对象一致
let a=[1,2,5,7,3,8,4];
Array.prototype.mysort=()=>{
console.log(this); //window
}
var n=new Array(1,2,3,4,5);
n.mysort();
法二:
let a=[1,2,5,7,3,8,4];
let that=this;
Array.prototype.mysort=function(){
console.log(that);
}
var n=new Array(1,2,3,4,5);
n.mysort();
例4:枚举对象中的this
let student={
init(){
document.addEventListener("click",function(){
console.log(this); //this指向当前document对象
});
}
}
student.init();
let student={
init(){
document.addEventListener("click",()=>{
console.log(this); //this指向init对象
});
}
}