Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 肥女大bbwbbwbbwbbw…,欧美**毛片动作视频,亚洲日本中文字幕

          整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          242.C# HTMLAgilityPack 解析H

          242.C# HTMLAgilityPack 解析HTML


          HTMLAgilityPack 是一個(gè)開源的.NET庫,旨在幫助開發(fā)人員處理和操作HTML文檔。它提供了解析HTML文檔、查詢DOM元素以及修改HTML內(nèi)容的功能。HTMLAgilityPack 基于XPath和LINQ查詢,使得開發(fā)者能夠以類似于操作XML文檔的方式來操作HTML文檔。這使得從復(fù)雜的HTML結(jié)構(gòu)中提取所需數(shù)據(jù)變得輕而易舉。

          正文


          HTMLAgilityPack 主要用于以下幾個(gè)方面:

          1. 解析HTML文檔:HTMLAgilityPack 可以將原始的HTML文檔解析成一個(gè)DOM(文檔對(duì)象模型)樹,使開發(fā)者能夠輕松地遍歷和操作HTML元素。
          2. 查詢和選擇元素:通過使用XPath表達(dá)式或LINQ查詢,您可以輕松地選擇和定位HTML文檔中的特定元素,從而實(shí)現(xiàn)數(shù)據(jù)的抽取和操作。
          3. 修改HTML內(nèi)容:您可以使用HTMLAgilityPack來添加、刪除或修改HTML元素和屬性,以滿足特定的需求。
          4. HTML格式化與轉(zhuǎn)換:HTMLAgilityPack 還允許您將HTML文檔格式化為漂亮的字符串或轉(zhuǎn)換為其他格式,如純文本或Markdown。

          常用方法與學(xué)用屬性

          以下是一些常用的HTMLAgilityPack方法和屬性,以及它們的用途:

          1. HtmlDocument.Load(string path):從指定路徑加載HTML文檔。
          2. HtmlDocument.LoadHtml(string html):從字符串加載HTML文檔。
          3. HtmlDocument.DocumentNode:獲取整個(gè)HTML文檔的根節(jié)點(diǎn)。
          4. SelectSingleNode(string xpath):根據(jù)XPath表達(dá)式選擇單個(gè)節(jié)點(diǎn)。
          5. SelectNodes(string xpath):根據(jù)XPath表達(dá)式選擇多個(gè)節(jié)點(diǎn)。
          6. InnerText:獲取或設(shè)置元素的文本內(nèi)容。
          7. OuterHtml:獲取或設(shè)置元素及其內(nèi)部內(nèi)容的HTML。
          8. Attributes:獲取元素的屬性集合。
          9. AppendChild(HtmlNode newChild):將新節(jié)點(diǎn)添加為子節(jié)點(diǎn)。
          10. Remove():從文檔中移除當(dāng)前節(jié)點(diǎn)。

          Nuget安裝 HtmlAgilityPack庫

          解析并顯示標(biāo)題

          我們使用 HttpClient 發(fā)送一個(gè) GET 請(qǐng)求到指定的 HTTPS URL,并且讀取返回的響應(yīng)內(nèi)容。

          如果出現(xiàn) HTTP 狀態(tài)碼 403 (Forbidden) 錯(cuò)誤表示您的請(qǐng)求被服務(wù)器拒絕,通常是因?yàn)榉?wù)器認(rèn)為您沒有權(quán)限訪問該資源。

          1. 檢查網(wǎng)站訪問權(quán)限:確保您有權(quán)訪問目標(biāo)網(wǎng)站。有些網(wǎng)站可能需要登錄或具有特定權(quán)限才能訪問其內(nèi)容。
          2. 用戶代理頭:有些網(wǎng)站要求用戶代理標(biāo)頭,您可以嘗試在請(qǐng)求中添加一個(gè)用戶代理標(biāo)頭來模擬瀏覽器行為
          3. Cookies 和 Session:如果目標(biāo)網(wǎng)站使用 cookies 或會(huì)話來管理訪問權(quán)限,請(qǐng)確保您正確處理這些信息。您可能需要發(fā)送適當(dāng)?shù)?cookies 或會(huì)話信息以獲取訪問權(quán)限。
          4. IP封鎖:某些網(wǎng)站可能根據(jù) IP 地址阻止訪問,如果您的 IP 被封鎖,您可能需要使用代理服務(wù)器來繞過封鎖。
          5. 代理服務(wù)器:如果您的網(wǎng)絡(luò)中存在代理服務(wù)器,請(qǐng)確保代理服務(wù)器的設(shè)置正確,并且允許訪問目標(biāo)網(wǎng)站。
          6. 身份驗(yàn)證:如果網(wǎng)站需要身份驗(yàn)證,您可能需要在請(qǐng)求中包含適當(dāng)?shù)纳矸蒡?yàn)證憑據(jù)。您可以使用 HttpClient 的 DefaultRequestHeaders.Authorization 屬性來添加身份驗(yàn)證標(biāo)頭。
          7. 請(qǐng)求頭和參數(shù):某些網(wǎng)站可能會(huì)要求特定的請(qǐng)求頭或查詢參數(shù),您需要查看網(wǎng)站的文檔或分析網(wǎng)絡(luò)請(qǐng)求以確定所需的請(qǐng)求標(biāo)頭和參數(shù)。
          private async void btnGetTitle_Click(object sender, EventArgs e)
          {
              HtmlAgilityPack.HtmlDocument doc=new HtmlAgilityPack.HtmlDocument();
          
              string htmlContent="";
              using (HttpClient httpClient=new HttpClient())
              {
                  try
                  {
          
                      httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3");
                     
                      HttpResponseMessage response=await httpClient.GetAsync("https://www.baidu.com");
                      
                      //檢查 HTTP 響應(yīng)的狀態(tài)碼是否表示成功
                      response.EnsureSuccessStatusCode();
          
                      //讀取內(nèi)容
                      byte[] bytes=await response.Content.ReadAsByteArrayAsync();
                      htmlContent=Encoding.UTF8.GetString(bytes);
          
                  }
                  catch (HttpRequestException ex)
                  {
          
                  }
              }
          
              doc.LoadHtml(htmlContent);
          
              HtmlNode titleNode=doc.DocumentNode.SelectSingleNode("//title");
              if (titleNode !=null)
              {
                  string title=titleNode.InnerText;
                  MessageBox.Show($"頁面標(biāo)題:{title}");
              }
          }
          
          

          提取所有鏈接

          /// <summary>
          /// 通過url取得html內(nèi)容
          /// </summary>
          /// <param name="url"></param>
          /// <returns></returns>
          private async Task<string> GetHtml(string url)
          {
          
              string htmlContent="";
              using (HttpClient httpClient=new HttpClient())
              {
                  try
                  {
          
                      httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3");
          
                      HttpResponseMessage response=await httpClient.GetAsync(url);
          
                      response.EnsureSuccessStatusCode();
          
                      //讀取內(nèi)容
                      byte[] bytes=await response.Content.ReadAsByteArrayAsync();
                      htmlContent=Encoding.UTF8.GetString(bytes);
          
                  }
                  catch (HttpRequestException ex)
                  {
          
                  }
              }
              return htmlContent;
          }
          
          private async void btnGetLinks_Click(object sender, EventArgs e)
          {
              HtmlAgilityPack.HtmlDocument doc=new HtmlAgilityPack.HtmlDocument();
              string htmlContent=await GetHtml("https://www.baidu.com");
              doc.LoadHtml(htmlContent);
          
              HtmlNodeCollection linkNodes=doc.DocumentNode.SelectNodes("//a[@href]");
              if (linkNodes !=null)
              {
                  foreach (HtmlNode linkNode in linkNodes)
                  {
                      string link=linkNode.GetAttributeValue("href", "");
                      lstLink.Items.Add(link);
                  }
              }
          }
          
          

          通過class 去找節(jié)點(diǎn)

          private async void btnGetSpecialLink_Click(object sender, EventArgs e)
          {
              HtmlAgilityPack.HtmlDocument doc=new HtmlAgilityPack.HtmlDocument();
              string htmlContent=await GetHtml("https://news.baidu.com/");
              doc.LoadHtml(htmlContent);
          
              HtmlNodeCollection linkNodes=doc.DocumentNode.SelectNodes("//*[@id=\"pane-news\"]/ul/li[@class=\"bold-item\"]/a");
              if (linkNodes !=null)
              {
                  foreach (HtmlNode linkNode in linkNodes)
                  {
                      string link=linkNode.GetAttributeValue("href", "");
                      string title=linkNode.InnerText;
                      lnkSpecialLink.Items.Add(title + " " + link);
                  }
              }
          }
          
          

          快速找到節(jié)點(diǎn)path

          Linq 找查節(jié)點(diǎn)



          SP.NET Core中,嚴(yán)格來講只有一個(gè)Asp.net Core MVC一個(gè)框架,Asp.net Core MVC既支持基于視圖的MVC開發(fā),也支持Web API和Razor Pages開發(fā)。

          ASP.NET Core MVC項(xiàng)目

          在VS2022中,一定選擇ASP.NET Core Web應(yīng)用(模型-視圖-控制器)項(xiàng)目模板

          項(xiàng)目中wwwroot文件夾包含圖片、JS、CSS等靜態(tài)文件,Models、Views、Controllers是與MVC相關(guān)的文件夾,appsetting.json是默認(rèn)配置文件,program.cs為入口代碼

          MVC模式包括:模型、視圖和控制器三部分,視圖負(fù)責(zé)展示內(nèi)容,也就是html網(wǎng)頁;控制器負(fù)責(zé)處理用戶的請(qǐng)求和為視圖準(zhǔn)備數(shù)據(jù);模型負(fù)責(zé)在視圖和控制器之前傳遞數(shù)據(jù)。

          MVC約定:

          MVC中,控制器由Controller類實(shí)現(xiàn),視圖一般為*.cshtml文件,模型則是C#中的普通類。

          控制器的名字一般以Controller結(jié)尾,并放在Controllers文件夾下

          視圖一般放到Views/控制器名稱文件夾下

          模型則放在model文件夾下

          工作過程

          瀏覽器提交的請(qǐng)求會(huì)被封裝到模型類的對(duì)象中,然后傳遞給控制器,控制器對(duì)其進(jìn)行處理后將結(jié)果放到模型類的對(duì)象中再傳給視圖,視圖則解析模型對(duì)象渲染成HTML輸出給瀏覽器。

          案例:

          1. 1. 在Models文件夾下創(chuàng)建一個(gè)類

          public record Person(string Name, bool IsVIP, DateTime CreatedTime);

          1. 1. 在Controllers文件夾下創(chuàng)建一個(gè)繼承自Controller類的TestController類
          public class TestController : Controller
          { //控制器類中被外界直接訪問的方法叫做操作方法
               public IActionResult Demo1(int a) //返回類型為IActionResult
               {
                   var model=new Person("Zack", true, new DateTime(1999, 9, 9));
                   return View(model);//將model對(duì)象傳遞給與操作方法同名的View
               }
          }
          1. 1. 在views文件夾中創(chuàng)建Test子目錄,然后再Test子目錄中添加視圖,視圖名稱為Demo1.cshtml
          @model MVC項(xiàng)目1.Models.Person
          <div>姓名:@Model.Name</div>
          <div>@(Model.IsVIP?"VIP":"普通會(huì)員")</div>
          <div>注冊(cè)時(shí)間:@Model.CreatedTime</div>

          @model MVC項(xiàng)目1.Models.Person:視圖文件是接收Person類數(shù)據(jù)的強(qiáng)類型視圖

          以@開頭的代碼后面跟C#語句,這種代碼叫做服務(wù)器端代碼

          我們?cè)谠L問.../Test/Demo1地址時(shí),框架會(huì)尋找名為TestController類中的Demo1方法來處理用戶的請(qǐng)求,運(yùn)行該操作方法,則會(huì)將view渲染成HTML輸出給瀏覽器

          ASP.NET Core Web API

          MVC開發(fā)的系統(tǒng)一般是在瀏覽器中查看,而目前訪問服務(wù)器的客戶端已不僅僅是瀏覽器了,很多客戶端和服務(wù)器之間一般都是使用JSON結(jié)構(gòu)來傳遞數(shù)據(jù),所以需要將數(shù)據(jù)進(jìn)行結(jié)構(gòu)化,執(zhí)行結(jié)構(gòu)化操作的服務(wù)叫做Web API。

          ASP.NET Core Web API與MVC的項(xiàng)目目錄結(jié)構(gòu)相似,但是沒有Views和Models文件夾,因?yàn)閃eb API直接返回的是結(jié)構(gòu)化數(shù)據(jù),不需要提供展示數(shù)據(jù)的視圖。

          樣板代碼WeatherForecastController,繼承自ControllerBase類,MVC中Controller類同樣是繼承自ControllerBase,Controller類中多了View和MVC中視圖相關(guān)的代碼。

              [ApiController] //Web API都要加
              [Route("[controller]")] //設(shè)定路由,[controller]代表控制器的名字,在這里是WeatherForecast
                                      //表示對(duì)/WeatherForecast路徑的請(qǐng)求由WeatherForecastController來處理
              public class WeatherForecastController : ControllerBase
              {
                  //當(dāng)對(duì)/WeatherForecast路徑發(fā)送Get請(qǐng)求時(shí)由該方法Get處理
                  [HttpGet(Name="GetWeatherForecast")]
                  public IEnumerable<WeatherForecast> Get()
                  {...
                  }
              }

          Post Put等方法

          先增加一個(gè)類SaveNoteRequest,然后在WeatherForecastController中增加一個(gè)SaveNote方法,并設(shè)定[HttpPost]

          public record SaveNoteRequest(string Title, string Content);
          
          [HttpPost]
          public string SaveNote(SaveNoteRequest req)
          {
              string filename=$"{req.Title}.txt";
              System.IO.File.WriteAllText(filename, req.Content);
              return filename;
          }

          可以在自帶Swagger頁面中進(jìn)行調(diào)試查看結(jié)構(gòu)數(shù)據(jù),注意只有勾選[啟用OpenAPI支持]才會(huì)生成一個(gè)接口定義瀏覽界面

          話說,人靠衣裝,佛靠金裝。大街上的小姐姐都喜歡把自己打扮得美美的,讓你忍不住多看幾眼,這就是裝飾的作用。



          1. 前言

          裝飾器是最新的 ECMA 中的一個(gè)提案,是一種與類(class)相關(guān)的語法,用來注釋或修改類和類方法。裝飾器在 Python 和 Java 等語言中也被大量使用。裝飾器是實(shí)現(xiàn) AOP(面向切面)編程的一種重要方式。



          下面是一個(gè)使用裝飾器的簡單例子,這個(gè) @readonly 可以將 count 屬性設(shè)置為只讀??梢钥闯鰜?,裝飾器大大提高了代碼的簡潔性和可讀性。

          class Person {
              @readonly count=0;
          }
          復(fù)制代碼

          由于瀏覽器還未支持裝飾器,為了讓大家能夠正常看到效果,這里我使用 Parcel 進(jìn)行了一下簡單的配置,可以去 clone 這個(gè)倉庫后再來運(yùn)行本文涉及到的所有例子,倉庫地址:learn es6

          本文涉及到 Object.defineProperty、高階函數(shù)等知識(shí),如果之前沒有了解過相關(guān)概念,建議先了解后再來閱讀本文。

          2. 裝飾器模式

          在開始講解裝飾器之前,先從經(jīng)典的裝飾器模式說起。裝飾器模式是一種結(jié)構(gòu)型設(shè)計(jì)模式,它允許向一個(gè)現(xiàn)有的對(duì)象添加新的功能,同時(shí)又不改變其結(jié)構(gòu),是作為對(duì)現(xiàn)有類的一個(gè)包裝。

          一般來說,在代碼設(shè)計(jì)中,我們應(yīng)當(dāng)遵循「多用組合,少用繼承」的原則。通過裝飾器模式動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就增加功能來說,裝飾器模式相比生成子類更為靈活。

          2.1 一個(gè)英雄聯(lián)盟的例子

          下班回去和朋友愉快地開黑,當(dāng)我正在用亞索「面對(duì)疾風(fēng)吧」的時(shí)候,突然想到,如果讓我設(shè)計(jì)亞索英雄,我該怎么實(shí)現(xiàn)呢?



          我靈光一閃,那肯定會(huì)先設(shè)計(jì)一個(gè)英雄的類。

          class Hero {
              attack() {}
          }
          復(fù)制代碼

          然后,再實(shí)現(xiàn)一個(gè) Yasuo 的類來繼承這個(gè) Hero 類。

          class Yasuo extends Hero {
              attack() {
                  console.log("斬鋼閃");
              }
          }
          復(fù)制代碼

          我還在想這個(gè)問題的時(shí)候,隊(duì)友已經(jīng)打了大龍,我的亞索身上就出現(xiàn)了大龍 buff 的印記。我突然想到,那該怎么給英雄增加大龍 buff 呢?那增加個(gè)大龍 buff 的屬性不行嗎?

          當(dāng)然不太行,要知道,英雄聯(lián)盟里面的大龍 buff 是會(huì)增加收益的。

          嗯,聰明的我已經(jīng)想到辦法了,再繼承一次不就好了嗎?

          class BaronYasuo extends Yasuo {}
          復(fù)制代碼

          厲害了,但是如果亞索身上還有其他 buff 呢?畢竟 LOL 里面是有紅 buff、藍(lán) buff、大龍 buff 等等存在,那豈不是有多少種就要增加多少個(gè)類嗎?



          可以換種思路來思考這個(gè)問題,如果把 buff 當(dāng)做我們身上的衣服。在不同的季節(jié),我們會(huì)換上不同的衣服,到了冬天,甚至還會(huì)疊加多件衣服。當(dāng) buff 消失了,就相當(dāng)于把這件衣服脫了下來。如下圖所示:



          衣服對(duì)人來說起到裝飾的作用,buff 對(duì)于亞索來說也只是增強(qiáng)效果。那么,你是不是有思路了呢? 沒錯(cuò),可以創(chuàng)建 Buff 類,傳入英雄類后獲得一個(gè)新的增強(qiáng)后的英雄類。

          class RedBuff extends Buff {
              constructor(hero) {
                  this.hero=hero;
              }
              // 紅buff造成額外傷害
              extraDamage() {
              }
              attack() {
                  return this.hero.attack() + this.extraDamage();
              }
          }
          class BlueBuff extends Buff {
              constructor(hero) {
                  this.hero=hero;
              }
              // 技能CD(減10%)
              CDR() {
                  return this.hero.CDR() * 0.9;
              }
          }
          class BaronBuff extends Buff {
              constructor(hero) {
                  this.hero=hero;
              }
              // 回城速度縮短一半
              backSpeed() {
                  return this.hero.backSpeed * 0.5;
              }
          }
          復(fù)制代碼

          定義好所有的 buff 類之后,就可以直接套用到英雄身上,這樣看起來是不是清爽了很多呢?這種寫法看起來很像函數(shù)組合。

          const yasuo=new Yasuo();
          const redYasuo=new RedBuff(yasuo); // 紅buff亞索
          const blueYasuo=new BlueBuff(yasuo); // 藍(lán)buff亞索
          const redBlueYasuo=new BlueBuff(redYasuo); // 紅藍(lán)buff亞索
          復(fù)制代碼



          3. ES7 裝飾器

          decorator(裝飾器)是 ES7 中的一個(gè)提案,目前處于 stage-2 階段,提案地址:JavaScript Decorators。

          裝飾器與之前講過的函數(shù)組合(compose)以及高階函數(shù)很相似。裝飾器使用 @ 作為標(biāo)識(shí)符,被放置在被裝飾代碼前面。在其他語言中,早就已經(jīng)有了比較成熟的裝飾器方案。

          3.1 Python 中的裝飾器

          先來看一下 Python 中的一個(gè)裝飾器的例子:

          def auth(func):
              def inner(request,*args,**kwargs):
                  v=request.COOKIES.get('user')
                  if not v:
                      return redirect('/login')
                  return func(request, *args,**kwargs)
              return inner
          
          @auth
          def index(request):
              v=request.COOKIES.get("user")
              return render(request,"index.html",{"current_user":v})
          復(fù)制代碼



          這個(gè) auth 裝飾器是通過檢查 cookie 來判斷用戶是否登錄的。auth 函數(shù)是一個(gè)高階函數(shù),它接收了一個(gè) func 函數(shù)作為參數(shù),返回了一個(gè)新的 inner 函數(shù)。

          在 inner 函數(shù)中進(jìn)行 cookie 的檢查,由此來判斷是跳回登錄頁面還是繼續(xù)執(zhí)行 func 函數(shù)。

          在所有需要權(quán)限驗(yàn)證的函數(shù)上,都可以使用這個(gè) auth 裝飾器,很簡潔明了且無侵入。

          3.2 JavaScript 裝飾器

          JavaScript 中的裝飾器和 Python 的裝飾器類似,依賴于 Object.defineProperty,一般是用來裝飾類、類屬性、類方法。

          使用裝飾器可以做到不直接修改代碼,就實(shí)現(xiàn)某些功能,做到真正的面向切面編程。這在一定程度上和 Proxy 很相似,但使用起來比 Proxy 會(huì)更加簡潔。

          注意:裝飾器目前還處于 stage-2,意味著語法之后也許會(huì)有變動(dòng)。裝飾器用于函數(shù)、對(duì)象等等已經(jīng)有一些規(guī)劃,請(qǐng)看:Future built-in decorators

          3.3 類裝飾器

          裝飾類的時(shí)候,裝飾器方法一般會(huì)接收一個(gè)目標(biāo)類作為參數(shù)。下面是一個(gè)給目標(biāo)類增加靜態(tài)屬性 test 的例子:

          const decoratorClass=(targetClass)=> {
              targetClass.test='123'
          }
          @decoratorClass
          class Test {}
          Test.test; // '123'
          復(fù)制代碼

          除了可以修改類本身,還可以通過修改原型,給實(shí)例增加新屬性。下面是給目標(biāo)類增加 speak 方法的例子:

          const withSpeak=(targetClass)=> {
              const prototype=targetClass.prototype;
              prototype.speak=function() {
                  console.log('I can speak ', this.language);
              }
          }
          @withSpeak
          class Student {
              constructor(language) {
                  this.language=language;
              }
          }
          const student1=new Student('Chinese');
          const student2=new Student('English');
          student1.speak(); // I can speak  Chinese
          
          student2.speak(); // I can speak  Englist
          復(fù)制代碼

          利用高階函數(shù)的屬性,還可以給裝飾器傳參,通過參數(shù)來判斷對(duì)類進(jìn)行什么處理。

          const withLanguage=(language)=> (targetClass)=> {
              targetClass.prototype.language=language;
          }
          @withLanguage('Chinese')
          class Student {
          }
          const student=new Student();
          student.language; // 'Chinese'
          復(fù)制代碼

          如果你經(jīng)常編寫 react-redux 的代碼,那么也會(huì)遇到需要將 store 中的數(shù)據(jù)映射到組件中的情況。connect 是一個(gè)高階組件,它接收了兩個(gè)函數(shù) mapStateToProps 和 mapDispatchToProps 以及一個(gè)組件 App,最終返回了一個(gè)增強(qiáng)版的組件。

          class App extends React.Component {
          }
          connect(mapStateToProps, mapDispatchToProps)(App)
          復(fù)制代碼

          有了裝飾器之后,connect 的寫法可以變得更加優(yōu)雅。

          @connect(mapStateToProps, mapDispatchToProps)
          class App extends React.Component {
          }
          復(fù)制代碼

          3.4 類屬性裝飾器

          類屬性裝飾器可以用在類的屬性、方法、get/set 函數(shù)中,一般會(huì)接收三個(gè)參數(shù):

          1. target:被修飾的類
          2. name:類成員的名字
          3. descriptor:屬性描述符,對(duì)象會(huì)將這個(gè)參數(shù)傳給 Object.defineProperty

          使用類屬性裝飾器可以做到很多有意思的事情,比如最開始舉的那個(gè) readonly 的例子:

          function readonly(target, name, descriptor) {
            descriptor.writable=false;
            return descriptor;
          }
          class Person {
              @readonly name='person'
          }
          const person=new Person();
          person.name='tom'; 
          復(fù)制代碼

          還可以用來統(tǒng)計(jì)一個(gè)函數(shù)的執(zhí)行時(shí)間,以便于后期做一些性能優(yōu)化。

          function time(target, name, descriptor) {
              const func=descriptor.value;
              if (typeof func==='function') {
                  descriptor.value=function(...args) {
                      console.time();
                      const results=func.apply(this, args);
                      console.timeEnd();
                      return results;
                  }
              }
          }
          class Person {
              @time
              say() {
                  console.log('hello')
              }
          }
          const person=new Person();
          person.say();
          復(fù)制代碼

          在 react 知名的狀態(tài)管理庫 mobx 中,也通過裝飾器來將類屬性置為可觀察屬性,以此來實(shí)現(xiàn)響應(yīng)式編程。

          import {
              observable,
              action,
              autorun
          } from 'mobx'
          
          class Store {
              @observable count=1;
              @action
              changeCount(count) {
                  this.count=count;
              }
          }
          
          const store=new Store();
          autorun(()=> {
              console.log('count is ', store.count);
          })
          store.changeCount(10); // 修改 count 的值,會(huì)引起 autorun 中的函數(shù)自動(dòng)執(zhí)行。
          復(fù)制代碼

          3.5 裝飾器組合

          如果你想要使用多個(gè)裝飾器,那么該怎么辦呢?裝飾器是可以疊加的,根據(jù)離被裝飾類/屬性的距離來依次執(zhí)行。

          class Person {
              @time
              @log
              say() {}
          }
          復(fù)制代碼

          除此之外,在裝飾器的提案中,還出現(xiàn)了一種組合了多種裝飾器的裝飾器例子。目前還沒見到被使用。

          通過使用 decorator 來聲明一個(gè)組合裝飾器 xyz,這個(gè)裝飾器組合了多種裝飾器。

          decorator @xyz(arg, arg2 {
            @foo @bar(arg) @baz(arg2)
          }
          @xyz(1, 2) class C { }
          復(fù)制代碼

          和下面這種寫法是一樣的。

          @foo @bar(1) @baz(2)
          class C { }
          復(fù)制代碼

          4. 裝飾器可以做哪些有意思的事情?

          4.1 多重繼承

          在實(shí)現(xiàn) JavaScript 多重繼承的時(shí)候,可以使用 mixin 的方式,這里結(jié)合裝飾器甚至還能更進(jìn)一步簡化 mixin 的使用。

          mixin 方法將會(huì)接收一個(gè)父類列表,用其裝飾目標(biāo)類。我們理想中的用法應(yīng)該是這樣:

          @mixin(Parent1, Parent2, Parent3)
          class Child {}
          復(fù)制代碼

          和之前實(shí)現(xiàn)多重繼承的時(shí)候?qū)崿F(xiàn)原理一致,只需要拷貝父類的原型屬性和實(shí)例屬性就可以實(shí)現(xiàn)了。

          這里創(chuàng)建了一個(gè)新的 Mixin 類,來將 mixins 和 targetClass 上面的所有屬性都拷貝過去。

          const mixin=(...mixins)=> (targetClass)=> {
            mixins=[targetClass, ...mixins];
            function copyProperties(target, source) {
              for (let key of Reflect.ownKeys(source)) {
                if (key !=='constructor'
                  && key !=='prototype'
                  && key !=='name'
                ) {
                  let desc=Object.getOwnPropertyDescriptor(source, key);
                  Object.defineProperty(target, key, desc);
                }
              }
            }
            class Mixin {
              constructor(...args) {
                for (let mixin of mixins) {
                  copyProperties(this, new mixin(...args)); // 拷貝實(shí)例屬性
                }
              }
            }
          
            for (let mixin of mixins) {
              copyProperties(Mixin, mixin); // 拷貝靜態(tài)屬性
              copyProperties(Mixin.prototype, mixin.prototype); // 拷貝原型屬性
            }
            return Mixin;
          }
          
          export default mixin
          復(fù)制代碼

          我們來測試一下這個(gè) mixin 方法是否能夠正常工作吧。

          class Parent1 {
              p1() {
                  console.log('this is parent1')
              }
          }
          class Parent2 {
              p2() {
                  console.log('this is parent2')
              }
          }
          class Parent3 {
              p3() {
                  console.log('this is parent3')
              }
          }
          @mixin(Parent1, Parent2, Parent3)
          class Child {
              c1=()=> {
                  console.log('this is child')
              }
          }
          const child=new Child();
          console.log(child);
          復(fù)制代碼

          最終在瀏覽器中打印出來的 child 對(duì)象是這樣的,證明了這個(gè) mixin 是可以正常工作的。

          注意:這里的 Child 類就是前面的 Mixin 類。



          也許你會(huì)問,為什么還要多創(chuàng)建一個(gè)多余的 Mixin 類呢?為什么不能直接修改 targetClass 的 constructor 呢?前面不是講過 Proxy 可以攔截 constructor 嗎?

          恭喜你,你已經(jīng)想到了 Proxy 的一種使用場景。沒錯(cuò),這里用 Proxy 的確會(huì)更加優(yōu)雅。

          const mixin=(...mixins)=> (targetClass)=> {
              function copyProperties(target, source) {
                  for (let key of Reflect.ownKeys(source)) {
                    if ( key !=='constructor'
                      && key !=='prototype'
                      && key !=='name'
                    ) {
                      let desc=Object.getOwnPropertyDescriptor(source, key);
                      Object.defineProperty(target, key, desc);
                    }
                  }
                }
              
                for (let mixin of mixins) {
                  copyProperties(targetClass, mixin); // 拷貝靜態(tài)屬性
                  copyProperties(targetClass.prototype, mixin.prototype); // 拷貝原型屬性
                }
                // 攔截 construct 方法,進(jìn)行實(shí)例屬性的拷貝
                return new Proxy(targetClass, {
                  construct(target, args) {
                    const obj=new target(...args);
                    for (let mixin of mixins) {
                        copyProperties(obj, new mixin()); // 拷貝實(shí)例屬性
                    }
                    return obj;
                  }
                });
          }
          復(fù)制代碼

          4.2 防抖和節(jié)流

          以往我們?cè)陬l繁觸發(fā)的場景下,為了優(yōu)化性能,經(jīng)常會(huì)使用到節(jié)流函數(shù)。下面以 React 組件綁定滾動(dòng)事件為例子:

          class App extends React.Component {
              componentDidMount() {   
                  this.handleScroll=_.throttle(this.scroll, 500);
                  window.addEveneListener('scroll', this.handleScroll);
              }
              componentWillUnmount() {
                  window.removeEveneListener('scroll', this.handleScroll);
              }
              scroll() {}
          }
          復(fù)制代碼

          在組件中綁定事件需要注意應(yīng)當(dāng)在組件銷毀的時(shí)候進(jìn)行解綁。而由于節(jié)流函數(shù)返回了一個(gè)新的匿名函數(shù),所以為了之后能夠有效解綁,不得不將這個(gè)匿名函數(shù)存起來,以便于之后使用。

          但是在有了裝飾器之后,我們就不必在每個(gè)綁定事件的地方都手動(dòng)設(shè)置 throttle 方法,只需要在 scroll 函數(shù)添加一個(gè) throttle 的裝飾器就行了。

          const throttle=(time)=> {
              let prev=new Date();
              return (target, name, descriptor)=> {
                  const func=descriptor.value;
                  if (typeof func==='function') {
                      descriptor.value=function(...args) {
          		        const now=new Date();
          		        if (now - prev > wait) {
          			        fn.apply(this, args);
          			        prev=new Date();
          		        }
                      }
                  }
              }
          }
          復(fù)制代碼

          使用起來比原來要簡潔很多。

          class App extends React.Component {
              componentDidMount() {
                  window.addEveneListener('scroll', this.scroll);
              }
              componentWillUnmount() {
                  window.removeEveneListener('scroll', this.scroll);
              }
              @throttle(50)
              scroll() {}
          }
          復(fù)制代碼

          而實(shí)現(xiàn)防抖(debounce)函數(shù)裝飾器和節(jié)流函數(shù)類似,這里也不再多說。

          const debounce=(time)=> {
              let timer;
              return (target, name, descriptor)=> {
                  const func=descriptor.value;
                  if (typeof func==='function') {
                      descriptor.value=function(...args) {
                          if(timer) clearTimeout(timer)
                          timer=setTimeout(()=> {
                              fn.apply(this, args)
                          }, wait)
                      }
                  }
              }
          }
          復(fù)制代碼

          如果對(duì)節(jié)流和防抖函數(shù)比較感興趣,那么可以去閱讀一下這篇文章:函數(shù)節(jié)流與函數(shù)防抖

          4.3 數(shù)據(jù)格式驗(yàn)證

          通過類屬性裝飾器來對(duì)類的屬性進(jìn)行類型的校驗(yàn)。

          const validate=(type)=> (target, name)=> {
              if (typeof target[name] !==type) {
                  throw new Error(`attribute ${name} must be ${type} type`)
              }
          }
          class Form {
              @validate('string')
              static name=111 // Error: attribute name must be ${type} type
          }
          復(fù)制代碼

          如果你覺得對(duì)屬性一個(gè)個(gè)手動(dòng)去校驗(yàn)太過麻煩,也可以通過編寫校驗(yàn)規(guī)則,來對(duì)整個(gè)類進(jìn)行校驗(yàn)。



          const rules={
              name: 'string',
              password: 'string',
              age: 'number'
          }
          const validator=rules=> targetClass=> {
              return new Proxy(targetClass, {
                  construct(target, args) {
                      const obj=new target(...args);
                      for (let [name, type] of Object.entries(rules)) {
                          if (typeof obj[name] !==type) {
                              throw new Error(`${name} must be ${type}`)
                          }
                      }
                      return obj;
                  }
              })
          }
          
          @validator(rules)
          class Person {
              name='tom'
              password='123'
              age='21'
          }
          const person=new Person();
          復(fù)制代碼

          4.4 core-decorators.js

          core-decorators 是一個(gè)封裝了常用裝飾器的 JS 庫,它歸納了下面這些裝飾器(只列舉了部分)。

          1. autobind:自動(dòng)綁定 this,告別箭頭函數(shù)和 bind
          2. readonly:將類屬性設(shè)置為只讀
          3. override:檢查子類的方法是否正確覆蓋了父類的同名方法
          4. debounce:防抖函數(shù)
          5. throttle:節(jié)流函數(shù)
          6. enumerable:讓一個(gè)類方法變得可枚舉
          7. nonenumerable:讓一個(gè)類屬性變得不可枚舉
          8. time:打印函數(shù)執(zhí)行耗時(shí)
          9. mixin:將多個(gè)對(duì)象混入類(和我們上面的 mixin 不太一樣)

          5. 總結(jié)

          裝飾器雖然還屬于不穩(wěn)定的語法,但在很多框架中都已經(jīng)廣泛使用,例如 Angular、Nestjs 等等,和 Java 中的注解用法非常相似。 裝飾器在 TypeScript 中結(jié)合反射后還有一些更高級(jí)的應(yīng)用,下篇文章會(huì)進(jìn)行深入講解。

          推薦閱讀:

          1. 裝飾器 —— 阮一峰
          2. JS 裝飾器(Decorator)場景實(shí)戰(zhàn)
          3. 探索JavaScript中的裝飾器模式
          4. 王者榮耀之「裝飾者模式」

          ?? 看完三件事

          如果你覺得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:

          1. 點(diǎn)贊,讓更多的人也能看到這篇內(nèi)容(收藏不點(diǎn)贊,都是耍流氓 -_-)
          2. 關(guān)注公眾號(hào)「前端小館」,不定期分享原創(chuàng)知識(shí)。
          3. 原鏈接:https://juejin.im/post/5e7822c3e51d4526f23a45ae

          主站蜘蛛池模板: 视频在线一区二区| 视频一区精品自拍| 看电影来5566一区.二区| 亚洲视频一区在线观看| 一区二区三区在线看| 国产精品高清一区二区人妖| 97久久精品午夜一区二区| 日本国产一区二区三区在线观看| 一区二区三区在线|日本| 精品国产AV无码一区二区三区| 日韩人妻无码一区二区三区99| 国产一区二区三区夜色| 美女免费视频一区二区| 国内精自品线一区91| 九九无码人妻一区二区三区| 久久精品无码一区二区WWW| 国产午夜精品一区二区三区漫画| 日本中文一区二区三区亚洲| 国产激情一区二区三区在线观看| 久久青青草原一区二区| 亚洲综合av一区二区三区不卡| 精品国产福利一区二区| 精品一区精品二区制服| 91精品一区二区| 日韩精品无码一区二区中文字幕| 在线精品亚洲一区二区小说| 不卡一区二区在线| 国产精品视频一区二区三区四| 国产在线观看一区二区三区精品| 国产成人精品一区二区三区 | 日本在线电影一区二区三区| 尤物精品视频一区二区三区 | 国产福利电影一区二区三区,日韩伦理电影在线福 | 国产日韩AV免费无码一区二区| 一区二区三区中文| 中文字幕日韩一区二区三区不| 亲子乱av一区二区三区| 精品一区二区三区免费| 亚洲视频免费一区| 福利一区二区在线| 日本精品高清一区二区|