45个每个开发人员都应该知道的JavaScript超级技巧

JavaScript是一种应用场景多且功能强大的语言,对于现代Web开发至关重要。以下是一些超级技巧,它们将帮助你成为更高效、更有效的JavaScript开发人员,每个技巧都有详细的解释和示例。

1.使用`let`和`const`代替`var`

问题:`var`具有函数作用域,这可能导致错误和不可预测的行为。

解决方案:使用具有块作用域的`let`和`const`。

letcount=0;constPI=3.14;

2.默认参数

问题:如果没有提供参数,函数可能会失败。

解决方案:使用默认参数设置后备值。

functiongreet(name='Guest'){return`Hello,${name}!`;}console.log(greet());//"Hello,Guest!"

默认参数确保函数具有合理的默认值,从而防止错误并使代码更加健壮。

3.模板文字

问题:字符串连接可能很麻烦且容易出错。

解决方案:使用模板文字进行更清晰、更易读的字符串插值。

constname='John';constgreeting=`Hello,${name}!`;console.log(greeting);//"Hello,John!"

模板文字使创建带有嵌入表达式和多行字符串的字符串变得更加容易。

4.解构赋值

问题:从对象和数组中提取值可能非常冗长。

解决方案:使用解构赋值更简洁地提取值。

constuser={name:'Jane',age:25};const{name,age}=user;console.log(name,age);//"Jane"25

解构赋值允许您轻松地将对象中的属性和数组中的元素提取到不同的变量中。

5.箭头函数

问题:传统函数表达式可能很冗长,并且不会在词汇上绑定“this”。

解决方案:使用箭头函数来实现更短的语法和词汇“this”。

constadd=(a,b)=>a+b;console.log(add(2,3));//5

箭头函数为函数表达式提供了简洁的语法,并确保`this`在词汇上是绑定的。

6.扩展运算符

问题:组合数组或对象可能很麻烦。

解决方案:使用扩展运算符可以轻松组合数组和对象。

constarr1=[1,2,3];constarr2=[4,5,6];constcombined=[…arr1,…arr2];console.log(combined);//[1,2,3,4,5,6]

扩展运算符允许您将一个数组或对象的元素扩展到另一个数组或对象中。

7.剩余参数

问题:处理可变数量的函数参数可能很棘手。

解决方案:使用剩余参数捕获数组中的所有参数。

functionsum(…args){returnargs.reduce((total,num)=>total+num,0);}console.log(sum(1,2,3,4));//10

剩余参数允许您将无限数量的参数作为数组处理,从而使您的函数更加灵活。

8.短路求值

问题:编写条件语句可能很冗长。

解决方案:使用短路求值编写简洁的条件。

constisLoggedIn=true;constuser=isLoggedIn&&{name:'Jane',age:25};console.log(user);//{name:'Jane',age:25}

9.可选链

问题:如果链中的任何部分为`null`或`undefined`,则访问深层嵌套的属性可能会导致错误。

解决方案:使用可选链安全地访问嵌套属性。

constuser={profile:{name:'Jane'}};constuserName=user.profile.name;console.log(userName);//"Jane"

可选链式连接允许您安全地访问嵌套属性,而无需明确检查链式连接的每一级是否为`null`或`undefined`。

10.空值合并

解决方案:仅在`null`或`undefined`时使用空值合并(``)提供默认值。

constuser={name:'',age:0};constuserName=user.name'Anonymous';constuserAge=user.age18;console.log(userName);//""console.log(userAge);//0

空值合并仅允许在左侧为“null”或“undefined”时提供默认值。

11.对象属性简写

问题:将变量分配给对象属性可能会重复。

解决方案:使用属性简写来简化对象创建。

constname='Jane';constage=25;constuser={name,age};console.log(user);//{name:'Jane',age:25}

属性简写允许您在属性名称与变量名称匹配时省略属性名称,从而使代码更简洁。

12.动态属性名称

问题:使用动态属性名称创建对象可能很冗长。

解决方案:使用计算属性名称动态创建对象属性。

constpropName='age';constuser={name:'Jane',[propName]:25};console.log(user);//{name:'Jane',age:25}

计算属性名称允许您动态创建对象属性,使用表达式的值作为属性名称。

13.数组`map()`、`filter()`和`reduce()`

问题:迭代数组以转换、过滤或累积值可能会重复。

解决方案:使用`map()`、`filter()`和`reduce()`进行常见的数组操作。

constnumbers=[1,2,3,4,5];constdoubled=numbers.map(num=>num*2);console.log(doubled);//[2,4,6,8,10]constevens=numbers.filter(num=>num%2===0);console.log(evens);//[2,4]constsum=numbers.reduce((total,num)=>total+num,0);console.log(sum);//15

这些数组方法提供了一种转换、过滤和减少数组的函数式方法,使您的代码更具表现力和简洁性。

14.字符串`includes()`、`startsWith()`和`endsWith()`

问题:检查字符串是否包含、以子字符串开头或以子字符串结尾可能很冗长。

解决方案:使用`includes()`、`startsWith()`和`endsWith()`进行更简单的字符串检查。

conststr='Hello,world!';console.log(str.includes('world'));//trueconsole.log(str.startsWith('Hello'));//trueconsole.log(str.endsWith('!'));//true

这些字符串方法提供了一种简单易读的方法来检查子字符串的存在、开始或结束。

15.函数参数中的数组和对象解构

问题:从作为函数参数传递的数组或对象中提取值可能很冗长。

解决方案:在函数参数中使用解构来直接提取值。

constuser={name:'Jane',age:25};functiongreet({name,age}){return`Hello,${name}!Youare${age}yearsold.`;}console.log(greet(user));//"Hello,Jane!Youare25yearsold."

函数参数中的解构允许您直接从传递给函数的对象或数组中提取值,从而使代码更简洁、更易读。

16.解构中的默认值

问题:解构对象时处理缺失的属性可能很麻烦。

解决方案:在解构中使用默认值来提供后备值。

constuser={name:'Jane'};const{name,age=18}=user;console.log(name);//"Jane"console.log(age);//18

解构中的默认值允许您为可能缺失的属性提供后备值,从而使您的代码更加健壮。

17.对象`assign()`

问题:克隆或合并对象可能很冗长且容易出错。

解决方案:使用`Object.assign()`克隆或合并对象。

consttarget={a:1};constsource={b:2};constmerged=Object.assign(target,source);console.log(merged);//{a:1,b:2}

`Object.assign()`允许您高效地克隆或合并对象,从而减少手动复制的需要。

18.数组`find()`和`findIndex()`

问题:使用循环在数组中查找元素或其索引可能很麻烦。

解决方案:使用`find()`和`findIndex()`使代码更易读。

constusers=[{id:1,name:'Jane'},{id:2,name:'John'},];constuser=users.find(u=>u.id===1);console.log(user);//{id:1,name:'Jane'}constindex=users.findIndex(u=>u.id===1);console.log(index);//0

这些数组方法提供了一种根据条件查找元素或其索引的简单方法,从而提高了代码的可读性。

19.数组`some()`和`every()`

问题:检查数组中的部分或全部元素是否满足条件可能会很冗长。

解决方案:使用`some()`和`every()`来获得更简洁的代码。

constnumbers=[1,2,3,4,5];consthasEven=numbers.some(num=>num%2===0);console.log(hasEven);//trueconstallEven=numbers.every(num=>num%2===0);console.log(allEven);//false

这些数组方法允许您以简洁的方式检查数组中的部分或全部元素是否满足条件。

20.数组`flat()`和`flatMap()`

问题:展平嵌套数组或映射和展平数组可能很麻烦。

解决方案:使用`flat()`和`flatMap()`使代码更易读。

constnested=[1,[2,[3,[4]]]];constflat=nested.flat(2);console.log(flat);//[1,2,3,[4]]constmapped=[1,2,3].flatMap(x=>[x,x*2]);console.log(mapped);//[1,2,2,4,3,6]

这些数组方法提供了一种简单的方法来展平嵌套数组,并在一个步骤中映射和展平。

21.数组`from()`和`of()`

问题:从可迭代对象或参数创建数组可能很冗长。

解决方案:使用`Array.from()`和`Array.of()`获得更简洁的代码。

constset=newSet([1,2,3]);constarrFromSet=Array.from(set);console.log(arrFromSet);//[1,2,3]constarrOfNumbers=Array.of(1,2,3);console.log(arrOfNumbers);//[1,2,3]

`Array.from()`允许您从可迭代对象创建数组,而`Array.of()`允许您从参数列表创建数组。

22.回调中的参数解构

问题:访问传递给回调的对象的属性可能很冗长。

解决方案:在回调参数中使用解构以获得更简洁的代码。

constusers=[{id:1,name:'Jane'},{id:2,name:'John'},];users.forEach(({id,name})=>{console.log(`UserID:${id},UserName:${name}`);});

回调参数中的解构允许您直接访问传递给回调的对象的属性,从而使代码更简洁。

23.可选回调函数

问题:处理可选回调函数可能很麻烦。

解决方案:使用短路求值来调用可选回调。

functionfetchData(url,callback){fetch(url).then(response=>response.json()).then(data=>{callback&&callback(data);});}

短路求值允许您仅在提供可选回调函数时才调用该函数,从而使代码更加健壮。

24.Promisify回调

问题:将基于回调的函数转换为承诺可能很麻烦。

解决方案:使用实用函数来promisify回调。

functionpromisify(fn){returnfunction(…args){returnnewPromise((resolve,reject)=>{fn(…args,(err,result)=>{if(err)reject(err);elseresolve(result);});});};}constreadFile=promisify(require('fs').readFile);readFile('path/to/file.txt','utf8').then(data=>console.log(data)).catch(err=>console.error(err));

Promisifying允许您将基于回调的函数转换为承诺,从而更轻松地使用async/await语法。

25.用于类似同步代码的Async/Await

问题:使用承诺编写异步代码可能冗长且难以阅读。

解决方案:使用async/await以同步风格编写异步代码。

Async/await提供了一种编写外观和行为都像同步代码的异步代码的方法,从而提高了可读性和可维护性。

26.链接承诺

问题:按顺序处理多个异步操作可能很麻烦。

解决方案:链式承诺处理多个异步操作。

链接Promise可让您按顺序处理多个异步操作,从而提高可读性和可维护性。

27.Promise.all用于并发执行

问题:同时处理多个异步操作可能具有挑战性。

解决方案:使用`Promise.all`来处理并发异步操作。

`Promise.all`允许您同时处理多个异步操作,并在所有操作完成后继续执行。

28.防抖动函数

问题:频繁的函数调用(例如在窗口调整大小事件期间)会降低性能。

解决方案:使用防抖动函数来限制函数执行的速率。

functiondebounce(func,wait){lettimeout;returnfunction(…args){clearTimeout(timeout);timeout=setTimeout(()=>func.apply(this,args),wait);};}window.addEventListener('resize',debounce(()=>{console.log('Windowresized');},200));

29.节流阀函数

问题:限制频繁触发的事件(如滚动或调整大小)的函数执行速率。

解决方案:使用节流阀函数来限制函数的执行速率。

functionthrottle(func,limit){letlastFunc;letlastRan;returnfunction(…args){if(!lastRan){func.apply(this,args);lastRan=Date.now();}else{clearTimeout(lastFunc);lastFunc=setTimeout(()=>{if(Date.now()-lastRan>=limit){func.apply(this,args);lastRan=Date.now();}},limit-(Date.now()-lastRan));}};}window.addEventListener('scroll',throttle(()=>{console.log('Windowscrolled');},200));

30.深度克隆对象

问题:克隆嵌套对象可能很棘手且容易出错。

解决方案:使用结构化克隆或Lodash等库来深度克隆对象。

constobj={a:1,b:{c:2}};constdeepClone=JSON.parse(JSON.stringify(obj));console.log(deepClone);//{a:1,b:{c:2}}

深度克隆确保嵌套对象按值复制,而不是按引用复制,从而防止对原始对象进行意外修改。

31.记忆化

问题:反复调用昂贵的函数会降低性能。

解决方案:使用记忆化来缓存昂贵的函数调用的结果。

functionmemoize(func){constcache=newMap();returnfunction(…args){constkey=JSON.stringify(args);if(cache.has(key)){returncache.get(key);}constresult=func.apply(this,args);cache.set(key,result);returnresult;};}constexpensiveFunction=memoize((num)=>{console.log('Computing…');returnnum*2;});console.log(expensiveFunction(2));//"Computing…"4console.log(expensiveFunction(2));//4

记忆化通过缓存昂贵的函数调用结果并返回缓存的结果以供后续具有相同参数的调用来提高性能。

32.柯里化函数

问题:创建具有多个参数的函数可能很麻烦。

解决方案:使用柯里化创建具有部分应用参数的函数。

functioncurry(func){returnfunctioncurried(…args){if(args.length>=func.length){returnfunc.apply(this,args);}returnfunction(…nextArgs){returncurried.apply(this,args.concat(nextArgs));};};}constsum=(a,b,c)=>a+b+c;constcurriedSum=curry(sum);console.log(curriedSum(1)(2)(3));//6console.log(curriedSum(1,2)(3));//6

通过柯里化,您可以创建可以用较少参数调用的函数,并返回接受其余参数的新函数。

33.部分应用

问题:调用带有重复参数的函数可能很繁琐。

解决方案:使用部分应用将一些参数预先应用于函数。

functionpartial(func,…presetArgs){returnfunction(…laterArgs){returnfunc(…presetArgs,…laterArgs);};}constmultiply=(a,b,c)=>a*b*c;constdouble=partial(multiply,2);console.log(double(3,4));//24

部分应用允许您通过预先应用一些参数来创建新函数,从而使您的代码更加灵活和可重用。

34.函数组合

问题:将多个函数组合成一个操作可能很麻烦。

解决方案:使用函数组合来组合多个函数。

constcompose=(…funcs)=>(arg)=>funcs.reduceRight((prev,fn)=>fn(prev),arg);constadd=(x)=>x+1;constmultiply=(x)=>x*2;constaddThenMultiply=compose(multiply,add);console.log(addThenMultiply(5));//12

函数组合允许您通过组合多个函数来创建新函数,从而使您的代码更加模块化和可重用。

35.函数流水线

问题:将一系列函数应用于一个值可能会很冗长。

解决方案:使用函数流水线按顺序应用一系列函数。

constpipe=(…funcs)=>(arg)=>funcs.reduce((prev,fn)=>fn(prev),arg);constadd=(x)=>x+1;constmultiply=(x)=>x*2;constaddThenMultiply=pipe(add,multiply);console.log(addThenMultiply(5));//12

函数流水线允许您按顺序将一系列函数应用于一个值,从而提高代码的可读性和可维护性。

36.自调用函数

问题:定义后立即执行函数可能很麻烦。

解决方案:使用立即调用函数表达式(IIFE)。

(function(){console.log('Thisrunsimmediately!');})();

IIFE允许您在定义后立即执行函数,这对于创建隔离范围和避免污染全局命名空间非常有用。

37.避免使用全局变量

问题:全局变量可能导致冲突和意外的副作用。

解决方案:使用局部变量和模块来避免污染全局命名空间。

//UsinglocalvariablesfunctiondoSomething(){letlocalVariable='Thisislocal';console.log(localVariable);}//UsingmodulesconstmyModule=(function(){letprivateVariable='Thisisprivate';return{publicMethod(){console.log(privateVariable);},};})();myModule.publicMethod();//"Thisisprivate"

避免使用全局变量有助于防止冲突和意外副作用,从而使您的代码更加模块化和易于维护。

38.使用闭包进行封装

问题:暴露函数的内部细节可能会导致误用。

解决方案:使用闭包封装内部细节。

functioncreateCounter(){letcount=0;return{increment(){count++;returncount;},decrement(){count-;returncount;},};}constcounter=createCounter();console.log(counter.increment());//1console.log(counter.increment());//2console.log(counter.decrement());//1

闭包允许您封装内部细节并仅公开必要的功能,从而提高代码安全性和可维护性。

39.模块模式

问题:将代码组织成可重用的模块可能具有挑战性。

解决方案:使用模块模式创建可重用和封装的代码。

constmyModule=(function(){letprivateVariable='Thisisprivate';functionprivateMethod(){console.log(privateVariable);}return{publicMethod(){privateMethod();},};})();myModule.publicMethod();//"Thisisprivate"

模块模式允许您创建可重用和封装的代码,从而改善代码组织和可维护性。

40.单例模式

问题:确保只创建一个类的实例可能具有挑战性。

解决方案:使用单例模式创建单个实例。

constsingleton=(function(){letinstance;functioncreateInstance(){return{name:'SingletonInstance',};}return{getInstance(){if(!instance){instance=createInstance();}returninstance;},};})();constinstance1=singleton.getInstance();constinstance2=singleton.getInstance();console.log(instance1===instance2);//true

单例模式确保只创建一个类的实例,这对于管理共享资源或配置很有用。

41.工厂模式

问题:创建具有复杂初始化的对象可能很麻烦。

解决方案:使用工厂模式创建对象。

functioncreateUser(name,role){return{name,role,sayHello(){console.log(`Hello,mynameis${this.name}andIama${this.role}`);},};}constadmin=createUser('Alice','admin');constuser=createUser('Bob','user');admin.sayHello();//"Hello,mynameisAliceandIamanadmin"user.sayHello();//"Hello,mynameisBobandIamauser"

工厂模式允许您以灵活且可重用的方式创建具有复杂初始化的对象。

42.观察者模式

问题:管理状态变化和通知多个组件可能具有挑战性。

解决方案:使用观察者模式来管理状态变化并通知观察者。

functionSubject(){this.observers=[];}Subject.prototype={subscribe(observer){this.observers.push(observer);},unsubscribe(observer){this.observers=this.observers.filter((obs)=>obs!==observer);},notify(data){this.observers.forEach((observer)=>observer.update(data));},};functionObserver(name){this.name=name;}Observer.prototype.update=function(data){console.log(`${this.name}receiveddata:${data}`);};constsubject=newSubject();constobserver1=newObserver('Observer1');constobserver2=newObserver('Observer2');subject.subscribe(observer1);subject.subscribe(observer2);subject.notify('Newdataavailable');//"Observer1receiveddata:Newdataavailable""Observer2receiveddata:Newdataavailable"

观察者模式允许您管理状态变化并通知多个观察者,从而改善代码组织和可维护性。

43.事件委托

问题:向多个元素添加事件监听器会降低性能。

解决方案:使用事件委托有效地管理事件。

document.getElementById('parent').addEventListener('click',(event)=>{if(event.target&&event.target.matches('button.className')){console.log('Buttonclicked:',event.target.textContent);}});

事件委托允许您通过向公共父元素添加单个事件侦听器并处理多个子元素的事件来有效地管理事件。

44.避免使用`eval()`

问题:使用`eval()`可能导致安全漏洞和性能问题。

解决方案:避免使用`eval()`并使用更安全的替代方案。

//Avoidconstcode='console.log("Hello,world!")';eval(code);//"Hello,world!"//Usesaferalternativesconstfunc=newFunction('console.log("Hello,world!")');func();//"Hello,world!"

避免使用`eval()`有助于防止安全漏洞和性能问题,从而使您的代码更安全、更高效。

45.使用`for…of`进行迭代

问题:使用`for…in`迭代数组容易出错。

解决方案:使用`for…of`迭代数组和其他可迭代对象。

constarr=[1,2,3,4,5];for(constvalueofarr){console.log(value);}

THE END
1.基于java的图书管理系统源代码经管文库(原现金基于java的图书管理系统源代码 https://bbs.pinggu.org/thread-13188800-1-1.html
2.A7816Java+mysql+servlet+jsp+mysql公共图书馆图书借阅系统的随着计算机的普及和互联网的广泛应用,公共图书馆图书借阅系统可以有效地实现图书管理的规范化和系统化,减少了人工管理过程中的缺陷。它可以对所有图书信息进行统一的操作和管理,使读者更容易借阅图书,使图书馆能够充分发挥其功能,使读者更好地享受图书资源。今天,计算机的发展是最快的。计算机的最大优点是它们可用于信息https://blog.51cto.com/u_12948819/12853946
3.图书管理系统(Java+Swing+MySQL)码农集市专业分享IT编程学习图书管理系统是一个用于管理图书馆的信息系统,主要通过Java Swing和MySQL数据库进行开发。系统主要包括以下部分: 1. 登录程序:用户需要输入用户名和密码才能登录系统,系统会验证用户的合法性并显示相应的操作界面。 2. 系统主程序:这是系统的入口,用户可以在此处执行各种操作,如查询图书、借阅图书、归还图书等。 3. https://www.coder100.com/index/index/content/id/4321478
4.java计算机毕业设计基于微信小程序的图书借阅管理系统[附源码传统的图书借阅管理系统大多依赖于人工操作和实体卡片,这不仅效率低下,而且容易出现信息更新不及时、借阅记录混乱等问题。随着移动互联网技术的普及,微信小程序作为一种轻量级的应用形态,凭借其无需下载安装、即用即走的特点,迅速赢得了广大用户的青睐。因此,将微信小程序应用于图书借阅管理系统中,实现图书借阅的智能化https://zhuanlan.zhihu.com/p/11592798232
5.使用JavaScript编程,完成图书管理系统使用JavaScript编程,完成图书管理系统 很简单!!! <!DOCTYPEhtml> .grid { margin: auto; width: 800px; text-align: center; } .grid table { width: 100%; border-collapse: collapse; } .grid th,td { padding: 10; border: 1px dashed orangehttps://blog.csdn.net/qiuweichen1215/article/details/129563929
6.基于springboot+vue的图书馆管理系统随着社会的发展,计算机的优势和普及使得阿博图书馆管理系统的开发成为必需。阿博图书馆管理系统主要是借助计算机,通过对图书借阅等信息进行管理。减少管理员的工作,同时也方便广大用户对所需图书借阅信息的及时查询以及管理。 阿博图书馆管理系统的开发过程中,采用B / S架构,主要使用Java技术进行开发,结合最新流行的springhttps://www.jianshu.com/p/66d6ddb580b3
7.786572258/laravel这个主机重写文件是我从原来的.htaccess翻译到nginx里面的。需要引入然后重启nginx服务器才能正常访问图书管理系统项目。 配置域名(我配置的是www.laravelbookmg.com)然后访问。 后台登录页地址是:www.laravelbookmg.com/admin,默认账户:admin@admin.com,123456 https://github.com/786572258/laravel-bookmg
8.江西交通职业技术学院本专业培养能在计算机等领域从事云计算平台的设计、开发与运维以及计算机软件开发与维护等方面工作的高素质技能型工程技术人才,对接云计算行业的软硬件设计开发与维护、系统集成、工程交付、项目管理、营销服务、教育服务和研发等工作。本专业与产业链的对应关系如图1所示。 https://mkszyxy.jxjtxy.edu.cn/news-show-1081.html
9.JavaWeb经典项目图书管理系统源码下载手打开发的97分高分设计项目,内含详细文档说明,可作为高分课程设计和期末大作业的参考,含有代码注释小白也可看的懂,有能力的小伙伴也可以在此基础上进行二开,项目代码完整下载即可运行。 JavaWeb图书管理系统源码+数据库+文档说明(高分项目) JavaWeb图书管理系统源码+数据库+文档说明(高分项目) JavaWeb图书管理系统源码https://www.iteye.com/resource/dushine2008-9768046
10.基于Web的图书管理系统.docx基于Web的图书管理系统.docx,PAGE 1 论文题目:基于Web的图书管理系统 论文题目: 基于Web的图书管理系统 摘要 本图书馆系统基于MVC设计模式,采用四层结构,主要包括:表现层、控制层、业务层、持久层。系统设计使用SSH(Struts2、Hibernate和Spring)三种框架,共分为图书https://m.book118.com/html/2023/0122/8052035057005031.shtm
11.基于Java简单实现图书馆借书管理系统项目一般分为管理员和读者两个角色,管理员可以登录系统、图书管理、书架、用户管理、读者管理、查看借阅记录管理等,读者角色可以登录系统查询图书信息、借阅和归还图书、查看个人借阅记录、编辑个人信息等。 项目功能简单,数据库只有管理员表、读者表、图书表和借阅记录表四张表,但基本实现了图书馆借书的管理功能,可以在https://www.tulingxueyuan.cn/tlzx/jsp/2116.html
12.JavaSwing期末大作业图书借阅管理系统图书借阅系统有以下四大模块:读者信息管理、图书信息管理、图书借阅管理、用户信息管理、类型信息管理,我们接下来对每一模块的具体功能进行分析需求。https://cloud.tencent.com/developer/article/2026592
13.Javascript中AJAX的图书管理代码实例详解javascript技巧这篇文章主要为大家详细介绍了AJAX的图书管理代码实例,使用数据库,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下+ 目录 GPT4.0+Midjourney绘画+国内大模型 会员永久免费使用!【 如果你想靠AI翻身,你先需要一个靠谱的工具!】 1、接口文档 2、代码结构 1 2 3 4 5 6 7 8 9 https://www.jb51.net/article/237205.htm