1. 块级作用域
es5中有一个比较坑的地方就是变量提升(variable hoisting),variable hoisting的根本原因就是es5中没有块级作用域。看一下下面的代码:
var a = 10;
var b = 20;
function add(flag) {
if (flag) {
var b = 10;
return a + b;
}
return a + b;
}
console.log(add());// NaN
console.log(add(true));// 20
注意,为什么add()
没有输出结果呢?因为变量提升。上面代码中,var b = 10
定义在if
中,由于es5没有块级作用域(函数体是es5中仅有的作用域块),所以变量b相当于在函数内部重新声明了一次,类似下面代码:
var a = 10;
var b = 20;
function add(flag) {
var b; // undefined
if (flag) {
b = 10;
return a + b;
}
return a + b; // if flag flase, then b is undefined
}
console.log(add());
console.log(add(true));
使用es6中的let
关键字,就没有这么恶心了:
var a = 10;
var b = 20;
function add(flag) {
if (flag) {
let b = 10;
return a + b;
}
return a + b;
}
console.log(add());// 30
console.log(add(true));// 20
值得一提的是,在for循环中使用var来定义变量,也会遇到变量提升的坑:
var arr = [];
for (var i = 0; i < 3; i++) {// i 是全局变量
arr.push(function () {
return i;
});
}
console.log(i); // 3, 由此可见i为全局变量
// arr中保持的三个函数的返回值均是全局变量i
for (var j = 0; j < 3; j++) {
console.log(arr[j]()); // 3 3 and 3
}
如果不使用es6中的let
,需要使用javascript中的闭包来救火了:
var arr = [];
for (var i = 0; i < 3; i++) {
(function (i) {
arr.push(function () {
return i; // 局部变量
});
})(i);
}
console.log(i);
for (var j = 0; j < 3; j++) {
console.log(arr[j]()); // return 0, 1, 2
}
使用es6,在for
循环中使用let
定义变量:
var arr = [];
for (let i = 0; i < 3; i++) {
arr.push(function () {
return i; // 局部变量
});
}
console.log(i); // error, 在全局中找不到变量i
for (var j = 0; j < 3; j++) {
console.log(arr[j]()); // return 0, 1, 2
}
2. 箭头函数
在es5中,this
关键字像幽灵一般跳来跳去,不同的场景下this
指向的对象不同。常见的坑是在回调函数中使用this
,this
会脱离当前对象的上下文,而指向全局变量window
对象。看下面掉进坑的代码:
function Person(firstname, lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
Person.prototype.getName = function () {
return this.firstname + this.lastname;
}
Person.prototype.getNameInCallback = function () {
setTimeout(function () {
console.log(this.firstname, this.lastname);
}, 1000);
}
var p = new Person('jianyong', 'lee');
console.log(p.getName()); // jianyonglee
p.getNameInCallback(); // undefined
注意到最后的p.getNameInCallback()
输出undefined。这个问题如何解决呢?如果不是使用es6来救火的话,可以考虑使用下面两个技巧:
1. 使用bind()
函数,为回调函数绑定上下文:
function Person(firstname, lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
Person.prototype.getName = function () {
return this.firstname + this.lastname;
}
Person.prototype.getNameInCallback = function () {
setTimeout(function () {
console.log(this.firstname, this.lastname);
}.bind(this), 1000);
}
var p = new Person('jianyong', 'lee');
console.log(p.getName()); // jianyonglee
p.getNameInCallback(); // jianyong lee
2.使用其他变量代理this
function Person(firstname, lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
Person.prototype.getName = function () {
return this.firstname + this.lastname;
}
Person.prototype.getNameInCallback = function () {
var ctx = this; // 用ctx变量记录当前对象
setTimeout(function () {
console.log(ctx.firstname, ctx.lastname); // 这里使用ctx
}, 1000);
}
var p = new Person('jianyong', 'lee');
console.log(p.getName()); // jianyonglee
p.getNameInCallback(); // jianyong lee
如果使用es6中的箭头函数就没这么多事:
function Person(firstname, lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
Person.prototype.getName = function () {
return this.firstname + this.lastname;
}
Person.prototype.getNameInCallback = function () {
setTimeout(() => {
console.log(this.firstname, this.lastname);
}, 1000);
}
var p = new Person('jianyong', 'lee');
console.log(p.getName());
p.getNameInCallback();