[번역] Angular DOM 업데이트 메커니즘 - Laravel/Angular 기술 공유
이 글에서는 주로 angularjs dom의 업데이트 메커니즘을 소개하고,angularjs의 모델 표현 및 프로그램 내부 아키텍처에 대한 많은 지식을 소개합니다.
angularjs의 모델 표현 공식:
DOM 업데이트 모델 변경으로 인해 발생하는 것은 모든 프런트엔드 프레임워크의 중요한 기능입니다(참고: 모델과 뷰의 동기화 유지). 물론 Angular도 예외는 아닙니다. 다음과 같이 템플릿 표현식을 정의합니다:
<span>Hello {{name}}</span>
또는 다음과 유사한 속성 바인딩(참고: 위 코드와 동일합니다):
<span></span>
name
값이 변경될 때마다 Angular는 마술처럼 평소와 같이 DOM 요소를 자동으로 업데이트합니다(참고: 최상위 코드는 name
值发生变化时,Angular 会神奇般的自动更新 DOM 元素(注:最上面代码是更新 DOM 文本节点,上面代码是更新 DOM 元素节点,两者是不一样的,下文解释)。这表面上看起来很简单,但是其内部工作相当复杂。而且,DOM 更新仅仅是 Angular 变更检测机制 的一部分,变更检测机制主要由以下三步组成:
DOM updates(注:即本文将要解释的内容)
child components
Input
bindings updatesquery list updates
本文主要探索变更检测机制的渲染部分(即 DOM updates 部分)。如果你之前也对这个问题很好奇,可以继续读下去,绝对让你茅塞顿开。
在引用相关源码时,假设程序是以生产模式运行。让我们开始吧!
程序内部架构
在探索 DOM 更新之前,我们先搞清楚 Angular 程序内部究竟是如何设计的,简单回顾下吧。
视图
从我的这篇文章 Here is what you need to know about dynamic components in Angular 知道 Angular 编译器会把程序中使用的组件编译为一个工厂类(factory)。例如,下面代码展示 Angular 如何从工厂类中创建一个组件(注:这里作者逻辑貌似有点乱,前一句说的 Angular 编译器编译的工厂类,其实是编译器去做的,不需要开发者做任何事情,是自动化的事情;而下面代码说的是开发者如何手动通过 ComponentFactory 来创建一个 Component 实例。总之,他是想说组件是怎么被实例化的):
const factory = r.resolveComponentFactory(AComponent); componentRef: ComponentRef<acomponent> = factory.create(injector);</acomponent>
Angular 使用这个工厂类来实例化 View Definition ,然后使用 viewDef 函数来 创建视图。Angular 内部把一个程序看作为一颗视图树,一个程序虽然有众多组件,但有一个公共的视图定义接口来定义由组件生成的视图结构(注:即 ViewDefinition Interface),当然 Angular 使用每一个组件对象来创建对应的视图,从而由多个视图组成视图树。(注:这里有一个主要概念就是视图,其结构就是 ViewDefinition Interface)
组件工厂
组件工厂大部分代码是由编译器生成的不同视图节点组成的,这些视图节点是通过模板解析生成的(注:编译器生成的组件工厂是一个返回值为函数的函数,上文的 ComponentFactory 是 Angular 提供的类,供手动调用。当然,两者指向同一个事物,只是表现形式不同而已)。假设定义一个组件的模板如下:
<span>I am {{name}}</span>
编译器会解析这个模板生成包含如下类似的组件工厂代码(注:这只是最重要的部分代码):
function View_AComponent_0(l) { return jit_viewDef1(0, [ jit_elementDef2(0,null,null,1,'span',...), jit_textDef3(null,['I am ',...]) ], null, function(_ck,_v) { var _co = _v.component; var currVal_0 = _co.name; _ck(_v,1,0,currVal_0);
注:由 AppComponent 组件编译生成的工厂函数完整代码如下
(function(jit_createRendererType2_0,jit_viewDef_1,jit_elementDef_2,jit_textDef_3) { var styles_AppComponent = ['']; var RenderType_AppComponent = jit_createRendererType2_0({encapsulation:0,styles:styles_AppComponent,data:{}}); function View_AppComponent_0(_l) { return jit_viewDef_1(0, [ (_l()(),jit_elementDef_2(0,0,null,null,1,'span',[],null,null,null,null,null)), (_l()(),jit_textDef_3(1,null,['I am ',''])) ], null, function(_ck,_v) { var _co = _v.component; var currVal_0 = _co.name; _ck(_v,1,0,currVal_0); }); } return {RenderType_AppComponent:RenderType_AppComponent,View_AppComponent_0:View_AppComponent_0};})
上面代码描述了视图的结构,并在实例化组件时会被调用。jit_viewDef_1
其实就是 viewDef 函数,用来创建视图(注:viewDef
函数很重要,因为视图是调用它创建的,生成的视图结构即是 ViewDefinition)。
viewDef 函数的第二个参数 nodes 有些类似 html 中节点的意思,但却不仅仅如此。上面代码中第二个参数是一个数组,其第一个数组元素 jit_elementDef_2
是元素节点定义,第二个数组元素 jit_textDef_3
DOM 텍스트 노드를 업데이트하는 것이고, 위 코드는 DOM 요소 노드
변경 감지 메커니즘
의 일부일 뿐입니다. 변경 감지 메커니즘은 주로 다음 세 단계로 구성됩니다.- DOM 업데이트(참고: 이 기사 내용 설명)🎜
- 🎜하위 구성 요소
입력
바인딩 업데이트🎜 - 🎜쿼리 목록 업데이트🎜
프로그램 내부 구조
🎜DOM 업데이트를 살펴보기 전에 먼저 Angular 프로그램이 내부적으로 어떻게 설계되었는지 살펴보겠습니다. 🎜보기
🎜내 기사에서 🎜다음은 Angular의 동적 구성 요소에 대해 알아야 할 사항입니다🎜 Angular 컴파일러는 프로그램에 사용되는 구성 요소를 팩토리 클래스(팩토리)로 컴파일한다는 것을 알아두세요. 예를 들어 다음 코드는 Angular가 팩토리 클래스에서 구성 요소를 생성하는 방법을 보여줍니다. 개발자는 모든 작업을 🎜자동화🎜해야 하며 다음 코드에서는 개발자가 🎜ComponentFactory🎜를 통해 구성 요소 인스턴스를 🎜수동으로🎜 생성하는 방법을 설명합니다. 🎜export const enum NodeFlags { TypeElement = 1 🎜 Angular. 이 팩토리 클래스를 사용하여 🎜View 정의🎜를 인스턴스화한 다음 🎜viewDef🎜 함수를 사용하여 🎜뷰를 생성합니다🎜. Angular는 내부적으로 프로그램을 뷰 트리로 간주합니다. 프로그램에는 많은 구성 요소가 있지만 구성 요소에 의해 생성된 뷰 구조를 정의하는 공통 뷰 정의 인터페이스가 있습니다(참고: 🎜ViewDefinition 인터페이스🎜). 물론 Angular는 각 구성 요소를 사용합니다. 객체를 사용하여 해당 뷰를 생성함으로써 여러 뷰로부터 뷰 트리를 형성합니다. (참고: 여기서 주요 개념 중 하나는 🎜View🎜이고 그 구조는 🎜ViewDefinition Interface🎜입니다.)🎜<h3 id="Component-Factory">Component Factory</h3>🎜Component Factory의 코드 대부분은 다음에서 생성된 다양한 뷰 노드로 구성됩니다. 이러한 View 노드는 템플릿 구문 분석을 통해 생성됩니다. (참고: 컴파일러에 의해 생성된 컴포넌트 팩토리는 반환 값이 함수인 함수입니다. 위의 ComponentFactory는 수동 호출을 위해 Angular에서 제공하는 클래스입니다. 물론 둘 다 똑같아, 표현 방식이 다를 뿐이야). 구성 요소를 정의하는 템플릿이 다음과 같다고 가정합니다. 🎜<pre class="brush:php;toolbar:false"><h1 id="Hello-name-and-another-prop">Hello {{name}} and another {{prop}}</h1>🎜컴파일러는 이 템플릿을 구문 분석하여 다음과 유사한 구성 요소 팩토리 코드를 생성합니다(참고: 이는 코드에서 가장 중요한 부분일 뿐입니다). 🎜
["Hello ", " and another ", ""]🎜참고: AppComponent 컴포넌트 컴파일에 의해 생성된 팩토리 함수 전체 코드는 다음과 같습니다🎜
{ text: 'Hello', bindings: [ { name: 'name', suffix: ' and another ' }, { name: 'prop', suffix: '' } ] }🎜위 코드는 뷰의 구조를 설명하며 컴포넌트를 인스턴스화할 때 호출됩니다.
jit_viewDef_1
은 실제로 뷰를 생성하는 데 사용되는 🎜viewDef🎜 함수입니다. (참고: viewDef
함수는 호출하여 뷰가 생성되고 생성된 뷰가 생성되기 때문에 매우 중요합니다. 구조는 🎜ViewDefinition 🎜)입니다. 🎜🎜🎜viewDef🎜 🎜nodes🎜 함수의 두 번째 매개변수는 HTML의 노드 의미와 다소 유사하지만 그 이상입니다. 위 코드의 두 번째 매개변수는 배열이고, 첫 번째 배열 요소 jit_elementDef_2
는 요소 노드 정의이고, 두 번째 배열 요소 jit_textDef_3
는 텍스트 노드 정의입니다. Angular 컴파일러는 다양한 노드 정의를 생성하며 노드 유형은 🎜NodeFlags🎜에 의해 설정됩니다. 나중에 Angular가 다양한 노드 유형을 기반으로 DOM 업데이트를 수행하는 방법을 살펴보겠습니다. 🎜🎜이 글은 요소와 텍스트 노드에만 관심이 있습니다. 🎜text + context[bindings[0][property]] + context[bindings[0][suffix]] + context[bindings[1][property]] + context[bindings[1][suffix]]🎜간단히 살펴보겠습니다. 🎜
注:上文作者说了一大段,其实核心就是,程序是一堆视图组成的,而每一个视图又是由不同类型节点组成的。而本文只关心元素节点和文本节点,至于还有个重要的指令节点在另一篇文章。
元素节点的结构定义
元素节点结构 是 Angular 编译每一个 html 元素生成的节点结构,它也是用来生成组件的,如对这点感兴趣可查看 Here is why you will not find components inside Angular。元素节点也可以包含其他元素节点和文本节点作为子节点,子节点数量是由 childCount
设置的。
所有元素定义是由 elementRef 函数生成的,而工厂函数中的 jit_elementDef_2()
就是这个函数。elementRef()
主要有以下几个一般性参数:
Name | Description |
---|---|
childCount | specifies how many children the current element have |
namespaceAndName | the name of the html element(注:如 'span') |
fixedAttrs | attributes defined on the element |
还有其他的几个具有特定性能的参数:
Name | Description |
---|---|
matchedQueriesDsl | used when querying child nodes |
ngContentIndex | used for node projection |
bindings | used for dom and bound properties update |
outputs, handleEvent | used for event propagation |
本文主要对 bindings 感兴趣。
注:从上文知道视图(view)是由不同类型节点(nodes)组成的,而元素节点(element nodes)是由 elementRef 函数生成的,元素节点的结构是由 ElementDef 定义的。
文本节点的结构定义
文本节点结构 是 Angular 编译每一个 html 文本 生成的节点结构。通常它是元素定义节点的子节点,就像我们本文的示例那样(注:<span>I am {{name}}</span>
,span
是元素节点,I am {{name}}
是文本节点,也是 span
的子节点)。这个文本节点是由 textDef 函数生成的。它的第二个参数以字符串数组形式传进来(注: Angular v5.* 是第三个参数)。例如,下面的文本:
<h1 id="Hello-name-and-another-prop">Hello {{name}} and another {{prop}}</h1>
将要被解析为一个数组:
["Hello ", " and another ", ""]
然后被用来生成正确的绑定:
{ text: 'Hello', bindings: [ { name: 'name', suffix: ' and another ' }, { name: 'prop', suffix: '' } ] }
在脏检查(注:即变更检测)阶段会这么用来生成文本:
text + context[bindings[0][property]] + context[bindings[0][suffix]] + context[bindings[1][property]] + context[bindings[1][suffix]]
注:同上,文本节点是由 textDef 函数生成的,结构是由 TextDef 定义的。既然已经知道了两个节点的定义和生成,那节点上的属性绑定, Angular 是怎么处理的呢?
节点的绑定
Angular 使用 BindingDef 来定义每一个节点的绑定依赖,而这些绑定依赖通常是组件类的属性。在变更检测时 Angular 会根据这些绑定来决定如何更新节点和提供上下文信息。具体哪一种操作是由 BindingFlags 决定的,下面列表展示了具体的 DOM 操作类型:
Name | Construction in template |
---|---|
TypeElementAttribute | attr.name |
TypeElementClass | class.name |
TypeElementStyle | style.name |
元素和文本定义根据这些编译器可识别的绑定标志位,内部创建这些绑定依赖。每一种节点类型都有着不同的绑定生成逻辑(注:意思是 Angular 会根据 BindingFlags 来生成对应的 BindingDef)。(想看更多就到PHP中文网AngularJS开发手册中学习)
更新渲染器
最让我们感兴趣的是 jit_viewDef_1
中最后那个参数:
function(_ck,_v) { var _co = _v.component; var currVal_0 = _co.name; _ck(_v,1,0,currVal_0); });
这个函数叫做 updateRenderer。它接收两个参数:_ck
和 _v
。_ck
是 check
的简写,其实就是 prodCheckAndUpdateNode 函数,而 _v
就是当前视图对象。updateRenderer
函数会在 每一次变更检测时 被调用,其参数 _ck
和 _v
也是这时被传入。
updateRenderer
函数逻辑主要是,从组件对象的绑定属性获取当前值,并调用 _ck
函数,同时传入视图对象、视图节点索引和绑定属性当前值。重要一点是 Angular 会为每一个视图执行 DOM 更新操作,所以必须传入视图节点索引参数(注:这个很好理解,上文说了 Angular 会依次对每一个 view 做模型视图同步过程)。你可以清晰看到 _ck
参数列表:
function prodCheckAndUpdateNode( view: ViewData, nodeIndex: number, argStyle: ArgumentType, v0?: any, v1?: any, v2?: any,
nodeIndex
是视图节点的索引,如果你模板中有多个表达式:
<h1 id="Hello-name">Hello {{name}}</h1> <h1 id="Hello-age">Hello {{age}}</h1>
编译器生成的 updateRenderer
函数如下:
var _co = _v.component; // here node index is 1 and property is `name` var currVal_0 = _co.name; _ck(_v,1,0,currVal_0); // here node index is 4 and bound property is `age` var currVal_1 = _co.age; _ck(_v,4,0,currVal_1);
更新 DOM
现在我们已经知道 Angular 编译器生成的所有对象(注:已经有了 view,element node,text node 和 updateRenderer 这几个道具),现在我们可以探索如何使用这些对象来更新 DOM。
从上文我们知道变更检测期间 updateRenderer
函数传入的一个参数是 _ck
函数,而这个函数就是 prodCheckAndUpdateNode。这个函数在继续执行后,最终会调用 checkAndUpdateNodeInline ,如果绑定属性的数量超过 10,Angular 还提供了 checkAndUpdateNodeDynamic 这个函数(注:两个函数本质一样)。
checkAndUpdateNodeInline
函数会根据不同视图节点类型来执行对应的检查更新函数:
case NodeFlags.TypeElement -> checkAndUpdateElementInline case NodeFlags.TypeText -> checkAndUpdateTextInline case NodeFlags.TypeDirective -> checkAndUpdateDirectiveInline
让我们看下这些函数是做什么的,至于 NodeFlags.TypeDirective
可以查看我写的文章 The mechanics of property bindings update in Angular 。
注:因为本文只关注 element node 和 text node
。
元素节点
对于元素节点,会调用函数 checkAndUpdateElementInline 以及 checkAndUpdateElementValue,checkAndUpdateElementValue
函数会检查绑定形式是否是 [attr.name, class.name, style.some]
或是属性绑定形式:
case BindingFlags.TypeElementAttribute -> setElementAttribute case BindingFlags.TypeElementClass -> setElementClass case BindingFlags.TypeElementStyle -> setElementStyle case BindingFlags.TypeProperty -> setElementProperty;
然后使用渲染器对应的方法来对该节点执行对应操作,比如使用 setElementClass
给当前节点 span
添加一个 class
。
文本节点
对于文本节点类型,会调用 checkAndUpdateTextInline ,下面是主要部分:
if (checkAndUpdateBinding(view, nodeDef, bindingIndex, newValue)) { value = text + _addInterpolationPart(...); view.renderer.setValue(DOMNode, value); }
它会拿到 updateRenderer
函数传过来的当前值(注:即上文的 _ck(_v,4,0,currVal_1);
),与上一次变更检测时的值相比较。视图数据包含有 oldValues 属性,如果属性值如 name
发生变化,Angular 会使用最新 name
值合成最新的字符串文本,如 Hello New World
,然后使用渲染器更新 DOM 上对应的文本。(想看更多就到PHP中文网AngularJS开发手册中学习)
注:更新元素节点和文本节点都提到了渲染器(renderer),这也是一个重要的概念。每一个视图对象都有一个 renderer 属性,即是 Renderer2 的引用,也就是组件渲染器,DOM 的实际更新操作由它完成。因为 Angular 是跨平台的,这个 Renderer2 是个接口,这样根据不同 Platform 就选择不同的 Renderer。比如,在浏览器里这个 Renderer 就是 DOMRenderer,在服务端就是 ServerRenderer,等等。从这里可看出,Angular 框架设计做了很好的抽象。
结论
我知道有大量难懂的信息需要消化,但是只要理解了这些知识,你就可以更好的设计程序或者去调试 DOM 更新相关的问题。我建议你按照本文提到的源码逻辑,使用调试器或 debugger 语句
一步步去调试源码。
알겠습니다. 이 기사는 여기서 끝납니다. (자세한 내용을 보려면 PHP 중국어 웹사이트 AngularJS 사용자 설명서를 방문하세요.) 궁금한 점이 있으면 아래에 메시지를 남겨주세요.
위 내용은 [번역] Angular DOM 업데이트 메커니즘 - Laravel/Angular 기술 공유의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

개발 환경에서 Python과 JavaScript의 선택이 모두 중요합니다. 1) Python의 개발 환경에는 Pycharm, Jupyternotebook 및 Anaconda가 포함되어 있으며 데이터 과학 및 빠른 프로토 타이핑에 적합합니다. 2) JavaScript의 개발 환경에는 Node.js, VScode 및 Webpack이 포함되어 있으며 프론트 엔드 및 백엔드 개발에 적합합니다. 프로젝트 요구에 따라 올바른 도구를 선택하면 개발 효율성과 프로젝트 성공률이 향상 될 수 있습니다.

예, JavaScript의 엔진 코어는 C로 작성되었습니다. 1) C 언어는 효율적인 성능과 기본 제어를 제공하며, 이는 JavaScript 엔진 개발에 적합합니다. 2) V8 엔진을 예를 들어, 핵심은 C로 작성되며 C의 효율성 및 객체 지향적 특성을 결합하여 C로 작성됩니다.

JavaScript는 웹 페이지의 상호 작용과 역학을 향상시키기 때문에 현대 웹 사이트의 핵심입니다. 1) 페이지를 새로 고치지 않고 콘텐츠를 변경할 수 있습니다. 2) Domapi를 통해 웹 페이지 조작, 3) 애니메이션 및 드래그 앤 드롭과 같은 복잡한 대화식 효과를 지원합니다. 4) 성능 및 모범 사례를 최적화하여 사용자 경험을 향상시킵니다.

C 및 JavaScript는 WebAssembly를 통한 상호 운용성을 달성합니다. 1) C 코드는 WebAssembly 모듈로 컴파일되어 컴퓨팅 전력을 향상시키기 위해 JavaScript 환경에 도입됩니다. 2) 게임 개발에서 C는 물리 엔진 및 그래픽 렌더링을 처리하며 JavaScript는 게임 로직 및 사용자 인터페이스를 담당합니다.

JavaScript는 웹 사이트, 모바일 응용 프로그램, 데스크탑 응용 프로그램 및 서버 측 프로그래밍에서 널리 사용됩니다. 1) 웹 사이트 개발에서 JavaScript는 HTML 및 CSS와 함께 DOM을 운영하여 동적 효과를 달성하고 jQuery 및 React와 같은 프레임 워크를 지원합니다. 2) 반응 및 이온 성을 통해 JavaScript는 크로스 플랫폼 모바일 애플리케이션을 개발하는 데 사용됩니다. 3) 전자 프레임 워크를 사용하면 JavaScript가 데스크탑 애플리케이션을 구축 할 수 있습니다. 4) node.js는 JavaScript가 서버 측에서 실행되도록하고 동시 요청이 높은 높은 요청을 지원합니다.

Python은 데이터 과학 및 자동화에 더 적합한 반면 JavaScript는 프론트 엔드 및 풀 스택 개발에 더 적합합니다. 1. Python은 데이터 처리 및 모델링을 위해 Numpy 및 Pandas와 같은 라이브러리를 사용하여 데이터 과학 및 기계 학습에서 잘 수행됩니다. 2. 파이썬은 간결하고 자동화 및 스크립팅이 효율적입니다. 3. JavaScript는 프론트 엔드 개발에 없어서는 안될 것이며 동적 웹 페이지 및 단일 페이지 응용 프로그램을 구축하는 데 사용됩니다. 4. JavaScript는 Node.js를 통해 백엔드 개발에 역할을하며 전체 스택 개발을 지원합니다.

C와 C는 주로 통역사와 JIT 컴파일러를 구현하는 데 사용되는 JavaScript 엔진에서 중요한 역할을합니다. 1) C는 JavaScript 소스 코드를 구문 분석하고 추상 구문 트리를 생성하는 데 사용됩니다. 2) C는 바이트 코드 생성 및 실행을 담당합니다. 3) C는 JIT 컴파일러를 구현하고 런타임에 핫스팟 코드를 최적화하고 컴파일하며 JavaScript의 실행 효율을 크게 향상시킵니다.

실제 세계에서 JavaScript의 응용 프로그램에는 프론트 엔드 및 백엔드 개발이 포함됩니다. 1) DOM 운영 및 이벤트 처리와 관련된 TODO 목록 응용 프로그램을 구축하여 프론트 엔드 애플리케이션을 표시합니다. 2) Node.js를 통해 RESTFULAPI를 구축하고 Express를 통해 백엔드 응용 프로그램을 시연하십시오.


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

VSCode Windows 64비트 다운로드
Microsoft에서 출시한 강력한 무료 IDE 편집기

MinGW - Windows용 미니멀리스트 GNU
이 프로젝트는 osdn.net/projects/mingw로 마이그레이션되는 중입니다. 계속해서 그곳에서 우리를 팔로우할 수 있습니다. MinGW: GCC(GNU Compiler Collection)의 기본 Windows 포트로, 기본 Windows 애플리케이션을 구축하기 위한 무료 배포 가능 가져오기 라이브러리 및 헤더 파일로 C99 기능을 지원하는 MSVC 런타임에 대한 확장이 포함되어 있습니다. 모든 MinGW 소프트웨어는 64비트 Windows 플랫폼에서 실행될 수 있습니다.

에디트플러스 중국어 크랙 버전
작은 크기, 구문 강조, 코드 프롬프트 기능을 지원하지 않음

Eclipse용 SAP NetWeaver 서버 어댑터
Eclipse를 SAP NetWeaver 애플리케이션 서버와 통합합니다.

Dreamweaver Mac版
시각적 웹 개발 도구
