精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Node.js模塊系統源碼探微

開發 前端
本文將帶領讀者領略 Node.js (以下簡稱 Node) 的模塊設計思想以及剖析部分核心源碼實現。

 [[285763]]

Node.js 的出現使得前端工程師可以跨端工作在服務器上,當然,一個新的運行環境的誕生亦會帶來新的模塊、功能、抑或是思想上的革新,本文將帶領讀者領略 Node.js (以下簡稱 Node) 的模塊設計思想以及剖析部分核心源碼實現。

CommonJS 規范

Node 最初遵循 CommonJS 規范來實現自己的模塊系統,同時做了一部分區別于規范的定制。CommonJS 規范是為了解決 JavaScript 的作用域問題而定義的模塊形式,它可以使每個模塊在它自身的命名空間中執行。

該規范強調模塊必須通過 module.exports 導出對外的變量或函數,通過 require() 來導入其他模塊的輸出到當前模塊作用域中,同時,遵循以下約定:

  •  在模塊中,必須暴露一個 require 變量,它是一個函數,require 函數接受一個模塊標識符,require 返回外部模塊的導出的 API。如果要求的模塊不能被返回則 require 必須拋出一個錯誤。
  •  在模塊中,必須有一個自由變量叫做 exports,它是一個對象,模塊在執行時可以在 exports 上掛載模塊的屬性。模塊必須使用 exports 對象作為唯一的導出方式。
  •  在模塊中,必須有一個自由變量 module,它也是一個對象。module 對象必須有一個 id 屬性,它是這個模塊的頂層 id。id 屬性必須是這樣的,require(module.id) 會從源出 module.id 的那個模塊返回 exports 對象(就是說 module.id 可以被傳遞到另一個模塊,而且在要求它時必須返回最初的模塊)。

Node 對 CommonJS 規范的實現

  •  定義了模塊內部的 module.require 函數和全局的 require 函數,用來加載模塊。
  •  在 Node 模塊系統中,每個文件都被視為一個獨立的模塊。模塊被加載時,都會初始化為 Module 對象的實例,Module 對象的基本實現和屬性如下所示: 
  1. function Module(id = "", parent) {  
  2.   // 模塊 id,通常為模塊的絕對路徑  
  3.   this.id = id;  
  4.   this.path = path.dirname(id);  
  5.   this.exports = {};  
  6.   // 當前模塊調用者  
  7.   this.parent = parent;  
  8.   updateChildren(parent, this, false);  
  9.   this.filename = null 
  10.   // 模塊是否加載完成   
  11.   this.loaded = false 
  12.   // 當前模塊所引用的模塊  
  13.   this.children = [];  
  •  每一個模塊都對外暴露自己的 exports 屬性作為使用接口。

模塊導出以及引用

在 Node 中,可使用 module.exports 對象整體導出一個變量或者函數,也可將需要導出的變量或函數掛載到 exports 對象的屬性上,代碼如下所示: 

  1. // 1. 使用 exports: 筆者習慣通常用作對工具庫函數或常量的導出  
  2. exports.name = 'xiaoxiang' 
  3. exports.add = (a, b) => a + b;  
  4. // 2. 使用 module.exports:導出一整個對象或者單一函數  
  5. ...  
  6. module.exports = {  
  7.   add,  
  8.   minus  

通過全局 require 函數引用模塊,可傳入模塊名稱、相對路徑或者絕對路徑,當模塊文件后綴為 js / json / node 時,可省略后綴,如下代碼所示: 

  1. // 引用模塊  
  2. const { add, minus } = require('./module');  
  3. const a = require('/usr/app/module');  
  4. const http = require('http'); 

注意事項:

  •  exports 變量是在模塊的文件級作用域內可用的,且在模塊執行之前賦值給 module.exports。 
  1. exports.name = 'test' 
  2. console.log(module.exports.name); // test  
  3. module.export.name = 'test' 
  4. console.log(exports.name); // test 
  •  如果為 exports 賦予了新值,則它將不再綁定到 module.exports,反之亦然: 
  1. exports = { name: 'test' };  
  2. console.log(module.exports.name, exports.name); // undefined, test 
  •  當 module.exports 屬性被新對象完全替換時,通常也需要重新賦值 exports: 
  1. module.exports = exports = { name: 'test' };  
  2. console.log(module.exports.name, exports.name) // test, test 

模塊系統實現分析

模塊定位

以下是 require 函數的代碼實現: 

  1. // require 入口函數  
  2. Module.prototype.require = function(id) {  
  3.   //...  
  4.   requireDepth++;  
  5.   try {  
  6.     return Module._load(id, this, /* isMain */ false); // 加載模塊  
  7.   } finally {  
  8.     requireDepth--;  
  9.   }  
  10. }; 

上述代碼接收給定的模塊路徑,其中的 requireDepth 用來記載模塊加載的深度。其中 Module 的類方法 _load 實現了 Node 加載模塊的主要邏輯,下面我們來解析 Module._load 函數的源碼實現,為了方便大家理解,我把注釋加在了文中。 

  1. Module._load = function(request, parent, isMain) {  
  2.   // 步驟一:解析出模塊的全路徑  
  3.   const filename = Module._resolveFilename(request, parent, isMain);   
  4.   // 步驟二:加載模塊,具體分三種情況處理  
  5.   // 情況一:存在緩存的模塊,直接返回模塊的 exports 屬性  
  6.   const cachedModule = Module._cache[filename];  
  7.   if (cachedModule !== undefined)   
  8.     return cachedModule.exports;  
  9.   // 情況二:加載內建模塊  
  10.   const mod = loadNativeModule(filename, request);  
  11.   if (mod && mod.canBeRequiredByUsers) return mod.exports;  
  12.   // 情況三:構建模塊加載  
  13.   const module = new Module(filename, parent);  
  14.   // 加載過之后就進行模塊實例緩存  
  15.   Module._cache[filename] = module;  
  16.   // 步驟三:加載模塊文件  
  17.   module.load(filename);  
  18.   // 步驟四:返回導出對象  
  19.   return module.exports;  
  20. }; 

加載策略

上面的代碼信息量比較大,我們主要看以下幾個問題:

  1.  模塊的緩存策略是什么?

    分析上述代碼我們可以看到, _load 加載函數針對三種情況給出了不同的加載策略,分別是:

  •   情況一:緩存命中,直接返回。
  •   情況二:內建模塊,返回暴露出來的 exports 屬性,也就是 module.exports 的別名。
  •   情況三:使用文件或第三方代碼生成模塊,最后返回,并且緩存,這樣下次同樣的訪問就會去使用緩存而不是重新加載。         

       2.  Module._resolveFilename(request, parent, isMain) 是怎么解析出文件名稱的?

我們看如下定義的類方法: 

  1. Module._resolveFilename = function(request, parent, isMain, options) {  
  2.  if (NativeModule.canBeRequiredByUsers(request)) {   
  3.      // 優先加載內建模塊  
  4.    return request;  
  5.  }  
  6.  let paths;  
  7.  // node require.resolve 函數使用的 options,options.paths 用于指定查找路徑  
  8.  if (typeof options === "object" && options !== null) {  
  9.    if (ArrayIsArray(options.paths)) {  
  10.      const isRelative =  
  11.        request.startsWith("./") ||  
  12.        request.startsWith("../") ||  
  13.        (isWindows && request.startsWith(".\\")) ||  
  14.        request.startsWith("..\\");  
  15.      if (isRelative) {  
  16.        paths = options.paths;  
  17.      } else {  
  18.        const fakeParent = new Module("", null);  
  19.        paths = [];  
  20.        for (let i = 0; i < options.paths.length; i++) {  
  21.          const path = options.paths[i];  
  22.          fakeParent.paths = Module._nodeModulePaths(path);  
  23.          const lookupPaths = Module._resolveLookupPaths(request, fakeParent);  
  24.          for (let j = 0; j < lookupPaths.length; j++) {  
  25.            if (!paths.includes(lookupPaths[j])) paths.push(lookupPaths[j]);  
  26.          }  
  27.        }  
  28.      }  
  29.    } else if (options.paths === undefined) {  
  30.      paths = Module._resolveLookupPaths(request, parent);  
  31.    } else {  
  32.         //...  
  33.    }  
  34.  } else {  
  35.    // 查找模塊存在路徑  
  36.    paths = Module._resolveLookupPaths(request, parent);  
  37.  } 
  38.   // 依據給出的模塊和遍歷地址數組,以及是否為入口模塊來查找模塊路徑  
  39.  const filename = Module._findPath(request, paths, isMain);  
  40.  if (!filename) {  
  41.    const requireStack = [];  
  42.    for (let cursor = parent; cursor; cursorcursor = cursor.parent) {  
  43.      requireStack.push(cursor.filename || cursor.id);  
  44.    }  
  45.    // 未找到模塊,拋出異常(是不是很熟悉的錯誤)  
  46.    let message = `Cannot find module '${request}'`;  
  47.    if (requireStack.length > 0) {  
  48.      messagemessage = message + "\nRequire stack:\n- " + requireStack.join("\n- ");  
  49.    }  
  50.    const err = new Error(message);  
  51.    err.code = "MODULE_NOT_FOUND" 
  52.    err.requireStack = requireStack;  
  53.    throw err;  
  54.  }  
  55.  // 最終返回包含文件名的完整路徑  
  56.  return filename;  
  57. }; 

上面的代碼中比較突出的是使用了 _resolveLookupPaths 和 _findPath 兩個方法。

  •  _resolveLookupPaths: 通過接受模塊名稱和模塊調用者,返回提供 _findPath 使用的遍歷范圍數組。 
  1. // 模塊文件尋址的地址數組方法  
  2.   Module._resolveLookupPaths = function(request, parent) {  
  3.    if (NativeModule.canBeRequiredByUsers(request)) {  
  4.      debug("looking for %j in []", request);  
  5.      return null;  
  6.    }  
  7.    // 如果不是相對路徑  
  8.    if (  
  9.      request.charAt(0) !== "." ||  
  10.      (request.length > 1 &&  
  11.        request.charAt(1) !== "." &&  
  12.        request.charAt(1) !== "/" &&  
  13.        (!isWindows || request.charAt(1) !== "\\"))  
  14.    ) {  
  15.      /**   
  16.       * 檢查 node_modules 文件夾  
  17.       * modulePaths 為用戶目錄,node_path 環境變量指定目錄、全局 node 安裝目錄   
  18.       */  
  19.      let paths = modulePaths 
  20.      if (parent != null && parent.paths && parent.paths.length) {  
  21.        // 父模塊的 modulePath 也要加到子模塊的 modulePath 里面,往上回溯查找  
  22.        paths = parent.paths.concat(paths);  
  23.      }  
  24.      return paths.length > 0 ? paths : null;  
  25.    }  
  26.    // 使用 repl 交互時,依次查找 ./ ./node_modules 以及 modulePaths  
  27.    if (!parent || !parent.id || !parent.filename) {  
  28.      const mainPaths = ["."].concat(Module._nodeModulePaths("."), modulePaths);      
  29.      return mainPaths;  
  30.    }  
  31.    // 如果是相對路徑引入,則將父級文件夾路徑加入查找路徑  
  32.    const parentDir = [path.dirname(parent.filename)];  
  33.    return parentDir;  
  34.   }; 
  •  _findPath: 依據目標模塊和上述函數查找到的范圍,找到對應的 filename 并返回。 
  1. // 依據給出的模塊和遍歷地址數組,以及是否頂層模塊來尋找模塊真實路徑  
  2. Module._findPath = function(request, paths, isMain) {  
  3.  const absoluteRequest = path.isAbsolute(request); 
  4.   if (absoluteRequest) {  
  5.   // 絕對路徑,直接定位到具體模塊  
  6.    paths = [""];  
  7.  } else if (!paths || paths.length === 0) {  
  8.    return false;  
  9.  } 
  10.   const cacheKey =  
  11.    request + "\x00" + (paths.length === 1 ? paths[0] : paths.join("\x00"));  
  12.  // 緩存路徑  
  13.  const entry = Module._pathCache[cacheKey];  
  14.  if (entry) return entry;  
  15.  let exts;  
  16.  let trailingSlash =  
  17.    request.length > 0 &&  
  18.    request.charCodeAt(request.length - 1) === CHAR_FORWARD_SLASH; // '/'  
  19.  if (!trailingSlash) {  
  20.    trailingSlash = /(?:^|\/)\.?\.$/.test(request);  
  21.  }  
  22.  // For each path  
  23.  for (let i = 0; i < paths.length; i++) {  
  24.    const curPath = paths[i];  
  25.    if (curPath && stat(curPath) < 1) continue;  
  26.    const basePath = resolveExports(curPath, request, absoluteRequest);  
  27.    let filename;  
  28.    const rc = stat(basePath);  
  29.    if (!trailingSlash) {  
  30.      if (rc === 0) { // stat 狀態返回 0,則為文件  
  31.        // File.  
  32.        if (!isMain) {  
  33.          if (preserveSymlinks) {  
  34.            // 當解析和緩存模塊時,命令模塊加載器保持符號連接。  
  35.            filename = path.resolve(basePath);  
  36.          } else {  
  37.            // 不保持符號鏈接  
  38.            filename = toRealPath(basePath);  
  39.          }  
  40.        } else if (preserveSymlinksMain) {  
  41.          filename = path.resolve(basePath);  
  42.        } else {  
  43.          filename = toRealPath(basePath);  
  44.        }  
  45.      }  
  46.      if (!filename) {  
  47.        if (exts === undefined) exts = ObjectKeys(Module._extensions);  
  48.        // 解析后綴名  
  49.        filename = tryExtensions(basePath, exts, isMain);  
  50.      }  
  51.    }  
  52.    if (!filename && rc === 1) {   
  53.      /**   
  54.        *  stat 狀態返回 1 且文件名不存在,則認為是文件夾  
  55.        * 如果文件后綴不存在,則嘗試加載該目錄下的 package.json 中 main 入口指定的文件  
  56.        * 如果不存在,然后嘗試 index[.js, .node, .json] 文件  
  57.      */  
  58.      if (exts === undefined) exts = ObjectKeys(Module._extensions);  
  59.      filename = tryPackage(basePath, exts, isMain, request);  
  60.    }  
  61.    if (filename) { // 如果存在該文件,將文件名則加入緩存  
  62.      Module._pathCache[cacheKey] = filename;  
  63.      return filename;  
  64.    }  
  65.  }  
  66.  const selfFilename = trySelf(paths, exts, isMain, trailingSlash, request);  
  67.  if (selfFilename) {  
  68.    // 設置路徑的緩存  
  69.    Module._pathCache[cacheKey] = selfFilename;  
  70.    return selfFilename;  
  71.  }  
  72.  return false;  
  73. }; 

模塊加載

標準模塊處理

閱讀完上面的代碼,我們發現,當遇到模塊是一個文件夾的時候會執行 tryPackage 函數的邏輯,下面簡要分析一下具體實現。 

  1. // 嘗試加載標準模塊  
  2. function tryPackage(requestPath, exts, isMain, originalPath) {  
  3.   const pkg = readPackageMain(requestPath);  
  4.   if (!pkg) {  
  5.     // 如果沒有 package.json 這直接使用 index 作為默認入口文件  
  6.     return tryExtensions(path.resolve(requestPath, "index"), exts, isMain);  
  7.   }  
  8.   const filename = path.resolve(requestPath, pkg);  
  9.   let actual =  
  10.     tryFile(filename, isMain) ||  
  11.     tryExtensions(filename, exts, isMain) ||  
  12.     tryExtensions(path.resolve(filename, "index"), exts, isMain);  
  13.   //...  
  14.   return actual;  
  15.  
  16. // 讀取 package.json 中的 main 字段  
  17. function readPackageMain(requestPath) {  
  18.   const pkg = readPackage(requestPath);  
  19.   return pkg ? pkg.main : undefined;  

readPackage 函數負責讀取和解析 package.json 文件中的內容,具體描述如下: 

  1. function readPackage(requestPath) {  
  2.   const jsonPath = path.resolve(requestPath, "package.json");  
  3.   const existing = packageJsonCache.get(jsonPath);  
  4.   if (existing !== undefined) return existing;  
  5.   // 調用 libuv uv_fs_open 的執行邏輯,讀取 package.json 文件,并且緩存  
  6.   const json = internalModuleReadJSON(path.toNamespacedPath(jsonPath));  
  7.   if (json === undefined) {  
  8.     // 接著緩存文件  
  9.     packageJsonCache.set(jsonPath, false);  
  10.     return false;  
  11.   }  
  12.   //...  
  13.   try {  
  14.     const parsed = JSONParse(json);  
  15.     const filtered = {  
  16.       name: parsed.name,  
  17.       main: parsed.main,  
  18.       exports: parsed.exports,  
  19.       type: parsed.type  
  20.     };  
  21.     packageJsonCache.set(jsonPath, filtered);  
  22.     return filtered;  
  23.   } catch (e) {  
  24.     //...  
  25.   }  

上面的兩段代碼完美地解釋 package.json 文件的作用,模塊的配置入口( package.json 中的 main 字段)以及模塊的默認文件為什么是 index,具體流程如下圖所示:

模塊文件處理

定位到對應模塊之后,該如何加載和解析呢?以下是具體代碼分析: 

  1. Module.prototype.load = function(filename) {  
  2.   // 保證模塊沒有加載過  
  3.   assert(!this.loaded);  
  4.   this.filename = filename;  
  5.   // 找到當前文件夾的 node_modules  
  6.   this.paths = Module._nodeModulePaths(path.dirname(filename));  
  7.   const extension = findLongestRegisteredExtension(filename);  
  8.   //...  
  9.   // 執行特定文件后綴名解析函數 如 js / json / node  
  10.   Module._extensions[extension](this, filename);  
  11.   // 表示該模塊加載成功  
  12.   this.loaded = true 
  13.   // ... 省略 esm 模塊的支持  
  14. }; 

后綴處理

可以看出,針對不同的文件后綴,Node.js 的加載方式是不同的,一下針對 .js, .json, .node 簡單進行分析。

  •  .js 后綴 js 文件讀取主要通過 Node 內置 API fs.readFileSync 實現。 
  1. Module._extensions[".js"] = function(module, filename) {  
  2.   // 讀取文件內容  
  3.   const content = fs.readFileSync(filename, "utf8");  
  4.   // 編譯執行代碼  
  5.   module._compile(content, filename);  
  6. }; 
  •  .json 后綴 JSON 文件的處理邏輯比較簡單,讀取文件內容后執行 JSONParse 即可拿到結果。 
  1. Module._extensions[".json"] = function(module, filename) {  
  2.   // 直接按照 utf-8 格式加載文件  
  3.   const content = fs.readFileSync(filename, "utf8"); 
  4.    //...  
  5.   try {  
  6.     // 以 JSON 對象格式導出文件內容  
  7.     module.exports = JSONParse(stripBOM(content));  
  8.   } catch (err) {  
  9.     //...  
  10.   }  
  11. }; 
  •  .node 后綴 .node 文件是一種由 C / C++ 實現的原生模塊,通過 process.dlopen 函數讀取,而 process.dlopen 函數實際上調用了 C++ 代碼中的 DLOpen 函數,而 DLOpen 中又調用了 uv_dlopen, 后者加載 .node 文件,類似 OS 加載系統類庫文件。 
  1. Module._extensions[".node"] = function(module, filename) {  
  2.   //... 
  3.    return process.dlopen(module, path.toNamespacedPath(filename));  
  4. }; 

從上面的三段源碼,我們看出來并且可以理解,只有 JS 后綴最后會執行實例方法 _compile,我們去除一些實驗特性和調試相關的邏輯來簡要的分析一下這段代碼。

編譯執行

模塊加載完成后,Node 使用 V8 引擎提供的方法構建運行沙箱,并執行函數代碼,代碼如下所示: 

  1. Module.prototype._compile = function(content, filename) {  
  2.   let moduleURL;  
  3.   let redirects;  
  4.   // 向模塊內部注入公共變量 __dirname / __filename / module / exports / require,并且編譯函數  
  5.   const compiledWrapper = wrapSafe(filename, content, this);  
  6.   const dirname = path.dirname(filename);  
  7.   const require = makeRequireFunction(this, redirects);  
  8.   let result;  
  9.   const exports = this.exports;  
  10.   const thisValue = exports 
  11.   const module = this 
  12.   if (requireDepth === 0) statCache = new Map();  
  13.       //...  
  14.    // 執行模塊中的函數  
  15.     result = compiledWrapper.call(  
  16.       thisValue,  
  17.       exports,  
  18.       require,  
  19.       module,  
  20.       filename,  
  21.       dirname  
  22.     );  
  23.   hasLoadedAnyUserCJSModule = true 
  24.   if (requireDepth === 0) statCache = null 
  25.   return result;  
  26. };  
  27. // 注入變量的核心邏輯  
  28. function wrapSafe(filename, content, cjsModuleInstance) {  
  29.   if (patched) {  
  30.     const wrapper = Module.wrap(content);  
  31.     // vm 沙箱運行 ,直接返回運行結果,env -> SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext);  
  32.     return vm.runInThisContext(wrapper, {  
  33.       filename,  
  34.       lineOffset: 0,  
  35.       displayErrors: true,  
  36.       // 動態加載  
  37.       importModuleDynamically: async specifier => {  
  38.         const loader = asyncESM.ESMLoader;  
  39.         return loader.import(specifier, normalizeReferrerURL(filename));  
  40.       }  
  41.     });  
  42.   }  
  43.   let compiled;  
  44.   try {  
  45.     compiled = compileFunction 
  46.       content,  
  47.       filename,  
  48.       0,  
  49.       0,  
  50.       undefined,  
  51.       false,  
  52.       undefined,  
  53.       [],  
  54.       ["exports", "require", "module", "__filename", "__dirname"]  
  55.     );  
  56.   } catch (err) {  
  57.     //...  
  58.   }  
  59.   const { callbackMap } = internalBinding("module_wrap");  
  60.   callbackMap.set(compiled.cacheKey, {  
  61.     importModuleDynamically: async specifier => {  
  62.       const loader = asyncESM.ESMLoader;  
  63.       return loader.import(specifier, normalizeReferrerURL(filename));  
  64.     }  
  65.   });  
  66.   return compiled.function;  

上述代碼中,我們可以看到在 _compile 函數中調用了 wrapwrapSafe 函數,執行了 __dirname / __filename / module / exports / require 公共變量的注入,并且調用了 C++ 的 runInThisContext 方法(位于 src/node_contextify.cc 文件)構建了模塊代碼運行的沙箱環境,并返回了 compiledWrapper 對象,最終通過 compiledWrapper.call 方法運行模塊。

結語

至此,Node.js 的模塊系統分析告一段落,Node.js 世界的精彩和絕妙無窮無盡,學習的路上和諸君共勉。 

 

責任編輯:龐桂玉 來源: 中國開源
相關推薦

2011-09-08 14:07:28

Node.js

2021-09-26 05:06:04

Node.js模塊機制

2020-04-15 15:48:03

Node.jsstream前端

2025-05-26 00:31:31

2019-12-10 10:23:57

Node.jsCluster前端

2013-11-01 09:34:56

Node.js技術

2015-03-10 10:59:18

Node.js開發指南基礎介紹

2023-06-30 23:25:46

HTTP模塊內存

2011-12-09 11:16:48

Node.js

2011-11-01 10:30:36

Node.js

2011-09-08 13:46:14

node.js

2011-09-02 14:47:48

Node

2011-09-09 14:23:13

Node.js

2012-10-24 14:56:30

IBMdw

2011-11-10 08:55:00

Node.js

2021-12-25 22:29:57

Node.js 微任務處理事件循環

2020-08-31 15:00:17

Node.jsrequire前端

2021-01-26 08:07:44

Node.js模塊 Async

2023-06-20 19:35:00

Node.js工具

2020-05-29 15:33:28

Node.js框架JavaScript
點贊
收藏

51CTO技術棧公眾號

国产一级久久| 成人av婷婷| 国产精品久线在线观看| 91嫩草在线| 五月婷婷色丁香| 色999日韩| 精品91自产拍在线观看一区| 日本熟妇人妻xxxxx| 国产在线观看a视频| 欧美激情福利| 樱花草国产18久久久久| 久久伦理网站| 国产免费黄色片| 一本一道久久综合狠狠老精东影业| 亚洲日韩欧美视频| 日韩伦理在线免费观看| 精品视频二区| 成人va在线观看| 欧美日本在线视频中文字字幕| 国产肉体xxxx裸体784大胆| 久久青草视频| 色一区在线观看| 久久亚洲a v| 1769视频在线播放免费观看| 99在线精品视频| 亚洲a级在线观看| 中文字幕人妻一区二区三区视频| 亚洲高清自拍| 欧美久久免费观看| 亚洲欧美日本国产有色| 午夜性色福利影院| 国产v综合v亚洲欧| 成人在线国产精品| 国产精品熟女视频| 91久久午夜| 色综合久久中文字幕综合网小说| 影音先锋男人在线| 九色成人国产蝌蚪91| 亚洲成人激情在线| 国产欧美视频一区| 91白丝在线| 亚洲美女屁股眼交| 国产精品jizz视频| 国产乱码精品一区二三区蜜臂| 久久综合激情| 欧美一级在线播放| 日韩特级黄色片| 亚洲三级毛片| 性色av一区二区三区| 青娱乐免费在线视频| 中国成人一区| 欧美另类xxx| 极品盗摄国产盗摄合集| 亚洲高清影视| 欧美大片在线看| 日韩欧美中文字幕视频| 午夜精品电影| 久久久久久成人| 日韩精品人妻中文字幕| 亚洲日本视频| 日韩av片永久免费网站| 中文字幕在线欧美| 三级在线观看一区二区| 国产精品久久久久久av| 欧美 亚洲 另类 激情 另类| 免费精品视频最新在线| 成人妇女淫片aaaa视频| 国产精品嫩草影院精东| 国产精品白丝jk白祙喷水网站| 97视频com| 97久久久久久久| 噜噜噜91成人网| 国产精品久久久久久久天堂 | 亚洲人午夜精品天堂一二香蕉| 亚洲精品电影在线一区| 国产视频在线播放| 亚洲一区影音先锋| 四虎永久在线精品无码视频| av免费在线一区| 91精品国产综合久久福利 | 四季久久免费一区二区三区四区| 亚洲综合丁香婷婷六月香| 日本一本中文字幕| www.com.cn成人| 欧美吻胸吃奶大尺度电影| 51xx午夜影福利| 免费毛片在线看片免费丝瓜视频 | 国内精品久久久久久久久久| 欧美一级视频| 国产精品香蕉av| 免费看日批视频| 蜜臀91精品一区二区三区 | 尤蜜粉嫩av国产一区二区三区| 久久精品国产精品亚洲毛片| 欧美成人女星排行榜| 素人fc2av清纯18岁| 精品视频在线播放一区二区三区| 精品噜噜噜噜久久久久久久久试看| 熟女人妻在线视频| 手机在线电影一区| 午夜精品蜜臀一区二区三区免费| 国产精品国产精品国产| 成人一级片在线观看| 日本一区免费观看| 深爱五月激情五月| 国产片一区二区| 日本福利视频一区| 国产欧美自拍| 日韩av在线网| 欧美成人免费看| 日本一不卡视频| 国产成人欧美在线观看| 99久久国产热无码精品免费| 久久综合狠狠综合久久激情| 欧美另类videosbestsex日本| 日本电影欧美片| 欧美精品一区二区三区在线播放 | 日本一本a高清免费不卡| 97人妻人人澡人人爽人人精品| 97精品国产97久久久久久久久久久久| 中文字幕一区二区三区乱码| 成人软件在线观看| 日韩电影在线观看中文字幕| 在线免费观看亚洲视频| 日韩av成人高清| 久久综合一区二区三区| 牛牛电影国产一区二区| 91麻豆精品国产91久久久久久| 中文字幕免费高清| 蜜桃成人av| 久久全国免费视频| 精品人妻一区二区三区蜜桃 | 久久久久久激情| 精品一区二区三区影院在线午夜| 欧美主播一区二区三区美女 久久精品人 | 精品一区91| www日韩欧美| 中文字幕网址在线| 国产日产欧产精品推荐色| 国产超级av在线| 美女福利一区| 97碰碰碰免费色视频| 亚洲国产精品国自产拍久久| 亚洲视频中文字幕| 91pony九色| 91高清一区| 亚洲a一级视频| xvideos国产在线视频| 555夜色666亚洲国产免| 久久精品三级视频| 麻豆91精品视频| 亚洲高清在线观看一区| 69堂免费精品视频在线播放| 亚洲香蕉在线观看| 黄色一级视频免费看| 久久婷婷国产综合国色天香| www.日日操| 国产成人久久| 国产精品三级网站| 日本中文在线观看| 欧美在线免费观看视频| 制服丨自拍丨欧美丨动漫丨| 美腿丝袜亚洲一区| 成年人免费观看的视频| 国产精品一区二区三区av | 成人线上视频| 亚洲人成电影在线| 中文字幕在线观看欧美| 《视频一区视频二区| 日本大胆人体视频| 99久久香蕉| 911国产网站尤物在线观看| 午夜成人鲁丝片午夜精品| 欧美午夜宅男影院在线观看| 国产美女免费网站| 国内久久婷婷综合| 日韩欧美精品免费| 综合干狼人综合首页| 国产精品福利在线观看| 午夜看片在线免费| 精品免费日韩av| 日韩电影在线观看一区二区| 国产精品女人毛片| 少妇伦子伦精品无吗| 性欧美暴力猛交另类hd| 亚洲欧美日韩不卡一区二区三区| 视频欧美一区| 欧美专区在线视频| 毛片网站在线免费观看| 日韩欧美亚洲范冰冰与中字| 五月婷婷婷婷婷| 成人丝袜视频网| www.色就是色| 欧美婷婷在线| 色一情一乱一伦一区二区三区 | 91精品国产高清91久久久久久| 宅男噜噜噜66国产日韩在线观看| 亚洲成人自拍| 精品无人区一区二区| 国产精品久在线观看| 精灵使的剑舞无删减版在线观看| 国产午夜精品一区理论片飘花 | 国产91综合网| 日韩av手机版| 亚洲东热激情| 一本色道久久综合亚洲精品婷婷| 久久97久久97精品免视看秋霞| 国产精品亚洲美女av网站| av福利在线导航| 日韩在线视频免费观看高清中文| 亚洲 国产 欧美 日韩| 91精品国产乱码久久蜜臀| 亚洲第一网站在线观看| 午夜成人在线视频| 男人操女人的视频网站| 国产欧美一区二区精品久导航| 在线看黄色的网站| 国产一区二区精品在线观看| 五码日韩精品一区二区三区视频| 亚洲国产aⅴ精品一区二区| 国产精品入口尤物| 日本不卡一二三| 992tv成人免费影院| 久久日韩视频| 色婷婷综合久久久久中文字幕1| 四虎影院在线播放| 五月天久久比比资源色| www青青草原| 成人欧美一区二区三区黑人麻豆| 中文字幕国产综合| 972aa.com艺术欧美| 稀缺呦国内精品呦| 国产成人av自拍| 亚洲精品国产久| 亚洲无线一线二线三线区别av| 亚洲精品一区二区三区樱花| 欧美日韩国产高清电影| 欧美大陆一区二区| 秋霞影院一区二区三区| 国产精品一码二码三码在线| 秋霞影院一区| 91在线|亚洲| 无码国模国产在线观看| 91亚洲午夜在线| 精品视频在线播放一区二区三区| 亚洲va久久久噜噜噜| 精品三级国产| 国产高清精品一区二区| 一区二区三区免费在线看| 99超碰麻豆| jizz久久精品永久免费| 俄罗斯精品一区二区| 国产成人福利av| 久久久亚洲综合网站| 天天久久夜夜| 日韩欧美精品一区二区三区经典| 精品国产乱码久久久| 日韩欧美三级电影| 97精品视频在线看| 伊人久久婷婷色综合98网| 99久久视频| 中国一级大黄大黄大色毛片| 国产综合视频| 97成人在线免费视频| 国语产色综合| 性欧美大战久久久久久久免费观看| 日本成人小视频| 中国一区二区三区| 欧美极品一区二区三区| 国产不卡一区二区视频| 欧美中文字幕| 男操女免费网站| 亚洲毛片网站| 18岁视频在线观看| 久久97超碰国产精品超碰| 亚洲精品久久久久久| 不卡一区中文字幕| 亚洲女优在线观看| 亚洲免费观看高清| 日本午夜精品理论片a级app发布| 色悠悠久久综合| 一本一道人人妻人人妻αv| 日韩欧美精品在线视频| 中文字幕观看视频| 日韩精品中文字幕一区二区三区| 无码国产精品一区二区免费16| 国产亚洲精品一区二区| av毛片在线免费看| 日本道色综合久久影院| 小说区图片区亚洲| 精品免费一区二区三区蜜桃| 久久蜜桃av| 我的公把我弄高潮了视频| 日本中文在线一区| 91人人澡人人爽| 国产日产欧美一区| 国产对白videos麻豆高潮| 在线精品视频免费观看| 亚洲国产精彩视频| 中文字幕在线观看日韩| 捆绑调教日本一区二区三区| 国产精品最新在线观看| 色综合久久中文| 99热都是精品| 老牛嫩草一区二区三区日本 | 久久亚洲综合色一区二区三区 | 免费一级在线观看| 九九热精品在线| 久久国内精品| 欧美亚洲丝袜| 在线精品一区二区| 极品粉嫩美女露脸啪啪| 久久久欧美精品sm网站| 欧美又粗又大又长| 欧美在线你懂的| 色视频在线观看| 亚洲成人动漫在线播放| 九七电影韩国女主播在线观看| 青青精品视频播放| www国产精品| 男人j进女人j| 毛片不卡一区二区| 国产av自拍一区| 欧美视频不卡中文| 亚洲成人黄色片| 欧美精品在线免费播放| 久久免费影院| 亚洲国产欧美一区二区三区不卡| 久久成人精品| 亚洲av无码一区二区三区网址 | 国产精一区二区| 亚洲高清视频一区| 奇米一区二区三区| 国产麻豆天美果冻无码视频| 五月综合激情网| 欧美一区二区三区黄片| 色中色综合影院手机版在线观看| 亚洲国产天堂| 亚洲综合av一区| 男人操女人的视频在线观看欧美| www.中文字幕av | 国产精品自产拍| 最近2019中文字幕mv免费看 | 精品欧美色视频网站在线观看| 国产精品久久久久久久美男 | 99re8在线精品视频免费播放| 久久久夜色精品| 亚洲精品99久久久久中文字幕| 啦啦啦中文在线观看日本| 99三级在线| 激情综合激情| 久久久久久久久免费看无码| 五月婷婷另类国产| 日本电影一区二区在线观看| 欧美主播福利视频| 欧美偷拍综合| 欧美在线aaa| 成人永久aaa| 日韩欧美性视频| 亚洲精品综合精品自拍| 日本免费一区二区三区四区| 日本一区二区不卡高清更新| 麻豆精品视频在线观看免费| 免费成人深夜夜行网站| 日韩欧美国产小视频| 国产剧情av在线播放| 国产精品永久免费在线| 91九色精品国产一区二区| 欧美人与性动交α欧美精品| 亚洲香蕉伊在人在线观| 五月婷婷丁香花| 国产精品久久久久久久久久小说 | 久久影院午夜片一区| 国产无套丰满白嫩对白| 永久免费毛片在线播放不卡| 99综合久久| av黄色在线网站| 国产精品女同一区二区三区| av网站免费大全| 45www国产精品网站| 久久日文中文字幕乱码| wwwxxxx在线观看| 一本久道中文字幕精品亚洲嫩| 香蕉视频免费在线播放| 成人欧美一区二区三区视频xxx| 免费精品视频| 中文字幕五月天| 日韩精品在线观看一区| 外国成人毛片| 日日橹狠狠爱欧美超碰| 综合中文字幕亚洲| 日韩三级电影网| 91av一区二区三区| 鲁大师影院一区二区三区| 午夜免费激情视频| 亚洲色图日韩av| 成人香蕉社区| 日本免费色视频| 日韩人体视频一二区| 在线中文字幕第一页|