<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>

        fenbox

        fenbox 查看完整檔案

        杭州編輯  |  填寫畢業院校SegmentFault  |  社稷師 編輯 hellowiki.com 編輯
        編輯

        主業設計,副業寫代碼

                  =,    (\_/)    ,=
                   /`-'--(")--'-'\
              woo /     (___)     \
                 /.-.-./ " " \.-.-.\

        08 年參與 Typecho 開源博客項目
        11 年參與 SegmentFault 開發者問答項目
        12 年創業,與 joyqi、sunny 駐扎蓮花街

        個人動態

        fenbox 關注了用戶 · 3月17日

        邊城 @jamesfancy

        從事軟件開發 20 年,在軟件分析、設計、架構、開發及軟件開發技術研究和培訓等方面有著非常豐富的經驗,近年主要在研究 Web 前端技術、基于 .NET 的后端開發技術和相關軟件架構。

        關注 11060

        fenbox 回答了問題 · 3月16日

        思否的私信功能使用有什么限制嗎?

        我們調整了私信權限,你現在應該是有權限的。

        關注 2 回答 1

        fenbox 贊了回答 · 3月16日

        解決vue項目如何讓網頁端瀏覽器緩存自動更新?

        首頁(即index.html)使用 meta no-cache 標簽來禁用緩存,就可以了。

        關注 3 回答 2

        fenbox 贊了文章 · 3月13日

        一道面試題讓你更加了解事件隊列

        今天在群里聊天,突然有人放出了一道面試題。經過群里一番討論,最終解題思路慢慢完善起來,我這里就整理一下群內解題的思路。

        該題定義了一個同步函數對傳入的數組進行遍歷乘二操作,同時每執行一次就會給 executeCount 累加。最終我們需要實現一個 batcher 函數,使用其對該同步函數包裝后,實現每次調用依舊返回預期的二倍結果,同時還需要保證 executeCount 執行次數為1。

        let executeCount = 0
        const fn = nums => {
          executeCount++
          return nums.map(x => x * 2)
        }
        
        const batcher = f => {
          // todo 實現 batcher 函數
        }
        
        const batchedFn = batcher(fn);
        
        const main = async () => {
          const [r1, r2, r3] = await Promise.all([
            batchedFn([1,2,3]),
            batchedFn([4,5]),
            batchedFn([7,8,9])
          ]);
        
          //滿足以下 test case
          assert(r1).tobe([2, 4, 6])
          assert(r2).tobe([8, 10])
          assert(r3).tobe([14, 16, 18])
          assert(executeCount).tobe(1)
        }

        抖機靈解法

        拿到題目的第一時間,我就想到了抖機靈的方法。直接面向用例編程,執行完之后重置下 executeCount 就好了。

        const batcher = f => {
          return nums => {
            try { return f(nums) } finally { executeCount = 1 }
          }
        }

        當然除非你不在乎這次面試,否則一般不建議你用這種抖機靈的方法回答面試官(不要問我為什么知道)。由于 executeCount 的值和 fn() 函數的調用次數呈正相關,所以這道理也就換成了我們需要實現 batcher() 方法返回新的包裝函數,該函數會被調用多次,但最終只會執行一次 fn() 函數。

        setTimeout 解法

        由于題干中使用了 Promise.all(),我們自然而然想到使用異步去解決。也就是每次調用的時候會把所以的傳參存下來,直到最后的時候再執行 fn() 返回對應的結果。問題在于什么時候觸發開始執行呢?自然而然我們想到了類似 debounce 的方式使用 setTimeout 增加延遲時間。

        const batcher = f => {
          let nums = [];
          const p = new Promise(resolve => setTimeout(_ => resolve(f(nums)), 100));
        
          return arr => {
            let start = nums.length;
            nums = nums.concat(arr);
            let end = nums.length;
            return p.then(ret => ret.slice(start, end));
          };
        };

        這里的難點在于預先定義了一個 Promise 在 100ms 之后才會 resolve。返回的函數本質只是將參數推入到 nums 數組中,待 100ms 后觸發 resolve 返回統一執行 fn() 后的結果并獲取對應于當前調用的結果片段。

        后來有群友反饋,實際上不用定義 100ms 直接 0ms 也是可以的。由于 setTimeout 是在 UI 渲染結束之后才會執行的宏任務,所以理論上來說 setTimeout() 的最小間隔值無法設置為 0。它的最小值和瀏覽器的刷新頻率有關系,根據 MDN 描述,它的最小值一般為 4ms。所以理論上它設置 0ms 和 100ms 效果是差不多的,都類似于 debounce 的效果。

        Promise 解法

        那么如何能實現延遲 0ms 執行呢?我們知道除了宏任務之外 JS 還有微任務,微任務隊列是在 JS 主線程執行完成之后立即執行的事件隊列。Promise 的回調就會存儲在微任務隊列中。于是我們將 setTimeout 修改成了 Promise.resolve(),最終發現也是可以實現同樣的效果。

        const batcher = f => {
          let nums = [];
          const p = Promise.resolve().then(_ => f(nums));
        
          return arr => {
            let start = nums.length;
            nums = nums.concat(arr);
            let end = nums.length;
            return p.then(ret => ret.slice(start, end));
          };
        };

        由于 Promise 的微任務隊列效果將 _ => f(nums) 推入微任務隊列,待主線程的三次 batcherFn() 調用都執行完成之后才會執行。之后 p 的狀態變為 fulfilled 后繼續完成最終 slice 的操作。

        后記

        最終分析下來,其實這道理的本質就是要通過某些方法將 fn() 函數的執行后置到主線程執行完畢,至于是使用宏任務還是微任務隊列,就看具體的需求了。除了 setTimeout() 之外,還有 setInterval(), requestAnimationFrame() 都是宏任務隊列。而微任務隊列里除了有 Promise 之外,還有 MutationObserver。關于宏任務和微任務隊列相關的,感興趣的可以看看《微任務、宏任務與Event-Loop》這篇文章。

        查看原文

        贊 1 收藏 0 評論 0

        fenbox 回答了問題 · 3月12日

        關于自建標簽展現的問題

        優先展示熱門標簽,非熱門標簽的可以通過搜索查到。

        關注 2 回答 1

        fenbox 回答了問題 · 3月12日

        解決換綁郵箱一直收不到郵件

        你好,服務商問題。
        已經解決,再試試。

        關注 3 回答 2

        fenbox 關注了用戶 · 3月4日

        CrazyCodes @crazycodes

        I am CrazyCodes,生命不息,編碼不止。

        GitHub : CrazyCodes

        CSDN : CrazyCodes

        掘金 : GraceDevelopment

        不止于技術的微信公眾號 : phpznb

        與我一起傳遞技術正能量!

        關注 4739

        fenbox 關注了用戶 · 3月3日

        xialeistudio @xialeistudio

        公眾號:Nodejs之路

        關注 4922

        fenbox 贊了文章 · 3月3日

        有道寫作瀏覽器擴展實踐

        有道寫作瀏覽器擴展作為一款為網頁增加英文語法批改的輔助工具,允許用戶在任意網頁上絕大部分的富文本編輯器、多行文本輸入框中編輯英文文本,可實時得到批改結果反饋,并自行接受建議自動修改,實現完美寫作。
        來源/ 有道技術團隊公眾號
        作者/ 李靖雯
        編輯/ 劉振宇

        一、背景介紹

        有道寫作服務是有道出品的寫作智能批改產品,為用戶提供優質的作文拼寫、語法、樣式方面的批改服務。有道寫作不僅僅支持瀏覽器擴展形式,還支持在其他平臺使用:例如有道詞典 APP-作文批改、Web 在線端、Word 插件、PC 詞典內。歡迎各位體驗。

        http://write.youdao.com/

        瀏覽器插件在瀏覽器里面的稱呼是 Browser Extension,也就是瀏覽器擴展,是一個擴展網頁瀏覽器功能的插件。它主要基于 HTML、JavaScript、CSS 開發,同時由于是擴展特性,可以利用瀏覽器本身提供的底層 API 接口進行開發,可以給所用頁面或者特定頁面添加一些特殊功能而不影響原本頁面邏輯。

        每個支持擴展的瀏覽器有自己下載擴展的應用商店,可以直接在應用商店下載。有些產品自己提供瀏覽器擴展的 .crx 文件讓用戶下載并安裝。

        二、適配瀏覽器

        有道寫作在 Windows/Mac 系統都可安裝,適配 Chrome、360安全瀏覽器、360極速瀏覽器、Edge 新版瀏覽器等,在以上瀏覽器商店中搜索有道寫作,點擊安裝按鈕即可。

        三、功能介紹&效果展示

        在介紹開發思路與實踐之前,我們先來直觀地看一下有道寫作瀏覽器擴展的實際效果,并對其功能進行簡單的介紹。

        3.1 表現方式

        視覺效果就是,給錯誤的文本字符下面畫一條橫線,在 hover 的時候,可以給文本增加一個高亮的效果。在選接受建議的時候,可以替換成我們想要的文本數據。

        image

        3.2 適用場景

        >>> 在線郵件編輯:

        163郵箱

        Outlook 郵箱

        Gmail

        >>> 社交動態、評論:

        Facebook

        微博動態

        評論

        >>> 工具、筆記類:

        有道翻譯

        Google 翻譯

        石墨文檔

        3.3 功能介紹

        >>> 實時批改:

        支持一邊修改一邊實時提供批改反饋,展示批改錯誤數量。

        >>> 語法檢測:

        image

        >>> 增強編輯框:

        可以查看每一個錯誤反饋詳細內容,并可分錯誤類型過濾查看結果。

        >>> 接受建議:

        點擊接受建議時候替換正確文本。

        image

        四、開發思路

        需求:擴展需要針對頁面上的可輸入文本的編輯框賦予批改的功能

        4.1 適配編輯器

        那么,網頁中可輸入文本的編輯框都有哪些呢?

        通常我們常見可輸入編輯框有:

        • 基于 Web 的表單可以輸入文本控件:input、textarea
        <input value="123"/>
        <textarea>123456</textarea>
        • 可編輯屬性的元素:contenteditable
        <blockquote contenteditable="true">
            <p>Edit this content to add your own quote</p>
        </blockquote> 

        Input 元素通常是一行且輸入范圍較短的內容,考慮到批改交互的功能,我們的擴展針對以下可輸入較多文本的編輯器進行兼容:

        • contenteditable 富文本編輯器
        • textarea
        • 其他文檔編輯器

        4.2 富文本編輯器

        我們常見基于 contenteditable 實現的富文本編輯器有百度編輯器、draft.js、 有道云筆記(舊版)等等。

        相比 textarea,富文本編輯器可以包含很多不同標簽,可以以用來渲染成不同字體顏色的文本、圖片、附件、視頻、音頻等等元素。

        實現基于瀏覽器的富文本編輯器的四要素

        四代編輯器的技術選型

        • 第一代編輯器主要是通過有限的 execCommand 指令對 html 文檔進行操作。
        • 第二代則是在 execCommand 基礎上,添加更多自定義指令甚至自己實現指令方式修改 html 文檔。
        • 第三代是引入數據模型(json/xml),綁定自定義實現指令從而渲染html文檔。
        • 第四代主要是直接拋棄整個 contenteditbale,單獨制定選區和監聽輸入事件。

        更多關于編輯器的介紹,可參考有道技術團隊之前發布的文章:

        為什么要介紹富文本編輯器內容呢,因為了解多這些編輯器實現方式和保存機制可以幫助后面實現并優化擴展的功能。

        4.3 初想

        一開始的想法是,將原始編輯器的純文本內容提取并發送到服務端,然后根據服務端返回的數據進行重新的拼接,在錯誤節點位置使用特殊標記標簽進行標注。

        以有道寫作 Web 端為例:

        使用這種方法實現批改效果的還有 163 郵箱英文智能檢查、Gmail 自帶寫信語法檢測功能等。這種方法適合我們自定義的編輯器,可以自己控制文本的渲染和指令。

        但由于瀏覽器擴展是基于別人寫的編輯器上進行的輔助工具,不能隨意修改其文本格式和樣式。比如復制帶有劃線的文本進行粘貼,會出現冗余的劃線(除非原本的編輯器有做粘貼文本的標簽過濾),但是不能寄希望于別人寫的編輯器都有這個功能。

        4.4 實現

        需要分別從兩個部分進行考慮:

        1. 如何定位畫線
        2. 如何接受建議替換正確文本

        如何定位畫線,并且可以給予其高亮的效果呢?

        需要解決的問題是:需要在不影響原編輯器的格式以及功能前提下,將結果劃線部分加入到界面上。

        >>>contenteditabe:

        • 第一步:虛擬輔助器邊框

        虛擬一個元素(大小相同,位置相對)在原始編輯器之上,將結果劃線標注在這個元素之上,我稱之為輔助器。

        因為是覆蓋在原始編輯器上,需要禁止輔助器的鼠標響應行為,在 hover 的時候需要將鼠標位置同步到輔助器之上。

        輔助器需要和原編輯器相同,才能定位準確,需要獲得原編輯器以下屬性。

        • 第二步:找準定位

        問題:如果單純提取元素的 innerHTML/InnerText/textContent 作為批改請求的參數,無法實現準確定位。

        原因:服務端返回結果是根據純文本定位,網頁上的編輯器格式是HTML文檔格式,包含不同字體不同格式的標簽。

        舉個例子:html 中有兩個塊級元素,分別展示兩句話,差異只在于兩句話 font-size 不一樣。

        通過 element.textContent 提取出來的內容都是相同的,校驗返回的錯誤標志結果也相同,如下:

        因為無法從純文本的角度得到兩種情況差異,難點就在于:需要解析 html 格式,將服務端數據轉換到實際格式位置中。

        要知道,這相當于要在一個空白的白板元素里添加一個多個絕對定位的高亮元素。需要知道每個錯誤相對原編輯框的相對位移,和自身寬高。

        下面是一個反向推敲的過程:

        1. 我需要得到的是 hightlightElement : { top, letf, width, height };
        2. 通過 range.getClientRects() 可以獲得我們想要的數據。
        3. 于是需要知道如何獲取一個錯誤節點對應的 range。

        1. 需要找到對應的開頭節點和它的相對位移、以及結束節點和它的相對位移。range: { startNode, stratOffest, endNode, endOffest}。方法就是通過錯誤節點在純文本的開始(fixedposStart)跟結束位置(fixedposEnd)通過遍歷全文每一個文本節點(textNodes)的數據長度(textNodes.nodeValue)進行計算得出。
        2. fixedposStart、fixedposEnd根據服務端返回數據經過稍微計算可得出。全文每一個文本節點(textNodes)需要通過過濾某些標簽得到。
        3. 所以需要先思考如何處理 html 中各種標簽問題。

        所以劃線的原理是:提取其純文本的 textnode 節點,根據結果 position 匹配開始的節點和位移、結束節點和位移,獲取其文本片段 range 對應編輯器的 x,y,height,width,畫出高亮區域。

        具體步驟如下:

        a. 根據原文所有 html 標簽加工過濾,提取純文本和加工后的文本節點集合:

        html 內有各種標簽節點,需要根據這些標簽不同意義,對標簽內的文本進行加工。比如針對 p 標簽,通常是表示段落,需要將其包裹的內容后面添加一個換行符。

        p 標簽處理例子:
        問題: 這個換行符是一起發給服務端的,服務端返回來的數據定位也算上了這個換行符。
        解決方案: 過濾標簽的同時記錄文本處理過的位置,在后面的計算反向處理。同時還需要注意字符的轉義問題,尤其注意零寬字符的處理。

        b. 提取純文本節點:

        (上圖文本內容根據標簽內容分成5個純文本節點)

        c. 結合服務端數據計算每個錯誤全文定位:

        比如 has 錯誤對應的錯誤節點信息。

        d. 根據定位獲取每個錯誤節點文本片段:

        e. 通過文本片段獲取相對視口的位置:

        劃線步驟圖

        • 第三步:在assist范圍內畫出線和高亮

        contenteditable 集合輔助器工作的流程圖

        >>> textarea:

        textarea 本身是無法獲取其 textnode 的,它相當于只有一個節點??紤]將其轉換成文本節點:

        • 創建一個隱形 mirror,這個 mirror 具備與原始編輯器相同邊框大小、可編輯區域。

        • textarea 任何文本變動同步到 mirror
        this.textarea.addEventListener('input',this.mirror.update);
        • 再為這個 mirror 創建一個 assist,同理上面處理 contenteditbale 的流程相一致。

        >>>關于突變:

        編輯器其實就是一個普通的元素,以下編輯器的交互會引起我們頁面內文本節點的變化:

        • 文本內容變化
        • 尺寸變化(窗口變大變?。?/li>
        • 位置變化
        • 字體大小變化(加粗,居中)
        • 滾動

        這些變化也就影響我們定位的變化,稱之為突變。需要處理每一個突變引起的重新定位問題(重點難點)。

        同時,需要監聽原始編輯器的輸入、字體變化、編輯器尺寸變化等等觸發 assist 的重新定位方法。

        // 通過ResizeObserver監聽編輯器尺寸變化 objResizeObserver = new ResizeObserver((entries) => {
            var entry = entries[0];
            this.elementResizeHandler(entry.target)
        }); 

        ResizeObserver 兼容性問題需要通過 polyfill 庫文件解決。

        重新定位方法(mutation):

        • 通過新舊 textnode array 比對,正向遍歷節點集合和反向遍歷節點集合,得到被修改的 textnode 是哪一個段文本節點 textnode 集合。
        • 只需要處理被影響的 textnode 所對應的錯誤節點集合根據移動的 offest 計算后面影響的節點位移。

        • 其他錯誤相對自己 textnode 的位移是不會改變的。

        如何接受一個建議,替換文本:

        替換文本意味要修改原編輯器的數據甚至格式,就會造成剛才說的對部分編輯器會引起格式錯亂和保存失敗的情況。

        難點:不影響原始數據存儲格式,不影響原始編輯器撤回操作,同時還能觸發原編輯器保存機制。

        解決方法:不直接用腳本修改 dom 節點,模擬用戶修改數據的方式:選中文字,替換內容。

        以新版有道云筆記為例子:

        1. 通過之前復雜計算得到結果片段,根據結果片段計算出對于可視窗口的位置,得到 {top, left, height, width}。
        2. 模擬鼠標從左向右滑動的操作事件加在內容區域。

        1. 找到自定義的自繪區域。
        2. 一個錯誤結果中可能涉及不同的樣式,我們僅獲取當前節點第一個片段的字體樣式,模擬一個粘貼事件。

        1. 在自繪區域觸發自定義粘貼事件。

        4.5 增強編輯框

        入口在點擊右下角按鈕或者 hover 出現結果卡片時候,點擊詳細建議進入

        >>>增強編輯框的作用:

        • 提供更大的編輯空間
        • 查看詳細的批改結果

        增強編輯框是一個特殊的 contenteditable 編輯器。

        >>> 初始化、關閉賦值:

        在初始化增強編輯器的時候,直接獲取原編輯器數據,這里忽略了原編輯器的一些樣式、圖片,只使用 html 數據部分。

        在增強編輯器中編輯后返回原編輯器時候,需要將新數據返回賦值。

        >>> 通信:

        增強編輯框是嵌入頁面的 iframe,只在頂層頁面出現。與原來頁面的通信是通過postMessage 方式。

        (注意:postMessage 不能傳遞 html 元素和過于復雜的 json object)

        如果是原本編輯器是 iframe,需要找到最上層 window.top,利用 window.top 和增強編輯框進行通信。

        五、整體流程

        上圖為有道寫作瀏覽器擴展從注入到瀏覽器頁面,以及運行的大致流程。

        為了在不影響用戶操作前提下,擴展腳本只會在當前頁面空閑時候加載,并且批改功能只在部分被用戶點擊 focus 的編輯器中激活。

        以上是開發有道寫作瀏覽器擴展過程中的開發思路和部分技術實現細節,借此機會分享給大家,歡迎與有道技術團隊一起探討更多關于前端、瀏覽器擴展的知識問題。

        查看原文

        贊 14 收藏 6 評論 2

        認證與成就

        • 認證信息 SegmentFault 產品設計合伙人
        • 獲得 655 次點贊
        • 獲得 147 枚徽章 獲得 14 枚金徽章, 獲得 64 枚銀徽章, 獲得 69 枚銅徽章

        擅長技能
        編輯

        開源項目 & 著作
        編輯

        • Typecho

          A PHP Blogging Platform. Simple and Powerful.

        注冊于 2011-04-26
        個人主頁被 13k 人瀏覽

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