WEB3全栈开发——面试专业技能点P3JavaScript / TypeScript
目录
一、ES6+ 语法
1. let 和 const 变量声明
2. 箭头函数 () => {}
3. 模板字符串
4. 解构赋值
5. 默认参数
6. 展开运算符 ...
7. Promise 和 async/await 异步处理
8. 类(class)和继承
9. 模块导入导出 import 和 export
10. Symbol 类型
11. Map 和 Set 数据结构
12. 生成器函数 function*
13. 可选链操作符 ?.
14. 空值合并运算符 ??
二、Javascript闭包
解释:
三、原型链
1.javascript原型链(经典函数构造器 + prototype)
解释:
2.TypeScript 版本(class 语法)
解释:
3.总结
三、作用域链
作用域链简介
1. JavaScript 版本示例
解释:
2. TypeScript 版本示例
解释:
总结
四、如何使用 TypeScript 构建类型安全的 DApp 项目
1. 使用 TypeScript 定义智能合约接口类型
2. 使用 web3.js 或 ethers.js 连接以太坊并调用合约(示例用 ethers.js)
3. 使用 TypeScript 定义事件类型并监听合约事件
4. 使用类型安全的 ABI 绑定工具(如 TypeChain)
5. 总结
五、泛型
概念
1. 泛型函数示例
解释:
2. 泛型接口示例
解释:
3. 泛型类示例
解释:
4. 泛型约束示例
解释:
六、接口
概念
1. TypeScript 接口示例
解释:
2. JavaScript 中模拟接口(无类型检查)
解释:
应用场景
七、装饰器
📌 一、装饰器是什么?
🧪 二、类装饰器示例
✅ 解释:
📦 三、方法装饰器示例
✅ 解释:
🏷️ 四、属性装饰器示例
✅ 解释:
📌 五、装饰器的应用场景
一、ES6+ 语法
“ES6+ 语法”指的是 ECMAScript 2015(即 ES6)及其之后版本的 JavaScript 语言新特性和语法。简单说,就是现代 JavaScript 的新语法和功能。
常见 ES6+ 语法包括:
-
let
和const
变量声明 -
箭头函数
() => {}
-
模板字符串
`Hello ${name}`
-
解构赋值
-
默认参数
-
展开运算符
...
-
Promise 和 async/await 异步处理
-
类(class)和继承
-
模块导入导出
import
和export
-
Symbol 类型
-
Map 和 Set 数据结构
-
生成器函数
function*
-
可选链操作符
?.
-
空值合并运算符
??
1. let
和 const
变量声明
let a = 10; // 可变变量
const b = 20; // 常量,不能重新赋值
// b = 30; // 会报错
解释: let
声明的变量有块级作用域,const
声明常量,值不能变。
2. 箭头函数 () => {}
const add = (x, y) => x + y;
console.log(add(2, 3)); // 5
解释: 箭头函数写法简洁,并且不绑定自己的 this
。
3. 模板字符串
const name = 'Alice';
console.log(`Hello, ${name}!`); // Hello, Alice!
解释: 用反引号 `
包裹,可以直接嵌入变量和表达式。
4. 解构赋值
const person = {name: 'Bob', age: 25};
const {name, age} = person;
console.log(name, age); // Bob 25const arr = [1, 2, 3];
const [first, second] = arr;
console.log(first, second); // 1 2
解释: 从对象或数组中快速提取值赋给变量。
5. 默认参数
function greet(name = 'Guest') {console.log(`Hello, ${name}`);
}
greet(); // Hello, Guest
greet('Alice'); // Hello, Alice
解释: 函数参数可以设置默认值。
6. 展开运算符 ...
const arr1 = [1, 2];
const arr2 = [...arr1, 3, 4];
console.log(arr2); // [1, 2, 3, 4]const obj1 = {a: 1, b: 2};
const obj2 = {...obj1, c: 3};
console.log(obj2); // {a:1, b:2, c:3}
解释: 展开数组或对象,合并或复制。
7. Promise 和 async/await 异步处理
// Promise
function fetchData() {return new Promise(resolve => {setTimeout(() => resolve('data'), 1000);});
}fetchData().then(data => console.log(data)); // data// async/await
async function asyncFetch() {const data = await fetchData();console.log(data);
}
asyncFetch(); // data
解释: 用 Promise 处理异步,async/await 语法更简洁。
8. 类(class)和继承
class Animal {constructor(name) {this.name = name;}speak() {console.log(`${this.name} makes a noise.`);}
}class Dog extends Animal {speak() {console.log(`${this.name} barks.`);}
}const d = new Dog('Rex');
d.speak(); // Rex barks.
解释: ES6 引入类语法,更接近传统面向对象。
9. 模块导入导出 import
和 export
// utils.js
export function sum(x, y) {return x + y;
}// main.js
import {sum} from './utils.js';
console.log(sum(2, 3)); // 5
解释: 支持模块化开发,导入导出代码片段。
10. Symbol 类型
const sym = Symbol('desc');
const obj = {};
obj[sym] = 'value';
console.log(obj[sym]); // value
解释: Symbol 是一种独一无二的标识符,常用作对象属性键,避免命名冲突。
11. Map 和 Set 数据结构
const map = new Map();
map.set('a', 1);
console.log(map.get('a')); // 1const set = new Set([1, 2, 2, 3]);
console.log(set); // Set {1, 2, 3}
解释: Map 是键值对集合,Set 是无重复值的集合。
12. 生成器函数 function*
function* gen() {yield 1;yield 2;yield 3;
}const g = gen();
console.log(g.next().value); // 1
console.log(g.next().value); // 2
解释: 生成器可暂停执行,逐步产出值。
13. 可选链操作符 ?.
const obj = {a: {b: 10}};
console.log(obj.a?.b); // 10
console.log(obj.x?.b); // undefined 不报错
解释: 访问嵌套属性时安全,不会因中间值为 null 或 undefined 报错。
14. 空值合并运算符 ??
const foo = null ?? 'default';
console.log(foo); // defaultconst bar = 0 ?? 42;
console.log(bar); // 0
解释: 当左侧是 null 或 undefined 时,返回右侧值。
二、Javascript闭包
闭包(Closure)示例
function outer() {let count = 0;return function inner() {count++;console.log(count);}
}const counter = outer();
counter(); // 1
counter(); // 2
counter(); // 3
解释:
-
函数
outer
返回了一个内部函数inner
。 -
inner
函数可以访问outer
的变量count
,即使outer
已经执行完毕。 -
这种函数和其访问的变量环境形成的组合,就叫闭包。
-
闭包常用来实现私有变量和数据封装。
三、原型链
1.javascript原型链(经典函数构造器 + prototype)
function Person(name) {this.name = name;
}Person.prototype.sayHello = function() {console.log(`Hello, my name is ${this.name}`);
};const alice = new Person('Alice');
alice.sayHello(); // Hello, my name is Aliceconsole.log(alice.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
解释:
-
每个对象都有一个内部属性
[[Prototype]]
(常用__proto__
访问),指向它的原型对象。 -
当访问对象的属性或方法时,如果自身没有,会沿着
[[Prototype]]
一层层往上找,这个查找链就是原型链。 -
上面例子中,
alice
访问sayHello
方法时没在自身属性里找到,就去它的原型对象Person.prototype
查找。 -
Person.prototype
的原型是Object.prototype
,这构成了原型链的多层关系。 -
Object.prototype
的原型是null
,链条终点。
2.TypeScript 版本(class 语法)
class Person {name: string;constructor(name: string) {this.name = name;}sayHello() {console.log(`Hello, my name is ${this.name}`);}
}const alice = new Person('Alice');
alice.sayHello(); // Hello, my name is Aliceconsole.log(Object.getPrototypeOf(alice) === Person.prototype); // true
console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null
解释:
-
TypeScript 使用
class
关键字声明类,语法更现代,代码更清晰。 -
sayHello
是类的方法,实际挂载在Person.prototype
上,实例通过原型链访问。 -
Object.getPrototypeOf()
用来获取对象的原型,效果和__proto__
类似,但更标准安全。 -
原型链关系和JavaScript版本完全一样:实例原型 → 类的
prototype
→Object.prototype
→null
。
3.总结
-
JavaScript版本用构造函数和显式原型,适合传统理解原型链机制。
-
TypeScript版本用类语法,更符合现代代码风格,本质上还是基于JavaScript原型链实现。
三、作用域链
作用域链简介
-
作用域链是当访问一个变量时,JavaScript 引擎从当前作用域开始,逐级向上查找变量的过程。
-
这保证了内层函数可以访问外层函数的变量。
-
作用域链和原型链不同,作用域链是关于变量查找的执行环境机制。
1. JavaScript 版本示例
function outer() {const a = 10;function inner() {const b = 20;console.log(a + b); // 30}inner();
}outer();
解释:
-
inner
函数内部访问变量a
,它不在自身作用域中。 -
于是它查找外层作用域
outer
,找到了变量a
,然后计算并输出结果。 -
这就是作用域链:
inner
→outer
→ 全局。
2. TypeScript 版本示例
TypeScript 的作用域链机制和 JavaScript 一样:
function outer() {const a: number = 10;function inner() {const b: number = 20;console.log(a + b); // 30}inner();
}outer();
解释:
-
语法和JavaScript几乎一致,只是变量类型显式声明了。
-
作用域链机制完全相同,内层函数能访问外层函数的变量。
总结
-
作用域链保证了变量从内层到外层逐级查找。
-
它是运行时上下文环境的一部分,和闭包密切相关。
-
JavaScript 和 TypeScript 的作用域链规则一致,TS 只是加了类型。
四、如何使用 TypeScript 构建类型安全的 DApp 项目
下面给你关于“使用 TypeScript 构建类型安全的 DApp(去中心化应用)项目”的几个核心知识点,附带简短代码示例和解释。
1. 使用 TypeScript 定义智能合约接口类型
// 定义智能合约函数的接口
interface MyContract {methods: {balanceOf(address: string): { call(): Promise<string> };transfer(to: string, amount: string): { send(): Promise<void> };};
}
解释:
通过接口定义智能合约方法,确保调用时参数类型和返回类型明确,减少错误。
2. 使用 web3.js 或 ethers.js 连接以太坊并调用合约(示例用 ethers.js)
import { ethers } from 'ethers';async function getBalance(contract: MyContract, address: string): Promise<string> {const balance = await contract.methods.balanceOf(address).call();return balance;
}
解释:
函数参数用类型接口约束 contract
,保证传入合约实例符合预期方法,address
是字符串。
3. 使用 TypeScript 定义事件类型并监听合约事件
interface TransferEvent {from: string;to: string;value: string;
}contract.on('Transfer', (from: string, to: string, value: string) => {const event: TransferEvent = { from, to, value };console.log('Transfer event:', event);
});
解释:
定义事件结构接口,事件监听回调参数用类型标注,方便后续类型检查和自动补全。
4. 使用类型安全的 ABI 绑定工具(如 TypeChain)
// 生成的合约类型(伪代码)
import { MyContract } from './types';const contract: MyContract = getContractInstance();const result = await contract.balanceOf('0x123...');
解释:
TypeChain 等工具根据合约 ABI 自动生成 TypeScript 类型,调用合约更安全,减少运行时错误。
5. 总结
-
用 TypeScript 接口和类型定义合约方法和事件,保证调用和监听的类型安全。
-
使用 ethers.js 或 web3.js 结合类型定义调用智能合约。
-
通过 TypeChain 等工具生成合约类型代码,减少手写错误。
五、泛型
概念
泛型可以让函数、类或接口在使用时指定类型,而不是在定义时就固定死,提高了代码复用性和类型安全。
JavaScript 本身没有泛型,泛型是 TypeScript(以及其他静态类型语言)提供的类型系统特性,用来增强代码的类型安全和复用性。
简单来说:
-
JavaScript 是动态类型语言,变量和函数的参数类型在运行时确定,没有静态类型检查。
-
TypeScript 在 JavaScript 基础上加了类型系统,其中就包括泛型,可以在编译阶段帮你检查类型,避免运行时错误。
所以,泛型是 TypeScript 的特色,JavaScript 没有对应的语法和概念。
1. 泛型函数示例
function identity<T>(arg: T): T {return arg;
}const str = identity<string>('hello'); // str 类型是 string
const num = identity<number>(123); // num 类型是 number
解释:
-
identity
是一个泛型函数,<T>
是类型参数,代表调用时传入的具体类型。 -
传入参数和返回值类型都与
T
一致,调用时指定类型,保证类型安全且复用性强。
2. 泛型接口示例
interface Box<T> {value: T;
}const box1: Box<string> = { value: 'hello' };
const box2: Box<number> = { value: 100 };
解释:
-
Box
是一个泛型接口,成员value
的类型由外部指定。 -
方便用同一个接口定义不同类型的对象。
3. 泛型类示例
class Stack<T> {private items: T[] = [];push(item: T) {this.items.push(item);}pop(): T | undefined {return this.items.pop();}
}const stack = new Stack<number>();
stack.push(10);
console.log(stack.pop()); // 10
解释:
-
泛型类
Stack
支持存放任何类型的元素,且保证类型一致性。 -
实例化时指定具体类型,保证操作时类型安全。
4. 泛型约束示例
interface Lengthwise {length: number;
}function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length);return arg;
}loggingIdentity('hello'); // 输出 5
loggingIdentity([1, 2, 3]); // 输出 3
// loggingIdentity(123); // 报错,number 没有 length 属性
解释:
-
泛型约束保证传入的类型必须有
length
属性。 -
防止传入不符合约束的类型。
六、接口
概念
接口用于定义对象的结构,让代码更规范,可扩展性更好。
1. TypeScript 接口示例
interface User {id: number;name: string;email?: string; // 可选属性
}function greet(user: User) {console.log(`Hello, ${user.name}`);
}const user1 = { id: 1, name: 'Alice' };
greet(user1);
解释:
-
interface User
定义了一个结构类型,规定对象必须有id
和name
,email
可选。 -
函数
greet
形参要求是User
类型,确保传入对象符合接口。
2. JavaScript 中模拟接口(无类型检查)
JavaScript 没有接口,但你可以用约定或运行时检查实现类似效果:
function greet(user) {if (typeof user.id !== 'number' || typeof user.name !== 'string') {throw new Error('Invalid user object');}console.log(`Hello, ${user.name}`);
}const user1 = { id: 1, name: 'Alice' };
greet(user1);
解释:
-
通过函数内部手动检查对象属性类型,保证参数符合预期结构。
-
但没有编译时类型检查,容易出错且不够方便。
总结:
-
TypeScript接口:静态类型检查,定义对象结构,是编译时用来提升代码健壮性的工具。
-
JavaScript:无接口概念,只能靠编码习惯和运行时检测保证对象结构。
应用场景
-
API 数据模型定义
-
前后端数据类型统一
-
高度协作项目中约定字段结构
七、装饰器
下面是 TypeScript 中的 装饰器(Decorator) 的讲解、使用条件、代码示例和解释。
📌 一、装饰器是什么?
装饰器是对类、方法、属性或参数的增强,是元编程的一种形式。需要在 tsconfig.json
中开启 experimentalDecorators
。
装饰器是 一种特殊的语法,用于 修改类、类方法、属性或参数的行为。它本质上是一个函数。
装饰器是 TypeScript 的高级功能之一,需要在 tsconfig.json
中启用:
{"compilerOptions": {"experimentalDecorators": true}
}
🧪 二、类装饰器示例
function Logger(constructor: Function) {console.log('Class decorated:', constructor.name);
}@Logger
class User {constructor(public name: string) {}
}
✅ 解释:
-
@Logger
是一个类装饰器。 -
它接收构造函数作为参数,在类定义时执行。
-
装饰器不会改变类本身行为,但可以扩展、增强或者记录日志。
📦 三、方法装饰器示例
function LogMethod(target: any,propertyKey: string,descriptor: PropertyDescriptor
) {const original = descriptor.value;descriptor.value = function (...args: any[]) {console.log(`Method ${propertyKey} called with`, args);return original.apply(this, args);};
}class MathTool {@LogMethodadd(a: number, b: number) {return a + b;}
}const tool = new MathTool();
tool.add(2, 3); // 控制台打印日志
✅ 解释:
-
@LogMethod
修改add
方法,使其在执行前打印参数。 -
可以用于日志记录、性能分析、权限验证等。
🏷️ 四、属性装饰器示例
function ReadOnly(target: any, propertyKey: string) {Object.defineProperty(target, propertyKey, {writable: false,});
}class Person {@ReadOnlyname = 'Alice';
}const p = new Person();
// p.name = 'Bob'; // ❌ 会失败(只读)
✅ 解释:
-
@ReadOnly
将属性设置为只读。
📌 五、装饰器的应用场景
-
日志打印(如方法调用参数)
-
权限控制
-
数据校验
-
自动绑定(如 Vue、NestJS 中常见用法)
-
AOP(面向切面编程)
-
NestJS 控制器、服务模块增强
-
Web3 签名校验封装