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

首頁 > 編程 > JavaScript > 正文

Angularjs 1.3 中的$parse實例代碼

2019-11-19 15:27:11
字體:
來源:轉載
供稿:網友

這次我們來看一下angular的Sandboxing Angular Expressions。關于內置方法的,核心有兩塊:Lexer和Parser。其中大家對$parse可能更了解一點。好了不多廢話,先看Lexer的內部結構:

1.Lexer

//構造函數var Lexer = function(options) { this.options = options;};//原型 Lexer.prototype = { constructor: Lexer, lex: function(){}, is: function(){}, peek: function(){ /* 返回表達式的下一個位置的數據,如果沒有則返回false */ }, isNumber: function(){ /* 判斷當前表達式是否是一個數字 */ }, isWhitespace: function(){/* 判斷當前表達式是否是空格符 */}, isIdent: function(){/* 判斷當前表達式是否是英文字符(包含_和$) */}, isExpOperator: function(){/* 判斷當時表達式是否是-,+還是數字 */}, throwError: function(){ /* 拋出異常 */}, readNumber: function(){ /* 讀取數字 */}, readIdent: function(){ /* 讀取字符 */}, readString: function(){ /*讀取攜帶''或""的字符串*/ }};

 這里指出一點,因為是表達式。所以類似"123"這類的東西,在Lexer看來應該算是數字而非字符串。表達式中的字符串必須使用單引號或者雙引號來標識。Lexer的核心邏輯在lex方法中:

lex: function(text) { this.text = text; this.index = 0; this.tokens = []; while (this.index < this.text.length) {  var ch = this.text.charAt(this.index);  if (ch === '"' || ch === "'") {  /* 嘗試判斷是否是字符串 */  this.readString(ch);  } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {  /* 嘗試判斷是否是數字 */  this.readNumber();  } else if (this.isIdent(ch)) {  /* 嘗試判斷是否是字母 */  this.readIdent();  } else if (this.is(ch, '(){}[].,;:?')) {  /* 判斷是否是(){}[].,;:? */  this.tokens.push({index: this.index, text: ch});  this.index++;  } else if (this.isWhitespace(ch)) {  /* 判斷是否是空白符 */  this.index++;  } else {  /* 嘗試匹配操作運算 */  var ch2 = ch + this.peek();  var ch3 = ch2 + this.peek(2);  var op1 = OPERATORS[ch];  var op2 = OPERATORS[ch2];  var op3 = OPERATORS[ch3];  if (op1 || op2 || op3) {   var token = op3 ? ch3 : (op2 ? ch2 : ch);   this.tokens.push({index: this.index, text: token, operator: true});   this.index += token.length;  } else {   this.throwError('Unexpected next character ', this.index, this.index + 1);  }  } } return this.tokens; }

主要看一下匹配操作運算。這里源碼中會調用OPERATORS。看一下OPERATORS:

var OPERATORS = extend(createMap(), { '+':function(self, locals, a, b) {  a=a(self, locals); b=b(self, locals);  if (isDefined(a)) {  if (isDefined(b)) {   return a + b;  }  return a;  }  return isDefined(b) ? b : undefined;}, '-':function(self, locals, a, b) {   a=a(self, locals); b=b(self, locals);   return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0);  }, '*':function(self, locals, a, b) {return a(self, locals) * b(self, locals);}, '/':function(self, locals, a, b) {return a(self, locals) / b(self, locals);}, '%':function(self, locals, a, b) {return a(self, locals) % b(self, locals);}, '===':function(self, locals, a, b) {return a(self, locals) === b(self, locals);}, '!==':function(self, locals, a, b) {return a(self, locals) !== b(self, locals);}, '==':function(self, locals, a, b) {return a(self, locals) == b(self, locals);}, '!=':function(self, locals, a, b) {return a(self, locals) != b(self, locals);}, '<':function(self, locals, a, b) {return a(self, locals) < b(self, locals);}, '>':function(self, locals, a, b) {return a(self, locals) > b(self, locals);}, '<=':function(self, locals, a, b) {return a(self, locals) <= b(self, locals);}, '>=':function(self, locals, a, b) {return a(self, locals) >= b(self, locals);}, '&&':function(self, locals, a, b) {return a(self, locals) && b(self, locals);}, '||':function(self, locals, a, b) {return a(self, locals) || b(self, locals);}, '!':function(self, locals, a) {return !a(self, locals);}, //Tokenized as operators but parsed as assignment/filters '=':true, '|':true});

可以看到OPERATORS實際上存儲的是操作符和操作符函數的鍵值對。根據操作符返回對應的操作符函數。我們看一下調用例子:

var _l = new Lexer({});var a = _l.lex("a = a + 1");console.log(a);

 結合之前的lex方法,我們來回顧下代碼執行過程:

1.index指向'a'是一個字母。匹配isIdent成功。將生成的token存入tokens中

2.index指向空格符,匹配isWhitespace成功,同上

3.index指向=,匹配操作運算符成功,同上

4.index指向空格符,匹配isWhitespace成功,同上

5.index指向'a'是一個字母。匹配isIdent成功。同上

7.index指向+,匹配操作運算符成功,同上

8.index指向空格符,匹配isWhitespace成功,同上

9.index指向1,匹配數字成功,同上

以上則是"a = a + 1"的代碼執行過程。9步執行結束之后,跳出while循環。剛才我們看到了,每次匹配成功,源碼會生成一個token。因為匹配類型的不同,生成出來的token的鍵值對略有不同:

number:{  index: start,  text: number,  constant: true,  value: Number(number) },string: {   index: start,   text: rawString,   constant: true,   value: string  },ident: {  index: start,  text: this.text.slice(start, this.index),  identifier: true /* 字符表示 */  },'(){}[].,;:?': { index: this.index, text: ch},"操作符": {  index: this.index,   text: token,   operator: true}//text是表達式,而value才是實際的值

number和string其實都有相對應的真實值,意味著如果我們表達式是2e2,那number生成的token的值value就應該是200。到此我們通過lexer類獲得了一個具有token值得數組。從外部看,實際上Lexer是將我們輸入的表達式解析成了token json??梢岳斫鉃樯闪吮磉_式的語法樹(AST)。但是目前來看,我們依舊還沒有能獲得我們定義表達式的結果。那就需要用到parser了。

2.Parser

先看一下Parser的內部結構:

//構造函數var Parser = function(lexer, $filter, options) { this.lexer = lexer; this.$filter = $filter; this.options = options;};//原型Parser.prototype = { constructor: Parser, parse: function(){}, primary: function(){}, throwError: function(){ /* 語法拋錯 */}, peekToken: function(){}, peek: function(){/*返回tokens中的第一個成員對象 */}, peekAhead: function(){ /* 返回tokens中指定成員對象,否則返回false */}, expect: function(){ /* 取出tokens中第一個對象,否則返回false */ }, consume: function(){ /* 取出第一個,底層調用expect */ }, unaryFn: function(){ /* 一元操作 */}, binaryFn: function(){ /* 二元操作 */}, identifier: function(){}, constant: function(){}, statements: function(){}, filterChain: function(){}, filter: function(){}, expression: function(){}, assignment: function(){}, ternary: function(){}, logicalOR: function(){ /* 邏輯或 */}, logicalAND: function(){ /* 邏輯與 */ }, equality: function(){ /* 等于 */ }, relational: function(){ /* 比較關系 */ }, additive: function(){ /* 加法,減法 */ }, multiplicative: function(){ /* 乘法,除法,求余 */ }, unary: function(){ /* 一元 */ }, fieldAccess: function(){}, objectIndex: function(){}, functionCall: function(){}, arrayDeclaration: function(){}, object: function(){}}

Parser的入口方法是parse,內部執行了statements方法。來看下statements:

statements: function() { var statements = []; while (true) {  if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))  statements.push(this.filterChain());  if (!this.expect(';')) {  // optimize for the common case where there is only one statement.  // TODO(size): maybe we should not support multiple statements?  return (statements.length === 1)   ? statements[0]   : function $parseStatements(self, locals) {    var value;    for (var i = 0, ii = statements.length; i < ii; i++) {     value = statements[i](self, locals);    }    return value;    };  } } }

這里我們將tokens理解為表達式,實際上它就是經過表達式通過lexer轉換過來的。statements中。如果表達式不以},),;,]開頭,將會執行filterChain方法。當tokens檢索完成之后,最后返回了一個$parseStatements方法。其實Parser中很多方法都返回了類似的對象,意味著返回的內容將需要執行后才能得到結果。

看一下filterChain:

filterChain: function() { /* 針對angular語法的filter */ var left = this.expression(); var token; while ((token = this.expect('|'))) {  left = this.filter(left); } return left; }

其中filterChain是針對angular表達式獨有的"|"filter寫法設計的。我們先繞過這塊,進入expression

expression: function() { return this.assignment(); }

再看assignment:

assignment: function() { var left = this.ternary(); var right; var token; if ((token = this.expect('='))) {  if (!left.assign) {  this.throwError('implies assignment but [' +   this.text.substring(0, token.index) + '] can not be assigned to', token);  }  right = this.ternary();  return extend(function $parseAssignment(scope, locals) {  return left.assign(scope, right(scope, locals), locals);  }, {  inputs: [left, right]  }); } return left; }

我們看到了ternary方法。這是一個解析三目操作的方法。與此同時,assignment將表達式以=劃分成left和right兩塊。并且兩塊都嘗試執行ternary。

ternary: function() { var left = this.logicalOR(); var middle; var token; if ((token = this.expect('?'))) {  middle = this.assignment();  if (this.consume(':')) {  var right = this.assignment();  return extend(function $parseTernary(self, locals) {   return left(self, locals) ? middle(self, locals) : right(self, locals);  }, {   constant: left.constant && middle.constant && right.constant  });  } } return left; }

在解析三目運算之前,又根據?將表達式劃分成left和right兩塊。左側再去嘗試執行logicalOR,實際上這是一個邏輯與的解析,按照這個執行流程,我們一下有了思路。這有點類似我們一般寫三目時。代碼的執行情況,比如: 2 > 2 ? 1 : 0。如果把這個當成表達式,那根據?劃分left和right,left就應該是2 > 2,right應該就是 1: 0。然后嘗試在left看是否有邏輯或的操作。也就是,Parser里面的方法調用的嵌套級數越深,其方法的優先級則越高。好,那我們一口氣看看這個最高的優先級在哪?

logicalOR -> logicalAND -> equality -> relational -> additive -> multiplicative -> unary

好吧,嵌套級數確實有點多。那么我們看下unary。

unary: function() { var token; if (this.expect('+')) {  return this.primary(); } else if ((token = this.expect('-'))) {  return this.binaryFn(Parser.ZERO, token.text, this.unary()); } else if ((token = this.expect('!'))) {  return this.unaryFn(token.text, this.unary()); } else {  return this.primary(); } }

這邊需要看兩個主要的方法,一個是binaryFn和primay。如果判斷是-,則必須通過binaryFn去添加函數。看下binaryFn

binaryFn: function(left, op, right, isBranching) { var fn = OPERATORS[op]; return extend(function $parseBinaryFn(self, locals) {  return fn(self, locals, left, right); }, {  constant: left.constant && right.constant,  inputs: !isBranching && [left, right] }); }

其中OPERATORS是之前聊Lexer也用到過,它根據操作符存儲相應的操作函數??匆幌耭n(self, locals, left, right)。而我們隨便取OPERATORS中的一個例子:

'-':function(self, locals, a, b) {   a=a(self, locals); b=b(self, locals);   return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0);  }

其中a和b就是left和right,他們其實都是返回的跟之前類似的$parseStatements方法。默認存儲著token中的value。經過事先解析好的四則運算來生成最終答案。其實這就是Parser的基本功能。至于嵌套,我們可以把它理解為js的操作符的優先級。這樣就一目了然了。至于primay方法。塔刷選{ ( 對象做進一步的解析過程。

Parser的代碼并不復雜,只是函數方法間調用密切,讓我們再看一個例子:

var _l = new Lexer({});var _p = new Parser(_l);var a = _p.parse("1 + 1 + 2");console.log(a()); //4

我們看下1+1+2生成的token是什么樣的:

[{"index":0,"text":"1","constant":true,"value":1},{"index":2,"text":"+","operator":true},{"index":4,"text":"1","constant":true,"value":1},{"index":6,"text":"+","operator":true},{"index":8,"text":"2","constant":true,"value":2}]

Parser根據lexer生成的tokens嘗試解析。tokens每一個成員都會生成一個函數,其先后執行邏輯按照用戶輸入的1+1+2的順序執行。注意像1和2這類constants為true的token,parser會通過constant生成需要的函數$parseConstant,也就是說1+1+2中的兩個1和一個2都是返回$parseConstant函數,通過$parseBinaryFn管理加法邏輯。

constant: function() { var value = this.consume().value; return extend(function $parseConstant() {  return value; //這個函數執行之后,就是將value值返回。 }, {  constant: true,  literal: true }); },binaryFn: function(left, op, right, isBranching) { var fn = OPERATORS[op];//加法邏輯 return extend(function $parseBinaryFn(self, locals) {  return fn(self, locals, left, right);//left和right分別表示生成的對應函數 }, {  constant: left.constant && right.constant,  inputs: !isBranching && [left, right] }); }

那我們demo中的a應該返回什么函數呢?當然是$parseBinaryFn。其中的left和right分別是1+1的$parseBinaryFn,right就是2的$parseConstant。

再來一個例子:

var _l = new Lexer({});var _p = new Parser(_l);var a = _p.parse('{"name": "hello"}');console.log(a);

這邊我們傳入一個json,理論上我們執行完a函數,應該返回一個{name: "hello"}的對象。它調用了Parser中的object

object: function() { var keys = [], valueFns = []; if (this.peekToken().text !== '}') {  do {  if (this.peek('}')) {   // Support trailing commas per ES5.1.   break;  }  var token = this.consume();  if (token.constant) {   //把key取出來   keys.push(token.value);  } else if (token.identifier) {   keys.push(token.text);  } else {   this.throwError("invalid key", token);  }  this.consume(':');  //冒號之后,則是值,將值存在valueFns中  valueFns.push(this.expression());  //根據逗號去迭代下一個  } while (this.expect(',')); } this.consume('}'); return extend(function $parseObjectLiteral(self, locals) {  var object = {};  for (var i = 0, ii = valueFns.length; i < ii; i++) {  object[keys[i]] = valueFns[i](self, locals);  }  return object; }, {  literal: true,  constant: valueFns.every(isConstant),  inputs: valueFns }); }

比方我們的例子{"name": "hello"},object會將name存在keys中,hello則會生成$parseConstant函數存在valueFns中,最終返回$parseObjectLiternal函數。

下一個例子:

var a = _p.parse('{"name": "hello"}["name"]');

這個跟上一個例子的差別在于后面嘗試去讀取name的值,這邊則調用parser中的objectIndex方法。

objectIndex: function(obj) { var expression = this.text; var indexFn = this.expression(); this.consume(']'); return extend(function $parseObjectIndex(self, locals) {  var o = obj(self, locals), //parseObjectLiteral,實際就是obj   i = indexFn(self, locals), //$parseConstant,這里就是name   v;  ensureSafeMemberName(i, expression);  if (!o) return undefined;  v = ensureSafeObject(o[i], expression);  return v; }, {  assign: function(self, value, locals) {  var key = ensureSafeMemberName(indexFn(self, locals), expression);  // prevent overwriting of Function.constructor which would break ensureSafeObject check  var o = ensureSafeObject(obj(self, locals), expression);  if (!o) obj.assign(self, o = {}, locals);  return o[key] = value;  } }); }

很簡單吧,obj[xx]和obj.x類似。大家自行閱讀,我們再看一個函數調用的demo

var _l = new Lexer({});var _p = new Parser(_l, '', {});var demo = { "test": function(){ alert("welcome"); }};var a = _p.parse('test()');console.log(a(demo));

我們傳入一個test的調用。這邊調用了parser中的functionCall方法和identifier方法

identifier: function() { var id = this.consume().text; //Continue reading each `.identifier` unless it is a method invocation while (this.peek('.') && this.peekAhead(1).identifier && !this.peekAhead(2, '(')) {  id += this.consume().text + this.consume().text; } return getterFn(id, this.options, this.text); }

看一下getterFn方法

...forEach(pathKeys, function(key, index) {  ensureSafeMemberName(key, fullExp);  var lookupJs = (index      // we simply dereference 's' on any .dot notation      ? 's'      // but if we are first then we check locals first, and if so read it first      : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key;  if (expensiveChecks || isPossiblyDangerousMemberName(key)) {  lookupJs = 'eso(' + lookupJs + ', fe)';  needsEnsureSafeObject = true;  }  code += 'if(s == null) return undefined;/n' +    's=' + lookupJs + ';/n'; }); code += 'return s;'; /* jshint -W054 */ var evaledFnGetter = new Function('s', 'l', 'eso', 'fe', code); // s=scope, l=locals, eso=ensureSafeObject /* jshint +W054 */ evaledFnGetter.toString = valueFn(code);...

這是通過字符串創建一個匿名函數的方法。我們看下demo的test生成了一個什么匿名函數:

function('s', 'l', 'eso', 'fe'){if(s == null) return undefined;s=((l&&l.hasOwnProperty("test"))?l:s).test;return s;}

這個匿名函數的意思,需要傳入一個上下文,匿名函數通過查找上下文中是否有test屬性,如果沒有傳上下文則直接返回未定義。這也就是為什么我們在生成好的a函數在執行它時需要傳入demo對象的原因。最后補一個functionCall

functionCall: function(fnGetter, contextGetter) { var argsFn = []; if (this.peekToken().text !== ')') {  /* 確認調用時有入參 */  do {  //形參存入argsFn  argsFn.push(this.expression());  } while (this.expect(',')); } this.consume(')'); var expressionText = this.text; // we can safely reuse the array across invocations var args = argsFn.length ? [] : null; return function $parseFunctionCall(scope, locals) {  var context = contextGetter ? contextGetter(scope, locals) : isDefined(contextGetter) ? undefined : scope;  //或者之前創建生成的匿名函數  var fn = fnGetter(scope, locals, context) || noop;  if (args) {  var i = argsFn.length;  while (i--) {   args[i] = ensureSafeObject(argsFn[i](scope, locals), expressionText);  }  }  ensureSafeObject(context, expressionText);  ensureSafeFunction(fn, expressionText);  // IE doesn't have apply for some native functions  //執行匿名函數的時候需要傳入上下文  var v = fn.apply   ? fn.apply(context, args)   : fn(args[0], args[1], args[2], args[3], args[4]);  if (args) {  // Free-up the memory (arguments of the last function call).  args.length = 0;  }  return ensureSafeObject(v, expressionText);  }; }

下面我們看一下$ParseProvider,這是一個基于Lex和Parser函數的angular內置provider。它對scope的api提供了基礎支持。

...return function $parse(exp, interceptorFn, expensiveChecks) {  var parsedExpression, oneTime, cacheKey;  switch (typeof exp) {  case 'string':   cacheKey = exp = exp.trim();   var cache = (expensiveChecks ? cacheExpensive : cacheDefault);   parsedExpression = cache[cacheKey];   if (!parsedExpression) {   if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {    oneTime = true;    exp = exp.substring(2);   }   var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;   //調用lexer和parser   var lexer = new Lexer(parseOptions);   var parser = new Parser(lexer, $filter, parseOptions);   parsedExpression = parser.parse(exp);   //添加$$watchDelegate,為scope部分提供支持   if (parsedExpression.constant) {    parsedExpression.$$watchDelegate = constantWatchDelegate;   } else if (oneTime) {    //oneTime is not part of the exp passed to the Parser so we may have to    //wrap the parsedExpression before adding a $$watchDelegate    parsedExpression = wrapSharedExpression(parsedExpression);    parsedExpression.$$watchDelegate = parsedExpression.literal ?    oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;   } else if (parsedExpression.inputs) {    parsedExpression.$$watchDelegate = inputsWatchDelegate;   }   //做相關緩存   cache[cacheKey] = parsedExpression;   }   return addInterceptor(parsedExpression, interceptorFn);  case 'function':   return addInterceptor(exp, interceptorFn);  default:   return addInterceptor(noop, interceptorFn);  } };

總結:Lexer和Parser的實現確實讓我大開眼界。通過這兩個函數,實現了angular自己的語法解析器。邏輯部分還是相對復雜

以上所述是小編給大家介紹的Angularjs 1.3 中的$parse實例代碼,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對武林網網站的支持!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品国语对白| 成人激情综合网| 77777少妇光屁股久久一区| 狠狠色狠狠色综合日日五| 日本久久久久亚洲中字幕| 亚洲国产精品va在线看黑人动漫| 一本大道香蕉久在线播放29| 国产美女扒开尿口久久久| 秋霞av国产精品一区| 久久影视电视剧凤归四时歌| 亚洲欧美资源在线| 中文字幕不卡在线视频极品| 欧美贵妇videos办公室| 91高潮精品免费porn| 性色av一区二区三区红粉影视| 欧美精品18videos性欧| 精品国产区一区二区三区在线观看| 欧美第一页在线| 日韩av在线免费观看| 日韩中文字幕免费视频| 欧美孕妇毛茸茸xxxx| 国产精品第七十二页| 欧美激情成人在线视频| 欧美大片免费观看在线观看网站推荐| 国语自产精品视频在线看一大j8| 国产综合视频在线观看| 国产精品白丝jk喷水视频一区| 亚洲国产日韩欧美在线动漫| 久久精品99国产精品酒店日本| 一本久久综合亚洲鲁鲁| 国产精品草莓在线免费观看| 亚洲欧美中文字幕在线一区| 91国语精品自产拍在线观看性色| 国产精品久久久久免费a∨| 国产一区二区三区在线播放免费观看| 日韩精品免费看| 在线亚洲欧美视频| 国产精品久久久91| 久久久久久久999精品视频| 尤物yw午夜国产精品视频明星| 夜夜嗨av色综合久久久综合网| 欧美日韩国产一区二区| xxxx性欧美| 久久在线免费视频| 欧美日韩国产丝袜美女| 亚洲国产精品系列| 国产一区二区三区免费视频| 在线亚洲午夜片av大片| 中文字幕在线成人| 国产成人精品在线视频| 伊人久久五月天| 久久精品99久久久久久久久| 欧洲美女7788成人免费视频| 成人h猎奇视频网站| 欧美激情va永久在线播放| 在线精品高清中文字幕| 亚洲欧洲国产伦综合| 久久久精品久久久久| 亚洲天堂av在线播放| 国产精品久久久av| 久久天天躁狠狠躁夜夜躁2014| 国产午夜精品视频免费不卡69堂| 久久中文久久字幕| 亚洲图片在线综合| 亚洲精品成人久久久| 国产视频丨精品|在线观看| 国产精品高精视频免费| 人人做人人澡人人爽欧美| 国产欧美一区二区白浆黑人| 欧美日韩国产一区二区| 欧美成人精品在线观看| 久久久电影免费观看完整版| xx视频.9999.com| 久久视频在线视频| 国产精品视频99| 6080yy精品一区二区三区| 欧美成人四级hd版| 日av在线播放中文不卡| 精品露脸国产偷人在视频| 精品中文字幕在线2019| 国产精品18久久久久久麻辣| 国产精品福利观看| 亚洲人成网站777色婷婷| 国产精品亚洲一区二区三区| 国产精品久久久精品| 日韩在线观看网站| 成人黄色av网| 日韩中文视频免费在线观看| 成人黄色片在线| 欧美极品少妇xxxxⅹ裸体艺术| 日韩经典第一页| 国产精品久久久久久五月尺| 1769国产精品| 欧美日韩国内自拍| 中日韩午夜理伦电影免费| 亚洲国产精品999| 日韩小视频在线| 欧美大片在线免费观看| 亚洲a一级视频| 久久精品视频在线观看| 成人在线观看视频网站| 国产精品91在线| 成人福利网站在线观看| 亚洲人成绝费网站色www| 欧美黑人xxx| 亚洲视频欧洲视频| 91久久在线观看| 亚洲视频电影图片偷拍一区| 4438全国亚洲精品在线观看视频| 欧美中文在线视频| 日韩av综合中文字幕| 国产视频精品在线| 精品亚洲男同gayvideo网站| 亚洲自拍偷拍色片视频| 91精品久久久久久久久久另类| 狠狠综合久久av一区二区小说| 色无极影院亚洲| 欧美成人精品在线观看| 欧美一区二区三区精品电影| 欧美日韩在线免费观看| 国产成人av网址| 亚洲91精品在线观看| 中文字幕久久亚洲| 在线观看欧美视频| 久久精品中文字幕电影| 91精品国产综合久久香蕉的用户体验| 亚洲人成电影在线| 日本乱人伦a精品| 日本高清视频精品| 日韩精品视频在线观看免费| 欧美性xxxxxxx| 国产美女搞久久| 国产精品91在线观看| 韩曰欧美视频免费观看| 午夜精品久久久99热福利| 日韩精品视频在线播放| 久久91亚洲人成电影网站| 亚洲最大中文字幕| 久久久久久久久中文字幕| 亚洲欧美国产视频| 亚洲免费中文字幕| 精品亚洲va在线va天堂资源站| 国产精品国产自产拍高清av水多| 日韩国产精品亚洲а∨天堂免| 久久最新资源网| 欧美激情xxxx| 亚洲一区二区三区四区在线播放| 国产精品日韩在线观看| www.久久撸.com| 57pao成人永久免费视频| 国产精品欧美在线| 亚洲一区二区在线| 蜜臀久久99精品久久久无需会员| 在线看福利67194| 日韩va亚洲va欧洲va国产| 91精品视频播放| 人人做人人澡人人爽欧美| 国产成人精品久久二区二区91| 精品中文字幕在线观看| 亚洲欧美国产一区二区三区| 久久精品91久久久久久再现| 亚洲黄色有码视频| 久久在线观看视频| 亚洲黄一区二区|