2.ES核心

Posted Myvlog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2.ES核心相关的知识,希望对你有一定的参考价值。

3.ES核心

3.1.核心概念

3.1.1.索引

一个索引就是一个拥有几分相似特征的文档的集合。

一个索引由一个名字来标识(必须全部是小写字母),并且当我们要对这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。在一个集群中,可以定义任意多的索引。

能搜索的数据必须索引,这样的好处是可以提高查询速度,

索引:一切设计都是为了提高搜索的性能。

3.1.2.类型

在一个索引中,可以定义一种或多种类型。

类型是索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。

版本 Type
5.x 支持多种 type
6.x 只能有一种 type
7.x 默认不再支持自定义索引类型(默认类型为:_doc)

3.1.3.文档

一个文档是一个可被索引的基础信息单元,也就是一条数据

在一个 index/type 里面,可以存储任意多的文档。

3.1.4.字段

相当于是数据表的字段,对文档数据根据不同属性进行的分类标识。

3.1.5.映射

mapping是处理数据的方式和规则方面做一些限制

处理ES里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。

3.1.6.分片

一个索引可以存储超出单个节点硬件限制的大量数据。

Elasticsearch提供了将索引划分成多份的能力,每一份就称之为分片。当创建一个索引的时,可以指定想要的分片的数量。每个分片本身也是一个功能完善并且独立的索引,这个索引可以被放置到集群中的任何节点上。

分片是一个很重要的概念

  1. 允许水平分割 / 扩展你的内容容量。
  2. 允许在分片之上进行分布式的、并行的操作,进而提高性能/吞吐量。

3.1.7.副本

Elasticsearch 允许创建分片的一份或多份拷贝,这些拷贝叫做复制分片()。

副本也是一个很重要的概念

  1. 在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的
  2. 扩展搜索量/吞吐量,因为搜索可以在所有的副本上并行运行。
  3. 每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。
  4. 分片和复制的数量可以在索引创建的时候指定。在索引创建之后,可以在任何时候动态地改变副本的数量,但是不能改变分片的数量。
  5. 默认情况下,Elasticsearch中的每个索引被分片 1 个主分片和 1 个复制。

3.1.8.分配

将分片分配给某个节点的过程,包括分配主分片或者副本。

如果是副本,还包含从主分片复制数据的过程。这个过程是由master节点完成的。

3.2.系统架构

一个运行中的Elasticsearch实例称为一个节点,而集群是由一个或者多个拥有相同cluster.name配置的节点组成, 它们共同承担数据和负载的压力。当有节点加入集群中或者从集群中移除节点时,集群将会重新平均分布所有的数据。

当一个节点被选举成为主节点时, 它将负责管理集群范围内的所有变更。 而主节点并不需要涉及到文档级别的变更和搜索等操作,所以当集群只拥有一个主节点的情况下,即使流量的增加它也不会成为瓶颈。 任何节点都可以成为主节点。

我们可以将请求发送到集群中的任何节点 ,包括主节点。 每个节点都知道任意文档所处的位置,并且能够将我们的请求直接转发到存储我们所需文档的节点。 无论我们将请求发送到哪个节点,它都能负责从各个包含我们所需文档的节点收集回数据,并将最终结果返回給客户端。

3.3.分布式集群

3.3.1. 故障转移

当集群中只有一个节点在运行时,意味着会有一个单点故障问题——没有冗余。

在同一台机器上启动了第二个节点时,只要它和第一个节点有同样的cluster.name配置,它就会自动发现集群并加入到其中。

但是在不同机器上启动节点的时候,要加入到同一集群,需要配置一个可连接到的单播主机列表。之所以配置为使用单播发现,以防止节点无意中加入集群。只有在同一台机器上运行的节点才会自动组成集群。

3.3.2.水平扩容

当集群中的节点超过分片数量时可以进行扩容

主分片的数目在索引创建时就已经确定了下来。

在运行中的集群上是可以动态调整副本分片

 
    "number_of_replicas" : 2 
 

如果只是在相同节点数目的集群上增加更多的副本分片并不能提高性能,因为每个分片从节点上获得的资源会变少。 但是更多的副本分片数提高了数据冗余量。

3.3.3应对故障

当主节点宕机之后,集群会重新选举一个新的主节点来保证正常工作。

旧的主节点重新运行之后,也不会恢复为主节点,但是数据会进行同步

3.3.4.路由计算

当索引一个文档的时候,文档会被存储到一个主分片中。ES会通过下面这个公式进行分配存储

routing:可变值,默认是文档的id,也可以设置成一个自定义的值。

  • routing通过hash函数生成一个数字,然后这个数字再除以number_of_primary_shards(主分片的数量)后得到余数。
  • 这个分布在0到number_of_primary_shards-1之间的余数,就是寻求的文档所在分片的位置。

这解释了为什么要在创建索引的时候就确定好主分片的数量并且永远不会改变这个数量,因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。

所有的文档API都接受一个叫做routing的路由参数 ,通过这个参数可以自定义文档到分片的映射。一个自定义的路由参数可以用来确保所有相关的文档。

3.4.分片控制

我们可以发送请求到集群中的任一节点。 每个节点都有能力处理任意请求。 每个节点都知道集群中任一文档位置,所以可以直接将请求转发到需要的节点上。

3.4.1.写流程

新建、索引和删除请求都是写操作, 必须在主分片上面完成之后才能被复制到相关的副本分片

流程

  1. 客户端向节点1发送请求。
  2. 节点使用文档的id确定文档属于分片0。请求会被转发到节点3(分片0所在的节点)。
  3. 节点3在主分片上执行请求。
    • 如果成功:将请求并行转发到其他节点的副本分片上。当所有的副本分片都报告成功,节点3向协调节点报告成功,协调节点向客户端报告成功。

3.4.2.读流程

我们可以从主分片或者从其它任意副本分片检索文档

流程

  1. 客户端向节点1发送获取请求。
  2. 节点使用文档的id来确定文档属于分片0分片0的副本分片存在于所有的节点上。 在这种情况下,它将请求转发到节点2
  3. 节点2将文档返回给节点1,然后将文档返回给客户端。
    1. 在处理读取请求时,协调结点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。
    2. 在文档被检索时,已经被索引的文档可能已经存在于主分片上但是还没有复制到副本分片。 在这种情况下,副本分片可能会报告文档不存在,但是主分片可能成功返回文档。 一旦索引请求成功返回给用户,文档在主分片和副本分片都是可用的。

3.4.3.更新流程

部分更新一个文档

流程

  1. 客户端向节点1发送更新请求。
  2. 节点1将请求转发到主分片所在的节点3
  3. 节点3从主分片检索文档,修改source字段中的JSON,并且尝试重新索引主分片的文档。如果文档已经被另一个进程修改,它会重试步骤 3 ,超过retry_on_conflict次后放弃。
  4. 如果节点3成功地更新文档,它将新版本的文档并行转发到节点1和节点2上的副本分片,重新建立索引。
    • 一旦所有副本分片都返回成功,节点3向协调节点也返回成功,协调节点向客户端返回成功。

当主分片把更改转发到副本分片时, 它不会转发更新请求。 相反,它转发完整文档的新版本。请记住,这些更改将会异步转发到副本分片,并且不能保证它们以发送它们相同的顺序到达。 如果 Elasticsearch 仅转发更改请求,则可能以错误的顺序应用更改,导致得到损坏的文档。

3.4.4.多文档操作流程

mgetbulk API的模式类似于单文档模式。区别在于协调节点知道每个文档存在于哪个分片中。它将整个多文档请求分解成每个分片的多文档请求,并且将这些请求并行转发到每个参与节点。协调节点一旦收到来自每个节点的应答,就将每个节点的响应收集整理成单个响应,返回给客户端

mget

  1. 客户端向节点1发送mget请求。
  2. 节点1为每个分片构建多文档获取请求,然后并行转发这些请求到托管在每个所需的主分片或者副本分片的节点上。
    • 一旦收到所有答复,节点1构建响应并将其返回给客户端

可以对 docs 数组中每个文档设置 routing 参数。

bulk API

  1. 客户端向节点1发送bulk请求。
  2. 节点1为每个节点创建一个批量请求,并将这些请求并行转发到每个包含主分片的节点主机。
  3. 主分片一个接一个按顺序执行每个操作。当每个操作成功时,主分片并行转发新文档(或删除)到副本分片,然后执行下一个操作。 一旦所有的副本分片报告所有操作成功,该节点将向协调节点报告成功,协调节点将这些响应收集整理并返回给客户端。

3.5.分片原理

分片是Elasticsearch最小的工作单元。

3.5.1.倒排索引

适用于快速的全文搜索。

正向索引:搜索引擎会将待搜索的文件都对应一个文件 ID,搜索时将这个ID和搜索关键字进行对应,形成 K-V 对,然后对关键字进行统计计数

但是互联网上收录在搜索引擎中的文档的数目是个天文数字,这样的索引结构根本无法满足实时返回排名结果的要求。所以,搜索引擎会将正向索引重新构建为倒排索引,即把文件ID对应到关键词的映射转换为关键词到文件ID的映射,每个关键词都对应着一系列的文件,这些文件中都出现这个关键词。

3.5.2.文档搜索

早期的全文检索会为整个文档集合建立一个很大的倒排索引并将其写入到磁盘。 一旦新的索引就绪,旧的就会被其替换,这样最近的变化便可以被检索到。

倒排索引被写入磁盘后是不可改变的。

不可改变的好处

  1. 不需要锁。
  2. 一旦索引被读入内核的文件系统缓存,旧会一直留在那里。
  3. 其它缓存在索引的生命周期内始终有效。
  4. 写入单个大的倒排索引允许数据被压缩,减少磁盘I/O和需要被缓存到内存的索引的使用量。

不可改变的缺点:如果让一个新的文档可被搜索,需要重建整个索引。

  • 这要么对一个索引所能包含的数据量造成了很大的限制
  • 要么对索引可被更新的频率造成了很大的限制。

3.5.3.动态更新索引

用于在保留不变性的情况下能实现倒排索引的更新

通过增加新的补充索引来反映新近的修改,而不是直接重写整个倒排索引。每一个倒排索引都会被轮流查询到,从最早的开始查询完后再对结果进行合并。

Lucene引入了按段搜索的概念。 每一 段本身都是一个倒排索引,但索引在Lucene中除表示所有段的集合外, 还增加了提交点的概念(一个列出了所有已知段的文件)

按段搜索执行流程

  1. 新文档被收集到内存索引缓存
  2. 不时的缓存被提交
    1. 一个新的段被写入磁盘
    2. 一个新的包含新段名字的提交点被写入磁盘
    3. 磁盘进行同步
  3. 新的段被开启,让它包含的文档可见以被搜索
  4. 内存缓存被清空,等待接收新的文档

3.5.4.近实时搜索

可以通过设置 refresh_interval , 降低每个索引的刷新频率

 
  "settings":  
    "refresh_interval": "30s"  
   
 

refresh_interval可以在既存索引上进行动态更新。 在生产环境中,正在建立一个大的新索引时,可以先关闭自动刷新,待开始使用该索引时,再把它们调回来

# 关闭自动刷新 
PUT /users/_settings 
 "refresh_interval": -1   
# 每一秒刷新 
PUT /users/_settings 
 "refresh_interval": "1s"   

3.5.5.持久化变更

translog提供所有还没有被刷到磁盘的操作的一个持久化记录。当Elasticsearch启动的时候, 它会从磁盘中使用最后一个提交点去恢复已知的段,并且会重放 translog中所有在最后一次提交后发生的变更操作。

3.5.5.段合并

Elasticsearch通过在后台进行段合并,小的段被合并到大的段,然后这些大的段再被合并到更大的段。

一旦合并结束,老的段被删除

3.6.文档分析

分析包含下面的过程

  1. 将一块文本分成适合于倒排索引的独立的词条
  2. 将这些词条统一化为标准格式以提高它们的可搜索性,或者recall分析器执行上面的工作。分析器实际上是将三个功能封装到了一个包里
    1. 字符过滤器:字符串按顺序通过每个字符过滤器 。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉 HTML,或者将 & 转化成 and
    2. 分词器:字符串被分词器分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条。
    3. Token过滤器:词条按顺序通过每个 token 过滤器 。这个过程可能会改变词条(例如,小写化 Quick ),删除词条(例如, 像 a, and, the 等无用词),或者增加词条(例如,像 jump 和 leap 这种同义词)。

ES6核心特性

摘要:聊JS离不开ES6啊!

前言

ES6 虽提供了许多新特性,但我们实际工作中用到频率较高并不多,根据二八法则,我们应该用百分之八十的精力和时间,好好专研这百分之二十核心特性,将会收到事半功倍的奇效!写文章不容易,请大家多多支持与关注!本文首发地址GitHub博客

技术分享图片

一、开发环境配置

这部分着重介绍:babel 编译ES6语法,如何用webpack实现模块化。

1. babel

为啥需要babel?

ES6 提供了许多新特性,但并不是所有的浏览器都能够完美支持。下图是各个浏览器对ES6兼容性一览表(以export为例)

技术分享图片

由上图可知,有些浏览器对于ES6并不是很友好,针对 ES6 的兼容性问题,很多团队为此开发出了多种语法解析转换工具(比如babel,jsx,traceur 等),可以把我们写的 ES6 语法转换成 ES5,相当于在 ES6 和浏览器之间做了一个翻译官。其中Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。

如何配置babel?

  • 首先要先安装node.js,运行npm init,然后会生成package.json文件
  • npm install --save-dev babel-core babel-preset-es2015 babel-preset-latest
  • 创建并配置.babelrc文件//存放在项目的根目录下,与node_modules同级
  • npm install -g babel-cli
  • babel -version

Babel的配置文件是.babelrc,存放在项目的根目录下。该文件用来设置转码规则和插件,具体内容如下:

//.babelrc文件
{
    "presets": ["es2015", "latest"],
    "plugins": []
}

验证babel配置是否成功

·创建./src/index.js
·内容:[1,2,3].map(item=>item+1);
·运行babel./src/index.js

运行后得到以下部分,说明已经成功配置了babel

"use strict";
[1, 2, 3].map(function (item) {
  return item + 1;
});

2. webpack

为啥要使用WebPack?

现今的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的JavaScript代码和一大堆依赖包,模快化工具就应运而生了,其中webpack 功能强大深受人们喜爱。
Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。

技术分享图片

如何配置webpack?

·npm install webpack babel-loader --save-dev
·创建并配置 webpack.config.js//webpack.config.js文件与package.json同级
·配置 package.json中的scripts
·运行 npm start
//配置 webpack.config.js  针对.js结尾的文件除了node_modules都用babel解析
module.exports = {
    entry: ‘./src/index.js‘,
    output: {
        path: __dirname,
        filename: ‘./build/bundle.js‘
    },
    module: {
        rules: [{
            test: /.js?$/,
            exclude: /(node_modules)/,
            loader: ‘babel-loader‘
        }]
    }
}
//配置 package.json中的scripts
"scripts": {
    "start": "webpack",
    "test": "echo "Error: no test specified" && exit 1"
  }

二、块级作用域

ES5 只有全局作用域和函数作用域(例如,我们必须将代码包在函数内来限制作用域),这导致很多问题:

情况1:内层变量覆盖外层变量

var tmp = new Date();
function f() {
  console.log(tmp); //undefined
  if (false) {   
    var tmp = "hello world";
  }
}

情况2:变量泄露,成为全局变量

var s = ‘hello‘;
for (var i = 0; i < s.length; i++) {
  console.log(s[i]);
}
console.log(i); // 5

ES6 提供 let 和 const 来代替 var 声明变量,新的声明方式支持用大括号表示的块级作用域,这会带来一些好处:

不再需要立即执行的函数表达式(IIFE)

在 ES5 中,我们需要构造一个立即执行的函数表达式去保证我们不污染全局作用域。在 ES6中, 我们可以使用更简单的大括号({}),然后使用 const 或者 let 代替 var 来达到同样的效果。

循环体中的闭包不再有问题

在 ES5 中,如果循环体内有产生一个闭包,访问闭包外的变量,会产生问题。在 ES6,你可以使用 “let” 来避免问题。

技术分享图片

防止重复声明变量

ES6 不允许在同一个作用域内用 let 或 const 重复声明同名变量。这对于防止在不同的 js 库中存在重复声明的函数表达式十分有帮助。

三、数组的扩展

Array.from() : 将伪数组对象或可遍历对象转换为真数组

如果一个对象的所有键名都是正整数或零,并且有length属性,那么这个对象就很像数组,称为伪数组。典型的伪数组有函数的arguments对象,以及大多数 DOM 元素集,还有字符串。

...
<button>测试1</button>
<br>
<button>测试2</button>
<br>
<button>测试3</button>
<br>
<script type="text/javascript">
let btns = document.getElementsByTagName("button")
console.log("btns",btns);//得到一个伪数组
btns.forEach(item=>console.log(item)) Uncaught TypeError: btns.forEach is not a function
</script>

针对伪数组,没有数组一般方法,直接遍历便会出错,ES6新增Array.from()方法来提供一种明确清晰的方式以解决这方面的需求。

Array.from(btns).forEach(item=>console.log(item))将伪数组转换为数组

技术分享图片

Array.of(v1, v2, v3) : 将一系列值转换成数组

当调用 new Array( )构造器时,根据传入参数的类型与数量的不同,实际上会导致一些不同的结果, 例如:

let items = new Array(2) ;
console.log(items.length) ; // 2
console.log(items[0]) ; // undefined
console.log(items[1]) ;
let items = new Array(1, 2) ;
console.log(items.length) ; // 2
console.log(items[0]) ; // 1
console.log(items[1]) ; // 2

当使用单个数值参数来调用 Array 构造器时,数组的长度属性会被设置为该参数。 如果使用多个参数(无论是否为数值类型)来调用,这些参数也会成为目标数组的项。数组的这种行为既混乱又有风险,因为有时可能不会留意所传参数的类型。

ES6 引入了Array.of( )方法来解决这个问题。该方法的作用非常类似Array构造器,但在使用单个数值参数的时候并不会导致特殊结果。Array.of( )方法总会创建一个包含所有传入参数的数组,而不管参数的数量与类型

let items = Array.of(1, 2);
console.log(items.length); // 2
console.log(items[0]); // 1
console.log(items[1]); // 2
items = Array.of(2);
console.log(items.length); // 1
console.log(items[0]); // 2

Array.of基本上可以用来替代Array()或newArray(),并且不存在由于参数不同而导致的重载,而且他们的行为非常统一。

数组实例的 find() 和 findIndex()

数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。

[1, 4, -5, 10].find((n) => n < 0) // -5

数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。

[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2

数组实例的includes()

Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值。该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。

[1, 2, 3].includes(2)   // true
[1, 2, 3].includes(3, -1); // true
[1, 2, 3, 5, 1].includes(1, 2); // true

没有该方法之前,我们通常使用数组的indexOf方法,检查是否包含某个值。indexOf方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判

[NaN].indexOf(NaN) // -1
[NaN].includes(NaN) // true

数组实例的 entries(),keys() 和 values()

ES6 提供entries(),keys()和values(),用于遍历数组。它们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

for (let index of [‘a‘, ‘b‘].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of [‘a‘, ‘b‘].values()) {
  console.log(elem);
}
// ‘a‘
// ‘b‘

for (let [index, elem] of [‘a‘, ‘b‘].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"

四、箭头函数

ES6 允许使用“箭头”(=>)定义函数。它主要有两个作用:缩减代码和改变this指向,接下来我们详细介绍:

缩减代码

const double1 = function(number){
   return number * 2;   //ES5写法
}
const double2 = (number) => {
 return number * 2;    //ES6写法
}
const double4 = number => number * 2; //可以进一步简化

多个参数记得加括号

const double6 = (number,number2) => number + number2;

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回

 const double = (number,number2) => {
   sum = number + number2 
   return sum;
 }

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错

// 报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报
let getTempItem = id => ({ id: id, name: "Temp" });

此外还有个好处就是简化回调函数

// 正常函数写法
[1,2,3].map(function (x) {
  return x * x;
});
// 箭头函数写法
[1,2,3].map(x => x * x);//[1, 4, 9]

改变this指向

长期以来,JavaScript 语言的this对象一直是一个令人头痛的问题,在对象方法中使用this,必须非常小心。箭头函数”绑定”this,很大程度上解决了这个困扰。我们不妨先看一个例子:

const team = {
  members:["Henry","Elyse"],
  teamName:"es6",
  teamSummary:function(){
    return this.members.map(function(member){
      return `${member}隶属于${this.teamName}小组`;    // this不知道该指向谁了
    })
  }
}
console.log(team.teamSummary());//["Henry隶属于undefined小组", "Elyse隶属于undefined小组"]

teamSummary函数里面又嵌了个函数,这导致内部的this的指向发生了错乱。
那如何修改:

方法一、let self = this

const team = {
  members:["Henry","Elyse"],
  teamName:"es6",
  teamSummary:function(){
    let self = this;
    return this.members.map(function(member){
      return `${member}隶属于${self.teamName}小组`;
    })
  }
}
console.log(team.teamSummary());//["Henry隶属于es6小组", "Elyse隶属于es6小组"]

方法二、bind函数

const team = {
  members:["Henry","Elyse"],
  teamName:"es6",
  teamSummary:function(){
    return this.members.map(function(member){
      // this不知道该指向谁了
      return `${member}隶属于${this.teamName}小组`;
    }.bind(this))
  }
}
console.log(team.teamSummary());//["Henry隶属于es6小组", "Elyse隶属于es6小组"]

方法三、 箭头函数

const team = {
  members:["Henry","Elyse"],
  teamName:"es6",
  teamSummary:function(){
    return this.members.map((member) => {
      // this指向的就是team对象
      return `${member}隶属于${this.teamName}小组`;
    })
  }
}
console.log(team.teamSummary());//["Henry隶属于es6小组", "Elyse隶属于es6小组"]

使用注意点

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

五、rest 参数

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。

rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
我们举个例子:如何实现一个求和函数?

传统写法:

function addNumbers(a,b,c,d,e){
  var numbers = [a,b,c,d,e];
  return numbers.reduce((sum,number) => {
    return sum + number;
  },0)
 }
 console.log(addNumbers(1,2,3,4,5));//15

ES6写法:

 function addNumbers(...numbers){
  return numbers.reduce((sum,number) => {
    return sum + number;
  },0)
 }
 console.log(addNumbers(1,2,3,4,5));//15

也可以与解构赋值组合使用

var array = [1,2,3,4,5,6];
var [a,b,...c] = array;
console.log(a);//1
console.log(b);//2
console.log(c);//[3, 4, 5, 6]

rest 参数还可以与箭头函数结合

const numbers = (...nums) => nums;
numbers(1, 2, 3, 4, 5)// [1,2,3,4,5]  

注意:①每个函数最多只能声明一个rest参数,而且 rest参数必须是最后一个参数,否则报错。

②rest参数不能用于对象字面量setter之中

let object = {
    set name(...value){   //报错
        //执行一些逻辑
    }
}

六、展开运算符

与剩余参数关联最密切的就是扩展运算符。剩余参数允许你把多个独立的参数合并到一个数组中;而扩展运算符则允许将一个数组分割,并将各个项作为分离的参数传给函数。

当用在字符串或数组前面时称为扩展运算符,个人觉得可以理解为rest参数的逆运算,用于将数组或字符串进行拆解。有些时候,函数不允许传入数组,此时使用展开运算符就很方便,不信的话,咱们看个例子:Math.max()方法,它接受任意数量的参数,并会返回其中的最大值。

let value1 = 25,                
let value2 = 50;
console.log(Math.max(value1, value2));    //    50

但若想处理数组中的值,此时该如何找到最大值?Math.max()方法并不允许你传入一个数组。其实你可以像使用rest参数那样在该数组前添加...,并直接将其传递给 Math.max()

let values = [25,50,75,    100]
//等价于console.log(Math.max(25,50,75,100));
console.log(Math.max(...values));    //100

扩展运算符还可以与其他参数混用

let values = [-25,-50,-75,-100]
console.log(Math.max(...values,0));    //0

扩展运算符拆解字符串与数组

var array = [1,2,3,4,5];
console.log(...array);//1 2 3 4 5
var str = "String";
console.log(...str);//S t r i n g

还可以实现拼接

var defaultColors = ["red","greed"];
var favoriteColors = ["orange","yellow"];
var fallColors = ["fire red","fall orange"];
console.log(["blue","green",...fallColors,...defaultColors,...favoriteColors]
//["blue", "green", "fire red", "fall orange", "red", "greed", "orange", "yellow"]

七、解构赋值----更方便的数据访问

ES6 新增了解构,这是将一个数据结构分解为更小的部分的过程。

解构为何有用?

在ES5及更早版本中,从对象或数组中获取信息、并将特定数据存入本地变量,需要书写许多并且相似的代码。例如:

 var expense = {
   type: "es6",
   amount:"45"
 };
 var type = expense.type;
 var amount = expense.amount;
 console.log(type,amount);

此代码提取了expense对象的type与amount值,并将其存在同名的本地变量上。虽然 这段代码看起来简单,但想象一下若有大量变量需要处理,你就必须逐个为其赋值;并且若有一个嵌套的数据结构需要遍历以寻找信息,你可能会为了一点数据而挖掘整个结构。

这就是ES6为何要给对象与数组添加解构。当把数据结构分解为更小的部分时,从中提取你要的数据会变得容易许多。

对象

上个例子中如果采用对象解构的方法,就很容易获取expense对象的type与amount值。

const { type,amount } = expense;
console.log(type,amount);

我们再来看个例子:

let node = {type:"Identifier",    name:"foo"},    
type = "Literal",name = 5;
({type,name}= node);//    使用解构来分配不同的值 
console.log(type); //    "Identifier" 
console.log(name); //    "foo"

注意:你必须用圆括号包裹解构赋值语句,这是因为暴露的花括号会被解析为代码块语句,而块语句不允许在赋值操作符(即等号)左侧出现。圆括号标示了里面的花括号并不是块语句、而应该被解释为表达式,从而允许完成赋值操作。

默认值:
可以选择性地定义一个默认值,以便在指定属性不存在时使用该值。若要这么做,需要在 属性名后面添加一个等号并指定默认值,就像这样:

let node = {
  type: "Identifier",
  name: "foo"
};
let {
  type,
  name,
  value = true
} = node;
console.log(type); //    "Identifier" 
console.log(name); //    "foo" 
console.log(value); //    true

嵌套对象解构:
使用类似于对象字面量的语法,可以深入到嵌套的对象结构中去提取你想要的数据。

let node = {
  type: "Identifier",
  name: "foo",
  loc: {
    start: {
      line: 1,
      column: 1
    },
    end: {
      line: 1,
      column: 4
    }
  }
};
let { loc: { start }} = node;
console.log(start.line); //    1 
console.log(start.column); //    1

本例中的解构模式使用了花括号,表示应当下行到node对象的loc属性内部去寻找start属性。

必须传值的解构参数

function setCookie(name, value, {
  secure,
  path,
  domain,
  expires
}) {
  //    设置cookie的代码 
}
setCookie("type", "js");//报错

在此函数内,name与value参数是必需的,而secure、path、domain与expires则不是。默认情况下调用函数时未给参数解构传值会抛出错误。像上例中如果setCookie不传第三个参数,就会报错。若解构参数是可选的,可以给解构的参数提供默认值来处理这种错误。

function setCookie(name, value, {
  secure,
  path,
  domain,
  expires
} = {}) {}
setCookie("type", "js");//不会报错

数组

const names = ["Henry","Bucky","Emily"];
const [name1,name2,name3] = names;
console.log(name1,name2,name3);//Henry Bucky Emily
const [name,...rest] = names;//结合展开运算符
console.log(rest);//["Bucky", "Emily"]

用{}解构返回数组个数

const {length} = names;
console.log(length);//3

数组解构也可以用于赋值上下文,但不需要用小括号包裹表达式。这点跟对象解构的约定不同。

let colors = ["red", "green", "blue"],
  firstColor = "black",
  secondColor = "purple";
[firstColor, secondColor] = colors;
console.log(firstColor); //    "red" 
console.log(secondColor);    // "green"

默认值:数组解构赋值同样允许在数组任意位置指定默认值。当指定位置的项不存在、或其值为undefined,那么该默认值就会被使用。

let colors = ["red"];
let [firstColor, secondColor = "green"] = colors;
console.log(firstColor); //    "red" 
console.log(secondColor);//    "green"

与rest参数搭配

在ES5中常常使用concat()方法来克隆数组,例如:

//在ES5中克隆数组 
var colors = ["red", "green", "blue"];
var clonedColors = colors.concat();
console.log(clonedColors); //"[red,green,blue]"

在ES6中,你可以使用剩余项的语法来达到同样效果

//在ES6中克隆数组 
let colors = ["red", "green", "blue"];
let [...clonedColors] = colors;
console.log(clonedColors); //[red,green,blue]

接下我们看个例子:如何将数组转化为对象

const points = [
  [4,5],
  [10,1],
  [0,40]
];
//期望得到的数据格式如下,如何实现?
// [
//   {x:4,y:5},
//   {x:10,y:1},
//   {x:0,y:40}
// ]
let newPoints = points.map(pair => {
  const [x,y] = pair;
  return {x,y}
})
//还可以通过以下办法,更为简便
let newPoints = points.map(([x,y]) => {
  return {x,y}
})
console.log(newPoints);

混合解构

const people = [
  {name:"Henry",age:20},
  {name:"Bucky",age:25},
  {name:"Emily",age:30}
];
//es5 写法 
var age = people[0].age;
console.log(age);
//es6 解构
const [age] = people;
console.log(age);//第一次解构数组 {name:"Henry",age:20}
const [{age}] = people;//再一次解构对象
console.log(age);//20

注意点

当使用解构来配合var、let、const来声明变量时,必须提供初始化程序(即等号右边的值)。下面的代码都会因为缺失初始化程序而抛出语法错误:

var { type, name }; // 语法错误! 
let { type, name }; // 语法错误!
const { type, name }; // 语法错误!

八、模板字符串(template string)

模板字符串是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。模板字符串中嵌入变量和函数,需要将变量名写在${}之中。

let name = "Henry";
function makeUppercase(word){
  return word.toUpperCase();
}
let template = 
  `
  <h1>${makeUppercase(‘Hello‘)}, ${name}!</h1>//可以存放函数和变量
  <p>感谢大家收看我们的视频, ES6为我们提供了很多遍历好用的方法和语法!</p>
  <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
  </ul>
  `;
document.getElementById(‘template‘).innerHTML = template;

技术分享图片

再举个例子,工作中常用到ElementUI库,在自定义一个弹出框时,使用模板字符串就很方便:

   await this.$alert(
          `<p><strong>确认是否升级${
            this.lectureName
          }</strong><br>(若已存在讲义套件,升级后请重新生成)</p>`,
          {
            dangerouslyUseHTMLString: true
          }
        )

九、Class 和传统构造函数有何区别

从概念上讲,在 ES6 之前的 JS 中并没有和其他面向对象语言那样的“类”的概念。长时间里,人们把使用 new 关键字通过函数(也叫构造器)构造对象当做“类”来使用。由于 JS 不支持原生的类,而只是通过原型来模拟,各种模拟类的方式相对于传统的面向对象方式来说非常混乱,尤其是处理当子类继承父类、子类要调用父类的方法等等需求时。
ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。但是类只是基于原型的面向对象模式的语法糖

对比在传统构造函数和 ES6 中分别如何实现类:

//传统构造函数
function MathHandle(x,y){
  this.x=x;
  this.y=y;
}
MathHandle.prototype.add =function(){
  return this.x+this.y;
};
var m=new MathHandle(1,2);
console.log(m.add())
//class语法
class MathHandle {
 constructor(x,y){
  this.x=x;
  this.y=y;
}
 add(){
   return this.x+this.y;
  }
}
const m=new MathHandle(1,2);
console.log(m.add())

这两者有什么联系?其实这两者本质是一样的,只不过是语法糖写法上有区别。所谓语法糖是指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。比如这里class语法糖让程序更加简洁,有更高的可读性。

typeof MathHandle //"function"
MathHandle===MathHandle.prototype.constructor //true

对比在传统构造函数和 ES6 中分别如何实现继承:

//传统构造函数继承
function Animal() {
    this.eat = function () {
        alert(‘Animal eat‘)
    }
}
function Dog() {
    this.bark = function () {
        alert(‘Dog bark‘)
    }
}
Dog.prototype = new Animal()// 绑定原型,实现继承
var hashiqi = new Dog()
hashiqi.bark()//Dog bark
hashiqi.eat()//Animal eat
//ES6继承
class Animal {
    constructor(name) {
        this.name = name
    }
    eat() {
        alert(this.name + ‘ eat‘)
    }
}
class Dog extends Animal {
    constructor(name) {
        super(name) // 有extend就必须要有super,它代表父类的构造函数,即Animal中的constructor
        this.name = name
    }
    say() {
        alert(this.name + ‘ say‘)
    }
}
const dog = new Dog(‘哈士奇‘)
dog.say()//哈士奇 say
dog.eat()//哈士奇 eat

Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。

Class 和传统构造函数有何区别

  • Class 在语法上更加贴合面向对象的写法
  • Class 实现继承更加易读、易理解,对初学者更加友好
  • 本质还是语法糖,使用prototype

十、Promise的基本使用和原理

在JavaScript的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。Promise 是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。

技术分享图片

ES6中的promise的出现给我们很好的解决了回调地狱的问题,所谓的回调地狱是指当太多的异步步骤需要一步一步执行,或者一个函数里有太多的异步操作,这时候就会产生大量嵌套的回调,使代码嵌套太深而难以阅读和维护。ES6认识到了这点问题,现在promise的使用,完美解决了这个问题。

Promise原理

一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。promise 对象初始化状态为 pending ;当调用resolve(成功),会由pending => fulfilled ;当调用reject(失败),会由pending => rejected。具体流程见下图:

技术分享图片

Promise的使用流程

  1. new Promise一个实例,而且要 return
  2. new Promise 时要传入函数,函数有resolve reject 两个参数
  3. 成功时执行 resolve,失败时执行reject
  4. then 监听结果
function loadImg(src){
   const promise=new Promise(function(resolve,reject){
     var img=document.createElement(‘img‘)
     img.onload=function(){
        resolve(img)
   }
     img.onerror=function(){
        reject()
   }
    img.src=src
 })
  return promise//返回一个promise实例
}
var src="http://www.imooc.com/static/img/index/logo_new.png"
var result=loadImg(src)
result.then(function(img){
    console.log(img.width)//resolved(成功)时候的回调函数
},function(){
    console.log("failed")//rejected(失败)时候的回调函数
})
result.then(function(img){
    console.log(img.height)
})

promise会让代码变得更容易维护,像写同步代码一样写异步代码,同时业务逻辑也更易懂。

十一、Iterator 和 for...of 循环

JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就需要一种统一的接口机制,来处理所有不同的数据结构。遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)

Iterator的作用:

  • 为各种数据结构,提供一个统一的、简便的访问接口;
  • 使得数据结构的成员能够按某种次序排列
  • ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。

原生具备iterator接口的数据(可用for of遍历)

  • Array
  • set容器
  • map容器
  • String
  • 函数的 arguments 对象
  • NodeList 对象
let arr3 = [1, 2, ‘kobe‘, true];
for(let i of arr3){
   console.log(i); // 1 2 kobe true
}
let str = ‘abcd‘;
for(let item of str){
   console.log(item); // a b c d
}   
var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
  console.log(e);
}
// Gecko
// Trident
// Webkit   

几种遍历方式比较

  • for of 循环不仅支持数组、大多数伪数组对象,也支持字符串遍历,此外还支持 Map 和 Set 对象遍历。
  • for in循环可以遍历字符串、对象、数组,不能遍历Set/Map
  • forEach 循环不能遍历字符串、对象,可以遍历Set/Map

十二、ES6模块化

ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {
    return a + b;
};
export { basicNum, add };
/** 引用模块 **/
import { basicNum, add } from ‘./math‘;
function test(ele) {
    ele.textContent = add(99 + basicNum);
}

如上例所示,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。

// export-default.js
export default function () {
  console.log(‘foo‘);
}

上面代码是一个模块文件export-default.js,它的默认输出是一个函数。
其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。

// import-default.js
import customName from ‘./export-default‘;
customName(); // ‘foo‘

上面代码的import命令,可以用任意名称指向export-default.js输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时import命令后面,不使用大括号。

如果觉得文章对你有些许帮助,欢迎在我的GitHub博客点赞和关注,感激不尽!

参考文章

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了9亿+错误事件,得到了Google、360、金山软件、百姓网等众多知名用户的认可。欢迎免费试用!

技术分享图片

版权声明

转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2018/11/29/es6-core-features/









以上是关于2.ES核心的主要内容,如果未能解决你的问题,请参考以下文章

Elasticsearch学习笔记2:ES核心概念 -- 索引倒排索引类型文档

[ES] ElasticSearch总结

[ES] ElasticSearch总结

Elasticsearch的安装及使用,这一篇就够了

ES(ElasticSearch)分布式全文搜索引擎

ElasticSearcho从入门到放弃:简介, lucene,概念, 安装