一、let&&const 块级作用域 const也是块级作用域
let、const不存在变量提升
// var 的情况 console.log(foo); // 输出undefined var foo = 2; // let 的情况 console.log(bar); // 报错ReferenceError let bar = 2;
var与let的区别:
(1)var定义的变量,作用域是整个封闭函数,是全域的。通过let定义的变量,作用域是在块内的;
(2)关于变量的提升。 var可以变量提升,但是let不存在变量提升。
(3)let不允许在相同的作用域内,重复声明同一个变量。
// 报错 function func() { var a = 1; let a = 10; } //如果在当前作用域中嵌套另一个作用域,便可在嵌套作用域中用let声明同名变量 function func() { var a = 1; if(condition){ let a = 10; } }
var a=10; function foo(){ console.log(a); let a=20; } foo()//ReferenceError //let和const声明可以让变量在其作用域上受限于它所使用的块、语句或表达式,与var不同的是,这些变量没有被提升,并且有一个所谓的暂时死区(TDZ)试图访问TDZ中
的这些变量将引发ReferenceError错误,因为只有执行到达声明时才能访问它们。
为什么需要块级作用域:
ES5只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景
- 第一种场景,内层变量可能会覆盖外层变量。
var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined
上面代码的原意是,
if
代码块的外部使用外层的tmp
变量,内部使用内层的tmp
变量。但是,函数f
执行后,输出结果为undefined
,原因在于变量提升,导致内层的tmp
变量覆盖了外层的tmp
变量。
-
第二种场景,用来计数的循环变量泄露为全局变量。
var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5
上面代码中,变量i
只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
- 第三种场景,循环中的函数
//(1)在循环中使用var声明 //循环中每次迭代同时共享着变量i,循环内部创建的函数全都保留了对相同变量的引用,循环结束时变量i的值为10 var a = []; for (var i = 0; i < 10; i++) { a.push(function () { console.log(i); }); } a.forEach(function(item){ item();//输出10次数字10 }) //(2)为解决以上问题,一般使用立即执行函数,以强制生成变量的副本,如 for (let i = 0; i < 10; i++) { a.push((function (num) { return function(){ console.log(num); } })(i)); } //(3)在循环中使用let声明 //每次循环时let声明都会创建一个新变量i,并将其初始化为当前值,所以内部函数每次都能得到属于他们自己的i的副本 for (let i = 0; i < 10; i++) { a.push(function () { console.log(i); }); } a.forEach(function(item){ item();//输出0-9 })
const用于声明一个常量,设定后值不会发生改变,强行对其进行重新赋值会报错。
这个不可变对于基础类型(按值访问)而言是值不可变;而对于引用类型(按引用访问)是引用地址不变,比如用const声明对象后,可以修改该对象的属性值。
const num=123; num=456; console.log(num);//Uncaught SyntaxError: Identifier 'num' has already been declared const obj={ a:2 } //可以修改对象属性的值 obj.a=4; console.log(obj.a);//4 //抛出语法错误 obj={ a:4 }
1.1 新增数据类型:Symbol
Symbol功能类似于一种标识唯一性的ID,通常情况下,我们可以通过调用Symbol()函数来创建一个Symbol实例。
let s1=Symbol();
或者可以传入一个可选的字符串参数
let s2=Symbol('another symbol');
由于Symbol是一种基础数据类型,所以当我们使用typeof去检查它的类型的时候,会返回一个属于自己的类型symbol。
另外,每个Symbol实例都是唯一的,因此,当你比较两个Symbol实例的时候,将总会返回false。
let s2=Symbol('another symbol'); let s3=Symbol('another symbol'); s2===s3 //false
二、变量的解构赋值
ES6允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,称为解构。本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
let[a,b,c]=[1,2,3];//等同于let a = 1;let b = 2;let c = 3;
如果解构不成功,变量的值就等于undefined
let [bar, foo] = [1]; console.log(bar);//1 console.log(foo);//undefined
解构不仅可以用于数组,还可以用于对象。
let { foo, bar } = { foo: "aaa", bar: "bbb" }; foo // "aaa" bar // "bbb"
三、箭头函数
ES6 允许使用“箭头”(=>
)定义函数。
var f = v => v;等同于 var f = function(v) { return v; };
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
var f = () => 5; // 等同于 var f = function () { return 5 }; var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; };
箭头函数与传统的js函数的区别:
(1)箭头函数体内的this对象,就是函数定义时所在的对象,而普通函数的this是函数运行时所在的对象。且箭头函数内部的this值不可被改变
(2)不可以当作构造函数,也就是说不可以使用new命令,否则会抛出一个错误。
因为箭头函数在创建时this对象就绑定了,更不会指向对象实例。
//普通函数 function fun(){ console.log('普通函数'); } var p=new fun();//普通函数 //箭头函数 var fun=()=>{ console.log('箭头函数'); } var p=new fun();//Uncaught TypeError: fun is not a constructor
(3)不可以使用arguments对象,该对象在函数体内不存在。
//普通函数 function fun(x,y){ console.log(arguments[0]); } fun('a','b');//a //箭头函数 var fun=(x,y)=>{ console.log(arguments[0]); } fun('a','b');//ƒ (e,t){return new v.fn.init(e,t,n)}
(4)不可以使用yield命令,因此箭头函数不能用作Generator函数。
(5)没有原型,因为不可以通过new关键字调用箭头函数,因而没有构建原型的需求,所以箭头函数不存在prototype这个属性。
ES5与ES6中函数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
一次都没更新。
this
指向的固定化,并不是因为箭头函数内部有绑定this
的机制,实际原因是箭头函数根本没有自己的this
,导致内部的this
就是外层代码块的this
。正是因为它没有this
,所以也就不能用作构造函数。
四、类
ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类,与多数传统语言类似。
为了简化原型链继承,es6的class大大减少了相关代码,不用再去构建有序的原型链,直接用extend就能实现继承。
//原型 function Student(name) { this.name = name; } Student.prototype.hello = function () { alert('Hello, ' + this.name + '!'); } //类 class Student { constructor(name) { this.name = name; } hello() { alert('Hello, ' + this.name + '!'); } } var xiaoming = new Student('小明'); xiaoming.hello();
class继承
用class定义对象的另一个好处就是继承更方便了,直接用extends来实现
class PrimaryStudent extends Student{ constructor(name,grade){ super(name); this.grade=grade; } myGrade(){ console.log('my grade is'+this.grade); } } var stu=new PrimaryStudent('an','32'); stu.hello();//hello,an stu.myGrade();//my grade is32
五、字符串模板
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
六、默认参数值
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
七、for of/in遍历
区别
(1)for in遍历的是数组的索引,而for of遍历的是数组的元素值;
(2)for in更适合遍历对象,不要使用for in遍历数组;
for...in
Array.prototype.method=function(){ console.log(this.length); } var myArr=[1,2,3,4,5]; console.log('for...in遍历'); for(var i in myArr){ console.log(myArr[i]) }
遍历结果:
当你需要向数组中添加额外的方法(或另一个对象)时,for...in
会遍历数组所有可枚举的属性,意味着如果向数组的原型中添加任何其他属性,这些属性也会出现在循环中。
for/in与Object.keys(obj)的区别:
最主要的区别是for/in会走原型链,而Object.keys()不会走原型链
for...of
for...of遍历的只是数组内的元素,不包含数组原型上的属性和方法。
(3)for of只能遍历拥有迭代器对象的对象
var obj={ a:1, b:2, c:3 } for(let i in obj){ console.log(i)//a,b,c } for(let item of obj){ console.log(item)//Uncaught TypeError: obj is not iterable }
一个数据结构只要部署了Symbol.iterator属性,就被视为具有iterator接口,就可以使用for of循环。
而对象是没有Symbol.iterator这个属性,所以使用for of会报obj is not iterable。
for of不同于forEach,他可以与break、continue和return配合使用,也就是说for of循环可以随时退出循环,而forach()不能跳出循环,break报错。
哪些数据结构部署了Symbol.iterator属性了呢??
- 数组Array
- Map
- Set
- String
- arguments对象
以上这些都可以直接使用for of循环,凡是部署了iterator接口的数据结构也都可以使用数组的扩展运算符和解构赋值操作。
假如想要对象也可以使用for of循环怎么办?使用Objct.keys()获取对象的key值集合后,再使用for of。
var obj={ a:1, b:2, c:3 } for(let item of Object.keys(obj)){ console.log(item)//a,b,c }
判断一个数据结构是否具有可迭代能力,只有当数据具有Symbol.iterator属性的时候才可以使用for...of进行迭代。
console.log(Array.prototype.hasOwnProperty(Symbol.iterator));//true
console.log(Object.prototype.hasOwnProperty(Symbol.iterator));//false
八、扩展运算符
扩展运算符(用三个连续的点 (...
) 表示)是 ES6 中的新概念,使你能够将字面量对象展开为多个元素
通过减少赋值语句的使用,或者减少通过下标访问数组或对象的方式,使代码更加简洁优雅,可读性更佳。
const books = ["Don Quixote", "The Hobbit", "Alice in Wonderland", "Tale of Two Cities"]; console.log(...books); Prints: Don Quixote The Hobbit Alice in Wonderland Tale of Two Cities
应用:
(1)复制数组, 使用扩展运算符拷贝对象或数组时,只对第一层深拷贝,二层及以后为浅拷贝
var a1=[1,3,2,5,3]; //ES5 var a2=a1.concat(); //ES6 var a3=[...a1];
(2)合并多个数组
//ES5 var a2=arr1.concat(arr2,arr3); //ES6 var a3=[...arr1,...arr2,...arr3];
(3)使用扩展运算符展开数组代替apply方法,将数组转为函数的参数。
//ES5 var a2=Math.max.apply(this,[1,5,3,8,6]); //ES6 var a3=Math.max(...[1,5,3,8,6]); //相当于Math.max(1,5,3,8,6);
(4)将字符串转为数组
var str='abcde'; //ES5 var a2=str.split(''); //ES6 var a3=[...str]; //["a", "b", "c", "d", "e"]
(5)Map结构
let map=new Map([ [1,'one1'], [2,'one2'], [3,'one3'] ]); let arr=[...map.keys()]; console.log(arr);//[1,2,3]
九、Set数据结构
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。 可用于数组去重
Set
本身是一个构造函数,用来生成 Set 数据结构。 Set函数接收一个数组作为参数用来初始化。
// 例一 const set = new Set([1, 2, 3, 4, 4]); [...set] 获Array.from(set); // [1, 2, 3, 4] // 例二 const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]); items.size // 5
(1)se结构与数组的区别
- 添加元素 add() 如:const set = new Set([1, 2, 3, 4, 4]); set.add('1');
- 删除元素 set.delete(value);
- 判断某值是否存在 set.has(value)
- 清除所有值 set.clear();
- 获取长度 set.size
(2)将set结构转换为数组:
- [...set]
- Array.from(set)
(3)Set和数组的相同点:
- 数组的map()和filter()方法用于set数据结构
注意写法:(不是在set实例后面写方法,是在参数数组后面)
- Set支持for...of和forEach循环
注意: 在forEach((item,index){})循环中发现:Set的value值与index值相同
Set结构:
数组:
10、Map数据结构
Map结构出现的目的:
由于JS的对象(Object)本质上是键值对的集合(Hash结构),但是传统意义上只能用字符串当作键,这就给它的使用带来了很大的限制。
ES6提供的Map数据结构,类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串——值”的对应,Map结构提供了“值——值”的对应,是一种更加完善的Hash结构,如果你需要“键值对”的数据结构
,Map比Object更合适。
传统对象键名必须为字符串
let obj={ a:1, 666:2 } console.log(obj.666);//报错
map结构是一种“值对值”的数据结构
let map=new Map(); map.set(666,2); console.log(map.get(666));//2
Map结构的属性和方法
(1)属性:size
(2)set(key,val):向字典中添加新元素
(3)get(key):通过键查找特定的数值并返回
(4)has(key):如果键存在字典中返回true
(5)delete(key)
(6)clear():将字典中的所有元素都删除
遍历方法
(1)keys():将字典中包含的所有键名以数组形式返回
(2)values():将字典中包含的所有数值以数组的形式返回
(3)forEach()遍历字典中的成员
Map结构初始化的参数,可以接受一个二维数组作为参数。
let map=new Map([ ['name','张三'], ['title','Author'] ]) console.log(map.size)//2 console.log(map.has('name'))//true console.log(map.get('name'))//"张三"
for(let key of map.keys()){ console.log(key);//name title } for(let val of map.values()){ console.log(val);//张三 Author }
11、ES6新增的数组方法?
(1)map()
map():对数组的每个元素进行一定的操作(映射)后,会返回一个新的数组。
map()传参与forEach()相同
(2)forEach()
forEach():该方法对数组中的每一项运行给定函数,该方法没有返回值。其实就是遍历循环
forEach()包含两个参数,第一个参数是匿名函数,第二个参数是this。匿名函数中包含三个参数:item(每一项的值),index(每一项的索引),self(代表所遍历函数自己)
map()和forEach()的区别:
map()会返回一个新的数组,forEach()不会返回数据;
forach()允许改变原数组的元素,map()返回的是新数组,不会修改原数组。
(3)filter()
filter():该方法对数组中的每一项运行给定函数,返回该函数会返回每一相对应的true或false,最后返回包含留下为true对应的元素的数组。
filter()传参与forEach()相同。
filter()用法:
创建一个数组,判断数组中是否存在某个值或过滤某些值
var arr=[2,4,3,2,5]; console.log(arr.filter(item=>item==3));//[3]
- 去掉数组中的空数组、空字符串、undefined、null
var arr = ['1','2',undefined,'','3.jpg',undefined,null]; var newArr = arr.filter(function(item){ return item; }) console.log(newArr);//["1", "2", "3.jpg"]
- 数组去重
var arr = [1,2,4,5,3,2,4,3,6,8,6,8,8,8]; var newArr=arr.filter(function(item,index,self){ return self.indexOf(item)==index; }) console.log(newArr);//[1, 2, 4, 5, 3, 6, 8]
(4)reduce()
归并方法reduce():该方法会迭代数组中的每一项,然后生成一个最终返回值。接收两个参数(回调函数,初始值),将第二个参数作为初始值
数组求和来启动累积。
reduce()接收一个函数作为累加器,
回调函数接收最多四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。
//数组求和 var arr = [1,2,4,5,3]; var sum=arr.reduce(function(pre,item){ return pre+item; },0) console.log(sum);//15
(5)every()
对数组的每一项都运行给定的函数,如果每一项都返回true,则返回true,否则返回false。
(6)some()
对数组的每一项都运行给定的函数,如果任一项返回true,则返回true。
function add(){ var arr=[1,2,3,4,5]; return arr.every(function(item){ return item>3; }) } console.log(add());//false function add(){ var arr=[1,2,3,4,5]; return arr.some(function(item){ return item>3; }) } console.log(add());//true
11、模块
导入、导出
12、Promise
13、Object.assign()、Object.create()