日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

[译] 关于Angular的变更检测(Change Detection)你需要知道这些

發(fā)布時(shí)間:2025/3/8 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [译] 关于Angular的变更检测(Change Detection)你需要知道这些 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

原文地址:Everything you need to know about change detection in Angular

如果你像我一樣,想對(duì)Angular的變更檢測(cè)機(jī)制有一個(gè)深入的理解,由于在網(wǎng)上并沒(méi)有多少有用的信息,你只能去看源碼。大多數(shù)文章都會(huì)提到每一個(gè)組件都會(huì)有一個(gè)屬于自己的變更檢測(cè)器(change detector),它負(fù)責(zé)檢查和這個(gè)組件,但是他們幾乎都僅限于在說(shuō)怎么使用immutable 數(shù)據(jù)和變更檢測(cè)策略,這篇文章將會(huì)讓你明白為什么使用immutable可以工作,并且臟檢查機(jī)制是如何影響檢查的過(guò)程的。還有,這篇文章將會(huì)引發(fā)你對(duì)性能優(yōu)化方面的一些場(chǎng)景的思考。

這篇文章包含2部分,第一部相當(dāng)?shù)挠屑夹g(shù)含量,它包含了一些指向源碼的鏈接,它詳細(xì)的介紹了臟檢查機(jī)制在Angular的底層是怎么運(yùn)行的,所有內(nèi)容是基·Angular的最新 版本-4.0.1(注:作者寫(xiě)這篇文章的時(shí)候,Angular的最新版本是4.0.1), 臟檢查機(jī)制的實(shí)現(xiàn)在這個(gè)版本的實(shí)現(xiàn)和之前的2.4.1版本是不一樣的,如果你對(duì)之前版本的實(shí)現(xiàn)感興趣的話,你可以在這個(gè)stackoverflow的答案上學(xué)習(xí)到一些東西。

第二部分介紹了變更檢測(cè)在應(yīng)用程序中該怎么使用,這部分內(nèi)容既適用于之前的2.4.1版本,也使用于最新的4.0.1版本,因?yàn)檫@部分的API并沒(méi)有改變。

將視圖(view)作為一個(gè)核心概念

在Angular的教程中提到過(guò),一個(gè)Angular應(yīng)用程序就是一個(gè)組件樹(shù),然而,Angular在底層用了一個(gè)低級(jí)的抽象,叫做 視圖(view)。一個(gè)視圖和一個(gè)組件之間有直接的關(guān)聯(lián):一個(gè)視圖對(duì)應(yīng)著一個(gè)組件,反之亦然。一個(gè)視圖通過(guò)一個(gè)叫component的屬性,保持著對(duì)與其所關(guān)聯(lián)的那個(gè)組件類的實(shí)例的引用。所有的操作(比如屬性檢查,DOM更新等),都會(huì)表現(xiàn)在視圖上面,因此從技術(shù)上來(lái)講,更正確的說(shuō)法是,Angular是一個(gè)視圖樹(shù),一個(gè)組件可以被看做是一個(gè)視圖的更高級(jí)的概念。下面是一些源碼中的關(guān)于視圖的介紹.

一個(gè)視圖是一個(gè)應(yīng)用程序UI的基本組成單位,它是能夠被一起創(chuàng)建和銷(xiāo)毀的最小的一個(gè)元素集合。
在一個(gè)視圖中,元素的屬性可以改變,但是它的結(jié)構(gòu)(數(shù)量和順序)不會(huì)被改變,只有通過(guò)一個(gè)ViewContainerRef來(lái)插入、移動(dòng)或是刪除內(nèi)嵌的視圖這些操作才可以改變?cè)氐慕Y(jié)構(gòu)。每一個(gè)視圖可以包含多個(gè)視圖容器。

在本文中,我將交替使用組件視圖和組件的概念。

在這里有一點(diǎn)需要注意的是,網(wǎng)上的所有文章和StackOverflow上的一些回答將變更檢測(cè)視為變更檢測(cè)器對(duì)象或者`ChangeDetectorRef`,指的就是我在這里所說(shuō)的視圖(view)。實(shí)際上,沒(méi)有一個(gè)單獨(dú)的對(duì)象來(lái)進(jìn)行變更檢測(cè),并且視圖才是變更檢測(cè)所運(yùn)行的地方。 復(fù)制代碼

每一個(gè)視圖通nodes屬性對(duì)它的子視圖有一個(gè)引用,因此,它可以在它的子視圖中執(zhí)行一些操作。

視圖狀態(tài)(View state)

每一個(gè)視圖都有一個(gè)狀態(tài),它扮演著非常重要的角色,因?yàn)楦鶕?jù)這個(gè)狀態(tài)的值,Angular來(lái)決定是要對(duì)這個(gè)視圖以及它的子視圖進(jìn)行變更檢測(cè)還是忽略掉。有許多可能的狀態(tài),但是下面的這幾個(gè)是與本文相關(guān)的幾個(gè)。

  • FirstCheck
  • ChecksEnabled
  • Errored
  • Destroyed
  • 如果ChecksEnabled是false或者視圖是Errored或者Destroyed的狀態(tài),變更檢測(cè)將會(huì)跳過(guò)這個(gè)視圖以及它的子視圖。默認(rèn)的,所有的視圖都被初始化為ChecksEnabled的狀態(tài),除非你設(shè)置了ChangeDetectionStrategy.OnPush。稍后將會(huì)詳細(xì)介紹。視圖的狀態(tài)也可以合并,例如,一個(gè)視圖既可以有FirstCheck的狀態(tài),也可以由ChecksEnabled的狀態(tài)。

    Angular有許多高級(jí)的概念來(lái)操作視圖,我在這里寫(xiě)了一些,其中一個(gè)就是viewRef,它封裝了基本的組件視圖,還有一個(gè)指定的方法detectChanges,當(dāng)一個(gè)異步事件發(fā)生的時(shí)候,Angular將會(huì)在它的頂級(jí)viewRef觸發(fā)變更檢測(cè),它會(huì)在對(duì)它自己進(jìn)行變更檢測(cè)后對(duì)它的子視圖進(jìn)行變更檢測(cè)。

    你可以通過(guò)ChangeDetectorRef標(biāo)記將這個(gè)viewRef注入到一個(gè)組件的constructor中:

    export class AppComponent {constructor(cd: ChangeDetectorRef) { ... } 復(fù)制代碼

    可以看下這兩個(gè)類的定義

    export declare abstract class ChangeDetectorRef {abstract checkNoChanges(): void;abstract detach(): void;abstract detectChanges(): void;abstract markForCheck(): void;abstract reattach(): void; } export abstract class ViewRef extends ChangeDetectorRef {... } 復(fù)制代碼

    變更檢測(cè)操作

    主邏輯負(fù)責(zé)對(duì)存在于checkAndUpdateView函數(shù)中的視圖進(jìn)行變更檢測(cè),它的大部分功能在子組件上執(zhí)行,這個(gè)函數(shù)從主組件開(kāi)始被每一個(gè)組件遞歸的調(diào)用,這就意味著隨著遞歸樹(shù)的展開(kāi),子組件在下一個(gè)調(diào)用中成為父組件。

    當(dāng)為特定視圖觸發(fā)此函數(shù)時(shí),它按照指定的順序執(zhí)行以下操作:

  • 如果一個(gè)視圖是第一次被檢查,則將ViewState.firstCheck設(shè)置為true,如果是已經(jīng)被檢查過(guò)了,則設(shè)置為false.

  • 檢查并更新在子組件/指令實(shí)例上的輸入屬性。

  • 更新子視圖變更檢測(cè)狀態(tài)(一部分是變更檢測(cè)策略的實(shí)現(xiàn))。

  • 對(duì)內(nèi)嵌的視圖執(zhí)行變更檢測(cè)(重復(fù)列出的這些步驟)。

  • 如果綁定的值改變的話,在子組件中調(diào)用 OnChanges生命周期鉤子。

  • 調(diào)用子組件的OnInit和ngDoCheck生命周期鉤子(OnInit只有在第一次檢查的時(shí)候才會(huì)被調(diào)用)。

  • 在子視圖組件實(shí)例中更新ContentChildren queryList。

  • 在子組件實(shí)例中調(diào)用AfterContentInit和AfterContentChecked生命周期鉤子(AfterContentInit只有在第一次檢查的時(shí)候才會(huì)被調(diào)用)。

  • 如果當(dāng)前視圖組件實(shí)例上的屬性變化的話,更新DOM插值表達(dá)式。

  • 對(duì)子視圖執(zhí)行變更檢查(重復(fù)這個(gè)列表里的步驟)。

  • 更新當(dāng)前視圖組件實(shí)例中的ViewChildren查詢列表。

  • 在當(dāng)前組件實(shí)例中調(diào)用AfterViewInit和AfterViewChecked生命周期鉤子(AfterViewInit只有在第一次檢查的時(shí)候才會(huì)被調(diào)用)。

  • 禁用當(dāng)前視圖的檢查(一部分是變更檢測(cè)策略的實(shí)現(xiàn))。

  • 基于上面的執(zhí)行列表,有幾個(gè)需要強(qiáng)調(diào)的事情。

    第一個(gè)事情就是onChanges生命周期鉤子是發(fā)生在子組件中的,它在子視圖被檢查之前觸發(fā)的,并且即使這個(gè)子視圖沒(méi)有進(jìn)行變更檢測(cè)它也會(huì)觸發(fā)。這是個(gè)很重要的信息,本文的第二部分你將會(huì)看到我們?cè)趺蠢眠@個(gè)信息。

    第二個(gè)事情就是當(dāng)視圖被檢測(cè)的時(shí)候,它的DOM的更新是作為變更檢測(cè)機(jī)制的一部分的,也就是說(shuō)如果一個(gè)組件沒(méi)有被檢查,即使這個(gè)組件的被用到模板上的屬性改變了,DOM也不會(huì)被更新。模板是在第一次檢查前就被渲染了,我所指的DOM更新實(shí)際上指的是插值表達(dá)式的更新,因此如果你有一個(gè)這樣的模板<span>some {{name}}</span>,DOM元素span將會(huì)在第一次檢查前就被渲染,而在檢查的時(shí)候,只有{{name}}這部分才會(huì)被渲染。

    另外一個(gè)有趣的發(fā)現(xiàn)是在變更檢測(cè)期間,一個(gè)子組件的視圖的狀態(tài)會(huì)被改變。我在前面提到過(guò)所有的組件視圖在初始化時(shí)默認(rèn)都是ChecksEnabled的的狀態(tài),但是對(duì)于那些使用了OnPush策略的組件來(lái)說(shuō),變更檢測(cè)將會(huì)在第一次檢查后被禁用。(上面操作列表中的第9步):

    if (view.def.flags & ViewFlags.OnPush) {view.state &= ~ViewState.ChecksEnabled; } 復(fù)制代碼

    這意味著在后面的變更檢測(cè)在執(zhí)行檢查時(shí),這個(gè)組件及它的所有子組件將會(huì)被忽略掉。文檔中說(shuō)一個(gè)設(shè)置了OnPush策略的組件只有在它綁定的輸入屬性改變的時(shí)候才會(huì)被檢查,因此必須通過(guò)設(shè)置ChecksEnabled位來(lái)啟用檢查,這也是下面的代碼所做的(步驟2):

    if (compView.def.flags & ViewFlags.OnPush) {compView.state |= ViewState.ChecksEnabled; } 復(fù)制代碼

    只有當(dāng)父級(jí)視圖綁定改變并且子組件視圖被初始化為ChangeDetectionStrategy.OnPush策略時(shí),狀態(tài)才會(huì)被更新。

    最后,當(dāng)前視圖的變更檢測(cè)負(fù)責(zé)開(kāi)啟它的子視圖的變更檢測(cè)(步驟8)。這是檢查子組件視圖狀態(tài)的地方,如果ChecksEnabled是true,那么執(zhí)行變更檢測(cè),下面是相關(guān)的代碼:

    viewState = view.state; ... case ViewAction.CheckAndUpdate:if ((viewState & ViewState.ChecksEnabled) &&(viewState & (ViewState.Errored | ViewState.Destroyed)) === 0) {checkAndUpdateView(view);} } 復(fù)制代碼

    現(xiàn)在你已經(jīng)知道了視圖的狀態(tài)控制著是否要對(duì)這個(gè)視圖以及它的子組件執(zhí)行變更檢測(cè),所以問(wèn)題是我們能控制這些狀態(tài)碼?答案是可以,這也是本文第二部分要講的內(nèi)容。

    有的聲明周期鉤子在DOM更新之前被調(diào)用(3,4,5),有的是在之后(9)。因此如果你有下面的組件層級(jí)關(guān)系:A -> B -> C,下面就是聲明周期鉤子被調(diào)用和綁定更新的順序。

    A: AfterContentInit A: AfterContentChecked A: Update bindingsB: AfterContentInitB: AfterContentCheckedB: Update bindingsC: AfterContentInitC: AfterContentCheckedC: Update bindingsC: AfterViewInitC: AfterViewCheckedB: AfterViewInitB: AfterViewChecked A: AfterViewInit A: AfterViewChecked 復(fù)制代碼

    探索含義(Exploring the implications)

    我們假設(shè)有下面的一個(gè)組件樹(shù):

    正如我們上面所學(xué)到的,每一個(gè)組件都有一個(gè)與之相關(guān)聯(lián)的組件視圖,每一個(gè)視圖初始化時(shí)的ViewState.ChecksEnabled都為true,這就意味著當(dāng)Angular執(zhí)行變更檢測(cè)時(shí),組件樹(shù)上的每一個(gè)組件杜輝被檢查。

    假設(shè)我們想禁用掉AComponent及它的子組件的變更檢測(cè),我們只需要很簡(jiǎn)單的把它的ViewState.ChecksEnabled設(shè)置為false就可以的。直接改變狀態(tài)是一個(gè)低級(jí)的操作,因此Angular為我們提供了一些在視圖上可用的公共方法。每一個(gè)組件都可以通過(guò)ChangeDetectorRef來(lái)獲得與其關(guān)聯(lián)的視圖的引用,Angular文檔中為這個(gè)類定義了如下的公共接口:

    class ChangeDetectorRef {markForCheck() : voiddetach() : voidreattach() : voiddetectChanges() : voidcheckNoChanges() : void } 復(fù)制代碼

    讓我們看看我們看以從中收獲點(diǎn)什么吧。

    deatch

    第一個(gè)我們可以操作視圖的方法是deatch,它僅僅是能夠禁用掉對(duì)當(dāng)前視圖的檢查:

    detach(): void { this._view.state &= ~ViewState.ChecksEnabled; }復(fù)制代碼

    讓我們看看怎么在代碼中使用它:

    export class AComponent {constructor(public cd: ChangeDetectorRef) {this.cd.detach();} 復(fù)制代碼

    它確保了在接下來(lái)的變更檢測(cè)中,以AComponent為開(kāi)始的左側(cè)部分將會(huì)被忽略掉(橘黃色的組件將不會(huì)被檢查):

    在這里有兩個(gè)地方需要注意--第一個(gè)就是就是我們改變了AComponent的檢測(cè)狀態(tài),所有它的子組件也不會(huì)被檢查。第二個(gè)就是由于左側(cè)的組件們北郵執(zhí)行變更檢測(cè),所有他們呢的模板視圖也不會(huì)被更新,下面是一個(gè)小例子來(lái)證明這一點(diǎn):

    @Component({selector: 'a-comp',template: `<span>See if I change: {{changed}}</span>` }) export class AComponent {constructor(public cd: ChangeDetectorRef) {this.changed = 'false';setTimeout(() => {this.cd.detach();this.changed = 'true';}, 2000);} 復(fù)制代碼

    第一次(檢查)的時(shí)候,span標(biāo)簽將會(huì)被渲染成文本See if I change: false. 當(dāng)2秒后,changed屬性變?yōu)閠rue的時(shí)候,span標(biāo)簽中的文本將不會(huì)改變,但當(dāng)我們刪掉this.cd.detach()的時(shí)候,一切都會(huì)如期執(zhí)行。

    reattach

    像本文中第一部分中所說(shuō)的那樣,如果綁定的輸入屬性aProp在AppComponent中改變了,AComponent的OnChanges生命周期鉤子仍舊會(huì)觸發(fā)。這就意味著一旦我們輸入屬性改變了,我們就可以激活當(dāng)前視圖的變更檢測(cè)器去執(zhí)行變更檢測(cè),然后在下個(gè)事件循環(huán)中再把它從deatch(變更檢測(cè)樹(shù)中分離)掉,下面的代碼片段證明了這一點(diǎn):

    export class AComponent {@Input() inputAProp;constructor(public cd: ChangeDetectorRef) {this.cd.detach();}ngOnChanges(values) {this.cd.reattach();setTimeout(() => {this.cd.detach();})} 復(fù)制代碼

    其實(shí),reattach僅僅對(duì)ViewState.ChecksEnabled進(jìn)行了位操作:

    reattach(): void { this._view.state |= ViewState.ChecksEnabled; } 復(fù)制代碼

    這跟我們把ChangeDetectionStrategy設(shè)置為OnPush幾乎是等價(jià)的:在第一次變更檢測(cè)執(zhí)行完后就禁用掉,然后當(dāng)父組件綁定的屬性改變時(shí)再啟用檢查,檢查完了之后再禁用掉。

    注意只有在禁用分支的最頂層的組件的OnChanges鉤子才會(huì)被觸發(fā),而不是禁用分支的所有組件。

    markForCheck

    reattach方法只能對(duì)當(dāng)前的組件啟用檢查,但是如果當(dāng)前的組件的父組件沒(méi)有啟用臟檢查的話,它將不起作用,這就意味著reattach方法僅僅對(duì)禁用分支的頂層組件起作用。

    我們需要一個(gè)方法來(lái)對(duì)所有的父組件一直到根組件都啟用臟檢查,這里有一個(gè)markForCheck的方法:

    let currView: ViewData|null = view; while (currView) {if (currView.def.flags & ViewFlags.OnPush) {currView.state |= ViewState.ChecksEnabled;}currView = currView.viewContainerParent || currView.parent; } 復(fù)制代碼

    從上面的實(shí)現(xiàn)中可以看到,它僅僅是向上遍歷,對(duì)所有的父組件啟用檢查一直到根組件。

    什么時(shí)候它是有用的呢?就像是ngOnChanges一樣,即使組件使用OnPush策略,ngDoCheck生命周期鉤子也會(huì)被觸發(fā),同樣的,只有在禁用分支的最頂層的組件中才會(huì)被觸發(fā),而不是禁用分支的所有組件。但是我們可以用這個(gè)鉤子來(lái)執(zhí)行一些定制化的邏輯,使我們的組件可以在一個(gè)變更檢測(cè)周期中執(zhí)行檢查。由于Angular僅僅檢查對(duì)象的引用,我們可以實(shí)現(xiàn)一些對(duì)象屬性的臟檢查:

    Component({...,changeDetection: ChangeDetectionStrategy.OnPush }) MyComponent {@Input() items;prevLength;constructor(cd: ChangeDetectorRef) {}ngOnInit() {this.prevLength = this.items.length;}ngDoCheck() {if (this.items.length !== this.prevLength) {this.cd.markForCheck(); this.prevLenght = this.items.length;}} 復(fù)制代碼

    detectChanges

    有一種方法只在當(dāng)前視圖和它的子視圖只運(yùn)行一次變更檢測(cè),那就是detectChanges方法, 這個(gè)方法在運(yùn)行變更檢測(cè)時(shí)候不管當(dāng)前組件的狀態(tài)是什么,那就意味著當(dāng)前的視圖可能會(huì)保持禁用檢查的狀態(tài),在下一個(gè)常規(guī)的變更檢測(cè)進(jìn)行時(shí),它將不會(huì)被檢查,下面是一個(gè)例子:

    export class AComponent {@Input() inputAProp;constructor(public cd: ChangeDetectorRef) {this.cd.detach();}ngOnChanges(values) {this.cd.detectChanges();} 復(fù)制代碼

    當(dāng)輸入屬性改變的時(shí)候,即使變更檢測(cè)器還保持著分離的狀態(tài),DOM也會(huì)更新。

    checkNoChanges

    變更檢測(cè)器上最后一個(gè)有用的方法是在運(yùn)行當(dāng)前的變更檢測(cè)時(shí),確保沒(méi)有變化發(fā)生。基本上,它執(zhí)行了本文第一部分那個(gè)步驟中的1,7,8的操作,并且當(dāng)它發(fā)現(xiàn)一個(gè)綁定值變化了或是決定DOM應(yīng)該要被更行的時(shí)候,將會(huì)拋出一個(gè)異常。

    總結(jié)

    以上是生活随笔為你收集整理的[译] 关于Angular的变更检测(Change Detection)你需要知道这些的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 少妇精品久久久久www | 亚洲高清无码久久久 | 欧美理论在线 | heyzo朝桐光一区二区 | 国产成人一区二区三区免费看 | 亚洲国产精品成人无久久精品 | 国产精品高潮呻吟久久av黑人 | 人人妻人人藻人人爽欧美一区 | 国产女人高潮视频 | 偷拍老头老太高潮抽搐 | 日本高清视频在线播放 | 国产成人综合久久 | www一级片 | av图区 | 以女性视角写的高h爽文 | 福利社午夜影院 | 日韩人成| 夜夜天天 | 可以在线观看的av网站 | 久久久美女视频 | 国产乡下妇女做爰毛片 | 久久精品久久久久 | 日韩成人性视频 | 超碰在线cao | 黄色三级视屏 | 蜜桃精品久久久久久久免费影院 | 日本成人毛片 | 真实的国产乱xxxx在线 | 精品久久无码中文字幕 | 国产亚洲精品精品国产亚洲综合 | 国产一区二区免费电影 | 怡春院一区二区 | 男人的天堂av女优 | av一区二区三区免费观看 | 91久久免费视频 | 三级视频网站 | 性欧美在线视频观看 | 手机在线看片日韩 | 又黄又爽网站 | 懂色av蜜臀av粉嫩av喷吹 | 欧美成人精品二区三区99精品 | 在线观看av片 | 韩国一区二区三区在线观看 | 日本二区在线观看 | 福利在线小视频 | 五月婷在线视频 | 国产精品久久伊人 | 99久久免费精品 | 国产精品一区二区在线看 | 亚洲黄色片在线观看 | 九色视频在线观看 | 欧美日韩中文字幕一区二区 | 天天干天天色综合 | а√中文在线资源库 | 国内毛片毛片毛片毛片 | 88久久精品无码一区二区毛片 | 在线视频a | 中国av一区 | 91麻豆精品一二三区在线 | 国产一区二区三区视频在线 | 久久夜色精品国产欧美乱极品 | 国产激情免费视频 | 国产亚洲欧美精品久久久www | 香蕉视频污在线观看 | 久久午夜无码鲁丝片午夜精品 | 国产又猛又黄 | 老熟女一区二区三区 | 男女日批在线观看 | 欧美少妇精品 | 精品视频导航 | 久久精品视屏 | 免费黄色一级大片 | 欧美一级黄色网 | 蜜桃精品久久久久久久免费影院 | 欧美 亚洲 | 欧美精品91| 99精品欧美一区二区蜜桃免费 | 亚洲二区在线播放视频 | 就要日就要操 | 亚洲精品福利网站 | 久久艹这里只有精品 | 色姑娘av| 欧美福利在线视频 | 日韩视频第一页 | 国产美女福利在线 | 国产精品波多野结衣 | 丰满雪白极品少妇流白浆 | 亚洲成人黄色在线观看 | 黄色av免费播放 | 香蕉一级视频 | 性猛交╳xxx乱大交 偷偷操不一样的久久 | 国产亚洲精 | 黄色网址链接 | 中文字幕免费在线看线人动作大片 | 可以免费看黄的网站 | 国产美女福利 | av在线资源播放 | 亚洲精品久久久久久久久久 | 一级性爱视频 |