最近要好好学下ES6,下文主要是对箭头函数和const,let的稍微详细点的理解。里面我主要的学习方法是写小例子,然后去分析ES6通过babel转码后有什么区别和和两者的对比。babel的使用可以看下:bable install,这里不涉及babel的使用。
箭头函数(Arrow Functions)
箭头函数最好的一点当然是箭头函数preserve the context of this from its lexical scope,保存我们上下文作用域中的this的指向。因为一般我们可能会遇到下面这些问题:
1 | function Person(name){ |
[ 'undefinedcld', 'undefinedseven' ]
,最后结果是这个。因为当我们使用nested functions
的时候,this的指向会发生变化。所以,我们没有箭头函数以前,是这样解决的:
1 | Person.prototype.add = function(arr){ |
或者是通过bind,这样解决的:1
2
3
4
5Person.prototype.add = function(arr){
return arr.map(function(item){
return this.name + item
}.bind(this))
}
或者是这样解决的,通过:that=this:1
2
3
4
5
6Person.prototype.add = function(arr){
var that = this
return arr.map(function(item){
return that.name + item
})
}
以上三种方法都能够解决。但是当我们有了箭头函数,就更加容易了:1
2
3Person.prototype.add = function(arr){
return arr.map((item)=>this.name+item)
}
而且最好的就是箭头函数大部分浏览器都支持啦。
而且对于返回值只有一个的时候,箭头函数非常简洁,比如:1
2
3var arr = [1,3,4]
console.log(arr.map(function(item){return item*item}))
console.log(arr.map((item)=>item*item))
通过babel转码后,箭头函数是这样的,跟自己写的一样:1
console.log(arr.map(function(item){return item*item}))
let and const
let与var之变量提升的区别
ES6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。相当于增加了块级作用域。
先看下下面的输出结果:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17var global = "global"
function test1(para){
if(para){
var global = "inner"
return global
}
return global
}
function test2(para){
if(para){
let global = "inner"
return global
}
return global
}
console.log(test1(false))
console.log(test2(false))
结果分别是:undefined,global。第一个是因为var 有变量提升,而let没有变量提升。我们可以看一下当我们通过babel编译以后会得到什么结果:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 ;
var global = "global";
function test1(para) {
if (para) {
var global = "inner";
return global;
}
return global;
}
function test2(para) {
if (para) {
var _global = "inner";
return _global;
}
return global;
}
console.log(test1(false));
console.log(test2(false));
babel把我们的let变成了var,并且把变量名称换了。正是因为let不存在变量提升,所以我们要先声明再使用。否则会报错,而不是输出undefined。
再来看下面这个例子:1
2
3
4{
var a = 3;
let b =4;
}
会被编译成下面这样。所以最后会报错。因为我们在外部打印b的时候,是ReferenceError: b is not defined
。1
2
3
4
5
6{
var a = 3;
var _b = 4;
}
console.log(a)
console.log(b)
let和var在闭包中的作用
由于i是var声明的。所以是全局变量。全局范围内都有效。所以当我们不管是调用哪一个a,都是输出最后一个i,也就是3。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var a = []
for(var i=0;i<4;i++){
a[i] = function(){
console.log(i)
}
}
a[2]()
var b = []
for(let i=0;i<4;i++){
b[i] = function(){
console.log(i)
}
}
b[2]()
我看了下babel编译后的结果。编译后它处理的方法是把函数声明提到了外面。我们通常也可以用里面(function(i){})(i)这中方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var a = [];
for (var i = 0; i < 4; i++) {
a[i] = function () {
console.log(i);
};
}
var b = [];
var _loop = function _loop(_i) {
b[_i] = function () {
console.log(_i);
};
};
for (var _i = 0; _i < 4; _i++) {
_loop(_i);
}
可惜了,我现在用谷歌来测试还是不能用。期待那么一天。火狐还是很好的。
let不允许重复声明
我们用var 的时候,都可以一直就重新声明。想重新声明就重新声明。但是let不允许在相同作用域内,重复声明同一个变量。比如先var a=1,再let a=2,这是不行的。再比如let a=1;let a=2这也是不行的。那比如说,我在一个函数内传入了一个param,我们let param = other值,这也是不行的。但是注意这前提室相同作用域如果是不同作用域肯定就是可以了,比如先let a=1,然后{let a=1},这样开辟了一个新的作用域。肯定是可以重复声明的。内层作用域可以定义外层作用域的同名变量。
1 | function f() { |
需要块级作用域let的原因
- 由于var是可以随时重新声明赋值的,又因为有变量提升这么回事,所以经常就会出现undefined的错误。内部变量覆盖了外部变量。
- 消除刚才我们提到的,由于循环计数的原因,导致了变量泄露成了全局变量。
我在想是不是可以这么理解,当我们使用了{}这个的时候,{}内部的和{}外部的元素就形成了两个不同的块级作用域。无论是我们直接{}还是通过if(){}或者function(){}这样。我不知道我这样总结的是不是太绝对了。
我们先前的是:内部作用域可以访问外部作用域。因为作用域链的存在。外部作用域不能访问内部作用域。因为作用域链只能向上查找。let的出现并没有影响这一点。
而且正是因为let的出现,以后估计我们的匿名函数(用于保护全局变量)用的也就会少很多了。
1 | (function () { |
1 | { |
const是single-assignment
const与我们先前学习别的语言一样,代表常量,一旦赋值就不可以改变。这也意味着必须在声明的时候赋值,否则后面不管再怎么赋值,都是错误的了。
这又包括两种情况,一种是普通模式,也就是没有用use strict,这种情况是重新赋值不会报错,但是值是不会变的,一直都是你最开始赋的初值。第二种情况是用了use strick,这种情况,是会报错的。
还有一点要注意,就是const的作用域是跟let一样,在当前的块级作用域里面的。外部作用域无法访问当前定义的const的作用域。
并且const也是不存在变量提升。这是个好事,让我们形成先定义,再使用的习惯,并且也可以有效的避免undefined这种情况发生。
还有一点,是我以前没有注意到的。感谢阮一峰老师的这个书。真棒。就是如果const定义的常量指向的是一个对象。这个时候,它实际上指向的是当前对象的地址。这个地址是在栈里面的,而这个真实的对象是在堆里面的。所以,我们使用const定义这个对象后,是可以改变对象的内容的。但是这个地址是不可以变的。意思也就是不可以给这个对象重新赋值,比如const foo={},foo = {},即使是这样foo好像什么都没有改变,但还是错误的。(然而我在普通模式下,并没有报错。。。)而foo.username=’cld’,这是完全可以的。这跟javascript存储引用对象的值的方式有密切的关系。
1 | const obj = Object.freeze({}) |
这段代码会在babel下转成这样:1
2
3
4
5var obj = Object.freeze({});
var b = {};
obj.name = "cld";
b.name = "seven";
事实是不管我们是以const 然后freeze object,或者是不freeze,babel都是会把它转成var这种形势。因为babel本身的作用就是transform我们的es6的代码的。当然我们运行的话,会报错:TypeError: Can't add property name, object is not extensible
。
在阮一峰老师的书里,我看到了他写的这样一块代码:1
2
3
4
5
6
7
8var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, value) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
看到了以后,眼睛亮了,因为记得前1,2个月面试的时候,有个面试官问我,知不知道有一个函数,可以直接获得所有obj的key值。我确实没用过js里的这个,只知道php里面有类似的函数是array_keys(),后来下去查,也没查到。就一直记在心里。今天一看这段代码就知道了是Object.keys(),真好。比如下面这个测试:
1 | var obj = {'username':['name1','name2'],'password':'seven'} |
打印结果是:
[ ‘username’, ‘password’ ]
[ ‘name1’, ‘name2’ ]
有点偏题了。上面我的那段阮一峰老师的代码是为了让除了将对象本身冻结,对象的属性也冻结。
然后看下babel转码后,会得到啥样的结果:1
2
3
4
5
6
7
8var constanize = function constanize(obj) {
Object.freeze(obj);
Object.keys(obj).forEach(function (key, value) {
if (_typeof(obj[key]) == 'object') {
constanize(obj[key]);
}
});
};
babel把箭头函数给转了,然后加了引号。但是typeof前面加_下划线是什么呢?我知道一般来说加下划线的变量为私有变量。。??
还有就是加入let后,就算let是在window环境下声明的。通过let定义的属性,也不能通过window获得。var声明的可以通过window对象获得。