【Angular專題】 (3)裝飾器decorator,一塊語法糖

来源:https://www.cnblogs.com/dashnowords/archive/2018/12/21/10158537.html
-Advertisement-
Play Games

[TOC] 一. Decorator裝飾器 修飾器是 加入的新特性, 中進行了大量使用,有很多內置的修飾器,後端的同學一般稱之為 “註解” 。修飾器的作用,實際上就是設計模式中常說的 裝飾者模式 的一種實現,早在 開始,設計模式原生化就已經是非常明顯的趨勢了,無論是 和`Iterator Proxy ...


目錄

一. Decorator裝飾器

修飾器是ES7加入的新特性,Angular中進行了大量使用,有很多內置的修飾器,後端的同學一般稱之為“註解”。修飾器的作用,實際上就是設計模式中常說的裝飾者模式的一種實現,早在ES6開始,設計模式原生化就已經是非常明顯的趨勢了,無論是for..of..Iterator介面的配合內化了迭代者模式Proxy對象實現的代理模式等等,都可以看出Javascript逐漸走向標準化的趨勢和決心。

裝飾者模式,是指在不必改變原類文件或使用繼承的情況下,動態地擴展一個對象的功能,為對象增加額外特性的一種設計模式。考慮到javascript中函數參數為對象時只傳遞地址這一特性,裝飾者模式實際上是非常好復現的,掌握其基本知識對於理解Angular技術棧的原理和執行流程是必不可少的,從結果的角度來看,使用裝飾器和直接修改類的定義沒有什麼區別,但使用裝飾器更符合開放封閉原則,且更符合聲明式的思想,本文著重分析Typescript中支持的幾種不同的裝飾器用法。

二. Typescript中的裝飾器

2.1 類裝飾器

類裝飾器,就是用來裝飾類的,它只接受一個參數,就是被裝飾的類。下麵的示例使用@testable修飾器為已定義的類加上一個__testable屬性:

//裝飾器修改的是類定義的表現,故在javascript中模擬時需要直接將變化添加至原型上
function testable(target: Function):void{
    target.prototype.__testable = false;
}

//使用類裝飾器
@testable
class Person{
    constructor(){}
}

//測試裝飾後的結果
let person = new Person();
console.log(person.__testable);//false

另一方面,我們可以使用工廠函數的方法生成一個可接收附加參數的裝飾器,藉助高階函數的思路不難理解,例如Angular中常見的這種形式:

//Angular中的組件定義
@Component({
    selector:'hero-detail',
    templateUrl:'hero-detail.html',
    styleUrls:['style.css']
})
export Class MyComponent{
    constructor(){}
}

//@Component裝飾者類的作用機制可以理解為:
function Component(params:any){
    return function(target: Function):void{
        target.prototype.metadata = params;
    }
}

這樣在組件被實例化時,就可以獲取到傳入的元數據信息。換句話說,Component({...})執行後返回的函數才是真正的類裝飾器,Component是一個接受參數然後生成裝飾器的函數,也就是裝飾器工廠,從元編程的角度來講,相當於修改了new操作符的行為。

2.2 方法裝飾器

方法修飾器聲明在一個方法的聲明之前,會被應用到方法的屬性描述符上,可以用來檢視,修改或者替換方法定義。它接收如下三個參數:

  • 1.靜態成員時參數是類的構造函數,實例成員時傳入類的原型對象。
  • 2.成員名
  • 3.成員屬性描述符

下麵的裝飾器@enumerable將被修飾對象修改為可枚舉:

//方法裝飾器,返回值會直接賦值給方法的屬性描述符。
function enumerable(target: any, propertyKey: string, descriptor:PropertyDescriptor):void{
    descriptor.enumerable = true;
}

class Person{
    constructor(){}
    
    @enumerable//使用方法裝飾器
    sayHi(){
        console.log('Hi');
    }
}

//測試裝飾後的結果
let person = new Person();
console.log(person.__testable);//false

更常用的方式依然是利用高階函數返回一個可被外部控制的裝飾器:

function enumerable(value: boolean){
    return function (target: any, propertyKey: string, descriptor:PropertyDescriptor):void{
        descriptor.enumerable = true;
    }
}

2.3 訪問器裝飾器

訪問器,一般指屬性的get/set方法,和普通方法裝飾器用法一致,需要註意的是typescript中不支持同時裝飾一個成員的get訪問器和set訪問器。

2.4 屬性裝飾器

屬性裝飾器表達式運行時接收兩個參數:

  • 1.對於靜態成員來說是類的構造函數,對於實例成員來說是類的原型對象。
  • 2.成員名

Typescript官方文檔給出的示例是這樣的:

class Greeter {
    @format("Hello, %s") greeting: string;
    
    constructor(message: string){
        this.greeting = message;
    }
    
    greet(){
        let formatString = getFormat(this, 'greeting');
        return formatString.replace('s%',this.greeting);
    }
    
}

然後定義@format裝飾器和getFormat函數:

.import "reflect-metadata";

const formatMetadataKey = Symbol("format");

function format(formatString: string) {
    return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
    return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

與方法裝飾器相比,屬性裝飾器的形參列表中並沒有屬性描述符,因為目前沒有辦法在定義一個原型對象的成員時描述一個實例屬性,也無法監視屬性的初始化方法。TS中的屬性描述符單獨使用時只能用來監視類中是否聲明瞭某個名字的屬性,示例中通過外部功能擴展了其實用性。Angular中最常見的屬性修飾器就是Input( )output( )

2.5 參數裝飾器

參數裝飾器一般用於裝飾參數,在類構造函數或方法聲明中裝飾形參。

它在運行時被當做函數調用,傳入下列3個參數:

  • 1.靜態成員時接收構造函數,實例成員時接收原型對象。
  • 2.成員名
  • 3.參數在函數參數列表中的索引。

TS中參數裝飾器單獨使用時只能用來監視一個方法的參數是否被傳入,Typescript官方給出的示例如下:

class Greeter {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    @validate
    greet(@required name: string) {//此處使用了參數修飾符
        return "Hello " + name + ", " + this.greeting;
    }
}

兩個裝飾器的定義如下:

import "reflect-metadata";
const requiredMetadataKey = Symbol('required');

/*
*@required參數裝飾器
*實現的功能就是當函數的參數必須填入時,將相關信息存儲到一個外部的數組中,可以看出參數裝飾器並*未對參數本身做出什麼修改。
*/
function required(target: Object, propertyKey:string | symbol, parameterIndex: number){
    let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
    existingRequiredParameters.push(parameterIndex);
    Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}

/*
*@validate裝飾器為方法裝飾器
*展示瞭如何通過操作方法屬性描述符中的value屬性來實現方法的代理訪問。
*/
function validate(target:any, propertyName: string, descriptor:TypedPropertyDescriptor<Function>){
    let method = descriptor.value;//方法的屬性修飾符的value就是方法的函數表達式
    descriptor.value = function(){
        let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);//在外部存儲中查找是否有必填參數
        if (requiredParameters){
            for(let parameterIndex of requiredParameters){
                if(parameterIndex >= arguments.length || arguments[parameterIndex] === undefined){
                    //傳入參數不足或被約束參數為undefined時拋出錯誤。
                    throw new Error('Missing required argument');
                }
            }
        }
        return method.apply(this, arguments);//如果沒有任何錯誤拋出則繼續執行原函數
    }
}

在Typescript中,裝飾器的運行順序基本依照參數裝飾器,方法裝飾器,訪問符裝飾器,屬性裝飾器,類裝飾器這樣的順序來運行,所以參數裝飾器和方法裝飾器可以聯合使用實現一些額外功能。

三. 用ES5代碼模擬裝飾器功能

ES5來模擬一下上述的方法裝飾器和參數裝飾器聯合作用的例子,就很容易看出裝飾器的作用:

//使用ES5語法模擬裝飾器
function Greeter(message){
    this.greeting = message;
}

Greeter.prototype.greet = function(name){
    return "Hello " + name + ", " + this.greeting;
}

//外部存儲的必要性校驗
requiredArray = {};

//參數裝飾器
function requireDecorator(FnKey,paramsIndex){
    requiredArray[FnKey] = paramsIndex;
} 

//裝飾器函數
function validateDecorator(Fn,FnKey){
    let method = Fn;
    return function(){
        let checkParamIndex = requiredArray[FnKey];
        if(checkParamIndex > arguments.length-1 || arguments[checkParamIndex] === undefined){
            throw new Error('params invalid');
        }
        return method.apply(this, arguments);
    }
}

//運行裝飾
requireDecorator('greet',0);
Greeter.prototype.greet = validateDecorator(Greeter.prototype.greet, 'greet');

//測試裝飾
let greeter = new Greeter('welcome to join the conference');
console.log(greeter.greet('Tony'));
console.log(greeter.greet());

在node環境中運行一下就可以看到,greet( )方法在未傳入參數時會報錯提示。

四. 小結

裝飾器實際上就是一種更加簡潔的代碼書寫方式,從代碼表現來理解,就是使用閉包和高階函數擴展或者修改了原來的表現,從功能角度來理解,達到了不修改內部實現的前提下動態擴展和修改類定義的目的。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • UIApplication深入研究 很多時候,我們不需要關心這個類,我們很少繼承這個類,偶爾會調用這個類的api來實現一些功能,但是不可否認,這個類是iOS編程中很重要的一個概念,所以我這裡寫這個文章來總結以下這個類的信息,如果寫的不對的地方,請留言,多謝。 UIApplication的核心作用是提 ...
  • Editor.md是一款優秀的開源Markdown 編輯器,在使用中遇到的一些問題和功能改進分享給需要的伙伴。 ...
  • 一、組件渲染 當組件的props或者state發生改變時,組件會自動調用render方法重新渲染。當父組件被重新渲染時,子組件也會被遞歸渲染。那麼組件是如何渲染的呢? 二、組件的生命周期 組件的聲明周期可分成三個狀態:Mounting,已插入真實 DOM;Updating,正在被重新渲染;Unmou ...
  • <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/> ...
  • redux數據流向 基本使用 " " ...
  • 學習時,為了搜集最全的中文資料,有時候不得不使用Baidu搜索引擎。在你還是個小菜雞的時候你可能會花費大量時間在百度上! 但是,時間久了你會發現,你總會被網路上一些奇奇怪怪或者有趣的事情吸引過去而逐漸忘記自己曾經打開百度是要乾什麼?時間就這樣被一些無關緊要的有趣的事情給浪費了。 對於廣告吧,還能使用 ...
  • 源地址:https://jingyan.baidu.com/article/546ae185fa4f721149f28cbf.htm 文件:index.htm 文件:calendar.js ...
  • 要解決跨域的問題,我們可以使用以下幾種方法: 1、通過jsonp跨域 2、通過修改document.domain來跨子域 3、使用window.name來進行跨域 4、使用HTML5中新引進的window.postMessage方法來跨域傳送數據 使用postMessage來跨域傳送數據還是比較直觀 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...