部落格

  • QQ是怎樣創造出來的?——解密好友系統的設計

    QQ是怎樣創造出來的?——解密好友系統的設計

    本篇介紹筆者接觸的第一個後台系統,從自身見聞出發,因此涉及的內容相對比較基礎,後台大牛請自覺略過。

    什麼是好友系統?

    簡單的說,好友系統是維護用戶好友關係的系統。我們最熟悉的好友系統案例當屬QQ,實際上QQ是一款即時通訊工具,憑着好友系統沉澱了海量的好友關係鏈,從而鑄就了一個堅不可摧的商業帝國。好友系統的重要性可見一斑。

    熟悉互聯網產品的人都知道,當產品有了一定的用戶量,往往會開發一個好友系統。其主要目的是增加用戶粘性(有了好友就會常來)或者增加社區活躍度(有了好友就會多交流)。

    而我的後台開發生涯就是從這樣一個系統開始的。

    那時候,好友系統對於我們團隊大部分人來說,都是一個全新的事物,因為我們大部分人都是應屆生。整個系統的架構自然不是我們一群黃毛小孩所能創造。當年的架構圖已經找不到了,但是憑着一點記憶和多年來的經驗積累,還是可以把當年的架構勾勒出來。

     

    如圖,好友系統的架構是常見的3層結構,包括接入層、邏輯層和數據層。

    我們先從數據層講起。

    因為我們對QQ太熟悉了,我們可以很容易地列出好友系統的數據主要包括用戶資料、好友關係鏈、消息(聊天消息和系統消息)、在線狀態等。

    互聯網產品往往要面對海量的請求併發,傳統的關係型數據庫比較難滿足讀寫需求。在存儲中,一般是讀多寫少的數據才會使用MySQL等關係型數據庫,而且往往還需要增加緩存來保證性能;NoSQL(Not Only SQL)應該是目前的主流。

    對於好友系統,用戶資料和好友關係鏈都使用了kv存儲,而消息使用公司自研的tlist(可以用redis的list替代),在線狀態下面再介紹。

    接着是邏輯層

    在這個系統中複雜度最高的應該是消息服務(而這個服務我並沒有參与開發[捂臉])。

    消息服務中,消息按類型分為聊天消息和系統消息(系統消息包括加好友消息、全局tips推送等),按狀態分為在線消息和離線消息。在實現中,維護3種list:聊天消息、系統消息和離線消息。聊天消息是兩個用戶共享的,系統消息和離線消息每個用戶獨佔。當用戶在線時,聊天消息和系統消息是直接發送的;如果用戶離線,就把消息往離線消息list存入一份,等用戶再次登錄時拉取。

    這樣看來,消息服務並不複雜?其實不然,系統設計中常規的流程設計往往是比較簡單的,但是對於互聯網產品,異常情況才是常態,當把各種異常情況都考慮進來時,系統就會非常複雜。

    這個例子中,消息發送丟包是一種異常情況,怎麼保證在丟包情況下,還能正常運行就是一個不小的問題。

    常見的解決方法是收包方回復確認包,發送方如果沒收到確認包就重發。但是確認包又可能丟包,那又可以給確認包增加一個確認包,這是一個永無止境的確認。

    解決方法可以參考TCP的重傳機制。那問題來了,我們為什麼不用TCP呢?因為TCP還是比較慢的,聊天消息的可靠性沒有交易數據要求那麼高,丟幾條消息並不會造成嚴重後果,但是如果用戶每次發送消息后都要等很久才能被收到,那體驗是很差的。

    一個比較折中的方案是,收包方回復確認包,如果發送方在一定時間內沒有收到確認就重發;如果收包方收到兩個相同的包(自定義seq一樣),去重即可。

    一個面試題引發的討論:

    面試時我常常會問候選人一個問題:在分佈式系統中怎樣實現一個用戶同時只能有一個終端在線(用戶在兩個地方先後登錄賬號,后一次登錄可以把前一次登錄踢下線)?這是互聯網產品中非常基礎的一個功能,考察的是候選人基本的架構設計能力。

    設計要先從接入服務器(下稱接口機)說起。接口機是好友系統對外的窗口,主要功能是維護用戶連接、登錄鑒權、加解密數據和向後端服務透傳數據等。用戶連接好友系統,首先是連接到接口機,鑒權成功后,接口機會在內存中維護用戶session,後續的操作都是基於session進行。

    如圖所示,用戶如果嘗試登錄兩次,接口機通過session就可以將第一次的登錄踢下線,從而保證只有一個終端在線。

    問題解決了嗎?

    沒有。因為實際系統肯定不會只有一台接口機,在多台接口的情況下,上面的方法就不可行了。因為每個接口機只能維護部分用戶的session,所以如果用戶先後連接到不同的接口機,就會造成用戶多處登錄的問題。

     

    自然可以想到,解決的方法就是要維護一個用戶狀態的全局視圖。在我們的好友系統中,稱為在線狀態服務。

    在線狀態服務,顧名思義就是維護用戶的在線狀態(登錄時間、接口機IP等)的服務。用戶登錄和退出會通過接口機觸發這裏的狀態變更。因為登錄包和退出包都可能丟包,所以心跳包也用作在線狀態維護(收到一次心跳標記為在線,收不到n次心跳標記為離線)。

    一種常用的方法是,採用bitmap存儲在線狀態,具體是指在內存中分配一塊空間,32位機器上的自然數一共有4294967296個,如果用一個bit來表示一個用戶ID(例如QQ號),1代表在線,0代表離線,那麼把全部自然數存儲在內存只要4294967296 / (8 * 1024 * 1024) = 512MB(8bit = 1Byte)。當然,實現中也可以根據需要給每個用戶分配更多的bit。

    於是,踢下線功能如圖所示。

     

    用戶登錄的時候,接口機首先查找本機上是否有session,如果有則更新session,接着給在線狀態服務發送登錄包,在線狀態服務檢查用戶是否已經在線,如果在線則更新狀態信息,並向上次登錄的接口機IP發送踢下線包;接口機在收到踢下線包時會檢查包中的用戶ID是否存在session,如果存在則給客戶端發送踢下線包並刪除session。

    在實際中,踢下線功能還有很多細節問題需要注意。

    又回到用戶先後登錄同一台接口機的情況:

     

    圖中踢下線流程是正確的,但是如果步驟10和13調換了順序(在UDP傳輸中是常見的)會發生什麼?大家可以自己推演一下,後到的踢下線包會把第二次登錄的A’踢下線了。這不是我們期望的。怎麼辦呢?

    解決方法分幾個細節,①接口機在收到13號登錄成功包時,先將session A替換成session A’,然後給客戶端A發生踢下線包(避免多處存活導致互相踢下線);②踢下線包中必須包含除用戶ID外的其他標識信息,session的唯一標識應該是ID+XXX的形式(我最開始採用的是ID+LoginTime),XXX是為了區分某次的登錄;③接口機在收到踢下線包的時候只要判斷ID+XXX是否吻合來決定是否給客戶端發踢下線包。

    現實情況,問題總是千奇百怪的,好在辦法總比問題多。

    比如我在項目中遇到過接口機和在線狀態服務時間漂移(差幾秒)的情況。這樣踢下線的唯一標識就不能是用戶ID+LoginTime的形式了。可以為每次的登錄生成一個唯一的UUID解決。類似的問題還有很多,不再贅述。

    總結一下,本篇主要介紹了好友系統的整體架構和部分模塊的實現方式。分佈式系統中各個模塊的實現其實並不難,難點主要在於應對複雜網絡環境帶來的問題(如丟包、時延等)和服務器異常帶來的問題(如為了應對服務器宕機會增加服務器冗餘度,進而又會引發其它問題)。

    好友系統雖然簡單,但麻雀雖小五臟俱全,架構設計的各種技術基本都有涉及。例如分層結構、負載均衡、平行擴展、容災、服務發現、服務器開發框架等方面,後面我會在各個不同的項目中介紹這些技術,敬請期待。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    大陸寄台灣空運注意事項

    大陸海運台灣交貨時間多久?

  • Cesium坐標系及坐標轉換詳解

    Cesium坐標系及坐標轉換詳解

    前言

    Cesium項目中經常涉及到模型加載、瀏覽以及不同數據之間的坐標轉換,弄明白Cesium中採用的坐標系以及各個坐標系之間的轉換,是我們邁向三維GIS大門的前提,本文詳細的介紹了Cesium中採用的兩大坐標系以及之間轉換的各種方法。

    Cesium中的坐標系

    Cesium中常用的坐標有兩種WGS84地理坐標系和笛卡爾空間坐標系,我們平時常用的以經緯度來指明一個地點就是用的WGS84坐標,笛卡爾空間坐標系常用來做一些空間位置變換如平移旋轉縮放等等。二者的聯繫如下圖。

    其中,WGS84地理坐標系包括 WGS84經緯度坐標系(沒有實際的對象)和 WGS84弧度坐標系(Cartographic);

             笛卡爾空間坐標系包括 笛卡爾空間直角坐標系(Cartesian3)、平面坐標系(Cartesian2),4D笛卡爾坐標系(Cartesian4)。

    WGS84坐標系

    World Geodetic System 1984,是為GPS全球定位系統使用而建立的坐標系統,坐標原點為地球質心,其地心空間直角坐標系的Z軸指向BIH (國際時間服務機構)1984.O定義的協議地球極(CTP)方向,X軸指向BIH 1984.0的零子午面和CTP赤道的交點,Y軸與Z軸、X軸垂直構成右手坐標系。我們平常手機上的指南針显示的經緯度就是這個坐標系下當前的坐標,進度範圍[-180,180],緯度範圍[-90,90]。

    WGS84坐標系

    Cesium目前支持兩種坐標系WGS84和WebMercator,但是在Cesium中沒有實際的對象來描述WGS84坐標,都是以弧度的方式來進行運用的也就是Cartographic類:

    new Cesium.Cartographic(longitude, latitude, height),這裏的參數也叫longitude、latitude,就是經度和緯度,計算方法:弧度= π/180×經緯度角度。

     笛卡爾空間直角坐標系(Cartesian3)

    笛卡爾空間坐標的原點就是橢球的中心,我們在計算機上進行繪圖時,不方便使用經緯度直接進行繪圖,一般會將坐標系轉換為笛卡爾坐標系,使用計算機圖形學中的知識進行繪圖。這裏的Cartesian3,有點類似於三維繫統中的Point3D對象,new Cesium.Cartesian3(x, y, z),裏面三個分量x、y、z。

    笛卡爾空間直角坐標系

    平面坐標系(Cartesian2)

    平面坐標系也就是平面直角坐標系,是一個二維笛卡爾坐標系,與Cartesian3相比少了一個z的分量,new Cesium.Cartesian2(x, y)。Cartesian2經常用來描述屏幕坐標系,比如鼠標在電腦屏幕上的點擊位置,返回的就是Cartesian2,返回了鼠標點擊位置的xy像素點分量。

    平面坐標系

    坐標轉換

    經緯度和弧度的轉換

    var radians=Cesium.Math.toRadians(degrees);//經緯度轉弧度
    var degrees=Cesium.Math.toDegrees(radians);//弧度轉經緯度

    WGS84經緯度坐標和WGS84弧度坐標系(Cartographic)的轉換

    //方法一:
    var longitude = Cesium.Math.toRadians(longitude1); //其中 longitude1為角度
    
    var latitude= Cesium.Math.toRadians(latitude1); //其中 latitude1為角度
    
    var cartographic = new Cesium.Cartographic(longitude, latitude, height);
    
    //方法二:
    var cartographic= Cesium.Cartographic.fromDegrees(longitude, latitude, height);//其中,longitude和latitude為角度
    
    //方法三:
    var cartographic= Cesium.Cartographic.fromRadians(longitude, latitude, height);//其中,longitude和latitude為弧度

    WGS84坐標系和笛卡爾空間直角坐標系(Cartesian3)的轉換

    通過經緯度或弧度進行轉換
    var position = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);//其中,高度默認值為0,可以不用填寫;longitude和latitude為角度
    
    var positions = Cesium.Cartesian3.fromDegreesArray(coordinates);//其中,coordinates格式為不帶高度的數組。例如:[-115.0, 37.0, -107.0, 33.0]
    
    var positions = Cesium.Cartesian3.fromDegreesArrayHeights(coordinates);//coordinates格式為帶有高度的數組。例如:[-115.0, 37.0, 100000.0, -107.0, 33.0, 150000.0]
    
    //同理,通過弧度轉換,用法相同,具體有Cesium.Cartesian3.fromRadians,Cesium.Cartesian3.fromRadiansArray,Cesium.Cartesian3.fromRadiansArrayHeights等方法

    注意:上述轉換函數中最後均有一個默認參數ellipsoid(默認值為Ellipsoid.WGS84)。

    通過過度進行轉換

    具體過度原理可以參考上邊的注意事項。

    var position = Cesium.Cartographic.fromDegrees(longitude, latitude, height);
    var positions = Cesium.Ellipsoid.WGS84.cartographicToCartesian(position);
    var positions = Cesium.Ellipsoid.WGS84.cartographicArrayToCartesianArray([position1,position2,position3]);

    笛卡爾空間直角坐標系轉換為WGS84

    直接轉換
    var cartographic= Cesium.Cartographic.fromCartesian(cartesian3);

    轉換得到WGS84弧度坐標系后再使用經緯度和弧度的轉換,進行轉換到目標值

    間接轉換
    var cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(cartesian3);
    var cartographics = Cesium.Ellipsoid.WGS84.cartesianArrayToCartographicArray([cartesian1,cartesian2,cartesian3]);

    平面坐標系(Cartesian2)和笛卡爾空間直角坐標系(Cartesian3)的轉換

    平面坐標系轉笛卡爾空間直角坐標系

    這裏注意的是當前的點(Cartesian2)必須在三維球上,否則返回的是undefined;通過ScreenSpaceEventHandler回調會取到的坐標都是Cartesian2。

    屏幕坐標轉場景坐標-獲取傾斜攝影或模型點擊處的坐標

    這裏的場景坐標是包含了地形、傾斜攝影表面、模型的坐標。

    通過viewer.scene.pickPosition(movement.position)獲取,根據窗口坐標,從場景的深度緩衝區中拾取相應的位置,返回笛卡爾坐標。

    var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    handler.setInputAction(function (movement) {
         var position = viewer.scene.pickPosition(movement.position);
         console.log(position);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    注:若屏幕坐標處沒有傾斜攝影表面、模型時,獲取的笛卡爾坐標不準,此時要開啟地形深度檢測(viewer.scene.globe.depthTestAgainstTerrain = true; //默認為false)。

    屏幕坐標轉地表坐標-獲取加載地形后對應的經緯度和高程

    這裡是地球表面的世界坐標,包含地形,不包括模型、傾斜攝影表面。

    通過viewer.scene.globe.pick(ray, scene)獲取,其中ray=viewer.camera.getPickRay(movement.position)。

    var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    handler.setInputAction(function (movement) {
         var ray = viewer.camera.getPickRay(movement.position);
         var position = viewer.scene.globe.pick(ray, viewer.scene);
         console.log(position);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    注:通過測試,此處得到的坐標通過轉換成wgs84后,height的為該點的地形高程值。

    屏幕坐標轉橢球面坐標-獲取鼠標點的對應橢球面位置

    這裏的橢球面坐標是參考橢球的WGS84坐標(Ellipsoid.WGS84),不包含地形、模型、傾斜攝影表面。

    通過 viewer.scene.camera.pickEllipsoid(movement.position, ellipsoid)獲取,可以獲取當前點擊視線與橢球面相交處的坐標,其中ellipsoid是當前地球使用的橢球對象:viewer.scene.globe.ellipsoid,默認為Ellipsoid.WGS84

    var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    handler.setInputAction(function (movement) {
         var position = viewer.scene.camera.pickEllipsoid(movement.position, viewer.scene.globe.ellipsoid);
         console.log(position);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

    注:通過測試,此處得到的坐標通過轉換成wgs84后,height的為0(此值應該為地表坐標減去地形的高程)。

    笛卡爾空間直角坐標系轉平面坐標系

    var cartesian2= Cesium.SceneTransforms.wgs84ToWindowCoordinates(viewer.scene,cartesian3)

    空間位置變換

    經緯度轉換到笛卡爾坐標系后就能運用計算機圖形學中的仿射變換知識進行空間位置變換如平移旋轉縮放。

    Cesium為我們提供了很有用的變換工具類:Cesium.Cartesian3(相當於Point3D)Cesium.Matrix3(3×3矩陣,用於描述旋轉變換)Cesium.Matrix4(4×4矩陣,用於描述旋轉加平移變換),Cesium.Quaternion(四元數,用於描述圍繞某個向量旋轉一定角度的變換)。

    下面舉個例子:

          一個局部坐標為p1(x,y,z)的點,將它的局部坐標原點放置到loc(lng,lat,alt)上,局部坐標的z軸垂直於地表,局部坐標的y軸指向正北,並圍繞這個z軸旋轉d度,求此時p1(x,y,z)變換成全局坐標笛卡爾坐p2(x1,y1,z1)是多少?

    var rotate = Cesium.Math.toRadians(d);//轉成弧度
    var quat = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_Z, rotate); //quat為圍繞這個z軸旋轉d度的四元數
    var rot_mat3 = Cesium.Matrix3.fromQuaternion(quat);//rot_mat3為根據四元數求得的旋轉矩陣
    var v = new Cesium.Cartesian3(x, y, z);//p1的局部坐標
    var m = Cesium.Matrix4.fromRotationTranslation(rot_mat3, Cesium.Cartesian3.ZERO);//m為旋轉加平移的4x4變換矩陣,這裏平移為(0,0,0),故填個Cesium.Cartesian3.ZERO
    m = Cesium.Matrix4.multiplyByTranslation(m, v);//m = m X v
    var cart3 = ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(lng, lat, alt)); //得到局部坐標原點的全局坐標
    var m1 = Cesium.Transforms.eastNorthUpToFixedFrame(cart3);//m1為局部坐標的z軸垂直於地表,局部坐標的y軸指向正北的4x4變換矩陣
    m = Cesium.Matrix4.multiplyTransformation(m, m1);//m = m X m1
    var p2 = Cesium.Matrix4.getTranslation(m);//根據最終變換矩陣m得到p2
    console.log('x=' + p2.x + ',y=' + p2.y + ',z=' + p2.z );

    總結

    通過本文,介紹了各個坐標系間的轉換問題,在具體項目中,可結合實際需求,靈活組合解決具體的實際問題。注意,博文是參照網上相關博客及結合自己的實踐總結得來,希望本文對你有所幫助,後續會更新更多內容,感興趣的朋友可以加關注,歡迎留言交流!

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

    ※試算大陸海運運費!

  • 詳解JavaScript錯誤捕獲和上報流程

    詳解JavaScript錯誤捕獲和上報流程

     

     

     

     

    怎麼捕獲錯誤並且處理,是一門語言必備的知識。在JavaScript中也是如此。

    那怎麼捕獲錯誤呢?初看好像很簡單,try-catch就可以了嘛!但是有的時候我們發現情況卻繁多複雜。

    • Q1: 同步可以try-catch,但一個異步回調,比如setTimeOut里的函數還可以try-catch嗎?

    • Q2: Promise的錯誤捕獲怎麼做?

    • Q3: async/await怎麼捕獲錯誤?

    • Q4: 我能夠在全局環境下捕獲錯誤並且處理嗎?

    • Q5: React16有什麼新的錯誤捕獲方式嗎?

    • Q6: 捕獲之後怎麼上報和處理?

     

    問題有點多,我們一個一個來。

     

    Q1. 同步代碼里的錯誤捕獲方式

    在同步代碼里,我們是最簡單的,只要try-catch就完了 

    function test1 () {
      try {
        throw Error ('callback err');
      } catch (error) {
        console.log ('test1:catch err successfully');
      }
    }
    test1();

    輸出結果如下,顯然是正常的

    Q2. 普通的異步回調里的錯誤捕獲方式(Promise時代以前)

    上面的問題來了,我們還能通過直接的try-catch在異步回調外部捕獲錯誤嗎?我們試一試 

    // 嘗試在異步回調外部捕獲錯誤的結果
    function test2 () {
      try {
        setTimeout (function () {
          throw Error ('callback err');
        });
      } catch (error) {
        console.log ('test2:catch err successfully');
      }
    }
    test2(); 

    輸出

    注意這裏的Uncaught Error的文本,它告訴我們錯誤沒有被成功捕捉。

    為什麼呢? 因為try-catch的是屬於同步代碼,它執行的時候,setTimeOut內部的的匿名函數還沒有執行呢。而內部的那個匿名函數執行的時候,try-catch早就執行完了。( error的內心想法:哈哈,只要我跑的夠慢,try-catch還是追不上我!)

    但是我們簡單想一想,誒我們把try-catch寫到函數裏面不就完事了嘛!

     

     

    function test2_1 () {
      setTimeout (function () {
        try {
          throw Error ('callback err');
        } catch (error) {
          console.log ('test2_1:catch err successfully');
        }
      });
    }
    test2_1();

    輸出結果如下,告訴我們這方法可行

     

    總結下Promise時代以前,異步回調中捕獲和處理錯誤的方法

    • 在異步回調內部編寫try-catch去捕獲和處理,不要在外部哦

    • 很多異步操作會開放error事件,我們根據事件去操作就可以了

    Q3. Promise里的錯誤捕獲方式

    可通過Promise.catch方法捕獲

    function test3 () {
      new Promise ((resolve, reject) => {
        throw Error ('promise error');
      }).catch (err => {
        console.log ('promise error');
      });
    }

    輸出結果

    >> reject方法調用和throw Error都可以通過Promise.catch方法捕獲

    function test4 () {
      new Promise ((resolve, reject) => {
        reject ('promise reject error');
      }).catch (err => {
        console.log (err);
      });
    } 

    輸出結果

     

    >> then方法中的失敗回調和Promise.catch的關係

    • 如果前面的then方法沒寫失敗回調,失敗時後面的catch是會被調用的

    • 如果前面的then方法寫了失敗回調,又沒拋出,那麼後面的catch就不會被調用了

    // then方法沒寫失敗回調
    function test5 () {
      new Promise ((resolve, reject) => {
        throw Error ('promise error');
      })
        .then (success => {})
        .catch (err => {
          console.log ('the error has not been swallowed up');
        });
    }
    // then方法寫了失敗回調
    function test5 () {
      new Promise ((resolve, reject) => {
        throw Error ('promise error');
      })
        .then (success => {},err => {})
        .catch (err => {
          console.log ('the error has not been swallowed up');
        });
    }

    輸出分別為

    1.the error has not been swallowed up
    2.無輸出

    Q4.async/await里的錯誤捕獲方式

    對於async/await這種類型的異步,我們可以通過try-catch去解決

    async function test6 () {
      try {
        await getErrorP ();
      } catch (error) {
        console.log ('async/await error with throw error');
      }
    }
     
    function getErrorP () {
      return new Promise ((resolve, reject) => {
        throw Error ('promise error');
      });
    }
    test6();
    
    

    輸出結果如下

     

    >> 如果被await修飾的Promise因為reject調用而變化,它也是能被try-catch的

    (我已經證明了這一點,但是這裏位置不夠,我寫不下了)

    Q5.在全局環境下如何監聽錯誤

    window.onerror可以監聽全局錯誤,但是很顯然錯誤還是會拋出

    window.onerror = function (err) {
      console.log ('global error');
    };
    throw Error ('global error');

     

    輸出如下

     

    Q6.在React16以上如何監聽錯誤

    >> componentDidCatch和getDerivedStateFromError鈎子函數

    class Bar extends React.Component {
      // 監聽組件錯誤
      componentDidCatch(error, info) {
        this.setState({ error, info });
      }
      // 更新 state 使下一次渲染能夠显示降級后的 UI
      static getDerivedStateFromError(error) {
        return { hasError: true };
      }
      render() {
      }
    }

     

     

    有錯誤,那肯定要上報啊!不上報就發現不了Bug這個樣子。Sentry這位老哥就是個人才,日誌記錄又好看,每次見面就像回家一樣

     

     

    Sentry簡單介紹

    Sentry provides open-source and hosted error monitoring that helps all software
    teams discover, triage, and prioritize errors in real-time.
    One million developers at over fifty thousand companies already ship
    better software faster with Sentry. Won’t you join them?
    —— Sentry官網

     

    Sentry是一個日誌上報系統,Sentry 是一個實時的日誌記錄和匯總處理的平台。專註於錯誤監控,發現和數據處理,可以讓我們不再依賴於用戶反饋才能發現和解決線上bug。讓我們簡單看一下Sentry支持哪些語言和平台吧

     

    在JavaScript領域,Sentry的支持也可以說是面面俱到

     

    參考鏈接
    https://docs.sentry.io/platforms/ 

    Sentry的功能簡單說就是,你在代碼中catch錯誤,然後調用Sentry的方法,然後Sentry就會自動幫你分析和整理錯誤日誌,例如下面這張圖截取自Sentry的網站中

     

    在JavaScript中使用Sentry 

    1.首先呢,你當然要註冊Sentry的賬號

    這個時候Sentry會自動給你分配一個唯一標示,這個標示在Sentry里叫做 dsn

    2. 安卓模塊並使用基礎功能

    安裝@sentry/browser 

    npm install @sentry/browser

     

    在項目中初始化並使用

    import * as Sentry from '@sentry/browser';
     
    Sentry.init ({
      dsn: 'xxxx',
    });
     
    try {
      throw Error ('我是一個error');
    } catch (err) {
        // 捕捉錯誤
      Sentry.captureException (err);
    }

    3.上傳sourceMap以方便在線上平台閱讀出錯的源碼

     

    // 安裝
    $ npm install --save-dev @sentry/webpack-plugin
    $ yarn add --dev @sentry/webpack-plugin
     
    // 配置webpack
    const SentryWebpackPlugin = require('@sentry/webpack-plugin');
    module.exports = {
      // other configuration
      plugins: [
        new SentryWebpackPlugin({
          include: '.',
          ignoreFile: '.sentrycliignore',
          ignore: ['node_modules', 'webpack.config.js'],
          configFile: 'sentry.properties'
        })
      ]
    }; 

    4. 為什麼不是raven.js?

     

    // 已經廢棄,雖然你還是可以用
    var Raven = require('raven-js');
    Raven
      .config('xxxxxxxxxxx_dsn')
      .install();

     

    Sentry的核心功能總結

    捕獲錯誤

    try { aFunctionThatMightFail(); } catch (err) { Sentry.captureException(err); }

     

    設置該錯誤發生的用戶信息

    下面每個選項都是可選的,但必須 存在一個選項 才能使Sentry SDK捕獲用戶: id 

    Sentry.setUser({
        id:"penghuwan12314"
      email: "penghuwan@example.com",
      username:"penghuwan",
      ip_addressZ:'xxx.xxx.xxx.xxx'
      });

     

    設置額外數據

    Sentry.setExtra("character_name", "Mighty Fighter");
    設置作用域 
    Sentry.withScope(function(scope) {
        // 下面的set的效果只存在於函數的作用域內
      scope.setFingerprint(['Database Connection Error']);
      scope.setUser(someUser);
      Sentry.captureException(err);
    });
    // 在這裏,上面的setUser的設置效果會消失

     

    設置錯誤的分組

    整理日誌信息,避免過度冗餘 

    Sentry.configureScope(function(scope) {
      scope.setFingerprint(['my-view-function']);
    });

     

    設置錯誤的級別

    在閱讀日誌時可以確定各個bug的緊急度,確定排查的優先書序

    Sentry.captureMessage('this is a debug message', 'debug');
    //fatal,error,warning,info,debug五個值
    // fatal最嚴重,debug最輕

     

    自動記錄某些事件

    例如下面的方法,會在每次屏幕調整時完成上報 

    window.addEventListener('resize', function(event){
      Sentry.addBreadcrumb({
        category: 'ui',
        message: 'New window size:' + window.innerWidth + 'x' + window.innerHeight,
        level: 'info'
      });
    })

    Sentry實踐的運用

    根據環境設置不同的dsn

    let dsn;
      if (env === 'test') {
        dsn = '測試環境的dsn';
      } else {
        dsn =
          '正式環境的dsn';
      }
     
    Sentry.init ({
      dsn
    });

     

     

     

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    ※專營大陸快遞台灣服務

    台灣快遞大陸的貨運公司有哪些呢?

  • Mirantis 收購 Docker EE | 雲原生生態周報 Vol. 28

    Mirantis 收購 Docker EE | 雲原生生態周報 Vol. 28

    作者 | 禪鳴、進超、心水、心貴

    業界要聞

    Mirantis 是一家紮根於 OpenStack 的雲公司,最近專註於 Kubernetes。該公司剛剛收購了 Docker 的企業部門,該業務部門包括 Docker Enterprise 技術平台及所有相關的知識產權、約 400 名員工中的 300 人、750 家企業客戶以及所有企業夥伴關係。

    Project Quay 包含一系列在 Apache 2.0 和其他開源許可證下許可的開源軟件。它遵循一個帶有維護者委員會的開源治理模型。

    KubeCon + CloudNativeCon North America 2019 於 11 月 18 日 在 San Diego 正式召開。

    上游重要進展

    KEP

    主要為了解決以下問題:

    • PreSidecars 將在普通容器之前啟動,但在 init 容器之後啟動,這樣它們就可以在您的主進程開始之前準備好;
    • PostSidecars 將在普通容器之後啟動,以便它們在您的主進程啟動后可以執行某些操作,例如更新 css 文件,轉發日誌等。

    主要為了解決以下問題:

    • Client/Server 版本偏移;
    • API 擴展支持;
    • 提供更簡單的選項來與 cli 工具進行集成(例如 jq);
    • 提供與 unix cli 標準集成的接口(xargs/find -exec/globbing);
    • 保留配置註釋,結構等。

    IP地址類型分解為IPv4IPv6。並逐步棄用原有地址類型,其在 1.17 中對新的 EndpointSlices 無效,然後在 1.18 中變得完全無效。

    K8S PR

    提供一種在 --show-enable-metrcis-for-version 設置時重新註冊隱藏指標的機制。

    有兩個原因:

    • 新版本中 http.CloseNotifier 已經被廢棄;
    • 如果請求協議為 HTTP/2.x,原始代碼使用 http.CloseNotifier 的情況下,每一個 Watch 將多花費 1 個 goruntine。在大規模場景下,過多的 goruntine 對 API Server 是一個非常大的負擔和性能瓶頸。

    在 Windows 上使用 Containerd 時,將由 kubelet 管理“ C: Windows  System32  drivers  etc  hosts”文件。

    為了減少 service controller 在節點有更新時,更新 backend 的延遲。

    當提供客戶端證書證書文件后,始終保持從磁盤重新啟動證書文件以進行新連接,並在證書更改時關閉連接。

    Knative

    當前 Kubernetes 社區(Kubebuilder 和 Metacontroller)正在研究控制平面可伸縮性,認為雖然用於 Kubernetes 工作的”無服務器控制器”是一個思想實驗,但距離我們並不遠,並且在技術上也是可行的。

    開源項目推薦

    阿里雲容器服務團隊自研 CNI 網絡插件,支持 VPC 和 ENI 等。

    Vmware 開源基於 OVS 的 Kubernetes 網絡方案。

    KubeSphere 是在 Kubernetes 之上構建的以應用為中心的多租戶容器管理平台,目前已經達到 GA 狀態。

    具有硬件資源感知工作負載放置策略的 Kubernetes Container Runtime Interface 代理服務。

    本周閱讀推薦

    CRDs/controllers 是 Kubernetes 中重要的組件,它們會將集群內的各種資源調整到期望狀態。學習 Reconciling 可以幫助我們更好的理解 CRDs/controllers 是如何工作的。

    通過漫畫的形式對 Openshift 及相關產品加以介紹,比較有趣。

    隨着時間的推移,Docker 開始根植於我們的日常生活當中。然而,Docker 一切輝煌的背後,技術社區中開始有不少人認為 Docker 正一路朝着沉沒的方向前進。那麼,這樣的判斷有沒有依據?Docker 真的快要不行了嗎?或者說,這隻是技術領域當中部分小年輕們一廂情願的偏執?

    由於目前國內並沒有比較好的 Go 語言書籍,而國外的優秀書籍因為英文的緣故在一定程度上也為不少 Go 語言愛好者帶來了一些學習上的困擾。

    為了加快擴散 Go 愛好者的國內群體,譯者在完成 《The Way to Go》 一書的閱讀後,決定每天抽出一點時間來進行翻譯工作,並以開源的形式免費分享給有需要的 Go 語言愛好者。

    在 Istio 服務網格中,每個 Envoy 佔用的內存也許並不算多,但所有 sidecar 增加的內存累積起來則是一個不小的数字。在進行商用部署時,我們需要考慮如何優化並減少服務網格帶來的額外內存消耗。

    Buoyant 創始人、Service Mesh 技術的提出者、第一個 Service Mesh Linkerd 的作者 Willian Morgan 為您解析 Service Mesh 現狀。

    “ 阿里巴巴雲原生微信公眾號(ID:Alicloudnative)關注微服務、Serverless、容器、Service Mesh等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,做最懂雲原生開發者的技術公眾號。”

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

    台灣海運大陸貨務運送流程

    兩岸物流進出口一站式服務

  • 從最近面試聊聊我所感受的職業天花板

    ## 特別特別嚴肅的申明 (正經的)

    未免引起誤解,標題已修改。

    我一開始寫這篇文章,也純粹是有感而發。實在沒想到會引起如此多的關注。甚至還被社區大佬翻牌。說實話,誠惶誠恐。

    再次申明一遍,我寫的也僅僅只是我個人的感受,我就是萬萬千千的普通碼農中的一個,所寫文章也僅僅是從自我角度出發。

    不具備任何普適性參考性。請大家看清標題,只是個人感受,不適用.net整個行業。尤其請大家不要拿我的經歷作為語言選擇的參考。

    。net也好,java也好,每個語言都有自己的生態,選擇語言之前請先確認自己的能力,請不要抱怨語言害了你,請先審視自己是否一直在努力進步,還是只是在隨大流。

    博主就是一個普通碼農,太高端的層面很難觸及,找工作的途徑也只有朋友內推和招聘網站。

    那麼我在這兩種途徑下所接觸到的信息,就是我目前的薪資已經到了這兩種渠道下的頂薪。所以,我自我感覺是我現在觸到我的天花板。

    那麼我向上突破的方法,要麼向社區大佬學習,努力突破到那個圈子,但是對於這個自己實在沒底,大佬的圈子太高,感覺自己能力不夠,對自己實在沒信心。

    第二種就是多語言發展,修鍊技術內功,管理內功,我覺着在努努力踮踮腳還是能夠到30K、40K月薪的小尾巴。

    最後奉勸各位園友,語言從來都不是決定自己前途的關鍵因素,更多的還是要修鍊自己的內功不斷向上突破。

    博主寫這篇文章的初衷只是想表述自己目前所面臨的職業困境,實在沒想到會有如此多的關注,用詞上的不當,給大家造成困擾,萬分抱歉。

     

    #0 前言

    入職新公司沒多久,閑來無事在博客園閑逛,看到園友分享的面試經歷,正好自己這段時間面試找工作,也挺多感想的,乾脆趁這個機會總結整理一下。

    博主13年開始實習,14年畢業。到現在也工作五六年了。今年面試最大的感受就是觸及了.net的天花板。坐標,杭州。

     #1 背景

    今年九月份從一家創業公司離職,原因么自然是公司創業失敗倒閉。

    當初以技術合伙人的身份進入,雄心勃勃,然後挨了一頓社會毒打,從此老實做人,面朝黃土背朝天,老老實實去搬磚。

    九月份出來,已經是中旬,開始刷新簡歷,準備穩坐釣魚台,等着電話信息轟炸。然後,等了两天,等了一首涼涼。直到這個時候博主才意識到,形式不對。

    我的思維還停留在兩三年前,工 作遍地,只要更新下簡歷就會有無數的面試邀請。同志門,情況變了呀,行業寒冬真不只是說說而已。

    沒辦法,只好花錢,刷新下簡歷,瀏覽崗位,主動出擊。中間接到了好幾個獵頭電話, 但特么都是java。好想吐槽一下,簡歷上.net辣么大的字,你們真的不識字么,21世紀了啊喂。

    #2 某建築類軟件公司

    主營業務:建築軟件,公司已上市。

    技術框架:.net平台,具體的不是特別了解

    招聘崗位:.net高級開發工程師

    面試:一共四輪面試。

    第一輪:就是HR了,簡單聊了下情況,為什麼離職,之前薪資多少,期望薪資多少。

    第二輪:他們某業務線的部門經理和技術主管共同面試。

    基本面試情況就是我在說他們在聽,我主要講解了項目的設計方案,使用的技術,遇到的困難,最終的解決方案。

    技術面試官就問了兩個問題,一是從.net升級到netcore中間碰到過哪些問題。

    第二個基於rabbitmq的分佈式事務是怎麼做的。

    然後他們部門經理問了些團隊管理的問題。如何做團隊成員的任務分配,有團隊成員向你提出離職或者漲薪你怎麼處理,團隊的代碼質量如果管控

    第三輪:他們的CTO,然後開始又是自我介紹。

    只好把之前的又重複一遍,巴拉巴拉。最後就問了一個分佈式事務的解決方案有那些,平時是怎麼使用的。

    最後聊了一下我的定位,就是進去是負責他們的平台架構,包括一些公用業務的架構封裝,老架構的netcore升級

    第四輪:最後是他們的公司董事長,上來又是先自我介紹。然後問了下職業規劃。

    接着就是拿着我的簡歷說這個工作跳動比較頻繁,尤其是從上一家比較大的公司跳槽到一個創業公司是基於一個什麼樣的考慮呢,感覺個人穩定性和職業性規劃都不夠。

    博主當時內心的os是黑人問號臉??????我能是基於什麼樣的考慮,我為了世界和平好不好。

    然後被大佬教育了一頓,灌輸了一些個人和公司共同體,什麼共贏發展什麼共同成長的理念。

    結果:通過,HR小姐姐來談薪資。

    只能給到20K,然後還是18k基本工資+2K的級別補貼,說是我進去之後定的級別是T3,

    然後每年三四月份和九十月份可以申請調薪調級,強制要求995?????? 我特么跳槽不漲薪就算了你還給我降薪,還995,PASS。

    #3 某醫美集團下轄子公司

    主營業務:醫美行業的sass軟件

    技術框架:GRPC

    面試:一輪,技術主管。

    招聘崗位:.net架構師

    主要問題:依賴注入的生命周期,在框架設計中的應用場景有那些。

    在技術選型時主要考慮的因素。

    在框架設計時會應用到那些設計模式,主要應用場景是什麼。

    對於netcore中間件的理解。

    應對系統高併發的解決方案。

    聊一聊對微服務的理解,基於netcore的微服務架構是怎麼設計的。

    面試結果:通過。但薪資只有20K,哎呦喂,你都對不起你招聘崗位的名字呀。

    #4 某物業管理軟件公司

    主營業務:做小區物業管理軟件,公司兩百多人。

    技術框架:.net mvc 三層

    招聘崗位:.net副總監

    面試:一輪。總監面試,但是木有問任何技術問題,也木有問任何團隊管理問題。逮者我之前的離職原因各種問。

    面試結果:未通過。一臉懵逼的出來,都不知道為啥沒通過。老子也是信了你的邪。

    #5 某電商初創企業。

    主營業務:拍賣類的電商平台。公司是初創,技術團隊都沒組建完整。

    面試:兩輪。

    第一輪是他們的一個技術負責人,只是看看了簡歷,然後問了一個讓我哭笑不得問題,就是如果你進入公司,發現周圍人技術都比較菜的時候,你是不是會看不起別人。 笑哭!!!

    第二輪是老闆,老闆就是主要負責畫大餅,聊前景,聊機遇。

    結果:通過。工資待遇給到稅前24K。

    但是我了解到老闆之前做互金,然後平台清盤。具體情況不清楚,大佬,惹不起,躲了躲了。

    在這裏一定奉勸各位園友,互金平台或者老闆有互金背景的千萬小心。

    我身邊已經不少朋友,被坑到,即使現在沒事,也說不定什麼時候就會被警察找上門。

    就有朋友,剛入職公司沒多久,而且公司業務也不是做互金的,結果沒幾天,警察上門,老闆帶走就因為老闆之前做互金,還是出事兒了。

    #6 某社交類公司

    主營業務:付費社交app,主打東南亞市場

    技術框架:.net 三層

    招聘崗位:.net高級開發工程師

    面試:三輪。

    第一輪:部門的CTO面試,互相聊得挺愉快。

    主要問了之前的項目微服務怎麼做的,服務拆分的粒度怎麼規劃,整個服務的架構怎麼規劃用到哪些技術。

    然後問了數據庫方面的分庫分表怎麼做的,用的什麼中間件,分庫分表後主鍵id如何生成。

    應對高併發架構上是怎麼處理的。如何保證redis的高併發高可用。面對緩存穿透、雪崩、擊穿怎麼解決的。

    消息隊列的高可用、消息的冪等性,面對消息積壓如何處理。

    接着就是聊團隊管理,還是人員管理,任務分配,質量保證這些問題。

    接手一個新團隊后如何摸清各成員能力,不同能力的人工作上應該怎麼安排。

    還有一個,就是你作為團隊主管你的工作時間是碎片化的,但同時你作為技術leader又要把控技術方案,而做技術是需要時間的連續性,你如何協調這兩者之間的衝突。

    挺有意思,只有技術管理一肩挑的團隊才會遇到這種問題了。

    最後介紹了一下團隊目前的組織架構,技術方向。嗯,要做.net升級,要做微服務。嗯,最後要轉java。誒,是不是有什麼奇怪的東西,.netcore它不香么。

    第二輪:人事面試。嗯,就是問問離職原因,然後介紹了下公司業務發展,前景規劃,入職后的主要工作職能,然後談了下期望薪資。

    第三輪:boos面。老闆,沒問什麼問題,就是聊了聊職業規劃,然後么他介紹公司發展方向,前景規劃,我作為一個負責任的捧哏, 當然舔着嘍。

    面試結果:通過。薪資談到稅前24K。但五險一金都是最低標準繳納。年終獎說是0到12個月,看績效。

    #7 某汽車製造公司的外包崗

    面試:外包公司有個技術經理做了一個簡單電話面試。然後就約着到甲方的公司進行面試。面試兩輪,是甲方的兩個平台架構師。問題都大同小異,不贅述了。

    面試結果:通過。但博主內心相當糾結,因為對於外包,網上實在是沒有好的評價,但是和兩個面試官聊得蠻愉快。

    當初去面試了,也純粹是因為好奇,反正當時面試邀請也少,閑着也是閑着么。

    薪資談到23k,對方說還是走了一個特別申請,甲方那邊兒再高給不了。五險一金都是最低標準。

    但是HR說這個崗位是甲方為了儲備人才招聘的,我當天面試過後,甲方就把這個崗位招聘關了,只招我一個,等到明年三四月份內部編製出來,我是妥妥轉到甲方。

    而且進去之後的工作也是和面試我的那個架構師一起工作,負責他們平台架構規劃。

    一開始去面試之前我都說了工資要求和最低標準,滿口說沒問題,結果面試完了就又不行了。你個糟老頭子,壞的很,我信你個鬼。

    #8 寫在最後

      中間也還有面試有其他幾家公司,套路問題都差不多,就不在寫出來了。找工作一共花費兩周時間,面試了也有八九家,但真正能給到期望工資的就那麼兩三家。這之間自己在網上主動投遞過,但基本都沒有回信。兩周過去,在回過頭來看,卻發現網上再找不到其他合適的崗位了,不是已經面試過,就是投遞了沒反應。到最後發現,我能選擇的就只有那麼幾家公司。而且,最嚴重的一個感受就是,我翻遍了所有的招聘網站,我目前所要的工資,已經是.net行業的天花板,往上沒有空間了。.net高級開發也好、.net架構師也好、技術經理也罷,能給到工資25K就已經是到頂了,而且崗位特別少。然後做cs方向的,價格開的比bs方向的還能高一些,頂薪能到三萬。做服務的.net被java搶佔了太多市場,即便有很多公司,初期是用.net做的,即便現在netcore已經跨平台,但公司做微服務還是要轉java,我真的好想問一句netcore它不香么,vs它不香么,都咋想的。

    #9 尾篇

      最後的最後。整理一下博主在做netcore微服務所用到的相關技術,做個整體的總結。後續會一點一點具體介紹,希望能形成一個系列,希望最後能堅持寫完。

      服務註冊/發現:consul或zookeeper,各有優劣,個人傾向consul

      分佈式通訊:restful api形式或rpc。

      分佈式事件總線:推薦使用cap。cap同時支持 RabbitMQ,Kafka,Azure Service Bus 等進行底層之間的消息發送,同時內置了TCC實現。

           網關、熔斷、降級、限流:ocelot網關,應該是當下netcore平台下最火熱的網關開源項目了。同時集成了polly來滿足熔斷、降級、限流的功能要求。

      配置中心:攜程的開源項目Apollo。博主之前是為了業務需求自己寫的,不具通用性。

      微服務監控:分佈式調用鏈跟蹤zipkin和skywalking,同時還可監控服務性能。推薦使用skywalking,對代碼無侵入。

            日誌監控ELK,這個不需要多介紹了,文章太多了。

      持續集成自動部署:GitLab+Jenkins+k8s

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    小三通海運與一般國際貿易有何不同?

    小三通快遞通關作業有哪些?

  • .NET Core 3 WPF MVVM框架 Prism系列之數據綁定

    .NET Core 3 WPF MVVM框架 Prism系列之數據綁定

     一.安裝Prism

     

    1.使用程序包管理控制台

    Install-Package Prism.Unity -Version 7.2.0.1367

    也可以去掉‘-Version 7.2.0.1367’獲取最新的版本

     2.使用管理解決方案的Nuget包

     

    在上面或許我們有個疑問?為啥安裝prism會跟Prism.Unity有關係,我們知道Unity是個IOC容器,而Prism本身就支持IOC,且目前官方支持幾種IOC容器:

    1.且unity由於是微軟官方的,且支持prism的組件化,由此我推薦使用prism.unity,在官方文檔中prism7不支持prism.Mef,Prism 7.1將不支持prism.Autofac 2.安裝完prism.unity就已經包含着所有prism的核心庫了,架構如下:

    二.實現數據綁定

    我們先創建Views文件夾和ViewModels文件夾,將MainWindow放在Views文件夾下,再在ViewModels文件夾下面創建MainWindowViewModel類,如下:

     

    xmal代碼如下:

    <Window x:Class="PrismSample.Views.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:prism="http://prismlibrary.com/"
            xmlns:local="clr-namespace:PrismSample"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800" prism:ViewModelLocator.AutoWireViewModel="True">
        <StackPanel>
            <TextBox Text="{Binding Text}" Margin="10" Height="100" FontSize="50" Foreground="Black" BorderBrush="Black"/>
            <Button  Height="100" Width="300" Content="Click Me" FontSize="50" Command="{Binding ClickCommnd}"/>
        </StackPanel>
    </Window>

     

    ViewModel代碼如下:

    using Prism.Commands;
    using Prism.Mvvm;
    
    namespace PrismSample.ViewModels
    {
       public class MainWindowViewModel:BindableBase
        {
            private string _text;
            public string Text
            {
                get { return _text; }
                set { SetProperty(ref _text, value); }
            }
    
            private DelegateCommand _clickCommnd;
            public DelegateCommand ClickCommnd =>
                _clickCommnd ?? (_clickCommnd = new DelegateCommand(ExecuteClickCommnd));
    
            void ExecuteClickCommnd()
            {
                this.Text = "Click Me!";
            }
    
            public MainWindowViewModel()
            {
                this.Text = "Hello Prism!";
            }
        }
    }

     

    啟動程序:

      點擊 click Me 按鈕:

    可以看到,我們已經成功的用prism實現數據綁定了,且View和ViewModel完美的前後端分離

    但是現在我們又引出了另外一個問題,當我們不想按照prism的規定硬要將View和ViewModel放在Views和ViewModels裏面,又或許自己的項目取名規則各不相同怎麼辦,這時候就要用到另外幾種方法:

    1.更改命名規則

    如果,公司命名規則很變態,導致項目結構變成這樣(這種公司辭職了算了):

    首先我們在App需要引入prism,修改‘Application’為‘prism:PrismApplication’且刪除StartupUri xmal代碼如下:  

    <prism:PrismApplication x:Class="PrismSample.App"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:prism="http://prismlibrary.com/"
                 xmlns:local="clr-namespace:PrismSample">
        <Application.Resources>
             
        </Application.Resources>
    </prism:PrismApplication>

      cs後台代碼如下:

    using Prism.Unity;
    using Prism.Ioc;
    using Prism.Mvvm;
    using System.Windows;
    using PrismSample.Viewsb;
    using System;
    using System.Reflection;
    
    namespace PrismSample
    {
        /// <summary>
        /// Interaction logic for App.xaml
        /// </summary>
        public partial class App : PrismApplication
        {
            //設置啟動起始頁
            protected override Window CreateShell()
            {
                return Container.Resolve<MainWindow>();
            }
    
            protected override void RegisterTypes(IContainerRegistry containerRegistry)
            {
    
            }
    
            //配置規則
            protected override void ConfigureViewModelLocator()
            {
                base.ConfigureViewModelLocator();
                ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
                {
                    var viewName = viewType.FullName.Replace(".Viewsb.", ".ViewModelsa.OhMyGod.");
                    var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
                    var viewModelName = $"{viewName}Test, {viewAssemblyName}";
                    return Type.GetType(viewModelName);
                });
            }
        }
    }

     

    上面這兩句是關鍵:

    “.Viewsb.” 表示View所在文件夾namespace,”.ViewModelsa.OhMyGod.” 表示ViewModel所在namespace

    var viewName = viewType.FullName.Replace(".Viewsb.", ".ViewModelsa.OhMyGod.");
    

      

    Test表示ViewModel後綴

    var viewModelName = $"{viewName}Test, {viewAssemblyName}";

     

    2.自定義ViewModel註冊

    我們新建一個Foo類作為自定義類,代碼如下:

    using Prism.Commands;
    using Prism.Mvvm;
    
    namespace PrismSample
    {
       public class Foo:BindableBase
        {
    
            private string _text;
            public string Text
            {
                get { return _text; }
                set { SetProperty(ref _text, value); }
            }
    
            public Foo()
            {
                this.Text = "Foo";
            }
    
            private DelegateCommand _clickCommnd;
            public DelegateCommand ClickCommnd =>
                _clickCommnd ?? (_clickCommnd = new DelegateCommand(ExecuteClickCommnd));
    
            void ExecuteClickCommnd()
            {
                this.Text = "Oh My God!";
            }
        }
    }

     

    修改App.cs代碼:

    protected override void ConfigureViewModelLocator()
            {
                base.ConfigureViewModelLocator();
                ViewModelLocationProvider.Register<MainWindow, Foo>();
                //ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
                //{
                //    var viewName = viewType.FullName.Replace(".Viewsb.", ".ViewModelsa.OhMyGod.");
                //    var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
                //    var viewModelName = $"{viewName}Test, {viewAssemblyName}";
                //    return Type.GetType(viewModelName);
                //});
            }

     

      運行:   點擊按鈕:  

    就算是不註釋修改命名規則的代碼,我們發現運行結果還是一樣,因此我們可以得出結論,

    這種直接的,不通過反射註冊的自定義註冊方式優先級會高點,在官方文檔也說明這種方式效率會高點

    且官方提供4種方式,其餘三種的註冊方式如下:

    ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), typeof(MainWindowTest)); 
    ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), () => Container.Resolve<Foo>());
    ViewModelLocationProvider.Register<MainWindow>(() => Container.Resolve<Foo>());

     

     本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

    小三通物流營運型態?

    ※快速運回,大陸空運推薦?

  • 【併發編程】synchronized的使用場景和原理簡介

    【併發編程】synchronized的使用場景和原理簡介

    1. synchronized使用

    1.1 synchronized介紹

    在多線程併發編程中synchronized一直是元老級角色,很多人都會稱呼它為重量級鎖。但是,隨着Java SE 1.6對synchronized進行了各種優化之後,有些情況下它就並不那麼重了。

    synchronized可以修飾普通方法,靜態方法和代碼塊。當synchronized修飾一個方法或者一個代碼塊的時候,它能夠保證在同一時刻最多只有一個線程執行該段代碼。

    • 對於普通同步方法,鎖是當前實例對象(不同實例對象之間的鎖互不影響)。

    • 對於靜態同步方法,鎖是當前類的Class對象。

    • 對於同步方法塊,鎖是Synchonized括號里配置的對象。

    當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。

    1.2 使用場景

    synchronized最常用的使用場景就是多線程併發編程時線程的同步。這邊還是舉一個最常用的列子:多線程情況下銀行賬戶存錢和取錢的列子。

    public class SynchronizedDemo {
    
    
        public static void main(String[] args) {
            BankAccount myAccount = new BankAccount("accountOfMG",10000.00);
            for(int i=0;i<100;i++){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            int var = new Random().nextInt(100);
                            Thread.sleep(var);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        double deposit = myAccount.deposit(1000.00);
                        System.out.println(Thread.currentThread().getName()+" balance:"+deposit);
                    }
                }).start();
            }
            for(int i=0;i<100;i++){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            int var = new Random().nextInt(100);
                            Thread.sleep(var);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        double deposit = myAccount.withdraw(1000.00);
                        System.out.println(Thread.currentThread().getName()+" balance:"+deposit);
    
                    }
                }).start();
            }
        }
    
        private static class BankAccount{
            String accountName;
            double balance;
    
            public BankAccount(String accountName,double balance){
                this.accountName = accountName;
                this.balance = balance;
            }
    
            public double deposit(double amount){
                balance = balance + amount;
                return balance;
            }
    
            public double  withdraw(double amount){
                balance = balance - amount;
                return balance;
            }
    
        }
    }

    上面的列子中,首先初始化了一個銀行賬戶,賬戶的餘額是10000.00,然後開始了200個線程,其中100個每次向賬戶中存1000.00,另外100個每次從賬戶中取1000.00。如果正常執行的話,賬戶中應該還是10000.00。但是我們執行多次這段代碼,會發現執行結果基本上都不是10000.00,而且每次結果 都是不一樣的。

    出現上面這種結果的原因就是:在多線程情況下,銀行賬戶accountOfMG是一個共享變量,對共享變量進行修改如果不做線程同步的話是會存在線程安全問題的。比如說現在有兩個線程同時要對賬戶accountOfMG存款1000,一個線程先拿到賬戶的當前餘額,並且將餘額加上1000。但是還沒將餘額的值刷新回賬戶,另一個線程也來做相同的操作。此時賬戶餘額還是沒加1000之前的值,所以當兩個線程執行完畢之後,賬戶加的總金額還是只有1000。

    synchronized就是Java提供的一種線程同步機制。使用synchronized我們可以非常方便地解決上面的銀行賬戶多線程存錢取錢問題,只需要使用synchronized修飾存錢和取錢方法即可:

    private static class BankAccount{
            String accountName;
            double balance;
    
            public BankAccount(String accountName,double balance){
                this.accountName = accountName;
                this.balance = balance;
            }
            //這邊給出一個編程建議:當我們對共享變量進行同步時,同步代碼塊最好在共享變量中加
            public synchronized double deposit(double amount){
                balance = balance + amount;
                return balance;
            }
            
            public synchronized double  withdraw(double amount){
                balance = balance - amount;
                return balance;
            }
    
        }

    2. Java對象頭

    上面提到,當線程進入synchronized方法或者代碼塊時需要先獲取鎖,退出時需要釋放鎖。那麼這個鎖信息到底存在哪裡呢?

    Java對象保存在內存中時,由以下三部分組成:

    • 對象頭
    • 實例數據
    • 對齊填充字節

    而對象頭又由下面幾部分組成:

    • Mark Word
    • 指向類的指針
    • 數組長度(只有數組對象才有)

    1. Mark Word
    Mark Word記錄了對象和鎖有關的信息,當這個對象被synchronized關鍵字當成同步鎖時,圍繞這個鎖的一系列操作都和Mark Word有關。Mark Word在32位JVM中的長度是32bit,在64位JVM中長度是64bit。

    Mark Word在不同的鎖狀態下存儲的內容不同,在32位JVM中是這麼存的:

    其中無鎖和偏向鎖的鎖標誌位都是01,只是在前面的1bit區分了這是無鎖狀態還是偏向鎖狀態。Epoch是指偏向鎖的時間戳。

    JDK1.6以後的版本在處理同步鎖時存在鎖升級的概念,JVM對於同步鎖的處理是從偏向鎖開始的,隨着競爭越來越激烈,處理方式從偏向鎖升級到輕量級鎖,最終升級到重量級鎖。

    JVM一般是這樣使用鎖和Mark Word的:

    • step1:當沒有被當成鎖時,這就是一個普通的對象,Mark Word記錄對象的HashCode,鎖標誌位是01,是否偏向鎖那一位是0。

    • step2:當對象被當做同步鎖並有一個線程A搶到了鎖時,鎖標誌位還是01,但是否偏向鎖那一位改成1,前23bit記錄搶到鎖的線程id,表示進入偏向鎖狀態。

    • step3:當線程A再次試圖來獲得鎖時,JVM發現同步鎖對象的標誌位是01,是否偏向鎖是1,也就是偏向狀態,Mark Word中記錄的線程id就是線程A自己的id,表示線程A已經獲得了這個偏向鎖,可以執行同步鎖的代碼。

    • step4:當線程B試圖獲得這個鎖時,JVM發現同步鎖處於偏向狀態,但是Mark Word中的線程id記錄的不是B,那麼線程B會先用CAS操作試圖獲得鎖,這裏的獲得鎖操作是有可能成功的,因為線程A一般不會自動釋放偏向鎖。如果搶鎖成功,就把Mark Word里的線程id改為線程B的id,代表線程B獲得了這個偏向鎖,可以執行同步鎖代碼。如果搶鎖失敗,則繼續執行步驟5。

    • step5:偏向鎖狀態搶鎖失敗,代表當前鎖有一定的競爭,偏向鎖將升級為輕量級鎖。JVM會在當前線程的線程棧中開闢一塊單獨的空間,裏面保存指向對象鎖Mark Word的指針,同時在對象鎖Mark Word中保存指向這片空間的指針。上述兩個保存操作都是CAS操作,如果保存成功,代表線程搶到了同步鎖,就把Mark Word中的鎖標誌位改成00,可以執行同步鎖代碼。如果保存失敗,表示搶鎖失敗,競爭太激烈,繼續執行步驟6。

    • step6:輕量級鎖搶鎖失敗,JVM會使用自旋鎖,自旋鎖不是一個鎖狀態,只是代表不斷的重試,嘗試搶鎖。從JDK1.7開始,自旋鎖默認啟用,自旋次數由JVM決定。如果搶鎖成功則執行同步鎖代碼,如果失敗則繼續執行步驟7。

    • step7:自旋鎖重試之後如果搶鎖依然失敗,同步鎖會升級至重量級鎖,鎖標誌位改為10。在這個狀態下,未搶到鎖的線程都會被阻塞。

    2. 指向類的指針
    該指針在32位JVM中的長度是32bit,在64位JVM中長度是64bit。Java對象的類數據保存在方法區。

    3. 數組長度
    只有數組對象保存了這部分數據。該數據在32位和64位JVM中長度都是32bit。

    synchronized對鎖的優化

    Java 6中為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”的概念。在Java 6中,鎖一共有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態,這幾個狀態會隨着競爭情況逐漸升級。鎖可以升級但不能降級,意味着偏向鎖升級成輕量級鎖后不能降級成偏向鎖。

    在聊偏向鎖、輕量級鎖和重量級鎖之前我們先來聊下鎖的宏觀分類。鎖從宏觀上來分類,可以分為悲觀鎖與樂觀鎖。注意,這裏說的的鎖可以是數據庫中的鎖,也可以是Java等開發語言中的鎖技術。悲觀鎖和樂觀鎖其實只是一類概念(對某類具體鎖的總稱),不是某種語言或是某個技術獨有的鎖技術。

    樂觀鎖是一種樂觀思想,即認為讀多寫少,遇到併發寫的可能性低,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,採取在寫時先讀出當前版本號,然後加鎖操作(比較跟上一次的版本號,如果一樣則更新),如果失敗則要重複讀-比較-寫的操作。java中的樂觀鎖基本都是通過CAS操作實現的,CAS是一種更新的原子操作,比較當前值跟傳入值是否一樣,一樣則更新,否則失敗。數據庫中的共享鎖也是一種樂觀鎖。

    悲觀鎖是就是悲觀思想,即認為寫多,遇到併發寫的可能性高,每次去拿數據的時候都認為別人會修改,所以每次在讀寫數據的時候都會上鎖,這樣別人想讀寫這個數據就會block直到拿到鎖。java中典型的悲觀鎖就是Synchronized,AQS框架下的鎖則是先嘗試cas樂觀鎖去獲取鎖,獲取不到,才會轉換為悲觀鎖,如ReentrantLock。數據庫中的排他鎖也是一種悲觀鎖。

    偏向鎖

    Java 6之前的synchronized會導致爭用不到鎖的線程進入阻塞狀態,線程在阻塞狀態和runnbale狀態之間切換是很耗費系統資源的,所以說它是java語言中一個重量級的同步操縱,被稱為重量級鎖。為了緩解上述性能問題,Java 6開始,引入了輕量鎖與偏向鎖,默認啟用了自旋,他們都屬於樂觀鎖

    偏向鎖更準確的說是鎖的一種狀態。在這種鎖狀態下,系統中只有一個線程來爭奪這個鎖。線程只要簡單地通過Mark Word中存放的線程ID和自己的ID是否一致就能拿到鎖。下面簡單介紹下偏向鎖獲取和升級的過程。

    還是就着這張圖講吧,會清楚點。

    當系統中還沒有訪問過synchronized代碼時,此時鎖的狀態肯定是“無鎖狀態”,也就是說“是否是偏向鎖”的值是0,“鎖標誌位”的值是01。此時有一個線程1來訪問同步代碼,發現鎖對象的狀態是”無鎖狀態”,那麼操作起來非常簡單了,只需要將“是否偏向鎖”標誌位改成1,再將線程1的線程ID寫入Mark Word即可。

    如果後續系統中一直只有線程1來拿鎖,那麼只要簡單的判斷下線程1的ID和Mark Word中的線程ID,線程1就能非常輕鬆地拿到鎖。但是現實往往不是那麼簡單的,現在假設線程2也要來競爭同步鎖,我們看下情況是怎麼樣的。

    • step1:線程2首先根據“是否是偏向鎖”和“鎖標誌位”的值判斷出當前鎖的狀態是“偏向鎖”狀態,但是Mark Word中的線程ID又不是指向自己(此時線程ID還是指向線程1),所以此時回去判斷線程1還是否存在;
    • step2:假如此時線程1已經不存在了,線程2會將Mark Word中的線程ID指向自己的線程ID,鎖不升級,仍為偏向鎖;
    • step3:假如此時線程1還存在(線程1還沒執行完同步代碼,【不知道這樣理解對不對,姑且先這麼理解吧】),首先暫停線程1,設置鎖標誌位為00,鎖升級為“輕量級鎖”,繼續執行線程1的代碼;線程2通過自旋操作來繼續獲得鎖。

    在JDK6中,偏向鎖是默認啟用的。它提高了單線程訪問同步資源的性能。但試想一下,如果你的同步資源或代碼一直都是多線程訪問的,那麼消除偏向鎖這一步驟對你來說就是多餘的。事實上,消除偏向鎖的開銷還是蠻大的。
    所以在你非常熟悉自己的代碼前提下,大可禁用偏向鎖:

     -XX:-UseBiasedLocking=false

    輕量級鎖

    “輕量級鎖”鎖也是一種鎖的狀態,這種鎖狀態的特點是:當一個線程來競爭鎖失敗時,不會立即進入阻塞狀態,而是會進行一段時間的鎖自旋操作,如果自旋操作拿鎖成功就執行同步代碼,如果經過一段時間的自旋操作還是沒拿到鎖,線程就進入阻塞狀態。

    1. 輕量級鎖加鎖流程
    線程在執行同步塊之前,JVM會先在當前線程的棧楨中創建用於存儲鎖記錄的空間,並將對象頭中的Mark Word複製到鎖記錄中,官方稱為Displaced Mark Word。然後線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。

    2. 輕量級鎖解鎖流程
    輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到對象頭,如果成功,則表示沒有競爭發生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。

    重量級鎖

    因為自旋會消耗CPU,為了避免無用的自旋(比如獲得鎖的線程被阻塞住了),一旦鎖升級成重量級鎖,就不會再恢復到輕量級鎖狀態。當鎖處於這個狀態下,其他線程試圖獲取鎖時,都會被阻塞住,當持有鎖的線程釋放鎖之後會喚醒這些線程,被喚醒的線程就會進行新一輪的奪鎖之爭。

    鎖自旋

    自旋鎖原理非常簡單,如果持有鎖的線程能在很短時間內釋放鎖資源,那麼那些等待競爭鎖的線程就不需要做內核態和用戶態之間的切換進入阻塞掛起狀態,它們只需要等一等(自旋),等持有鎖的線程釋放鎖后即可立即獲取鎖,這樣就避免用戶線程和內核的切換的消耗。

    但是線程自旋是需要消耗CPU的,說白了就是讓CPU在做無用功,線程不能一直佔用CPU自旋做無用功,所以需要設定一個自旋等待的最大時間。如果持有鎖的線程執行的時間超過自旋等待的最大時間扔沒有釋放鎖,就會導致其它爭用鎖的線程在最大等待時間內還是獲取不到鎖,這時爭用線程會停止自旋進入阻塞狀態。

    自旋鎖盡可能的減少線程的阻塞,這對於鎖的競爭不激烈,且佔用鎖時間非常短的代碼塊來說性能能大幅度的提升,因為自旋的消耗會小於線程阻塞掛起操作的消耗!但是如果鎖的競爭激烈,或者持有鎖的線程需要長時間佔用鎖執行同步塊,這時候就不適合使用自旋鎖了,因為自旋鎖在獲取鎖前一直都是佔用cpu做無用功,線程自旋的消耗大於線程阻塞掛起操作的消耗,其它需要cup的線程又不能獲取到cpu,造成cpu的浪費。

    JDK7之後,鎖的自旋特性都是由JVM自身控制的,不需要我們手動配置。

    鎖對比

    鎖的優化

    • 減少鎖的時間:不需要同步的代碼不加鎖;
    • 使用讀寫鎖:讀操作加讀鎖,可以併發讀,寫操作使用寫鎖,只能單線程寫;
    • 鎖粗化:假如有一個循環,循環內的操作需要加鎖,我們應該把鎖放到循環外面,否則每次進出循環,都進出一次臨界區,效率是非常差的;

    參考

    • https://blog.csdn.net/lkforce/article/details/81128115
    • 《併發編程藝術》

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    台灣寄大陸海運貨物規則及重量限制?

    大陸寄台灣海運費用試算一覽表

  • Java編程思想——第14章 類型信息(二)反射

    六、反射:運行時的類信息

      我們已經知道了,在編譯時,編譯器必須知道所有要通過RTTI來處理的類。而反射提供了一種機制——用來檢查可用的方法,並返回方法名。區別就在於RTTI是處理已知類的,而反射用於處理未知類。Class類與java.lang.reflect類庫一起對反射概念進行支持,該類庫包含Field、Method以及Constructor(每個類都實現了Member接口)。這些類型是由JVM運行時創建的,用來表示未知類種對應的成員。使用Constructor(構造函數)創建新的對象,用get(),set()方法讀取和修改與Field對象(字段)關聯的字段,用invoke()方法調用與Method對象(方法)關聯的方法。這樣,匿名對象的類信息就能在運行時被完全確定下來,而在編譯時不需要知道任何事情。

      其實,當反射與一個未知類型的對象打交道時,JVM只是簡單地檢查這個對象,在做其他事情之前必須先加載這個類的Class對象。因此,那個類的.class文件對於JVM來說必須時可獲取的(在本地或網絡上)所以反射與RTTI的區別只在於:對於RTTI來說,編譯器在編譯時打開和檢查.class文件,而對於反射來說,.class文件在編譯時是不可獲得的,所以是運行時打開和檢查.class文件。反射在需要創建更動態的代碼時很有用。

    七、動態代理

      代理是基本的設計模式:為其他對象提供一種代理,以便控制對象,而在對象前或后加上自己想加的東西。

    interface Interface {
        void doSomething();
    
        void doSomeOtherThing(String args);
    }
    
    class RealObject implements Interface {
    
        @Override
        public void doSomething() {
            System.out.println("doSomething");
        }
    
        @Override
        public void doSomeOtherThing(String args) {
            System.out.println("doSomeOtherThing" + args);
        }
    }
    
    class SimpleProxy implements Interface {
    
        private Interface proxyId;
    
        public SimpleProxy(Interface proxyId) {
            this.proxyId = proxyId;
        }
    
        @Override
        public void doSomething() {
            //將原有的doSomething 方法添加上了一個輸出 這就是代理之後新增的東西
            //就好比某公司代理遊戲后加的內購
            System.out.println("SimpleProxy doSomething");
            proxyId.doSomething();
        }
    
        @Override
        public void doSomeOtherThing(String args) {
            proxyId.doSomeOtherThing(args);
            //新增的東西可以在原有之前或之後都行
            System.out.println("SimpleProxy doSomeOtherThing" + args);
        }
    }
    
    public class SimpleProxyDemo {
        static void consumer(Interface i) {
            i.doSomething();
            i.doSomeOtherThing(" yi gi woli giao");
        }
    
        public static void main(String[] args) {
            consumer(new RealObject());
            System.out.println("-----  -----  -----");
            consumer(new SimpleProxy(new RealObject()));
        }
    }

    結果:

    doSomething
    doSomeOtherThing yi gi woli giao
    -----  -----  -----
    SimpleProxy doSomething
    doSomething
    doSomeOtherThing yi gi woli giao
    SimpleProxy doSomeOtherThing yi gi woli giao

      因為consumer()接受的Interface,所以無論是RealObject還是SimpleProxy,都可以作為參數,而SimpleProxy插了一腳 代理了RealObject加了不少自己的東西。

      java的動態代理更前進一步,因為它可以動態創建代理並動態地處理對所代理方法的調用。在動態代理上所做的所有調用都會被重定向到單一的調用處理器上,它的工作是揭示調用的類型並確定相應的對策。

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    interface Interface {
        void doSomething();
    
        void doSomeOtherThing(String args);
    }
    
    class RealObject implements Interface {
    
        @Override
        public void doSomething() {
            System.out.println("doSomething");
        }
    
        @Override
        public void doSomeOtherThing(String args) {
            System.out.println("doSomeOtherThing" + args);
        }
    }
    
    class DynamicProxyHandler implements InvocationHandler {
        private Object proxyId;
    
        public DynamicProxyHandler(Object proxyId) {
            this.proxyId = proxyId;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("**** proxy:" + proxy.getClass() + ", method" + method + ", args:" + args);
            if (args != null) {
                for (Object arg : args) {
                    System.out.println(" " + arg);
                }
            }
            return method.invoke(proxyId, args);
        }
    }
    
    public class SimpleProxyDemo {
        static void consumer(Interface i) {
            i.doSomething();
            i.doSomeOtherThing(" yi gi woli giao");
        }
    
        public static void main(String[] args) {
            RealObject realObject = new RealObject();
            consumer(realObject);
            System.out.println("-----  -----  -----");
         //動態代理 可以代理任何東西 Interface proxy
    = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynamicProxyHandler(realObject)); consumer(proxy); } }

    結果:

    doSomething
    doSomeOtherThing yi gi woli giao
    -----  -----  -----
    **** proxy:class $Proxy0, methodpublic abstract void Interface.doSomething(), args:null
    doSomething
    **** proxy:class $Proxy0, methodpublic abstract void Interface.doSomeOtherThing(java.lang.String), 
    args:[Ljava.lang.Object;@7ea987ac  yi gi woli giao
    doSomeOtherThing yi gi woli giao

    通過Proxy.newProxyInstance()可以創建動態代理,這個方法需要三個參數:

    1. 類加載器:可以從已經被加載的對象中獲取其類加載器;

    2. 你希望該代理實現的接口列表(不可以是類或抽象類,只能是接口);

    3. InvocationHandler接口的一個實現;

    在 invoke 實現中還可以根據方法名處對不同的方法進行處理,比如:

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("**** proxy:" + proxy.getClass() + ", method" + method + ", args:" + args);
            if (args != null) {
                for (Object arg : args) {
                    System.out.println(" " + arg);
                }
            }
            if (method.getName().equals("doSomething")) { System.out.println("this is the proxy for doSomething"); } return method.invoke(proxyId, args);
        }

    還可以對參數或方法進行更多的操作因為 你已經得到了他們 盡情的使用你的代理權吧 ~~ 先加它十個內購。

    九、接口與類型信息

      interface關鍵字的一種重要目標就是允許程序員隔離構件,進而降低耦合。反射,可以調用所有方法,甚至是private。唯獨final是無法被修改的,運行時系統會在不拋任何異常的情況接受任何修改嘗試,但是實際上不會發生任何修改。

        void callMethod(Object a, String methodName) throws Exception {
            Method method = a.getClass().getDeclaredMethod(methodName);
            method.setAccessible(true);
            method.invoke(a);
        }

     

     

      

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    大陸寄台灣空運注意事項

    大陸海運台灣交貨時間多久?

  • 在校生如何面向面試地學習Java,匹馬行天下之思維決定高度篇——大學再努力,培訓機構做兄弟,你的簡歷能幫你爭取到面試機會嗎

    在校生如何面向面試地學習Java,匹馬行天下之思維決定高度篇——大學再努力,培訓機構做兄弟,你的簡歷能幫你爭取到面試機會嗎

        最近我在博客園裡,看到不少在校的同學在學java,而且,在我最近舉辦的一次直播活動中,也有不少在校生同學來提問,java該怎麼學。

        對於那些已經工作的同學,哪怕才工作不久,畢竟也能從項目實踐里總結和探索java的學習途徑和相關方法。但在校生同學由於沒機會接觸實際項目,在學習內容、進階途徑和學成標準這些方面都是兩眼一抹黑,而大學里的內容可能偏重於理論,講述的技術往往也和軟件公司里常用的技術不匹配。

        這就導致了很多上心的在校生,雖然很努力,但到畢業時,才發現自己起早貪黑學成的技能並不能幫他們找到工作。在本文里,就將結合我面試實習生和畢業生的標準,專門給在校生這個群體一些學習Java方面的建議。

    1 明確目標,先說下公司面試應屆生的標準

        我最近可能都在大公司,到畢業季,會到一些學校去校招,校招的標準描述起來很簡單:Java方面能幹活,或者能經短期幫帶后能幹活,具體標準如下。

        1 Web框架方面,需要以全棧的形式,了解SSM,或Spring Boot或Spring Cloud從前端到後端的基本語法,至少能在項目經理短期幫助下,能照着別人的例子寫代碼。應屆生候選人只要能講清楚相關框架的語法點和流程即可,最多再附帶說明下mybatis等組件的用法,至於redis,dubbo,根本沒要求。

        2 數據庫方面,能會基本的增刪改查即可,外帶一些基本概念,比如事務怎麼處理,JDBC里批處理怎麼處理。

        3 Java語法(也就是核心方面),其實如果能講清楚SSM等Web框架技能,這塊只要刷題就能過,比如就問些ArrayList和LinkedList的差別,以及多線程等的概念。

        4 人看上去聽話,想法少,學習能力強,責任心強,不是刺頭,這塊其實大多數人都可以達標。

        以上不要求有商業項目經驗,當然如果有,絕對是加分項,而且這還是大公司的面試應屆生的標準。至於一些小公司,或者是一些外包公司,有時候能招到人就阿彌陀佛了(至於有些能力比較強的一本的應屆生願不願意去還難說)。有些在招收應屆生的時候,不少小公司甚至在“項目經驗”方面沒要求,哪怕沒學習項目經驗也不要緊,只會問些網上比較大路的面試題,能說上來即可。

    2 面試中大多數應屆生的實際表現

        從面試官角度來看,招收應屆生的標準其實是很低的,對應的,在招初級開發時,多少都需要有商業項目經驗。從這個角度來看,應屆生最好在校期間就找到工作,畢業后兩三個月找不到工作問題還不怎麼大,但如果半年後再找不到工作,那麼到時候被面試的標準就高於“應屆生”的標準了。

        這裏我無意貶低應屆生的水平,畢竟我們都是從這個階段過來的,但從面試情況來看,至少有將近一半的應屆生達不到標準,下面列些具體的表現。

        1 沒有框架開發的經驗,這裏最低要求是能自行搭建個SSM項目,但不少同學根本沒有。

        2 數據庫方面,就知道理論上的增刪改查,甚至不會在MySQL, Oracle和SQL Server平台上運行過SQL語句。

        3 Java核心方面,掌握了一大堆項目里一定不會用的,比如Swing之類的界面編程技術,但該掌握的多線程集合等,一些常用的概念也不清楚。

        論動手能力,有些同學甚至沒有在Eclipse等IDE上運行通Java代碼,或者出了基本的語法錯誤不知道如何自行解決,至於沒有debug調試經驗的,就更加見怪不怪了,而在代碼里需要加必要的try…catch異常處理語句,這就更加鳳毛麟角了。

        在一些一本大學里,理論和實際操作能力較差的同學雖然不多,但也有,就更別提其它大學和大專了。我也和一些大學老師打過交道,也看過一些大學里用的Java和數據庫等編程方面的教材,再結合諸多應屆生在面試時的表現,我的感受是,或許大學階段更會培養學生的理論素養,但大學生朋友在讀大學階段,一定要提升實際的編程能力,包含但不限於(SSM)框架的編程能力,數據庫實際操作能力和Java核心部分代碼的開發和調試能力 。 

    3 哪些大學里學到的知識點面試大概率不問(根本不用太費精力看的技術有哪些)

         前幾天我看到篇大學生朋友寫的文章,,或許很多大學生朋友也知道上進,平時也在不斷看各種資料,但可能苦於方法不當,可能有些大學老師也沒真在公司里干過,也沒法給出合適的學習建議,所以導致最終畢業找工作時,能力沒達到基本的期望要求。

        也就是說,大學教育和公司面試需求之間存在差距,這就給一些培訓機構帶來了商機。但培訓機構收的錢也不便宜,而培訓班也不是一定能保證學生能找到工作,關鍵要靠自己。從這裏開始,就將給出有實際操作性的學習建議。

        我最近接觸到不少大學生朋友,發現他們努力正在學的知識,面試時未必會問,也就是說,這些點白學了。之前已經提到了面試的標準,這裏就將結合具體的知識點,列出面試時需要掌握的最低技能標準,除此之外,大概率不會問的。

        1  Java核心方面,集合,多線程,異常處理,IO,JDBC,面向對象技能,大概率會問到,其它的沒提到的,比如圖形化界面,NIO,Socket網絡編程,甚至Applet之類的,不會問到。

        2 數據庫方面,會寫增刪改查的SQL語句,知道存儲過程之類的概念,會通過索引優化查詢條件,會建表,會些諸如子查詢,group by,having,表關聯查詢等基本SQL技能,這裏請注意,至少得用過一種商業化數據庫。

        3 框架方面,需要有Spring+Spring MVC+mybatis框架的實際操作能力(不是商業項目開發能力),至於有Spring Boot或Spring Cloud,那更好了。

        4 綜合技能方面,能知道基本的數據結構知識(線性表外帶排序外帶一些樹的技能),基本的操作系統知識(一般僅限於線程進程概念),基本網絡通訊知識(一般僅限於網絡通訊模型和tcp udp協議),但這僅僅是“需要知道”而已。

         大家其實也可以通過看各種職位描述和招聘需求,看下哪些技能實際上是不會問的,對於這些知識,就不用學,從而把精力用到學實際Java相關技能上。

         這裏需要說明,在大學階段學的很多知識,不能說沒用。比如網絡通訊里的tcp底層通訊細節,這些技能或者要等到工作5年後升級到高級開發或架構師的時候才會用到,而且以高級開發視角觀察需要掌握的通訊協議細節知識,絕對要比大學階段要複雜。

        換句話說,很多技能,在大學階段也就“需要了解有這事”,以在大學階段的經歷,再多用時間學,估計也無法達到“實際項目的需求”,而且等到有實際項目經驗時,再學這類技能也就是一兩周的事情。兩廂一對比,結論就很明確了:在校階段應該更多積累實際開發能力,因為更得靠這個找工作。 

    4 用一個月的時間了解Java核心部分的內容

        通過上文,大家大致可以了解到畢業時找工作的目標,如果再不了解,可以實際看下招聘要求,甚至直接多去參加招聘會和面試,總之優先考察實際的開發能力,具體在Java核心部分,該如何高效學習呢?

        1 在電腦上裝jdk,eclipse,別用editplus之類的工具,最好再用eclipse的自動編譯功能。這方面,其實是鍛煉自己的動手搭建環境的能力,工作后,開發是一方面,搭建環境的能力同樣重要。

        2 剛開始,一定得去找兩三本Java入門書,先通過運行現有代碼,理解代碼的含義。別光看書不運行,開始階段,也多運行別人的代碼,別自己敲代碼。這裏建議直接找書,因為相比一些視頻教材,畢竟書上的知識很系統,而且能正式出版的書一般沒代碼問題,能直接運行。不建議自己敲代碼,是因為自己敲代碼時,多少會遇到問題,遇到問題后延誤學習進度是小,因為一直得不到解決從而影響學習信心,甚至終止學習了,事情就大了。

        3 如果找到兩三本Java入門書,一般其中涵蓋的知識系統大多很相似,大家可以先運行一遍所有代碼,這樣就能大致掌握代碼結構和基本知識點,而且由於書上代碼一般問題不大,而且質量也不會低,至少不會有太大的阻礙性問題。

        4 當運行好以後,着重觀察集合,面向對象,多線程,IO處理,JDBC,異常處理相關章節,這個階段,是以掌握API用法為主,在這基本上再看下諸如接口,抽象類,異常處理流程,垃圾回收之類的高級知識點。 

        在上述基礎上,如果可以通過資料的幫助,用Java實現堆棧,鏈表,隊列,散列表,樹等的數據結構,同時操練各種排序算法,這對找工作也有些幫助。

    5 用半個月的時間,以MySQL為例,了解數據庫的大致操作

        在數據庫方面,最好也去找本書,同時在MYSQL上實踐。為什麼選MYSQL?因為這比較輕,相比Oracle而言,好安裝,當然如果有條件裝SQL Server之類的,那就更好了。 

        1 在MySQL數據庫上,實踐各種增刪改查的SQL語句,實踐建表,建索引能技巧,同時實踐一下諸如子查詢,with as等等複雜的SQL語句。

        2  用JDBC連同MYSQL,在Java代碼里做各種增刪改查的操作。

        3  在此基礎上,了解諸如索引,範式和鎖等概念,這時候雖然認識也會很膚淺,但至少不會一頭霧水了。

        這樣,在數據庫方面,好歹有實際操作經驗了,這為之後的項目實踐,能打下很好的基礎。

    6 用一個月的時間,了解基於Spring的web框架

        面試時更看重的是框架經驗,這塊學習的建議如下。

        1 先通過運行代碼,了解Spring里IOC, AOP,這時應該注意各種配置。

        2 熟悉Spring的基本概念后,可以嘗試跑一個SSM的小例子,這個例子可以非常簡答,就一個頁面也行,但要包含Spring MVC和Mybatis諸多要素,這樣大家好歹能知道框架的構成,在這個基礎上,可以繼續擴展,加些必要的業務,從而進一步了解這個框架。

        在這個階段,還是最好看書上的例子,因為書上的例子一定能通,而且還會帶部署和運行的步驟,還是不建議自己敲代碼,因為SSM框架相對複雜,在這個階段如果自己敲,很有可能會因為問題太多而放棄。

        3 在自己機器上跑通SSM框架的案例后,可以網上找個帶業務的系統,比如圖書管理系統等,從中看些前端和後端交互數據的流程,同時,結合業務看Mybatis里的ORM過程,以及Spring里的常用註解。     

    7 在學習過程中,可以避免的誤區

        Java方面,本人按照上述步驟輔導過不少在校的同學,只要肯上心練習,效果不會太差,不過很多同學在實踐過程中會走彎路,這裏列些普遍存在的問題,請大家在操練的過程中盡量避免。

        1 別鑽牛角尖,先面再點。比如有同學對一個知識點不理解,或者一段代碼運行有問題,就會在這個點上耗費很多時間,不解決就不繼續。其實在這個過程中,首先需要全方位掌握SSM框架、Java技術和數據庫,個別點如果有問題,可以跳過,或者一個案例運行不通可以運行其它類似的,總之別在一個點上花費太多的時間。

        2 再啰嗦一下,最好先照着書上代碼運行,開始階段的學習方法是“複製粘貼運行理解”,在自己已經有一定的基礎后,再嘗試自己寫代碼。

        3 在操練SSM項目時,有些同學會照着視頻上提到步驟做,如果有些視頻步驟不對,這樣就會有問題,所以還是建議照着書做。

        4 工具要選對,剛開始就eclipse,或者Idea,別用editplus或命令行。

        上述是方法上的誤區,其實最大的問題出在態度上,上述學習過程持續時間不會短,快則兩三個月,慢則半年,如果中途因為效果不明顯而放棄,那就很可惜了。 

    8 有學習項目經驗后,爭取找些商業項目的實踐機會

        按照上述步驟,讓自己擁有最基本的SSM以及其它Java和數據庫相關技能后,要做的絕不是繼續積累學習項目經驗,而是盡可能去找實習的機會,以積累商業項目的經驗。在找實習經驗方面,大家可以參照如下的建議。

        1 在我之前的博文里也提到,大三時,打聽計算機學院里哪些老師和外面公司有合作,一般碩導都有這樣的項目,然後直接去找老師,剛開始不要錢,只求經驗,或許對各位在校生同學而言,這種方式是比較可行的,本人第一個商業項目經驗也是由此得到的。

        2 一般學校里都會安排實習,實習的過程中,一定要重視,這個是實打實的商業項目經驗。

        3 寒暑假,找軟件公司,這可能會比較艱辛,因為在校階段自己非常難找相關實習機會,但要去找。

        4 這個大家根據自己的實際情況自己斟酌:如果報培訓班,多少能積累些項目經驗,但這僅僅是學習項目經驗,不過在培訓班裡,可以找相關老師推薦實習的機會。

        5 如果實在找不到實習的機會,那麼盡可能通過各種渠道,去找商業項目經驗的案例,我知道有些網站有,但不做廣告。雖然靠這種方式積累的商業項目經驗質量就打折扣了,但好歹聊勝於無,而且畢竟很多畢業生,連學習項目經驗都沒。 

        不少在校的同學發現,哪怕實際只幹了三個月的商業項目經驗,自己的技能也會很大程度提升,而且實際的商業項目經驗,會讓大家掌握書本上根本不會多提但項目里一定會用的技能,比如JVM內存調優或多線程併發。從這意義上來講,只要有條件,大學生朋友應該擠破頭去找商業項目的經驗,而不是悠哉游哉地坐在機房裡敲代碼。只要你有商業項目的經驗,哪怕就三個月,找工作時你就有代差優勢。

    9 畢業生準備簡歷的要點

        按照上述步驟,大家在畢業時,多少會有些商業項目經驗,再不濟也能有學習項目經驗,請記住,在招聘畢業生時,第一看項目經驗,第二看項目里包含的技能,第三再問算法和理論問題,至於邏輯題和情商題,只供參考。

         對此,畢業生在簡歷中,一定得突出做過的項目經驗,優先挖掘商業項目經驗,實在沒有學習項目也行。如果沒任何項目經驗,那麼找工作時會吃力很多。本文的重點是講學習方法,準備簡歷的技能只是稍微提到。這塊可以參考的之前寫的博文,。如果有時間的話,或許我會再專門針對畢業生朋友,寫篇文章講在java方面,如何準備簡歷和面試,以及如何找工作。 

    10 總結:最多堅持半年,技能就會大變樣

        我記得兩年前,我的Python能力僅限於寫hello world,我運行代碼看文檔,辛苦堅持了半年,自認為就達到了出版書的地步,再過了半年,果真就從出版社接到了一本以股票案例講述Python技術的選題,並自認為寫的內容不會誤人子弟。

        我持續關注了一位大學生網友的公眾號,也就是寫了半年多博客,他技術看上去就更專業多了。能堅持不懈地上進,這種精神值得提倡,雖然我工作很久了,但也得時刻警惕,不能懈怠,這也是我肯推薦該公眾號的原因,不僅推薦其內容,更提倡這種精神。

        不光是這位同學,經我培訓的其它很多大學生,也只要肯上心學,最多半年,最短三個月,就能從小白進階到能實際幹活的水平,而且還真能面試進軟件公司幹活。 

        本文雖然長,但其中也是盡我所能,給出大學生朋友若干有實際操作性的學習建議,其實對於其它初學者,本文給出的建議同樣適用。希望本文能幫到大家,最後感謝大家能讀完此文。

    版權說明:

        如果要轉載本文,請先徵得本人同意。

     

     

     

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※專營大陸空運台灣貨物推薦

    台灣空運大陸一條龍服務

  • Tsx寫一個通用的button組件

    Tsx寫一個通用的button組件

    一年又要到年底了,vue3.0都已經出來了,我們也不能一直還停留在過去的js中,是時候學習並且在項目中使用一下Ts了。

      如果說jsx是基於js的話,那麼tsx就是基於typescript的

      廢話也不多說,讓我們開始寫一個Tsx形式的button組件,

      ts真的不僅僅只有我們常常熟知的數據類型,還包括接口,類,枚舉,泛型,等等等,這些都是特別重要的

      項目是基於vue-cli 3.0 下開發的,可以自己配置Ts,不會的話那你真的太難了

      

     

         我們再compenonts中新建一個button文件夾,再建一個unit文件夾,button放button組件的代碼,unit,放一些公共使用模塊

      我們再button文件夾下創建 ,index .tsx放的button源碼,index.less放的是樣式,css也是不可缺少的

           

     

       分析一下button需要的一些東西

      第一個當然是props,還有一個是點擊事件,所以我們第一步就定義一下這兩個類型

    type ButtonProps = {
      tag: string,
      size: ButtonSize,
      type: ButtonType,
      text: String
    }
    
    type ButtonEvents = {
      onClick?(event: Event) :void
    }
    type ButtonSize = 'large' | 'normal' | 'small' | 'mini'
    type ButtonType = 'default' | 'primary' | 'info' | 'warning' | 'danger'

      因為button是很簡單的組件,內部也沒有一些特別的狀態需要改變,所以我們用函數式組件的方式去寫(之後的render會用到這個方法)

    function Button (h: CreateElement, props: ButtonProps, slots: DefaultSlots, ctx: RenderContext<ButtonProps>) {
      const { tag, size, type } = props
      let text
      console.log(slots)
      text = slots.default ? slots.default() : props.text
      function onClick (event: Event) {
        emit(ctx, 'click', event)
      }
      let classes = [size,type]
      return (
        <tag
          onClick = {onClick}
          class = {classes}
        >
          {text}
        </tag>
      )
    }

      h 是一個預留參數,這裏並沒有用到 ,CreateElement  這個是vue從2.5之後提供的一個類型,也是為了方便在vue項目上使用ts

      props 就是button組件的傳入的屬性,slots插槽,ctx,代表的是當前的組件,可以理解為當前rendercontext執行環境this

      DefaultSlots是我們自定義的一個插槽類型

    export type ScopedSlot<Props = any> = (props?: Props) => VNode[] | VNode | undefined;
    
    export type ScopedSlots = {
      [key: string]: ScopedSlot | undefined;
    }

      插槽的內容我們都是需要從ctx中讀取的,默認插槽的key就是defalut,具名插槽就是具體的name

      button放發內部還有一個具體的點擊事件,還有一個emit方法,從名字我們也可以看的出,他是粗發自定義事件的,我們這裏當然不能使用this.emit去促發,

      所以我們需要單獨這個emit方法,我們知道組件內所以的自定義事件都是保存在listeners里的,我們從ctx中拿取到所以的listeners

    
    
    
    

      import { RenderContext, VNodeData } from ‘vue’ // 從vue中引入一些類型


    function
    emit (context: RenderContext, eventName: string, ...args: any[]) { const listeners = context.listeners[eventName] if (listeners) { if (Array.isArray(listeners)) { listeners.forEach(listener => { listener(...args) }) } else { listeners(...args) } }

      這樣我們組件內部的事件觸發就完成了

      我們的button肯定是有一些默認的屬性,所以,我們給button加上默認的屬性

    Button.props = {
      text: String,
      tag: {
        type: String,
        default: 'button'
      },
      type: {
        type: String,
        default: 'default'
      },
      size: {
        type: String,
        default: 'normal'
      }
    }

      我們定義一個通用的functioncomponent 類型

    type FunctionComponent<Props=DefaultProps, PropsDefs = PropsDefinition<Props>> = {
      (h: CreateElement, Props:Props, slots: ScopedSlots, context: RenderContext<Props>): VNode |undefined,
      props?: PropsDefs
    }

      PropsDefinition<T>  這個是vue內部提供的,對 props的約束定義

      不管怎麼樣我們最終返回的肯定是一個對象,我們把這個類型也定義一下

      ComponentOptions<Vue> 這個也是vue內部提供的

     interface DrmsComponentOptions extends ComponentOptions<Vue> {
      functional?: boolean;
      install?: (Vue: VueConstructor) => void;
    }

      最終生成一個組件對象

    function transformFunctionComponent (fn:FunctionComponent): DrmsComponentOptions {
      return {
        functional: true, // 函數時組件,這個屬性一定要是ture,要不render方法,第二個context永遠為underfine
        props: fn.props,
        model: fn.model,
        render: (h, context): any => fn(h, context.props, unifySlots(context), context)
      }
    }

      unifySlots 是讀取插槽的內容

    // 處理插槽的內容
    export function unifySlots (context: RenderContext) {
      // use data.scopedSlots in lower Vue version
      const scopedSlots = context.scopedSlots || context.data.scopedSlots || {}
      const slots = context.slots()
    
      Object.keys(slots).forEach(key => {
        if (!scopedSlots[key]) {
          scopedSlots[key] = () => slots[key]
        }
      })
    
      return scopedSlots
    }

      當然身為一個組件,我們肯定是要提供全局注入接口,並且能夠按需導入

      所以我們給組件加上名稱和install方法,install 是 vue.use() 方法使用的,這樣我們能全部註冊組件

    export function CreateComponent (name:string) {
      return function <Props = DefaultProps, Events = {}, Slots = {}> (
        sfc:DrmsComponentOptions | FunctionComponent) {
        if (typeof sfc === 'function') {
          sfc = transformFunctionComponent(sfc)
        }
        sfc.functional = true
        sfc.name = 'drms-' + name
        sfc.install = install
        return sfc 
      }
    }

      index.tsx 中的最後一步,導出這個組件

    export default CreateComponent('button')<ButtonProps, ButtonEvents>(Button)

      還少一個install的具體實現方法,加上install方法,就能全局的按需導入了

    function install (this:ComponentOptions<Vue>, Vue:VueConstructor) {
      const { name } = this
      Vue.component(name as string, this)
    }

     

       最終實現的效果圖,事件的話也是完全ok的,這個我也是測過的

     

       代碼參考的是vant的源碼:

      該代碼已經傳到git:     dev分支應該是代碼全的,master可能有些並沒有合併

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益