ES6语法基础

来源:互联网 发布:破解版gta5显示无网络 编辑:程序博客网 时间:2024/06/04 08:57

变量
ES6中,增加了let和const两个关键字声明变量。
建议大家放弃var声明变量的模式。var的特性与现代语言差异过大,非常容易造成bug
1,let
let声明变量非常简单,如下代码:

let name = 'Tom';console.log(name);let a = 1;let b = 2;let sum = a+b;

在声明变量中,变量名为驼峰命名法。例如:let myName = ‘Tom’;
变量一定要先申明,在使用,例如:

console.log(name);//错误,找不到namelet name = 'Tom';

2,const
const声明的变量,其值不能改变,代码如下:

const age = 10;age = 11;//error,const 声明的变量,其值不能改变

但大家一定注意一点,const声明的变量值不能变,但如果该变量为对象,其对象属性值可变。

const person={    name:'Tom',    age:10,};person.name = 'Jhon';//没问题person = {    name:'TT',    age:11}//error,错误,不能改变person的值

3,变量作用域

let和const声明的变量,以大括号为作用域边界,只有在变量作用域内,才可以使用该变量,超过变量作用域再使用该变量,报错。例如:

{    const name = 'Tom';    console.log(name);//正确}console.log(name);//错误!name作用域再上面大括号内,超过无法使用

在有多层作用域嵌套的情况下,如果有同名变量,内层变量会屏蔽外面变量作用域,例如:

{    const name = 'Tom';    {        const name = 'Jim';        console.log(name);//打印为Jim    }    console.log(name);//打印为Tom}

在多层作用域嵌套下,如没有同名变量,作用域范围不变。例如:

{    const name = 'Tom';    {        console.log(name);//打印为Tom    }    console.log(name);//打印为Tom}

注意,在多层作用域嵌套下,内层如有声明同名变量,内层变量声明之前,变量不能使用。例如:

{    const name = 'Tom';    {        console.log(name);//错误!找不到name变量!        const name = 'Jim';    }    console.log(name);//打印为Tom}

字符串
JavaScript的字符串就是用”或”“括起来的字符表示。
如果’本身也是一个字符,那就可以用”“括起来,比如”I’m OK”包含的字符是I,’,m,空格,O,K这6个字符。
如果字符串内部既包含’又包含”怎么办?可以用转义字符\来标识,比如:

'I\'m \"OK\"!';

表示的字符串内容是:

I'm "OK"!

1,字符串模板
要把多个字符串连接起来,可以用+号连接:

const name = 'Tom';const age = 10;const message = 'Hello, ' + name + ', your age is' + age;console.log(message);

如果有很多变量需要连接,用+号就比较麻烦。ES6新增了一种模板字符串,表示方法和上面的多行字符串一样,但是它会自动替换字符串中的变量:

const name = 'Tom';const age = 10;const message = `Hello, ${name}, your age is ${age}`JavaScript为字符串提供了一些常用方法,注意,调用这些方法本身不会改变原有字符串的内容,而是返回一个新字符串

注意包裹字符串的字符为反引号,esc按键下方的字符,英文状态下输入。

2,多行字符串
由于多行字符串用\n写起来比较费事,所以最新的ES6标准新增了一种多行字符串的表示方法,用反引号包裹。

`HelloWorld`//这是一个多行字符串

3,操作字符串

获取字符串长度

const s = 'Hello, world!';s.length; // 13

要获取字符串某个指定位置的字符,使用类似Array的下标操作,索引号从0开始:

const s = 'Hello, world!';s[0]; // 'H's[6]; // ' 's[7]; // 'w's[12]; // '!'s[13]; // undefined 超出范围的索引不会报错,但一律返回undefined

注意:字符串是不可变的,如果对字符串的某个索引赋值,不会有任何错误,但是,也没有任何效果

const s = 'Test';s[0] = 'X';console.log(s); // s仍然为'Test'·

JavaScript为字符串提供了一些常用方法,注意,调用这些方法本身不会改变原有字符串的内容,而是返回一个新字符串。
indexOf,会搜索指定字符串出现的位置:

const s = 'hello, world';s.indexOf('world'); // 返回7s.indexOf('World'); // 没有找到指定的子串,返回-1

substring,返回指定索引区间的子串:

const s = 'hello, world's.substring(0, 5); // 从索引0开始到5(不包括5),返回'hello's.substring(7); // 从索引7开始到结束,返回'world'

更多JavaScript 字符串操作,可参见 W3School教程

数组

JavaScript的Array可以包含任意数据类型,并通过索引来访问每个元素。通过length可以访问数组长度。
1,通过索引访问数组元素

//通过索引读取数组中的元素const item = array[0];console.log(item);//输出0

注意:JavaScript中对数组越界操作没有异常处理

const array = [1,2,3,4];console.log(array[5]);//不报错,打印undefined

2, 通过数组索引,可以修改数组中元素值

const array = [1,2,3,4];array[0] = 5;console.log(array[0]);//输出为5

注意:对越界修改数组的值,会改变数组长度,且为赋值的元素值为undefined。不建议此操作

const array = [1,2,3,4];array[5] = 6;console.log(array);//打印结果为[1,2,3,4,undefined,6]

3,push和pop

//push()向数组末尾添加元素const array = [1,2,3,4];array.push(5);console.log(array);//打印结果[1,2,3,4,5]//pop()从数组末尾删除元素const array = [1,2,3,4];array.pop();console.log(array);//打印结果为[1,2,3];

4,splice

该方法可以操作数组在制定位置添加或删除元素

//删除指定元素//splice(index,length)从index位置开始删除长度为lengthconst array = [1,2,3,4];array.splice(1,1);//从下标1开始删除一个元素,即把2删除console.log(array);//输出结果[1,3,4]
//添加制定元素//splice(index,0,item1,item2,...),表示从index位置开始添加各元素,其他元素后移const array = [1,2,3,4];array.splice(1,0,5);console.log(array);//输出结果[1,5,2,3,4]
//混合操作,添加删除const array = [1,2,3,4];array.splice(1,1,5);console.log(array);//输出结果[1,5,3,4]

其他Array操作方法,详见W3SchoolArray教程

对象
JavaScript的对象是一种无序的集合数据类型,它由若干键值对组成。
键值对是有键和值组成的,一般称为key-value。键和值直接用冒号分割,键只能为字符串,值可以为任何类型。
在对象中,多个键值对之前没有顺序概念,之间用逗号const obj = {
name:’Tom’,
age:10,
‘a:b’:’xx’,
}符串,默认可以不加单引号,但如果字符串本身不加单引号有歧义
比如,上方第三个键值对中的键,为a:b一个字符串,如不加单引号,会有歧义,所以需要加。
通过对象的键可以对其对应的值进行读写操作。

const obj = {    name:'Tom',    age:10,    'a:b':'xxx',}//通过 . 读取对象属性值console.log(obj.name);//输出Tomobj.name = 'XXX';console.log(obj.name);//输出XXX//通过 [] 读取对象属性值,中括号内需要填字符串console.log(obj.['age']);obj['age'] = 11;console.log(obj['age']);//有歧义的键,只能用[]号方式访问console.log(obj.['a:b']);//使用变量作为键时,只能使用[]方式访问const key = 'name';console.log(obj.[key]);

函数

JavaScript中提供函数的定义和调用方式
1,基本语法

{  return x + y;}const sum = add(1,2);console.log(sum);//结果为3

2,参数默认值

function add(x = 1,y = 2){    return x+y;}const sum1 = add();console.log(sum1);//结果为3const sum2 = add(2);console.log(sum2);//结果为4const sum3 =  add(2,3);console.log(sum3);//结果为5

3,箭头函数

ES6增加箭头函数特性

(x,y)=>{    return x + y;}

箭头函数没有函数名,只描述了参数列表和函数体。
通常情况下,箭头函数一般作为值进行传递。

const aFunc = ()=>{    console.log('xxx');}//将箭头函数赋值给aFunc变量const bFunc = aFunc;//通过aFunc变量为bFunc变量赋值

箭头函数无法直接被调用,只能借助存放箭头函数值的变量进行调用,调用方法与调用普通函数一样

const add = (x,y)=>{    return x+y;}const sum = add(1,2);console.log(sum);//结果为3

箭头函数也经常作为函数的参数,为函数形参赋值

function Test(func){    func();}const f1 = ()=>{    console.log('a');}const f2 = ()=>{    console.log('b');}Test(f1);//输出结果为aTest(f2);//输出结果为b

4,高阶函数

高阶函数本质为能够接收其他函数作为参数传入的函数。也就是说,高阶函数本身也是一种函数,只不过它的参数可以为其他函数,一般我们用箭头函数作为高阶函数的参数。

例如我们上文中提到的Test()函数,可以称为一个高阶函数。

我们对高阶函数的要求为,会使用,不要求会编写。这是基于我们当前开发技术提出的要求。在ReactNative开发中,我们会遇到很多写好的高阶函数,供我们使用,来完成更复杂的功能。但我们并不需要自己编写一个高阶函数。

所以本节重点是研究如何使用高阶函数。

首先我们需要先明确一个概念:函数的类型。

函数类型由其输入参数和返回值决定,输入和返回值相同的函数,我们称之为同一类型函数。例如:

(x,y)=>{    return x+y;}(a,b)=>{    return a-b;}

这两个为同类型函数,因输入参数和返回值类型相同。
我们来看第一个例子,首先我给出一个写好的高阶函数,同学们不用纠结高阶函数如何实现。

//func1的参数是一个f,f的类型为无输入参数,无返回值。//func1为一个高阶函数function func1(f){    f();}

有了func1这个高阶函数,所有无输入参数,无返回值的函数,都可以作为参数出入该高阶函数。例如:

f1 = ()=>{    console.log('hello');}func1(f1);//输出hello

读到这里,大家可能会有一个疑问。使用了高阶函数,执行操作也仅仅是打印一个hello字符串。这样做的意义是什么。接下来我们便进行讨论,解释高阶函数的意义。
5,高阶函数的意义

在讨论这个问题之前,我们先明确两个概念,在上文中提到的,

func1:我们称之为高阶函数。

f1:是高阶函数的参数,也是一个函数。我们一般称之为这个函数为功能函数

高阶函数+功能函数的组合,才能实现完整的功能。其中高阶函数一般为框架提供,功能函数一般由使用框架的开发者自己编写。

为什么要是这样的组合呢?

是因为,我们做应用开发,尤其是App开发,如果代码从零开始写,那成本是非常高的。所以,我们在开发的时候,都是在各种框架下进行二次开发,这样可以非常有效的提升开发效率,避免大量重复基础的工作。我们可以认为,如果一个App的开发工作是100,那么一个优秀的框架可以帮助我们完成其中的70-80的工作量。我们只需要完成20-30的工作量便可开发一个App并发布。

所以,开发效率的核心是框架。

那框架是如何被开发出来的呢?

这个问题比较复杂,在此我们举一个简单的例子,方便大家理解,并不做深入探讨。
比如我们书中提供一个计算框架共大家使用。

计算这个功能不止包含法则,还包含数据的存储和验证。我们平时写的1+2,仅仅是通过运算符对计算法则的描述。

function compute(x,y,f){   return f(x,y);}

上面代码中的compute便是我给大家的一个计算框架,我们可以想计算框架中传入两个计算变量一个计算规则,计算规则通过函数来描述。
我们先用计算框架做一个简单的加法计算:

//准备计算数据const p = 1;const q = 2;//先用函数描述一个加法计算规则const f1 = (a,b)=>{    return a+b;}//调用计算框架进行计算const sum = compute(p,q,f1);console.log(sum);//打印结果为3

计算框架使用我们传入的计算数据和计算法则,计算出我们的结果。这个计算框架就是高阶函数。

到这里,大家可能还是不能够理解,我简单的a+b也能完成这样的操作,为什么要用计算框架这个高阶函数。这是因为,我们的问题不足够复杂,没法提现计算框架的优势。下面我们在构造一个更复杂的计算。

我们规定,计算法则为:两个输入参数之和如果为奇数就相乘,如果之和为偶数结果为0。

这样稍微复杂的计算规则,普通的处理方式就没法进行了,这时候就体现出计算框架的功能了。

//构造计算规则const f2 = (x,y)=>{    const s = x+y;    if(s%2 == 0){        return 0;    }    return x*y;}const result1 = compute(2,2,f2);console.log(result1);//结果为0;const result2 = compute(1,2,f2);console.log(result2);//结果为2

通过计算框架,我们不需要关系具体计算过程,我们只需要描述特定的业务即可。
这便是高阶函数的意义:提供了一种使用框架的方法,让我们开发更加高效。
6,高阶函数的应用

这一节我们举两个例子,说明高阶函数的使用场景

  • 提供操作基础
  • 提供事件回调

一般来说,高阶函数无法独自使用,必须配合我们写的功能函数才能起作用。
案例1,数组的遍历

//一般我们使用for循环进行数组的遍历const array = [1,2,3,4];for(let i = 0; i < array.length; i++){    const value = array[i];    console.log(value);}

此种方式,我们需要手写一个for循环来进行,但是大家思考一下,所有的数组遍历模式都差不多,那么有没有框架将for循环这种重复的基础操作封装,只让我们写遍历规则呢。

答案是肯定的,JavaScript语言本身的Array对象中,包含了一个map()方法,该方法便是一个高阶函数,其参数需要传入一个函数,函数类型为:两个输入参数,一个返回值。

第一个输入参数为数组中某一个元素的值

第二个输入参数为该元素在数组中的索引

map方法会用返回值组成一个新的数组并返回

const array = [1,2,3,4];array.map((value,index)=>{    console.log(value);})//依次打印数组中每一个值const arr1 = [1,2,3,4];const arr2 = arr1.map((value,index)=>{    return value * 2;});console.log(arr2);//结果为[2,4,6,8];

通过使用框架提供的高阶函数map,我们无需关注如果遍历数组,只需要关注遍历规则即可。

案例2,Timer事件回调

Timer是JavaScript中定时器,可以进行周期性操作,例如每隔1秒钟打印一个字符串。实现这样周期性操作,同样使用的高阶函数

//setInterval(func,msecond)//该函数有两个输入参数,第一个输入参数为一个函数,类型为无输入参数,无返回值。第二个输入参数为间隔时间,单位毫秒//我们通过函数描述周期性需要完成的任务,例如是一个打印任务const task = ()=>{    console.log('hello');};//只需要将此任务传入高阶函数,并设置好间隔时间,该任务便会安装计划运行,setInterval(task,1000);//每1秒钟打印一个hello

高阶函数的应用场景很多,我们经常用的就是以上两种类型。

对于高阶函数,是区分程序员能力的标杆,后续的案例,我们会接触越来越多的高阶函数。使大家对高阶函数的引用有更加深刻的认识。

JSON

JSON是JavaScript Object Notation的缩写,它是一种数据交换格式。

在JSON中,一共就一下几种数据类型:

  • number:和JavaScript的 number完全一致;
  • boolean:就是JavaScript的true或false;
  • string:就是JavaScript的string;
  • null:就是JavaScript的null;
  • array:就是JavaScript的Array表示方式——[];
  • object:就是JavaScript的{ … }表示方式。

上面的数据可以任意嵌套和组合。

由于JSON非常简单,很快就风靡Web世界,并且成为ECMA标准。几乎所有编程语言都有解析JSON的库,而在JavaScript中,我们可以直接使用JSON,因为JavaScript内置了JSON的解析。

把任何JavaScript对象变成JSON,就是把这个对象序列化成一个JSON格式的字符串,这样才能够通过网络传递给其他计算机。

如果我们收到一个JSON格式的字符串,只需要把它反序列化成一个JavaScript对象,就可以在JavaScript中直接使用这个对象了。
1,序列化

JSON序列化是指,将JavaScript中的对象转换为JSON格式的字符串。

例如我们在数据库中查询到了一个人的信息,需要把这个信息通过网络传递到前台的App进行显示,这时,我们需要将此对象进行JSON序列化,转换成JSON字符串才能进行传输。

const person = {    name:'Tom',    age:10,    tel:'18612341234'}//JSON序列化使用JavaScript语言中自带的JSON解析器的stringify方法const jsonData = JSON.stringify(person);console.log(jsonData);//字符串:'{"name":"Tom","age":11,"tel":'1861234123'}'

2,反序列化

JSON反序列化也叫JSON解析。

拿到一个JSON格式的字符串,我们直接用JSON.parse()把它变成一个JavaScript对象。

一般来说,JSON格式的字符串都是通过网络请求获取

在此,我们为了测试JSON反序列化的功能,手写一个本地的JSON格式字符串。

const jsonData = '{"name":"Tom","age":11,"tel":18612341234}';const person = JSON.parse(jsonData);console.log(person.name);//结果为Tom

Class

ES6新增了class关键字,对面向对象编程有了更好的支持。
1,基本语法

class Point {    constructor(x,y){        this.x = x;        this.y = y;    }    toString(){        return `x is ${this.x}, y is ${this.y}`;    }}const p = new Point(1,2);const s = p1.toString();console.log(s);//输出结果为x is 1, y is 2;

上面代码演示了ES6中的class的基本用法。

  • 使用class定义一个类
  • constructor为类的构造方法,当使用类实例化一个对象是,该方法会调用,并接受实例化时传入的参数
  • 类的属性不需要提前声明,可以直接使用,为定义的属性值为null
  • 声明类的方法时,不需要写function

2,this对象

this对象表示该类创建的对象,可以简单理解为自身。

this有两种使用场景:

  • 访问自身属性
  • 调用自身方法
class Point {  constructor\(x,y\){        this.x = x;        this.y = y;    }  toString(){      //通过this访问自身属性      return `x is ${this.x}, y is ${this.y}`;  }}class Point {  constructor\(x,y\){        this.x = x;        this.y = y;    }  toString(){      //通过this访问自身属性      return `x is ${this.x}, y is ${this.y}`;  }  logSelf(){      //通过this调用自身方法      const s = this.toString();      console.log(s)  }}const p1 = new Point(1,2);p1.logSelf();

3,手动绑定this对象

在特定情况下,类中的方法在调用时,会产生this为undefined的情况。这些情况较为复杂,后续章节会详细说明,在此不展开讲解。

例如,如下代码,变无法正常运行:

class Timer {    constructor(x){        this.x = x;    }    start(){        setInterval(this.event,1000);    }    event(){        console.log('a');        console.log(this.x);    }}const t = new Timer(1);t.start();

上述代码的运行结果为,每秒钟,打印一个a字符,本应该一起打印的属性x的值,无法输出。

原因为,在此种情况下,event方法中的this对象为undefined,所以无法通过this对象访问属性x的值。

我们在学习初期,无法区分哪些情况this对象会丢失,所以我们采用一个统一的保守方案来修改此bug。

只要是自己写的方法且方法中用到了this对象,我们都统一在constructor方法中,手动对其绑定this对象,避免this对象丢失的情况发生。

class Timer {    constructor(x){        this.x = x;        //手动绑定this        this.event = this.event.bind(this);    }    start(){        setInterval(this.event,1000);    }    event(){        console.log('x');            console.log(this.x);    }}const t = new Timer(1);t.start();

通过手动绑定this对象之后,this对象丢失的情况便不会发生。

4,类的继承

在ES6中,提供的extends关键字用来实现类的继承关系。

class Base {    say(){        console.log('this is Base');    }}class Sub extends Base {    sayHello(){        console.log('this is Sub');    }}const s = new Sub();s.say();//输出结果为this is Bases.sayHello();//输出结果为 this is Sub

Sub类通过继承获得say方法,所有通过Sub实例化的对象也同样可以使用该方法。

如果子类的方法与父类方法同名,那么子类的方法会覆盖父类的方法。

class Base {  say(){    console.log('this is Base');  }}class Sub extends Base {  say(){    console.log('this is Sub');  }}const s = new Sub();s.say();//输出结果为this is Sub

5,super对象

super对象和this对象类似。this对象表示对象本身,super对象表示本身对象的父类对象。
super对象的使用场景两个:

  • 调用与子类同名的父类方法
  • 调用父类的constructor
class Base {  say(){    console.log('this is Base');  }}class Sub extends Base {  say(){    //调用父类的say方法    super.say();    console.log('this is Sub');  }}const s = new Sub();s.say();//输出结果为this is Basethis is Sub
class Point {    constructor(x,y){        this.x = x;        this.y = y;    }}class Point3D extends Point {    constructor(x,y,z){        //调用父类的constructor构造函数        super(x,y);        this.z = z;    }    logSelf(){        console.log(`x:${this.x},y:${this.y},z:${this.z}`);    }}const p = new Point3D(1,2,3);p.logSelf();//输出结果x:1,y:2,z:3

Module模块系统

ES6提供了语言级别的模块系统解决方案,是模块化编程更加简单和清晰。

我们将一个JavaScript文件看做一个js模块,在模块内部的变量和表达式,只要在相同的作用域中,不需要额外的引入操作变可以直接使用。

但在不同模块之前的变量或者表达式无法直接使用,需要使用Module系统进行暴露和导入操作,才可以使用。

在编写代码时,我们通常在一个js模块中编写一个类,方面后期的维护和阅读。当在另一个js模块中需要使用这个类时,我们需要进行以下两步操作

  • 在编写类的js模块中,暴露该类的定义
  • 在使用类的js模块中,引入该类的定义

在完成以上两步操作之后,便可以在新的模块中使用已经写好的类的代码。
1,暴露模块接口

在ES6中,使用export关键字对模块内的定义进行暴露。
一般我们暴露三种接口:

  • 变量接口
  • 函数接口
  • Class接口

暴露接口方法有两种形式,分别为暴露定义和通过大括号暴露名称。直接暴露名称为错误方法!

注意:export命令,只能写在模块的顶层作用域(注意,不是顶部)。顶层作用域的意思是不能再任何一个大括号内写export

//变量接口//正确1,暴露定义export const a = 1;export const b = 2;//正确2const x = 1;const y = 1//通过大括号暴露名称export {     x,    y,};//错误!!!cosnt x = 1;export x;
//函数接口//正确1export function f1(){    console.log(1);}export function f2(){    console.log(2);}//正确2function f1(){    console.log(1);}function f2(){    console.log(2);}export {   f1,   f2,}
//class接口//正确1export class AClass {}export class BClass {}//正确2class AClass {}class BClass {}export {    AClass,    BClass,}

2,引入模块接口

引入模块使用import关键字进行。使用方法如下

import { 接口名 } from '相对路径'

例如,我们首先在a.js文件中暴露一个变量,

//a.jsexport const name = 'Tom';

如果我们想在b.js文件中使用这个变量,我们需要引入该变量接口。

我们假定a.js和b.js在同级目录下。

import { name } from './a.js';console.log(name);//输出结果为Tom

在import命令中,如引入文件为.js,那么其拓展名可以省略,其他类型文件不可以省略

import { name } from './a';与上午中代码等效,日常开发经常省略.js

在引入中一定注意,暴露的变量名和引入的变量名,一定要一致,如果不一致,将引入失败。
3,默认暴露

在上述讲解中,暴露和引入都必须使用相同的接口名,如果不相同便会出错。这样的要求在实际使用中不是很方便,在使用其他人的模块是,还需要阅读其源码才能完成正确引入。

在ES6通过这样的暴露引入机制同时,为了方面开发者更加方便的使用模块,增加了一个默认暴露引入机制。

该机制通过export default关键字进行暴露,且同一模块中,只能使用一次export default ,同时可以使用传统的export进行暴露

//a.jsexport default const name = 'Tom';

当引入默认暴露的接口是,接口名称可以自定义,无需和暴露名一致。同时也不需要大括号

import XXX from './a';console.log(XXX);//输出结果为Tom

默认暴露和普通暴露机制可以同时使用:

//a.jsexport default const name = 'Tom';export const age = 10;
import Name , { age } from './a';console.log(Name);console.log(age);

4, as重命名

在除了默认暴露以外的情况,暴露和引入的接口名必须一致。但有时模块内部有自己的命名规则,对外暴露是命名规则可能过于繁琐,我们便可以使用 as 关键字做暴露或者引入重命名,方面后面编码使用该接口。

暴露重命名

//a.jsconst xxx_name = 'Tom';export {    xxx_name as name,}import { name } from './a';console.log(name)

引入重命名

//a.jsexport const name = 'Tom';import { name as xxx_name } from './a'console.log(xxx_name);