本文主要介绍了一下js和ts的基础语法,为前端开发zuo
更详细的 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
(数字)
NaN
(Not a Number 的缩写),如果把 NaN
作为参数进行任何数学运算,结果也会是 NaN
。NaN
如果通过 ==
、 !=
、 ===
、以及 !==
与其他任何值比较都将不相等 – 包括与其他 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
(对象)
null
(空)
undefined
(未定义)
可以看到函数和数组也属于对象
JavaScript 有三种声明变量的方式。
如果声明了一个变量却没有对其赋值,那么这个变量的类型就是 undefined
const
很明显是一个常量,他是只读的,而let
与var
的主要区别在于,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 循环:
for (let value of array) {
// do something with value
}
for
…in
:
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来解决这个问题
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
是在 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 */
由微软开发的开源编程语言,它是 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;
就是原生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是一个广泛使用的JavaScript编译器。它的主要作用是将高版本的JavaScript代码(通常是 ECMAScript 2015+ 标准)转换为向后兼容的低版本JavaScript代码,以确保在各种浏览器和环境中都能正确执行。
具体来说,Babel可以执行以下任务:
使用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
添加插件分两步,
npm install babel-plugin-xxx
进行安装因为一个标准新的特性有很多,一个个添加很麻烦,所以我们可以使用babel为我们预制好的preset组合,如下面用ES6+的预制preset-env
我们可以通过先安装预设,然后添加到配置文件来实现
yarn add --dev @babel/preset-env
{
"presets": [
"preset1",
"preset2",
["preset3", {options}]
]
}
安装包
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的缩写