亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

jQuery源碼分析之Callbacks詳解

2019-11-20 12:57:32
字體:
來源:轉載
供稿:網友

代碼的本質突出順序、有序這一概念,尤其在javascript――畢竟javascript是單線程引擎。

javascript擁有函數式編程的特性,而又因為javascript單線程引擎,我們的函數總是需要有序的執行。優秀代碼常常 把函數切割成各自的模塊,然后在某一特定條件下執行,既然這些函數是有序的執行,那么我們為什么不編寫一個統一管理的對象,來幫助我們管理這些函數――于是,Callbacks(回調函數)誕生。

什么是Callbacks

javascript中充斥著函數編程,例如最簡單的window.onload承接的就是一個函數,悲催的是window.onload直接賦值的話只能接收一個函數,如果有好幾個函數想要在onload中執行,那么我們就需要編寫如下代碼:

復制代碼 代碼如下:

        function a(elem) {
            elem.innerHTML = '我是函數a,我要改變Element的HTML結構';
        };
        function b(elem) {
            elem.innerHTML = '我的函數b,我要改變Element的style';
        }
        window.onload = function () {
            var elem = document.getElementById('test');
            a(elem);
            b(elem);
        };

回調函數初衷就是建立在這么個玩意兒的上面,不再讓我們分散這些函數,而是把這些函數統一整理。可以看見,我們在window.onload中希望針對一個Element做兩件事情:先改變html結構,然后改變這個html的style。兩個函數同樣是針對一個Element操作,而這兩個函數最終的執行都是有序進行的。那么我們為什么不編寫一個這樣的對象管理這些函數呢。當然, 這只是回調函數的最基礎的存在意義,我們需要的不僅僅是這樣一個簡單的回調函數對象,我們需要一個更加強大的回調函數。好吧,這只是一個簡單的用例,那么我可以告訴你這個回調函數除了一個個執行函數之外,它還可以做什么。

Callbacks本質就是控制函數有序的執行,Javascript是單線程引擎,也就說,javascript同一時間只會有一處代碼在運行――即便是Ajax、setTimeout。 這兩個函數看起來好像都是異步的,其實并非如此,瀏覽器在運行javascript代碼的時候,這些代碼都會被有序的壓入一個隊列中,當你運行Ajax的時候,瀏覽器會把Ajax 壓入代碼隊列,瀏覽器在處理javascript代碼是從這個代碼隊列中一個一個取代碼執行的――Callbacks,迎合了這種單線程引擎。

當然,我們要的,不僅僅是這樣一個簡單的工具對象――在jQuery源碼中,Callbacks提供了一組函數的基本管理,為Deferred(異步隊列)提供了基礎,同時也服務于Queue(同步隊列)。 Deferred用于抹平/扁平化金字塔編程(大量的回調函數嵌套,例如Ajax中需要根據請求返回碼決定執行的代碼); 而Queue,驅動著jQuery.animate(動畫引擎)。

那么我們就來編寫一個Callbacks吧。

Callbacks模型

Array(數組):
既然我們Callbacks要承接一系列函數,那么必然需要有一個容器。我們可以使用一個數組,并把每一個函數壓到該數組中,需要執行的時候,循環數組項執行。

工作模型

這個Callbacks需要非常的強大,并不僅僅是壓入函數,然后執行這么簡單,這個Callbacks應該擁有良好的執行模型。
once:當前Callbacks對象中所有的函數只會執行一次,執行一次完之后就會被釋放掉,我們可以為使用Callbacks對象的用戶提供一個穩定有效的方案,確保函數只會執行一次,之后不再執行,穩定了這些函數的線程。
auto:自動執行模型,這是個有意思的模型,有些函數依賴上一層函數,例如函數b的執行依賴于函數a,那么我們提供一個自動執行的模型:第一次執行這個Callbacks之后,每次添加函數到Callbacks的時候,自動執行過去添加的那些函數,并把最后一次給定的參數數據傳遞給過去的那些函數,這樣就從Callbacks中抹平了這些依賴函數之間需要反復觸發的關系,這是個有意思的模型。
once&auto:我們可以讓它更強大,同時工作once和auto模型,即:當每次添加函數到Callbacks中的時候,過去的函數都會執行,然后,釋放掉這些過去的函數,下次繼續添加函數的時候,過去的那些函數不會再執行,因為once模型,已經把它們釋放掉了。

API:

add(function) - 添加一個(或多個)函數到Callbacks對象中:當然,如果你并不添加函數只是好奇看看Callbacks,我們也將讓你繼續享受你的樂趣――我們并不會拋出異常,因為這對于我們來說并不擅長。
remove(function) - 移除一個Callbacks中的一個函數:既然有了添加,那么我們也應該提供反悔的方案,我們是多么的平易近人,容忍著別人過去所做的一切。
has(function) - 判斷Callbacks中是否包含一個函數:哦?你竟然不確定是否包含這個函數,當初可是你丟進來的?。∧阍趺慈绱笋R虎?不過既然你問我的話,我仍然會告訴你Callbacks是否包含這個函數,我知道你很繁忙,并不能記住和確定所有的事情。
empty() - 清空Callbacks:這些函數對于你失去了意義了么?什么?已經執行過你就不想要了?所以你希望可以清空它?好吧,為了內存君我還是忍下你這個需求。
disable() - 廢掉一個Callbacks:為了和別人的代碼穩定的存在,我選擇了自我犧牲――沒錯,這個方法可以廢掉Callbacks,徹底的廢掉,就如同它曾經尚未存在過一般。
disabled() - 判斷這個Callbacks是否已經被廢掉:如果你仍然不相信Callbacks是否真的自我犧牲,那么這個方法可以讓你安心。
lock(boolean) - 鎖定這個Callbacks對象:你害怕它并不穩定,但是你又不想舍棄它,lock是個不錯的方法,它接收一個Boolean的參數,表示是否需要鎖定這個對象,當然,無參的它用于讓你確定Callbacks是否被鎖定。
fire(data) - 執行這個Callbacks中的函數:我們做的這一切,不都是為了這一刻執行的宿命么?參數將會成為這些需要執行的函數的參數。
fireWith(context,data) - 執行Callbacks中的函數,并且指定上下文。在fire()里,所有的函數的Context(上下文)都是Callbacks對象,而fireWidth(),可以讓你重新定義這些要執行的函數的上下文,多么自由的編程啊,Callbacks為你考慮了一切。
fired() - 判斷這個Callbacks過去是否已經執行過:我們相信,多數時候你并不知道過去做過什么,但是我們記錄了你做的一切,如果你過去曾經執行過這個Callbacks對象,那么你休想否認,因為我們知道過去你是否執行了這個Callbacks。

基本模塊實現

簡單的實現
我們先來簡單的實現一個Callbacks:

復制代碼 代碼如下:

(function (window, undefined) {
            var Callbacks = function () {
                //通過閉包保護這些私有變量
                var list = [],//回調函數列表
                    fired;//是否執行過
                //返回一個閉包的Callbakcs對象
                return {
                    add: function (fn) {
                        //當Callbacks廢棄掉的時候,list為undefined
                        if (list) {
                            //添加一個回調函數
                            list.push(fn);
                            //支持鏈式回調
                        }
                        return this;
                    },
                    fireWith: function (context, data) {
                        //觸發回調函數,并指定上下文
                        if (list) {
                            fired = true;
                            for (var i = 0, len = list.length; i < len; i++) {
                                //當Callbacks中某一個函數返回false的時候,停止Callbacks后續的執行
                                if (list[i].apply(context, data) === false)
                                    break;
                            }
                        }
                        return this;
                    },
                    fire: function () {
                        //觸發回調函數
                        //調用fireWith并指定上下文
                        return this.fireWith(this, arguments);
                    },
                    empty: function () {
                        //清空list即可
                        if (list)//當這個Callbacks廢棄掉的時候,Callbacks不應該可以繼續使用
                            list = [];
                        return this;
                    },
                    disable: function () {
                        //廢棄這個Callbacks對象,后續的回調函數列表不再執行
                        list = undefined;
                        return this;
                    },
                    disabled: function () {//檢測這個Callbacks是否已經廢掉
                        //轉換為boolean返回
                        return !list;
                    },
                    fired: function () {//這個callbacks是否執行過
                        return !!fired;
                    }
                };
            };
            //注冊到window下
            window.Callbacks = Callbacks;
        }(window));

然后我們測試一下這個Callbacks:

復制代碼 代碼如下:

        var test = new Callbacks();
        test.add(function (value) {
            console.log('函數1,value是:' + value);
        });
        test.add(function (value) {
            console.log('函數2,value是:' + value);
        });
        test.fire('這是函數1和函數2的值');
        console.log('查看函數是否執行過:' + test.fired());
        test.disable();//廢棄這個Callbacks
        console.log('查看函數是否被廢棄:' + test.disabled());
        test.add(function () {
            console.log('添加第三個函數,這個函數不應該被執行');
        });
        test.fire();

打開瀏覽器的控制臺我們可以看見運行結果正常。

once和auto(memory)實現

once:
once讓這個callbacks中的函數運行一次之后就不再運行。原理非常的簡單,上面的代碼中,我們可以看見有一個變量list承接函數列表,所以我們只需要把過去執行過的代碼清空即可。我們用一個全局變量,保存當前執行模型,如果是once模型,就在fireWith()里讓這個list失效即可:

復制代碼 代碼如下:

(function (window, undefined) {
            var Callbacks = function (once) {
                //通過閉包保護這些私有變量
                var list = [],//回調函數列表
                    fired;//是否執行過
                //返回一個閉包的Callbakcs對象
                return {
                    //...省略部分代碼
                    fireWith: function (context, data) {
                        //觸發回調函數,并指定上下文
                        if (list) {
                            fired = true;
                            for (var i = 0, len = list.length; i < len; i++) {
                                //當Callbacks中某一個函數返回false的時候,停止Callbacks后續的執行
                                if (list[i].apply(context, data) === false)
                                    break;
                            }
                        }
                        //如果配置了once模型,則全局變量once為true,則list重置
                        if (once) list = undefined;
                        return this;
                    }
                    //...省略部分代碼
                };
            };
            //注冊到window下
            window.Callbacks = Callbacks;
        }(window));

auto:

auto(memory)模型在jQuery中是以memory命名的,最初被這個命名給混淆了,仔細看了用法才確定改成auto――它的作用就是“第一次fire()之后,后續add()的函數自動執行”,以下情況可以用到:當添加一組函數到Callbacks之后,臨時又需要追加一個函數,那么即時運行這個新追加的函數――不得不說,為了使用的便利,這個模式變得有點難以理解。實現起來就是在add()的時候判斷是否是auto模型,如果是auto模型,則執行這個函數。 但是,我們需要在第一次fire()之后才自動執行,沒有fire()過的Callbacks并不該被自動執行,并且,每次自動執行后,還需要把最后一次使用的參數傳遞傳遞給這個自動執行的函數。

或許大家會想到如下代碼:

復制代碼 代碼如下:

(function (window, undefined) {
            var Callbacks = function (once, auto) {
                var list = [],
                    fired,
                    lastData;//保存最后一次執行的參數
                return {
                    add: function (fn) {
                        if (list) {
                            list.push(fn);
                            // ― 自動執行模式
                            //最后一次使用的參數傳遞過去,這里丟失了Context(上下文)
                            //為了不讓這里丟失上下文,我們或許還需要聲明一個變量保存最后一次使用的Context
                            if (auto) this.fire(lastData);
                        }
                        return this;
                    },
                    fireWith: function (context, data) {
                        if (list) {
                            lastData = data;// ― 記錄最后一次使用的參數
                            fired = true;
                            for (var i = 0, len = list.length; i < len; i++) {
                                if (list[i].apply(context, data) === false)
                                    break;
                            }
                        }
                        if (once) list = [];
                        return this;
                    }
                    //部分代碼省略
                };
            };
            //注冊到window下
            window.Callbacks = Callbacks;
        }(window));

但是在jQuery里采用了更奇妙的用法,獲取jQuery作者也自豪這種用法,所以命名這個模型為memory――就是讓上面的變量auto不僅僅表示當前是auto執行模式,并且作為最后一次參數的容器,它既表示了auto,也表示了memory。(下面的代碼非jQuery是根據jQuery代碼思路而寫,非源碼):

復制代碼 代碼如下:

(function (window, undefined) {
            var Callbacks = function (auto) {
                var list = [],
                    fired,
                    memory,//主演在這里,就是memory
                    coreFire = function (data) {
                        //真正的觸發函數方法
                        if (list) {
                            //&&表達式妙用
                            memory = auto && data;//記錄最后一次的參數,如果不是auto模式則不會記錄這個參數
                            //如果是auto模式,那么這個auto將不會為false,它會是一個數組
                            fired = true;
                            for (var i = 0, len = list.length; i < len; i++) {
                                if (list[i].apply(data[0], data[1]) === false)
                                    break;
                            }
                        }
                    };
                return {
                    add: function (fn) {
                        if (list) {
                            //添加一個回調函數
                            list.push(fn);
                            //自動執行模式,注意如果auto模型
                            //memory是在coreFire()里賦值的,默認是false
                            if (memory) coreFire(auto);
                        }
                        //支持鏈式回調
                        return this;
                    },
                    fireWith: function (context, data) {
                        if (once) list = [];
                        //這里調用coreFire,把參數轉換為數組了
                        coreFire([context, data]);
                        return this;
                    }
                    /*部分代碼省略*/
                };
            };
            window.Callbacks = Callbacks;
        }(window));

我們在上一個auto實現的代碼中看到我們丟失了Context,jQuery早在fireWith()中修復了這個bug――在fireWith()中修復參數。jQuery把fireWith()中本來應該執行函數的邏輯給抽離出來,我們暫時將它命名為coreFire(),在原fireWith()中,將參數拼接成一個數組:第一個參數表示上下文,第二個參數表示傳遞進來的參數。然后執行coreFire()。

在add()的時候,jQuery并沒有給變量auto(memory)賦值,而是選擇在coreFire()中給auto(memory)賦值,這樣就保證了第一次fire()之后才會開啟自動執行。

按照上面所說,coreFire()接收的參數其實是一個數組,第一個參數是上下文,第二個參數是外面傳遞進來的參數。同時把這個數組賦值給auto(memory),這樣,變量auto(是否自動執行模式)的定義就變成了memory(記憶最后一次傳遞的參數)。
真是一石二鳥的神思路,神想法,不得不點贊。我定義這個為auto是因為它的本身就是一個自動執行的模型,順便保存了最后一次fire()的參數,而jQuery定義為memory或許也是作者感嘆這里的鬼斧神工吧。

至于once&auto就是把這兩個代碼揉合到一起而已,只需要在coreFire()里判定如果是auto模式,那么就把list重置為一個新的數組,否則直接設置為undefined即可。

源碼

這份代碼是自己對應jQuery手寫的一份,將一些jQuery公有的函數都寫了進來,并非代碼片段,所以可以直接引用運行。

復制代碼 代碼如下:

(function (window, undefined) {
    /*
    * 一個回調函數工具對象,注意這個工作對象工作完成之后就會清空數組:
    *   提供一組普通的API,但它有如下工作模型 -
    *                     once - 單次執行模型:每次工作一次,后續不再工作
    *                     auto - 自動執行模型:每添加一個回調函數,自動執行現有的回調函數集合里的所有回調函數,并將本次的參數傳遞給所有的回調函數
    *
    */

    //工具函數
    var isIndexOf = Array.prototype.indexOf,    //Es6
        toString = Object.prototype.toString,   //緩存toString方法
        toSlice = Array.prototype.slice,        //緩存slice方法
        isFunction = (function () {             //判定一個對象是否是Function
            return "object" === typeof document.getElementById ?
            isFunction = function (fn) {
                //ie下對DOM和BOM的識別有問題
                try {
                    return /^/s*/bfunction/b/.test("" + fn);
                } catch (x) {
                    return false
                }
            } :
            isFunction = function (fn) { return toString.call(fn) === '[object Function]'; };
        })(),
        each = function () {                    //循環遍歷方法
            //第一個參數表示要循環的數組,第二個參數是每次循環執行的函數
            if (arguments.length < 2 || !isFunction(arguments[1])) return;
            //為什么slice無效??
            var list = toSlice.call(arguments[0]),
                fn = arguments[1],
                item;
            while ((item = list.shift())) {//沒有直接判定length,加速
                // 為什么這里用call就可以,而apply就不行?
                //搞定 - apply的第二個參數必須是一個array對象(沒有驗證array-like是否可以,而call沒有這個要求)
                //apply是這樣描述的:如果 argArray(第二個參數) 不是一個有效的數組或者不是 arguments 對象,那么將導致一個 TypeError。
                fn.call(window, item);
            }
        },
        inArray = function () {                     //檢測數組中是否包含某項,返回該項索引
            //預編譯
            return isIndexOf ? function (array, elem, i) {
                if (array)
                    return isIndexOf.call(array, elem, i);
                return -1;
            } : function (elem, array, i) {
                var len;
                if (array) {
                    len = array.length;
                    i = i ? i < 0 ? Math.max(0, len + i) : i : 0;
                    for (; i < len; i++) {
                        if (i in array && array[i] === elem) {
                            return i;
                        }
                    }
                }
                return -1;
            }
        }();

    var Callbacks = function (option) {
        option = toString.call(option) === '[object Object]' ? option : {};
        //使用閉包,因為每個新建的callbacks都有自己的狀態
        var list = [],      //回調列表
            _list = [],     //如果鎖定這個callbacks對象,則清空list,將原list置入_list
            fired,          //是否執行過
            firingStart,    //當前回調函數列表執行的函數索引(起點)
            firingLength,   //回調函數的數組長度
            auto,   //標志是否自動執行,如果需要自動執行,則auto記憶著最后一次回調的參數(最后一次fire的參數),這是一個很詭異的且奇葩的用法
            //這個變量用法很詭異和犀利,既包含了是否指定執行的標志,又記錄了數據
            //這個auto配合once簡直就是喪心病狂:【第一次】執行了fire后才會自動執行,配合once可以做到:一次執行,后面不再追加和執行代碼,保證了一組回調數據的穩定和安全
            stack = !option.once && [],     //一個callbacks棧,如果當前正在執行回調數組,而在執行中又新添了回調函數,那么把新的回調函數,那么新的回調函數都會壓入該棧
            firing = false, //callbacks是否正在工作/執行
        //觸發回調函數
            fire = function (data) {
                //注意這個data是個數組,如果配置了auto模式,那么auto永遠不會為false,因為auto會是個數組
                auto = option.auto && data; //在這里,如果配置要求記憶最后的參數,則記憶這個參數(非常犀利的用法,直接取了數據)
                fired = true;
                firingIndex = firingStart || 0;
                firingStart = 0;//清空firingStart(不清空下次執行有出問題啦)
                firingLength = list.length;         //緩存list長度,外界可以訪問
                firing = true; //正在執行回調函數
                for (; firingIndex < firingLength; firingIndex++) {
                    if (list[firingIndex].apply(data[0], data[1]) === false) {
                        //注意,如果配置了option.auto(自動執行),并且stack(棧)里存在函數,那么add()代碼里有一段對于auto判定會直接執行本方法的代碼
                        //我們要阻止掉那段代碼,所以設置auto為false
                        auto = false;
                        break;
                    }//當函數返回false,終止執行后續隊列
                }
                firing = false; //標志狀態已經執行完畢回調函數[stack(棧)里面的函數尚未執行]
                //如果這個棧在沒有配置once的情況下肯定是[],所以一定存在
                //這里主要作用是,如果沒有配置once,則攔截下面的代碼,如果配置了once,執行完代碼清空數據
                if (stack) {
                    if (stack.length)//先把下面清空list狀態的代碼攔截掉,再判定是否有棧
                        fire(stack.shift()); //從棧頭部取出,并遞歸fire()方法
                }
                else if (auto)    //代碼走到這里,證明已經配置了option.once(只執行一次),于是把list清空
                    list = [];
                else                //證明沒有配置auto,但是配置了once,那么祭出終極大法,直接廢了這個callbacks對象
                    self.disable();
            };
        var self = {
            add: function () {//添加一個回調函數
                if (list) {
                    var start = list.length;
                    (function addCallback(args) {
                        each(args, function (item) {
                            if (isFunction(item)) {//是函數,則壓入回調列表
                                list.push(item);
                                //注意typeof 和Object.prototype.toString是不一樣的
                            } else if (toString.call(item) === '[object Array]') {//如果是個數組,則遞歸壓入回調列表,這個判定拋棄了array-like
                                addCallback(item);
                            }
                        });
                    })(arguments);
                }
                if (firing)//如果當前正有回調函數在執行,那么需要更新當前回調函數列表的length,否則這個新壓入的回調函數就會被掠過。
                    firingLength = list.length;
                else if (auto) {//如果當前沒有執行回調函數,并且要求自動執行
                    //注意這里是給firingStart賦值,上面fire方法中正在使用的是firingIndex,這里不會影響到上面代碼的執行線路
                    firingStart = start;
                    //執行我們新加入的小伙伴
                    fire(auto);
                }
                return this;
            },
            fire: function () {//觸發回調函數
                self.fireWith(this, arguments);
                return this;
            },
            fireWith: function (context, args) {//觸發回調函數,并指定上下文
                //如果配置了once,stack將為undefined,而once又需要保證只執行一次,所以一旦執行過一次,這里的代碼不會再執行
                if (list && (!fired || stack)) {
                    //修正參數
                    //在這里,context索引為0
                    //而參數列表索引為2
                    //轉換為數組訪問是因為對象表示更加的消耗資源,在頂層的fire()代碼中有auto[記憶參數,自動執行]這個功能,如果采用對象則開銷了更大的內存
                    args = [context,
                        args ?
                        args.slice && args.slice()
                        || toSlice.call(args) :
                        []
                    ];
                    fire(args);
                }
                return this;
            },
            remove: function () {//移除一個回調函數
                if (list) {
                    each(arguments, function (item) {
                        var index;
                        //可能有多項,index可以在循環中表示檢索的范圍,之前檢索的過的可以不用再檢索
                        while ((index = inArray(item, list, index)) > -1) {
                            list.splice(index, 1);
                            if (firing) {
                                //保證上面fire中正在執行的函數列表能夠正確運行,fire中設定全局這些變量為的就是這里可以異步移除
                                if (index <= firingLength)//修正長度
                                    firingLength--;
                                if (index <= firingLength)//修正索引
                                    firingIndex--;
                            }
                        }
                    });
                }
                return this;
            },
            has: function (fn) {//是否包含一個回調函數
                return fn ? inArray(fn, list) > -1 : list && list.length;
            },
            empty: function () {//清空這個callbacks對象
                list = [];
                firingLength = 0;
                return this;
            },
            disable: function () {//廢掉這個callbacks對象,后續的回調函數列表不再執行
                list = stack = auto = undefined;
                return this;
            },
            disabled: function () {//是否已經廢掉
                return !list; //轉換為boolean
            },
            lock: function (isLock) {//鎖定或解鎖這個callbacks對象
                //無參,判斷這個callbacks是否被鎖定
                if (isLock == null) return !!_list;
                if (isLock) {//鎖
                    _list = stack && list.concat(stack) || list;
                    list = undefined;
                } else {//解鎖,jQuery并沒有提供解鎖功能,解鎖讓Callbacks變得不穩定
                    list = _list;
                    _list = undefined;
                }
                return this;
            },
            fired: function () {//這個callbacks是否執行過
                //轉換為boolean,包括undefined,null,''等
                return !!fired;
            }
        };
        return self;
    };
    window.$ = window.$ || {};
    window.$.Callbacks = window.Callbacks = Callbacks;
}(window));

下載

Github:https://github.com/linkFly6/linkfly.so/blob/master/LinkFLy/jQuery/jQuery.LinkFLy/Callbacks.js

以上就是本文給大家分享的全部內容了,希望大家能夠喜歡。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久久影院在线观看| 欧美视频在线视频| 亚洲欧美日韩精品久久亚洲区| 91精品国产91久久久久| 欧美另类在线观看| 日韩欧美国产中文字幕| 国产视频丨精品|在线观看| 国产精品日韩一区| 久久精品视频免费播放| 久久久国产视频91| 欧美日韩在线第一页| 成人午夜两性视频| 中文字幕日韩电影| 日韩毛片中文字幕| 国产视频丨精品|在线观看| 51色欧美片视频在线观看| 国产精品久久久久久婷婷天堂| 久久频这里精品99香蕉| 国产精品美女呻吟| 国产精品mp4| 一区二区三区国产视频| 亚洲性生活视频在线观看| 精品福利在线视频| 久久视频这里只有精品| 九九热99久久久国产盗摄| 亚洲人成欧美中文字幕| 欧美激情综合亚洲一二区| 亚洲成av人片在线观看香蕉| 亚洲欧洲国产一区| 国产精品一久久香蕉国产线看观看| www.99久久热国产日韩欧美.com| 亚洲区免费影片| 97视频在线观看成人| 91大神福利视频在线| 午夜精品视频在线| 成人有码视频在线播放| 一本色道久久88综合日韩精品| 久久精品色欧美aⅴ一区二区| 久久伊人色综合| 8x海外华人永久免费日韩内陆视频| 91沈先生在线观看| 亚洲三级 欧美三级| 欧美日韩电影在线观看| 一区二区三区视频在线| 亚洲成av人片在线观看香蕉| 亚洲欧美日韩视频一区| 日韩av在线免播放器| 国产亚洲欧洲在线| 福利一区视频在线观看| 久久久久久网址| 黄色一区二区三区| 日韩激情视频在线| 亚洲电影在线看| 日韩av免费在线播放| 国产精品福利网站| 91免费看片在线| 欧美韩国理论所午夜片917电影| 成人性教育视频在线观看| 国产欧美日韩高清| 亚洲国产精品人久久电影| 精品欧美激情精品一区| 国产精品欧美一区二区三区奶水| 狠狠色香婷婷久久亚洲精品| 欧美在线视频播放| 91日本在线视频| 久久艳片www.17c.com| 欧美精品在线播放| 国产成人精品综合久久久| 日韩在线中文视频| 亚洲精品98久久久久久中文字幕| 久久91亚洲精品中文字幕奶水| 亚洲自拍偷拍视频| 国产精品福利在线| 97色在线视频| 成人午夜在线影院| 国产精品久久久久久久天堂| 伊人亚洲福利一区二区三区| 欧美视频中文在线看| 亚洲综合日韩中文字幕v在线| 尤物tv国产一区| 92看片淫黄大片欧美看国产片| 国产精品视频免费观看www| 亚洲级视频在线观看免费1级| 亚洲女人被黑人巨大进入| 精品福利一区二区| 日韩欧中文字幕| 欧美国产日产韩国视频| 亚洲天堂开心观看| 久久精品影视伊人网| 亚洲精品www| 亚洲美女视频网| 精品无人区乱码1区2区3区在线| 精品久久久久久久久久国产| 中文字幕亚洲色图| 成人国产精品免费视频| 欧美另类第一页| 91在线观看免费网站| 欧美一区二区三区图| 国产不卡视频在线| 亚洲精品福利资源站| 日韩欧美成人免费视频| 91超碰中文字幕久久精品| 91视频九色网站| 欧美日韩免费看| 成人av在线网址| 久久精品国产亚洲一区二区| 欧美亚洲另类视频| 亚洲黄色免费三级| 国产日韩在线观看av| 亚洲欧美精品在线| 国产一区二区三区免费视频| 久久亚洲国产精品| 亚洲娇小xxxx欧美娇小| 国产精品久久久久77777| 亚洲精品久久久久久久久久久| 久久久久国色av免费观看性色| 国产亚洲综合久久| 欧美激情视频在线免费观看 欧美视频免费一| 中文日韩在线视频| 久久久精品国产网站| 性色av一区二区三区在线观看| 夜夜嗨av一区二区三区免费区| 国产视频一区在线| 欧美日韩亚洲一区二| 欧美精品久久久久| 日韩精品视频免费| 亚洲最大福利视频网站| 少妇高潮 亚洲精品| www.日本久久久久com.| 欧美日韩免费区域视频在线观看| 国产99在线|中文| 欧美韩国理论所午夜片917电影| 国产精品久久久久久五月尺| 亚洲高清不卡av| 国产精品999| 亚洲第一区在线观看| 狠狠躁夜夜躁久久躁别揉| 亚洲综合中文字幕68页| 欧美精品免费播放| 国模gogo一区二区大胆私拍| 91精品在线国产| 萌白酱国产一区二区| 欧美在线视频免费播放| 久久99热精品这里久久精品| 国产精品免费一区豆花| 在线精品视频视频中文字幕| 一区二区在线免费视频| 亚洲成人激情在线观看| 精品女同一区二区三区在线播放| 国产精品永久免费在线| 国产美女主播一区| 日韩欧美中文字幕在线观看| 日韩免费在线观看视频| 日韩福利视频在线观看| 尤物九九久久国产精品的分类| 欧美精品在线免费播放| 黄色成人在线免费| 国产精品毛片a∨一区二区三区|国| 在线视频精品一| 2018中文字幕一区二区三区| 亚洲国产第一页| 国产欧美精品日韩| 亚洲视频在线观看网站| 91久久久国产精品|