ES6
ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。
ES6 实际上是一个泛指,泛指 ES2015 及后续的版本。
每一次标准的诞生都意味着语言的完善,功能的加强。JavaScript语言本身也有一些令人不满意的地方。
- 变量提升特性增加了程序运行时的不可预测性
- 语法过于松散,实现相同的功能,不同的人可能会写出不同的代码
let及const
1、let: ES6中新增的用于声明变量的关键字。let声明的变量只在所处于的块级有效
是ES6新增的声明格式,用于补全ES5标准中var声明变量的不足:在JavaScript中用 ‘var’ 来声明变量会出现变量提升的情况,即通过"var"声明的变量系统都会把声明隐式的升至顶部,这样的特性往往会让刚接触JavaScript及习惯其他语言的开发人员不适应,导致程序出现问题。
注意: 使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性。
例题:
var arr = [];
for (var i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[0]();
arr[1]();
关键点在于: 变量i是全局的,函数执行时输出的都是全局作用域下的i值
let arr = [];
for (let i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[0]();
arr[1]();
关键点在于: 每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的i值
2、const: 使用const声明的是常量,常量的值不能通过重新赋值来改变,并且不能重新声明,所以每次通过const来声明的常量必须进行初始化
-
具有块级作用域 if (true) {
const a = 10;
}
console.log(a)
-
声明常量时必须赋值 const PI;
-
常量赋值后,值不能修改 const PI = 3.14;
PI = 100;
const arry = [100, 200];
arry[0] = 'a'; arry[1] = 'b';
console.log(arry);
arry = ['a', 'b'];
-
如果声明的是对象,可以修改对象的属性值,但不允许修改已经声明的对象 const obj = {
name: '张三',
age: 20
}
obj.name = '李四'
console.log(obj)
obj = {}
-
如果让对象属性不能修改,可以借助 Object.freeze函数 来 冻结对象 const obj = {
name: '张三',
age: 20
}
Object.freeze(obj);
obj.name = "李四";
console.log(obj);
-
通过Object.freeze冻结对象需要注意的是:不能冻结多层对象 const obj = {
name: '张三',
age: 20,
family: {
father: {
name: '张安',
age: 48
}
}
}
Object.freeze(obj);
obj.family.father.age = 50;
console.log(obj);
-
解决多层冻结问题可以通过封装一个deepFreeze函数来实现: const obj = {
name: '张三',
age: 20,
family: {
father: {
name: '张安',
age: 48
}
}
}
function deepFreeze(obj){
Object.freeze(obj);
for(let key in obj){
if(obj.hasOwnProperty(key) && typeof obj[key] === 'object'){
deepFreeze(obj[key])
}
}
}
deepFreeze(obj);
obj.family.father.age = 50;
console.log(obj);
3、临时死区:
let和const都是块级标识符,所以let和const都是在当前代码块内有效,常量不存在变量提升的情况。
但是通过let和const声明的常量,会放在 临时死区(temporal dead zone),通过下面代码可以看出:
{
console.log(typeof a);
let a = 10;
}
即使通过安全的typeof操作符也会报错,原因是JavaScript引擎在扫描代码变量时,要么会把变量提升至顶部(遇到 var 声明),要么会把变量放在临时死区(遇到 let 和 const 声明)。
因此,这里通过let声明的’a’变量会被放在临时死区,所以在声明之前打印就会报错
可复习变量提升相关知识:JavaScript 作用域(链)、预解析、闭包函数
4、循环中let和const的使用:
(1)在ES5标准中,for循环中的循环变量都是通过var来声明的,由于var没有独立的作用域,导致在循环中创建函数时会出现结果和思路不一致的情况,如下:
let funArr = [];
for(var i=0;i<5;i++) {
funArr.push(function(){
console.log(i)
})
}
funArr.forEach(item=> {
item()
})
循环结果不是预想的0,1,2,3,4,而是5个5,这是因为var声明在循环中作用域共用,并且会把i保存在全局作用域中。
(2)要解决循环中保存函数的问题,可以利用 闭包 创建独立的作用域,代码如下:
let funArr = [];
for(var i=0;i<5;i++) {
(
function(i) {
funArr.push(function(){
console.log(i)
})
}
)(i)
}
funArr.forEach(item=> {
item()
})
这样通过自执行函数就可以解决循环中创建函数的问题。
(3)利用ES6中 let 和 const 提供的 块级作用域 可以让代码更简洁:
let funArr = [];
for(let i=0;i<5;i++) {
funArr.push(function(){
console.log(i)
})
}
funArr.forEach(item=> {
item()
})
(4)在 for-in或for-of循环 中使用const时,方法与let一致:
let obj = {
name: '张三',
age: 20
}
for(const i in obj){
console.log(i)
}
let arr = ['张三','李四','王五']
for(const value of arr){
console.log(value)
}
5、let、const、var 的区别:
- 使用 var 声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象
- 使用 let 声明的变量,其作用域为该语句所在的代码块内,不存在变量提升
- 使用 const 声明的是常量,在后面出现的代码中不能再修改该常量的值
解构赋值
ES6中允许从数组中提取值,按照对应位置,对变量赋值。对象也可以实现解构。
1、数组的解构:
(1) 赋值多个变量
在ES5标准中赋值多个变量采用的方法是:
var a = 10;
var b = 20;
var c = 30;
ES6提供了更简洁的解构赋值来实现上述变量的定义:
let [a,b,c] = [10,20,30];
等号右边的值会按照顺序依次赋值给左边的变量。
(2)非一一对应关系的赋值
let [a,b] = [10,20,30]
console.log(a,b);
let [a,b,c] = [10,20]
console.log(a);
console.log(b);
console.log(c);
(3)也可以通过'...' 把特定的元素放在变量里
let [a,...arr] = [10,20,30]
console.log(a);
console.log(arr);
(4)可以通过解构赋值来互换变量
let a = 10;
let b = 20;
[a,b] = [b,a];
console.log(a,b)
2、对象的解构:
(1)对象解构的写法与数组解构类似
let obj = {
name: '张三',
age: 20,
height: '178com'
}
let { name,age,height } = obj;
console.log(name,age,height);
(2)也可以解构多层对象
let person = {
name: '张三',
age: 20,
family: {
father: '张武',
mother: '李燕'
}
}
let { name,age,family: {father,mother }} = person
console.log(name,father)
(3)在解构对象时也可以自定义变量名称:
let obj = {
name: '张三',
age: 20
}
let { name:myname,age:myage } = obj;
console.log(myname,myage);
3、解构的默认值和参数的解构:
(1)不管是数组的解构赋值,还是对象的解构赋值都可以添加默认参数。如下:
let obj = {
name: '李四',
age: 20
}
let { name,age,height="178com" } = obj;
console.log(height);
(2)在函数参数中使用解构,参数解构也可以给默认参数
function fun({name,age,height="178com"} = {}){
console.log(name,age);
}
let obj = {
name: '张三',
age: 20
}
fun(obj)
模板字符串
ES5标准中一般输出模板是通过字符串拼接的方式进行的。
在ES6中可以通过模板字符串简化字符串的拼接,模板字符串通过反引号来表示'``' ,如果要嵌入变量通过'${ 变量名 }' 来实现:
let arr = [
{
name: '张三',
age: 20
},
{
name: '李四',
age: 23
},
{
name: '王五',
age: 25
}
]
let str = "";
for(let i=0;i<arr.length;i++){
str += `姓名是:${ arr[i].name },年龄是:${ arr[i].age }`;
}
console.log(str)
Symbol类型
ES5中提供了 6种数据类型 分别是:undefined、null、boolean、string、number、object
ES6中新增了一种数据类型Symbol来表示唯一的值,每个创建的Symbol都是唯一的,这样在实际运用中可以创建一些唯一的属性及定义私有变量。例如:
let s1=Symbol;
let s2=Symbol('s2');
let s3=Symbol('s2');
console.log(s1);
console.log(s2);
console.log(s2===s3)
1、目前前端项目都会采用模块化构建,为了防止对象属性名被覆盖,可以通过symbol来定义属性名。例如:
const NAME = Symbol('name')
let obj = {
[NAME]:'张三',
age: 20
}
module.export = obj;
import obj from './a.js'
const NAME = Symbol('name');
obj[NAME] = '李四';
console.log(obj);
2、利用Symbol作为属性名,属性名不会被 Object.keys()、Object.getOwnPropertyNames()、for…in循环返回。例如:
let obj={
[Symbol('name')]:'张三',
age:40,
height:'178cm'
}
for(let key in obj){
console.log(key);
console.log(obj[key]);
}
let keys=Object.keys(obj);
console.log(keys);
let name=Object.getOwnPropertyNames(obj);
console.log(name);
3、可以在类里利用Symbol来 定义私有属性及方法例如:
let People=(
function(){
let name=Symbol('name');
class p{
constructor(yourname){
this[name]=yourname;
}
sayName(){
console.log(`姓名:${this[name]}`)
}
}
return p
}
)();
let p1=new People('张三');
console.log(p1[Symbol('name')]);
p1.sayName();
Set和Map数据结构
1、Set
Set类似于数组,但是它里面每一项的值是唯一的,没有重复的值,set是一个构造函数,用来生成set的数据结构
let s = new Set();
let arr = [2, 3, 5, 4, 5, 2, 2];
arr .forEach(item => arr.add(item));
for (let i of s) {
console.log(i);
}
4种操作方法:
- add(value):添加某个值,返回 Set 结构本身
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功
- has(value):返回一个布尔值,表示该值是否为 Set 的成员
- clear():清除所有成员,没有返回值
4种遍历方法:可用于遍历成员
- keys():返回一个键名的遍历器
- values():返回一个键值的遍历器
- entries():返回一个键值对的遍历器
- forEach(): 使用回调函数遍历每个成员
注意: 由于Set结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。
实现并集(Union)、交集(Intersect)、差集(Difference):
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
let union = new Set([...a, ...b]);
let intersect = new Set([...a].filter(x => b.has(x)));
let difference = new Set([..a].filter(x => !b.has(x)));
2、Map
JavaScript的对象(Object)本质上是键值对的集合(Hash结构),但是只能用字符串作为键。这给它的使用带来了很大的限制。
为了解决这个问题,ES6 提供了Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值( 包括对象)都可以当作键。也就是说,Object 结构提供了“字符串一值”的对应,Map结构提供了“值一值"的对应,是种更完善的Hash结构实现。
var m= new Map();
var o ={p: "Hello World"};
m.set(o, "content")
m.get(o)
m.has(o)
m.delete(o)
m.has(o)
操作方法:
- size属性:返回Map结构的成员总数
- set(key, value):设置key所对应的键值,然后返回整个Map结构。如果key已经有值,则键值会被更新,否则就新生成该键
- get(key):读取key对应的键值,如果找不到key,则返回undefined
- has(key):返回一个布尔值,表示某个键是否在Map数据结构中
- delete(key):删除某个键,返回true。如果删除失败,则返回false
- clear():清除所有成员,没有返回值
4种遍历方法:可用于遍历成。
- keys(): 返回一个键名的遍历器
- values(): 返回一个键值的遍历器
- entries(): 返回一个键值对的遍历器
- forEach(): 使用回调函数遍历每个成员
箭头函数
1、函数形参的默认值:在很多情况下,需要在使用函数的时候给定默认参数
在ES5标准中:
function fun(name,age,cb){
name = typeof(name !== 'undefined')?name: '张三'
age = typeof(age !== 'undefined')?age: 20
cb = typeof(cb !== 'undefined')?cb:function(){}
console.log(name,age);
}
fun("李四",0)
ES6中函数默认参数:
function fun(name='张三',age=20,cb){
console.log(name,age)
}
fun();
2、函数形参不定参数:在很多情况下,使用函数传参的时候,形参的数量是不固定的,这时候要获取参数值就会比较麻烦。
在ES5标准中可以通过 隐藏参数arguments 来获取,此时会把所有参数放在arguments中。例如:
function fun() {
console.log(arguments);
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
}
fun('张三',20,'178cm')
3、箭头函数:箭头语法最大的特点是有箭头=> 符号,当然箭头语法有很多变式写法:const fn = () => {}
(1) 没有参数,用括号代替 若只有一句,不带花括号,自带return
let fun = ()=> "张三"
(2) 一个参数,括号可以省略
let fun = arg=> "李四"
(3) 多个参数
let fun = (arg1,arg2)=> arg1 + arg2
console.log(fun(1,3))
(4) 利用箭头语法里隐式返还的时候需要注意对象的情况,需要注意如下错误情况:
let fun = () => {
name: '张三',
age: 20
}
自认为返还的是一个对象,但是这里的大括号和函数里的大括号在含义上有冲突,系统会认为大括号是 函数里的括号,而不是对象里的括号,导致报错
let fun = () => ({
name: '张三',
age: 20
})
console.log(fun());
(5) 箭头函数里没有this绑定,箭头函数中的this,指向的是 函数定义位置的上下文this
let obj = {
id: 2,
fun: function() {
console.log(this.id);
}
}
obj.fun()
上面代码可以打印出id为2,this指向了obj,所以this.id可以取得obj.id。如果改成箭头语法会发现,函数中this指向改变了,代码如下:
let obj = {
id: 2,
fun: ()=>{
console.log(this.id);
}
}
obj.fun()
this.id 获取不到值,原因是:箭头函数没有this绑定,箭头函数中的this会指向最近的上层this,即this的指向是window
(6) 使用箭头语法的时候 没有隐藏参数arguments的绑定,代码如下:
let fun = (arg1,arg2) => {
console.log(arguments);
return arg1 + arg2
}
fun()
类
在ES5标准中通过构造函数来模拟类的功能,一般会定义一个构造函数,把一类功能做封装,通过new运算符来调用
function Person(name){
this.name=name,
this.age=20
}
Person.prototype.fun=function(){
console.log('ES5中类定义...')
}
let p1=new Person();
p1.fun()
在ES6标准中提供class关键字来定义类,在写法上更简洁、语义化更强
class Person{
constructor(name,age){
this.name=name;
this.age=age;
}
get myage(){
return this.age;
}
set myage(newAge){
this.age=newAge
}
fn(){
console.log('ES6中类定义...')
}
}
let p1=new Person('李四',28);
p1.fn();
console.log('年龄:'+p1.myage);
ES6支持通过getter、setter在原型上定义属性。创建getter的时候需要用关键字get ,创建setter的时候需要用关键字set
类的静态属性、静态方法为类的所有对象共享,而不属于某个具体的对象
class Student{
static schoolName = '加州大学';
constructor(name,age){
this.name=name;
this.age=age;
}
static showSchoolName(){
console.log(this.schoolName)
}
showStudent(){
console.log(`姓名:${this.name} 年龄:${this.age}`);
}
}
let s1=new Student('张三',20);
Student.showSchoolName();
s1.showStudent();
继承:
在ES5标准中可以通过call、apply、bind来实现构造函数的继承,实现方式如下
function Dad(name,age){
this.name=name;
this.age=age;
}
function Son(name,age=25){
Dad.call(this,name,age)
this.height='178cm';
}
Son.prototype.show=function(){
console.log(this.name);
console.log(this.age);
}
let s1=new Son('张三')
s1.show()
上述方式可以实现构造函数的继承,但是如果有方法在Dad原型上实现,还需要考虑原型的继承,单纯的原型赋值继承还会涉及传址问题,所以实现起来比较繁琐
ES6标准中类的继承:通过extends关键字实现
class Dad{
constructor(name){
this.name=name;
}
fun(){
console.log('父类的成员方法...')
}
}
class Son extends Dad{
constructor(name){
super(name);
}
hobby(){
console.log('篮球');
}
show(){
super.fun();
console.log(`姓名:${this.name}`);
}
}
let s1=new Son('张三');
s1.show();
在继承中需要调用super()方法继承父类的构造方法。super()在使用过程中需要注意以下两点:
- 在访问this之前一定要调用super()
- 如果不调用super(),可以让子类构造函数返还一个对象
|