vue-scroll 源碼解讀

(function(){
  // 自定義 Vue, isVueLoaded
  var Vue;
  var isVueLoaded = true;
  // 判斷是否有 require 方法,如果有則通過 require 方式引入 Vue,否則直接從 window 獲取
  if(typeof require === 'undefined'){
    Vue = window.Vue;
  }else{
    Vue = require('vue');
  }
  // 如果沒有 Vue, 則 isVueLoaded 賦值為 false, 并且 warn 提示用戶去下載 Vue
  if(!Vue) {
    isVueLoaded = false;
    console.warn('Vue is not loaded yet. Please make sure it is loaded before installing vue-scroll.');
  }

  // 定義 scrollPlugin 對象阔蛉,以下是自定義 vue 插件的方式
  var scrollPlugin = {
    // 對象的 install 屬性, 屬性值是一個 function, 參數(shù)是 Vue 和 options
    install: function(Vue, options){
      // 定義常量
      var EVENT_SCROLL = 'scroll';
        //  定義一個 Q 構造函數(shù)
      function Q(){
        // 定義數(shù)組
        var elements = [];
        var listeners = [];

        var addListener = function(element, eventType, funcs){
          // 再次定義局部變量 EVENT_SCROLL
          var EVENT_SCROLL = 'scroll';
          var scrollData = {};

          // https://github.com/wangpin34/vue-scroll/issues/1
          // 如果 element 是 document.body 或者 window 等大視圖的話
          if((element === document.body || element === document || element === window) && eventType === EVENT_SCROLL){
            // 直接給 document 定義 onscroll 方法,原本為空,現(xiàn)在直接內(nèi)部定義,滾動時觸發(fā)
            document.onscroll = function(e) {
              // 自定義對象的屬性
              // 此處的 scrollTop 取值直接是 document.body 的值
              scrollData.scrollTop = document.body.scrollTop;
              scrollData.scrollLeft = document.body.scrollLeft;
              // 將傳入的方法一個一個執(zhí)行掉
              funcs.forEach(function(func){
                func && func(e, scrollData);
              })
            }
          }else {
            // element 應該是具體元素,如果不存在,則直接當做是 window.event
            // 滾動時自動調(diào)用此方法, 此時 scroll 方法已經(jīng)添加到 funcs 里了
            // 如果綁定的是具體元素滑臊,具體元素滾動會觸發(fā)下面方法的執(zhí)行
            var listener = function(e) {
              e = e || window.event;
              e.target = e.target || e.srcElement;
              if(eventType === EVENT_SCROLL){
                scrollData.scrollTop = element.scrollTop;
                scrollData.scrollLeft = element.scrollLeft;
              }

              funcs.forEach(function(func){
                // 執(zhí)行所有的 func
                // 從外部獲取 function,并且將元素和 scrollData 傳遞給外部的方法
                (typeof func !== 'undefined') && func(e, scrollData);
              })
            }

             // 如果是新版瀏覽器,則支持 addEventListener ,通過該方法添加監(jiān)聽事件
            if(element.addEventListener){
              // 參數(shù)分別是事件名和觸發(fā)事件時需要執(zhí)行的函數(shù)
              element.addEventListener(eventType, listener);
            }else{
              // 老版本瀏覽器(IE8及以下)只支持 attachEvent, 則通過改該方法添加監(jiān)聽事件
              element.attachEvent('on' + eventType, listener);
            }

          }
        }
        //  _initialized 是標記位,用來標記 Q 對象是否初始化了(給原型賦予了任何方法)
        // 如果這個值未定義蚪拦,構造函數(shù)將用原型方式繼續(xù)定義對象的方法
        // 如果未初始化,則進行一系列操作,然后設置 _initialized 為 true,表示此時已經(jīng)被初始化了
        // 此方法是動態(tài)原型模式,下方代碼只有初次調(diào)用構造函數(shù)的時候才會執(zhí)行
        if(typeof Q._initialized == 'undefined'){
          // element 為外部傳入元素, eventType 這里為 scroll, func 為外部傳入方法
          Q.prototype.bind = (function(element, eventType, func){
            var funcs;

            // 查看元素是否在 elements 里,沒有則添加進去
            if(elements.indexOf(element) < 0){
              elements.push(element);
              // 給 listeners 添加一個空對象
              listeners.push({});
              // listeners 的數(shù)組最后一個元素內(nèi)容賦值給 funcs
              funcs = listeners[listeners.length - 1];
            }else{
              // 將 element 對應的 listener 賦值給 funcs
              funcs = listeners[elements.indexOf(element)];
            }

            var eventFuncs;
            // funcs 對應的事件類型的事件不存在
            if(!funcs[eventType]){
              //Initialize event listeners
              funcs[eventType] = [];
              //Bind to the element once
              addListener(element, eventType, funcs[eventType]);
            }
            eventFuncs = funcs[eventType];
            // func 為外部傳入的方法
            eventFuncs.push(func);

          }).bind(this);

          Q.prototype.unbind = (function(element, eventType, func){
            var funcs;

            if(elements.indexOf(element) < 0){
              console.warn('There are no listener could be removed.');
              return 1;
            }else{
              funcs = listeners[elements.indexOf(element)];
            }

            var eventFuncs;
            if(!funcs[eventType] || (eventFuncs = funcs[eventType]).indexOf(func) < 0){
              console.warn('There are no listener could be removed.');
              return;
            }
            eventFuncs.splice(eventFuncs.indexOf(func), 1);
            console.log('A event listener is removed successfully');
          }).bind(this);

          Q._initialized = true;
        }
      }

      // 生成實例
      var q = new Q();
      // 定義 Vue 指令
      Vue.directive('scroll', {
        bind: function(el, binding, vnode){
          // 如果 binding.value 不是一個函數(shù),則進行錯誤提示
          if(!binding.value || typeof binding.value !== 'function'){
            throw new Error('The scroll listener is not a function');
          }
          // 三個參數(shù)分別表示元素,事件,以及需要執(zhí)行的方法
          q.bind(el, EVENT_SCROLL, binding.value);
        },
        inserted: function(el, binding){
          //To do, check whether element is scrollable and give warn message when not
        },
        update: function(el, binding){
          // 如果綁定的函數(shù)和綁定之前的函數(shù)一致,則直接返回
          if(binding.value === binding.oldValue){
            return;
          }
          // 如果綁定的不是函數(shù),則報錯
          if(!binding.value || typeof binding.value !== 'function'){
            throw new Error('The scroll listener is not a function');
          }
          // 綁定新函數(shù),解綁舊函數(shù)
          q.bind(el, EVENT_SCROLL, binding.value);
          q.unbind(el, EVENT_SCROLL, binding.oldValue);
        },
        unbind: function(el, binding){
          // 如果綁定的是函數(shù),則解綁,否則報錯
          if(binding.value && typeof binding.value === 'function'){
            q.unbind(el, EVENT_SCROLL, binding.oldValue);
          }else{
            throw new Error('The scroll listener is not a function');
          }
        }
      })

    }
  }
export default scrollPlugin;

  // 輸出
  if(typeof module !== 'undefined' && typeof module.exports !== 'undefined'){
    module.exports = scrollPlugin;
  }else{
    window.vScroll = scrollPlugin;
  }
})()

該插件地址:https://github.com/wangpin34/vue-scroll
執(zhí)行順序:

  1. 通過 vue 指令牍疏,執(zhí)行指令中的 bind蠢笋,指令首次綁定元素時執(zhí)行,只執(zhí)行一次
  2. 該 bind 方法中有一個 q 實例執(zhí)行自己(構造函數(shù)中定義)的 bind 方法鳞陨,并傳遞了三個參數(shù)
  3. 構造函數(shù)的 bind 方法昨寞,給 listeners 和 funcs 分別添加對應的需要監(jiān)聽的元素和對應的方法,結構如下:
    funcs = { scroll: [] }
    listeners = [ { scroll: [] } ]
  4. 查看 funcs 是否有對應事件的方法厦滤,如果沒有援岩,則執(zhí)行 addListener
  5. 查看 element 是全局的元素如 document 之類的還是具體元素
  6. 獲取元素的 scrollTop 和 scrollLeft
  7. 執(zhí)行所有外部傳入的方法,由于此時 funcs 的 scroll 屬性還是空掏导,所以這時候并沒有執(zhí)行任何方法
  8. 給元素新增監(jiān)聽事件享怀,監(jiān)聽到事件的時候執(zhí)行外部傳入的方法。
  9. 將 funcs 的 scroll 賦值給 eventFuncs 并將外部方法傳遞到 eventFuncs 中趟咆。這樣eventFuncs 就有對應的事件類型和事件觸發(fā)時需要執(zhí)行的方法添瓷。
  10. 觸發(fā)事件,如 scroll 則會執(zhí)行對應方法值纱。

實測直接在元素上添加指令和對應的方法無效鳞贷,原因是滾動時無法觸發(fā) scroll 事件。因為視圖中只有 window 和 document 是固定不變的虐唠,其它元素相對于 window 是滾動的搀愧,換句話的意思就是滾動 window 內(nèi)部元素時觸發(fā)了 window 的 scroll 事件(表現(xiàn)為 window 出現(xiàn)了滾動條),但是其它元素相對于彼此之間是固定不變的,所以監(jiān)聽某一個元素的滾動是監(jiān)聽不到的咱筛。
解決方式很簡單搓幌,只需要控制元素的高度不變,內(nèi)部子元素超出父元素的高度眷蚓,設置父元素 overflow: auto 或者 scroll 即可鼻种。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市沙热,隨后出現(xiàn)的幾起案子叉钥,更是在濱河造成了極大的恐慌,老刑警劉巖篙贸,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件投队,死亡現(xiàn)場離奇詭異,居然都是意外死亡爵川,警方通過查閱死者的電腦和手機敷鸦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寝贡,“玉大人扒披,你說我怎么就攤上這事∑耘荩” “怎么了碟案?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長颇蜡。 經(jīng)常有香客問我价说,道長,這世上最難降的妖魔是什么风秤? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任鳖目,我火速辦了婚禮,結果婚禮上缤弦,老公的妹妹穿的比我還像新娘领迈。我一直安慰自己,他們只是感情好碍沐,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布惦费。 她就那樣靜靜地躺著,像睡著了一般抢韭。 火紅的嫁衣襯著肌膚如雪薪贫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天刻恭,我揣著相機與錄音瞧省,去河邊找鬼扯夭。 笑死,一個胖子當著我的面吹牛鞍匾,可吹牛的內(nèi)容都是我干的交洗。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼橡淑,長吁一口氣:“原來是場噩夢啊……” “哼构拳!你這毒婦竟也來了?” 一聲冷哼從身側響起梁棠,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤置森,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后符糊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凫海,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年男娄,在試婚紗的時候發(fā)現(xiàn)自己被綠了行贪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡模闲,死狀恐怖建瘫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情尸折,我是刑警寧澤啰脚,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站翁授,受9級特大地震影響拣播,放射性物質發(fā)生泄漏晾咪。R本人自食惡果不足惜收擦,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谍倦。 院中可真熱鬧塞赂,春花似錦、人聲如沸昼蛀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叼旋。三九已至仇哆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間夫植,已是汗流浹背讹剔。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工油讯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人延欠。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓陌兑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親由捎。 傳聞我的和親對象是個殘疾皇子兔综,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容