搜尋
首頁web前端js教程[譯] Angular 屬性綁定更新機制 - Laravel/Angular 技術分享

[譯] Angular 屬性綁定更新機制 - Laravel/Angular 技術分享

Sep 07, 2018 pm 04:11 PM
angular.jsjavascripttypescript

本篇文章主要的講述了關於angularjs屬性綁定更新機制,還有angularjs更新屬性的詳解,都在這篇文章中,現在就讓我們一起來看這篇文章吧

angularjs屬性綁定更新機制解釋:

所有現代前端框架都是用元件來合成UI,這樣很自然就會產生父子元件層級,這就需要框架提供父子組件通訊的機制。同樣,Angular 也提供了兩種方式來實作父子元件通訊:輸入輸出綁定共享服務。對於 stateless presentational components 我更喜歡輸入輸出綁定方式,然而對於 stateful container components 我使用共享服務方式。

本文主要介紹輸入輸出綁定方式,特別是當父元件輸入綁定值變化時,Angular 如何更新子元件輸入值。如果想了解 Angular 如何更新目前元件 DOM,可以查看 翻譯 Angular DOM 更新機制,這篇文章也會有助於加深對本文的理解。由於我們將探索Angular 如何更新DOM 元素和組件的輸入綁定屬性,所以假定你知道Angular 內部是如何表現組件和指令的,如果你不是很了解並且很感興趣,可以查看譯為何Angular 內部沒有發現元件, 這篇文章主要講了Angular 內部如何使用指令形式來表示元件。而本文對於元件和指令兩個概念互換使用,因為 Angular 內部就是把元件當作指令。

模板綁定語法

你可能知道Angular 提供了屬性綁定語法 —— [],這個語法很通用,它可以用在子元件上,也可以用在原生DOM 元素上。如果你想從父元件把資料傳給子元件b-comp 或原生DOM 元件span,你可以在父元件範本中這麼寫:

import { Component } from '@angular/core';

@Component({
  moduleId: module.id,
  selector: 'a-comp',
  template: `
      <b-comp></b-comp>
      <span></span>
  `
})
export class AComponent {
  AText = 'some';
}

你不必為原生DOM 元素做些額外的工作,但是對於子元件b-comp 你需要申明輸入屬性textContent

@Component({
    selector: 'b-comp',
    template: 'Comes from parent: {{textContent}}'
})
export class BComponent {
    @Input() textContent;
}

這樣當父元件AComponent.AText 屬性改變時,Angular 會自動更新子元件BComponent.textContent 屬性,和原生元素span.textContent 屬性。同時,也會呼叫子元件 BComponent 的生命週期鉤子函數 ngOnChanges(註:實際上還有 ngDoCheck,見下文)。

你可能好奇 Angular 是怎麼知道 BComponentspan 支援 textContent 綁定的。這是因為Angular 編譯器在解析模板時,如果遇到簡單DOM 元素如span,就去查找這個元素是否定義在dom_element_schema_registry,從而知道它是HTMLElement 子類, textContent 是其中的一個屬性(註:可以試試如果span 綁定一個[abc]=AText 就報錯,沒辦法識別abc 屬性);如果遇到了元件或指令,就去查看其裝飾器@Component/@Directive 的元資料input 屬性裡是否有該綁定屬性項,如果沒有,編譯器同樣會拋出錯誤:

Can’t bind to ‘textContent’ since it isn’t a known property of …

這些知識都很好理解,現在讓我們進一步看看其內部發生了什麼。

元件工廠

儘管在子元件BComponentspan 元素綁定了輸入屬性,但是輸入綁定更新所需的資訊全部在父元件AComponent 的元件工廠裡。讓我們看下AComponent 的元件工廠代碼:

function View_AComponent_0(_l) {
  return jit_viewDef1(0, [
     jit_elementDef_2(..., 'b-comp', ...),
     jit_directiveDef_5(..., jit_BComponent6, [], {
         textContent: [0, 'textContent']
     }, ...),
     jit_elementDef_2(..., 'span', [], [[8, 'textContent', 0]], ...)
  ], function (_ck, _v) {
     var _co = _v.component;
     var currVal_0 = _co.AText;
     var currVal_1 = 'd';
     _ck(_v, 1, 0, currVal_0, currVal_1);
  }, function (_ck, _v) {
     var _co = _v.component;
     var currVal_2 = _co.AText;
     _ck(_v, 2, 0, currVal_2);
  });
}

如果你讀了譯Angular DOM 更新機制譯為何Angular 內部沒有發現元件,就會對上面程式碼中的各個視圖節點比較熟悉了。在前兩個節點中,jit_elementDef_2 是元素節點,jit_directiveDef_5 是指令節點,這兩個組成了子元件BComponent;第三個節點 jit_elementDef_2 也是元素節點,組成了span 元素。 (想看更多就到PHP中文網AngularJS開發手冊中學習)

節點綁定

#相同類型的節點使用相同的節點定義函數,但區別是接收的參數不同,例如jit_directiveDef_5 節點定義函數參數如下:

jit_directiveDef_5(..., jit_BComponent6, [], {
    textContent: [0, 'textContent']
}, ...),

其中,參數{textContent: [0, 'textContent']} 叫做props,這點可以查看directiveDef 函數的參數列表:

directiveDef(..., props?: {[name: string]: [number, string]}, ...)

props 參數是一個對象,每一個鍵為綁定屬性名,對應的值為綁定索引和綁定屬性名組成的數組,例如本例中只有一個綁定,textContent 對應的值為:

{textContent: [0, 'textContent']}

如果指令有多個綁定定,比如:

<b-comp></b-comp>

props 参数值也包含两个属性:

jit_directiveDef5(49152, null, 0, jit_BComponent6, [], {
    textContent: [0, 'textContent'],
    otherProp: [1, 'otherProp']
}, null),

Angular 会使用这些值来生成当前指令节点的 binding,从而生成当前视图的指令节点。在变更检测时,每一个 binding 决定 Angular 使用哪种操作来更新节点和提供上下文信息,绑定类型是通过 BindingFlags 设置的(注:每一个绑定定义是 BindingDef,它的属性 flags: BindingFlags 决定 Angular 该采取什么操作,比如 Class 型绑定和 Style 型绑定都会调用对应的操作函数,见下文)。比如,如果是属性绑定,编译器会设置绑定标志位为:

export const enum BindingFlags {
    TypeProperty = 1 <blockquote>注:上文说完了指令定义函数的参数,下面说说元素定义函数的参数。</blockquote><p>本例中,因为 <code>span</code> 元素有属性绑定,编译器会设置绑定参数为 <code>[[8, 'textContent', 0]]</code>:</p><pre class="brush:php;toolbar:false">jit_elementDef2(..., 'span', [], [[8, 'textContent', 0]], ...)

不同于指令节点,对元素节点来说,绑定参数结构是个二维数组,因为 span 元素只有一个绑定,所以它仅仅只有一个子数组。数组 [8, 'textContent', 0] 中第一个参数也同样是绑定标志位 BindingFlags,决定 Angular 应该采取什么类型操作(注:[8, 'textContent', 0] 中的 8 表示为 property 型绑定):

export const enum BindingFlags {
    TypeProperty = 1 <p>其他类型标志位已经在文章 <strong>译 Angular DOM 更新机制</strong> 有所解释:</p><pre class="brush:php;toolbar:false">TypeElementAttribute = 1 <p>编译器不会为指令定义提供绑定标志位,因为指令的绑定类型也只能是 <code>BindingFlags.TypeProperty</code>。</p><blockquote>注:<strong>节点绑定</strong> 这一节主要讲的是对于元素节点来说,每一个节点的 <strong>binding</strong> 类型是由 <strong>BindingFlags</strong> 决定的;对于指令节点来说,每一个节点的 <strong>binding</strong> 类型只能是 <code>BindingFlags.TypeProperty</code>。</blockquote><h2 id="updateRenderer-和-updateDirectives">updateRenderer 和 updateDirectives</h2><p>组件工厂代码里,编译器还为我们生成了两个函数:</p><pre class="brush:php;toolbar:false">function (_ck, _v) {
    var _co = _v.component;
    var currVal_0 = _co.AText;
    var currVal_1 = _co.AProp;
    _ck(_v, 1, 0, currVal_0, currVal_1);
},
function (_ck, _v) {
    var _co = _v.component;
    var currVal_2 = _co.AText;
    _ck(_v, 2, 0, currVal_2);
}

如果你读了 译 Angular DOM 更新机制,应该对第二个函数即 updateRenderer 有所熟悉。第一个函数叫做 updateDirectives。这两个函数都是 ViewUpdateFn 类型接口,两者都是视图定义的属性:

interface ViewDefinition {
  flags: ViewFlags;
  updateDirectives: ViewUpdateFn;
  updateRenderer: ViewUpdateFn;

有趣的是这两个函数的函数体基本相同,参数都是 _ck_v,并且两个函数的对应参数都指向同一个对象,所以为何需要两个函数?

因为在变更检测期间,这是不同阶段的两个不同行为:

  • 更新子组件的输入绑定属性

  • 更新当前组件的 DOM 元素

这两个操作是在变更检测的不同阶段执行,所以 Angular 需要两个独立的函数分别在对应的阶段调用:

  • updateDirectives——变更检测的开始阶段被调用,来更新子组件的输入绑定属性

  • updateRenderer——变更检测的中间阶段被调用,来更新当前组件的 DOM 元素

这两个函数都会在 Angular 每次的变更检测时 被调用,并且函数参数也是在这时被传入的。让我们看看函数内部做了哪些工作。

_ck 就是 check 的缩写,其实就是函数 prodCheckAndUpdateNode,另一个参数就是 组件视图数据。函数的主要功能就是从组件对象里拿到绑定属性的当前值,然后和视图数据对象、视图节点索引等一起传入 prodCheckAndUpdateNode 函数。其中,因为 Angular 会更新每一个视图的 DOM,所以需要传入当前视图的索引。如果我们有两个 span 和两个组件:

<b-comp></b-comp>
<b-comp></b-comp>

编译器生成的 updateRenderer 函数和 updateDirectives 函数如下:

function(_ck, _v) {
    var _co = _v.component;
    var currVal_0 = _co.AText;
    
    // update first component
    _ck(_v, 1, 0, currVal_0);
    var currVal_1 = _co.AText;
    
    // update second component
    _ck(_v, 3, 0, currVal_1);
}, 
function(_ck, _v) {
    var _co = _v.component;
    var currVal_2 = _co.AText;
    
    // update first span
    _ck(_v, 4, 0, currVal_2);
    var currVal_3 = _co.AText;

    // update second span
    _ck(_v, 5, 0, currVal_3);
}

没有什么更复杂的东西,这两个函数还不是重点,重点是 _ck 函数,接着往下看。

更新元素的属性

从上文我们知道,编译器生成的 updateRenderer 函数会在每一次变更检测被调用,用来更新 DOM 元素的属性,并且其参数 _ck 就是函数 prodCheckAndUpdateNode。对于 DOM 元素的更新,该函数经过一系列的函数调用后,最终会调用函数 checkAndUpdateElementValue,这个函数会检查绑定标志位是 [attr.name, class.name, style.some] 其中的哪一个,又或者是属性绑定:

case BindingFlags.TypeElementAttribute -> setElementAttribute
case BindingFlags.TypeElementClass     -> setElementClass
case BindingFlags.TypeElementStyle     -> setElementStyle
case BindingFlags.TypeProperty         -> setElementProperty;

上面代码就是刚刚说的几个绑定类型,当绑定标志位是 BindingFlags.TypeProperty,会调用函数 setElementProperty,该函数内部也是通过调用 DOM Renderer 的 setProperty 方法来更新 DOM。

注:setElementProperty 函数里这行代码 view.renderer.setProperty(renderNode,name, renderValue);,renderer 就是 Renderer2 interface,它仅仅是一个接口,在浏览器平台下,它的实现就是 DefaultDomRenderer2

更新指令的属性

上文中已经描述了 updateRenderer 函数是用来更新元素的属性,而 updateDirective 是用来更新子组件的输入绑定属性,并且变更检测期间传入的参数 _ck 就是函数 prodCheckAndUpdateNode。只是进过一系列函数调用后,最终调用的函数却是checkAndUpdateDirectiveInline,这是因为这次节点的标志位是 NodeFlags.TypeDirective

checkAndUpdateDirectiveInline 函数主要功能如下:

  1. 从当前视图节点里获取组件/指令对象

  2. 检查组件/指令对象的绑定属性值是否发生改变

  3. 如果属性发生改变:

    a. 如果变更策略设置为 OnPush,设置视图状态为 checksEnabled

    b. 更新子组件的绑定属性值

    c. 准备 SimpleChange 数据和更新视图的 oldValues 属性,新值替换旧值

    d. 调用生命周期钩子 ngOnChanges

  4. 如果该视图是首次执行变更检测,则调用生命周期钩子 ngOnInit

  5. 调用生命周期钩子 ngDoCheck

当然,只有在生命周期钩子在组件内定义了才被调用,Angular 使用 NodeDef 节点标志位来判断是否有生命周期钩子,如果查看源码你会发现类似如下代码:

if (... && (def.flags & NodeFlags.OnInit)) {
  directive.ngOnInit();
}
if (def.flags & NodeFlags.DoCheck) {
  directive.ngDoCheck();
}

和更新元素节点一样,更新指令时也同样把上一次的值存储在视图数据的属性 oldValues 里(注:即上面的 3.c 步骤)。

好了,本篇文章到这就结束了(想看更多就到PHP中文网AngularJS使用手册中学习),有问题的可以在下方留言提问。


以上是[譯] Angular 屬性綁定更新機制 - Laravel/Angular 技術分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
從C/C到JavaScript:所有工作方式從C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript引擎:比較實施JavaScript引擎:比較實施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的演變:當前的趨勢和未來前景JavaScript的演變:當前的趨勢和未來前景Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

神秘的JavaScript:它的作用以及為什麼重要神秘的JavaScript:它的作用以及為什麼重要Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器