我只想说学习ES6真的好痛苦,但是为了加深印象,巩固基础我还是自己整理一下吧,如果要很详细的整理,我觉得会很累,所以我就很随意啦。
一,let,const
1,不存在变量提升:先使用再let声明 会报错 ReferenceError
2,暂时性死区:在let声明变量完成之前,都属于变量的死区,任何使用这个变量的都会抛出一个错误,例如let x = x;//ReferenceError
这个就错在还没有声明的时候就调用x,一定要等到声明完成之前
3,不允许重复声明:不允许在相同作用域中重复声明同一个变量function(){var a;let a;}
(例如:不可以在函数内部重新声明参数)
4,es5只有全局作用域和函数作用域;es6新增了块级作用域,es6允许块级作用域的任意嵌套
内层作用域无法读取外层作用域的变量,内层作用域可以定义外层作用域的同名变量
例如:
function a() {
let i = 1;
if (true) {
let i = 2;//内层定义外层的同名变量
}
console.log(i);//1
}
a();
function a() {
var i = 1;
if (true) {
var i = 2;
}
console.log(i);//2
}
a();
5,const一旦声明必须初始化,而且不能改变,否则会报错,const的作用域和let命令的作用域一样,都是块级作用域,const声明的常量也是不提升,存在死区,只在声明的块级作用域内有效,不可重复声明
6,全局变量和顶层对象的属性的关系:在es5中这两者是相等的(此处是被认为是js设计的最大败笔之一)。es6中全局变量和顶层对象的属性将逐渐脱钩(但是为了保持兼容性):var命令和function命令声明的全局变量依旧是顶层对象的属性,另一方面规定:let const class命令声明的全局变量,不属于顶层对象的属性。
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1
let b = 1;
window.b // undefined
二,变量的解构赋值
1,数组的解构赋值:
(1)let [a,b,c] = [1,2,3] //a=1 b=2 c=3
只要等号两边的模式相同,左边的变量就会被赋予对应的值,如果解构不成功变量的值就是undefined。
(2)还有一种是不完全解构:
let [x, y] = [1, 2, 3];x // 1 y // 2
这样也是可以成功的
数组的解构赋值等号右边的如果不是数组就会报错(准确来说不是可遍历的结构)
2,对象的解构:
对象的解构赋值是按照属性名的变量名进行赋值的,并不是数组那样固定位置的模式。
注意:对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined
上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。
3,字符串的解构赋值:
字符串在解构赋值时,字符串被转换成类似数组的对象。来看代码:
const [a,b,c,d,e] = 'hello'
a//'h'
b//'e'
c//'l'
d//'l'
e//'o'
4,数值和布尔值的解构赋值:
数值和布尔值在解构赋值时,(如果在等号右边的不是对象或者数组,都会转换成对象),会被转换成对象,在被包装成的对象都有toString
属性,看代码:
let [toString:s] = 123;
s === Number.prototype.toString //true
let [toString:s] = true;
s === Boolean.prototype.toString //true
由于undefined 和null无法转换成对象,所以对这两个进行解构赋值会报错
说了这么多,变量的解构赋值有什么用呢?
1,交换变量的值
以前的交换变量的方法:
let x=1;
let y=2;
let z;
z=x;
x=y;
y=z;
可能这样的代码对早已熟悉的你来讲可能一点难度都没有,但是现在有一种很简单粗暴的方式:
let x = 1;
let y = 2;
[x,y] = [y,x];
这样的代码看起来是不是很清楚,一眼就可以看出你的代码是干什么用的;
2,从函数返回多个值
函数只能返回一个值,要返回多个值只能利用数组和对象,利用解构赋值的方法就可以非常方便的取出这些值
function a(){
return[1,2,3];
}
let [a,b,c] = a();
function a(){
return{
foo:1,
bar:2
};
}
let {foo,bar} = a();
3,提取jason数据
let jasonData = {
id:1508010228,
status:"ok",
data:[13,465]
}
let {id,status,data:number} = jasonData;
console.log(id,status,number);//42, "ok",[13,465]
三,函数的扩展
1,箭头函数:
let f = function(num1,num2){
return num1+num2;
}
//上面的函数等同于下面的箭头函数
let f = (num1,num2)=>{
return num1+num2;
}
//或者
let f = (num1,num2)=> num1+num2;
如果箭头函数里要返回一个对象的话,对象要加一个圆括号:
let person = (name,age)=>({name:'ld',age:21})
console.log(person().name)//ld
console.log(person().age)//21
箭头函数有几个需要注意的:
- this对象是定义时的对象,不是使用时候的对象
- 不可以当作构造函数 也就是不能使用new一个实例对象
- 不可以使用argument对象 该对象不在函数体内 如果要用可以用rest参数代替
在这里举几个this对象的例子
function foo(){
setTimeout(function(){
console.log("id="+this.id)
},100)
}
id = 21;
foo.call({id:42});
//控制台会打印出 id=21
我们知道在普通函数里 this是使用时候的对象 ,上面代码中 this对象所在的匿名函数是setTimeout函数的参数,所以当100毫秒后执行到setTimeout函数的时候(也就是使用的时候)this对象指向的是全局对象window 我们在全局作用域中为id赋值为21 所以控制台打印的是id=21
但是将setTimeout函数里面的匿名函数改为箭头函数时:
function foo(){
setTimeout(()=>{
console.log("id="+this.id)
},100);
}
id = 21;
foo.call({id:42});
//控制台会打印出 id=42
setTimeout的参数时一个箭头函数,这个例子中箭头函数定义生效是在foo函数生成时,真正执行时在100毫秒以后,因为箭头函数中this时定义时的对象,所以this.id指向的时本例中的42
接下来再换个花样:
let foo=()=>{
setTimeout(()=>{
console.log("id="+this.id)
},100);
}
id = 21;
foo.call({id:42});
//控制台会打印出id=21
我将上面的第二个例子又改了改,将foo函数的定义也换成箭头函数的形式,结果和第一个例子一样输出id=21;我是这样理解的:
setTimeout里的参数是箭头函数,所以this指向的是箭头函数定义生效时的对象,也就是foo函数,但是foo函数也是箭头函数形式的,它所在的对象应该是全局对象window 所以就变成了第一个例子。
2,嵌套的箭头函数
将一个元素插入到数组里的逻辑代码实现
//插入一个指定元素到数组中得逻辑 将什么插入到什么得那个位置里
function insert(value) {
return {
into: function (array) {
return {
after: function (afterValue) {
array.splice(array.indexOf(afterValue) + 1, 0, value);
//splice():改变原本数组,向数组中添加或删除项目,返回被删除的项目
return array;
}
}
}
}
}
let a = [1, 3, 4];
insert(2).into(a).after(1);
console.log(a);
splice函数详细用法见详细见http://www.w3school.com.cn/jsref/jsref_splice.asp
//es6写法
let insert = (value) => ({
into: (array) => ({
after: (afterValue) => {
array.splice(array.indexOf(afterValue) + 1,0 , value);
return array;
}
})
})
let a = [1,3,4];
insert(2).into(a).after(1);
console.log(a);
insert(5).into(a).after(4);
console.log(a)
3,尾递归和尾调用优化
尾调用优化:函数调用时,在内存中会形成一个调用帧,用来保存调用位置和内部变量的信息,例如,在A函数里调用B函数,在A函数上方就会形成一个B函数的调用帧,以此类推,就会形成一个调用栈。
由于尾调用是在函数的最后操作,所以不需要保留外层函数的调用帧,以为调用位置和内部变量等信息不会在用到了,只要直接调用内层函数的调用帧就可以了(注意:内层函数如果使用到外层函数的变量,则外层函数的调用帧不会被内层函数的调用帧取代,也就是不会进行尾调用优化)
//尾调用优化
function a(){
return b();
}
上面的尾调用优化是一个函数调用另一个函数,递归则是自己调用自己;
尾递归:一般的递归,在内存中会产生大量的调用帧,有可能会造成栈溢出,如果使用尾递归 则不会发生这种情况,只会产生一个调用帧