《WebKit 技术内幕》学习之九(1): JavaScript引擎

1 概述

1.1 JavaScript语言

        说起JavaScript语言,又要讲一个典型的从弱小到壮大的奋斗史。起初,它只是一个非常不起眼的语言,用来处理非常小众的问题。所以,从设计之初,它的目标就是解决一些脚本语言的问题,因为设计的能力有限,性能不需要重点考虑。因为使用场景少,所以用户对于性能的要求也比较低。在过去几年,由于Web时代的来临和HTML5的兴起,这一切让JavaScript前所未有地成为焦点,自然它的功能和性能在大家的努力下都有了长足的进步。

        JavaScript是一种脚本语言,主要用在Web的客户端(这样说也不准确,Node.js和其他一些用法的出现就是例外),它的出现是为了控制网页客户端的逻辑,例如同用户的交互、异步通信等需求。当然,在HTML5高速发展的今天,它的作用越来越大,被广泛地使用在各种其他技术中。

        本质上它是一种解释型语言(不知道按照现在的实现来看,这么说是不是准确),函数是它的第一等公民,也就是函数也能够当作参数或者返回值来传递。示例代码9-1是一个简单的例子,读者可以看到一个简单的函数“getOperation”,它的返回值就是一个匿名函数。

        示例代码9-1 函数作为函数的返回值

    function getOperation() {
     return function () {
       print("JavaScript");
     };
    }

        JavaScript语言的另一个重大特点就是,它是一种无类型语言,或者说是动态类型语言。相比较而言,C++或者Java等语言都是静态类型语言,它们在编译的时候就能够知道每个变量的类型。但是,JavaScript的语言特性让我们没有办法在编译的时候知道变量的类型,所以只能在运行的时候才能确定,这导致JavaScript语言的规范面临着性能方面的巨大压力。在运行时计算和决定类型,会带来很严重的性能损失,这导致了JavaScript语言的运行效率比C++或者Java都要低很多。

        先看示例代码9-2所示的一个简单的JavaScript代码,它是一个简单得不能再简单的包含两个参数的JavaScript函数,其目的就是计算参数a的属性为x的值与参数b的属性为y的值的和。

示例代码9-2 一个简单的JavaScript函数

    function add(a, b) {
     return a.x*a.y + b.x*b.y;  // 这里对象a和b的类型未知
    }

        问题来了,当JavaScript引擎分析到该段代码的时候,根本没有办法知道a和b是什么类型,唯一的办法就是运行的时候根据实际传递过来的对象再来计算。读者可能会好奇,这好像并没什么特别的嘛,事实上这会导致严重的性能问题。

        让我们来简单解释一下为什么静态类型能够大量地节省运行时间。示例代码9-3是一个简单的C++函数,它同9-2类似,不同之处在于参数必须指定类型。

        示例代码9-3 一个简单的C++函数

 
int add(Class1 a, Class1 b) {class Class1 {
return a.x*a.y + b.x*b.y;int x;
}int y;
}

        当编译示例代码9-3中左边部分的时候,根据右边部分类型Class1的定义,获取对象a的属性x的时候,其实就是对象a的地址,大小是一个整形。同时获取对象b的属性y的时候,其实就是对象b的地址加上4个字节(不同的平台上可能不同,但是一旦平台确定,其值是固定的),这些都是在生成本地代码的时候确定的,无须在运行本地代码的时候决定它们的地址和类型是什么,这显然能够节省时间。

        图9-1 示例代码9-3中的类和对象的结构表示

        图9-1中最右侧表示的是类Class1的属性对应的地址信息,在编译阶段,编译器根据int类型来决定属性占用4个字节,地址就是对象的地址,因为偏移量为0。所以对于y来说,访问它只需要将对象地址加上4个字节即可,也就是偏移量为4。所以在编译的时候,能够确定访问对象a中属性的偏移量,根据这些信息,可以生成相应的汇编代码。其中的符号信息,例如字符“x”和“y”运行时都不再需要,因为不再需要额外的查找这些属性地址的工作。在C++和Java等语言中,已事先知道所存取的成员变量(类)类型,所以语言解释系统(Interpreting System)只要利用数组和位移来存取这些变量和方法的地址等。位移信息使它只要几个机器语言指令,就可以存取变量、找出变量或执行其他任务。

        现在继续回到JavaScript代码中来,对于传统的JavaScript解释器,所有这一切都是解释执行,所以效率不会高到哪去。不管是解释器还是现在更为高效的JIT(Just-In-Time)技术,面临的难题都是类型问题。现在我们也将JavaScript代码的处理分成两个阶段,就是编译阶段(虽然跟传统的编译有些不同)和执行阶段。对于JavaScript引擎来说,因为没有C++或者Java这样的强类型语言的类型信息,所以JavaScript引擎通常的做法就是如图9-2所表示的方法来存储每一个对象。

                        图9-2 示例代码9-2的对象a和b的结构表示

        基本的工作方式是这样,当创建对象a的时候(这个当然是执行阶段),如果它包含两个属性(根据JavaScript的语言特性,没有类型,而且这些属性都是动态创建的,属性就是前面说的C++的类成员变量),那么引擎会为它们创建如图9-2左边所示的结构,也就是属性名-属性值对,需要强调的是这些属性名(典型的做法就是采用字符串)都是会被保存的,因为之后访问该对象的属性值时需要通过属性名匹配来获取相应的值。读者看到对象b是同样的结构,也同样保存相同的属性,因为JavaScript没有类型,所以每个对象需要自己保存这些信息。在降低性能的同时,读者也会发现它们存在内容冗余的部分,比如对象a和对象b都保存相同的属性名,随着对象的增多,这显然会带来空间上的巨大浪费。

        追根究底,这里的目的获取对象属性值的具体位置,也就是相对于对象基地址的偏移位置。从这个角度来看,JavaScript和C++语言(下面的解释需要对C++语言有一些基本的认识)上的区别包括以下几个部分。

  • 编译确定位置 :C++有明确的两个阶段,而编译这些位置的偏移信息都是编译器在编译的时候就决定了的,当C++代码编译成本地代码之后,对象的属性和偏移信息都计算完成。因为JavaScript没有类型,所以只有在对象创建的时候才有这些信息,因而只能在执行阶段确定,而且JavaScript语言能够在执行时修改对象的属性(不是属性值,而是添加或者删除属性本身)。
  • 偏移信息共享 :C++因为有类型定义,所以所有对象都是按照该类型来确定的,而且不能在执行的时候动态改变类型,因为这些对象都是共享偏移信息的。访问它们只需要按照编译时确定的偏移量即可。而对于C++模板的支持,其实是多份代码,因为本质上其道理是相同的。JavaScript则不同,每个对象都是自描述,属性和位置偏移信息都包含在自身的结构中。
  • 偏移信息查找 :C++中查找偏移地址很简单,都是在编译代码时,对使用到某类型的成员变量直接设置偏移量。而对于JavaScript,使用到一个对象则需要通过属性名匹配才能查找到对应的值,这实在太费时间了。

        对于这个问题读者可能觉得其对性能的影响不大,其实不是这样。因为对象属性的访问非常普遍而且次数非常频繁,而通过偏移量来访问值并且知道该值的类型,使用少数两个汇编指令就能完成,但是,对于图9-2中的通过属性名来匹配对于性能造成的影响可能会多很多倍,因为属性名匹配需要特别长的时间,而且额外浪费很多内存空间。

        有方法解决这一问题吗?答案是肯定的。当然要达到跟C++和Java一样的效率很难,但是已经有很多方法能够逐步接近了,笔者在介绍JavaScriptCore引擎和V8引擎的时候再论述它们,因为这些新技术的确带来了性能上的巨大进步。

        推动JavaScript运行速度提高的另一大利器是JIT(Just-In-Time)技术,它不是一项全新的技术,其作用是解决解释性语言的性能问题,主要思想是当解释器将源代码解释成内部表示的时候(Java字节码就是一个典型例子),JavaScript的执行环境不仅是解释这些内部表示,而且将其中一些字节码(主要是使用率高的部分)转成本地代码(汇编代码),这样可以被CPU直接执行,而不是解释执行,从而极大地提高性能。JIT技术被广泛地使用在各种语言的执行环境中,例如Java虚拟机,经过长时间的演进之后,目前使用在JavaScript的众多引擎中,例如JavaScriptCore、V8、SpiderMonkey等中。

        下面要说的是JavaScript的作用域链和闭包等概念,它们非常重要,这两个概念带来了编程上的便易性和模块化,本节主要讲述它们的原理,后面会介绍它们是如何被实现的。

        首先介绍一个学术解释,“闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分”。通俗来说,就是当执行到一条语句的时候,哪些对象(或者其他环境因素)能够被使用。JavaScript使用作用域链来实现闭包,作用域链由执行环境维护,JavaScript中所有的标识符都是通过作用域链来查找值的。用示例来解释它们比较清楚,示例代码9-4是两段功能类似但是影响却不同的常见JavaScript代码,下面结合闭包和作用域链来分析它们。

        假设这一段代码被保存在一个单独的JS文件中,当某个包含该JS文件的网页运行在浏览器中的时候,JavaScript已经预先创建好一个全局的域,该域会包含一个全局的上下文,该上下文可能包含window、navigator(网页中)等内置的对象,同时也包含当前执行位置的一些信息。例如代码9-4中的第一行,当执行到该行时,就定义了“me”并赋值1,上下文就包含了一个“me”变量,接下来的语句就能够使用该变量。图9-3中的全局上下文就包含这些信息。

 图9-3 示例代码9-4中左侧所涉及的作用域链

示例代码9-4 使用闭包技术的JavaScript函数

 
var me = 1;var me = 1;
function add(x) {(function (x) {
var me = 2;var me = 2;
function internal() {function internal() {
return me + x;return me + x;
}}
return internal() + 3;    return internal() + 3;
}})(1);
add(1);

        当执行到左边第二行的时候,函数“add”也被加入全局上下文中(事实上,这有两个阶段,在之前的阶段,“add”已经被加入上下文中,所以在“add”函数声明之前使用它也是可以的),所有的代码都能够使用它。如果不巧在它之前也有同样名为“add”的函数,那么之前的函数会被覆盖。所以,假如我们并不希望“add”被其他地方使用,而且不要覆盖之前的函数,因为这样会污染全局空间,造成不必要的麻烦。一个正确的做法是示例代码9-4中右侧的使用方法,稍后做介绍。

        在“add”函数中,执行环境同样会建立一个该函数的上下文,包含该函数中的心理,例如第三行,又是一个变量“me”。该上下文同时会指向全局上下文。继续看代码,到函数“internal”内部,同样如此,执行环境也会为它建立一个上下文,如图9-3中右下角的上下文,指向它的父上下文,这样其实就形成了一个作用域链。在“internal”函数内部,它使用了变量“me”。首先,执行环境检查当前的上下文,查找有无变量“me”,当然在本例中无法找到,于是它会接着在它的父上下文中查找,显然“add”函数的上下文中包含“me”,所以不需要继续向上查找。如果“add”函数上下文中没有包含该变量,那么执行环境会不停向上查找,直到找遍全局上下文为止。

        下面解释示例代码9-4右侧函数的好处。当包含一个.js文件的时候,它的全局函数在其他.js文件中也可见,这直接导致名字冲突和模块化问题。因为没有C++的名空间机制和Java的包机制,每个.js文件中的函数命名可能相同,这直接导致名冲突。当开发者只是希望该“add”函数在内部使用的时候,那么他可以像右侧一样使用一个匿名函数,然后直接调用它,这样这个函数就不会污染全局空间。同时,匿名函数内部也使用了一个内部函数“internal”。根据前面介绍的作用域链技术,“internal”函数只在该匿名函数内部有效,完全不会影响其他代码,这里使用的就是闭包技术。

1.2 JavaScript引擎

        什么是JavaScript引擎?简单来讲,就是能够将JavaScript代码处理并执行的运行环境。要解释这一概念,需要了解一些编译原理的基础概念和现代语言需要的一些新编译技术。

        首先来看C/C++语言。由前面描述可知,处理该语言通常的做法实际上就是使用编译器直接将它们编译成本地代码,这一切都是由开发人员在代码编写完成之后实施的,如图9-4所示。用户只是使用这些编译好的本地代码,被系统的加载器加载执行,这些本地代码由操作系统调度CPU直接执行,无须额外处理。

图9-4 C++编译器生成本地代码的过程

        其次,来看看Python等脚本语言。处理脚本语言通常的做法是开发者将写好的代码直接交给用户,用户使用脚本的解释器将脚本文件加载然后解释执行,如图9-5所示。当然,现在Python也可以支持将脚本编译生成中间表示。但是,通常情况下,脚本语言不需要开发人员去编译脚本代码,这主要是因为脚本语言对使用场景和性能的要求与其他类型的语言不同。

图9-5 解释器解释执行过程

        然后来看看Java语言。其做法是明显的两个阶段,首先是像C++语言一样的编译阶段,但是,同C++编译器生成的本地代码结果不同,Java代码经过编译器编译之后生成的是字节码,字节码是跨平台的一种中间表示,不同于本地代码。该字节码与平台无关,能够在不同操作系统上运行。在运行字节码阶段,Java的运行环境是Java虚拟机加载字节码,使用解释器执行这些字节码。如果仅是这样,那Java的性能就比C++差太多了。现代Java虚拟机一般都引入了JIT技术,也就是前面说的将字节码转变成本地代码来提高执行效率。图9-6描述这两个阶段,第一阶段对时间要求不严格,第二阶段则对每个步骤所花费的时间非常敏感,时间越短越好。

图9-6 Java代码的编译和执行过程

        最后回到JavaScript语言上来。前面已经说了它是一种解释性脚本语言。但是,众多工程师不断投入资源来提高它的速度使得它能够使用Java虚拟机和C++编译器中的众多技术来改进工作方式。早期也是由解释器来解释即可,就是将源代码转变成抽象语法树,然后在抽象语法树上解释执行。随着将Java虚拟机的JIT技术引入,现在的做法是将抽象语法树转成中间表示(也就是字节码),然后通过JIT技术转成本地代码,这能够大大地提高执行效率。当然也有些直接从抽象语法树生成本地代码的JIT技术,例如V8。这是因为JavaScript跟Java还是有以下一些区别的。

        其一是类型。JavaScript是无类型的语言,其对于对象的表示和属性的访问比Java存在更大的性能损失。不过现在已经出现了一些新的技术,参考C++或者Java的类型系统的优点,构建隐式的类型信息,这些后面将逐一介绍。

        其二,Java语言通常是将源代码编译成字节码,这同执行阶段是分开的,也就是从源代码到抽象语法树再到字节码这段时间的长短是无所谓的(或者说不是特别重要),所以尽可能地生成高效的字节码即可。而对于JavaScript而言,这些都是在网页和JavaScript文件下载后同执行阶段一起在网页的加载和渲染过程中来实施的,所以对它们的处理时间也有着很高的要求。

        图9-7描述了JavaScript代码执行的过程,这一过程中因为不同技术的引入,导致其步骤非常复杂,而且因为都是在代码运行过程中来处理这些步骤,所以每个阶段的时间越少越好,而且每引入一个阶段都是额外的时间开销,可能最后的本地代码执行效率很高,但是如果之前的步骤耗费太多时间,最后的执行结果可能并不会好。所以不同的JavaScript引擎选择了不同的路径,这里先不仔细介绍,后面再描述它们。

                图9-7 JavaScript代码的编译和执行过程

所以一个JavaScript引擎不外乎包括以下几个部分。

  • 编译器 。主要工作是将源代码编译成抽象语法树,在某些引擎中还包含将抽象语法树转换成字节码。
  • 解释器 。在某些引擎中,解释器主要是接收字节码,解释执行这个字节码,同时也依赖垃圾回收机制等。
  • JIT工具 。一个能够能够JIT的工具,将字节码或者抽象语法树转换成本地代码,当然它也需要依赖牢记
  • 垃圾回收器和分析工具(ProfiIer) 。它们负责垃圾回收和收集引擎中的信息,帮助改善引擎的性能和功效。

1.3 JavaScript引擎和渲染引擎

        前面介绍了网页的工作过程需要使用两个引擎,也就是渲染引擎和JavaScript引擎。从模块上看,它们是两个独立的模块,分别负责不同的事情:JavaScript引擎负责执行JavaScript代码,而渲染引擎负责渲染网页。JavaScript引擎提供调用接口给渲染引擎,以便让渲染引擎使用JavaScript引擎来处理JavaScript代码并获取结果。这当然不是全部,事情也不是这么简单,JavaScript引擎需要能够访问渲染引擎构建的DOM树,所以JavaScript引擎通常需要提供桥接的接口,而渲染引擎则根据桥接接口来提供让JavaScript访问DOM的能力。在现在众多的HTML5能力中,很多都是通过JavaScript接口提供给开发者的,所以这部分同样需要根据桥接接口来实现具体类,以便让JavaScript引擎能够回调渲染引擎的具体实现。图9-8描述了两种引擎之间的相互调用关系。

                        图9-8 渲染引擎和JavaScript引擎的关系

        在WebKit中,两种引擎通过桥接接口来访问DOM结构,这对性能来说是一个重大的损失因为每次JavaScript代码访问DOM都需要通过复杂和低效的桥接接口来完成。鉴于访问DOM树的普遍性,这是一个常见的问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

rancher和k8s接口地址,Kubernetes监控体系,cAdvisor和kube-state-metrics 与 metrics-server

为了能够提前发现kubernetes集群的问题以及方便快捷的查询容器的各类参数,比如,某个pod的内存使用异常高企 等等这样的异常状态(虽然kubernetes有自动重启或者驱逐等等保护措施,但万一没有配置或者失效了呢)&#xff0…

yum仓库和NFS文件共享服务

一、yum仓库简介: 1.yum仓库简介: yum是一个基于RPM包(是Red-Hat Package Manager红帽软件包管理器的缩写)构建的软件更新机制,能够自动解决软件包之间的依赖关系。解决了日常工作中的大量查找安装依赖包的时间 为什…

html 3D 倒计时爆炸特效

下面是代码&#xff1a; <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>HTML5 Canvas 3D 倒计时爆炸特效DEMO演示</title><link rel"stylesheet" href"css/style.css" media"screen&q…

【Linux C | 进程】创建进程 | vfork函数+exec函数,以及system函数——文中很多C语言例子帮助理解

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

深入剖析Python最强大图片处理模块——Pillow

目录 一、引言 二、Pillow的功能与特点 三、Pillow的应用场景 四、代码示例 五、Pillow与其他图像处理库的对比 六、结论 一、引言 在Python中&#xff0c;Pillow是用于图像处理的最强大和最流行的库之一。它提供了丰富的功能&#xff0c;使得用户可以轻松地处理、编辑和…

【RT-DETR有效改进】 | 主干篇 | EfficientViT高效的特征提取网络完爆MobileNet系列(轻量化网络结构)

前言 大家好&#xff0c;我是Snu77&#xff0c;这里是RT-DETR有效涨点专栏。 本专栏的内容为根据ultralytics版本的RT-DETR进行改进&#xff0c;内容持续更新&#xff0c;每周更新文章数量3-10篇。 专栏以ResNet18、ResNet50为基础修改版本&#xff0c;同时修改内容也支持Re…

HTTPS基本概念

HTTP 与 HTTPS 有哪些区别&#xff1f; HTTP 是超文本传输协议&#xff0c;信息是明文传输&#xff0c;存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷&#xff0c;在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议&#xff0c;使得报文能够加密传输。HTTP 连接建立相…

国家急救日倡议活动暨120急救大课堂公益培训在京成功举办

2024年1月20日&#xff0c;由国家卫生健康委员会等多个相关部门指导&#xff0c;中国医院协会急救中心&#xff08;站&#xff09;分会主办&#xff0c;北京急救中心承办的“国家急救日”倡议活动暨急救科普大课堂公益培训系列活动&#xff0c;在全国范围内启动。 健康中国行动…

Kotlin程序设计 扩展篇(一)

Kotlin程序设计&#xff08;扩展一&#xff09; **注意&#xff1a;**开启本视频学习前&#xff0c;需要先完成以下内容的学习&#xff1a; 请先完成《Kotlin程序设计》视频教程。请先完成《JavaSE》视频教程。 Kotlin在设计时考虑到了与Java的互操作性&#xff0c;现有的Ja…

源 “MySQL 5.7 Community Server“ 的 GPG 密钥已安装,但是不适用于此软件包。请检查源的公钥 URL 是否配置正确

Is this ok [y/d/N]: y Downloading packages: 警告&#xff1a;/var/cache/yum/x86_64/7/mysql57-community/packages/mysql-community-server-5.7.44-1.el7.x86_64.rpm: 头V4 RSA/SHA256 Signature, 密钥 ID 3a79bd29: NOKEY 从 file:///etc/pki/rpm-gpg/RPM-GPG-KEY-mysql 检…

加速社区数字化转型:物业app开发的最新趋势

在当今数字化时代&#xff0c;社区数字化转型已经成为业界焦点。特别是在物业管理领域&#xff0c;物业app开发正成为加速社区数字化转型的关键趋势。本文将探讨物业app开发的最新趋势&#xff0c;以及如何通过这些趋势推动社区数字化转型。 物业app开发的关键趋势 随着智能手…

postman测试导入文件

01 上传文件参数 1.选择请求方式 选择post请求方式&#xff0c;输入请求地址 2.填写Headers Key&#xff1a;Content-Type &#xff1b; Value&#xff1a;multipart/form-data 如下图 3.填写body 选择form-data&#xff0c;key选择file类型后value会出现按钮&#xff0…

(十二)Head first design patterns代理模式(c++)

代理模式 代理模式&#xff1a;创建一个proxy对象&#xff0c;并为这个对象提供替身或者占位符以对这个对象进行控制。 典型例子&#xff1a;智能指针... 例子&#xff1a;比如说有一个talk接口&#xff0c;所有的people需要实现talk接口。但有些人有唱歌技能。不能在talk接…

[AutoSar]BSW_OS 06 Autosar OS_Alarms

一、 目录 一、关键词平台说明一、Timer1.1 配置1.2Periodical Interrupt Timer (PIT)和High Resolution Timer (HRT) 二、Alarm 工作机制三、Code3.1创建一个15ms的runnable3.2mapping到basic task3.3生成代码 关键词 嵌入式、C语言、autosar、OS、BSW 平台说明 项目ValueO…

【开源】基于JAVA语言的停车场收费系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 停车位模块2.2 车辆模块2.3 停车收费模块2.4 IC卡模块2.5 IC卡挂失模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 停车场表3.2.2 车辆表3.2.3 停车收费表3.2.4 IC 卡表3.2.5 IC 卡挂失表 四、系统实现五、核心代码…

2024.1.22(150. 逆波兰表达式求值)

2024.1.22(150. 逆波兰表达式求值) 相信看完动画大家应该知道&#xff0c;这和1047. 删除字符串中的所有相邻重复项是差不错的&#xff0c;只不过本题不要相邻元素做消除了&#xff0c;而是做运算&#xff01; // 定义一个Solution类 class Solution { // 定义一个公共方法…

手机上菜谱记录簿在哪 用备忘录放大看菜谱更清晰

作为一个热爱生活的现代人&#xff0c;我深知健康饮食的重要性。然而&#xff0c;每当我想亲手为自己和家人烹饪美食时&#xff0c;厨艺的不精常常让我望而却步。好在互联网时代&#xff0c;网上搜罗的各式菜谱成了我的救星。但问题是&#xff0c;每次做菜时都得反复查找&#…

幻兽帕鲁专用服务器

随着幻兽帕鲁这款游戏的热度持续升温&#xff0c;我们遍寻全网&#xff0c;带给各位玩家一个全新的、高品质的游戏体验——莱卡云服务器。有幻兽帕鲁的热衷者们无需再为了服务器的选取困扰&#xff0c;因为我们可以肯定地说&#xff1a;选择莱卡云&#xff0c;你不会失望。 首先…

尝试着在Stable Diffusion里边使用SadTalker进行数字人制作

首先需要标明的是&#xff0c;我这里是图片说话类型&#xff0c;而且是看了知识星球AI破局俱乐部大航海数字人手册进行操作的。写下这篇文章是防止我以后遗忘。 我使用的基础软件是Stable Diffusion&#xff0c;SadTalker是作为插件放进来的&#xff0c;需要注意的是这对自己的…

用户资源(菜单)控制学习使用

效果图 第一步 需要再定义常量资源 //信访听证 资源前缀public static final String RESPREFIX_MODULE_XINFTZ_"module_xinftz_";//听证专家库public static final ConstantItem RES_MODULE_XINFTZ_TINGZZJK new ConstantItem(RESPREFIX_MODULE_XINFTZ_ "tin…