JS和TS的基础语法学习以及babel的基本使用

简介

本文主要介绍了一下js和ts的基础语法,为前端开发zuo

JavaScript

更详细的 JavaScript 学习资料:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript

简介

定位 : JavaScript 是一种动态语言,它包含类型、运算符、标准内置( built-in)对象和方法。在基本语法方面,JavaScript 有很多和 C/C++相似的地方,经常在浏览器开发中使用

依附宿主 : 与大多数编程语言不同,JavaScript 没有输入或输出的概念。它是一个在宿主环境(host environment)下运行的脚本语言,任何与外界沟通的机制都是由宿主环境提供的。浏览器是最常见的宿主环境,但在非常多的其他程序中也包含 JavaScript 解释器,如 Adobe Acrobat、Adobe Photoshop、SVG 图像、Yahoo! 的 Widget 引擎,Node.js之类的服务器端环境。

标准 : 我们有时候也会看到 ECMAScript 或者 ES6 之类的称呼,ECMA 是 JavaScript 的标准化组织,ECMAScript 是针对 JavaScript 语言制定的标准,之所以不叫 JavaScript,是因为 Java 和 JavaScript 的商标都被注册了。因此 ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 JScript 和 ActionScript)

运行环境

安装js解释器node.js,终端输入npm -v检查是否安装成功

安装vscode插件code runner便于运行js代码

然后就可以ctrl+alt+N或者点击顶部有个三角形的图标就可以运行js代码了,另外js也可以直接F5运行

console.log("Hello World")

或者终端输入

node js/learn.js 

数据类型

  • Number(数字)

    • 3/2=1.5(默认浮点运算)
    • 特殊的值 NaN(Not a Number 的缩写),如果把 NaN 作为参数进行任何数学运算,结果也会是 NaNNaN如果通过 ==!==== 、以及 !==与其他任何值比较都将不相等 – 包括与其他 NAN 值进行比较。必须使用 Number.isNaN()isNaN() 函数
    • 内置对象 Math支持一些高级的计算;
  • String(字符串)

    • 编码规范 : JavaScript 中的字符串是一串 Unicode 字符序列

    • 声明 : '"皆可

    • 转换 : 可以使用内置函数 parseInt()parseFloat()来将字符串转为 number

    • 占位符

      let name = "Bob",
        time = "today";
      console.log(`Hello ${name}, how are you ${time}?`);
      
      let a = 5;
      let b = 10;
      console.log(`Fifteen is ${a + b} and
      not ${2 * a + b}.`);
      
  • Boolean(布尔)

  • Symbol(符号)(ES2015 新增)

  • Object

    (对象)

    • Function(函数)

    • Array(数组)

      • JavaScript 数组的长度和元素类型都是非固定的,并且其数据在内存中也可以不连续

      • 允许undefine这种数据

      • 和上面的 String 类似,可以new Array()来创建数组,当然更简单的是使用字面量来创建let a =['abc',1];

      • let a = [1, 2, , 4];
        console.log(a[2]);
        a[100] = 100;
        a.pop();
        console.log(a.length);
        
    • Date(日期)

    • RegExp(正则表达式)

  • null(空)

  • undefined(未定义)

可以看到函数和数组也属于对象

变量

声明

JavaScript 有三种声明变量的方式。

  • var

    声明一个全局或者函数作用域变量,可选初始化一个值。

  • let

    声明一个块作用域的局部变量,可选初始化一个值。

  • const

    声明一个块作用域的只读常量,必须初始化一个值。

如果声明了一个变量却没有对其赋值,那么这个变量的类型就是 undefined

const很明显是一个常量,他是只读的,而letvar的主要区别在于,let的作用域是块作用域,而var的作用域是全局或者函数作用域(const也是块作用域),并且let没有变量提升

变量提升

即变量的使用在声明之前,正常情况会报错,而变量提升相当于提前声明了undefined

使用let更容易调试出潜藏的错误,因为如果未提前声明会直接报错,如下面代码所示

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

他们的详细区别可见此处

展开语法

展开语法(Spread syntax),让我们可以快速利用一个已有的数组,构造一个新的数组

let a = [1, 2, 3];
let b = { 1: "1", 2: "2" };

let c = [...a, 4];
//[1, 2, 3, 4]
let d = { ...b, 3: "3" };
//{1: "1", 2: "2", 3: "3"}

运算符

这里只介绍与 C++不同的部分

  • 求幂:x**2

  • 全等和不全等:x===y x!==y比较两个操作数是否相等且类型相同

  • 一元的正:即+,如果操作数在之前不是 number,试图将其转换为 number

  • 字符串运算:+可以直接连接两个字符串,并同时会尝试将另一个操作数转换为 string

  • 解构赋值:将属性/值从对象/数组中取出,赋值给其他变量,例如

    var a, b, rest;
    [a, b, ...rest] = [10, 20, 30, 40, 50];
    console.log(a); // 10
    console.log(b); // 20
    console.log(rest); // [30, 40, 50]
    
    var o = { p: 42, q: true };
    var { p, q } = o;
    

控制结构

JavaScript 的控制结构与其他类 C 语言类似,在此进行一下罗列

分支

if

//分支
var name = "kittens";
if (name == "puppies") {
  name += "!";
} else if (name == "kittens") {
  name += "!!";
} else {
  name = "!" + name;
}
name == "kittens!!"; // true

switch

switch (action) {
  case "draw":
    drawIt();
    break;
  case "eat":
    eatIt();
    break;
  default:
    doNothing();
}

循环

while

while (true) {
}

do … while

var input;
do {
  input = get_input();
} while (inputIsNotValid(input));

for

for (var i = 0; i < 5; i++) {

}

独特

JavaScript 也还包括其他两种重要的 for 循环:

forof

for (let value of array) {
  // do something with value
}

forin

for (let property in object) {
  // do something with object property
}

for ... in是为遍历对象属性而构建的,不建议与数组一起使用

函数

定义

function add(x, y) {
  var total = x + y;
  return total;
}

参数

不够 : 缺少的参数会被 undefined 替代

过多 : 多的参数会存在 arguments 这个内部对象中,可以像数组一样来访问它,如下

function add() {
  var sum = 0;
  for (var i = 0, j = arguments.length; i < j; i++) {
    sum += arguments[i];
  }
  return sum;
}
add(2, 3, 4, 5); // 14

多参数改进 : 因为arguments写起来又丑又长,我们可以用剩余参数来实现相似的功能,如下

function avg(first, ...args) {
  var sum = first;
  for (let value of args) {
    sum += value;
  }
  return sum / args.length;
}

avg(2, 3, 4, 5); // 3.5

嵌套函数

JavaScript 也允许在一个函数内部定义函数,它们可以访问父函数作用域中的变量

function parentFunc() {
  var a = 1;

  function nestedFunc() {
    var b = 4; // parentFunc 无法访问 b
    return a + b;
  }
  return nestedFunc(); // 5
}

匿名函数

便于我们实现一些简短的函数

//直接调用
(function (x, y) {
  return x + y;
})(1, 2);
//3

//作为参数传递
setTimeout(function () {
  console.log("111");
}, 1000);

//赋值给变量
const add = function (x, y) {
  return x + y;
};
add(1, 2);
//3

箭头函数

让我们可以用更简单的方法定义函数,而非全都使用function,常用于匿名函数

(param1, param2,, paramN) => { statements }

(param1, param2,, paramN) => expression
//相当于:(param1, param2, …, paramN) =>{ return expression; }

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

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

对象

JavaScript 中的对象,Object,可以简单理解成“名称-值”对

一般像下面这样定义

//创建
let person = {};

//添加
person.name = "John";
person.age = 30;
person.isStudent = false;

//删除
delete person.age;

//修改
person.name = "Tom";

//访问
console.log(person.name); 
console.log(person['name']); 

成员函数

let person = {
  name: "Alice",
  age: 25,
  sayHello: function() {
    console.log("Hello, my name is " + this.name);//使用this访问
  }
};
person.sayHello(); 

有关 OOP 的细节在这里不再介绍,其概念与 C++有一些相似性,如果想复习一下 OOP 并且了解 Js 中的对象可以参考这里

异步

我们在代码执行时,会遇到那种加载资源的函数,会阻塞我们的程序运行

但为了充分利用我们的计算机,因为它可以同时处理多个任务,所以让这种阻塞的操作在后台执行,本体代码继续执行

这种同时执行多个函数的情况就叫做异步,关于异步有下面几种方式进行实现,各有优劣

回调函数

定义 : 回调函数(callbacks)即作为参数传递给那些在后台执行的其他函数

作用 : 当那些后台运行的代码结束,就调用 callbacks 函数,通知你工作已经完成,所以可以用回调函数实现异步

由于我们在后面很少单纯用回调来实现异步(这种写法比较古老而且有一些缺点)

这里展示一个简单的例子,利用setTimeout 来模拟阻塞函数

setTimeout(() => {
  console.log("hi");
}, 2000);
console.log("bye");

有关回调函数,还有一个比较有意思的回调地狱的情况会出现,即随着代码量的增大,代码可读性会很差,如下面

如需要对pizza订单处理,先是进行toppings选择pizza类型,然后order生成订单,然后pizza开始制作

因为每段执行都需要等待时间,所以采用异步进行实现

chooseToppings(function (toppings) {
  placeOrder(
    toppings,
    function (order) {
      collectOrder(
        order,
        function (pizza) {
          cookPizza(pizza);
        },
        failureCallback
      );
    },
    failureCallback
  );
}, failureCallback);

Promise

见证了上面的回调地狱,于是有了promise来解决这个问题

fetch("products.json")
  .then(function (response) {
    return response.json();
  })
  .then(function (json) {
    products = json;
    initialize();
  })
  .catch(function (err) {
    console.log("Fetch problem: " + err.message);
  });

这里的fetch() 返回一个 promise. 是表示异步操作完成或失败的对象。可以说,它代表了一种中间状态。 本质上,这是浏览器说“我保证尽快给您答复”的方式,因此得名“promise”。而在上面的代码中,跟在 promise 后面的是

  • 两个 then() 块。两者都包含一个回调函数,如果前一个操作成功,该函数将运行,并且每个回调都接收前一个成功操作的结果作为输入,因此可以继续对它执行其他操作。每个 .then()块返回另一个 promise,这意味着可以将多个.then()块链接到另一个块上,这样就可以依次执行多个异步操作。
  • 如果其中任何一个then()块失败,则在末尾运行catch()块——与同步try...catch类似,catch()提供了一个错误对象,可用来报告发生的错误类型。

Promise 对象本质上表示的是一系列操作的中间状态,或者说是未来某时刻一个操作完成或失败后返回的结果。Promise 并不保证操作在何时完成并返回结果,但是保证在当前操作成功后执行您对操作结果的处理代码,或在操作失败后,优雅地处理操作失败的情况。

对于上面的pizza订单,而用 Promise 我们可以这样优雅的实现

chooseToppings()
  .then((toppings) => placeOrder(toppings))
  .then((order) => collectOrder(order))
  .then((pizza) => cookPizza(pizza))
  .catch(failureCallback);

async await

asyncawait是在 ECMAScript 2017 中添加的 promises 的语法糖,使得异步代码更易于编写和后续阅读。

直接在要执行的函数前面加async和await

使用 async、await 会使你的代码看起来更像是同步代码,读起来也十分容易理解,因为他实际上就是在顺序执行,但是在等待 await 的时候并不会产生阻塞,影响其他渲染任务

下面时promise和async的对比

Promise

fetch("coffee.jpg")
  .then((response) => response.blob())
  .then((myBlob) => {
    let objectURL = URL.createObjectURL(myBlob);
    let image = document.createElement("img");
    image.src = objectURL;
    document.body.appendChild(image);
  })
  .catch((e) => {
    console.log(
      "There has been a problem with your fetch operation: " + e.message
    );
  });

async await

async function myFetch() {
  try {
    let response = await fetch("coffee.jpg");
    let myBlob = await response.blob();

    let objectURL = URL.createObjectURL(myBlob);
    let image = document.createElement("img");
    image.src = objectURL;
    document.body.appendChild(image);
  } catch (e) {
    console.log(e);
  }
}

myFetch();

模块化

ECMAScript 模块 | Node.js v20 文档 (nodejs.cn)

下面是导入/导出方式

import transform from './transform.js' /* default import */
import { var1 } from './consts.js' /* import a specific item */
import('http://example.com/example-module.js').then(() => {console.log('loaded')})
export const MODE = 'production' /* exported const */

TypeScript

简介

由微软开发的开源编程语言,它是 JavaScript 的超集,运行时编译器将其转化为JavaScript代码,再交由js引擎来执行

本质区别 : 添加了类型系统,让代码便于静态分析,同时支持更广泛的面向对象

更加深入的学习可以参考官方文档中文),在这里我们做简单介绍

运行环境

js安装运行环境,另外需要用npm(Node Package Manager)安装下面的两个包,在终端输入下面命令以配置运行环境

npm install -g ts-node  
npm install -g typescript

然后就可以按ctrl+alt+n运行ts代码了

const first: string = "Hello World";
console.log(first);

或者直接终端输入

ts-node ts/learn.ts#注意cd到工作目录

类型注解

直接使用:来标注类型

常见

//布尔值
let isDone: boolean = false;

//数字
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;

//字符串
let name: string = "bob";
name = "smith";
let sentence: string = `Hello, my name is ${name}.`;

//数组
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];

//元组,表示一个已知元素数量和类型的数组,各元素的类型不必相同
// Declare a tuple type
let x: [string, number];
// Initialize it
x = ["hello", 10]; // OK
// Initialize it incorrectly
x = [10, "hello"]; // Error

//枚举
enum Color {
  Red,
  Green,
  Blue,
}
let c: Color = Color.Green;

//Void,表示一个函数没有返回值
function warnUser(): void {
  console.log("This is my warning message");
}

//Null 和 Undefined
let u: undefined = undefined;
let n: null = null;
//他们是所有类型的子类型
// 这样不会报错
let num: number = undefined;

any

就是原生js的类型,如果没有声明类型,则会默认为any,以便于能兼容js

同时也用于为那些在编程阶段还不清楚类型的变量指定一个类型,比如来自用户输入或第三方代码库的内容。

下面给出指定函数类型的例子

// 完整,这里详细描述了函数类型
let myAdd: (x: number, y: number) => number = function (
    x: number,
    y: number
): number {
    return x + y;
};

//推断前面
let myAdd = function (x: number, y: number): number {
    return x + y;
};
//推断后面,这种写法比较喜欢
let myAdd: (x: number, y: number) => number = function (x, y) {
    return x + y;
};

console.log(myAdd(1, 2))

ts 也可以设定可选参数以及参数默认值,可选参数在参数后加?即可

let myAdd = function(x: number = 1, y?: number): number { ...};

联合类型

联合类型(Union Types)表示取值可以为多种类型中的一种。联合类型使用 | 分隔每个类型。

let myFavoriteNumber: string | number;
myFavoriteNumber = "seven";
myFavoriteNumber = 7;

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法,如下,不会根据访问的方法去确定类型

这种情况我们就可以使用类型断言

function getLength(something: string | number): number {
  return something.length;
}

//index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
//   Property 'length' does not exist on type 'number'.

类型断言

当我们知道我们的数据是哪个类型时

类型断言的两种方式为

let someValue: any = "hello world";

let strLength0: number = (someValue).length;
let strLength1: number = (<string>someValue).length;
let strLength2: number = (someValue as string).length;

console.log(strLength0); 

类型别名

我们使用 type 创建类型别名,即自定义类型,常用于联合类型

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;

接口

类似于结构体

interface Person {
  name: string;
  age?: number;//这里为可选类型
}

let tom: Person = {
  name: "Tom",
  age: 25,
};

let jack: Person = {
  name: "Jack",
};

泛型

这个概念和 C/C++里的模板比较相似,由于基础学习,所以不详细了解

template <typename T>
function identity<T>(arg: T): T {
  return arg;
}
let myIdentity: <T>(arg: T) => T = identity;

模块

任何声明(比如变量,函数,类,类型别名或接口,正则表达式)都能够通过添加export关键字来导出。

可以在声明的时候直接导出

export interface StringValidator {
  isAcceptable(s: string): boolean;
}

export const numberRegexp = /^[0-9]+$/;

export class ZipCodeValidator implements StringValidator {
  isAcceptable(s: string) {
    return s.length === 5 && numberRegexp.test(s);
  }
}

也可以在声明之后的任意位置导出,并且可以重命名

class ZipCodeValidator implements StringValidator {
  isAcceptable(s: string) {
    return s.length === 5 && numberRegexp.test(s);
  }
}
export { ZipCodeValidator };
export { ZipCodeValidator as mainValidator };

在导入的时候,可以直接导入,也可以进行重命名

import { ZipCodeValidator } from "./ZipCodeValidator";

import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";

每个模块都可以有一个default导出。 默认导出使用 default关键字标记;并且一个模块只能够有一个default导出。对于default模块在导入的时候不必加大括号,而且可以直接重命名

//OneTwoThree.ts
export default "123";

导入

import num from "./OneTwoThree";

Babel

简介

官方文档Babel 中文网

Babel是一个广泛使用的JavaScript编译器。它的主要作用是将高版本的JavaScript代码(通常是 ECMAScript 2015+ 标准)转换为向后兼容的低版本JavaScript代码,以确保在各种浏览器和环境中都能正确执行。

具体来说,Babel可以执行以下任务:

  1. 将ES6+代码转换为ES5代码: Babel最常用于将使用最新JavaScript标准(如箭头函数、模板字符串、解构赋值等)编写的代码转换为较旧版本的JavaScript,以确保在不支持最新标准的浏览器中也能正常运行。
  2. 处理JSX: Babel也可以处理JSX语法,将其转换为React.createElement调用,以便在不支持JSX的环境中使用React。
  3. 插件支持: Babel是高度可扩展的,允许用户通过插件自定义转换过程。这意味着你可以根据项目的需求添加或删除转换规则。
  4. 将其他语言编译为JavaScript: Babel的生态系统还支持将其他语言(如TypeScript、Flow等)编译为JavaScript。

使用Babel的主要优势之一是,它使开发者能够使用最新的JavaScript语法和功能,而无需担心在目标环境中的兼容性问题。开发者可以编写具有清晰、现代语法的代码,而Babel负责将其转换为更广泛支持的JavaScript版本。

使用

首先babel是不提供转换的,他是通过不同的插件来转换不同的语法

插件分为语法插件和转译插件,因为一般转译插件都包含对应语法插件,所以很少直接使用语法插件

基本

安装

yarn add --dev @babel/core @babel/cli #前者是核心库,后者是用命令行操控babel

使用

./node_modules/.bin/babel src --out-dir lib
#将当前目录src文件夹的js代码转换,也可以写成
npx babel src --out-dir lib

插件

添加插件分两步,

  1. 先将插件名添加到配置文件中(根目录下创建 .babelrc 或 babel.package.json,添加plugin字段)
  2. 使用npm install babel-plugin-xxx进行安装

preset

因为一个标准新的特性有很多,一个个添加很麻烦,所以我们可以使用babel为我们预制好的preset组合,如下面用ES6+的预制preset-env

我们可以通过先安装预设,然后添加到配置文件来实现

yarn add --dev @babel/preset-env
{
    "presets": [
        "preset1",
        "preset2",
        ["preset3", {options}]
	]
}

将TS转为JS

安装包

yarn add @babel/core @babel/cli @babel/preset-env @babel/preset-typescript @babel/node --dev
# @babel/core是Babel的核心库
# @babel/cli是Babel的命令行工具
# @babel/preset-env是一组将最新JavaScript语法转化的预设插件集
# @babel/preset-typescript是一组将TypeScript语法转化的预设插件集
# @babel/node可以应用所选的Babel工具并像node一样运行代码

修改配置文件

{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-typescript"
    ]
}

转换

npx babel src -d lib # -d是--out-dir的缩写