位置:首页 > web前端 > javascript

ES6新特性的深入理解

dearweb 发布:2021-10-20 09:51:21阅读:

在之前的文章中我也写过es6新特性但写的不是很全,今天将会更加全面的来介绍ES6新增属性的特性,闲话不多说,下面我们一起来学习吧

本文的主要介绍内容:

· 块级作用域、块级变量let、块级常量const

· 箭头函数

· 参数处理(默认参数/...)

· 模板字面量(模板字符串)

· 对象的扩展

· 解构赋值

· 模块(import/export)

· 类(class/extends)

· WeakSet

· Promise

块级作用域、变量let、常量const

· 由一对 { } 界定的语句叫做块语句,这在其他编程语言中叫做复合语句。

一般理解的块:

   块:由{}包括住

   if(){}:是一个块

   for(){}:是一个块

这些也可理解成块:

   一个函数:函数作用域

   <script>标签:全局作用域

· JS中用var声明的变量是没有块级作用域的,只有函数作用域和全局作用域。

var x = 1;
{
  var x = 2;
}
console.log(x);
//会输出2,因为块中的var语句与块前面的var语句作用域相同
//在C或Java中,这段代码会输出 1
//这代码证明了var没有块作用域

· 相比之下,使用 let 和 const 声明的变量是有块级作用域的。

let x = 1;
{
  let x = 2;
}
console.log(x); 

// 输出 1
// x被限制在块级作用域中
// 这里将let换成const结果也一样

· 经典的例子(背住):

var a = [];
for (var i = 0; i < 10; i++) {
      a[i] = function () {console.log(i);};
}
a[0]();                // 10
a[1]();                // 10
a[6]();                // 10

/********************/

var a = [];
for (let i = 0; i < 10; i++) {
      a[i] = function () {console.log(i);};
}
a[0]();                // 0
a[1]();                // 1
a[6]();                // 6

第一个中的i是没有块级作用域的,全局只有一个变量i,每次循环的{}中的代码都指向同一个i

第二个中的i是有块级作用域的,当前的i只在本轮循环有效,每次循环的{}中的代码都指向不同的i

值得注意的是:for的()是一个父作用域,{}是一个子作用域

因为当前的i只在本轮循环有效,所以let定义的i在每次循环都会重新定义一遍,每次都是一个新变量

· 补充:

使用function时也有块级作用域

foo('outside');  // TypeError: foo is not a function
{
  function foo(location) {
   console.log('foo is called ' + location);
  }
  foo('inside'); // 正常工作并且打印 'foo is called inside' 
}

· var变量提升,let变量暂时性死区:

//var会发生变量提升现象,将变量提升到函数顶部或全局顶部 
console.log(foo); //输出undefined,不会报错
var foo = 2; 

//let没有这种情况,必须在let声明后调用该变量 (const和let一样)
//let和const的这种现象称为暂时性死区
//从块开始到let声明之间是“暂存死区”
console.log(bar); //报错ReferenceError 
let bar = 2;

· let的错误

在同一个作用域中用let重复定义一个变量将引起TypeError

if (x) {
  let foo;
  let foo; // TypeError thrown.
}

· const的概念


必须在声明的同一语句中指定它的值

const声明创建一个值的只读引用,也就是说只能通过const定义的变量来读这个值,不能修改这个值,但是如果这个值本身发生了变化,那么const定义的变量所对应的值也就跟着变化,比如当引动内容是对象的情况下。

一个常量不能和它所在作用域内的其他变量或函数拥有相同的名称。

下面错误,常量要求有一个初始值

const FOO;

// 下面正确,常量可以定义成对象
const MY_OBJECT = {"key": "value"};
// 下面错误,改变常量会失败
MY_OBJECT = {"OTHER_KEY": "value"};
// 对象属性并不在保护的范围内,下面会成功执行(对象本身发生变化)
MY_OBJECT.key = "otherValue";

// 定义一个常量
const MY_FAV = 20;
// 不能同名,会出错
var MY_FAV = 20; 
// 也会报错
let MY_FAV = 20;

箭头函数

1、书写上用=>代替了function 

2、普通函数的this指向window 而ES6箭头函数里面的this指向定义时的那个对象 而不是运行时的那个对象

基础语法:

//一般语法:
(参数1, 参数2, …, 参数N) => { 函数声明 }

//相当于:(参数1, 参数2, …, 参数N) =>{ return 表达式; }
(参数1, 参数2, …, 参数N) => 表达式(单一)

// 当只有一个参数时,圆括号是可选的:
(单一参数) => {函数声明}
单一参数 => {函数声明}

// 没有参数的函数应该写成一对圆括号
() => {函数声明}

高级语法:

(*注: 字面量一般指[1, 2, 3] 或者{name: "mdn"} 这种简洁的构造方式)

*//加括号的函数体返回对象字面表达式:
参数=> ({foo: bar})

//支持剩余参数和默认参数
(参数1, 参数2, ...rest) => {函数声明}
(参数1 = 默认值1,参数2, …, 参数N = 默认值N) => {函数声明}

//同样支持参数列表解构
let f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
f();  // 6
例子:

箭头函数比较适用于原本需要匿名函数的地方,比如用在数组内置方法map、filter、forEach、reduce的回调函数中

var elements = [
  'Hydrogen',
  'Helium',
  'Lithium',
  'Beryllium'
];

elements.map(function(element) { 
  return element.length; 
}); // 返回数组:[8, 6, 7, 9]

// 上面的普通函数可以改写成如下的箭头函数
elements.map((element) => {
  return element.length;
}); // [8, 6, 7, 9]

重点注意:

this 对象的指向是可变的,但是在箭头函数中,它是固定的。箭头函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。

参数处理

1. 给函数设置默认参数值

在ES6以前,我们想要给参数设置默认值得这么做:

function multiply(a, b) {
  //JavaScript中函数的参数默认是undefined
  //没有值传入或者undefined被传入给b时给b一个默认值
  b = (typeof b !== 'undefined') ?  b : 1;
  return a * b;
}

multiply(5, 2); // 10
multiply(5);    // 5

现在学习了ES6之后就变得很简单:

function multiply(a, b = 1) {
  return a * b;
}
multiply(5, 2); // 10
multiply(5);    // 5

注意参数的传递是从左到右的:

function f(x = 1, y) { 
  return [x, y]; 
}

f(); // [1, undefined]
f(2); // [2, undefined]

2. 剩余参数(...)

剩余参数语法允许我们将一个不定数量的参数表示为一个数组。

语法:

function(a, b, ...theArgs) {
   ...
}

说明:

必须是函数的最后一个参数以...为前缀

它是由剩余实参组成的Array数组

这个例子中的theArgs收集第三个以及之后的所有实参

实例:

function fun1(...theArgs) {
  alert(theArgs.length);//可以使用所有数组属性方法
}
fun1();  // 弹出 "0", 因为theArgs没有元素
fun1(5); // 弹出 "1", 因为theArgs只有一个元素

3. 展开运算符(...)

展开语法, 可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。

(注: 字面量一般指[1, 2, 3] 或者{name: "mdn"} 这种简洁的构造方式)

在函数调用时使用展开语法:

function sum(x, y, z) {
  return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers));//6

/****下例表示可以多次使用****/

function myFunction(v, w, x, y, z) { }
var args = [0, 1];
myFunction(-1, ...args, 2, ...[3]);

构造字面量数组时使用展开语法:

/****构造字面量数组或连接数组****/

var parts = ['shoulders', 'knees']; 
var lyrics = ['head', ...parts, 'and', 'toes']; 
// ["head", "shoulders", "knees", "and", "toes"]
var arr = [...parts, ...lyrics];

/****支持浅拷贝(一维数组)****/

var arr = [1, 2, 3];
var arr2 = [...arr];
arr2.push(4); 

// arr2 此时变成 [1, 2, 3, 4]
// arr 不受影响

构造字面量对象时使用展开语法:

var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
var clonedObj = { ...obj1 };
// 浅拷贝
// 克隆后的对象: { foo: "bar", x: 42 }

var mergedObj = { ...obj1, ...obj2 };
// 合并后的对象: { foo: "baz", x: 42, y: 13 }

四. 模板字面量(模板字符串)

由反引号 ` 来代替单引号 ' 或者 双引号 " 。

单行字符串:

`string text`
多行字符串(注意回车也包含在内)
`string text line 1
 string text line 2`

console.log(`string text line 1
string text line 2`);//不再用"+"或"\"连接两行字符串
插入表达式:
`string text ${expression} string text`

var a = 5;
var b = 10;
console.log(`a + b is ${a + b}`);//不再用"+"连接表达式

带标签的模板字符串:

标签使我们可以用函数来解析模板字符串,直接看例子:

var person = 'Mike';
var age = 28;

function myTag(strings, personExp, ageExp) {

  var str0 = strings[0]; // "that "
  var str1 = strings[1]; // " is a "

  var ageStr;
  if (ageExp > 99){
    ageStr = 'centenarian';
  } else {
    ageStr = 'youngster';
  }

  return str0 + personExp + str1 + ageStr;
}

var output = myTag`that ${ person } is a ${ age }`;

console.log(output);
// that Mike is a youngster

原始字符串: 在标签函数的第一个参数中,存在一个特殊的属性raw ,我们可以通过它来访问模板字符串的原始字符串,而不经过特殊字符的替换。

例子:

function tag(strings) {
  console.log(strings.raw[0]);
}
tag`string text line 1 \n string text line 2`;
// logs "string text line 1 \n string text line 2"

对象的扩展

创建对象时可以简化方法和重名的键值对
//一般情况
var name="pan";
var age=20;
var people = {
      name: name,
      age: age,
      getName: function() {
           console.log(this.name)
      }
};

//ES6简化后
var name="pan";
var age=20;
var people = {
      name,
      age,
      getName(){
           console.log(this.name)
      }
};
计算属性名(属性名可以用表达式)
var i = 0;
var a = {
  ["foo" + ++i]: i,
  ["foo" + ++i]: i,
  ["foo" + ++i]: i
};
console.log(a.foo1); // 1
console.log(a.foo2); // 2
console.log(a.foo3); // 3
__proto__

当定义的 属性:值 为 __proto__:值 时,不会创建名为__proto__的属性。 

但当值为对象时,将更改原型(因为__proto__原本指向原型)。 所以不建议使用这个属性名(避免更改__proto__的指向)。

解构赋值

解构赋值语法是一种 Javascript 表达式,它使得将值从数组,或属性从对象,提取到不同的变量中,成为可能。 目的就是将数组中的值或对象中的属性方便的提取到其他变量中 解构数组语法:

var x, y;
[x, y] = [1, 2, 3];
console.log(x); // 1
console.log(y); // 2

//或者:
var [x, y] = [1, 2, 3];
console.log(x); // 1
console.log(y); // 2

小技巧(不用临时变量也能交换变量):

var a = 1;
var b = 3;
//等价于[a,b]=[3,1]
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1

解构对象语法:

var o = {p: 42, q: true};
var {p, q} = o;

console.log(p); // 42
console.log(q); // true

注意:对象解构与数组解构有一个重要的不同点,数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

模块功能主要由两个命令构成:export和import。

export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。 一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量。

导出export

var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export {firstName, lastName, year};

上面代码是profile.js文件,保存了用户信息,ES6将其视为一个模块,里面用export命令对外部输出了三个变量。除了这种普通变量,输出的变量还可以是一个函数。另外,export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。最后,export/import命令不能在块级作用域内,可以在全局作用域任何位置。


导入import

import语句只能在声明了type="module"的script的标签中使用。动态导入:import(),它不需要依赖type="module"的script标签。按照一定的条件或者按需加载模块的时候,动态import()是非常有用的。而静态的import是初始化加载的最优选择。

// main.js
import {firstName, lastName, year} from './profile.js';

function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

上面代码的import命令,用于加载profile.js文件,并从中输入变量。 import命令接受一对大括号,里面指定要从其他模块导入的变量名。 大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。

注意:import语句会执行所加载的模块 import './lodash.js'; 

· 执行lodash模块,但是不输入任何值 整体导入: import * as myModule from './my-module.js';

· 不用加大括号{} 

· 将my-module里面输出的变量/函数, 

· ·输入到myModule这个对象中作为属性/方法。 

· 通过myModule.属性名/myModule.方法()来调用。 默认导出/导入默认值: 告别使用import命令的时候,用户需要知道所要加载的变量名或函数名的现状 

· 先用export default语法默认导出

· 既然是默认输出,那么一个模板只能使用一次

export default function foo() {
  console.log('foo');
}
//导入默认值
import customName from './default.js';//不用加大括号{}
customName(); // 'foo'

对class类的支持 

ES6 中引入的 JavaScript 类实质上是 JavaScript 现有的基于原型的继承的语法糖。类语法不会为JavaScript引入新的面向对象的继承模型。

如何创建类:

class Rectangle {
  constructor(height, width) { //构造方法
    this.height = height;
    this.width = width;
  }
}
//或者
let Rectangle = class {
  constructor(height, width) { //构造方法
    this.height = height;
    this.width = width;
  }
};

如何使用类:

class Rectangle {
    // constructor
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
    // Getter
    get area() {
        return this.calcArea()
    }
    // Method
    calcArea() {
        return this.height * this.width;
    }
}
const square = new Rectangle(10, 10);

console.log(square.area);
// 100

类的静态方法:不能通过一个类实例调用静态方法(这里和Java语法有区别)

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    static distance(a, b) { //static语法
        const dx = a.x - b.x;
        const dy = a.y - b.y;

        return Math.hypot(dx, dy);
    }
}

const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
//直接通过类名调用
console.log(Point.distance(p1, p2));

类的继承(extends)

class Animal { 
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(this.name + ' makes a noise.');
  }
}

class Dog extends Animal {
   constructor(name) {
    super(name); // 调用超类构造函数
  }

  speak() {
    console.log(this.name + ' barks.');
  }
}

var d = new Dog('Mitzie');
// 'Mitzie barks.'
d.speak();

调用父类方法(super)

class Cat { 
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(this.name + ' makes a noise.');
  }
}

class Lion extends Cat {
  speak() {
    super.speak();
    console.log(this.name + ' roars.');
  }
}

Symbol

特性

Symbol()函数,返回一个symbol类型的值,该类型具有静态属性和静态方法。

每个Symbol()返回的symbol值都是唯一的,这是该数据类型仅有的目的,可以作为对象属性的标识符使用

Symbol()存在原型链Symbol.prototype

不支持语法:"new Symbol()"创建对象

不支持将一个 symbol 值转换为一个 number。

不支持将一个字符隐式创建,例如: symbol('a') + '1231'


总结

Symbol用法很强大普遍,es5私有api中已经有很多的使用,例如:常用的String.prototype.split()。

Symbol创建的值唯一。

替代传统的常量定义保证唯一并且不被串改将是非常友好的。

定义私有属性将会非常方便。

WeakSet

WeakSet与Set类似,也是不重复的值的集合,但WeakSet的成员只能是对象。WeakSet引用的对象都是弱引用,如果其他对象不再引用该对象,那么垃圾回收机制就会自动回收这些对象所占用的内存,不考虑该对象还存在于WeakSet之中。

Promise

Promise主要是异步编程的一个解决方法。从语法上来看,Promise是一个对象。ES6中规定Promise是一个构造函数。

它有三个状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败);状态一旦改变,不可逆。

Promise的原型对象上有三个方法用来接收Promise执行完成时的状态:

Promise.prototype.then()

then方法有两个回调函数形式的参数,第一个参数是Promise成功的回调,第二个则是失败的回调,第二个参数可选。

Promise.prototype.catch()

catch方法主要是Promise失败的回调。

Promise.prototype.finally()

finally方法可接收Promise无论成功或失败的结果。

Promise的方法:

Promise.all

all方法可以数组形式接收一组Promise实例,当所有实例执行完成且状态是fulfilled时,才会返回结果。返回结果是每一个实例执行返回的结果组成的数组。一旦有一个实例执行结果为rejected,all方法就会返回rejected。

Promise.race

race方法也是以数组形式接收一组Promise实例,只要有一个实例执行完成,race方法就会返回结果。返回结果就是率先执行完成的实例返回的结果。

Promise.allSettled

allSettled方法也是以数组形式接收一组Promise实例,不管实例执行结果是fulfilled还是rejected,都会等所有实例执行完成才返回结果。返回结果是每一个实例执行返回的结果组成的数组。

Promise.any

any方法也是以数组形式接收一组Promise实例,一旦有一个实例执行结果为fulfilled,any方法就会返回fulfilled。当所有实例执行完成且状态是rejected时,返回结果才是rejected。

上述就是ES6新特性的所有概念性的知识点,希望你能从中得到自己想要的。

最后简单直观的概括一下

ES5

保留关键字: promise.catch(function() { });

ES6

箭头函数 Arrow functions: <C onPress={() => this.setState({pressed: true})} />
块级作用域 Block scoping: let greeting = 'hi';
数组的扩展运算 Call spread: Math.max(...array);
类 Classes: class C extends React.Component { render() { return <View />; } }
常量 Constants: const answer = 42;
解构 Destructuring: var {isActive, style} = this.props;
for...of: for (var num of [1, 2, 3]) {}
模块 Modules: import React, { Component } from 'react';
动态属性键 Computed Properties: var key = 'abc'; var obj = {[key]: 10};
对象方法的简写 Object Consise Method: var obj = { method() { return 10; } };
对象属性的简写 Object Short Notation: var name = 'vjeux'; var obj = { name };
参数的扩展运算 Rest Params: function(type, ...args) { }
字符串模板 Template Literals: var who = 'world'; var str = `Hello ${who}`;

ES8

参数列表末尾允许放置逗号 Function Trailing Comma: function f(a, b, c,) { }
异步函数 Async Functions: async function doStuffAsync() { const foo = await doOtherStuffAsync(); };


24人点赞 返回栏目 分享一波

小礼物走一波,支持作者

还没有人赞赏,支持一波吧

留言 评论仅代表网友个人 留言列表

暂无留言,快来抢沙发吧!

手机扫码查看 手机扫码查看