ES6基础语法

144 阅读26分钟

ES6基础语法


面向过程和面向对象

  • 面向过程:分析出解决问题所需的步骤,然后按照步骤一步一步地完成

  • 面向对象:把一个事务分解成若干个对象,由对象的分工合作来完成事务

    面向对象具有灵活、代码复用、容易维护等一系列优点,比较适合做大型项目,但是面向对象的性能不如面向过程,如果和硬件联系紧密则适合面向过程,这两者各有优缺点

类和对象

类:抽取多个事物共有的属性和行为并封装成一个模板,这个模板就是类(抽象的、泛指的)

对象:根据类具有的属性和行为实例化一个具体事物,这个事物就是对象(具体的、特指的)

类和实例对象的创建

创建一个简单的类可以使用class关键字,语法格式如下

class 类名 {
    ...
}
// 创建一个类,在类里面写相应的属性和方法即可

创建完类之后就可以使用new关键字创建一个简单的实例对象,语法格式如下

var 对象名 = new 类名();
// 创建指定类的一个实例对象

常用类的创建方式

在实际开发中,我们一般是要传递参数过去的,所以就必须在类里面写constructor()构造函数,此外有时候还需要在类里面添加方法,写法如下

class 类名 {
    constructor(形参1,...,形参n) {
        this.attr1 = 形参1;     // 把形参的值作为当前实例对象的属性值
        ...
        this.attrn = 形参n;
    }
    方法名(形参1,...,形参n) {
        ...
    }
    ... 
}
// 创建一个类,可以实现参数的传递
// attr1 ~ attrn表示当前实例对象的属性,一般它们和形参的名字对应是相同的
// 在类里面写方法的时候不需要加function关键字

下面举个例子演示一下通过传参来创建实例对象的操作

class Star {
    constructor(starName, starAge, starSong) {
        this.starName = starName;
        this.starAge = starAge;
        this.starSong = starSong;
    }
    sing(nightSong) {
        console.log("掌声欢迎!今晚唱" + "《" + nightSong + "》");
    }
}
var zjl = new Star("周杰伦", "18", "稻香");
var xzq = new Star("薛之谦", "24", "演员");

zjl对象和xzq对象分别如下(可以尝试在控制台打印一下)

// zjl对象
{starName: '周杰伦', starAge: '18', starSong: '稻香'}
// xzq对象
{starName: '薛之谦', starAge: '24', starSong: '演员'}

然后实例对象可以调用类里面的方法,代码如下

zjl.sing("给我一首歌的时间");
xzq.sing("丑八怪");
// 第一行打印"掌声欢迎!今晚唱《给我一首歌的时间》"
// 第二行打印"掌声欢迎!今晚唱《丑八怪》"

this关键字的相关说明

在类里面,this永远指向的是调用当前函数的那个事物。这个事物大多数情况下都是对象,但也有可能是其他事物,这就要看谁调用函数,那么函数里面的this就会指向谁,所以this未必指向当前实例对象(只是说它在绝大多数情况下都是指向当前实例对象的)。

一般来说,普通函数里面的this指向window对象,对象里面函数的this指向当前对象,构造函数里面的this指向内存中new出来的那个实例对象,添加事件的事件处理函数里面的this指向事件源,定时器函数里面的this指向window对象,立即执行函数里面的this也是指向window对象

new关键字的执行过程

new关键字的执行过程:在javascript中,只要遇到了new关键字都会事先在内存中创建一个空的实例对象,然后传递参数执行相应类里面的构造函数,而里面的this就指向这个空的实例对象,构造函数执行完毕之后这个空的实例对象就会变成一个有数据的实例对象,最后返回这个有数据的实例对象。理解new关键字这个执行过程是十分必要的

constructor()构造函数的作用

  • 接收传递过来的实参

  • 自动返回一个实例对象

    声明:构造函数是可以自动返回值的,所以不需要return关键字来返回。如果这个构造函数不写在类里面的话,类里面也会自动生成这个函数,这个时候会直接返回一个空的对象。

类的继承

在面向对象的编程思想中,继承指的是子类可以继承父类的属性和方法,子类继承父类的写法如下

class 父类名 {
    ...
}
class 子类名 extends 父类名 {
    ...
}
// 先声明一个父类,再声明一个继承这个父类的子类

一个类继承另一个类的时候,子类是拥有父类里面的属性和方法的,只是没有写在子类里面而已。这样的话如果使用子类实例化一个对象,那么这个对象可以调用父类里面的方法,举例如下

class Father {
    constructor() {}
    say() {
        console.log("我是父类里面的方法");
    }
}
class Son extends Father {
    
}
var son = new Son();
son.say();
// 控制台打印"我是父类里面的方法"
// 在子类里面不要写构造函数,这样默认使用父类的构造函数来实例化对象,才能调用父类里面的方法

super()函数

前言:在子类继承父类的过程中,一定要特别注意一点-如果子类和父类里面都有constructor()构造函数,那么使用子类实例化的对象不能调用父类里面的方法,原因在于子类和父类在发生冲突的时候是以子类为准的,使用子类的构造函数来实例化的对象只能调用子类里面的方法而不能调用父类的。为了解决这个问题,就需要使用super()函数

super()函数可以在子类中调用父类的构造函数,这种操作相当于是直接拿父类的构造函数来实例化一个对象,那么这个实例对象就可以调用父类里面的方法。此外,super()函数还可以在子类中调用父类的普通函数,下面来演示以下这两个写法

  • 在子类中调用父类的构造函数

    class Father {
    	constructor() {}
    	say() {
    		console.log("我是父类里面的方法");
    	}
    }
    class Son extends Father {
        constructor() {
            super();
        }
    }
    var son = new Son();
    son.say();
    // 控制台打印输出"我是父类里面的方法"
    
  • 在子类中调用父类的普通函数

    class Father {
    	constructor() {}
    	say() {
    		console.log("我是父类里面的方法");
    	}
    }
    class Son extends Father {
        say() {
            console.log("我是子类里面的方法");
            super.say();
        }
    }
    var son = new Son();
    son.say();
    // 控制台第一行打印"我是父类里面的方法",第二行打印"我是子类里面的方法"
    

    子类继承父类的几点说明:用子类实例化一个对象使用一定是父类的构造函数(它不能使用自己的构造函数),这个对象是可以调用子类和父类里面的方法(优先调用子类);此外,子类中要么不写构造函数,要么在构造函数里面添加super()关键字,否则浏览器器会直接报错。子类里面写构造函数的时候,不仅要添加super(),还要将它放在最前面

解构赋值

解构赋值的全称是分解数据结构进行赋值,指的是利用数组或对象将对应的值赋值给对应的变量。解构赋值可以分为数组解构赋值和对象解构赋值

  • 数组解构赋值举例如下

    let [a, b, c]  = [1, 2, 3];
    // 声明三个变量的同时给它们赋不同的值,此时a=1,b=2,c=3
    // 注意等号的左边是中括号的时候,代表数组解构赋值,右边是数组但是左边不是
    

    说明:如果变量的个数和值的个数不一致,那么分为两种情况

    1. 变量的个数小于值的个数:把前面若干个值传递给所有变量,多出来那部分值不传递

      let [a, b, c] = [1, 2, 3, 4, 5];
      console.log(a, b, c);
      // 控制台输出1 2 3
      
    2. 变量的个数大于值的个数:把所有的值传递给前面若干个变量,多出来那部分变量的值为undefined

      let [a, b, c] = [1, 2];
      console.log(a,b,c);
      // 控制台输出1, 2, undefined
      
  • 对象解构赋值举例如下

    let { name, age } = { name: "JohnYong", age: 20};
    // 声明两个变量name和age,同时给它们分别赋值为"JohnYong"和20
    // 注意等号的左边是花括号的时候,代表对象解构赋值,右边是对象但是左边不是
    

    对象解构赋值和数组解构赋值的基本思想是一样的,这里当变量个数和值的个数不一样的时候则按照数组解构赋值那样的情况来处理。需要注意的是数组解构赋值传递的是值,对象解构赋值传递的是属性值。

    此外,对象结构赋值中的变量名和属性名是一种匹配的关系而不是对应的关系,也就是说只要变量名和属性名匹配到了(变量名和属性名相同)就可以传递属性值过去,匹配不成功的变量就会得到undefined,就算顺序不一样也没关系,比如下面这个例子

    let cat = {
    	name: "小波",
    	breed: "波斯猫",
    	age: 8,
    	coatColor: "白色"
    }
    let {age, sex, coatColor, breed, weight} = cat;
    console.log(age, sex, coatColor, breed, weight);
    // 控制台打印结果为→8 undefined '白色' '波斯猫' undefined
    // 这种情况下的变量用来匹配属性,还用来获取对应的值
    

    在对象解构赋值中还可以给匹配属性的变量起一个别名,这样变量名和属性名就可以不一样,比如

    let { name: myName, age: myAge } = { name: "周杰伦", age: 25 };
    console.log(myName, myAge);
    // 控制台打印结果为→周杰伦 25
    // 这种情况下冒号左边的字符串用来匹配属性,冒号右边的变量用来获取对应的值
    

块级作用域

在ES6之前的版本中,只有全局作用域和局部作用域,ES6引入了块级作用域这个概念。块级作用域其实指的是在花括号里面的作用域

let声明变量

let关键字是ES6中新增的用来声明变量的关键字,使用它声明的变量具有以下特征

  • 只在当前块级作用域有效,比如说

    if (true) {
    	let a = 10;
    	console.log(a);
    }
    console.log(a);
    // 第一行打印10,第二行直接报错"a is not defined"
    
    if (true) {
    	var a = 10;
    	console.log(a);
    }
    console.log(a);
    // 第一行打印10,第二行也打印10
    

    由上述结果可以看出,使用let声明的变量只在当前的块级作用域有效,使用var声明的变量只在当前的函数级作用域有效

  • 没有变量提升,比如说

    console.log(a);
    let a = 10;
    // 控制台报错"a is not defined"
    
    console.log(a);
    var a = 10;
    // 控制台打印结果为undefined
    // 这里只会提升声明的部分,赋值的部分不会提升,相当于
    var a;
    console.log(a);
    a = 10;
    

    由上述结果可以看出,使用let声明的变量没有变量提升的这一操作,但是使用var声明的变量会进行预解析将声明变量提升到当前作用域的最前面。所以说let关键字会感觉严格规范一点,var关键字会比较随意

  • 具有暂时性死区特性

    暂时性死区:在花括号里面一旦使用let声明了变量,那么当前的花括号(也就是块级作用域)可认为是一个暂时性死区。这个暂时性死区和外部没有任何关联,外面得到变量也不能放里面使用,比如说

    var temp = 123;
    if (true) {
    	console.log(temp);
    	let temp;
    }
    // 控制台直接报错'temp is not defined'
    

    由上述结果可以看出虽然使用let声明变量不会进行变量提升,而且在块级作用域外部使用var声明了变量,但是打印结果并不是123。这就说明暂时性死区不允许外面的变量放在里面使用,所以才会报错

const声明常量

const关键字是ES6中新增的用来声明常量的关键字,使用它声明的常量具有以下特征

  • 只在当前块级作用域有效

  • 没有变量提升

  • 具有暂时性死区特性

  • 声明的时候必须赋值

  • 其值一旦确定则无法更改,这里举个例子

    const obj = {
    	name: "周杰伦",
    	age: 25
    }
    const arr = ["李白", "范仲淹"];
    obj.name = "陈奕迅";
    arr[1] = "杜甫";
    console.log(obj.name);
    console.log(arr[1]);
    arr = 123;
    // 第一行打印"陈奕迅",第二行打印"杜甫",第三行直接报错
    

    从上述结果可以看出,对于引用数据类型来说,可以修改里面的值,但是不能重新给常量赋值(也就是拿其他的值去覆盖这个引用数据类型),这一点需要注意。const关键字其实只认地址,只要地址不发生变化那么都是允许的;在重新赋值的时候,常量对应的地址就会变化,从而导致报错,但是修改引用数据类型里面的值的时候常量对应的地址是不变的,只有里面的值会发生改变,这个时候就不会报错

箭头函数

箭头函数是用来简化函数定义的,相比于其他类型的函数,箭头函数更加简洁,其定义语法如下

const 常量名 = () => {
	函数体
}
// 函数一般是不变的,所以可使用const关键字来声明
// 通过`常量名()`来调用箭头函数

当函数体中只有一个语句则可以省略大括号,如果省略了大括号那么函数执行完毕之后会自动将结果返回。如果没有省略大括号则需要return关键字才能返回结果,比如

const fn = () => 1+1;
let result = fn();
console.log(result);
// 控制台输出结果为2

当形参只有一个的时候,可以省略箭头函数的小括号,比如

const fn = num => num * 10;
console.log(fn(3));
// 控制台输出结果为30
// 在上述箭头函数中num表示形参,num * 10是函数体

这里需要说明一点:当没有形参或者形参个数大于1个的时候,这个小括号必须写

箭头函数中的this指向

对于以往的普通函数(命名函数和匿名函数)来说,只要谁调用了它,那么它里面的this就会指向谁(箭头函数可以认为是一个特殊的匿名函数)。但是箭头函数没有这个特点,也就说箭头函数里面没有自己this指向,要是在箭头函数里面写了this关键字,那这个this就是箭头函数定义位置的this,而不是自己本身的this,举个例子

function fn() {
	console.log(this);
}
fn();
// 这种函数调用方式默认是window对象来调用,所以控制台打印window对象

修改函数内部的this指向

call方法

使用函数名.call(对象名,[实参1,...,实参n])可以修改某一个函数里面的this指向并调用函数,对象名写在第一位,后面全部写实参(如果不传参则可以不写)。比如

let obj = {
    name: "我是函数指向的对象"
}
function fn() {
	console.log(this);
    return () => {
        console.log(this);
    }
}
const arrFn = fn.call(obj);
arrFn();
// 控制台第一行和第二行都打印obj对象
// 修改函数里面的this指向之后,它的返回值就是函数的返回结果
apply方法

使用函数名.apply(对象名,[数组名])可以修改某一个函数里面的this指向并调用函数,它的功能用法基本和call方法是一致的,唯一不一样的就是调用函数的时候必须传递一个数组过去。所以说这个方法里面最多是两个参数,如果数组名不写则不传参,下面举个例子

let obj = {
    name: "我是函数指向的对象"
}
function fn(star1,star2,star3) {
    console.log(this);
    console.log(star1,star2,star3);
}
fn.apply(obj,["李行亮","金志文","汪苏泷"]);
// 第一行打印obj对象,第二行打印→李行亮 金志文 汪苏泷
// 第二个参数必须传递数组,否则会报错

apply方法可以用来求一个数组中的最大值或最小值,其基本原理为:给Math对象下的max或min方法修改其this指向为Math对象并传递一个数组过去,这样最终就会返回一个最大值或最小值,比如

var arr = [20,52,121,39,88];
var max = Math.max.apply(Math, arr);
var min = Math.min.apply(Math, arr);
console.log(max, min);
// 控制台打印121和20
bind方法

使用函数名.bind(对象名,[实参1,...,实参n])可以修改某一个函数里面的this指向,但是不会调用函数,它必须通过返回值来调用函数,举例如下

let obj = {
    name: "我是函数指向的对象"
}
function fn(star1,star2,star3) {
    console.log(this);
    console.log(star1,star2,star3);
}
let f1 = fn.bind(obj,"李行亮","金志文","汪苏泷");
f1();
// 第一行打印obj对象,第二行打印→李行亮 金志文 汪苏泷
// 调用的时候如果要传递参数,实参是写在bind方法里面而不是调用函数的里面
// 调用bind方法后的返回值是修改完函数内部this指向之后的新函数

综合上述三种方法,其实使用最多的是bind方法,主要原因是它不会自动调用函数,而只是单纯地修改函数里面的this指向,后面有需要的时候再去调用。而call方法和apply方法都会自动调用函数,这一功能往往不是我们想要的,比如说下面这一例子

var btn = document.querySelector("button");
btn.onclick = function() {
    this.disabled = true;
    setTimeout(function() {
        this.disabled = false;
    }.bind(this), 3000)
}
// 上述代码用来实现当鼠标单击按钮后将按钮变为禁用状态并且三秒后恢复使用状态
// 因为定时器里面的this是默认指向window对象的,所以必须通过bind方法将定时器函数里面的this指向修改为定时器函数外面的this指向(也就是当前btn对象),这样就能实现我们想要的效果

接收剩余实参

在ES6中,当参数传递出现了实参个数大于形参个数的时候,为了防止剩余形参丢失,可以将...形参名放在所有形参的最后面用来接收剩余实参,举例如下

function fn(sum1, sum2, ...aa) {
	console.log(aa);
}
fn(1, 2, 3, 4, 5);
// 控制台打印[3, 4, 5]
// 用来接收剩余实参的那个形参实际上是一个数组

注意一点:如果参数只写...形参名,那么这个形参可以接收所有实参。此外,虽然说函数里面的arguments可以接收所有实参,但是它是只适用于普通函数,不适用于箭头函数,箭头函数里面没有arguments这个数组来接收所有实参。而...形参名放在所有形参的最后面接收剩余实参适用于普通函数和箭头函数,比如下面这个例子

const fn = (...args) => {
	console.log(args);
}
fn(1,2);
// 控制台打印[1, 2]

const fn = (num1, num2) => {
	console.log(arguments);
}
fn(1,2);
// 浏览器直接报错

在数组解构赋值中也可以使用剩余参数,举例如下

let [num1, num2, ...otherNum] = [55, 40, 109, 160];
console.log(otherNum);
// 控制台打印输出[109, 160];

合并数组

前置知识点:在前面说的接收剩余实参中,扩展运算符3个点还有其他用处,比如在数组名的前面加三个点可以返回一个参数序列,举例如下

let starArr = ["周杰伦", "林俊杰" ,"陈奕迅"];
console.log(...starArr)
// 控制台打印的并不是一个数组,而是参数序列"周杰伦", "林俊杰" ,"陈奕迅"
// 上述操作等同于console.log("周杰伦", "林俊杰" ,"陈奕迅");

合并数组的第一种方法:定义一个数组,把参数序列写在数组里面,举个例子

let arr1 = ["Chinese", "Math", "English"];
let arr2 = [80, 90, 85];
let arr = [...arr1, ...arr2];
console.log(arr);
// 控制台输出['Chinese', 'Math', 'English', 80, 90, 85]

合并数组的第二种方法:定义一个空数组然后调用push()方法,把参数序列作为待加入的值,比如

let singer1 = ["许嵩", "周华健", "邓紫棋"];
let singer2 = ["郑源"]
let singer3 = ["李荣浩", "焦迈奇", "薛之谦", "单色凌"];
let allSinger = [];
allSinger.push(...singer1, ...singer2, ...singer3);
console.log(allSinger);
// 控制台打印['许嵩', '周华健', '邓紫棋', '郑源', '李荣浩', '焦迈奇', '薛之谦', '单色凌']

伪数组转换为真正数组

第一种方法:前面讲的扩展运算符3个点可以将伪数组(又称类数组或者集合)转换成真正意义的数组,语法格式如下

var 新数组名 = [...伪数组名];
// 此时的新数组就是真正意义的数组

比如获取页面中所有的div元素,会返回一个伪数组,然后将其转换为真正意义上的数组

var div = document.querySelectorAll("div");
var divArr = [...div];
// 此时的divArr才是一个真正意义的数组

第二种方法:使用Array对象下面的from方法也可以将伪数组转换为真正意义的数组语法格式如下

let 新数组名 = Array.from(伪数组名);
// 此时的新数组就是一个真正意义的数组

比如在页面中写几个div元素,然后通过下面这串代码感受一下运行结果

console.log(document.getElementsByTagName("div"));
let divArr = Array.from(document.getElementsByTagName("div"));
console.log(divArr);
// 第一行打印出伪数组,第二行打印出真正意义的数组

如果在Array对象的from方法里面写两个参数的话,可以先将伪数组里面的元素进行处理再转换为真正意义的数组,后面那个函数会循环地操作数组元素,语法格式如下

let 新数组名 = Array.from(伪数组名, 函数)

比如下面这个例子,item形参表示里面的每个数组元素

let div = document.getElementsByTagName("div");
let divArr = Array.from(div, item => item + "元素");
// 先将伪数组里面的元素加上"元素"这个字符串再转换为真正意义的数组
console.log(divArr);
// 控制台打印['[object HTMLDivElement]元素', '[object HTMLDivElement]元素', '[object HTMLDivElement]元素']

伪数组的真正模样

伪数组的真正模样是一个对象,假如数组里面有n-1个元素,那么前面n-1个属性都为索引(索引从0开始),第n个属性为length,属性值就相当于数组元素,比如下面这个伪数组

let arrLike = {
	0: "周杰伦",
	1: "林俊杰",
	2: "李荣浩",
	length: 3
}
console.log(arrLike[2],arrLike.length);
// 控制台打印"李荣浩"3

按照上面的写法去写一个对象的时候,其实就是在写伪数组

数组的一些扩展方法

查找数组元素

Array对象下面的find()方法可以用来查找满足条件的第一个数组元素,如果满足则返回这个数组元素,否则返回undefined。这个方法可以循环地操作item和index下标,item表示当前循环的数组元素,index表示当前循环的数组元素对应的下标。其语法格式如下

let 变量名 = 数组名.find(函数);
// 这个函数一般使用箭头函数,支持传递item形参和index形参

比如下面这个例子

let allMajor = ["软件工程","计算机科学与技术","物联网工程","通信工程", "数字媒体技术"];
let major = allMajor.find((item, index) => item.includes("技术"));
// 查找数组中包含"技术"的第一个数组元素并返回
console.log(major);
// 控制台打印"计算机科学与技术"

从上述操作可以看出,使用find方法的时候,其函数形参里面的函数体一般写的是查找条件。此外,如果只写一个形参则表示item,写两个形参则表示item和index。

查找数组元素下标

Array对象下面的findIndex()方法可以用来查找满足条件的第一个数组元素的下标,其用法和上面"查找数组元素"是一样的,前面返回的是数组元素,这里返回的是数组元素的下标。同理,当有多个数组元素满足条件时,只会返回第一个数组元素的下标。举个例子

let allNum = [32,12,52,30,58];
let num = allNum.findIndex((item,index) => item > 30)
// 查找数组中大于30的第一个数组元素的下标并返回
console.log(num);
// 控制台打印0

注意:如果数组元素中都不满足查找条件,则返回-1,而不是undefined

判断是否包含数组元素

Array下面有个includes()方法可以判断某个数组是否包含给定的值,如果包含则返回true,否则返回false,语法格式如下

数组名.includes(值);

举例如下

let subject = ["英语", "地理", "化学"];
console.log(subject.includes("地理"));
console.log(subject.includes("历史"));
// 控制台第一行打印true,第二行打印false

模板字符串

模板字符串是ES6新增的创建字符串的方式,它使用反引号来定义,语法格式如下

let 变量名 = `字符串内容`;

模板字符串的特点如下:

  • 字符串里面可以使用$符号解析变量,比如

    let name = "JohnYong";
    let str = `大家好,我是${name}`;
    console.log(str);
    // 控制台输出大家好,我是JohnYong
    // 对于一般字符串来说是需要+号来拼接的
    
  • 字符串里面支持换行

    let str = `
    	<h1>大标题</h1>
    	<p>文本内容</p>
    `
    console.log(str);
    // 在控制台的打印结果中是包括换行的,
    
  • 字符串里面可以使用$符号调用函数

    function getAge() {
    	let age = 25;
    	return age;
    }
    let str = `我叫李荣浩,今年${getAge()}岁`;
    console.log(str);
    // 控制台打印我叫李荣浩,今年25岁
    // 在模板字符串中调用函数得到的是其返回值
    

String对象的一些扩展方法

  • startsWith()方法可以用来判断当前字符串是否以对应的字符串作为开头,endsWith()方法可以用来判断当前字符串是否以对应的字符串作为结尾,它们的语法格式为

    字符串变量.startsWith(字符串);
    字符串变量.endsWith(字符串);
    

    下面举个例子体会一下

    let str = "A lot of people want to lose weight but never succeed"
    console.log(str.startsWith("A lot of"));
    console.log(str.endsWith("fail"));
    console.log(str.endsWith("d"));
    // 控制台第一行打印true,第二行打印false,第三行打印true
    
  • repeat()方法可以重复当前字符串,其语法格式如下

    字符串变量.repeat(数字);
    // 数字表示重复的次数,最终返回一个新的字符串
    

    比如下面这个例子

    let str = "多敲代码";
    let strThree = str.repeat(3);
    console.log(strThree);
    // 控制台打印→"多敲代码多敲代码多敲代码"
    console.log("6".repeat(3))
    // 控制台打印→"666"
    

Set数据结构

在ES6中,Set数据结构是一种新增的数据结构,这个数据结构可以认为是元素不允许重复的数组(但是它的样子是一个对象)。所以这个数据结构中每个元素都是唯一的,创建Set数据结构可以利用Set构造函数,语法格式如下

const 常量名 = new Set();
// 创建一个空的Set数据结构
// 为了方便描述,这个常量在我们后面都叫Set数据结构
// 通过常量名.size可以获取Set数据结构里面元素的个数

还有一种创建方式就是将数组转换为Set数据结构,语法格式如下

const 常量名 = new Set(数组名);
// 创建一个将对应数组转换而来的Set数据结构

如果对应的数组包含重复元素,那么转换为Set数据结构之后会自动帮助我们去除里面的重复元素(简称去重),下面举个例子

let company = ["腾讯","华为","阿里","华为","谷歌"];
const setArr = new Set(company);
console.log(setArr);
// 控制台打印{'腾讯', '华为', '阿里', '谷歌'}

Set数据结构转换为数组的操作如下:

let arr = [...常量名];
// 此时的arr就是一个真正意义的数组
// 利用这个特点可以做数组的去重操作

set数据结构还给我们提供了一些方法供我们去调用,下面说几个常用的方法

方法名描述
add(value)给当前Set数据结构添加某个值,返回Set数据结构本身
delete(value)给当前Set数据结构删除某个值,返回表示是否删除成功的布尔值
has(value)判断当前Set数据结构是否有对应的值,返回布尔值
clear()清除当前Set数据结构里面的所有值,返回undefined

下面举例来体会一下这些方法的使用

let carArr = ["奥迪", "五菱宏光", "保时捷", "长城", "本田"];
const newArr = new Set(carArr);
newArr.add("捷达");
console.log(newArr);
// 控制台打印{'奥迪', '五菱宏光', '保时捷', '长城', '本田''捷达'}
newArr.delete("五菱宏光");
console.log(newArr);
// 控制台打印{'奥迪', '保时捷', '长城', '本田''捷达'}
console.log(newArr.has("本田"));
// 控制台打印true
newArr.clear();
console.log(newArr);
// 控制台打印{}

遍历Set数据结构可以使用forEach方法,比如说

const newArr = new Set(["稻香","等你下课","夜曲"]);
newArr.forEach( item => {
    console.log(item);
})
// 控制台依次输出"稻香","等你下课","夜曲"

函数新的定义方式

前面我们知道定义函数的两种方式:命名函数的定义和匿名函数的定义。而在ES6中新增了一种定义方式,也就是使用Function()构造函数来定义一个函数,语法格式如下

var 变量名 = new Function("形参1",... , "形参n", "函数体");
// 定义函数的时候,形参和函数体必须要用引号包起来
// 上面的形参不是必填的,如果填了形参,那么函数体必须放在最后面
// 函数体部分直接写代码,不用加括号{}

通过变量名("形参1",... , "形参n")即可调用函数,下面举个例子

var fn = new Function("a","b","c","console.log(a+b+c);");
fn(1,2,3);
// 控制台打印6

需要说明的点:这种函数定义的执行效率是比较低的,因此使用较少;此外,所有的函数其实都是这个Function内置对象的实例对象,如果使用instanceof检测一个函数是否为对象,结果是返回true,所以需要知道函数也是一个对象

立即执行函数

立即执行函数指的是不需要手动调用而直接执行的函数,了解一下即可。其语法格式如下

(function() {
    函数体
})()
// 前面那个小括号用来封装函数,后面那个小括号用来自动调用函数