整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          為HTML頁面添加視頻、音頻的方法-零基礎自學網頁制作

          頻、視頻的格式

          開始學習之前,我們要下載些素材用來測試使用,地址如下:https://pan.baidu.com/s/1reRWno0ibYRcYXjw4MClqw

          提取碼:td80

          其中包括兩個視頻一個音頻和一個安裝程序。

          學習如何在頁面中添加音頻、視頻之前應該了解一點基礎知識。

          我們應該都有在互聯網上下載電影、視頻、音樂的經歷,大家會注意到,有的視頻文件名的后綴是.avi,有的視頻是.mp4,還有.mov的,據不完全統計,常見的各種視頻格式有十幾種之多。

          常見的音頻的格式比起視頻來會顯得少一些,一般是.wav和.mp3格式。

          為什么存儲相同的內容可以有這么多不同的格式呢?

          格式產生的核心在于對音頻、視頻等多媒體文件進行的不同編碼方式。

          那什么是編碼呢?

          簡單來說我們可以把"編碼"這個詞分成兩個部分,第一個是"編",也就是"整理、組織"的意思,第二個是"碼",也就是我們平時所說的"數碼"。

          首先說"碼",我們的計算機中的數據最終都是通過二進制的數字(0和1)來存儲或計算的,這些0或1就是數碼。無論代碼、程序、圖片、音樂、視頻、文字等的存儲與計算都不例外。不管多么復雜或簡單的文件,在計算機看來,都是一大堆0和1。

          一個0或1被稱為1比特,圖片或視頻中的一個黑白像素通常是8比特(八位),如果一張1080乘720個像素的圖片所占內存的大小就是1080*720*8=6220800字節,約等于0,74mb。如果一個視頻每秒中有25幀,也就是一秒鐘在我們眼前閃過25張圖片(視頻播放實際上就是在我們眼前快速的更替圖片,這些圖片在大腦中會被自動連成動作,這也是小的時候在課本的角上畫好一套走路的小人的不同動作后,快速翻動書頁,畫面中的小人會走路的原因,大家可以自行百度"視覺暫留原理")。

          一秒鐘25張1080乘720的圖片的視頻,一秒鐘就會占0.74*25=18.5mb的內存。如果是一分鐘呢,18.5*60=1110mb約等于1.08gb。這樣的數據量是不是很嚇人。

          但事實上我們下載的1080*720的一小時三十分鐘左右的視頻的體積往往也沒有超過1gb,這又是為什么呢?

          這就是"編"的功勞!對數碼進行整理和組織的主要目的是壓縮體積,壓縮數據體積既能節省磁盤又能方便傳播與攜帶,是信息技術的關鍵技術之一,壓縮的方法一般有兩類,一類叫做無損壓縮,也就是通過對這一大堆數碼進行一個特殊的組合使其占有更小的空間,一類叫做有損壓縮,是在無損壓縮的基礎上剔除掉人眼睛識別不到的冗余信息。具體的壓縮過程涉及到很多數學知識,這里大家簡單了解一下即可。

          壓縮后的視頻或音頻文件最終通過播放器對該文件的壓縮算法進行逆向運算后,還原成計算機可以解讀的畫面和聲音再呈現給觀眾,這個過程叫做"解碼"。

          通過"編"的方式壓縮文件體積,通過"解"的方式再還原出文件內容成了處理大規模數據的通用手法。

          不同的編碼和解碼方式催生出不同的文件格式,這種情況下,瀏覽器在播放視頻的時候就要有應對不同格式的不同解碼方式,在15年以前,瀏覽器為了能夠播放不同格式的視頻,就要調用電腦中不同的播放器,這個過程的寫法非常麻煩。隨著技術不斷地整合,時至今日,在頁面中播放視頻不需要這么復雜的寫法了,但是因為每個瀏覽器都不是包打一切,因此,雖然不用指定播放器,但是也要預設不同格式的視頻來應對不同的瀏覽器。

          因此,我們在這一部分的學習中除了講解如何向頁面添加不同格式的音視頻外還會告訴大家如何為音視頻轉換格式。

          為頁面添加音頻、視頻

          添加音頻使用<audio></audio>標簽,這個標簽被所有瀏覽器支持,是html5推薦的音頻導入標簽,但是遺憾的是在html4標準中是不被支持的或者說是非法的。

          這里給大家簡要介紹一下html5和html4的區別。

          簡單來說呢,一個html文件的第一條語句是<!DOCTYPE HTML>,它就是HTML5標準的文件。如果是html4,它的第一條聲明語句有三種寫法,像這樣

          一:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

          二:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

          三:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">

          版權聲明:本文為CSDN博主「痦子」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。

          原文鏈接:https://blog.csdn.net/yh1061632045/article/details/81518141

          這讓我想到了孔乙己的"茴"字的多種寫法

          是不是很麻煩,其實html5比html4更簡單,功能更強大,而且我們一直以html5的標準進行學習,所以大家不必糾結。

          下面我們導入一個音頻試試吧。示例代碼如下:

          <!DOCTYPE HTML>
            <html>
            <head> 
            <title>音視頻導入</title>
            </head> 
            <body>
            <audio controls="controls"> 
              <source src="audio/千年的祈禱.mp3" type="audio/mp3" />
            </audio>
            </body> 
            </html>

          頁面效果如下:

          其中controls屬性就是用來顯示播放控制界面的,就是這個:(偷懶的話可以寫成"controls"就ok,不必加"="以及后面的內容了。)

          如果以后您使用自己編寫的控制界面,就可以不添加這個屬性。

          刪掉這個屬性后就是這樣:這樣為自定義的播放控制界面留出了位置。

          <audio></audio>標簽夾著<source>標簽,一個<audio></audio>標簽中可以添加多個<source>用以支持不同的格式要求。示例代碼如下:(這段代碼來自w3school)

          <audio controls="controls"> 
            <source src="song.ogg" type="audio/ogg" /> 
            <source src="song.mp3" type="audio/mpeg" />
            Your browser does not support the audio element.<!--你的瀏覽器不支持這個音頻元素-->
          </audio>

          type屬性是告訴瀏覽器音樂文件的類型。

          不同格式的文件的生成需要我們自己去做,這就涉及到如何給一個音頻文件進行格式轉化的問題。這個問題我們明天再說,今天先學習為頁面添加音頻和視頻。

          下面我們來看一下視頻的導入方法,示例代碼如下:

          <video controls> 
            <source src="video/阿塔麗.mp4" type="video/mp4" />
          </video>

          頁面效果如下:

          我們可以通過設置height和width屬性來控制視頻的面積。實例代碼如下:

          <video controls width="850" height="500" > 
            <source src="video/阿塔麗.mp4" type="video/mp4" />
          </video>

          頁面效果如下:

          視頻畫面變小了,和視頻并排的是我們之前添加的音頻文件,由此可知,這兩個元素都是內聯元素。

          今天的內容結束了,明天我們繼續學習格式轉換和為不同瀏覽器預設不同音視頻格式的方法。

          如果您有任何疑問請給我留言,如有問題或錯誤請予以斧正!

          HTML完整學習目錄

          HTML序章(學習目的、對象、基本概念)——零基礎自學網頁制作

          HTML是什么?——零基礎自學網頁制作

          第一個HTML頁面如何寫?——零基礎自學網頁制作

          HTML頁面中head標簽有啥用?——零基礎自學網頁制作

          初識meta標簽與SEO——零基礎自學網頁制作

          HTML中的元素使用方法1——零基礎自學網頁制作

          HTML中的元素使用方法2——零基礎自學網頁制作

          HTML元素中的屬性1——零基礎自學網頁制作

          HTML元素中的屬性2(路徑詳解)——零基礎自學網頁制作

          使用HTML添加表格1(基本元素)——零基礎自學網頁制作

          使用HTML添加表格2(表格頭部與腳部)——零基礎自學網頁制作

          使用HTML添加表格3(間距與顏色)——零基礎自學網頁制作

          使用HTML添加表格4(行顏色與表格嵌套)——零基礎自學網頁制作

          16進制顏色表示與RGB色彩模型——零基礎自學網頁制作

          HTML中的塊級元素與內聯元素——零基礎自學網頁制作

          初識HTML中的<div>塊元素——零基礎自學網頁制作

          在HTML頁面中嵌入其他頁面的方法——零基礎自學網頁制作

          封閉在家學網頁制作!為頁面嵌入PDF文件——零基礎自學網頁制作

          HTML表單元素初識1——零基礎自學網頁制作

          HTML表單元素初識2——零基礎自學網頁制作

          HTML表單3(下拉列表、多行文字輸入)——零基礎自學網頁制作

          HTML表單4(form的action、method屬性)——零基礎自學網頁制作

          HTML列表制作講解——零基礎自學網頁制作

          為HTML頁面添加視頻、音頻的方法——零基礎自學網頁制作

          音視頻格式轉換神器與html視頻元素加字幕——零基礎自學網頁制作

          HTML中使用<a>標簽實現文本內鏈接——零基礎自學網頁制作

          果你是 JavaScript 的新手,一些像 “module bundlers vs module loaders”、“Webpack vs Browserify” 和 “AMD vs.CommonJS” 這樣的術語,很快讓你不堪重負。

          JavaScript 模塊系統可能令人生畏,但理解它對 Web 開發人員至關重要。

          在這篇文章中,我將以簡單的言語(以及一些代碼示例)為你解釋這些術語。 希望這對你有會有幫助!

          什么是模塊?

          好作者能將他們的書分成章節,優秀的程序員將他們的程序劃分為模塊。

          就像書中的章節一樣,模塊只是文字片段(或代碼,視情況而定)的集群。然而,好的模塊是高內聚低松耦的,具有不同的功能,允許在必要時對它們進行替換、刪除或添加,而不會擾亂整體功能。

          為什么使用模塊?

          使用模塊有利于擴展、相互依賴的代碼庫,這有很多好處。在我看來,最重要的是:

          1)可維護性: 根據定義,模塊是高內聚的。一個設計良好的模塊旨在盡可能減少對代碼庫部分的依賴,這樣它就可以獨立地增強和改進,當模塊與其他代碼片段解耦時,更新單個模塊要容易得多。

          回到我們的書的例子,如果你想要更新你書中的一個章節,如果對一個章節的小改動需要你調整每一個章節,那將是一場噩夢。相反,你希望以這樣一種方式編寫每一章,即可以在不影響其他章節的情況下進行改進。

          2)命名空間: 在 JavaScript 中,頂級函數范圍之外的變量是全局的(這意味著每個人都可以訪問它們)。因此,“名稱空間污染”很常見,完全不相關的代碼共享全局變量。

          在不相關的代碼之間共享全局變量在開發中是一個大禁忌。正如我們將在本文后面看到的,通過為變量創建私有空間,模塊允許我們避免名稱空間污染。

          3)可重用性:坦白地說:我們將前寫過的代碼復制到新項目中。 例如,假設你從之前項目編寫的一些實用程序方法復制到當前項目中。

          這一切都很好,但如果你找到一個更好的方法來編寫代碼的某些部分,那么你必須記得回去在曾經使用過的其他項目更新它。

          這顯然是在浪費時間。如果有一個我們可以一遍又一遍地重復使用的模塊,不是更容易嗎?

          如何創建模塊?

          有多種方法來創建模塊,來看幾個:

          模塊模式

          模塊模式用于模擬類的概念(因為 JavaScript 本身不支持類),因此我們可以在單個對象中存儲公共和私有方法和變量——類似于在 Java 或 Python 等其他編程語言中使用類的方式。這允許我們為想要公開的方法創建一個面向公共的 API,同時仍然將私有變量和方法封裝在閉包范圍中。

          有幾種方法可以實現模塊模式。在第一個示例中,將使用匿名閉包,將所有代碼放在匿名函數中來幫助我們實現目標。(記住:在 JavaScript 中,函數是創建新作用域的唯一方法。)

          例一:匿名閉包

          (function () {
            // 將這些變量放在閉包范圍內實現私有化
            
            var myGrades = [93, 95, 88, 0, 55, 91];
            
            var average = function() {
              var total = myGrades.reduce(function(accumulator, item) {
                return accumulator + item}, 0);
              
                return '平均分 ' + total / myGrades.length + '.';
            }
          
            var failing = function(){
              var failingGrades = myGrades.filter(function(item) {
                return item < 70;});
                
              return '掛機科了 ' + failingGrades.length + ' 次。';
            }
          
            console.log(failing()); // 掛機科了次
          
          }());
          
          
          

          使用這個結構,匿名函數就有了自己的執行環境或“閉包”,然后我們立即執行。這讓我們可以從父(全局)命名空間隱藏變量。

          這種方法的優點是,你可以在這個函數中使用局部變量,而不會意外地覆蓋現有的全局變量,但仍然可以訪問全局變量,就像這樣:

              var global = '你好,我是一個全局變量。)';
              
             (function () {
                // 將這些變量放在閉包范圍內實現私有化
                
                var myGrades = [93, 95, 88, 0, 55, 91];
                
                var average = function() {
                  var total = myGrades.reduce(function(accumulator, item) {
                    return accumulator + item}, 0);
                  
                    return '平均分 ' + total / myGrades.length + '.';
                }
              
                var failing = function(){
                  var failingGrades = myGrades.filter(function(item) {
                    return item < 70;});
                    
                  return '掛機科了 ' + failingGrades.length + ' 次。';
                }
              
                console.log(failing()); // 掛機科了次
                onsole.log(global); // 你好,我是一個全局變量。
              
              }());
          

          注意,匿名函數的圓括號是必需的,因為以關鍵字 function 開頭的語句通常被認為是函數聲明(請記住,JavaScript 中不能使用未命名的函數聲明)。因此,周圍的括號將創建一個函數表達式,并立即執行這個函數,這還有另一種叫法 立即執行函數(IIFE)。如果你對這感興趣,可以在這里了解到更多。

          例二:全局導入

          jQuery 等庫使用的另一種流行方法是全局導入。它類似于我們剛才看到的匿名閉包,只是現在我們作為參數傳入全局變量:

          (function (globalVariable) {
          
            // 在這個閉包范圍內保持變量的私有化
            var privateFunction = function() {
              console.log('Shhhh, this is private!');
            }
          
            // 通過 globalVariable 接口公開下面的方法
           // 同時將方法的實現隱藏在 function() 塊中
          
            globalVariable.each = function(collection, iterator) {
              if (Array.isArray(collection)) {
                for (var i = 0; i < collection.length; i++) {
                  iterator(collection[i], i, collection);
                }
              } else {
                for (var key in collection) {
                  iterator(collection[key], key, collection);
                }
              }
            };
          
            globalVariable.filter = function(collection, test) {
              var filtered = [];
              globalVariable.each(collection, function(item) {
                if (test(item)) {
                  filtered.push(item);
                }
              });
              return filtered;
            };
          
            globalVariable.map = function(collection, iterator) {
              var mapped = [];
              globalUtils.each(collection, function(value, key, collection) {
                mapped.push(iterator(value));
              });
              return mapped;
            };
          
            globalVariable.reduce = function(collection, iterator, accumulator) {
              var startingValueMissing = accumulator === undefined;
          
              globalVariable.each(collection, function(item) {
                if(startingValueMissing) {
                  accumulator = item;
                  startingValueMissing = false;
                } else {
                  accumulator = iterator(accumulator, item);
                }
              });
          
              return accumulator;
          
            };
          
           }(globalVariable));
          

          在這個例子中,globalVariable 是唯一的全局變量。與匿名閉包相比,這種方法的好處是可以預先聲明全局變量,使得別人更容易閱讀代碼。

          例三:對象接口

          另一種方法是使用立即執行函數接口對象創建模塊,如下所示:

          var myGradesCalculate = (function () {
              
            // 將這些變量放在閉包范圍內實現私有化
            var myGrades = [93, 95, 88, 0, 55, 91];
          
            // 通過接口公開這些函數,同時將模塊的實現隱藏在function()塊中
          
            return {
              average: function() {
                var total = myGrades.reduce(function(accumulator, item) {
                  return accumulator + item;
                  }, 0);
                  
                return'平均分 ' + total / myGrades.length + '.';
              },
          
              failing: function() {
                var failingGrades = myGrades.filter(function(item) {
                    return item < 70;
                  });
          
                return '掛科了' + failingGrades.length + ' 次.';
              }
            }
          })();
          
          myGradesCalculate.failing(); // '掛科了 2 次.' 
          myGradesCalculate.average(); // '平均分 70.33333333333333.'
          
          

          正如您所看到的,這種方法允許我們通過將它們放在 return 語句中(例如算平均分和掛科數方法)來決定我們想要保留的變量/方法(例如 myGrades)以及我們想要公開的變量/方法。

          例四:顯式模塊模式

          這與上面的方法非常相似,只是它確保所有方法和變量在顯式公開之前都是私有的:

          var myGradesCalculate = (function () {
              
            // 將這些變量放在閉包范圍內實現私有化
            var myGrades = [93, 95, 88, 0, 55, 91];
            
            var average = function() {
              var total = myGrades.reduce(function(accumulator, item) {
                return accumulator + item;
                }, 0);
                
              return'平均分 ' + total / myGrades.length + '.';
            };
          
            var failing = function() {
              var failingGrades = myGrades.filter(function(item) {
                  return item < 70;
                });
          
              return '掛科了' + failingGrades.length + ' 次.';
            };
          
            // Explicitly reveal public pointers to the private functions 
            // that we want to reveal publicly
          
            return {
              average: average,
              failing: failing
            }
          })();
          
          myGradesCalculate.failing(); // '掛科了 2 次.' 
          myGradesCalculate.average(); // '平均分 70.33333333333333.'
          
          

          這可能看起來很多,但它只是模塊模式的冰山一角。 以下是我在自己的探索中發現有用的一些資源:

          • Learning JavaScript Design Patterns:作者是 Addy Osmani,一本簡潔又令人印象深刻的書籍,蘊藏著許多寶藏。
          • Adequately Good by Ben Cherry:包含模塊模式的高級用法示例。
          • Blog of Carl Danley:模塊模式概覽,也是 JavaScript 許多設計模式的資源庫。

          CommonJS 和 AMD

          所有這些方法都有一個共同點:使用單個全局變量將其代碼包裝在函數中,從而使用閉包作用域為自己創建一個私有名稱空間。

          雖然每種方法都有效且都有各自特點,但卻都有缺點。

          首先,作為開發人員,你需要知道加載文件的正確依賴順序。例如,假設你在項目中使用 Backbone,因此你可以將 Backbone 的源代碼 以<script> 腳本標簽的形式引入到文件中。

          但是,由于 Backbone 對 Underscore.js 有很強的依賴性,因此 Backbone 文件的腳本標記不能放在Underscore.js 文件之前。

          作為一名開發人員,管理依賴關系并正確處理這些事情有時會令人頭痛。

          另一個缺點是它們仍然會導致名稱空間沖突。例如,如果兩個模塊具有相同的名稱怎么辦?或者,如果有一個模塊的兩個版本,并且兩者都需要,該怎么辦?

          幸運的是,答案是肯定的。

          有兩種流行且實用的方法:CommonJSAMD

          CommonJS

          CommonJS 是一個志愿者工作組,負責設計和實現用于聲明模塊的 JavaScript API。

          CommonJS 模塊本質上是一個可重用的 JavaScript,它導出特定的對象,使其可供其程序中需要的其他模塊使用。 如果你已經使用 Node.js 編程,那么你應該非常熟悉這種格式。

          使用 CommonJS,每個 JavaScript 文件都將模塊存儲在自己獨立的模塊上下文中(就像將其封裝在閉包中一樣)。 在此范圍內,我們使用 module.exports 導出模塊,或使用 require 來導入模塊。

          在定義 CommonJS 模塊時,它可能是這樣的:

          function myModule() {
            this.hello = function() {
              return 'hello!';
            }
             
            this.goodbye = function() {
              return 'goodbye!';
            }
          }
          
          module.exports = myModule;
          
          

          我們使用特殊的對象模塊,并將函數的引用放入 module.exports 中。這讓 CommonJS 模塊系統知道我們想要公開什么,以便其他文件可以使用它。

          如果想使用 myModule,只需要使用 require 方法就可以,如下:

          var myModule = require('myModule');
          
          var myModuleInstance = new myModule();
          myModuleInstance.hello(); // 'hello!'
          myModuleInstance.goodbye(); // 'goodbye!'
          
          

          與前面討論的模塊模式相比,這種方法有兩個明顯的好處:

          1. 避免全局命名空間污染
          2. 依賴關系更加明確

          另外需要注意的是,CommonJS 采用服務器優先方法并同步加載模塊。 這很重要,因為如果我們需要三個其他模塊,它將逐個加載它們。

          現在,它在服務器上運行良好,但遺憾的是,在為瀏覽器編寫 JavaScript 時使用起來更加困難。 可以這么說,從網上讀取模塊比從磁盤讀取需要更長的時間。 只要加載模塊的腳本正在運行,它就會阻止瀏覽器運行其他任何內容,直到完成加載,這是因為 JavaScript 是單線程且 CommonJS 是同步加載的。

          AMD

          CommonJS一切都很好,但是如果我們想要異步加載模塊呢? 答案是 異步模塊定義,簡稱 AMD

          使用 AMD 的加載模塊如下:

          define(['myModule', 'myOtherModule'], function(myModule, myOtherModule) {
            console.log(myModule.hello());
          });
          
          

          define 函數的第一個參數是一個數組,數組中是依賴的各種模塊。這些依賴模塊在后臺(以非阻塞的方式)加載進來,一旦加載完畢,define 函數就會調用第二個參數,即回調函數執行操作。

          接下來,回調函數接收參數,即依賴模塊 - 示例中就是 myModulemyOtherModule - 允許函數使用這些依賴項, 最后,所依賴的模塊本身也必須使用 define 關鍵字來定義。例如,myModule如下所示:

          define([], function() {
          
            return {
              hello: function() {
                console.log('hello');
              },
              goodbye: function() {
                console.log('goodbye');
              }
            };
          });
          
          

          因此,與 CommonJS 不同,AMD 采用瀏覽器優先的方法和異步行為來完成工作。 (注意,有很多人堅信在開始運行代碼時動態加載文件是不利的,我們將在下一節關于模塊構建的內容中探討更多內容)。

          除了異步性,AMD 的另一個好處是模塊可以是對象,函數,構造函數,字符串,JSON 和許多其他類型,而CommonJS 只支持對象作為模塊。

          也就是說,和CommonJS相比,AMD不兼容io、文件系統或者其他服務器端的功能特性,而且函數包裝語法與簡單的require 語句相比有點冗長。

          UMD

          對于同時支持 AMD 和 CommonJS 特性的項目,還有另一種格式:通用模塊定義(Universal Module Definition, UMD)。

          UMD 本質上創造了一種使用兩者之一的方法,同時也支持全局變量定義。因此,UMD 模塊能夠同時在客戶端和服務端同時工作。

          簡單看一下 UMD 是怎樣工作的:

          (function (root, factory) {
            if (typeof define === 'function' && define.amd) {
                // AMD
              define(['myModule', 'myOtherModule'], factory);
            } else if (typeof exports === 'object') {
                // CommonJS
              module.exports = factory(require('myModule'), require('myOtherModule'));
            } else {
              // Browser globals (Note: root is window)
              root.returnExports = factory(root.myModule, root.myOtherModule);
            }
          }(this, function (myModule, myOtherModule) {
            // Methods
            function notHelloOrGoodbye(){}; // A private method
            function hello(){}; // A public method because it's returned (see below)
            function goodbye(){}; // A public method because it's returned (see below)
          
            // Exposed public methods
            return {
                hello: hello,
                goodbye: goodbye
            }
          }));
          

          Github 上 enlightening repo 里有更多關于 UMD 的例子。

          Native JS

          你可能已經注意到,上面的模塊都不是 JavaScript 原生的。相反,我們已經創建了通過使用模塊模式、CommonJS 或 AMD 來模擬模塊系統的方法。

          幸運的是,TC39(定義 ECMAScript 的語法和語義的標準組織)一幫聰明的人已經引入了ECMAScript 6(ES6)的內置模塊。

          ES6 為導入導出模塊提供了很多不同的可能性,已經有許多其他人花時間解釋這些,下面是一些有用的資源:

          • jsmodules.io
          • exploringjs.com

          與 CommonJS 或 AMD 相比,ES6 模塊最大的優點在于它能夠同時提供兩方面的優勢:簡明的聲明式語法和異步加載,以及對循環依賴項的更好支持。

          也許我個人最喜歡的 ES6 模塊功能是它的導入模塊是導出時模塊的實時只讀視圖。(相比起 CommonJS,導入的是導出模塊的拷貝副本,因此也不是實時的)。

          下面是一個例子:

          // lib/counter.js
          
          var counter = 1;
          
          function increment() {
            counter++;
          }
          
          function decrement() {
            counter--;
          }
          
          module.exports = {
            counter: counter,
            increment: increment,
            decrement: decrement
          };
          
          
          // src/main.js
          
          var counter = require('../../lib/counter');
          
          counter.increment();
          console.log(counter.counter); // 1
          
          

          在這個例子中,我們基本上創建了兩個模塊的對象:一個用于導出它,一個在我們需要的時候引入。

          此外,在 main.js 中的對象目前是與原始模塊是相互獨立的,這就是為什么即使我們執行 increment 方法,它仍然返回 1,因為引入的變量和最初導入的變量是毫無關聯的。需要改變你引入的對象唯一的方式是手動執行增加:

          counter.counter++;
          console.log(counter.counter); // 2
          

          另一方面,ES6創建了我們導入的模塊的實時只讀視圖:

          // lib/counter.js
          export let counter = 1;
          
          export function increment() {
            counter++;
          }
          
          export function decrement() {
            counter--;
          }
          
          
          // src/main.js
          import * as counter from '../../counter';
          
          console.log(counter.counter); // 1
          counter.increment();
          

          console.log(counter.counter); // 2

          超酷?我發現這一點是因為ES6允許你可以把你定義的模塊拆分成更小的模塊而不用刪減功能,然后你還能反過來把它們合成到一起, 完全沒問題。

          什么是模塊打包?

          總體上看,模塊打包只是將一組模塊(及其依賴項)以正確的順序拼接到一個文件(或一組文件)中的過程。正如 Web開發的其它方方面面,棘手的問題總是潛藏在具體的細節里。

          為什么需要打包?

          將程序劃分為模塊時,通常會將這些模塊組織到不同的文件和文件夾中。 有可能,你還有一組用于正在使用的庫的模塊,如 Underscore 或 React。

          因此,每個文件都必須以一個 <script> 標簽引入到主 HTML 文件中,然后當用戶訪問你的主頁時由瀏覽器加載進來。 每個文件使用 <script> 標簽引入,意味著瀏覽器不得不分別逐個的加載它們。

          這對于頁面加載時間來說簡直是噩夢。

          為了解決這個問題,我們將所有文件打包或“拼接”到一個大文件(或視情況而定的幾個文件),以減少請求的數量。 當你聽到開發人員談論“構建步驟”或“構建過程”時,這就是他們所談論的內容。

          另一種加速構建操作的常用方法是“縮減”打包代碼。 縮減是從源代碼中移除不必要的字符(例如,空格,注釋,換行符等)的過程,以便在不改變代碼功能的情況下減少內容的整體大小。

          較少的數據意味著瀏覽器處理時間會更快,從而減少了下載文件所需的時間。 如果你見過具有 “min” 擴展名的文件,如 “underscore-min.js” ,可能會注意到與完整版相比,縮小版本非常小(不過很難閱讀)。

          除了捆綁和/或加載模塊之外,模塊捆綁器還提供了許多其他功能,例如在進行更改時生成自動重新編譯代碼或生成用于調試的源映射。

          構建工具(如 Gulp 和 Grunt)能為開發者直接進行拼接和縮減,確保為開發人員提供可讀代碼,同時有利于瀏覽器執行的代碼。

          打包模塊有哪些不同的方法?

          當你使用一種標準模塊模式(上部分討論過)來定義模塊時,拼接和縮減文件非常有用。 你真正在做的就是將一堆普通的 JavaScript 代碼捆綁在一起。

          但是,如果你堅持使用瀏覽器無法解析的非原生模塊系統(如 CommonJS 或 AMD(甚至是原生 ES6模塊格式)),則需要使用專門工具將模塊轉換為排列正確、瀏覽器可解析的代碼。 這就是 Browserify,RequireJS,Webpack 和其他“模塊打包工具”或“模塊加載工具”的用武之地。

          除了打包和/或加載模塊之外,模塊打包器還提供了許多其他功能,例如在進行更改時生成自動重新編譯代碼或生成用于調試的源映射。

          下面是一些常見的模塊打包方法:

          打包 CommonJS

          正如前面所知道的,CommonJS以同步方式加載模塊,這沒有什么問題,只是它對瀏覽器不實用。我提到過有一個解決方案——其中一個是一個名為 Browserify 的模塊打包工具。Browserify 是一個為瀏覽器編譯 CommonJS模塊的工具。

          例如,有個 main.js 文件,它導入一個模塊來計算一組數字的平均值:

          var myDependency = require(‘myDependency’);
          
          var myGrades = [93, 95, 88, 0, 91];
          
          var myAverageGrade = myDependency.average(myGrades);
          

          在這種情況下,我們有一個依賴項(myDependency),使用下面的命令,Browserify 以 main.js 為入口把所有依賴的模塊遞歸打包成一個文件:

          browserify main.js -o bundle.js
          

          Browserify 通過跳入文件分析每一個依賴的 抽象語法樹(AST),以便遍歷項目的整個依賴關系圖。一旦確定了依賴項的結構,就把它們按正確的順序打包到一個文件中。然后,在 html 里插入一個用于引入 “bundle.js” 的 <script> 標簽,從而確保你的源代碼在一個 HTTP 請求中完成下載。

          類似地,如果有多個文件且有多個依賴時,只需告訴 Browserify 的入口文件路徑即可。最后打包后的文件可以通過 Minify-JS 之類的工具壓縮打包后的代碼。

          打包 AMD

          如果你正在使用 AMD,你需要使用像 RequireJS 或者 Curl 這樣的 AMD 加載器。模塊加載器(與模塊打包工具不同)會動態加載程序需要運行的模塊。

          提醒一下,AMD 與 CommonJS 的主要區別之一是它以異步方式加載模塊。 從這個意義上說,對于 AMD,從技術上講,實際上并不需要構建步驟,因為異步加載模塊意味著在運行過程中逐步下載那些程序所需要的文件,而不是用戶剛進入頁面就一下把所有文件都下載下來。

          但實際上,對于每個用戶操作而言,隨著時間的推移,大容量請求的開銷在生產中沒有多大意義。 大多數 Web 開發人員仍然使用構建工具打包和壓縮 AMD 模塊以獲得最佳性能,例如使用 RequireJS 優化器,r.js 等工具。

          總的來說,AMD 和 CommonJS 在打包方面的區別在于:在開發期間,AMD 可以省去任何構建過程。當然,在代碼上線前,要使用優化工具(如 r.js)進行優化。

          Webpack

          就打包工具而言,Webpack 是一個新事物。它被設計成與你使用的模塊系統無關,允許開發人員在適當的情況下使用 CommonJS、AMD 或 ES6。

          你可能想知道,為什么我們需要 Webpack,而我們已經有了其他打包工具了,比如 Browserify 和 RequireJS,它們可以完成工作,并且做得非常好。首先,Webpack 提供了一些有用的特性,比如 “代碼分割”(code splitting) —— 一種將代碼庫分割為“塊(chunks)”的方式,從而能實現按需加載。

          例如,如果你的 Web 應用程序,其中只需要某些代碼,那么將整個代碼庫都打包進一個大文件就不是很高效。 在這種情況下,可以使用代碼分割,將需要的部分代碼抽離在"打包塊",在執行按需加載,從而避免在最開始就遇到大量負載的麻煩。

          代碼分割只是 Webpack 提供的眾多引人注目的特性之一,網上有很多關于 “Webpack 與 Browserify 誰更好” 的激烈討論。以下是一些客觀冷靜的討論,幫助我稍微理清了頭緒:

          • https://gist.github.com/subst...點擊預覽
          • http://mattdesl.svbtle.com/br...
          • http://blog.namangoel.com/bro...

          ES6 模塊

          當前 JS 模塊規范(CommonJS, AMD) 與 ES6 模塊之間最重要的區別是 ES6 模塊的設計考慮到了靜態分析。這意味著當你導入模塊時,導入的模塊在編譯階段也就是代碼開始運行之前就被解析了。這允許我們在運行程序之前移,移除那些在導出模塊中不被其它模塊使用的部分。移除不被使用的模塊能節省空間,且有效地減少瀏覽器的壓力。

          一個常見的問題,使用一些工具,如 Uglify.js ,縮減代碼時,有一個死碼刪除的處理,它和 ES6 移除沒用的模塊又有什么不同呢?只能說 “視情況而定”。

          死碼消除(Dead codeelimination)是一種編譯器原理中編譯最優化技術,它的用途是移除對程序運行結果沒有任何影響的代碼。移除這類的代碼有兩種優點,不但可以減少程序的大小,還可以避免程序在運行中進行不相關的運算行為,減少它運行的時間。不會被運行到的代碼(unreachable code)以及只會影響到無關程序運行結果的變量(Dead Variables),都是死碼(Dead code)的范疇。

          有時,在 UglifyJS 和 ES6 模塊之間死碼消除的工作方式完全相同,有時則不然。如果你想驗證一下, Rollup’s wiki 里有個很好的示例。

          ES6 模塊的不同之處在于死碼消除的不同方法,稱為“tree shaking”。“tree shaking” 本質上是死碼消除反過程。它只包含包需要運行的代碼,而非排除不需要的代碼。來看個例子:

          假設有一個帶有多個函數的 utils.js 文件,每個函數都用 ES6 的語法導出:

          export function each(collection, iterator) {
            if (Array.isArray(collection)) {
              for (var i = 0; i < collection.length; i++) {
                iterator(collection[i], i, collection);
              }
            } else {
              for (var key in collection) {
                iterator(collection[key], key, collection);
              }
            }
           }
          
          export function filter(collection, test) {
            var filtered = [];
            each(collection, function(item) {
              if (test(item)) {
                filtered.push(item);
              }
            });
            return filtered;
          }
          
          export function map(collection, iterator) {
            var mapped = [];
            each(collection, function(value, key, collection) {
              mapped.push(iterator(value));
            });
            return mapped;
          }
          
          export function reduce(collection, iterator, accumulator) {
              var startingValueMissing = accumulator === undefined;
          
              each(collection, function(item) {
                if(startingValueMissing) {
                  accumulator = item;
                  startingValueMissing = false;
                } else {
                  accumulator = iterator(accumulator, item);
                }
              });
          
              return accumulator;
          }
          

          接著,假設我們不知道要在程序中使用什么 utils.js 中的哪個函數,所以我們將上述的所有模塊導入main.js中,如下所示:

          import * as Utils from ‘./utils.js’;
          

          最終,我們只用到的 each 方法:

          import * as Utils from ‘./utils.js’;
          
          Utils.each([1, 2, 3], function(x) { console.log(x) });
          
          

          “tree shaken” 版本的 main.js 看起來如下(一旦模塊被加載后):

          function each(collection, iterator) {
            if (Array.isArray(collection)) {
              for (var i = 0; i < collection.length; i++) {
                iterator(collection[i], i, collection);
              }
            } else {
              for (var key in collection) {
                iterator(collection[key], key, collection);
              }
            }
           };
          
          each([1, 2, 3], function(x) { console.log(x) });
          
          

          注意:只導出我們使用的 each 函數。

          同時,如果決定使用 filte r函數而不是每個函數,最終會看到如下的結果:

          import * as Utils from ‘./utils.js’;
          
          Utils.filter([1, 2, 3], function(x) { return x === 2 });
          

          tree shaken 版本如下:

          function each(collection, iterator) {
            if (Array.isArray(collection)) {
              for (var i = 0; i < collection.length; i++) {
                iterator(collection[i], i, collection);
              }
            } else {
              for (var key in collection) {
                iterator(collection[key], key, collection);
              }
            }
           };
          
          function filter(collection, test) {
            var filtered = [];
            each(collection, function(item) {
              if (test(item)) {
                filtered.push(item);
              }
            });
            return filtered;
          };
          
          filter([1, 2, 3], function(x) { return x === 2 });
          

          此時,each 和 filter 函數都被包含進來。這是因為 filter 在定義時使用了 each。因此也需要導出該函數模塊以保證程序正常運行。

          構建 ES6 模塊

          我們知道 ES6 模塊的加載方式與其他模塊格式不同,但我們仍然沒有討論使用 ES6 模塊時的構建步驟。

          遺憾的是,因為瀏覽器對 ES6模 塊的原生支持還不夠完善,所以現階段還需要我們做一些補充工作。

          下面是幾個在瀏覽器中 構建/轉換 ES6 模塊的方法,其中第一個是目前最常用的方法:

          1. 使用轉換器(例如 Babel 或 Traceur)以 CommonJS、AMD 或 UMD 格式將 ES6 代碼轉換為 ES5 代碼,然后再通過 Browserify 或 Webpack 一類的構建工具來進行構建。
          2. 使用 Rollup.js,這其實和上面差不多,只是 Rollup 捎帶 ES6 模塊的功能,在打包之前靜態分析ES6 代碼和依賴項。 它利用 “tree shaking” 技術來優化你的代碼。 總言,當您使用ES6模塊時,Rollup.js 相對于 Browserify 或 Webpack 的主要好處是 tree shaking 能讓打包文件更小。 需要注意的是,Rollup提 供了幾種格式來的打包代碼,包括 ES6,CommonJS,AMD,UMD 或 IIFE。 IIFE 和 UMD 捆綁包可以直接在瀏覽器中工作,但如果你選擇打包 AMD,CommonJS 或 ES6,需需要尋找能將代碼轉成瀏覽器能理解運行的代碼的方法(例如,使用 Browserify, Webpack,RequireJS等)。

          小心踩坑

          作為 web 開發人員,我們必須經歷很多困難。轉換語法優雅的ES6代碼以便在瀏覽器里運行并不總是容易的。

          問題是,什么時候 ES6 模塊可以在瀏覽器中運行而不需要這些開銷?

          答案是:“盡快”。

          ECMAScript 目前有一個解決方案的規范,稱為 ECMAScript 6 module loader API。簡而言之,這是一個綱領性的、基于 Promise 的 API,它支持動態加載模塊并緩存它們,以便后續導入不會重新加載模塊的新版本。

          它看起來如下:

          // myModule.js
          
          export class myModule {
            constructor() {
              console.log('Hello, I am a module');
            }
          
            hello() {
              console.log('hello!');
            }
          
            goodbye() {
              console.log('goodbye!');
            }
          }
          


           // main.js
          System.import(‘myModule’).then(function(myModule) {
            new myModule.hello();
          });
          
          // ‘hello!’
          

          你亦可直接對 script 標簽指定 “type=module” 來定義模塊,如:

          <script type="module">
            // loads the 'myModule' export from 'mymodule.js'
            import { hello } from 'mymodule';
          
            new Hello(); // 'Hello, I am a module!'
          </script>
          
          

          更加詳細的介紹也可以在 Github 上查看:es-module-loader

          此外,如果您想測試這種方法,請查看 SystemJS,它建立在 ES6 Module Loader polyfill 之上。 SystemJS 在瀏覽器和 Node 中動態加載任何模塊格式(ES6模塊,AMD,CommonJS 或 全局腳本)。

          它跟蹤“模塊注冊表”中所有已加載的模塊,以避免重新加載先前已加載過的模塊。 更不用說它還會自動轉換ES6模塊(如果只是設置一個選項)并且能夠從任何其他類型加載任何模塊類型!

          有了原生的 ES6 模塊后,還需要模塊打包嗎?

          對于日益普及的 ES6 模塊,下面有一些有趣的觀點:

          HTTP/2 會讓模塊打包過時嗎?

          對于 HTTP/1,每個TCP連接只允許一個請求。這就是為什么加載多個資源需要多個請求。有了 HTTP/2,一切都變了。HTTP/2 是完全多路復用的,這意味著多個請求和響應可以并行發生。因此,我們可以在一個連接上同時處理多個請求。

          由于每個 HTTP 請求的成本明顯低于HTTP/1,因此從長遠來看,加載一組模塊不會造成很大的性能問題。一些人認為這意味著模塊打包不再是必要的,這當然有可能,但這要具體情況具體分析了。

          例如,模塊打包還有 HTTP/2 沒有好處,比如移除冗余的導出模塊以節省空間。 如果你正在構建一個性能至關重要的網站,那么從長遠來看,打包可能會為你帶來增量優勢。 也就是說,如果你的性能需求不是那么極端,那么通過完全跳過構建步驟,可以以最小的成本節省時間。

          總的來說,絕大多數網站都用上 HTTP/2 的那個時候離我們現在還很遠。我預測構建過程將會保留,至少在近期內。

          CommonJS、AMD 與 UMD 會被淘汰嗎?

          一旦 ES6 成為模塊標準,我們還需要其他非原生模塊規范嗎?

          我覺得還有。

          Web 開發遵守一個標準方法進行導入和導出模塊,而不需要中間構建步驟——網頁開發長期受益于此。但 ES6 成為模塊規范需要多長時間呢?

          機會是有,但得等一段時間 。

          再者,眾口難調,所以“一個標準的方法”可能永遠不會成為現實。

          總結

          希望這篇文章能幫你理清一些開發者口中的模塊和模塊打包的相關概念,共進步。

          久沒參與魔方的發布工作了,今天提筆,有種莫名的生疏和惶恐,從下午四點半一直坐到現在,3個小時,刪刪寫寫。千言無語,千頭萬緒,千百般滋味涌上心頭,真的很懷念那些年每個周五定期一更的日子,簡單而快樂。

          2006年12月Vista優化大師發布第一個測試版,再到2009年9月發布魔方0.1,時光荏苒,便過去了9年。很多當初最早的那批朋友,現在都成了老友,有時候一覺醒來,發現軟媒的那個最老用戶群里的消息閃爍,由衷的開心,沒有什么比熟悉的感覺更有韻味。

          產品部的魔方一哥過來催文了,盡管他說理解我的心情啊什么的,但是為了保持一貫的不加班作風,我得直入主題了——

          魔方6.16正式版現在發布,“忍不住”還是讓產品組加了新功能,本來說好的要克制加新功能的沖動,重點大幅改進清理等原有常用功能的。

          這次忍不住要加的新功能,大家在標題里面已經看到了,就是一鍵提取微軟官方的精美聚焦壁紙(美化大師頂部加入了“聚焦壁紙”)。熟悉微軟的朋友都知道,Win10TH2開始系統增加了“Windows 聚焦”壁紙,大家在系統設置的“個性化”-“鎖屏設置”里面可以設置鎖屏的背景壁紙為“Windows 聚焦”,如下圖所示:

          這些微軟官方提供的鎖屏壁紙還是非常精美的,會自動的下載并切換,于是魔方便加入了提取功能,需要注意的是,這兒的提取,是提取的本機已經下載的,如果您之前沒有開啟過Windows聚焦功能,是抓不到的。這個抓取功能更方便的是讓大家隨時保存最新的。那過去的好看聚焦壁紙怎么辦?別著急,我們在軟件界面提供了所有Win10歷史聚焦壁紙下載大全的鏈接,很貼心的說。

          當然,這次魔方還有其他的有愛更新,例如清理大師的重復文件查找支持了批量選擇和刪除操作,例如設置大師中加入了讓資源管理器關閉mkv文件的預覽以防止卡頓,軟媒雷達、軟媒時間、WiFi助手、軟媒壓縮等都有界面和功能修復改進。

          具體更新內容的細節,請看下面的更新歷史吧!

          PS:按照慣例,軟媒魔方將在發布數十分鐘后才放開自動升級。

          一、軟媒魔方更新歷史

          軟媒魔方 6.1.6.0 正式版 - 2015年12月17日

          魔方主程序 6.1.6.0:

          新增:應用大全 - 增加旗魚瀏覽器PC版入口

          軟媒美化大師 3.6.9.0:

          新增:針對Win10 TH2增加Windows聚焦壁紙一鍵提取功能

          軟媒時間 3.1.3.0:

          修正:界面 - 多云和陰天的小圖標弄反的問題修正:界面 - 系統開啟高DPI的時候,窗口太小的問題

          軟媒清理大師 3.7.3.0:

          新增:隱私清理 - Office 2016打開歷史記錄清理

          改進:重復文件查找 - 支持批量操作改進:注冊表清理 - 屏蔽注冊表清理功能

          修正:隱私清理 - Office 2013打開歷史記錄清理不掉的問題

          軟媒設置大師 3.6.8.0:

          新增:資源管理器 - 關閉MKV視頻預覽

          軟媒雷達 6.0.7.0:

          修正:本機信息 - 讀取IE的Flash Player版本號錯誤的問題修正:界面 - 系統開啟高DPI的時候,窗口太小的問題

          軟媒IE管理大師 1.9.8.0:

          修正:界面 - 系統開啟高DPI的時候,窗口太小的問題

          軟媒WiFi助手 1.1.8.0:

          修正:穩定性 - 啟動WiFi助手時可能發生的崩潰問題

          軟媒壓縮 1.1.5.0:

          修正:界面 - 系統開啟高DPI的時候,窗口太小的問題

          二、為什么大家都在用魔方?

          軟媒魔方好不好?軟媒魔方有什么用?為什么要用軟媒魔方?

          先列出一些基本組件功能:

          1、清理大師:一鍵清理、深度清理、注冊表清理、字體清理,還有隱私清理。。。難道你不需要?

          2、美化大師:改系統字體、DIY win7開始按鈕、設置開機動畫、破解系統主題、修改系統聲音。。。怎么個性怎么玩,美化大師全搞定!

          3、優化大師:一鍵加速、添加、刪除系統啟動項,讓你輕松掌控系統進程的開啟!

          4、軟媒時間,軟媒全球首創獨創的創意,任務欄時間區加入農歷、天氣等顯示,不占任何額外空間,超NB!

          5、軟媒桌面:哈,Windows系統里多了類似蘋果Mac OS X的快捷欄,方便大發了。

          6、軟件管家:精選裝機必備軟件大全,下載杠杠的……

          7、系統雷達:任務欄窗口+桌面懸浮窗自由隨意選擇,實時監控網絡流量、CPU、內存占用、磁盤讀寫,簡單、清晰、方便!

          8、U盤啟動:一鍵制作U盤系統安裝盤,裝機還是PE維護簡直輕松到極點!

          9、硬盤裝機:僅需兩步,輕松幫你重裝系統,win7、win8、win10,想裝什么裝什么,你肯定需要!

          10、WiFi共享助手:超級簡單,打開軟件,一鍵開啟熱點,立馬擺脫手機流量不夠用的困擾!!親,你的流量還夠用嗎?

          還有啥?上面列出的連一半功能都不到,還有網速測試、磁盤大師、文件校驗、文件解鎖、文件分割合并、文件粉碎……等等,還有好多功能,你能想到的基本都有!

          軟媒魔方現在已經有4000萬用戶下載使用,好用沒得說!

          三、軟媒魔方軟件截圖

          只需一鍵,智能模式讓你可以放心的把軟媒魔方介紹給你的老婆、小姨子和表妹……

          右上角輕松切換到專業模式,熟悉中包含著科學改進后的經典布局

          軟媒時間,風云變幻-不占用任何額外空間,萬年歷天氣鬧鐘記事本在任務欄時間區完美呈現;

          軟媒桌面,好玩好看-喜歡的鼠標拖進來,不愛的鼠標拖出去,桌面干凈整潔靈動驚艷;

          軟媒雷達,實時偵探-把握您愛機的每一跳脈動,精確掌控系統軟硬件的占用資源;

          軟件管家,純凈精干-編輯精選裝機必備的500款最常用軟件,一鍵安裝絕無插件;

          四、軟媒魔方下載信息

          初級用戶解惑:安裝包和綠色版有什么不同?安裝版下載后直接雙擊運行即可進入向導安裝,方便快捷。綠色版是ZIP壓縮格式,直接解壓到你指定的文件夾路徑下。

          軟媒 - 存在,創造價值。

          微信搜索“IT之家”關注搶6s大禮!下載IT之家客戶端(戳這里)也可參與評論抽樓層大獎!


          主站蜘蛛池模板: 久久精品无码一区二区三区免费| 福利一区二区三区视频在线观看| 精品亚洲A∨无码一区二区三区| 欧美日韩精品一区二区在线视频| 日韩在线一区二区三区免费视频| 日韩免费观看一区| 在线观看日韩一区| 亚洲av福利无码无一区二区| 伊人久久一区二区三区无码| 国产99视频精品一区| 日韩A无码AV一区二区三区| 午夜福利一区二区三区高清视频| 一区二区三区免费在线视频 | 亚洲丶国产丶欧美一区二区三区| 国产精品高清一区二区三区| 日本一区二区三区精品国产 | 中文字幕色AV一区二区三区| 一区二区不卡视频在线观看| 国产一区二区三区亚洲综合| 精品少妇人妻AV一区二区| 中文字幕无线码一区2020青青| 日韩精品成人一区二区三区| 国产午夜精品一区理论片| 一区二区三区免费电影| 日本高清无卡码一区二区久久| 国产在线一区二区杨幂| 免费观看日本污污ww网站一区 | av无码人妻一区二区三区牛牛| 国产91一区二区在线播放不卡| 国产乱码伦精品一区二区三区麻豆 | 日韩免费视频一区二区| 日韩精品一区二区三区色欲AV| 亚洲国产精品自在线一区二区| 中文字幕精品亚洲无线码一区| 亚洲永久无码3D动漫一区| 国产激情无码一区二区app| 一区二区视频在线播放| 中文字幕AV无码一区二区三区| 国产精品久久久久一区二区三区| 手机福利视频一区二区| 日韩精品一区二区午夜成人版 |