ES新特性系列(一)—— ES的简介与ES6

      前几天在BOSS上了解现在的前端工作的情况和各个公司要求的技术栈情况,看到一条非常有意思的要求:“能够理解并使用ES6、ES7、ES8、ES9、ES10新特性,都2024年了你总不能只知道ES6吧?”

      各位彦祖现在现在就回忆一下,自己是否能把上述的ES系列的常用新特性都能对应的讲出来,并知道它是如何使用的?是不是发现其实很多特性自己在工作中可能用到了,但是并不知道它是ES几的,甚至不知道它是ES的特性之一。

      本期的内容就是将目前ES系列的常用特性归纳并总结起来,以便无论是新入门前端还是想要应对面试的各位彦祖亦菲能够垂阅。话不多说,正文开始。

                                          

一、什么是ES?它的作用是什么?

1.什么是ES?

        ES的全称是ECMAScript,是一种由 Ecma International(欧洲计算机制造商协会)通过 ECMA-262 标准化的脚本程序设计语言。

      我们可以这样理解:Javascript语言当初被创造出来的时候,它主要用于网景公司的Navigator浏览器。后来各个浏览器厂商都觉得Javascript不错,于是他们都开始在自己的浏览器中应用了Javascript,但是同时又对它进行了个性化的处理,这就导致了同一段JavaScript代码在不同的浏览器中可能会有不同的行为。

      程序员说:OMG!难道我写一个功能就必须得知道N多个浏览器的Javascript代码含义?

      为了解决这个问题,网景公司将JavaScript提交给ECMA国际(一个制定国际标准的组织)进行标准化。这个标准就是ECMAScript。通过制定ECMAScript标准,可以确保所有遵循该标准的JavaScript环境都有一致的行为。这也符合了计算机开发要求的开放性。

2.ECMAScript 的主要作用和目标
  1. 定义标准:ECMAScript 定义了 JavaScript 的语法和行为标准,让各大浏览器厂商在实现时有具体的规范遵循,保证 JavaScript 代码在不同的环境中有相对统一的执行效果。

  2. 提供新特性和语法:每个新的 ECMAScript 版本都会引入新的语言特性和改进,使得 JavaScript 能够更好地满足现代 Web 开发的需求。例如,ES6(也称为 ECMAScript 2015)引入了许多重要的新特性,如箭头函数、Promises、类等。

  3. 推动 JavaScript 的发展:ECMAScript 的更新和改进推动了 JavaScript(及其方言)的发展和进步,使其成为今天 Web 开发中最常用的编程语言之一。

  4. 促进互操作性和一致性:通过提供统一的标准,ECMAScript 有助于确保不同的 JavaScript 实现(例如,不同浏览器的 JavaScript 引擎)在处理相同的 JavaScript 代码时能够产生一致的结果。

二、ES6新特性

        由于ES系列特性较多,我们本次就先讲ES6的常用新特性:

1.let和const

        ES6增加了两个新的变量声明方式:let和const——准确的说,const(英文意思就是常量)声明的是常量。

       我们先来看下var的弊端:

console.log(x)  // 不是报错,而是输出undefined,这就是x这个声明已经被提升到了顶层,只是还没有被赋值,这就是声明提升

var x = 10
var x = 15  // x被重复声明都不会报错

function someFunction() {
  var x = 20;  // 修改全局变量 x
  console.log(x);  // 输出 20
}

someFunction();
console.log(x);  // 最终输出 20,全局的 x 已经被修改

      看下let的对比:

console.log(x) // 直接报错,let、const不允许声明提升

// var x = 10  如果在上面使用var,函数中再用let声明会发生什么?
let x = 10
// let x = 50  直接报错,let、const不允许重复声明

function someFunction() {
 let x = 20;  // 修改全局变量 x
  console.log(x);  // 输出 20
}

someFunction();  // 输出10
console.log(x);  // 输出20,x没有被修改

       const有着和let相同的块级作用域,它相对let更特殊的是声明的是常量,不可修改:

const x = 10

x = 20  // 报错

       从以上的对比中我们发现let声明的变量的作用域被限制在最近的 {} 块中。而 var 声明的变量具有函数作用域或全局作用域,这有时会导致意料之外的结果。

       所以let、const的主要特点就是不允许重复声明、不存在变量提升、不影响作用域链、暂时性死区。

2.模板字符串

       在 ES6 中,模板字符串是一种新的字符串字面量语法,它可以包含嵌入的表达式。嵌入的表达式和变量会在运行时求值,并将结果转换为字符串。

      通俗点来说就是模板字符串可以在你的渲染模板中嵌入变量、表达式、甚至是函数调用。我们来看以下代码:

插入变量:

let name = 'World';
let greeting = `Hello, ${name}!`;
console.log(greeting);  // 输出 "Hello, World!"

多行插入:

let text = `Hello,
World!`;
console.log(text);
// 输出:
// Hello,
// World!

插入表达式:

let x = 1;
let y = 2;
let result = `The sum is ${x + y}.`;
console.log(result);  // 输出 "The sum is 3."

        模板字符串在我们的实际开发中非常常用,学会灵活且巧妙的运用它也是对各位彦祖亦菲的代码能力大有裨益的!

3.promise

         promise意为承诺——承诺好的,保证完成并给你结果。

         promise主要的作用是解决以下问题:

  1. 回调地狱(Callback Hell):当你需要在异步操作完成之后执行其他异步操作时,你可能需要嵌套回调函数。随着异步操作数量的增加,代码将变得难以阅读和维护。

  2. 错误处理:在传统的回调模式中,错误处理通常需要在每个回调函数中单独进行,这使得代码冗余且易错。

  3. 同步多个异步结果:在回调模式下,同步多个异步操作的结果通常需要手动进行状态管理,这非常复杂。

         在没有promise之前,我们如果遇到流程很长、比较复杂的异步操作,我们可能需要等待第一个异步流程出了结果,然后调用其回调函数1、然后回调函数1出了结果后、在调用回调函数2......

// 这里定义N个异步函数
function asyncOperation1(callback) {
  setTimeout(() => {
    console.log('Operation 1 complete');
    callback();
  }, 1000);
}

function asyncOperation2(callback) {
  setTimeout(() => {
    console.log('Operation 2 complete');
    callback();
  }, 1000);
}

function asyncOperation3(callback) {
  setTimeout(() => {
    console.log('Operation 3 complete');
    callback();
  }, 1000);
}
 // ......更多
asyncOperation1(() => {
  asyncOperation2(() => {
    asyncOperation3(() => {
        // ......更多的回调函数
      console.log('All operations complete');
        // ......
    });
  });
});

      套一层两层可能还好,哪要是套100层呢?代码就会直接堆积成 “屎山” ,也被称为 “回调地狱”,这样写显然是不符合我们代码要求的简洁与可读性强的要求的,于是便有了promise:

// 将异步操作封装为返回 Promise 的函数
function asyncOperation1() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('Operation 1 complete');
            resolve();
        }, 1000);
    });
}

function asyncOperation2() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('Operation 2 complete');
            resolve();
        }, 1000);
    });
}

function asyncOperation3() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('Operation 3 complete');
            resolve();
        }, 1000);
    });
}

// 使用链式调用来执行异步操作
asyncOperation1()
    .then(asyncOperation2)
    .then(asyncOperation3)
    .then(() => {
        console.log('All operations complete');
    })
    .catch((err) => {
        console.error('An error occurred: ' + err);
    });

       可以看出:promise仅用链式调用就大大简化了异步代码的书写,并且还有统一的错误处理,直接解决了回调地狱,而且简介明了,方便调试和改正。

4.模块化

       为了使 JavaScript 可以更好地组织和复用代码。ES6新增了模块化的新特性:

        ES6的模块化使用的关键词是import(引入)、export(导出)、default(默认导出)、as(重命名导入或导出的名字),示例:

// 导出变量
export let name = 'Alice';

// 导出函数
export function sayHello() {
  console.log('Hello');
}

// 导出类
export class Circle {
  constructor(radius) {
    this.radius = radius;
  }
}

// 默认导出
export default function() {
  console.log('Default export');
}

// 重命名导出
function foo() {}
export { foo as bar };

// 导入全部导出
import * as math from './math.js';

// 导入特定导出
import { add, subtract } from './math.js';

// 导入默认导出
import myDefault from './module.js';

// 重命名导入
import { foo as myFoo } from './module.js';

      遵循模块化开发,对我们有如下的帮助:

  1. 解决命名冲突:在大型的项目中,全局命名冲突可能会成为问题。通过模块化,我们可以让变量和函数都在模块作用域内,避免了全局命名冲突。

  2. 提高代码的可维护性:模块化可以让我们按照功能将代码组织成不同的模块,每个模块具有明确的职责。当需要修改某个功能时,只需要找到对应的模块进行修改,而不需要在整个代码库中查找。

  3. 提高代码的可读性和可理解性:每个模块只包含相关的代码,这让代码更容易理解。另外,通过 import 和 export,我们可以清楚地看到模块之间的依赖关系。

  4. 代码复用:模块化让代码更容易复用。当我们实现了一个可复用的功能,我们可以将它放到一个模块中,然后在需要的地方导入使用。

  5. 延迟加载:通过模块化,我们可以实现代码的延迟加载,也就是按需加载。只有当模块真正需要被使用时才会加载,这可以提高应用的加载速度。

5.symble

        Symbol 是 ES6 引入的一种新的原始数据类型,表示独一无二的值。它的主要作用是创建对象的唯一属性名,避免属性名冲突。下面直接看例子:

// symble的唯一性
let sym1 = Symbol("description");
let sym2 = Symbol("description");
console.log(sym1 === sym2); // 输出 false

        Symbol 属性不会出现在 for...in、for...of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。但它可以被 Object.getOwnPropertySymbols 和 Reflect.ownKeys() 返回。

6.拓展运算符(...)和解构赋值

       拓展运算符(...)允许一个表达式在期望多个参数(用于函数调用)或多个元素(用于数组字面量)或多个变量(用于解构赋值)的位置展开。例子如下:

// 在函数调用中使用拓展运算符
function sum(x, y, z) {
  return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 输出 6

// 在数组字面量中使用拓展运算符
let arr1 = [1, 2, 3];
let arr2 = [...arr1, 4, 5, 6]; // arr2 现在是 [1, 2, 3, 4, 5, 6]

        平时如果咱们浅拷贝数据的话就可以用拓展运算符来处理。       

        值得注意的是,对象也可以使用拓展运算符,但是它是“升级”过后的原因,对象能用拓展运算符属于ES9的内容。

         解构赋值语法是一种 JavaScript 表达式,可以将属性/值从对象或数组中取出,赋值给其他变量。示例如下:

// 对象的解构赋值
let obj = {a: 1, b: 2, c: 3};
let {a, b, c} = obj;
console.log(a, b, c); // 输出 1 2 3

// 数组的解构赋值
let arr = [1, 2, 3];
let [x, y, z] = arr;
console.log(x, y, z); // 输出 1 2 3
7.箭头函数

     箭头函数主要有以下三个特点:

1.更简洁的语法:箭头函数提供了一种更简洁的函数定义语法。比如,一个普通的函数可以被箭头函数简化为:

// 普通函数
const add = function(x, y) {
  return x + y;
}

// 箭头函数
const add = (x, y) => x + y;

2.不绑定this:箭头函数不会创建自己的 this 值。在箭头函数内部,this 的值与箭头函数定义时的外部函数相同。这在处理一些涉及 this 的问题(例如事件处理器或回调函数)时非常有用。

function Timer() {
  this.seconds = 0;
  setInterval(() => this.seconds++, 1000); // this 指向 Timer 实例
}
let timer = new Timer();
setTimeout(() => console.log(timer.seconds), 3100); // 输出 3

3.没有 arguments 对象:在箭头函数中,不存在 arguments 对象。如果你需要使用 arguments,可以使用 rest 参数代替。

const foo = (...args) => console.log(args);
foo(1, 2, 3, 4); // 输出 [1, 2, 3, 4]

      箭头函数在代码开发中也非常常用,学会灵活的使用箭头函数可以使咱们的代码看起来更加简洁规范。

8.class类

        一句话说明class类:就是JavaScript专门引入的用来提供一种更简洁、更直观的语法来创建对象和处理继承的东西。

       我们创建一个“人”的calss类来说明:

class Person {
  constructor(name, age, gender, hobbies) {
    this.name = name;
    this.age = age;
    this.gender = gender;
    this.hobbies = hobbies;
  }

  introduce() {
    console.log(`Hi, my name is ${this.name}. I am a ${this.age} year old ${this.gender} and I enjoy ${this.hobbies.join(', ')}.`);
  }
}

      这里我们创建了一个包含了人的姓名、性别、年龄等属性的类,constructor包含类这个类的核心属性,然后定义了一个introduce即介绍的方法。我们看这个类如何继承:

class Student extends Person {
  constructor(name, age, gender, hobbies, major, school) {
    super(name, age, gender, hobbies);
    this.major = major;
    this.school = school;
  }

  introduce() {
    super.introduce();
    console.log(`I am studying ${this.major} at ${this.school}.`);
  }
}

        在接下来的Student中,我们使用super关键词继承了Person中的所有核心属性,并新增了“专业”和“学校”两个新的属性,这样是不是就避免了创建Student时候重写一遍Person属性的麻烦?

        最后,我们创建一个 Person 实例和一个 Student 实例,并调用他们的 introduce 方法:

let bob = new Person('Bob', 30, 'male', ['coding', 'reading']);
bob.introduce();  // 输出 "Hi, my name is Bob. I am a 30 year old male and I enjoy coding, reading."

let alice = new Student('Alice', 20, 'female', ['painting', 'music'], 'Computer Science', 'Harvard');
alice.introduce();  // 输出 "Hi, my name is Alice. I am a 20 year old female and I enjoy painting, music. I am studying Computer Science at Harvard."

class 的引入为 JavaScript 带来了很多好处:

  1. 语法简洁class 提供了一个更清晰且简洁的语法来创建对象和实现继承。这使得代码更易于编写和理解。

  2. 易于理解:对于有其他面向对象编程语言背景的开发者来说,class 的概念更加符合他们的思维习惯,使得他们更容易理解和接受。

  3. 封装性class 提供了基于类的封装,可以更好地组织代码,将相关的数据和行为封装到一个对象中,提高代码的可读性和可维护性。

  4. 继承和多态class 提供了基于类的继承,可以创建子类并复用父类的代码,这大大增加了代码的可复用性。通过方法的重写,还可以实现多态,提高了代码的灵活性。

  5. 更好的支持:许多现代的 JavaScript 功能和框架,如 React 和 Angular,都优先支持 class 语法。

三、总结

        本期主要是为各位彦祖亦菲介绍了ES的来源与ES6的常用新特性,深入学习ES系列不仅可以让我们的代码的开发更方便、简介和优雅,还有一个作用就是它也是面试常问的一些问题,希望本期内容能给大家带来帮助!点赞关注,咱们下期再讲ES的后续系列!

  • 52
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值