JS 是怎样运行起来的

article/2025/9/30 13:29:11

这里填写标题

  • 1. Js 是怎样运行起来的?
    • 1.1. 前言
    • 1.2. V8 引擎
    • 1.3. CPU 是如何执行机器指令的?
    • 1.4. CPU 执行机器指令的流程
    • 1.5. V8 引擎的编译流水线
    • 1.6. 完整的分析一段 JavaScript 代码是怎样被执行的
      • 1.6.1. 初始化基础环境
      • 1.6.2. 解析源码生成 AST 和作用域
      • 1.6.3. 依据 AST 和作用域生成字节码
      • 1.6.4. 解释执行字节码
      • 1.6.5. 即时编译
    • 1.7. V8 的优化策略
      • 1.7.1. 重新引入字节码
      • 1.7.2. 延迟解析
      • 1.7.3. 隐藏类
      • 1.7.4. 快属性与慢属性
      • 1.7.5. 内联缓存
    • 1.8. 参考链接

1. Js 是怎样运行起来的?

1.1. 前言

我们所写的 JavaScript 代码是怎样被计算机认识并且执行的呢? 这中间的过程具体是怎样的呢?

有的同学可能已经知道, Js 是通过 Js 引擎运行起来的, 那么

什么是 Js 引擎?

  • Js 引擎是怎样编译执行和优化 Js 代码的?
  • Js 引擎有很多种, 比如 Chrome 使用的 V8 引擎, Webkit 使用的是 JavaScriptCore, React Native 使用的是 Hermes。今天我们主要来分析一下比较主流的 V8 引擎是怎样运行 Js 的。

1.2. V8 引擎

V8_Preview

在介绍 V8 引擎的概念之前, 我们先来回顾一下编程语言。编程语言可以分为机器语言、汇编语言、高级语言。

  • 机器语言: 由 0 和 1 组成的二进制码, 对于人类来说是很难记忆的, 还要考虑不同 CPU 平台的兼容性。
  • 汇编语言: 用更容易记忆的英文缩写标识符代替二进制指令, 但还是需要开发人员有足够的硬件知识。
  • 高级语言: 更简单抽象且不需要考虑硬件, 但是需要更复杂、耗时更久的翻译过程才能被执行。

Machine_Language

到了这里我们知道, 高级语言一定要转化为机器语言才能被计算机执行, 而且越高级的语言转化的时间越久。高级语言又可以分为解释型语言、编译型语言。

  • 编译型语言: 需要编译器进行一次编译, 被编译过的文件可以多次执行。如 C++、C 语言。
  • 解释型语言: 不需要事先编译, 通过解释器一边解释一边执行。启动快, 但执行慢。

我们知道 JavaScript 是一门高级语言, 并且是动态类型语言, 我们在定义一个变量时不需要关心它的类型, 并且可以随意的修改变量的类型。而在像 C++ 这样的静态类型语言中, 我们必须提前声明变量的类型并且赋予正确的值才行。也正是因为 JavaScript 没有像 C++ 那样可以事先提供足够的信息供编译器编译出更加低级的机器代码, 它只能在运行阶段收集类型信息, 然后根据这些信息进行编译再执行, 所以 JavaScript 也是解释型语言。

这也就意味着 JavaScript 要想被计算机执行, 需要一个能够快速解析并且执行 JavaScript 脚本的程序, 这个程序就是我们平时所说的 JavaScript 引擎。这里我们给出 V8 引擎的概念: V8 是 Google 基于 C++ 编写的开源高性能 Javascript 与 WebAssembly 引擎。用于 Google Chrome(Google 的开源浏览器) 以及 Node.js 等。

1.3. CPU 是如何执行机器指令的?

将高级语言转化为机器语言之后, CPU 又是怎样执行的呢? 我们以一段 C 代码为例:

int main()
{int x = 1;int y = 2;int z = x + y;return z;
}}

先来看一下以上代码被转换为机器语言是什么样子。下图左侧是用十六进制表示的二进制机器码, 中间部分是汇编代码, 右侧是指令的含义。

Main_ASM

1.4. CPU 执行机器指令的流程

  • 首先程序在执行之前会被装进内存。
  • 系统会将二进制代码中的第一条指令的地址写入到 PC 寄存器中。

CPU_Register

  • CPU 根据 PC 寄存器中的地址, 从内存中取出指令。
  • 将下一条指令的地址更新到 PC 寄存器中。
  • 分析当前取出指令, 并识别出不同的类型的指令, 以及各种获取操作数的方法。
  • 加载指令: 从内存中复制指定长度的内容到通用寄存器中, 并覆盖寄存器中原来的内容。
  • 存储指令: 将寄存器中的内容复制到内存某个位置, 并覆盖掉内存中的这个位置上原来的内容。

CPU_Mem

上图中 movl 指令后面的 %ecx 就是寄存器地址, -8(%rbp) 是内存中的地址, 这条指令的作用是将寄存器中的值拷贝到内存中。

  • 更新指令: 复制两个寄存器中的内容到 ALU 中, 也可以是一块寄存器和一块内存中的内容到 ALU 中, ALU 将两个字相加, 并将结果存放在其中的一个寄存器中, 并覆盖该寄存器中的内容。…
  • 执行指令完毕, 进入下一个 CPU 时钟周期。

1.5. V8 引擎的编译流水线

接下来我们先从宏观的角度来看一下 V8 是怎么执行 JavaScript 代码的, 然后再对每一步进行分析。

  • 初始化基础环境;
  • 解析源码生成 AST 和作用域;
  • 依据 AST 和作用域生成字节码;
  • 解释执行字节码; 监听热点代码;

V8_Runtime

1.6. 完整的分析一段 JavaScript 代码是怎样被执行的

1.6.1. 初始化基础环境

V8 执行 Js 代码是离不开宿主环境的, V8 的宿主可以是浏览器, 也可以是 Node.js。下图是浏览器的组成结构, 其中渲染引擎就是平时所说的浏览器内核, 它包括网络模块, Js 解释器等。当打开一个渲染进程时, 就为 V8 初始化了一个运行时环境。

V8_Flow

运行时环境为 V8 提供了堆空间, 栈空间、全局执行上下文、消息循环系统、宿主对象及宿主 API 等。V8 的核心是实现了 ECMAScript 标准, 此外还提供了垃圾回收器等内容。

V8_Stack

1.6.2. 解析源码生成 AST 和作用域

基础环境准备好之后, 接下来就可以向 V8 提交要执行的 JavaScript 代码了。首先 V8 会接收到要执行的 JavaScript 源代码, 不过这对 V8 来说只是一堆字符串, V8 并不能直接理解这段字符串的含义, 它需要结构化这段字符串。

function add(x, y) {var z = x+yreturn z
}console.log(add(1, 2))

比如针对如上源代码, V8 首先通过解析器 (parser) 解析成如下的抽象语法树 AST:

[generating bytecode for function: add]--- AST ---FUNC at 12. KIND 0. LITERAL ID 1. SUSPEND COUNT 0. NAME "add". PARAMS. . VAR (0x7fa7bf8048e8) (mode = VAR, assigned = false) "x". . VAR (0x7fa7bf804990) (mode = VAR, assigned = false) "y". DECLS. . VARIABLE (0x7fa7bf8048e8) (mode = VAR, assigned = false) "x". . VARIABLE (0x7fa7bf804990) (mode = VAR, assigned = false) "y". . VARIABLE (0x7fa7bf804a38) (mode = VAR, assigned = false) "z". BLOCK NOCOMPLETIONS at -1. . EXPRESSION STATEMENT at 31. . . INIT at 31. . . . VAR PROXY local[0] (0x7fa7bf804a38) (mode = VAR, assigned = false) "z". . . . ADD at 32. . . . . VAR PROXY parameter[0] (0x7fa7bf8048e8) (mode = VAR, assigned = false) "x". . . . . VAR PROXY parameter[1] (0x7fa7bf804990) (mode = VAR, assigned = false) "y". RETURN at 37. . VAR PROXY local[0] (0x7fa7bf804a38) (mode = VAR, assigned = false) "z"

V8 在生成 AST 的同时, 还生成了 add 函数的作用域:

Global scope:function add (x, y) { // (0x7f9ed7849468) (12, 47)// will be compiled// 1 stack slots// local vars:VAR y;  // (0x7f9ed7849790) parameter[1], never assignedVAR z;  // (0x7f9ed7849838) local[0], never assignedVAR x;  // (0x7f9ed78496e8) parameter[0], never assigned}

在解析期间, 所有函数体中声明的变量和函数参数, 都被放进作用域中, 如果是普通变量, 那么默认值是 undefined, 如果是函数声明, 那么将指向实际的函数对象。在执行阶段, 作用域中的变量会指向堆和栈中相应的数据。

1.6.3. 依据 AST 和作用域生成字节码

生成了作用域和 AST 之后, V8 就可以依据它们来生成字节码了。AST 之后会被作为输入传到字节码生成器 (BytecodeGenerator), 这是 Ignition 解释器中的一部分, 用于生成以函数为单位的字节码。


[generated bytecode for function: add (0x079e0824fdc1 <SharedFunctionInfo add>)]Parameter count 3Register count 2Frame size 160x79e0824ff7a @    0 : a7                StackCheck0x79e0824ff7b @    1 : 25 02             Ldar a10x79e0824ff7d @    3 : 34 03 00          Add a0, [0]0x79e0824ff80 @    6 : 26 fb             Star r00x79e0824ff82 @    8 : 0c 02             LdaSmi [2]0x79e0824ff84 @   10 : 26 fa             Star r10x79e0824ff86 @   12 : 25 fb             Ldar r00x79e0824ff88 @   14 : ab                ReturnConstant pool (size = 0)Handler Table (size = 0)Source Position Table (size = 0)

1.6.4. 解释执行字节码

和 CPU 执行二进制机器代码类似: 使用内存中的一块区域来存放字节码; 使通用寄存器用来存放一些中间数据; PC 寄存器用来指向下一条要执行的字节码; 栈顶寄存器用来指向当前的栈顶的位置。

V8_CPU_Register

  • StackCheck 字节码指令就是检查栈是否达到了溢出的上限。
  • Ldar 表示将寄存器中的值加载到累加器中。
  • Add 表示寄存器加载值并将其与累加器中的值相加, 然后将结果再次放入累加器。
  • Star 表示 把累加器中的值保存到某个寄存器中。
  • Return 结束当前函数的执行, 并将控制权传回给调用方。返回的值是累加器中的值。

1.6.5. 即时编译

在解释器 Ignition 执行字节码的过程中, 如果发现有热点代码(HotSpot), 比如一段代码被重复执行多次, 这种就称为热点代码, 那么后台的编译器 TurboFan 就会把该段热点的字节码编译为高效的机器码, 然后当再次执行这段被优化的代码时, 只需要执行编译后的机器码就可以了, 这样就大大提升了代码的执行效率。这种字节码配合解释器和编译器的技术被称为即时编译(JIT)。

JS_Bytecode

1.7. V8 的优化策略

下面我们来看一下, V8 为了提升解析和执行 Js 的速度, 做了哪些优化。由于篇幅关系, 这里只介绍 5 个优化点。

1.7.1. 重新引入字节码

早期的 V8 团队认为先生成字节码再执行字节码的方式会降低代码的执行效率, 于是直接将 JavaScript 代码编译成机器代码。这样做带来的问题有两点, 一是需要较长的编译时间, 二是产生的二进制机器码需要占用较大的内存空间。

JS_Bytecode_In

使用字节码的话虽然牺牲了一点执行效率, 但是节省了内存空间并且降低了编译时间。此外, 字节码也降低了 V8 代码的复杂度, 使得 V8 移植到不同的 CPU 架构平台更加容易。这是因为统一将字节码转换为不同平台的二进制代码要比编译器编写不同 CPU 体系的二进制代码更加容易。

1.7.2. 延迟解析

通过 V8 的编译流程我们可以看出, V8 执行 JavaScript 代码需要经过编译和执行两个阶段。

  • 编译过程: 是指 V8 将 JavaScript 代码转换为字节码, 或者二进制机器代码的阶段。
  • 执行阶段: 是指解释器解释执行字节码, 或者是 CPU 直接执行二进制机器代码的阶段。

V8 并不会一次性将所有的 JavaScript 解析为中间代码, 这主要是基于以下两点:

  • 如果一次解析和编译所有的 JavaScript 代码, 过多的代码会增加编译时间, 这会严重影响到首次执行 JavaScript 代码的速度, 让用户感觉到卡顿。
  • 其次, 解析完成的字节码和编译之后的机器代码都会存放在内存中, 如果一次性解析和编译所有 JavaScript 代码, 那么这些中间代码和机器代码将会一直占用内存。

延迟解析是指解析器在解析的过程中, 如果遇到函数声明, 那么会跳过函数内部的代码, 并不会为其生成 AST 和字节码。

1.7.3. 隐藏类

我们可以结合一段代码来分析下隐藏类是怎么工作的:

let point = {x:100,y:200}

当 V8 执行到这段代码时, 会先为 point 对象创建一个隐藏类, 在 V8 中, 把隐藏类又称为 map, 每个对象都有一个 map 属性, 其值指向内存中的隐藏类。隐藏类描述了对象的属性布局, 它主要包括了属性名称和每个属性所对应的偏移量, 比如 point 对象的隐藏类就包括了 x 和 y 属性, x 的偏移量是 4, y 的偏移量是 8。

V8_Hide

有了隐藏类之后, 那么当 V8 访问某个对象中的某个属性时, 就会先去隐藏类中查找该属性相对于它的对象的偏移量, 有了偏移量和属性类型, V8 就可以直接去内存中取出对应的属性值, 而不需要经历一系列的查找过程, 那么这就大大提升了 V8 查找对象的效率。

1.7.4. 快属性与慢属性

当我们在控制台输入如下代码时:

function Foo() {this[100] = 'test-100'this[1] = 'test-1'this["B"] = 'bar-B'this[50] = 'test-50'this[9] =  'test-9'this[8] = 'test-8'this[3] = 'test-3'this[5] = 'test-5'this["A"] = 'bar-A'this["C"] = 'bar-C'
}var bar = new Foo()for(key in bar){console.log(`index:${key}  value:${bar[key]}`)
}

打印出来的结果如下:

index:1  value:test-1index:3  value:test-3index:5  value:test-5index:8  value:test-8index:9  value:test-9index:50  value:test-50index:100  value:test-100index:B  value:bar-Bindex:A  value:bar-Aindex:C  value:bar-C

之所以出现这样的结果, 是因为在 ECMAScript 规范中定义了数字属性应该按照索引值大小升序排列, 字符串属性根据创建时的顺序升序排列。

  • 数字属性称为排序属性, 在 V8 中被称为 elements。
  • 字符串属性就被称为常规属性, 在 V8 中被称为 properties。

下面我们执行这样一段代码, 看一看当对象中的属性数目发生变化时, 其在内存中结构是怎样变化的。

function Foo(property_num,element_num) {// 添加排序属性for (let i = 0; i < element_num; i++) {this[i] = `element${i}`}// 添加常规属性for (let i = 0; i < property_num; i++) {let ppt = `property${i}`this[ppt] = ppt}
}var bar = new Foo(10,10)

将 Chrome 开发者工具切换到 Memory 标签, 然后点击左侧的小圆圈就可以捕获以上代码的内存快照, 最终截图如下所示:

Chrome_Debug_Memory

将创建的对象属性的个数调整到 20 个:

var bar2 = new Foo(20,10)

Chrome_Elements

总结: 当对象中的属性过多时, 或者存在反复添加或者删除属性的操作, 那么 V8 就会将线性的存储模式 (快属性) 降级为非线性的字典存储模式(慢属性), 这样虽然降低了查找速度, 但是却提升了修改对象的属性的速度。

1.7.5. 内联缓存

我们再来看一段这样的代码。

function loadX(o) {o.y = 4return o.x
}var o = { x: 1,y:3}
var o1 = { x: 3 ,y:6}for (var i = 0; i < 90000; i++) {loadX(o)loadX(o1)
}

通常 V8 获取 o.x 的流程是这样的: 查找对象 o 的隐藏类, 再通过隐藏类查找 x 属性偏移量, 然后根据偏移量获取属性值, 在这段代码中 loadX 函数会被反复执行, 那么获取 o.x 流程也需要反复被执行。为了提升对象的查找效率。V8 执行的策略就是使用内联缓存 (Inline Cache), 简称为 IC。IC 会为每个函数维护一个反馈向量 (FeedBack Vector), 反馈向量记录了函数在执行过程中的一些关键的中间数据。然后将这些数据缓存起来, 当下次再次执行该函数时, V8 就可以直接利用这些中间数据, 节省了再次获取这些数据的过程。V8 会在反馈向量中为每个调用点分配一个插槽(Slot), 比如 o.y = 4 和 return o.x 这两段就是调用点 (CallSite), 因为它们使用了对象和属性。每个插槽中包括了插槽的索引 (slot index)、插槽的类型 (type)、插槽的状态 (state)、隐藏类 (map) 的地址、还有属性的偏移量, 比如上面这个函数中的两个调用点都使用了对象 o, 那么反馈向量两个插槽中的 map 属性也都是指向同一个隐藏类的, 因此这两个插槽的 map 地址是一样的。

JS_Slot_Map

通过内联缓存策略, 就能够提升下次执行函数时的效率, 但是这有一个前提, 那就是多次执行时, 对象的形状是固定的, 如果对象的形状不是固定的, 这意味着 V8 为它们创建的隐藏类也是不同的。面对这种情况, V8 会选择将新的隐藏类也记录在反馈向量中, 同时记录属性值的偏移量, 这时, 反馈向量中的一个槽里就会出现包含了多个隐藏类和偏移量的情况, 如果超过 4 个, 那么 V8 会采取 hash 表的结构来存储。讲到这里我的分享就结束了, 如果有不足之处欢迎大家多多批评指正。

1.8. 参考链接

  • https://www.cnblogs.com/nickchen121/p/10722720.html
  • https://v8.dev/docs
  • https://juejin.cn/post/6844904161163608078
  • https://time.geekbang.org/column/article/211682
  • https://www.jianshu.com/p/e4a75cb6f268

http://chatgpt.dhexx.cn/article/nThTCTCO.shtml

相关文章

bootstrap+javascript制作体重标准计算器

一、表单编辑 使用bootstrap可以快速创建表单样式。needs-validation 可以验证表单&#xff0c;如果验证不通过&#xff0c;表单不会提交&#xff1b; onSubmit"return false;"使得表单提交不会刷新页面&#xff1b; 下方js代码是用来控制表单验证&#xff1b; &l…

如何白嫖world.js、china.js以及各个省的js和json文件

全是干货&#xff0c;看仔细了。 作者在工作中遇到了绘制世界地图的场景&#xff0c;但是由于现在echarts的官网已经不提供地图文件下载了。 所以需要自己找资源&#xff0c;在csdn中用积分下载的资源还是挺多的。 但是碍于个别同学没有积分或者喜欢白嫖&#xff0c;所以这里…

js中?.、??的具体用法

1、?. &#xff08;可选链运算符&#xff09; 在javascript中如果一个值为null、undefined&#xff0c;直接访问下面的属性&#xff0c; 会报 Uncaught TypeError: Cannot read properties of undefined 异常错误。 而在真实的项目中是会出现这种情况&#xff0c;有这个值就…

JS之BigNumber.js 讲解

JavaScript因为存在计算的精度问题,所以直接计算就可能会导致各种各样的bug,为了解决这个问题,就要使用BigNumber.js这个库。 至于为什么JavaScript会有精度问题呢,可以看https://github.com/camsong/blog/issues/9。简单来说就是因为:JavaScript中所有的数字(包括整数和…

Js运动

JavaScript之Js运动 在我们进行web开发的过程中&#xff0c;为了与用户进行更加友好、有趣的交互&#xff0c;拥有一套完美的运动框架就能轻松解决。 这次就给大家带来Javascript学习中Js运动的编写和注意事项。实现运动的思想很简单&#xff0c;首先对这个元素获取定位&…

更优雅的编写JavaScript,使用这些函数秒变大神

如果你刚接触JavaScript可能你还没有听说过.map()&#xff0c;.reduce()&#xff0c;.filter()。或者听说过&#xff0c;看过别人用过但是自己在实际项目中没有用过。在国内很多开发项目都是需要考虑IE8的兼容&#xff0c;为了兼容很多JavaScript好用的方法和技巧都被埋没了。但…

JavaScript 计算标准体重的公式

判断标准体重 世卫计算方法&#xff1a; 男性&#xff1a;(身高cm&#xff0d;80)70&#xfe6a;标准体重 女性&#xff1a;(身高cm&#xff0d;70)60&#xfe6a;标准体重 标准体重正负10&#xfe6a;为正常体重 标准体重正负10&#xfe6a;~ 20&#xfe6a;为体重过重或…

ping命令显示时间

awk显示ping的时间ping 127.0.0.1 | awk { print $0"\t" strftime("%Y-%m-%d %H:%M:%S",systime()) } 注释&#xff1a;\t //换行字符 $0 //打印整行{print $0 "\t"} //逐行打印 strftime&#xff08;&#xff09;//时间函数。一般配合系统时间函…

Linux Command date 显示时间

Linux Command date 显示时间 文章目录 Linux Command date 显示时间1. 简介2. 参数3. 日期格式4. 实例 1. 简介 命令功能&#xff1a;date 可以用来显示或设定系统的日期与时间。 2. 参数 -d<字符串>&#xff1a;显示字符串所指的日期与时间。字符串前后必须加上双引…

网页显示时间代码

网页显示时间代码如下&#xff1a; <test.html> <html> <body> <SCRIPT languagejavascript> function CurentTime(){var now new Date();var hh now.getHours();var mm now.getMinutes();var ss now.getTime() % 60000;var ms ss % 1000;ss (s…

android 显示系统时间,Android 实时获取当前时间并显示

1、首先创建子线程与主线程进行数据交互的Handler &#xff0c;并更新UI SuppressLint("HandlerLeak") private Handler mHandler new Handler() { Override public void handleMessage(Message msg) { switch (msg.what) { case UPDATE_TIME: String time (String…

用Android studio完成简单的显示时间

用用Android studio完成简单的显示时间&#xff0c;并完成基础的布局改变&#xff0c;如字体大小&#xff0c;字体颜色等等问题。 在value中&#xff0c;颜色设置&#xff0c;可以自定义颜色。 dimens中完成字体大小的设置。 完成日历设置&#xff1a; package com.qst.Ca; im…

windows系统ping包显示时间(绝对好用)

使用管理员加打windows10中的Windows PowerShell&#xff0c;使用以下命令开始ping 例如ping192.168.0.1&#xff1a; ping.exe -t 192.168.0.1 |Foreach{"{0} - {1}" -f (Get-Date),$_}运行效果&#xff1a; 如果要ping又要记录到文本文档&#xff1a; ping.ex…

HTML页面显示时间——网页数字时钟、钟表

HTML页面显示时间——网页数字时钟、钟表 一个HTML网页上动态显示系统时间&#xff0c;可以使用javascript的Date对象&#xff0c;在javascript中new 一个date对象&#xff0c;并且根据这个date对象获取相应的时间日期的具体日期时间&#xff0c;比如 年 月 日 时分秒&#xff…

怎么将计算机工具栏时间去除,电脑任务栏不显示日期只显示时间的详细处理方法...

通常情况下&#xff0c;电脑右下角是会同时显示日期和时间的&#xff0c;这样可以方便我们查看。可是最近有用户在使用电脑时&#xff0c;却遇到了任务栏不显示日期只显示时间的问题&#xff0c;不知道怎么回事&#xff0c;更加不清楚如何解决&#xff0c;那么对于这一情况&…

Linux之history命令显示时间和IP

文章目录 一、需求说明二、配置步骤1、临时显示操作时间2、永久设置 三、补充说明1、HISTFILESIZE和HISTSIZE2、显示命令执行用户3、格式化输出参数配置4、历史命令保存位置 一、需求说明 系统运维工作中我们有时候需要查看历史命令&#xff0c;可以通过history命令查看&#x…

cmd长ping记录日志和时间_ping命令结果中如何显示时间

展开全部 可以62616964757a686964616fe78988e69d8331333431373266通过批处理实现&#xff0c;在后面加上时间。 echo off d: for /l %%i in (1,1,%999999999%) do ( ping %1192.168.1.1 -n 1% && echo %% >>ping.txt date /t >>ping.txt time /t >>p…

Vue中如何动态显示时间?

1&#xff1a;先上效果图&#xff1a; 2&#xff1a;注意点&#xff1a; &#xff08;1&#xff09;created( ) 与 mounted( )的区别就是&#xff1a; created 在模板渲染成html前调用&#xff0c;即通常初始化某些属性值&#xff0c;然后再渲染成视图 mounted在…

QT学习 实时显示时间

今天完成一个实时显示时间的小demo 先上DJ先上DJ 先看一下效果 以两种形式显示当前具体时间 先附上代码&#xff0c;再总结一下核心代码 &#xff08;1&#xff09; myweather.ui文件 创建一个Label&#xff0c;ObjectName值为text&#xff1b;创建一个LCD Number&#xff…

安卓实时显示时间

安卓开发过程中&#xff0c;有时候会用到实时的显示当前时间的功能&#xff0c;比如&#xff1a;自定义的状态栏就需要实时的更新当前时间&#xff0c;看下面图就是自定义的状态栏实时的更新时间&#xff1a; 实时显示更新时间代码&#xff1a;TimeThread.java import android.…