您好,欢迎访问一九零五行业门户网

全面掌握,ECMAScript的新特性

今天javascript栏目带大家认识ecmascript的新特性。
es6成为javascript的下一代标准后,标准委员会(tc39)在每年都会发布一个es的新版本,每个版本里都引入了很多实用的新特性,在日常的项目开发中,如果我们掌握这些实用的新特性,将大大的提升开发效率,下面让我们全面的掌握这些es的新特性吧~
let 和const之前使用var来定义变量,为我们提供了新的方式
let用来声明变量,const用来声明常量。
如何使用const tag = 我是常量;let a;a = 2;console.log(tag, a= + a); //我是常量   a=2复制代码
四个特点一、只在块级作用域内有效let和const为javascript新增了块级作用域,通常情况下,{}包裹的代码拥有的作用域就是块级作用域,声明的变量或常量只在块级作用域内有效,外部不能访问。
if (true) { //外层块级作用域  let a = 1;  const a = 1;  if (true) {   //内层块级作用域    let a = 2;  }  console.log(a,a); //(1)输出:1 , 1}console.log(a); //(2)uncaught referenceerror: a is not defined复制代码
上面有两个块级作用域,都声明了变量a,但外层块级作用域与内层块级作用域无关,所以(1)处输出的是外层的变量值1,(2)处访问了不在一个块级作用域定义的变量,所以会报错。
另外一个理解块级作用域的示例。
//for循环体内的定时器//在es6之前,是没有块级作用域的,变量用var声明,直接挂载在全局作用域上for (var i = 0; i < 3; i++) { settimeout(function () { console.log(i); //3、3、3 }, 100);}//使用var声明,for同步操作优先于settimeout异步操作,在开始执行settimeout的时候,//for循环已经执行完,i为3,则后续每次settimeout输出的i都是3//使用let声明的话,则会在循环体内部形成闭包,每次for循环都会给闭包提供每次循环i的值,//并且不被释放,最终settimeout里会分别输出0、1、2for (let i = 0; i < 3; i++) { settimeout(function () { console.log(i); //0 1 2 }, 100);}复制代码
二、暂时性死区不能在变量和常量声明之前使用。
let和const命令会使区块形成封闭作用域,若在声明之前使用,就会报错,这个在语法上,称为“暂时性死区”(简称tdz)。
if (true) { tmp = "abc"; // referenceerror let tmp; }复制代码
三、不能重复声明let a = 1;let a = 2;//报错 syntaxerror: identifier 'a' has already been declared const b=1;const b=2;//报错 syntaxerror: identifier 'b' has already been declared 复制代码
四、不属于顶层对象let声明的变量,全局对象(window,global,self)不能访问到
let a = 10;console.log(window.a); //undefined复制代码
stringes6对字符串进行了一些扩展,如下:
模板字符串es6新增了模板字符串(字符串)的方式定义字符串,用来解决之前字符串很长要换行、字符串中有变量或者表达式遇到的问题,下面是具体的使用场景
//一、打印长字符串且换行,直接在模板字符串中保留格式即可let welcome=` 你好 欢迎来到es6 ——谢谢`console.log(welcome);/* 输出结果为: 你好 欢迎来到es6 ——谢谢*///二、字符串中有变量或者表达式,直接在模板字符串中使用${变量/表达式}即可let type = "es6";let name1 = "mango";let name2 = "和goman";let welcome = `欢迎${name1 + name2}来到${type}世界`; console.log(welcome); //learn1.js?c1a0:7 欢迎mango和goman来到es6世界复制代码
方法string.prototype.includes()判断字符串是否包含一个指定字符串,返回boolean类型。
const str = "ecmascript"console.log(str.includes("ec")); //true 找不到返回false 复制代码
startswith()和endswith()startswith()用来判断字符串是否以指定字符串作为开头,返回boolean类型。
endswith()用来判断字符串是否以指定字符串作为结尾,返回boolean类型。
const str = "ecmascript"console.log(str.startswith("ecm")); //true console.log(str.endswith("script")); //true 复制代码
string.prototype.repeat()将原有字符串重复n遍,得到一个新的字符串
const str = "ecmascript";console.log(str.repeat(3)); //ecmascriptecmascriptecmascript复制代码
numberes6开始逐步减少全局性方法,使得语言逐步模块化,所以把一些处理数值的方法转移到了number对象上,功能行为保持不变。
//将目标转换为整数//es5parseint("5.6") //5//es6number.parseint("5.6") //5//将目标转换为浮点数//es5parsefloat("12.45str") //12.45//es6number.parsefloat("12.45str") //12.45复制代码
另外,为了便于开发,number还增加了一些方法和属性
一、判断一个数值是否是整数number.isinteger(25) // truenumber.isinteger(25.1) // false二、获取javascript最大安全值和最小安全值number.max_safe_integer=9007199254740991number.min_safe_integer=-9007199254740991三、判断一个数值是否是在安全范围number.issafeinteger(9007199254740992) //false复制代码
symbol新引入原始数据类型,用来表示独一无二的值。
声明方式let sym = symbol();let sym2 = symbol();console.log(sym == sym2); //false 生成的值是独一无二的,所以不相等console.log(typeof sym); //symbol typeof查看值的类型为symbollet symwithdesc = symbol("name"); //symbol()括号内可以添加描述console.log(symwithdesc.tostring()); //输出:symbol(name) 打印描述需要转换成字符串复制代码
项目中应用
一、消除魔术字符串
假如我们需要做一个点击菜单,做不同处理的功能,我们通常会这样实现。
const clickmenu = function (menu) { switch (menu) { case "home": break; case "me": break; }};clickmenu("home")复制代码
"home"这种可能会多次出现,与代码形成强耦合的字符串就是魔术字符串,在项目中我们应该尽量消除魔术字符串,下面使用symbol消除魔术字符串
const menu_type = { home: symbol(), me: symbol(),};const clickmenu = function () { switch (menu) { case menu_type.home: break; case menu_type.me: break; }};clickmenu(menu_type.home);复制代码
二、作为对象独一无二的属性值
假如我们想生成一个公司人名对象,并以每个人名为key值,这时候如果有人名重名便会有问题,而symbol能解决这个问题
const scores = { [symbol("张三")]: { age: 22, }, [symbol("李四")]: { age: 21, }, [symbol("张三")]: { age: 20, },};复制代码
注意,通过symbol定义的属性,只能通过下面两种方式进行遍历,否则无法获取属性。
for (let key of object.getownpropertysymbols(scores)) { console.log(key, key);}for (let key of reflect.ownkeys(scores)) { console.log(key, scores[key]);}复制代码
set和map
为了更方便地实现数据操作,es6新增了set和map两种数据结构。
setset是类似于数组,但成员的值都是唯一的数据结构。
新建新建一个存储月份的set数据结构,可以定义一个空的set实例,也可以是带有数组形式的默认数据。
let monthsets = new set();let monthsets2 = new set(["一月","二月","三月"]);复制代码
基本使用//添加数据monthsets.add("一月");monthsets.add("二月").add("三月");console.log(monthsets); //set(3) {"一月", "二月", "三月"}//遍历集合set//foreach():使用回调函数遍历每个成员monthsets.foreach((item) => console.log(item)); //一月 二月  三月//for...of:直接遍历每个成员for (const item of monthsets) {  console.log(item);    //一月 二月  三月}//删除数据monthsets.delete(二月);console.log(monthsets); // set(2) {一月, 三月}monthsets.clear(); //console.log(monthsets); // set(0) {}复制代码
常见应用set数据结构在实际项目中还有很多应用场景。
let monthsets = new set([一月, 二月, 三月]);//一、快速判断数据元素是否存在monthsets.has(一月); //true//二、统计数据元素个数monthsets.size; //3console.log(monthsets.size); //3//三、数组去重let arr = [1, 2, 3, 2, 3, 4, 5];let set = new set(arr);console.log(set); // {1, 2, 3, 4, 5}//四、合并去重let arr = [1, 2, 3];let arr2 = [2, 3, 4];let set = new set([...arr, ...arr2]);console.log(set); // {1, 2, 3, 4}//五、取数组交集let arr1 = [1, 2, 3];let arr2 = [2, 3, 4];let set1 = new set(arr1);let set2 = new set(arr2);let resultset = new set(arr1.filter((item) => set2.has(item)));console.log(array.from(resultset)); // [2, 3]//六、取数组差级let arr1 = [1, 2, 3];let arr2 = [2, 3, 4];let set1 = new set(arr1);let set2 = new set(arr2);let arr3 = arr1.filter((item) => !set2.has(item));let arr4 = arr2.filter((item) => !set1.has(item));console.log([...arr3, ...arr4]);  //[1, 4]复制代码
weaksetweakset与set类似,也是不重复的值的集合,但weakset的成员只能是对象。weakset引用的对象都是弱引用,如果其他对象不再引用该对象,那么垃圾回收机制就会自动回收这些对象所占用的内存,不考虑该对象还存在于weakset之中。
react源码中有很多地方使用到了weakset,例如在react-reconciler/src/reactfiberhotreloading.new.js中。
export function markfailederrorboundaryforhotreloading(fiber: fiber) {  if (__dev__) {    if (resolvefamily === null) {      // hot reloading is disabled.      return;    }    if (typeof weakset !== 'function') {      return;    }    if (failedboundaries === null) {      failedboundaries = new weakset();    }    failedboundaries.add(fiber);  }}复制代码
mapmap是一种键值对集合,与对象类似,但object只支持“字符串:值”,而map支持“各种类型的值:值”,map给我们提供了更合适的“键值对”数据结构。
基本使用//定义let map = new map();//添加数据let address = { address: 江苏 };map.set(name, es6);map.set(27, 年龄信息);map.set(address, 地址信息);console.log(map); //{name => es6, 27 => 年龄信息, {…} => 地址信息}//获取数据let name = map.get(name);let age = map.get(27);let addressobj = map.get(address);console.log(name, age, addressobj);//获取成员数量console.log(map.size);  //3//判断是否指定key成员console.log(map.has(name)); //true复制代码
map的遍历map通常可以用foreach和for...of的方式进行遍历。
//定义let map = new map();map.set(id, 1);map.set(name, mango);map.set(address, {  province: 江苏,  city: 南京,});map.foreach((key, value) => console.log(key, value));for (const [key, value] of map) {  console.log(key, value);}//输出  id 1      name mango      address {province: 江苏, city: 南京}复制代码
weakmapweakmap与map类似,也是用来生成键值对的集合。但weakmap只接受对象作为键名,并且键名所指向的对象,属于弱引用对象。
数组的扩展es6对数组进行了很多的扩展,具体如下
扩展运算符扩展运算符是三个点(...),将一个数组转为用逗号分隔的参数序列,通常用在函数参数中。
假如我们需要一个求和函数,并且支持传入任意数量的值。
function sum(...params) {  let sum = arr.reduce(function (prev, cur) {    return prev + cur;  });  return sum;}let arr = [1, 2, 3, 4, 5];console.log(sum(arr)); //输出 15复制代码
array.from()array.from()方法从一个类似数组或可迭代对象创建一个新的浅拷贝的数组实例,通常有以下四种实用场景。
//一、克隆一个数组let num = [1, 2, 3];let newnum = array.from(num);console.log(newnum, num === newnum);  //[1, 2, 3] false//二、使用指定值,初始化一个数组//给定长度为10,默认值是数组2和对象{key:1}let length = 4;let defaultvalue = 2;let defaultobj = { key: 1 };let arrvalue = array.from({ length }, (item,index) => defaultvalue);let arrobj = array.from({ length }, (item,index) => defaultobj);console.log(arrvalue); // [2, 2, 2, 2]console.log(json.stringify(arrobj)); //[{key:1},{key:1},{key:1},{key:1}]//三、生成值范围数组function range(end) {  return array.from({ length: end }, (item, index) => index);}let arr = range(4);console.log(arr); // [0, 1, 2, 3]//四、数组去重,结合set使用let arr = [1, 1, 2, 3, 3];let set = new set(arr);console.log(array.from(set));复制代码
创建数组如何创建一个数组,有下面几种常用方式
//一、数组字面量const arr1 = [];//二、构造函数const arr2 = array(3);  //[null,null,null]const arr3 = array(3);  //[3]//这时想要用构造函数创建一个数字为7的数组,发现上面方式是无法满足的,而es6提供了array.of()能满足我们的需求const arr3 = array.of(7);  //[7]复制代码
数组查找
find()方法返回数组中满足提供的测试函数的第一个元素的值,若没有找到对应元素返回undefined
findindex()方法返回数组中满足提供的测试函数的第一个元素的索引,若没有找到对应元素则返回-1。
假如我们想要在一个成绩数组中,找到达到及格分数的最低分。
const score = [34, 23, 66, 12, 90, 88, 77, 40];const passmin = score.find((value) => value > 60);console.log(passmin); //66const pass = score.findindex((value) => value > 60);console.log(pass); //2复制代码
数组遍历es6新增 for...of 数组遍历方式
const score = [34, 23, 66, 12,];for (let value of score) {  console.log(value); //  34, 23, 66, 12}复制代码
函数的扩展
es6对函数进行了很多的扩展,具体如下
函数参数设置默认值es6允许为函数的参数设置默认值,即可以直接写在参数定义的后面。
//参数b设置了默认值为2,在方法调用的时候并没有传值,所以b直接使用默认值function sum(a, b = 2) {  return a + b;}console.log(sum(1)); //3复制代码
rest参数es6引入reset参数,形式为...变量名,可以用来获取传递给函数的多余参数。
function sum(a, ...values) {  console.log(a, values); //1   [2, 3, 4, 5]}sum(1, 2, 3, 4, 5);复制代码
name和length属性name属性返回函数名,length属性返回没有指定默认值的参数个数。
function sum(a, b, c, d = 1) {  console.log(a, values); //1   [2, 3, 4, 5]}console.log(sum.name);  //sumconsole.log(sum.length);  //3复制代码
箭头函数es6允许使用箭头(=>)的方式定义函数,有下面几种箭头函数实现形式。
想要实现一个加法函数,es5的形式如下
function sum(a, b) {  return a + b;}复制代码
而如果使用箭头函数实现的话,则如下
sumarrow = (a, b) => {  return a + b;};复制代码
上面是箭头函数的基本变现形式,不同的场景还有不同的实现形式。
//对于上面的sumarrow函//一、如果只有一个参数,可以省略括号sumarrow = a => {  return a;};二、如果返回值是表达式,可以省略return和{}sumarrow = a => a;三、如果返回值是字面量对象,一定要用小括号包起来sumarrow = () => ({ a: 1, b: 2 });复制代码
箭头函数与普通函数除了实现方式不同外,还有个不同的点就是对this的处理方式。
//普通函数let math = {  name: mathname,  sum: function (a, b) {    console.log(this.name); //math    return a + b;  },};math.sum();//箭头函数globalthis.name = globalname;let math = {  name: mathname,  sum: (a, b) => {    console.log(this.name); //globalname    return a + b;  },};math.sum();复制代码
从上面示例可以看到,箭头函数和普通函数最终打印的this.name不一致。对于普通函数,this指向的是调用sum方法的math对象,所以this.name打印的是“mathname”。而对于箭头函数,this指向的是定义sum方法的全局对象,所以this.name打印的是“globalname”。
在后续的开发过程中,我们将会经常使用到箭头函数,在使用的过程中,我们需要有以下几点注意
箭头函数中this指向定义时所在的对象,而不是调用时所在的对象不可以当作构造函数不可以使用yield命令,不能作用generator函数解构赋值
解构赋值是一种表达式,可以将属性和值从对象和数组中取出,赋值给其他变量。
如何使用对象解构赋值假如我们拿到一个对象,需要获取指定的属性值。则解构赋值让我们无需通过调用属性的方式赋值,而是通过指定一个与对象结构相同模板的方式,获取想要的属性值。
const people = {  name: es6,  age: 27,  sex: male,};//如果通过调用属性赋值,则需要这么做let name = people.name;let age = people.age;let sex = people.sex;console.log(name, age, sex); //es6 27 male//而使用解构赋值的方式,代码会更加的清晰简单const { name, age } = people;console.log(name, age); //es6 27 male复制代码
除了上面这种基本用法,还有其他使用方式
const people = {  name: es6,  age: 27,  sex: male,};// 一、属性顺序不需保持一致,名称相同即可const { age, name, sex } = people;console.log(name, age, sex);  //es6 27 male//二、取值时,重新定义变量名const { age: newage, name: newname, sex: newsex } = people;console.log(name, age, sex); //uncaught referenceerror: age is not definedconsole.log(newname, newage, newsex); //es6 27 male//三、赋值过程中设置默认值const { nickname = 昵称, age } = people;console.log(nickname, age); //昵称 27//四、reset运算符。只获取想要的属性,其他属性都放在新的变量里。const { name, ...peopleparams } = people;console.log(name, peopleparams); //es6 {age: 27, sex: male}//五、嵌套对象取值const people = {  name: es6,  address: {    province: 江苏,  },};const { address: { province }} = people;console.log(province); //江苏复制代码
数组解构赋值假如我们拿到一个数组,需要获取指定的元素值。
const [a, b, c] = [1, 2, 3];console.log(a, b, c);   //1 2 3复制代码
除了上面这种基本用法,还有其他使用方式
//一、待解构的除了是数组,还可以是任意可遍历的对象const [a, b, c] = new set([1, 2, 3]);console.log(a, b, c); //1 2 3//二、被赋值的变量还可以是对象的属性,不局限于单纯的变量const num = {};[num.a, num.b, num.c] = [1, 2, 3];console.log(num); //{a: 1, b: 2, c: 3}//三、解构赋值在循环体中的应用const num = {  a: 10,  b: 20,  c: 30,};for (const [key, value] of object.entries(num)) {  console.log(key, value); //a 10    b 20    c 30}//四、跳过赋值元素const [a, , c] = [1, 2, 3]; //存在空位的数组叫稀疏数组console.log(a, c);  //1 3//五、rest 参数const [a,...other] = [1, 2, 3];console.log(a, other);  //1    [2, 3]//六、赋值过程中设置默认值const [a, , , d = 10] = [1, 2, 3];console.log(d); //10复制代码
字符串解构赋值字符串解构赋值可以当成数组解构赋值
const [a, b, c, d] = ecmascript2015;console.log(a, b, c, d); //e c m a复制代码
对象的扩展
es6对对象进行了很多的扩展,具体如下
属性的简洁表示法从es6开始,如果对象的属性名和属性值相同,则有简写的方式。
let province = 江苏;const address = {  province, //等同于 province: province  city: 南京,};复制代码
属性名表达式从es6开始,可以使用变量或表达式定义对象的属性。
let key = province;const address = {  [key]: 省份,  city: 南京,};console.log(address); //{province: 省份, city: 南京}复制代码
object.is()判断两个值是否是同一个值。在object.is()之前,有“==”和“===”两种方式判断值是否相等,但这两个方式都有一定缺陷,如下
//== 在判断相等前会对不是同一类型的变量进行强制转换,最终导致“”与false相等console.log( == false);   //true//=== 会将-0与+0视为相等,而将number.nan与nan视为不相等console.log(-0 === +0); //trueconsole.log(number.nan === nan);    //false复制代码
所以,需要一种运算,在所有场景下,只要两个值是一样的,那么就应该相等,在实际项目开发过程中,推荐使用object.is()来判断值相等。
console.log(object.is(-0, +0)); //falseconsole.log(object.is(number.nan, nan)); //truelet a = { value: 1 };let b = { value: 1 };console.log(object.is(a, b)); //false  对象都是同一个引用才相等复制代码
object.assign()
用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,它将返回目标对象。
语法:
object.assign(target, ...sources)    参数说明:target:目标对象sources:源对象返回值:合并后的目标对象
const target = { a: 1,};const source = { b: b, c: c };const assignobj = object.assign(target, source);console.log(assignobj); //{a: 1, b: b, c: c}  //其他应用//一、如果目标对象与源对象属性具有相同值,则源对象属性值会覆盖目标对象属性值const target = { a: 1,b: 2};const source = { b: b, c: c };const assignobj = object.assign(target, source);console.log(assignobj); //{a: 1, b: b, c: c}    //目标对象的b属性值被覆盖//二、源对象可以有多个值const target = { a: 1 };const source1 = { b: b, c: c };const source2 = { d: d, e: e };const assignobj = object.assign(target, source1, source2);console.log(assignobj); //{a: 1, b: b, c: c, d: d, e: e}复制代码
对象遍历假如我们想要循环遍历一个对象的键与值,则可以使用下面几种方式进行遍历
const score = {  name: mango,  age: 25,  score: 80,};//for...infor (let key in score) {  console.log(key, score[key]); // 分别输出:name mango 、 age 25 、score 80}//object.keys()用来获取所有key组成的数组object.keys(scoreobj).foreach(key => {  console.log(key, scoreobj[key]) //分别输出:name mango 、 age 25 、score 80})//object.getownpropertynames()用来获取所有key组成的数组object.getownpropertynames(scoreobj).foreach(key => {  console.log(key, scoreobj[key]) //分别输出:name mango 、 age 25 、score 80})//reflect.ownkeys()用来获取所有key组成的数组reflect.ownkeys(scoreobj).foreach(key => {  console.log(key, scoreobj[key]) //分别输出:name mango 、 age 25 、score 80})复制代码
classjavascript是一种基于对象的语言,我们遇到的所有东西几乎都是对象,但es6之前是没有class的,而在es6版本中正式引入了class,让javascript成为了一种真正的面向对象语言,我们可以像下面这样在javascript中进行面向对象编程。
//通过class关键字定义类class people{    //类的构造函数  constructor(name, age) {    this.name = name;    this.age = age;  }  //实例方法  getname() {    return this.name;  }  //静态方法  static say() {    console.log(hello es6);  }}//继承class student extends people {  constructor(name, age) {    super(name, age);  }}//对象创建与调用let student = new student(mango, 27);student.getname();student.say();复制代码
通过上面的代码,我们具体说明下javascript中进行面向对象编程。
类的声明通过class关键字声明类,支持构造函数construct做对象初始化。
class people{    constructor() {    //初始化  }}复制代码
属性class对象中有两种对象属性,分别是实例属性和静态属性。实例属性必须定义在类的方法里,而静态属性必须定义在类的外面。
class people{  constructor() {    //定义实例属性    this.name = ;    this.age = 0;  }}people.desc=类描述;  //定义的静态属性//访问people people=new people();console.log(people.name);console.log(people.name);复制代码
类中定义的属性,默认都是可读可写的,但是如果这时候我们想指定属性不可被修改该如何实现呢?那么便要用到set和get了,set和get可以定义一个属性,但是如果只有get而没有set,则属性不可以进行修改。
class people {  get sex() {    return 男;  }}let people = new people();console.log(people.sex);people.sex=女 //uncaught typeerror: cannot set property sex of #<people> which has only a getter复制代码
方法class对象中有三种方法,分别是构造方法、实例方法还有静态方法。
class people {  //构造方法  constructor(name, age) {    this.namea = name;    this.age = age;  }  //实例方法  getname() {    return this.namea;  }  //静态方法  static say() {    console.log(hello  + people.desc);  }}people.desc = 类描述;let people = new people(mango, 27);let name = people.getname();console.log(name); //mangopeople.say(); //hello 类描述复制代码
继承继承是面向对象语言很重要的一大特征,es6新加入了extends和super关键字来实现继承。
class people {  constructor(name) {    this.name = name;  }  getname() {    return this.name;  }}//继承class student extends people {  constructor(name, age) {    super(name, age);  }}//student类继承了people类,student对象中super调用了父类的构造函数,并传递了name参数,因为继承的特性,student也拥有了父类的getname()方法let student = new student(es6);console.log(student.getname());复制代码
通过以上对class的学习,我们得知道其实class并不是新引入的数据类型,其实class只是一种语法糖,它的实质完全可以看作构造函数的另一种写法。
class people {  constructor(name) {    this.name = name;  }  getname() {    return this.name;  }}console.log(typeof people); //functionconsole.log(people.prototype);  //{constructor: ƒ, getname: ƒ}复制代码
js是单线程的异步编程其实就是处理异步任务,在进行异步编程之前,我们需要了解javascript是单线程的,在同一时间只能做一件事。
javascript之所以设计成单线程,是与它的用途有关。作为浏览器脚本语言,javascript的主要用途是与用户互动以及操作dom。这决定了它只能是单线程,否则会带来很多复杂的同步问题。例如,如果javascript同时有两个线程,一个线程在某个dom节点上添加内容,另一个线程删除了这个节点,这时浏览器就不晓得以哪个线程为准。所以,为了避免复杂,从一诞生,javascript就是单线程的。
单线程就意味着,所有任务都需要排队,前一个任务结束,后一个任务才会执行。那么如果前一个任务很长的话,那么后面一个任务不是就一直需要等待了吗?于是乎,js将所有任务分成了两类,同步和异步。
同步:只有前一个任务执行完毕,才能执行后一个任务
异步:当同步任务执行到某个需要耗时执行的运行环境api时,就会触发异步任务,此时运行环境(浏览器或node)就会单独开线程去处理这些异步任务。
javascript运行原理下面是javascript运行原理图,同步任务在js主线程完成,异步任务则新开一个线程
疑问:不是说javascript是单线程的吗,那为什么又新开了一条线程处理任务呢,这不是多线程方式吗?
有这个疑问不奇怪,我们需要清楚javascript单线程其实说的是javascript引擎是单线程的,开发者只能通过单线程的方式进行javascript开发,而新开了一条线程处理任务是底层执行环境决定的,javascript执行环境是多线程。
在实际项目中,异步编程使用场景极其之多,请求个接口数据、创建个定时器、缓存个数据都离不开异步编程的身影,为了更好的处理异步任务,es6给我们提供两种新的方式,分别是promise和generator。
promisepromise 是一个代理对象,代表了一个异步任务的最终成功或者失败状态。promise允许你为异步任务的成功或失败分别设置对应的处理方法,以类似同步的方式便捷的进行异步编程。
一个promise有三种状态:
pending:初始状态,既不是成功,也不是失败状态。fulfilled:成功状态,代表着任务执行完成rejected:失败状态,代表着任务执行失败基本使用创建promise对象const promise = new promise(function (resolve, reject) {    let result=执行异步任务;    if(result){        //如果异步任务成功完成        resolve()    }else{        //如果异步任务执行失败        reject();    }});复制代码
创建promise对象需要传递一个executor参数,executor是带有resolve和reject两个参数的函数,这两个参数是javascript引擎提供的两个函数,promise构造函数执行会立即调用executor函数。
当promise中的异步任务执行成功时,调用resolve(),将promise对象的状态从pending改为fulfilled(未完成到成功)当promise中的异步任务执行失败时,调用reject(),将promise对象的状态从pending改为rejected(未完成到失败)
如何使用实例好promise对象后,我们可以使用下面的语法来对异步任务完成后的状态进行处理。
promise.then(onfulfilled,onrejected)
promise.then(  (result) => {    console.log(异步任务处理成功,执行相应方法);  },  (error) => {    console.log(异步任务处理失败,执行相应方法);  });复制代码
但在具体的项目开发中,我们通常都是使用已经存在的promise对象,下面我们就通过使用常用的promise对象fetch获取接口数据。
案例实现我们需要调用接口,获取一个用户列表数据
fetch(http://jsonplaceholder.typicode.com/users)  .then(function (response) {    return response.json();  })  .then(function (res) {    console.log(res);   // [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]  });复制代码
response是一个包含响应结果的response对象,它只是一个http响应,而不是真正的json。为了获取json的内容,需要使用json()方法获取一个promise对象,然后再使用then获取json数据。
其他使用promise.prototype.catch()promise提供了catch()方法,用来捕获异步操作过程中遇到的错误异常,使用场景如下
const catchpromise = new promise(function (resolve, reject) {  reject(new error(error msg));});catchpromise.then().catch((e) => {  console.error(e); //error: error msg});复制代码
在promise对象中,除了可以使用reject(new error())的方式触发异常,还可以使用throw new error()的方式触发异常,但不建议使用throw new error()的方式,因为这种方式不会改变promise的状态。
promise.prototype.all()promise.all()用于处理多个异步任务,例如处理多张图片上传。promise.all()接受一个promise对象数组作为参数,执行完毕返回一个promise对象。
promise.all()的状态变化:
传入的promise对象数组全部变为fulfill状态则返回成功,调用resolve()
传入的promise对象数组有一个变为reject状态则返回失败,调用reject()
const promise1 = new promise(function (resolve, reject) {  settimeout(function () {    resolve(promise1);  }, 2000);});const promise2 = new promise(function (resolve, reject) {  settimeout(function () {    resolve(promise2);  }, 1000);});const promise3 = new promise(function (resolve, reject) {  settimeout(function () {    resolve(promise3);  }, 3000);});const promiseall = promise.all([promise1, promise2, promise3]);promiseall.then(function (results) {  console.log(results); // [promise1, promise2, promise3]});复制代码
promise.prototype.race()promise.race()也是用于处理多个异步任务,与promise.all()一样,promise.race()接受一个promise对象数组作为参数,执行完毕返回一个promise对象。
promise.race()的状态变化:
传入的promise对象数组有一个变为resolve状态则返回成功,调用resolve()
传入的promise对象数组有一个变为reject状态则返回失败,调用reject()
const promise1 = new promise(function (resolve, reject) {  settimeout(function () {    reject(promise1);  }, 2000);});const promise2 = new promise(function (resolve, reject) {  settimeout(function () {    resolve(promise2);  }, 1000);});const promise3 = new promise(function (resolve, reject) {  settimeout(function () {    resolve(promise3);  }, 3000);});const promiseall = promise.race([promise1, promise2, promise3]);promiseall.then(function (results) {  console.log(results); // promise2});复制代码
generator函数generator函数是用来处理异步任务的函数,函数内部包裹的就是异步任务的处理。generator不同于普通函数,当执行到异步任务,可以暂停,直到异步任务执行完毕再继续往下执行,类似同步的方法进行异步编程。
如何使用generator函数在使用上具体有以下特点
:定义generator函数时,function后面需要加上星号,例如function generatorfuncname()yield:需要暂停去执行异步任务时,需要在代码前面加上yield标识,例如yield fetch()next():调用generator函数后获得的是一个指针对象,调用指针的next方法,可以用来分阶段逐步执行generator函数。
那么具体如何使用generator函数实现异步编程呢,在学习promise的时候,我们实现了一个获取一个用户列表数据的案例,下面我们看看如何使用generator函数实现吧。
function* loadusers() {  const api = http://jsonplaceholder.typicode.com/users;  console.log(等待数据请求);  yield fetch(api); //暂停,开始执行异步任务  console.log(数据请求完成);  console.log(继续其他逻辑操作);}const generator = loadusers();const promise = generator.next().value;console.log(promise);promise  .then(function (response) {    return response.json();  })  .then(function (result) {    console.log(result); //[{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]    generator.next(); //打印:数据请求完成  继续其他逻辑操作。异步任务执行完毕后调用next(),继续执行generator函数中后续代码  });复制代码
proxyproxy翻译过来叫代理,proxy可以通过自定义行为来改变对象的基本操作,例如属性赋值、查找、枚举、函数调用等。
基本语法const p=new proxy(target,handler);参数说明:target:需要使用proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至是另外一个代理)handler:代理目标对象基本操作的对象返回值:p:target被代理后不可以直接访问,而只能访问
proxy使用场景设置对象属性的读写权限let people = {  name: mango,  //设置属性不可读  age: 27,  //设置属性不可被修改};let peoplehandler = {  //设置属性值读取的捕捉器  get: function (target, prop, receiver) {    if (object.is(prop, name)) {      return prop + 属性不可读;    }  },  //设置属性值操作的捕捉器  set: function (target, prop, value) {    if (object.is(prop, age)) {      throw new error(prop + 属性不可写);    }  },};let peopleproxy = new proxy(people, peoplehandler);peopleproxy.age = 10; // uncaught error: age属性不可写console.log(peopleproxy.name); //输出:name属性不可读复制代码
给接口返回的对象中字段为null的属性设置默认值let people = {  address: null,};let peoplehandler = {  //设置属性值读取的捕捉器  get: function (target, prop, receiver) {    return target[prop]  默认值;  //空值合并操作符  },};let peopleproxy = new proxy(people, peoplehandler);console.log(peopleproxy.address); //输出:默认值复制代码
拦截函数调用//handler.apply()用于拦截函数的调用function sum(a, b) {  console.log(a + b);  return a + b;}////对于sum方法,关注的是处理数据相加的逻辑//通过代理则可以处理在调用方法时候,对参数的校验,数据打点等const sumproxy = new proxy(sum, {  apply: function (target, thisarg, argumentslist) {    console.log(调用了方法, 打点);  },});sumproxy(1, 2);复制代码
**
module
es6在语言标准上,通过module实现了模块功能,现阶段几乎取代之前用来实现javascript模块化的commonjs和amd规范,成为了浏览器环境和node环境通用的模块化解决方案。
module实现的模块化属于“编译时加载”,即在编译时就完成了模块之间的加载,通过这种“编译时加载”的方式,使得在不运行代码的情况下就可以通过词法分析、语法分析等对程序代码进行扫描,以验证代码的规范性、安全性和可维护性,让静态分析成为了可能。
如何使用
module实现的模块化功能主要有两个命令构成:
export:导出命令,用于提供模块的对外接口import:导入命令,用于引入其他模块提供的接口
一个模块就是一个独立的文件,该文件内的所有变量,外部无法获取,如果想要外部获取模块内的某些变量,就必须使用export关键字导出变量,在需要引入该导出变量的的模块中必须使用import关键字引入变量。
export下面举例说明export命令导出对外接口的几种方式,在exportdemo.js文件中,
导出变量//方法一export let a = 1;//方法二let b = 2;export { b };复制代码
导出函数//方法一export function test(){    console.log(name);}//方法二let test2=function test(){    console.log(name);}export {test2 as newname}复制代码
注意在方法二中,使用了as关键字,as关键字可以在导出时重命名对外的接口名。
导出类//方法一export class people {  say() {      console.log(hello module);  }}//方法二export { people };复制代码
import使用export导出了模块中的对外接口后,其他js文件就可以通过import关键字加载这个模块,使用如下。
//大括号中的变量名必须与被导出对外接口名一致import { a, b, test as newtest, people } from ./exportdemo;//导入a和b的变量console.log(a, b);//导入test方法,同样可以使用as关键字在导入的时候重命名newtest();//导入people类let people = new people();people.say();复制代码
其他使用方式**
在日常开发过程中,module模块化还有下面几种常见的使用方式。
使用*指定一个对象,加载模块中的所有导出//import { a, b, test, people } from ./exportdemo;//上面的导入方式可以改写成下面方式import * as exportmodule from ./exportmodule;//使用的使用,加上前缀即可exportmodule.test()复制代码
通过export default,为模块指定默认导出名//导出const people = {  say: function () {    console.log(hello module);  },};export default people;//导入import people from ./exportmodule;people.say(); //hello module复制代码
export和import一起使用,处理先输入后输出的情况//假如有a、b、c三个文件模块,//c文件模块如下 c.jslet people={name:mango,age:27};let address=南京;export { people, address };//有下面几种使用场景//一、在b中导入c中的people和address,并导出给a使用export {people,address} from 'c'//二、在b中整体导入c,并导出给a使用export * from 'c'//三、在b中导入people,并作为b的导出名称【具名接口改为默认接口】export {people as default} from 'c'//当c的导出方式为export default的时候,并可以使用【默认接口改为具名接口】export {default as newpeople} from 'c'复制代码
相关免费学习推荐:javascript(视频)
以上就是全面掌握,ecmascript的新特性的详细内容。
其它类似信息

推荐信息