ES6是下一代JavaScript语言标准的统称,每年6月发布一次修订版,迄今为止已经发布了3个版本,分别是ES2015、ES2016、ES2017。
最近一边玩linux一边玩网络安全,今天突然闲下来更新一下blog,写一下ES6。ES6(ECMAScript6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了。Mozilla公司将在这个标准的基础上,推出JavaScript 2.0。

ECMAScript和JavaScript到底是什么关系?

简单来说,ECMAScript是JavaScript语言的国际标准,JavaScript是ECMAScript的实现。

let

允许你声明一个作用域被限制在块级中的变量、语句或者表达式。与var关键字不同的是,它声明的变量只能是全局或者整个函数块的。
let与var的区别

1
2
3
4
5
6
7
{
var a = 100;
let b = 200;
}
console.log(a); //100
console.log(b); //b
is not defined -- Error

let不存在变量提升

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//ES5****************************
var a = [];
for (var i = 0; i < 10; i++) {
var c = i;
a[i] = function () {
console.log(c);
};
};
a[5](); //9
//ES6**************************
var b = [];
for (var j = 0; j < 10; j++) {
let d = j;
b[j] = function () {
console.log(d);
};
};
b[5](); //5

let不受外界影响

1
2
3
4
5
6
var a = 100;
{
console.log(a); //undefined
let a = 100;
console.log(a); //100
}

let不允许重复声明,模块内也不允许重复声明

1
2
3
4
5
6
7
8
9
10
{
var a = 100;
var a = 200;
console.log(a); //200
}
{
let a = 100;
let a = 200;
console.log(a); //报错
}

块级作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	console.log("ES5:");
function fun() {
var num = 100;
if (true) {
var num = 200;
};
console.log(num);
};
fun(); //200
**********************************************
console.log("ES6:");
function fun() {
let num = 100;
if (true) {
let num = 200;
};
console.log(num);
};
fun(); //100

立即执行函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
	console.log("ES5:");
function fun() {
console.log("I am outside!");
};
(function () {
if (false) {
function fun() {
console.log("I am inside!");
};
};
fun();
}());
******************************************
console.log("ES6:");
function fun() {
console.log("I am outside!");
};
(function () {
if (false) {
function fun() {
console.log("I am inside!");
};
};
fun();
}());

立即执行函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
	console.log("ES5:");
function fun() {
console.log("I am outside!");
};
(function () {
if (false) {
function fun() {
console.log("I am inside!");
};
};
fun();
}());
******************************************
console.log("ES6:");
function fun() {
console.log("I am outside!");
};
(function () {
if (false) {
function fun() {
console.log("I am inside!");
};
};
fun();
}());

const

const Pi = 3.1415926535;
声明创建一个常量,其作用域可以是全局或本地声明的块。 与var变量不同,全局常量不会变为窗口对象的属性。需要一个常数的初始化器;也就是说,必须在声明的同一语句中指定它的值。
const声明创建一个值的只读引用。但这并不意味着它所持有的值是不可变的,只是变量标识符不能重新分配。例如,在引用内容是对象的情况下,这意味着可以改变对象的内容(例如,其参数)。另外常量不能和它所在作用域内的其他变量或函数拥有相同的名称。

const块级作用域

1
2
3
4
if (true) {
const Pi = 3.14159265;
};
console.log(Pi); //Pi is not defined

const暂时性死区

1
2
3
4
if (true) {
console.log(Pi); //undefined
const Pi = 3.14159265;
};

const不可重复声明

1
2
3
4
5
{
var a = 100;
const a = 200;
console.log(a);
}

const对象

1
2
3
4
5
6
const person = {};
person.name = "hhardyy";
person.age = 22;
console.log(person.name); //hhardyy
console.log(person.age); //22
console.log(person); //Object {name: "hhardyy", age: 22}

const对象错误方法

1
2
3
4
5
6
7
const person = {};
person.name = "hhardyy";
person.age = 22;
console.log(person.name);
console.log(person.age);
console.log(person);
person = {}; //person is read-only

const数组

1
2
3
4
5
6
7
8
9
10
11
12
	const arr = [];
console.log(arr);
console.log(arr.length);
**********************************
arr.push("Hello world!");
console.log(arr);
console.log(arr.length);
**********************************
arr.length = 0;
console.log(arr);
console.log(arr.length);// 错误用法
arr = ["Hello Everyone!"];

const对象冻结

1
2
3
4
5
6
const person = Object.freeze({});
person.name = "hhardyy";
person.age = 22;
console.log(person.name); //undefined
console.log(person.age); //undefined
console.log(person); //Object

使用const对象冻结

1
2
3
4
5
6
7
const person = Object.freeze({
name: "hhardyy",
age : 22
});
console.log(person.name); //hhardyy
console.log(person.age); //22
console.log(person); //Object

跨模块常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   //	module.js
export const intVariantName = 100;
export const FloatVariantName = 3.14159165;
export const charVariantName = "variantValue";
// use.js
import * as variant from './module';
console.log(variant.intVariantName); //100
console.log(variant.FloatVariantName); //3.14159165
console.log(variant.charVariantName); //variantValue
// otherUse.js
import { FloatVariantName, charVariantName } as variant from './module';
console.log(variant.FloatVariantName); //3.14159165
console.log(variant.charVariantName); //variantValue
// OnlyInt.js
import intVariantName as variant from './module';
console.log(variant.intVariantName); //100

全局变量属性

1
2
3
4
5
6
7
8
9
10
var varName = "varValue";
// 浏览器环境下
console.log(window.varName); //varValue
// Node.js环境下
//console.log(global.varName); //varValue
// 通用环境
console.log(this.varName); //varValue
let letName = "letValue";
console.log(window.letName); //undefined -- use strict
console.log(this.letName); //undefined -- use strict

数组的解构赋值

数组解构

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

对应位置

1
2
3
4
5
6
7
8
9
10
11
12
let [foo, [[bar], base]] = [1, [[2], 3]];
console.log(foo); //1
console.log(bar); //2
console.log(base); //3
let [, , third] = ["first", "second", "third"];
console.log(third); //third
let [one, , three] = ["One", "Two", "Three"];
console.log(one); //One
console.log(three); //Three
let [head, ...tail] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(head); //0
console.log(tail); //[1, 2, 3, 4, 5, 6, 7, 8, 9]

数组解构不成功

1
2
3
4
5
   var [temp] = [];
console.log(temp); //undefined
var [first, second] = [100];
console.log(first); //100
console.log(second);//undefined

不完全解构

1
2
3
4
5
6
7
let [x, y] = [1, 2, 3];
console.log(x); //1
console.log(y); //2
let [a, [b], c] = [1, [2, 3], 4];
console.log(a); //1
console.log(b); //2
console.log(c); //4

制定默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
var [temp = "string"] = [];
console.log(temp); //string
var [temp = "string"] = ["tempString"];
console.log(temp); //tempString
var [x = "aaa", y] = ["bbb"];
console.log(x); //bbb
console.log(y); //undefined
var [m, n = "aaa"] = ["bbb"];
console.log(m); //bbb
console.log(n); //aaa
var [p, q = "aaa"] = ["bbb", undefined];
console.log(p); //bbb
console.log(q); //aaa

非遍历结构–报错

1
2
3
4
5
var [temp] = 1;			//1[Symbol.iterator] is not a function at eval
var [temp] = false; //false[Symbol.iterator] is not a function at eval
var [temp] = NaN; //NaN[Symbol.iterator] is not a function at eval
var [temp] = undefined; //Cannot read property 'Symbol(Symbol.iterator)' of undefined at eval
var [temp] = null; //Cannot read property 'Symbol(Symbol.iterator)' of null at eval

Iterator接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let [a, b, c] = new Set(["a", "b", "c"]);
console.log(a); //a
console.log(b); //b
console.log(c); //c
function* fibs() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
};
var [first, second, third, fourth, fifth, sixth] = fibs();
console.log(sixth); //5

对象的解构赋值

对象的解构赋值不需要按照顺序

1
2
3
var { name, age } = { name: "Conan", age: 28 };
console.log(name); //Conan
console.log(age); //28

变量名与属性名不一致

1
2
3
4
5
6
7
8
9
10
11
12
// var { person_name, person_age, person_id } = { id: "007", name: "Conan", age: 28 };
// console.log(person_name); //undefined
// console.log(person_age); //undefined
// console.log(person_id); //undefined
var { name: person_name, age: person_age, id: person_id } = { id: "007", name: "Conan", age: 28 };
console.log(person_name); //Conan
console.log(person_age); //28
console.log(person_id); //007
let object = { first: "Hello", last: "World" };
let { first: firstName, last: lastName} = object;
console.log(firstName); //Hello
console.log(lastName); //World

对象解构默认值

1
2
3
4
5
6
7
var { x = 3 } = {};
console.log(x); //3
var { x, y = 5 } = { x: 1 };
console.log(x); //1
console.log(y); //5
var { message: msg = "You Are A Person!" } = {};
console.log(msg); //You Are A Person!

对象解构默认值条件

1
2
3
4
var { x = 3 } = { x: undefined };
console.log(x); //3
var { y = 3 } = { y: null };
console.log(y); //null

已声明变量的解构赋值

1
2
3
var x;
({x} = { x: 1 });
console.log(x); //1

现有对象的方法

1
2
3
console.log(Math.sin(Math.PI/6));	//0.49999999999999994
let { sin, cos, tan, log } = Math;
console.log(sin(Math.PI/6)); //0.49999999999999994

字符串的解构赋值

1
2
3
4
5
6
const [ a, b, c, d, e ] = "Hello";
console.log(a); //H
console.log(b); //e
console.log(c); //l
console.log(d); //l
console.log(e); //o

字符串的属性解构

1
2
3
4
const { length: len } = "Hello";
console.log(len); //5
const { length } = "Hello World!";
console.log(length);//12

函数参数的解构赋值

1
2
3
4
function sum([x, y]) {
return x + y;
};
console.log(sum([1, 2])); //3

函数参数解构赋值的默认值

1
2
3
4
5
6
7
function fun ({x = 0, y = 0} = {}) {
return [x, y];
};
console.log(fun({x: 100, y: 200})); //[100, 200]
console.log(fun({x: 100})); //[100, 0]
console.log(fun({})); //[0, 0]
console.log(fun()); //[0, 0]

函数参数解构赋值的默认值undefined

1
2
3
4
5
6
7
function fun ({x, y} = { x: 0, y: 0 }) {
return [x, y];
};
console.log(fun({x: 100, y: 200})); //[100, 200]
console.log(fun({x: 100})); //[100, undefined]
console.log(fun({})); //[undefined, undefined]
console.log(fun()); //[0, 0]

箭头函数

Arrow Function(箭头函数)。
为什么叫Arrow Function?因为它的定义用的就是一个箭头:

1
x => x * x

相当于ES5的

1
2
3
function (x) {
return x * x;
}

有些浏览器不支持箭头函数,测试方法

1
2
3
'use strict';//使用严格模式
var fn = x => x * x;
console.log('浏览器不支持');

普通函数中的this:
1.this总是代表它的直接调用者(js的this是执行上下文), 例如 obj.function ,那么function中的this就指向obj
2.在默认情况(非严格模式下,未使用 ‘use strict’),没找到直接调用者,则this指的是 window
3.在严格模式下,没有直接调用者的函数中的this是undefined
4.使用call,apply,bind(ES5新增)绑定的,this指的是绑定的对象

箭头函数中的this
没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象, 定义它的时候,可能环境是window;

ES6加入了新的数据类型Symbol与新的数据结构set、map。

symbol最大的特点是唯一性,symbol不能隐式的转换类型,所以和其他数类型数据做运算时会报错。但是,symbol可以显式地转为字符串和布尔类型。对象object,在添加symbol类型属性时,必须用[]外扩symbol,不能用点式法对其进行操作。如同其他类型数据一般,symbol也有一些内置方法。

1
2
let a=Symbol();
console.log(typeof a);//symbol,js语言的数据类型再添一员

Symbol不是一个构造函数,如果new Symbol会报错

1
2
3
4
5
6
7
8
9
10
11
12
var sym1 = Symbol();
var sym2 = Symbol("foo");
var sym3 = Symbol("foo");
console.log(sym1, sym2, sym3) //输出Symbol() Symbol(foo) Symbol(foo)
用同样的参数创建两次, 也是不相等的:
Symbol("foo") === Symbol("foo"); //输出:false

let a=Symbol();
let b=Symbol();
console.log(a);//Symbol()
console.log(b);//Symbol(),a与b虽然不相等,但是打印出的数据却是一样的,为了区分他们,可以传参:
let c=Symbol('ccc');//Symbol(ccc),传入的参数仅做标记,没有任何实际意义

symbol类型的数据不能与其他类型的数据进行运算,它可以转为string与boolean类型,但是不能转换为number类型,

数据结构Set。

类似于数组,只不过其成员值都是唯一的,没有重复的值,向Set加入值的时候,不会发生类型转换。

1
2
3
4
5
6
7
8
9
10
11
Set本身是一个构造函数,用来生成Set数据结构。
var s = new Set();
// 通过add方法向Set结构中加入成员
[2, 3, 5, 4, 5, 2, 2].map(x => s.add(x))
for (i of s) { console.log(i) }
// 2 3 4 5 (表明set结构不会添加重复的值)

Set结构接收一个数组作为参数,用来初始化。
var items = new Set([1, 2, 3, 4, 5, 5, 5]);
items.size
// 5

set的四个方法:

add(value) : 添加value。
delete(value) : 删除value。
has(value) : 返回一个布尔值,表示该值是否为Set的成员。
clear() : 清除所有成员。

数据结构Map

Js对象本质上是键值对的集合。但是只能使用字符串充当键。ES6为了解决这个问题提供了Map结构。它类似对象,也是键值对集合,但是”键”的范围不限于字符串,对象也可以当作键。

1
2
3
4
5
6
7
8
9
10
var data = {};
var el = document.getElementById("Div");
// 将DOM节点el当做对象data的键,但是对象只接受字符串作为键名,所以el被自动转换为字符串"[Object HTMLDivElement]"
data[el] = metadata;

//map下
var m = new Map();
o = {p: "hello world"};
m.set(o, "content");//o作为m的一个键
console.log( m.get(o) ); // content

Map函数可接收一个数组进行初始化。

1
var map = new Map([["name", "hhardyy"], ["title", "hhardyytitle"]]);

Map的6种方法:
size : 返回成员总数。
set(key, value) : 设置一个键值对。
get(key) : 读取一个键。
has(key) : 返回一个布尔值,表示某个键是否在Map结构中。
delete(key) : 删除某个键。
clear() : 清除所有成员。
Map原生提供三个遍历器。
key() : 返回键名的遍历器。
values() : 返回键值的遍历器。
entries() : 返回所有成员的遍历器。

1
2
3
4
5
6
7
8
9
10
11
12
for ( let key of map.key() ) {
console.log("key: %s", key);
}
for ( let value of map.value() ) {
console.log("value: %s", value);
}
for ( let item of map.entries() ) {
console.log("Key: %s, Value: %s", item[0], item[1]);
}
for (let item of map) {
console.log("Key: %s, Value: %s", item[0], item[1]);
}

Map还有一个forEach方法,与数组中的forEach方法类似,也可以实现遍历。

1
2
3
map.forEach(function(value, key, map) {
console.log("Key: %s, Value: %s", key, value);
});

forEach方法还可接受第二个参数,用来绑定this。

1
2
3
4
5
6
7
8
9
10
var reporter = {
report: function(key, value) {
console.log("key: %s, Value: %s", key, value);
}
};

map.forEach(function(value, key, map) {
this.report(key, value)
}, reporter);
forEach()方法的回调函数中的this, 就指向reporter

promise

举个栗子,有5个互相嵌套的异步请求
第一种实现方式,callback回调,这种方法如果想更改一些业务需求,还是比较麻烦的,而且如果在没有注释的情况下,去看代码可能有点头大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function func(cb){
setTimeout(()=>{
cb&&cb()
},1000)
}
func(()=>{
console.log(1);
func(()=>{
console.log(2);
func(()=>{
console.log(3)
func(()=>{
console.log(4)
func(()=>{
console.log(5)
})
})
})
})
})

第二种实现方式,Promise,可以做成像无脑安装软件一样一直下一步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function func2(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve()
},1000)
})
}
func2().then(()=>{
console.log(11)
return func2()
}).then(()=>{
console.log(22)
return func2()
}).then(()=>{
console.log(33)
return func2()
}).then(()=>{
console.log(44)
return func2()
}).then(()=>{
console.log(55)
return func2()
})

实现的效果是一样的
实现的效果是一样的

什么是Promise?Promise就是用同步的方式,书写异步代码,用它原生的写法有点繁琐,因为要new多个Promise对象,然后每个发送ajax,然后再每个then(resolve,reject),这里就直接写个处理过的

1
Promise.all([ajax1,ajax2,ajax3,ajaxn]).then(res=>{成功},err=>{失败})

还有个叫 promise.race
promise.all是与的关系,promise.race中的race=竞速的意思,也就是哪个先到先请求哪个

1
2
3
4
5
6
7
promise.race([
$.ajax({url:'hhardyy.com/data/1'})
$.ajax({url:'hhardyy.com/data/2'})
$.ajax({url:'hhardyy.com/data/3'})
$.ajax({url:'hhardyy.com/data/4'})比如这个先到了就用这个
$.ajax({url:'hhardyy.com/data/5'})
])

面向对象

以前的面向对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 对象
function User(name,pass){
this.name=name;
this.pass=pass;
}
User.prototype.showName=function(){
console.log(this.name);
}
User.prototype.showPass=function(){
console.log(this.pass)
}
let user=new User('hhardyy','123456')
user.showName();
user.showPass();
// 继承
function VipUser(name,pass,level){
User.call(this,name,pass);
this.level=level;
}
VipUser.prototype=new User();
VipUser.prototype.constructor=VipUser;
VipUser.prototype.showLevel=function(){
console.log(this.level)
}
let vipuser=new VipUser('huangbingzhen','123456',3)
console.log("继承")
vipuser.showName();
vipuser.showPass();
vipuser.showLevel();

ES6的面向对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//对象
class User{
constructor(name,pass){
this.name=name;
this.pass=pass;
}
showName(){
console.log(this.name)
}
showPass(){
console.log(this.pass)
}
}
let user=new User('huangbingzhen','123456')
user.showName();
user.showPass();
// 继承
class vipUser extends User{
constructor(name,pass,level){
super(name,pass) //超类
this.level=level
}
showLevel(){
console.log(this.level)
}
}
let vipuser=new vipUser('huangbingzhen','123456',3)
vipuser.showName();
vipuser.showPass();
vipuser.showLevel();

generator

generator: 生成器
普通函数=一路到底,generator函数=中间能停
比方:普通函数=飞机高铁,行驶途中不允许下车一下;generator函数=出租车(:老哥我下车一下马上回来,:去吧)
generator函数

1
function* show |  function * show() | function *show()

yield:作用和java里头的一样

1
2
3
4
5
6
7
8
9
function *show(){
console.log('a');
yield; //截断
console.log('b');
}
let genObj=show();
console.log(genObj)
genObj.next() //只出a
genObj.next() //出完a,b

它相当于把一个大函数分成了几个小函数show_1()、show_2(),应用场景=比如请求数据

1
2
3
4
5
6
7
8
9
10
11
12
13
function ajaxFunc(){
代码...
$.ajax(xxx.function(){
然后为了写的好,下面的代码就要写到这里,回调代码...,然后就形成回调地狱~~~
})
代码...
}
//generator函数
function ajaxFunc(){
...代码
yield $.ajax(xxx,function(){}) //到这里就可以暂停一下读数据
...代码
}

yield可以传参

1
2
3
4
5
6
7
8
9
function *show(num){
console.log(num)
console.log('a');
let item=yield;
console.log('b')
console.log(item)
}
let genObject=show(100)
genObject.next(12)

第一个过程传参=原来函数的传参方法,第一个过程是没办法给yield传参的

1
2
3
4
5
6
7
function *show(num){
console.log(num);
yield
console.log('b')
}
第二个过程之后就可以
genObject.next(5)

1
2
3
4
5
6
7
8
9
10
11
12
function *show(){
console.log('a');
yield 12;
console.log('b');
return 55
}
let genObj=show()
let res1=genObj.next()
let res2=genObj.next()
console.log(res1) //{value: 12, done: false}
console.log(res2) //最后一道工序,yield已经没有了,变成了undefined,所以输出的是{value: undefined, done: true},这个地方想拿到返回值需要在函数里面写return,
比如return55就可以输出来{value: 55, done: true}

ES7&&ES8

includes:检查数组是否包含某个东西

1
2
3
let arr=[1,2,3,4,5]
console.log(arr.includes(1)) //true
console.log(arr.includes(6)) //false

keys/values/entries

keys=>把所有的key拿出来
values=>把所有的value拿出来
entries=>把所有的键值对拿出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let arr=[11,22,33,44,55]
for(let item of arr.keys()){
console.log(item) //0,1,2,3,4
}
for(let item of arr.values()){
console.log(item) //11,22,33,44,55
}
for(let item of arr.entries()){
console.log(item) //[0,11],[1,22],[2,33],[3,44],[4,55]
}
entries加上解构赋值可以这么玩
for(let [key,value] of arr.entries()){
console.log(`${key}:${value}`)
}

需要注意的是上面这些json暂时玩不了

这哥仨running
这哥仨running

结构赋值
结构赋值

以前的求幂是用Math的pow,ES7给了个比较6的方法,就是两个**,像python

1
2
console.log(Math.pow(5,2))   //25
console.log(5**2) //25

字符串

ES6多了个startsWith/endsWith,7的时候多了个padStart/padEnd,也就是前后补东西,很简单理解,比方说过去我要取一个字符串的前面10位

1
2
3
4
5
6
let str='hhardyy'
console.log(`(${str.padStart(10)})`) //( hhardyy),不够10位它会补上,endsWith也是差不多
console.log(`(${str.padEnd(10)})`) //(hhardyy )
//我还可以要求补位的时候补上我喜欢的东西
console.log(`(${str.padStart(10,'6')})`)//(666hhardyy)
console.log(`(${str.padEnd(10,'6')})`) //(hhardyy666)

async await

ES6的generator是配合yield用的,具体看上面写的ES6的东西,这里说的是ES7出来一个把它们取代的,叫做async
await,区别就是用generator的时候需要用到一个renner,但是async+await就不需要,可以直接用,但是对浏览器版本要求比较高,它的写法

1
2
3
4
5
async function Object(){
console.log('a')
await
console.log('b')
}

举例肯定是数据交互,要起一个本地服务,ajaxFunc是封装的一个普通的原生ajax,就不放出来了

1
2
3
4
5
6
async function getData(){
let data1=await ajaxFunc('arr.txt')
let data2=await ajaxFunc('json.txt')
console.log(data1,data2)
}
getData()

目录
目录
get数据
get数据

过去的generator写法,需要写一个runner,有人把它写成了库给别人去install,还有一点是generator不能写成箭头函数,但是async可以 async getData()=>{}

1
2
3
4
5
renner(function *getData(){
let data1=yield ajaxFunc('arr.txt')
let data2=yield ajaxFunc('json.txt')
console.log(data1,data2)
})