<dfn id="hx5t3"><strike id="hx5t3"><em id="hx5t3"></em></strike></dfn>

    <thead id="hx5t3"></thead><nobr id="hx5t3"><font id="hx5t3"><rp id="hx5t3"></rp></font></nobr>

    <listing id="hx5t3"></listing>

    <var id="hx5t3"></var>
    <big id="hx5t3"></big>

      
      

      <output id="hx5t3"><ruby id="hx5t3"></ruby></output>
      <menuitem id="hx5t3"><dfn id="hx5t3"></dfn></menuitem>

      <big id="hx5t3"></big>

        justjavac

        justjavac 查看完整檔案

        天津編輯  |  填寫畢業院校自由職業  |  前端 編輯 discuss.flarum.org.cn 編輯
        編輯

        會寫點 js 代碼

        個人動態

        justjavac 回答了問題 · 1月20日

        typescript class定義私有方法

        如果你需要 es 中的真正的私有屬性,而非 ts 的假私有屬性,可以這樣寫:

        class Test {
            #a = () => console.log(1)
        
            public b() {
                this.#a();
            }
        }

        如果你需要支持低版本的 js,而只是希望這個屬性不可見(其實還是可以訪問的),可以將這個數值設置為不可枚舉。

        function Test() {}
        
        Object.defineProperty(Test.prototype, "a", {
          enumerable: false,
          writable: true,
        });
        
        Test.prototype.a = function() {
          console.log(111)
        }

        關注 3 回答 2

        justjavac 回答了問題 · 2020-11-20

        解決express的res.redirect('back')是干什么用的???

        這個并不是常規意義上的“退回上一步”。這個操作是將訪問重定向到了請求頭(request headers)的 referer。

        當我們從 https://a.com/xxx 跳轉到 https://b.com/yyy 時,我們向 yyy 發送的請求頭里面,referer 是 https://a.com/xxx。此時調用 res.redirect('back') 就會回退到 xxx 頁面。

        但是當我們進入到 yyy 頁面后,再刷新頁面,此時的 referer 會變成 https://b.com/yyy,表現的結果就是,會停留在本頁不動。

        而你的情況更特殊,100% 是停留在本頁,根本不會跳轉到上一頁。

        以為你在 yyy 頁面中使用超鏈接發起一個請求,<a href="/back">后退</a>,此時的 referer 永遠是 https://b.com/yyy,所以你會停留在本頁面。

        你還可以這么理解:你目前在 yyy 頁面,點擊鏈接后你跳轉到了 back 頁面,但是 back 頁面的邏輯是回退到上一個頁面,于是又跳回了 yyy 頁面。

        關注 1 回答 1

        justjavac 回答了問題 · 2020-11-20

        解決問一個網絡傳輸和序列化問題!

        序列化(Serialization)是將對象的狀態信息轉換為可以存儲或傳輸的形式的過程,對應的則是反序列化的過程,可以通過從存儲區中讀取或反序列化對象的狀態,重新創建該對象。

        content-type 表示數據以什么樣的方式進行編碼。比如需要傳遞一個對象 User,這個對象有 2 個 key 和 2 個 value。

        let user = {
          "name": "justjavac",
          "age": 18
        }

        在內存中這個對象不僅僅只有這 2 個屬性,還有原型鏈以及其他一些對象的狀態信息和內存布局信息。然而我們只需要傳輸或者存儲這 2 個信息,也就是說我們要對 User 的這 2 個屬性進行序列化。

        而編碼就涉及到,我們如何將這個對象通過網絡進行傳輸,為了確保接收方(服務器)能夠正確的解碼這個對象,我們不僅僅要發送序列化的字符串,還要發送 content-type,告知對方以何種方式進行解碼。

        我們可以編碼為:

        name:justjavac\n
        age:18

        或者

        name=justjavac&age=18

        或者

        name:justjavac;age=18

        服務器接收到數據后,再根據 content-type 的編碼類型進行解碼。

        瀏覽器和服務器只支持第二種,這種方式的優勢是發送的數據量少,缺點是沒有類型信息。服務器接收到數據后,根據約定轉化為對應的類型(此類型不必和前端 js 一一對應)。

        另一種編碼就是 json,發送數據為:

        {"name":"justjavac","age":18}

        這種方式包含了類型信息。

        此外 http 還支持另外的 2 中編碼方式(共4種)。

        此外,如果你的業務涉及到加密或者更優秀的編碼方式,也可以使用 raw 方式傳遞原始字符串數據,然后前后端約定編碼/解碼算法。

        比如傳遞

        u^3hpo)4hAU

        以上。

        關注 2 回答 2

        justjavac 回答了問題 · 2020-11-18

        解決從csv文件導入行情數據時,列名是亂碼

        應該是編碼問題,在 Windows 系統中 csv 是 gb2312 編碼。試試把編碼改為 utf8。

        關注 2 回答 2

        justjavac 贊了文章 · 2020-11-16

        基于 Serverless 的 Valine 可能并沒有那么香

        Valine 是一款樣式精美,部署簡單的評論系統, 第一次接觸便被它精美的樣式,無服務端的特性給吸引了。它最大的特色是基于 LeanCloud 直接在前端進行數據庫操作而無需服務端,極大的縮減了部署流程,僅需要在靜態頁引入 Valine SDK 即可。

        ?????? 初識 Valine

        以下是 Valine 官網提供的快速部署腳本,其中 appIdappKey 是你在 LeanCloud 上創建應用后對應的應用密鑰。也正是基于這對密鑰,Valine 在內部調用了 LeanCloud SDK 進行數據的獲取,最終將數據渲染在 #vcomments 這個 DOM 上。這便是 Valine 的大概原理。

        <head>
          ..
          <script data-original='//unpkg.com/valine/dist/Valine.min.js'></script>
          ...
        </head>
        <body>
          ...
          <div id="vcomments"></div>
          <script>
            new Valine({
              el: '#vcomments',
              appId: 'Your appId',
              appKey: 'Your appKey'
            })
          </script>
        </body>

        有同學可能會有疑問了,appIdappKey 都直接寫在前端了,那豈不是誰都可以修改數據了?這就需要牽扯到 LeanCloud 的數據安全問題了,官方專門寫了篇文檔《數據和安全》 來說明這個問題。簡單的理解就是針對數據設置用戶的讀寫權限,確保正確的人對數據有且僅有正確的權限來保證數據的安全。

        乍聽一下,保證用戶數據只讀的話,感覺還是挺安全的??墒聦嵳娴娜绱嗣?,讓我們繼續來看看。

        ???♂? Valine 的問題

        ?? 閱讀統計篡改

        Valien 1.2.0 增加了文章閱讀統計的功能,用戶訪問頁面就會在后臺 Counter 表中根據 url 記錄訪問次數。由于每次訪問頁面都需要更新數據,所以在權限上必須設置成可寫,才能進行后續的字段更新。這樣就造成了一個問題,實際上該條數據是可以被更新成任意值的。感興趣的同學可以打開 https://valine.js.org/visitor... 官網頁面后進入控制臺輸入以下代碼試試。試完了記得把數改回去哈~

        const counter = new AV.Query('Counter');
        const resp = await counter.equalTo('url', '/visitor.html').find();
        resp[0].set('time', -100001).save();
        location.reload();

        可以看到該頁面的訪問統計被設置成了 -100000 了。這個問題唯一值得慶幸的是 time 字段的值是 Number 類型的,其它的值都無法插入。如果是字符串類型的話就是一個 XSS 漏洞了。

        該問題有一個解決辦法,就是不使用次數累加的存儲方式。更改為每次訪問都存儲一條只讀的訪問記錄,讀取的時候使用 count() 方法進行統計。這樣所有數據都是只讀的,就不存在篡改的問題了。這種解決方案唯一的問題就是數據量會比較大,對查詢會造成一定壓力。當然如果是在基于原數據不變的情況下,只能是增加一層服務端來做修改權限的隔離了。

        ?? XSS 安全

        從很早的版本開始就有用戶報告了 Valine 的 XSS 問題,社區也在使用各種方法在修復這些問題。包括增加驗證碼,前端XSS過濾等方式。不過后來作者才明白,前端的一切驗證都只能防君子,所以把驗證碼之類的限制去除了。

        現有的邏輯里,前端發布評論的時候會將 Markdown 轉換成 HTML 然后走一下前端的一個 XSS 過濾方法最后提交到 LeanCloud 中。從 LeanCloud 中拿到數據之后因為是 HTML 直接插入進行顯示即可。很明顯,這個流程是存在問題的。只要直接提交的是 HTML 而且拿到 HTML 之后直接進行展示的話,XSS 從根本上是無法根除的。

        那有沒有根本的解決辦法?其實是有的。針對存儲型的 XSS 攻擊,我們可以使用轉義編碼進行解決。只要效仿早前 BBCode 的做法,提交到數據庫的是 Markdown 內容。前端讀取到內容對所有 HTML 進行編碼后再進行 Markdown 轉換后展示。

        function encodeForHTML(str){
          return ('' + str)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')    
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#x27;')
            .replace(/\//g, '&#x2F;');
        };

        由于 Serverless 攻擊者是可以直達存儲階段,所以數據存儲之前的一切防范是無效的,只能在讀取展示過程處理。由于所有的 HTML 轉義后無法解析,Markdown 相當于我們根據自定義的語法解析成 HTML,保證轉換后的 HTML 沒有被插入的機會。

        不過這個方法存在一個問題,那就是對老數據存在不兼容。因為這相當于修改了存儲和展示的規則,而之前一直存儲的都是 HTML 內容,修復后之前的數據將無法展示 HTML 樣式。而為了能在存儲的還是 HTML 情況下規避 XSS 安全問題,唯一的辦法就是增加服務端中間層。存儲階段增加一道閥門,將轉義階段提前至存儲階段,保證新老數據的通用。

        ?? 隱私泄露

        說完了存儲的問題,我們再來看看讀取的問題。攻擊者除了可以直達存儲,也可以直達讀取,當一個數據庫的字段開放了讀取權限后,相當于該字段的內容對攻擊者是透明的。

        在評論數據中,有兩個字段是用戶比較敏感的數據,分別是 IP 和郵箱。燈大甚至專門寫了一篇文章來批判該問題 《請馬上停止使用Valine.js評論系統,除非它修復了用戶隱私泄露問題》。甚至掘金社區在早期使用 LeanCloud 的時候也暴出過泄露用戶手機號的安全問題。

        為了規避這個問題,Valine 作者增加了 recordIP 配置用來設置是否允許記錄用戶 IP。由于是 Serverless,目前能想到的也只是不存儲的方式解決了。不過該配置項會存在一個問題,就是該配置項的配置權在網站,隱私的問題是評論者遇到的,也就是說評論者是無權管理自己的隱私的。

        除了這個矛盾點之外,還有就是郵箱的問題。郵箱本質上只需要返回 md5 用來獲取 Gravatar 頭像即可。但是由于無服務端的限制,只能返回原始內容由前端計算。而郵箱我們又需要獲取到原始值,方便做評論回復郵件通知功能。所以我們也不能不存儲,或者存儲 md5 后的值。

        該問題的解決方案只能是增加一層服務端,通過服務端過濾敏感信息解決這個問題。

        ?? Waline!

        基于以上原因,我們發現只有增加一層服務端中間層才能很好的解決 Valine 的安全問題,所以 Waline 橫空出世了!Waline 與 Valine 最大的不同就是增加了服務端中間層,解決 Valine 暴露出來的安全問題。同時基于服務端的特性,提供了郵件通知、微信通知、評論后臺管理、LeanCloud, MySQL, MongoDB, SQLite, PostgreSQL 多存儲服務支持等諸多特性。不僅如此,Waline 默認使用 Vercel 部署,實現完全免費部署!

        Waline 最初的目標僅僅是為 Valine 增加上服務端中間層。但是由于作者不知為何從 1.4.0 版本開始只推送編譯后的文件到 Github 倉庫中,源文件停止更新。導致我只能連帶前端也實現一遍。當然前端的很多代碼和邏輯為了和 Valine 的配置保持一致都有參考 Valine,甚至在名字上,我也是從 Valine 上衍生的,讓大家能明白這個項目是 Valine 的衍生版。

        ?? 后記

        Serverless 的概念火了非常多年,但技術沒有銀彈,我們在看到它的優點的同時,也要正視它所帶來的問題。而 Serverless 自己可能也意識到了這個問題,從早期的無服務端慢慢轉向了無服務器,更偏向 BaaS 了。不過由于 Valine 沒有開放源代碼,所以上面說的一些問題和解決方法只能等待作者自己發現這件事了。

        查看原文

        贊 9 收藏 2 評論 0

        justjavac 贊了文章 · 2020-10-27

        React 架構的演變 - Hooks 的實現

        這是這個系列的最后一篇文章了,終于收尾了?? 。

        React Hooks 可以說完全顛覆了之前 Class Component 的寫法,進一步增強了狀態復用的能力,讓 Function Component 也具有了內部狀態,對于我個人來說,更加喜歡 Hooks 的寫法。當然如果你是一個使用 Class Component 的老手,初期上手時會覺得很苦惱,畢竟之前沉淀的很多 HOC、Render Props 組件基本沒法用。而且之前的 Function Component 是無副作用的無狀態組件,現在又能通過 Hooks 引入狀態,看起來真的很讓人疑惑。Function Component 的另一個優勢就是可以完全告別 this ,在 Class Component 里面 this 真的是一個讓人討厭的東西??。

        Hook 如何與組件關聯

        在之前的文章中多次提到,Fiber 架構下的 updateQueue、effectList 都是鏈表的數據結構,然后掛載的 Fiber 節點上。而一個函數組件內所有的 Hooks 也是通過鏈表的形式存儲的,最后掛載到 fiber.memoizedState 上。

        function App() {
          const [num, updateNum] = useState(0)
        
          return <div
            onClick={() => updateNum(num => num + 1)}
          >{ num }</div>
        }
        
        export default App

        我們先簡單看下,調用 useState 時,構造鏈表的過程:

        var workInProgressHook = null
        var HooksDispatcherOnMount = {
          useState: function (initialState) {
            return mountState(initialState)
          }
        }
        
        function function mountState(initialState) {
          // 新的 Hook 節點
          var hook = mountWorkInProgressHook()
          // 緩存初始值
          hook.memoizedState = initialState
          // 構造更新隊列,類似于 fiber.updateQueue
          var queue = hook.queue = {
            pending: null,
            dispatch: null,
            lastRenderedState: initialState
          }
          // 用于派發更新
          var dispatch = queue.dispatch = dispatchAction.bind(
            null, workInProgress, queue
          )
          // [num, updateNum] = useState(0)
          return [hook.memoizedState, dispatch]
        }
        
        function mountWorkInProgressHook() {
          var hook = {
            memoizedState: null,
            baseState: null,
            baseQueue: null,
            queue: null,
            next: null
          }
        
          if (workInProgressHook === null) {
            // 構造鏈表頭節點
            workInProgress.memoizedState = workInProgressHook = hook
          } else {
            // 如果鏈表已經存在,在掛載到 next
            workInProgressHook = workInProgressHook.next = hook
          }
        
          return workInProgressHook
        }

        Hook

        如果此時有兩個 Hook,第二個 Hook 就會掛載到第一個 Hook 的 next 屬性上。

        function App() {
          const [num, updateNum] = useState(0)
          const [str, updateStr] = useState('value: ')
        
          return <div
            onClick={() => updateNum(num => num + 1)}
          >{ str } { num }</div>
        }
        
        export default App

        Hook

        Hook 的更新隊列

        Hook 通過 .next 彼此相連,而每個 Hook 對象下,還有個 queue 字段,該字段和 Fiber 節點上的 updateQueue 一樣,是一個更新隊列在,上篇文章 《React 架構的演變-更新機制》中有講到,React Fiber 架構中,更新隊列通過鏈表結構進行存儲。

        class App extends React.Component {
          state = { val: 0 }
          click () {
            for (let i = 0; i < 3; i++) {
              this.setState({ val: this.state.val + 1 })
            }
          }
          render() {
            return <div onClick={() => {
              this.click()
            }}>val: { this.state.val }</div>
          }
        }

        點擊 div 之后,產生的 3 次 setState 通過鏈表的形式掛載到 fiber.updateQueue 上,待到 MessageChannel 收到通知后,真正執行更新操作時,取出更新隊列,將計算結果更新到 fiber.memoizedState 。

        setState

        hook.queue 的邏輯和 fiber.updateQueue 的邏輯也是完全一致的。

        function App() {
          const [num, updateNum] = useState(0)
        
          return <div
            onClick={() => {
              // 連續更新 3 次
              updateNum(num => num + 1)
              updateNum(num => num + 1)
              updateNum(num => num + 1)
            }}
          >
            { num }
          </div>
        }
        
        export default App;
        var dispatch = queue.dispatch = dispatchAction.bind(
          null, workInProgress, queue
        )
        // [num, updateNum] = useState(0)
        return [hook.memoizedState, dispatch]

        調用 useState 的時候,返回的數組第二個參數為 dispatch,而 dispatchdispatchAction bind 后得到。

        function dispatchAction(fiber, queue, action) {
          var update = {
            next: null,
            action: action,
            // 省略調度相關的參數...
          };
        
          var pending = queue.pending
          if (pending === null) {
            update.next = update
          } else {
            update.next = pending.next
            pending.next = update
          }
          queue.pending = update
        
          // 執行更新
          scheduleUpdateOnFiber()
        }

        可以看到這里構造鏈表的方式與 fiber.updateQueue 如出一轍。之前我們通過 updateNumnum 連續更新了 3 次,最后形成的更新隊列如下:

        更新隊列

        函數組件的更新

        前面的文章分享過,Fiber 架構下的更新流程分為遞(beginWork)、歸(completeWork)兩個步驟,在 beginWork 中,會依據組件類型進行 render 操作構造子組件。

        function beginWork(current, workInProgress) {
          switch (workInProgress.tag) {
            // 其他類型組件代碼省略...
            case FunctionComponent: {
              // 這里的 type 就是函數組件的函數
              // 例如,前面的 App 組件,type 就是 function App() {}
              var Component = workInProgress.type
              var resolvedProps = workInProgress.pendingProps
              // 組件更新
              return updateFunctionComponent(
                current, workInProgress, Component, resolvedProps
              )
            }
          }
        }
        
        function updateFunctionComponent(
            current, workInProgress, Component, nextProps
        ) {
          // 構造子組件
          var nextChildren = renderWithHooks(
            current, workInProgress, Component, nextProps
          )
          reconcileChildren(current, workInProgress, nextChildren)
          return workInProgress.child
        }
        

        看名字就能看出來,renderWithHooks 方法就是構造帶 Hooks 的子組件。

        function renderWithHooks(
            current, workInProgress, Component, props
        ) {
          if (current !== null && current.memoizedState !== null) {
            ReactCurrentDispatcher.current = HooksDispatcherOnUpdate
          } else {
            ReactCurrentDispatcher.current = HooksDispatcherOnMount
          }
          var children = Component(props)
          return children
        }

        從上面的代碼可以看出,函數組件更新或者首次渲染時,本質就是將函數取出執行了一遍。不同的地方在于給 ReactCurrentDispatcher 進行了不同的賦值,而 ReactCurrentDispatcher 的值最終會影響 useState 調用不同的方法。

        根據之前文章講過的雙緩存機制,current 存在的時候表示是更新操作,不存在的時候表示首次渲染。

        function useState(initialState) {
          // 首次渲染時指向 HooksDispatcherOnMount
          // 更新操作時指向 HooksDispatcherOnUpdate
          var dispatcher = ReactCurrentDispatcher.current
          return dispatcher.useState(initialState)
        }

        HooksDispatcherOnMount.useState 的代碼前面已經介紹過,這里不再著重介紹。

        // HooksDispatcherOnMount 的代碼前面已經介紹過
        var HooksDispatcherOnMount = {
          useState: function (initialState) {
            return mountState(initialState)
          }
        }

        我們重點看看 HooksDispatcherOnMount.useState 的邏輯。

        var HooksDispatcherOnUpdateInDEV = {
          useState: function (initialState) {
            return updateState()
          }
        }
        
        function updateState() {
          // 取出當前 hook
          workInProgressHook = nextWorkInProgressHook
          nextWorkInProgressHook = workInProgressHook.next
        
          var hook = nextWorkInProgressHook
          var queue = hook.queue
          var pendingQueue = queue.pending
        
          // 處理更新
          var first = pendingQueue.next
          var state = hook.memoizedState
          var update = first
        
          do {
            var action = update.action
            state = typeof action === 'function' ? action(state) : action
        
            update = update.next;
          } while (update !== null && update !== first)
        
        
          hook.memoizedState = state
        
          var dispatch = queue.dispatch
          return [hook.memoizedState, dispatch]
        }

        如果有看之前的 setState 的代碼,這里的邏輯其實是一樣的。將更新對象的 action 取出,如果是函數就執行,如果不是函數就直接對 state 進行替換操作。

        總結

        React 系列的文章終于寫完了,這一篇文章應該是最簡單的一篇,如果想拋開 React 源碼,單獨看 Hooks 實現可以看這篇文章:《React Hooks 原理》。Fiber 架構為了能夠實現循環的方式更新,將所有涉及到數據的地方結構都改成了鏈表,這樣的優勢就是可以隨時中斷,為異步模式讓路,Fiber 樹就像一顆圣誕樹,上面掛滿了各種彩燈(alternate、EffectList、updateQueue、Hooks)。

        推薦大家可以將這個系列從頭到尾看一遍,相信會特別有收獲的。

        image

        查看原文

        贊 12 收藏 8 評論 2

        justjavac 贊了文章 · 2020-10-23

        jvm類加載器,類加載機制詳解,看這一篇就夠了

        前言

        今天我們來講講jvm類加載的過程,我們寫了那么多類,卻不知道類的加載過程,豈不是很尷尬。

        jvm的啟動是通過引導類加載器(bootstrap class loader)創建一個初始類(initial class)來完成的,這個類是由jvm的具體實現指定的。[來自官方規范]

        jvm組成結構之一就是類裝載器子系統,我們今天就來仔細講講這個組件。

        Java代碼執行流程圖

        大家通過這個流程圖,了解一下我們寫好的Java代碼是如何執行的,其中要經歷類加載器這個流程,我們就來仔細講講這里面的知識點。

        image.png

        類加載子系統

        image.png

        類加載系統架構圖

        暫時看不懂這兩張圖沒關系,跟著老哥往下看

        image.png

        類的生命周期

        類的生命周期包括:加載、鏈接、初始化、使用和卸載,其中加載、鏈接、初始化,屬于類加載的過程,我們下面仔細講解。使用是指我們new對象進行使用,卸載指對象被垃圾回收掉了。

        類加載的過程

        image.png

        • 第一步:Loading加載
        通過類的全限定名(包名 + 類名),獲取到該類的.class文件的二進制字節流

        將二進制字節流所代表的靜態存儲結構,轉化為方法區運行時的數據結構

        內存中生成一個代表該類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口

        總結:加載二進制數據到內存 —> 映射成jvm能識別的結構 —> 在內存中生成class文件。

        • 第二步:Linking鏈接

        鏈接是指將上面創建好的class類合并至Java虛擬機中,使之能夠執行的過程,可分為驗證、準備、解析三個階段。

        ① 驗證(Verify)

        確保class文件中的字節流包含的信息,符合當前虛擬機的要求,保證這個被加載的class類的正確性,不會危害到虛擬機的安全。

        ② 準備(Prepare)

        為類中的靜態字段分配內存,并設置默認的初始值,比如int類型初始值是0。被final修飾的static字段不會設置,因為final在編譯的時候就分配了

        ③ 解析(Resolve)

        解析階段的目的,是將常量池內的符號引用轉換為直接引用的過程(將常量池內的符號引用解析成為實際引用)。如果符號引用指向一個未被加載的類,或者未被加載類的字段或方法,那么解析將觸發這個類的加載(但未必觸發這個類的鏈接以及初始化。)

        事實上,解析器操作往往會伴隨著 JVM 在執行完初始化之后再執行。 符號引用就是一組符號來描述所引用的目標。符號引用的字面量形式明確定義在《Java 虛擬機規范》的Class文件格式中。直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。

        解析動作主要針對類、接口、字段、類方法、接口方法、方法類型等。對應常量池中的 CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。

        • 第三步:initialization初始化
        初始化就是執行類的構造器方法init()的過程。

        這個方法不需要定義,是javac編譯器自動收集類中所有類變量的賦值動作和靜態代碼塊中的語句合并來的。

        若該類具有父類,jvm會保證父類的init先執行,然后在執行子類的init。

        類加載器的分類

        image.png

        • 第一個:啟動類/引導類:Bootstrap ClassLoader
        這個類加載器使用C/C++語言實現的,嵌套在JVM內部,java程序無法直接操作這個類。

        它用來加載Java核心類庫,如:JAVA_HOME/jre/lib/rt.jar、resources.jar、sun.boot.class.path路徑下的包,用于提供jvm運行所需的包。

        并不是繼承自java.lang.ClassLoader,它沒有父類加載器

        它加載擴展類加載器應用程序類加載器,并成為他們的父類加載器

        出于安全考慮,啟動類只加載包名為:java、javax、sun開頭的類

        • 第二個:擴展類加載器:Extension ClassLoader
        Java語言編寫,由sun.misc.Launcher$ExtClassLoader實現,我們可以用Java程序操作這個加載器

        派生繼承自java.lang.ClassLoader,父類加載器為啟動類加載器

        從系統屬性:java.ext.dirs目錄中加載類庫,或者從JDK安裝目錄:jre/lib/ext目錄下加載類庫。我們就可以將我們自己的包放在以上目錄下,就會自動加載進來了。

        • 第三個:應用程序類加載器:Application Classloader
        Java語言編寫,由sun.misc.Launcher$AppClassLoader實現。

        派生繼承自java.lang.ClassLoader,父類加載器為啟動類加載器

        它負責加載環境變量classpath或者系統屬性java.class.path指定路徑下的類庫

        它是程序中默認的類加載器,我們Java程序中的類,都是由它加載完成的。

        我們可以通過ClassLoader#getSystemClassLoader()獲取并操作這個加載器

        • 第四個:自定義加載器
        一般情況下,以上3種加載器能滿足我們日常的開發工作,不滿足時,我們還可以自定義加載器

        比如用網絡加載Java類,為了保證傳輸中的安全性,采用了加密操作,那么以上3種加載器就無法加載這個類,這時候就需要自定義加載器

        自定義加載器實現步驟

        繼承java.lang.ClassLoader類,重寫findClass()方法

        如果沒有太復雜的需求,可以直接繼承URLClassLoader類,重寫loadClass方法,具體可參考AppClassLoaderExtClassLoader。

        獲取ClassLoader幾種方式

        它是一個抽象類,其后所有的類加載器繼承自 ClassLoader(不包括啟動類加載器)

        //?方式一:獲取當前類的?ClassLoader
        clazz.getClassLoader()
        //?方式二:獲取當前線程上下文的?ClassLoader
        Thread.currentThread().getContextClassLoader()
        //?方式三:獲取系統的?ClassLoader
        ClassLoader.getSystemClassLoader()
        //?方式四:獲取調用者的?ClassLoader
        DriverManager.getCallerClassLoader()

        類加載機制—雙親委派機制

        jvm對class文件采用的是按需加載的方式,當需要使用該類時,jvm才會將它的class文件加載到內存中產生class對象。

        在加載類的時候,是采用的雙親委派機制,即把請求交給父類處理的一種任務委派模式。

        image.png

        • 工作原理

        (1)如果一個類加載器接收到了類加載的請求,它自己不會先去加載,會把這個請求委托給父類加載器去執行。

        (2)如果父類還存在父類加載器,則繼續向上委托,一直委托到啟動類加載器:Bootstrap ClassLoader

        (3)如果父類加載器可以完成加載任務,就返回成功結果,如果父類加載失敗,就由子類自己去嘗試加載,如果子類加載失敗就會拋出ClassNotFoundException異常,這就是雙親委派模式

        • 第三方包加載方式:反向委派機制

        在Java應用中存在著很多服務提供者接口(Service Provider Interface,SPI),這些接口允許第三方為它們提供實現,如常見的 SPI 有 JDBC、JNDI等,這些 SPI 的接口屬于 Java 核心庫,一般存在rt.jar包中,由Bootstrap類加載器加載。而Bootstrap類加載器無法直接加載SPI的實現類,同時由于雙親委派模式的存在,Bootstrap類加載器也無法反向委托AppClassLoader加載器SPI的實現類。在這種情況下,我們就需要一種特殊的類加載器來加載第三方的類庫,而線程上下文類加載器(雙親委派模型的破壞者)就是很好的選擇。

        從圖可知rt.jar核心包是有Bootstrap類加載器加載的,其內包含SPI核心接口類,由于SPI中的類經常需要調用外部實現類的方法,而jdbc.jar包含外部實現類(jdbc.jar存在于classpath路徑)無法通過Bootstrap類加載器加載,因此只能委派線程上下文類加載器把jdbc.jar中的實現類加載到內存以便SPI相關類使用。顯然這種線程上下文類加載器的加載方式破壞了“雙親委派模型”,它在執行過程中拋棄雙親委派加載鏈模式,使程序可以逆向使用類加載器,當然這也使得Java類加載器變得更加靈活。

        image.png

        • 沙箱安全機制

        自定義 String 類,但是在加載自定義 String 類的時候會率先使用引導類加載器加載,而引導類加載器在加載的過程中會先加載 JDK 自帶的文件(rt.jar 包中的 javalangString.class),報錯信息說沒有 main 方法就是因為加載的 rt.jar 包中的 String 類。這樣可以保證對 Java 核心源代碼的保護,這就是沙箱安全機制。

        IT 老哥

        我是一個通過大學四年自學,進入大廠做高級java開

        發的程序員。關注我,每天分享技術干貨,助你進大

        廠。我可以,你也可以的。

        如果你覺得文章寫的還不錯,請幫老哥點個贊!
        查看原文

        贊 11 收藏 9 評論 0

        justjavac 回答了問題 · 2020-10-03

        有什么用vuePress做的,比較好的知識庫嗎?

        維基百科就是開源的 mediawiki

        關注 2 回答 3

        justjavac 發布了文章 · 2020-09-15

        鴻蒙系統中的 JS 開發框架

        今天鴻蒙終于發布了,開發者們也終于“沸騰”了。

        源碼托管在國內知名開源平臺碼云上,https://gitee.com/openharmony

        我也第一時間下載了源碼,研究了一個晚上,順帶寫了一個 hello world 程序,還順手給鴻蒙文檔提了 2 個 PR。

        當然我最感興趣的就是鴻蒙的 JS 框架 ace_lite_jsfwk,從名字中可以看出來這是一個非常輕量級的框架,官方介紹說是“輕量級 JS 核心開發框架”。

        當我看完源碼后發現它確實輕。其核心代碼只有 5 個 js 文件,大概也就 300-400 行代碼吧。(沒有單元測試)

        • runtime-coresrccoreindex.js
        • runtime-coresrcobserverobserver.js
        • runtime-coresrcobserversubject.js
        • runtime-coresrcobserverutils.js
        • runtime-coresrcprofilerindex.js

        從名字可以看出來,這些代碼實現了一個觀察者模式。也就是說,它實現了一個非常輕量級的 MVVM 模式。通過使用和 vue2 相似的屬性劫持技術實現了響應式系統。這個應該是目前培訓班的“三大自己實現”之一了吧。(自己實現 Promise,自己實現 vue,自己實現 react)

        utils 里面定義了一個 Observer 棧,存放了觀察者。subject 定義了被觀察者。當我們觀察某個對象時,也就是劫持這個對象屬性的操作,還包括一些數組函數,比如 push、pop 等。這個文件應該是代碼最多的,160 行。observer 的代碼就更簡單了,五六十行。

        而當我們開發的時候,通過 Toolkit 將開發者編寫的 HML、CSS 和 JS 文件編譯打包成 JS Bundle,然后再將 JS Bundle 解析運行成C++ native UI 的 View 組件進行渲染。

        “通過支持三方開發者使用聲明式的 API 進行應用開發,以數據驅動視圖變化,避免了大量的視圖操作,大大降低了應用開發難度,提升開發者開發體驗”?;旧暇褪且粋€小程序式的開發體驗。

        在 srccorebaseframework_min_js.h 文件中,這段編譯好的 js 被編譯到了 runtime 里面。編譯完的 js 文件不到 3K,確實夠輕量。

        js runtime 沒有使用 V8,也沒有使用 jscore。而是選擇了 JerryScript。JerryScript 是用于物聯網的超輕量 JavaScript 引擎。它能夠在內存少于 64 KB 的設備上執行 ECMAScript 5.1 源代碼。這也是為什么在文檔中說鴻蒙 JS 框架支持 ECMAScript 5.1 的原因。

        從整體看這個 js 框架大概使用了 96% 的 C/C++ 代碼,1.8% 的 JS 代碼。在 htm 文件中寫的組件會被編譯為原生組件。而 app_style_manager.cpp 和同級的七八個文件則用來解析 css,最終生成原生布局。

        雖然在 SDK 中有幾個 weex 包,也發現了 react 的影子。但是在 C/C++ 代碼中并沒有看到 yoga 相關的內容(全局搜索沒發現)。而 SDK 中的那些包僅僅是做 loader 用的,大概是為了在 webpack 打包時解析 htm 組件用的。將 htm 的 template 編譯為 js 代碼。

        整體而言,比我預想的要好一些。

        查看原文

        贊 16 收藏 7 評論 3

        justjavac 發布了文章 · 2020-09-15

        逐行分析鴻蒙系統的 JavaScript 框架

        我在前文中曾經介紹過鴻蒙的 Javascript 框架,這幾天終于把 JS 倉庫編譯通過了,期間踩了不少坑,也給鴻蒙貢獻了幾個 PR。今天我們就來逐行分析鴻蒙系統中的 JS 框架。

        文中的所有代碼都基于鴻蒙的當前最新版(版本為 677ed06,提交日期為 2020-09-10)。

        鴻蒙系統使用 JavaScript 開發 GUI 是一種類似于微信小程序、輕應用的模式。而這個 MVVM 模式中,V 其實是由 C++ 來承擔的。JavaScript 代碼只是其中的 ViewModel 層。

        鴻蒙 JS 框架是零依賴的,只在開發打包過程中使用到了一些 npm 包。打包完之的代碼是沒有依賴任何 npm 包的。我們先看一下使用鴻蒙 JS 框架寫出來的 JS 代碼到底長什么樣。

        export default {
          data() {
            return { count: 1 };
          },
          increase() {
            ++this.count;
          },
          decrease() {
            --this.count;
          },
        }

        如果我不告訴你這是鴻蒙,你甚至會以為它是 vue 或小程序。如果單獨把 JS 拿出來使用(脫離鴻蒙系統),代碼是這樣:

        const vm = new ViewModel({
          data() {
            return { count: 1 };
          },
          increase() {
            ++this.count;
          },
          decrease() {
            --this.count;
          },
        });
        
        console.log(vm.count); // 1
        
        vm.increase();
        console.log(vm.count); // 2
        
        vm.decrease();
        console.log(vm.count); // 1

        倉庫中的所有 JS 代碼實現了一個響應式系統,充當了 MVVM 中的 ViewModel。

        下面我們逐行分析。

        src 目錄中一共有 4 個目錄,總計 8 個文件。其中 1 個是單元測試。還有 1 個性能分析。再除去 2 個 index.js 文件,有用的文件一共是 4 個。也是本文分析的重點。

        src
        ├── __test__
        │   └── index.test.js
        ├── core
        │   └── index.js
        ├── index.js
        ├── observer
        │   ├── index.js
        │   ├── observer.js
        │   ├── subject.js
        │   └── utils.js
        └── profiler
            └── index.js

        首先是入口文件,src/index.js,只有 2 行代碼:

        import { ViewModel } from './core';
        export default ViewModel;

        其實就是重新導出。

        另一個類似的文件是 src/observer/index.js,也是 2 行代碼:

        export { Observer } from './observer';
        export { Subject } from './subject';

        observer 和 subject 實現了一個觀察者模式。subject 是主題,也就是被觀察者。observer 是觀察者。當 subject 有任何變化時需要主動通知被觀察者。這就是響應式。

        這 2 個文件都使用到了 src/observer/utils.js,所以我們先分析一下 utils 文件。分 3 部分。

        第一部分

        export const ObserverStack = {
          stack: [],
          push(observer) {
            this.stack.push(observer);
          },
          pop() {
            return this.stack.pop();
          },
          top() {
            return this.stack[this.stack.length - 1];
          }
        };

        首先是定義了一個用來存放觀察者的棧,遵循后進先出的原則,內部使用 stack 數組來存儲。

        • 入棧操作 push,和數組的 push 函數一樣,在棧頂放入一個觀察者 observer。
        • 出棧操作 pop,和數組的 pop 函數一樣,在將棧頂的觀察者刪除,并返回這個被刪除的觀察者。
        • 取棧頂元素 top,和 pop 操作不同,top 是把棧頂元素取出來,但是并不刪除。

        第二部分

        export const SYMBOL_OBSERVABLE = '__ob__';
        export const canObserve = target => typeof target === 'object';

        定義了一個字符串常量 SYMBOL_OBSERVABLE。為了后面用著方便。

        定義了一個函數 canObserve,目標是否可以被觀察。只有對象才能被觀察,所以使用 typeof 來判斷目標的類型。等等,好像有什么不對。如果 targetnull 的話,函數也會返回 true。如果 null 不可觀察,那么這就是一個 bug。(寫這篇文章的時候我已經提了一個 PR,并詢問了這種行為是否是期望的行為)。

        第三部分

        export const defineProp = (target, key, value) => {
          Object.defineProperty(target, key, { enumerable: false, value });
        };

        這個沒有什么好解釋的,就是 Object.defineProperty 代碼太長了,定義一個函數來避免代碼重復。

        下面再來分析觀察者 src/observer/observer.js,分 4 部分。

        第一部分

        export function Observer(context, getter, callback, meta) {
          this._ctx = context;
          this._getter = getter;
          this._fn = callback;
          this._meta = meta;
          this._lastValue = this._get();
        }

        構造函數。接受 4 個參數。

        context 當前觀察者所處的上下午,類型是 ViewModel。當第三個參數 callback 調用時,函數的 this 就是這個 context。

        getter 類型是一個函數,用來獲取某個屬性的值。

        callback 類型是一個函數,當某個值變化后執行的回調函數。

        meta 元數據。觀察者(Observer)并不關注 meta 元數據。

        在構造函數的最后一行,this._lastValue = this._get()。下面來分析 _get 函數。

        第二部分

        Observer.prototype._get = function() {
          try {
            ObserverStack.push(this);
            return this._getter.call(this._ctx);
          } finally {
            ObserverStack.pop();
          }
        };

        ObserverStack 就是上面分析過的用來存儲所有觀察者的棧。將當前觀察者入棧,并通過 _getter 取得當前值。結合第一部分的構造函數,這個值存儲在了 _lastValue 屬性中。

        執行完這個過程后,這個觀察者就已經初始化完成了。

        第三部分

        Observer.prototype.update = function() {
          const lastValue = this._lastValue;
          const nextValue = this._get();
          const context = this._ctx;
          const meta = this._meta;
        
          if (nextValue !== lastValue || canObserve(nextValue)) {
            this._fn.call(context, nextValue, lastValue, meta);
            this._lastValue = nextValue;
          }
        };

        這部分實現了數據更新時的臟檢查(Dirty checking)機制。比較更新后的值和當前值,如果不同,那么就執行回調函數。如果這個回調函數是渲染 UI,那么則可以實現按需渲染。如果值相同,那么再檢查設置的新值是否可以被觀察,再決定到底要不要執行回調函數。

        第四部分

        Observer.prototype.subscribe = function(subject, key) {
          const detach = subject.attach(key, this);
          if (typeof detach !== 'function') {
            return;
          }
          if (!this._detaches) {
            this._detaches = [];
          }
          this._detaches.push(detach);
        };
        
        Observer.prototype.unsubscribe = function() {
          const detaches = this._detaches;
          if (!detaches) {
            return;
          }
          while (detaches.length) {
            detaches.pop()();
          }
        };

        訂閱與取消訂閱。

        我們前面經常說觀察者和被觀察者。對于觀察者模式其實還有另一種說法,叫訂閱/發布模式。而這部分代碼則實現了對主題(subject)的訂閱。

        先調用主題的 attach 方法進行訂閱。如果訂閱成功,subject.attach 方法會返回一個函數,當調用這個函數就會取消訂閱。為了將來能夠取消訂閱,這個返回值必需保存起來。

        subject 的實現很多人應該已經猜到了。觀察者訂閱了 subject,那么 subject 需要做的就是,當數據變化時即使通知觀察者。subject 如何知道數據發生了變化呢,機制和 vue2 一樣,使用 Object.defineProperty 做屬性劫持。

        下面再來分析觀察者 src/observer/subject.js,分 7 部分。

        第一部分

        export function Subject(target) {
          const subject = this;
          subject._hijacking = true;
          defineProp(target, SYMBOL_OBSERVABLE, subject);
        
          if (Array.isArray(target)) {
            hijackArray(target);
          }
        
          Object.keys(target).forEach(key => hijack(target, key, target[key]));
        }

        構造函數?;緵]什么難點。設置 _hijacking 屬性為 true,用來標示這個對象已經被劫持了。Object.keys 通過遍歷來劫持每個屬性。如果是數組,則調用 hijackArray。

        第二部分

        兩個靜態方法。

        Subject.of = function(target) {
          if (!target || !canObserve(target)) {
            return target;
          }
          if (target[SYMBOL_OBSERVABLE]) {
            return target[SYMBOL_OBSERVABLE];
          }
          return new Subject(target);
        };
        
        Subject.is = function(target) {
          return target && target._hijacking;
        };

        Subject 的構造函數并不直接被外部調用,而是封裝到了 Subject.of 靜態方法中。

        如果目標不能被觀察,那么直接返回目標。

        如果 target[SYMBOL_OBSERVABLE] 不是 undefined,說明目標已經被初始化過了。

        否則,調用構造函數初始化 Subject。

        Subject.is 則用來判斷目標是否被劫持過了。

        第三部分

        Subject.prototype.attach = function(key, observer) {
          if (typeof key === 'undefined' || !observer) {
            return;
          }
          if (!this._obsMap) {
            this._obsMap = {};
          }
          if (!this._obsMap[key]) {
            this._obsMap[key] = [];
          }
          const observers = this._obsMap[key];
          if (observers.indexOf(observer) < 0) {
            observers.push(observer);
            return function() {
              observers.splice(observers.indexOf(observer), 1);
            };
          }
        };

        這個方法很眼熟,對,就是上文的 Observer.prototype.subscribe 中調用的。作用是某個觀察者用來訂閱主題。而這個方法則是“主題是怎么訂閱的”。

        觀察者維護這一個主題的哈希表 _obsMap。哈希表的 key 是需要訂閱的 key。比如某個觀察者訂閱了 name 屬性的變化,而另一個觀察者訂閱了 age 屬性的變化。而且屬性的變化還可以被多個觀察者同時訂閱,因此哈希表存儲的值是一個數組,數據的每個元素都是一個觀察者。

        第四部分

        Subject.prototype.notify = function(key) {
          if (
            typeof key === 'undefined' ||
            !this._obsMap ||
            !this._obsMap[key]
          ) {
            return;
          }
          this._obsMap[key].forEach(observer => observer.update());
        };

        當屬性發生變化是,通知訂閱了此屬性的觀察者們。遍歷每個觀察者,并調用觀察者的 update 方法。我們上文中也提到了,臟檢查就是在這個方法內完成的。

        第五部分

        Subject.prototype.setParent = function(parent, key) {
          this._parent = parent;
          this._key = key;
        };
        
        Subject.prototype.notifyParent = function() {
          this._parent && this._parent.notify(this._key);
        };

        這部分是用來處理屬性嵌套(nested object)的問題的。就是類似這種對象:{ user: { name: 'JJC' } }。

        第六部分

        function hijack(target, key, cache) {
          const subject = target[SYMBOL_OBSERVABLE];
        
          Object.defineProperty(target, key, {
            enumerable: true,
            get() {
              const observer = ObserverStack.top();
              if (observer) {
                observer.subscribe(subject, key);
              }
        
              const subSubject = Subject.of(cache);
              if (Subject.is(subSubject)) {
                subSubject.setParent(subject, key);
              }
        
              return cache;
            },
            set(value) {
              cache = value;
              subject.notify(key);
            }
          });
        }

        這一部分展示了如何使用 Object.defineProperty 進行屬性劫持。當設置屬性時,會調用 set(value),設置新的值,然后調用 subject 的 notify 方法。這里并不進行任何檢查,只要設置了屬性就會調用,即使屬性的新值和舊值一樣。notify 會通知所有的觀察者。

        第七部分

        劫持數組方法。

        const ObservedMethods = {
          PUSH: 'push',
          POP: 'pop',
          UNSHIFT: 'unshift',
          SHIFT: 'shift',
          SPLICE: 'splice',
          REVERSE: 'reverse'
        };
        
        const OBSERVED_METHODS = Object.keys(ObservedMethods).map(
            key => ObservedMethods[key]
        );

        ObservedMethods 定義了需要劫持的數組函數。前面大寫的用來做 key,后面小寫的是需要劫持的方法。

        function hijackArray(target) {
          OBSERVED_METHODS.forEach(key => {
            const originalMethod = target[key];
        
            defineProp(target, key, function() {
              const args = Array.prototype.slice.call(arguments);
              originalMethod.apply(this, args);
        
              let inserted;
              if (ObservedMethods.PUSH === key || ObservedMethods.UNSHIFT === key) {
                inserted = args;
              } else if (ObservedMethods.SPLICE) {
                inserted = args.slice(2);
              }
        
              if (inserted && inserted.length) {
                inserted.forEach(Subject.of);
              }
        
              const subject = target[SYMBOL_OBSERVABLE];
              if (subject) {
                subject.notifyParent();
              }
            });
          });
        }

        數組的劫持和對象不同,不能使用 Object.defineProperty。

        我們需要劫持 6 個數組方法。分別是頭部添加、頭部刪除、尾部添加、尾部刪除、替換/刪除某幾項、數組反轉。

        通過重寫數組方法實現了數組的劫持。但是這里有一個需要注意的地方,數據的每一個元素都是被觀察過的,但是當在數組中添加了新元素時,這些元素還沒有被觀察。因此代碼中還需要判斷當前的方法如果是 push、unshift、splice,那么需要將新的元素放入觀察者隊列中。

        另外兩個文件分別是單元測試和性能分析,這里就不再分析了。

        查看原文

        贊 28 收藏 12 評論 9

        認證與成就

        • SegmentFault 講師
        • 獲得 5510 次點贊
        • 獲得 131 枚徽章 獲得 5 枚金徽章, 獲得 39 枚銀徽章, 獲得 87 枚銅徽章

        擅長技能
        編輯

        開源項目 & 著作
        編輯

        注冊于 2012-03-07
        個人主頁被 62.8k 人瀏覽

        一本到在线是免费观看_亚洲2020天天堂在线观看_国产欧美亚洲精品第一页_最好看的2018中文字幕