探索Python编程的技巧:多线程魔法、网络舞台、正则魔法阵与递归迷宫

一 多线程

1.1 进程和线程

  • 进程: 就是一个程序,运行在系统之上,称这个程序为一个运行进程,并分配进程ID方便系统管理。
  • 线程:线程是归属于进程的,一个进程可以开启多个线程,执行不同的工作,是进程的实际工作最小单位。
  • 操作系统中可以运行多个进程,即多任务运行
  • 一个进程内可以运行多个线程,即多线程运行

  • 进程之间是内存隔离的, 即不同的进程拥有各自的内存空间。
  • 线程之间是内存共享的,线程是属于进程的,一个进程内的多个线程之间是共享这个进程所拥有的内存空间的。
    在这里插入图片描述

1.2 并行、并发执行概念

  • 在Python中,多线程用于实现并行和并发执行任务。虽然多线程可以让你同时执行多个任务,但由于Python的全局解释锁(Global Interpreter Lock,GIL)的存在,多线程并不能实现真正的多核并行。然而,多线程仍然可以用于执行I/O密集型任务,因为在这些任务中,线程可能会在等待I/O操作完成时释放GIL,从而允许其他线程运行。

  • 并行执行:

    • 并行执行是指多个任务在同一时刻同时运行,各自独立地占用一个CPU核心。
    • 在Python中,由于GIL的存在,多线程并不适合用于CPU密集型任务的并行执行。
    • 多个进程同时在运行,即不同的程序同时运行,称之为:多任务并行执行
    • 一个进程内的多个线程同时在运行,称之为:多线程并行执行
  • 并发执行:

    • 并发执行是指多个任务交替执行通过快速切换执行任务的上下文来实现“同时”执行的错觉。
    • 这在处理I/O密集型任务时非常有效,因为线程可能会在等待I/O完成时让出CPU资源给其他线程。

1.3 多线程编程

  • 在Python中,可以使用threading模块来创建和管理多线程。

  • threading.Thread类可以创建一个线程对象,用于执行特定的任务函数。

  • threading.Thread类的一般语法和一些常用参数:

    thread_obj = threading.Thread(target=function_name, args=(), kwargs={}, daemon=False)
    # 启动线程
    thread_obj.start() 
    
  • target: 必需的参数用于指定线程要执行的函数(任务)。函数会在新线程中运行。

  • args: 可选参数用于传递给目标函数的位置参数,以元组形式提供。如果函数不需要参数,可以传递一个空元组或省略这个参数。

  • kwargs: 可选参数用于传递给目标函数的关键字参数,以字典形式提供。如果函数不需要关键字参数,可以传递一个空字典或省略这个参数。

  • daemon: 可选参数,布尔值,用于指定线程是否为守护线程。守护线程会在主线程结束时被终止,而非守护线程会等待所有线程完成后再终止。

  • 使用threading.Thread类创建线程对象并启动线程

import threading
import timedef print_numbers():for i in range(1, 6):print(f"Number: {i}")time.sleep(1)def print_letters():for letter in ['a', 'b', 'c', 'd', 'e']:print(f"Letter: {letter}")time.sleep(1)if __name__ == "__main__":thread1 = threading.Thread(target=print_numbers)thread2 = threading.Thread(target=print_letters)thread1.start()  # Start the first threadthread2.start()  # Start the second threadthread1.join()   # 用于阻塞当前线程,直到被调用的线程完成其执行thread2.join()   # 用于阻塞当前线程,直到被调用的线程完成其执行print("All threads completed")
  • 在这个示例中,创建了两个线程对象,每个线程对象都关联一个不同的任务函数(print_numbersprint_letters)。然后启动这两个线程,并等待它们完成。最后,我们输出一个提示,表示所有线程都已完成。

  • thread1thread2 是两个线程对象,而 thread1.join()thread2.join() 是在主线程中调用的。当调用这些方法时,主线程会阻塞,直到对应的线程(thread1thread2)完成了它们的执行。

  • thread1.join()thread2.join() 语句确保在两个子线程执行完成后,主线程才会输出 “All threads completed” 这条消息。如果不使用 join(),主线程可能会在子线程还没有完成时就继续执行,导致输出消息的时机不确定。

1.4 补充:join()方法

  • 在多线程编程中,join() 方法用于阻塞当前线程,直到被调用的线程完成其执行。具体来说,thread1.join() 表示当前线程(通常是主线程)会等待 thread1 线程完成后再继续执行。

  • 这种等待的机制可以确保主线程在所有子线程执行完成后再继续执行,从而避免可能出现的线程之间的竞争条件和不确定性。这在需要等待所有线程完成后进行进一步操作或获取线程执行结果时非常有用。

1.5 并行、并发实现演示

  • 并行执行多个任务演示:
import threadingdef task1():print("Task 1 started")# ... some code ...print("Task 1 finished")def task2():print("Task 2 started")# ... some code ...print("Task 2 finished")if __name__ == "__main__":thread1 = threading.Thread(target=task1)thread2 = threading.Thread(target=task2)thread1.start()thread2.start()thread1.join()thread2.join()print("All tasks completed")

  • 并发执行演示:
import threading
import timedef task1():print("Task 1 started")time.sleep(2)  # Simulate I/O operationprint("Task 1 finished")def task2():print("Task 2 started")time.sleep(1)  # Simulate I/O operationprint("Task 2 finished")if __name__ == "__main__":thread1 = threading.Thread(target=task1)thread2 = threading.Thread(target=task2)thread1.start()thread2.start()thread1.join()thread2.join()print("All tasks completed")

1.6 Thread参数传递使用演示

  • 当使用threading.Thread创建线程对象时,可以通过argskwargsdaemon参数传递不同类型的信息给线程。
  • 以下是针对每种参数的示例:
  1. 使用 args 参数传递位置参数:
import threadingdef print_numbers(start, end):for i in range(start, end + 1):print(f"Number: {i}")if __name__ == "__main__":thread1 = threading.Thread(target=print_numbers, args=(1, 5))thread2 = threading.Thread(target=print_numbers, args=(6, 10))thread1.start()thread2.start()thread1.join()thread2.join()print("All threads completed")
  1. 使用 kwargs 参数传递关键字参数:
import threadingdef greet(name, message):print(f"Hello, {name}! {message}")if __name__ == "__main__":thread1 = threading.Thread(target=greet, kwargs={"name": "Alice", "message": "How are you?"})thread2 = threading.Thread(target=greet, kwargs={"name": "Bob", "message": "Nice to meet you!"})thread1.start()thread2.start()thread1.join()thread2.join()print("All threads completed")
  1. 使用 daemon 参数设置守护线程:
import threading
import timedef count_seconds():for i in range(5):print(f"Elapsed: {i} seconds")time.sleep(1)if __name__ == "__main__":thread = threading.Thread(target=count_seconds)thread.daemon = True  # 设置线程为守护线程thread.start()# No need to join daemon threads, they will be terminated when the main thread endsprint("Main thread completed")
  • 将线程 thread 设置为守护线程(daemon = True)。这意味着当主线程结束时,守护线程也会被终止,而无需使用 join() 等待它完成。

二 网络编程

2.1 Socket初识

  • Python的套接字(Socket)编程是一种基本的网络编程技术,它可以在网络上建立连接并进行数据传输。
  • socket (简称 套接字) 是进程之间通信一个工具,进程之间想要进行网络通信需要socket。Socket负责进程之间的网络数据传输,是数据的搬运工。
    在这里插入图片描述

2.2 客户端和服务端

  • 2个进程之间通过Socket进行相互通讯,就必须有服务端和客户端
    • Socket服务端:等待其它进程的连接、可接受发来的消息、可以回复消息
    • Socket客户端:主动连接服务端、可以发送消息、可以接收回复
      在这里插入图片描述

2.3 创建socket对象详解

  • 在创建套接字对象时,通常是可以不指定参数的。如果没有指定参数,将会使用默认的参数,这些参数在 socket 模块中预先定义。默认情况下,socket 函数将创建一个 IPv4 的流式套接字。

  • 例如,以下代码将创建一个默认的 IPv4 TCP 套接字:

    import socket# 创建一个默认的 IPv4 TCP 套接字
    default_socket = socket.socket()# 后续代码中可以使用 default_socket 进行操作
    
  • 这种方式在很多情况下都是适用的,特别是当你只需要一个简单的 IPv4 TCP 套接字时。

    socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    1. socket:Python 的内置套接字模块,它提供了在网络上进行通信的基本功能。
    2. socket.AF_INET:表示套接字地址簇(Address Family),用于指定套接字使用的地址类型。AF_INET 表示使用 IPv4 地址。还可以使用 AF_INET6 来表示 IPv6 地址。
    3. socket.SOCK_STREAM:表示套接字的类型。SOCK_STREAM 表示这是一个流式套接字,它基于 TCP 协议提供了可靠的、面向连接的、双向的数据流传输。
  • 综合起来,socket.socket(socket.AF_INET, socket.SOCK_STREAM) 创建了一个基于 IPv4 地址和 TCP 协议的流式套接字对象,你可以使用这个套接字对象来建立连接、发送和接收数据。

  • 如果需要创建基于 UDP 协议的套接字,可以使用 socket.SOCK_DGRAM,例如:

    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    

2.4 accept()方法详解

  • accept() 方法是在服务器端套接字上调用的方法,用于接受客户端的连接请求。它会阻塞程序,直到有客户端尝试连接到服务器,然后返回一个新的套接字用于与该客户端进行通信,以及客户端的地址信息。

    client_socket, client_address = server_socket.accept()
    
    • accept():接受客户端的连接请求。当调用这个方法时,它会阻塞程序,直到有客户端连接到服务器。一旦有连接请求到达,该方法将返回两个值:一个是表示与客户端通信的新套接字对象,另一个是客户端的地址信息。
    • client_socket新创建的套接字对象,用于与连接的客户端进行通信(可以使用这个套接字来接收和发送数据)
    • client_address:元组类型,包含客户端的 IP 地址和端口号。例如,('192.168.1.100', 54321)
  • 一般来说,服务器在一个循环中使用 accept() 方法,以便能够接受多个客户端的连接。每当有新的客户端连接到服务器时,accept() 方法会返回一个新的套接字和客户端的地址,然后服务器可以将新套接字添加到连接池,与客户端进行通信。

  • 注意:accept() 方法在没有连接请求时会一直阻塞程序。如果你希望设置超时或者非阻塞的连接等待,你可以在创建服务器套接字后设置相应的选项。这样,在没有连接请求时调用 accept() 方法将立即返回,不会阻塞程序的执行。

  • 例如,在创建服务器套接字后可以使用以下代码将其设置为非阻塞模式:

    server_socket.setblocking(False)
    

2.5 发送信息方法详解

  • send() 方法和 sendall() 方法都用于在套接字上发送数据,但它们有一些不同之处。

send(data) 方法:

  • send() 方法是用于发送数据的基本方法,它接受一个字节流(bytes)作为参数,并尝试将数据发送到连接的对方。
  • 如果成功发送全部数据,该方法将返回发送的字节数。如果没有发送完全部数据,可能返回一个小于请求发送数据的字节数。
  • 如果在发送过程中出现问题(例如连接中断),send() 方法可能会引发异常。

sendall(data) 方法:

  • sendall() 方法也用于发送数据,但它更加健壮,会自动处理数据分片和重试。

  • 无论数据有多大,sendall() 方法会尽力将所有数据都发送出去,直到全部数据都被发送成功或发生错误。

  • 方法不会立即返回,而是在所有数据都发送成功后才返回 None。如果发生错误,它可能引发异常。

  • sendall() 方法在发送数据时会自动处理数据的分片,确保数据都被正确发送。

  • 在大多数情况下,如果想要简单地发送一小段数据,可以使用 send() 方法。然而,如果需要发送大量数据或者确保数据被完整、可靠地发送,那么使用 sendall() 方法会更好,因为它会自动处理数据分片和错误处理。

  • 使用 send() 方法:

    client_socket.send(b"Hello, server!")
    
  • 使用 sendall() 方法:

    data = b"Hello, server!"
    client_socket.sendall(data)
    

2.6 Socket编程演示

  • 服务器端代码:

    import socket# 1. 创建Socket对象
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 2. 绑定服务器地址和端口
    server_address = ('127.0.0.1', 12345)
    server_socket.bind(server_address)# 3. 开始监听端口 backlog=5 标识允许的连接数量,超出的会等待,可以不填,不填自动设置一个合理的值
    server_socket.listen(5)print("Waiting for a connection...")
    # 4. 接收客户端连接,获得连接对象
    client_socket, client_address = server_socket.accept()
    print(f"Connected to {client_address}")# 5. 客户端连接后,通过recv方法 接收并发送数据
    while True:data = client_socket.recv(1024).decode('utf-8')# recv接受的参数是缓冲区大小,一般给1024即可# recv方法的返回值是一个字节数组也就是bytes对象,不是字符串,可以通过decode方法通过UTF-8编码,将字节数组转换为字符串对象if not data:breakprint(f"Received: {data}")# 6. 通过client_socket对象(客户端再次连接对象),调用方法,发送回复消息msg = input("请输入你要和客户端回复的消息:")if msg == 'exit':breakclient_socket.sendall(msg.eccode("UTF-8"))# 7.关闭连接
    client_socket.close()
    server_socket.close()
    

  • 客户端代码:
    import socket# 1.创建socket对象
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 2.连接服务器地址和端口
    server_address = ('127.0.0.1', 12345)
    client_socket.connect(server_address)# 3.发送数据
    while True:# 发送消息msg = input("请输入要给服务端发送的消息:")if msg == 'exit':breakclient_socket.sendall(msg.encode("UTF-8"))# 4.接收返回消息recv_data = client_socket.recv(1024)  # 1024是缓冲区的大小,一般1024即可。 同样recv方法是阻塞的      print(f"服务端回复的消息是:{recv_data.decode('UTF-8')}")# 5.关闭连接
    client_socket.close()
    

三 正则表达式

3.1 正则表达式概述

  • 正则表达式,又称规则表达式(Regular Expression),是使用单个字符串来描述、匹配某个句法规则的字符串,常被用来检索、替换那些符合某个模式(规则)的文本。
  • 正则表达式就是使用:字符串定义规则,并通过规则去验证字符串是否匹配。
  • 比如,验证一个字符串是否是符合条件的电子邮箱地址,只需要配置好正则规则,即可匹配任意邮箱。通过正则规则: (^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$) 即可匹配一个字符串是否是标准邮箱格式

3.2 Python正则表达式使用步骤

  • 使用正则表达式的一些基本步骤和示例:
  1. 导入re模块:
import re
  1. 使用re.compile()编译正则表达式:
# `pattern_here`应该替换为实际正则表达式
pattern = re.compile(r'pattern_here')
  1. 使用编译后的正则表达式进行匹配:
text = "This is an example text for pattern matching."
result = pattern.search(text)
if result:print("Pattern found:", result.group())
else:print("Pattern not found.")

3.3 正则的基础方法

  • Python正则表达式,使用re模块,并基于re模块中基础方法来做正则匹配。

  • 匹配(Match):使用match()来从字符串的开头开始匹配。匹配成功返回匹配对象(包含匹配的信息),匹配不成功返回空

result = pattern.match(text)
  • 搜索(Search):使用search()来查找文本中的第一个匹配项。整个字符串都找不到,返回None
result = pattern.search(text)
  • 查找所有(Find All):使用findall()来找到所有匹配项,并返回一个列表。找不到返回空list: []
results = pattern.findall(text)
  • 替换(Replace):使用sub()来替换匹配项。
# `replacement`应该是希望替换匹配项的内容
new_text = pattern.sub(replacement, text)
  • 分割(Split):使用split()来根据匹配项分割字符串。
parts = pattern.split(text)
  • 在正则表达式中,你可以使用不同的元字符(例如.*+?[]()等)来构建复杂的模式,以便进行更精确的匹配。

  • 演示使用正则表达式从文本中提取所有的电子邮件地址

import retext = "Contact us at: john@example.com or jane@example.org for more information."pattern = re.compile(r'\b[\w.-]+@[\w.-]+\.\w+\b')
email_addresses = pattern.findall(text)for email in email_addresses:print(email)

3.4 元字符匹配

  • 单字符匹配
    在这里插入图片描述
    示例:
    字符串 s = "itheima1 @@python2 !!666 ##itcast3"
  • 找出全部数字: re.findall(r '\d', s)
  • 字符串的r标记,表示当前字符串是原始字符串,即内部的转义字符无效而是普通字符
  • 找出特殊字符: re.findall(r '\W', s)
  • 找出全部英文字母:re.findall(r '[a-zA-Z]', s)
  • []内可以写:[a-zA-Z0-9] 这三种范围组合或指定单个字符如[aceDFG135]

  • 数量匹配
    在这里插入图片描述
  • 边界匹配
    在这里插入图片描述
  • 分组匹配
    在这里插入图片描述

四 递归

  • 递归是一种编程技术(算法),即方法(函数)自己调用自己的一种特殊编程写法。在Python中,可以使用递归来解决许多问题,特别是那些可以被分解为相同或类似子问题的问题。
    在这里插入图片描述

  • 在使用递归时,需要确保定义递归基(base case),这是递归结束的条件,以避免无限循环。每次递归调用都应该将问题规模减小,使其朝着递归基的条件靠近。

  • 使用递归计算阶乘:

    def factorial(n):if n == 0:return 1  # 递归基else:return n * factorial(n - 1)  # 递归调用num = 5
    result = factorial(num)
    print(f"The factorial of {num} is {result}")
    
    • factorial 函数通过不断地调用自身来计算阶乘。当 n 达到递归基条件 n == 0 时,递归结束,不再调用自身。
  • 然而,递归并不总是最有效的解决方法,因为它可能会导致函数调用的嵌套层数过深,从而消耗大量的内存和处理时间。在一些情况下,使用循环或其他方法可能更有效。

  • 在编写递归函数时,要确保递归调用朝着递归基靠近,避免陷入无限循环,同时考虑性能方面的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/38567.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++面向对象】--- 继承 的奥秘(下篇)

个人主页:平行线也会相交💪 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【C之路】💌 本专栏旨在记录C的学习路线,望对大家有所帮助🙇‍ 希望我们一起努力、成长&…

Postman接口自动化测试实战,从0到1一篇彻底打通...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 postman中的测试 …

【网络基础】传输层

【网络基础】传输层 文章目录 【网络基础】传输层1、端口号1.1 工具 2、UDP协议2.1 协议端格式2.2 UDP特点2.3 传输数据报2.4 缓冲区2.5 基于UDP应用层协议2.6 使用注意事项 3、TCP协议3.1 协议段格式3.2 ACK机制3.3 超时重传机制3.4 连接管理机制3.5 滑动窗口3.6 流量控制3.7 …

207、仿真-51单片机脉搏心率与血氧报警Proteus仿真设计(程序+Proteus仿真+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、硬件设计 二、设计功能 三、Proteus仿真图 四、程序源码 资料包括: 需要完整的资料可以点击下面的名片加下我,找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方案一&a…

猿辅导Motiff与IXDC达成战略合作,将在UI设计领域推动AI革新更多可能性

近日,“IXDC 2023国际体验设计大会”在北京国家会议中心拉开序幕,3000设计师、1000企业、200全球商业领袖,共襄为期5天的用户体验创新盛会。据了解,此次大会是以“设计领导力”为主题,分享全球设计、科技、商业的前沿趋…

报错解决:matlab机器人工具箱不支持将脚本 DHFactor 作为函数执行

matlab使用机器人工具箱出现报错: 不支持将脚本 DHFactor 作为函数执行: D:\MATLAB\install\toolbox\rvctools\robot\DHFactor.m 解决办法:重新到上图的rvctool重重新安装一下工具箱就好了。 到目录"$机器人工具箱路径$\rvctools" 在matlab命…

使用Scanner接收用户输入

扫描输入的两种方式 Scanner主要提供了两个方法来扫描输入: (1)hasNextXxx():是否还有下一个输入项,Xxx可以是Int,Long等代表基本数据类型的字符串。 如果只是判断是否包含下一个字符串,则直…

新手开抖店多久可以出单?

​开抖店是一种越来越流行的创业方式,在社交媒体平台上开店销售各种商品,比如服装、配饰、美妆和家居用品等等。对于新手来说,他们可能会很关心自己开抖店能够多久出单。虽然这个问题没有一个固定的答案,但是以下是一些关键的运营…

【boost网络库从青铜到王者】第三篇:asio网络编程中的buffer缓存数据结构

文章目录 1、关于buffer数据结构1.1、简单概括一下,我们可以用buffer() 函数生成我们要用的缓存存储数据。1.2、但是这太复杂了,可以直接用buffer函数转化为send需要的参数类型:1.3、output_buf可以直接传递给该send接口。我们也可以将数组转化为send接受…

docker发展历史

docker 一、docker发展历史很久以前2013年2014年2015年2016年2017年2018年2019年及未来 二、 docker概述定义:docker底层运行原理:docker简述核心概念容器特点Docker与虚拟机的区别: 三、容器在内核中支持两种重要技术四、namespace的六项隔离五、虚拟化产品有哪些1…

CAS 的执行流程 ?CAS 中 ABA 问题如何解决 ?CAS 在 Java 中有哪些实现类 ?

目录 1. CAS 的执行流程 2. CAS 中的 ABA 问题 3. 如何解决 CAS 中的 ABA 问题 4.CAS 在Java 中的实现类有哪些 1. CAS 的执行流程 CAS 比较并替换的大致流程是这样的: 它有三个操作单位:V(内存值),A(…

3D沉浸式旅游网站开发案例复盘【Three.js】

Plongez dans Lyon网站终于上线了。 我们与 Danka 团队和 Nico Icecream 共同努力,打造了一个令我们特别自豪的流畅的沉浸式网站。 这个网站是专为 ONLYON Tourism 和会议而建,旨在展示里昂最具标志性的活动场所。观看简短的介绍视频后,用户…

Android 面试笔记整理-Binder机制

作者:浪人笔记 面试可能会问到的问题 从IPC的方式问到Binder的优势为什么zygote跟其他服务进程的通讯不使用BinderBinder线程池和Binder机制 等等这些问题都是基于你对Binder的理解还有对其他IPC通讯的理解 IPC方式有多少种 传统的IPC方式有Socket、共享内存、管道…

云计算虚拟仿真实训平台

一、云计算虚拟仿真系统概述 云计算虚拟仿真系统是一种基于云计算技术和虚拟化技术的系统,用于实现各种仿真和模拟任务。它可以提供强大的计算能力和资源管理,为用户提供灵活、高效、可扩展的仿真环境。 该系统通常由一组服务器、网络和存储设备组成&am…

uniapp开发小程序-有分类和列表时,进入页面默认选中第一个分类

一、效果: 如下图所示,进入该页面后,默认选中第一个分类,以及第一个分类下的列表数据。 二、代码实现: 关键代码: 进入页面时,默认调用分类的接口,在分类接口里做判断&#xff…

神经网络基础-神经网络补充概念-08-逻辑回归中的梯度下降算法

概念 逻辑回归是一种用于分类问题的机器学习算法,而梯度下降是优化算法,用于更新模型参数以最小化损失函数。在逻辑回归中,我们使用梯度下降算法来找到最优的模型参数,使得逻辑回归模型能够更好地拟合训练数据。 逻辑回归中的梯…

无监督学习之主成分分析-半导体制造高维数据如何降维

数据降维不只存在于半导体数据中,它是存在于各行各业的,我们要分析的数据维数较多的时候全部输入维数较大这时就要采取降维的方法综合出主要的几列用于我们的分析。 PCA的哲学理念是要抓住问题的主要矛盾进行分析,是将多指标转化为少数几个…

前端技术栈es6+promise

let入门使用、 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>let 基本使用</title><script type"text/javascript">let name "hspedu教育";//老韩解读//1. conso…