C#、JS/TS/UTS的最基础语法,包括了变量、常量、运算符、控制语句和作用域。我觉得大家应该可以在5分钟之内看完。。。what!开个玩笑!大家可以多看看作用域这节,还是费了点心思来缕清楚的。
一、程序开始
1.1 C#程序的开始
//Program.cs
//C#的代码都必须包含在类中,Program类是程序默认的入口类,Main方法是程序默认的入口方法
//关于类,详见类章节
public class Program
{
public string Country = "cn"; //类的成员之一:字段
public string Street{get;set} //类的成员之二:属性,本质上是方法
public void SayHi(){Console.WriteLine("Hi");} //类的成员之三:方法
//Main方法,也是类的成员之一,它比较特殊,是程序的入口方法
static void Main(string[] args)
{
SyaHi(); //调用函数
var a = 10; //定义变量
Console.WriteLine(a); //控制台打印变量a的值
//WriteLine()是Console类的静态方法
}
}
1.2 TS程序的开始(JS和UTS一样)
//如果没有工程化,我们要创建立即执行函数来模拟程序入口
//函数式编程,可以直接在全局作用域中定义任意元素
()=>{
let x = 1; //定义变量
console.log(x); //调用console对象的方法log(),控制台打印变量x的值
function func(){console.log("hi");} //定义函数
func(); //调用函数
}();
二、命名规范
2.1 C#命名规范
- 类/枚举/结构体/委托:大驼峰
- 接口:I大驼峰
- 方法/属性/事件:大驼峰
- 字段:_小驼峰
- 变量/参数:小驼峰
- 明确使用访问修饰词,如public、private、protect、internal,默认是private
2.2 TS命名规范(JS和UTS一样)
- 类/枚举:大驼峰
- 接口:I大驼峰
- 方法/属性:小驼峰
- 变量/参数:小驼峰
- 除非必要,不用加访问修饰词,默认为public,按需使用private和protect
三、定义变量和常量
3.1 C#中定义变量和常量
//1、定义变量========================================================================
//C#中,变量必须在方法内部定义和使用,不存在脱离类的全局变量
public class Program
{
static void Main(string[] args)
{
int a1; //声明变量
a1 = 1; //变量赋值
int a2 = 2; //声明变量时初始化
int a3 = a2*2; //初始化时,可以使用表达式进行运算
/*
报错,使用变量a4时,未被赋值。变量使用前,必须已赋值
int a4;
int a5 = a4*3;
*/
var a6 = 1; //使用var定义,类型推断,编译器自动推断a5类型为int
//var a6; //报错,使用自动推荐功能,申明变量时必须初始化
}
}
//2、定义编译时常量==================================================================
//编译时,比如在编写代码时、重新生成时,代码编辑器会自动调用编译器对代码进行检查和编译
//运行时,比如调试状态中运行的程序、项目上线后运行的程序
public class Program
{
static void Main(string[] args)
{
//const int a; //报错,const定义编译时常量,必须初始化赋值
const int a = 1; //定义方法中的本地常量
//a = 2; //报错,常量赋值后不能再改变
}
}
//类中的常量字段,详见《类》
3.2 TS中定义变量和常量(UTS和TS一样)
//1、定义变量========================================================================
//使用let,不要使用var
let a = 1 //自动推断类型,指定类型方式:let a:number = 1
a = 2 //通过
a = "hello" //报错,申明是自动推导为number类型,不能再赋值为string类型
let b:number //如果申明变量时没有初始化,则必须确定类型
//使用let后,不存在变量提升,它们定义的变量有明确的作用域。不要再使用var。
//var a //下面代码中,a使用了var,变量定义会提升到全局作用域,重复申明,报错
{
let a = 1 //通过
var a = 1 //报错
}
//2、const定义编译时常量=============================================================
const a = 1
a = 2 //报错,常量不能修改值
const b = {name:"mc",age:28}; b.age = 40; //通过,为什么?详见下节的类型概述
class Program{
//public const a2:number = 1; //报错,不能在类中使用const创建数据成员
main():number{
const a2:number = 2; //可以在方法中定义局部常量
return a1+a2;
}
}
//类中的常量属性,详见《类》
四、运算符(C#和TS几乎一样)
4.1 基本的运算符(赋值、比较、算术、逻辑、三元)
//1、赋值运算符=====================================================================
x = y x = y
x += y x = x + y
x -= y x = x - y
x *= y x = x * y
x /= y x = x / y
x %= y x = x % y //取余
//2、比较运算符,返回true或false=====================================================
//值类型比较值,引用类型比较地址
var1==var2
var1!=var2
//只能用于数值类型
var1>var2
var1>=var2
var1<var2
var1<=var2
//***注意JS和TS中的双等号,等式两边会自动转换类型后再进行比较,三等号是全比较
1 == "1" //true,自动转换类型了
1 === "1" //false,不会自动转换类型,即仅值要一样,类型也要一样
//***UTS中,强类型约束,两等号必须类型和值都一样,三等号只用于判断引用类型的地址
//3、算数运算符=====================================================================
++ //有前后之分。(let a=++b; b先自增,再赋值给a) (let c=b++; b先赋值给c,再自增)
--
+
-
*
/
% //取余。TS例子:let a = 10%3;(a=1)
//4、逻辑运算符=====================================================================
//JS和TS中condition会自动转换,0、""、null、undefined、false等为假,其它为真
//UTS不会自动转换,condition必须为boolean类型。强类型语言C#,也是如此
condition1 && condition2 //逻辑与
condition1 || condition2 //逻辑或
!condition1 //逻辑非
//5、三元运算符?:===================================================================
const status = age >= 18 ? "adult" : "minor"
4.2 字符串运算符
//1、字符串连接======================================================================
"my " + "string";
//转义字符
\\ //反斜杠本身
\' //单引号
\" //双引号
\n //换行
\r //回车
\t //制表符
\b //退格
\f //换页
//2、C#的模板字符串(插值)===========================================================
string name = "mc";
int age = 28;
string greeting = $"Hello, 我是{name},我今年{age}岁了";
//C#的原始字符串,带来JS中``的爽感,不用再到处写转义符了(C#11新增功能)
var jsonStr = """{"id":1, "url":"/static/my.jpg", "name":"MC"}""";
//或者多行(多行时,首尾的""",必须独占一行)
var jsonStr = """
{
"id":1,
"url":"/static/my.jpg",
"name":"MC"
}
""";
//和模板字符串一起使用,注意是$$,{{}}。如果里面
var jsonStr = $$"""
{
"id":1,
"url":"/static/my.jpg",
"name":{{name}}
}
""";
//插值变量外面需要有{}符号,输出you are at:{...},{...}
var location = $$"""
You are at:{{{Longitude}}, {{Latitude}}}
""";
//之前还有一个@原义标识符,有了原始字符串后,可以丢弃
//3、JS、TS和UTS的模板字符串=========================================================
const name = 'mc';
const age = 28;
const greeting = `Hello, 我是${name},我今年${age}岁了`;
4.3 展开运算符
//1、C#中的展开运算符和集合字面量定义,在C#11中带来,比较新,使用上有限制===============
int[] a = [1, 2, 3, 4, 5, 6, 7, 8]; //创建数组
List<string> b = ["one", "two", "three"]; //创建集合
Span<char> c = ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i']; //创建span
int[][] twoD = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; //创建二维数组
//展开运算符
int[] row0 = [1, 2, 3];
int[] row1 = [4, 5, 6];
int[] row2 = [7, 8, 9];
int[] single = [..row0, ..row1, ..row2]; //创建数组
//2、JS、TS和UTS中的展开运算符=======================================================
const array1 = ['a', 'b'];
const array2 = ['c', ...array1, 'd']; //将数组展开
myFunction('a', ...['b', 'c']); //函数调用,展开参数
this.dataList.push(...res.data); //操作数组
//函数的剩余参数
function fn(str: string, ...args: string[]) {
console.log(str, args)
}
//还可以操作对象
const person = { name: "John", age: 30, city: "New York" };
const updatedPerson = { age: 35, occupation: "Software Engineer" };
const updatedUser = {...person, ...updatedPerson};
4.4 可空类型运算符
//1、C#定义可空类型变量==============================================================
string? a = null //?紧跟类型
//2、TS定义可空类型变量==============================================================
let a:string|null = null
let a?:string = null //?紧跟变量
//3、C#和TS中,使用方式一样==========================================================
//使用方法A,if判断
if (a != null) {}
//使用方法B:成员调用,?.运算符,可以连续使用
console.log(a?.length)
bob?.department?.head?.name
//使用方法C:空值合并,??运算符
let b = a??"hello" //如果a不为空,则直接赋值给b;如果为空,则将默认值"hello"赋值给b
//使用方法D:非空断言,!运算符,明确知道有值时使用
const l = a!.length
4.5 类型断言运算符
//C#中的类型断言,复杂些,涉及多态====================================================
class Parent{}
class Child : Parent
{
public void SomeMethod() { }
}
class Program
{
static void Main()
{
//变量obj是Parent类型,但可以指向Child类型的实例对象,详见《多态》章节
Parent obj = new Child();
// 尝试进行类型断言
Child childObj = obj as Child;
if (childObj!= null)
{
childObj.SomeMethod();
}
}
}
//2、JS不存在类型断言,以下为TS/UTS中的类型断言=======================================
const a: any = 'a' //申明时不确定,赋值后可以确定了
a as string //所以可以断言为string,之后可以当成string类型使用
const a: string | null = 'a'
a as string
1.0 as Double
1.0 as number
type t = {id:number}; {"id":1} as t
{"id":1} as UTSJSONObject; //UTSJSONObject是UTS中的内置类型,是字面量对象的默认类型
4.6 类型操作运算符
//C#中的类型操作,typeof和GetType()==================================================
//C#的typeof和GetType主要用于返回类型的元数据,用于反射操作,作用更大
//typeof,参数为类型,返回类型的元数据,编译期确定
typeof(int);
class Student{}
typeof(Student);
//GetType是所有实例对象都有的一个方法,在运行期获取实例对象所属的类型元数据
var s1 = new Student();
s1.GetType();
//2、JS、TS和UTS中的类型操作,typeof和instanceof======================================
//typeof用于返回变量或表达式的类型,UTS中可以返回kotlin或swift的原生类型
let a = 10.0
let b: Double = 3.14 //原生类型
let c: Int = 2 //原生类型
typeof a == "number" //true
typeof b == "Double" //true
typeof c == "Int" //true
//typeof的结果,即可用于判断实例,也可用于判断类,但判断类型没有实际意义
boolean类型对象 > boolean
number类型对象 > number
string类型对象 > string
null > object
any > 运行时的实际类型
function > function //function是一种特殊类型
class类名 > function
class实例 > object
interface接口名 > undefined
type类型别名 > object
内置对象,如Array、Data等 > object
平台专有的数值和字符串类型 > 相对应的平台数据类型 (UTS适用)
//instanceof用于判断对象的原型链上是否有该类实例
//boolean,number,string,null,any等基本类型不能使用
//内置对象,如Array、Data等 > 具体的内置类型
//自定义类型 > 具体定义的类型或其父类
function fn(obj: any) {
if (obj instanceof Date) {
// ...
}
}
class MyClass{}
type MyType = {name:string}
let myClass = new MyClass()
let myType:MyType = {name:"MC"}
console.log(myClass instanceof MyClass) //true
console.log(myType instanceof MyType) //true
五、控制流程(C#和TS几乎一样,例子为TS)
5.1 条件分支
//1、if=============================================================================
if (condition_1) {
// statement_1;
} else if (condition_2) {
// statement_2;
} else {
// statement_last;
}
//2、switch=========================================================================
switch (expression) {
case label_1:
// statements_1
break;
case label_2:
// statements_2
break;
default:
// statements_def
break;
}
5.2 循环
//1、for============================================================================
for (let i = 0; i < 10; i++) {
//...
}
//Array等集合类型可以使用集合类型的实例方法forEach()
//2、while==========================================================================
let n = 0;
let x = 0;
while (n < 3) {
n++;
x+=n;
}
//3、do while=======================================================================
let i = 0;
do {
i += 1;
} while (i < 10);
//4、break/continue=================================================================
//break,跳出当前整个循环
let x = 0;
while (true) {
x++;
if (x > 5) {
break;
}
} // 输出 6
//continue,跳出当次循环,继续下一个循环
for (let i = 0; i < 10; i++) {
if (i > 5) {
console.log(i)
continue; // continue 后,本次循环结束,i+1后开始下一次循环。
// 这里的代码不会被执行,因为上一行 continue 了
}
}
//***5、C#中的foreach循环和TS中的forof/forEach/forin=================================
//C#中的foreach--------------------------
int[] numbers = { 10, 20, 30, 40, 50 };
foreach (int num in numbers) //int类型定义,可以使用var代替,类型推断
{
Console.WriteLine(num);
}
//TS中的for...of,遍历可迭代对象-----------------
let numbers: number[] = [10, 20, 30, 40, 50];
for (let num of nmbers) {
console.log(num);
}
//TS中的forEach,可迭代对象的实例方法-----------
let numbers: number[] = [10, 20, 30, 40, 50];
numbers.forEach(num => {
console.log(num);
});
//TS中的for...in,遍历对象-------------------
const person: {name:string;age:number} = {
name: "John",
age: 30
};
for (const key in person) {
console.log(`Key: ${key}, Value: ${person[key]}`);
}
5.3 异常
//抛出异常
throw new Error("Hi There!");
//捕获异常
try {
// 检测的代码
} catch (e: Error) {
// 捕获到异常后执行
} finally {
// 无论是否捕获到异常,都会执行
}
六、作用域
2.1 C#中的作用域
作用域指当前代码执行的环境。这个环境包括硬件和软件两个层面,硬件通常指机算机划给程序运行的内存区域,软件是指程序的运行时环境。C#是类型化的语言,所有代码都必须包含在类中,作用域划分清晰,符号{}定义了作用域,作用域之间的嵌套关系也很明确,所以在C#中很少有作用域的问题。
public class Program
{ //程序开始,在内存中创建一个作用域A
public string Country = "cn"; //字段Country在作用域A内
static void Main(string[] args)
{ //方法Main开始,创建一个作用域B
//在new Student()创建实例时,也会创建一个作用域B1,执行完并将对象赋值给s1后销毁
var student = new Student("mc",28);
//Console.WriteLine(x); //报错,不能访问嵌套的内部作用域变量x
{ //创建作用域B2
var x = 1; //变量x在作用域B2内
//可以访问外部作用域定义的变量student
//嵌套作用域中,内层作用域可以使用外层作用域的变量,反过来则不行
//内层作用域使用变量时,会先从当前作用域找,如果找不到,则向外逐层开始找
//如果到最外层还找不到,则报错
Console.WriteLine(student.Name);
} //销毁作用域B2
} //方法Main结束,作用域B销毁
} //程序结束,作用域A销毁
public class Student
{
public string Name;
public int Age;
public Shape(string name, int age)
{
//Shape()是构造函数,构造函数内的this,指向调用构造函数时创建出来的实例对象
this.Name = name;
this.Age = age
}
}
2.2 TS中的作用域(JS和UTS一样)
- TS中存在全局作用域(程序入口)、函数作用域(函数/模块)和块级作用域({}内)
- 作用域是静态的,编写代码时就确定,但代码运行时有一个执行上下文,这个是动态的
- 执行上下文可以认为是代码执行时所在的对象,this指向的就是这个对象
- 全局执行上下文,浏览器端是window,node端是global,存在于整个程序的生命周期
- 函数执行上下文,函数运行开始前创建,函数运行结束后销毁
- 模块化后,包含export的文件就是一个模块,模块作用域本质上是一个函数作用域
- 再说this指向问题,也即执行上下文的问题:
- 在全局环境下,this指向全局对象
- 普通函数的this是动态的,指向调用该函数的对象,call()、apply()和bind()方法可以动态变换this
- 箭头函数的this是静态的,指定定义时所在的对象,call()、apply()和bind()方法不能动态变换this
- 在使用类(构造函数)创建对象实例时,this指向新创建的实例对象,和C#表现一致
//假设当前运行环境为浏览器,当前文件为程序入口
//程序开始,创建全局作用域A,全局执行上下文为window
let a = 1; //变量a在全局作用域内,运行在全局上下文window中
console.log(this); //log方法在全局上下文中执行,this指向window
//函数func在全局作用域A内,此处代码只是定义,没有执行,没有函数执行上下文
function func(){ //函数开始执行前,创建函数作用域B(调用函数时才会创建)
let x = 1; //变量x在作用域B内
console.log(this) //函数运行时才有this,此例指向window,定义时没有执行上下文
var y = 1; //使用var定义,变量y会提升到全局作用域,挂在window下,易出错,不要用
{ //创建块级作用域B1
var z = 1; //变量z在块级作用域B1内
console.log(x); //可以访问外部作用域定义的变量x
console.log(this); //块级作用域的this继承自所在的函数
} //销毁块级作用域B1
} //销毁函数作用域B
func(); //调用函数,创建函数作用域B,在全局对象中调用,this指向window
//this是window
const arrowFn = () => console.log(this);
arrowFn();
//this是obj,call()方法不能变更this
const obj = {
arrowFn: () => console.log(this)
};
const newContext = { name: 'New Context' };
obj.arrowFn.call(newContext);