• 都别说工资低了,我们来一起写简单的dom选择器吧!


    前言

    我师父(http://www.cnblogs.com/aaronjs/)说应当阅读框架(jquery),所以老夫就准备开始看了

    然后公司的师兄原来写了个dom选择器,感觉不错啊!!!原来自己从来没有想过能写这些,所以我们今天一起来试试吧

    我写的这个很简单,就是最简单的那种了,但是公司师兄写的很好。。。。。。

    简单dom选择器

    先上个完整的代码吧,因为我已经写起了:

      1 <html xmlns="http://www.w3.org/1999/xhtml">
      2 <head>
      3     <title></title>
      4 </head>
      5 <body>
      6     <div id="parent">
      7         <div class="child1" id="c">
      8             <div>
      9             </div>
     10             <input type="button" value="测试"  class="child1.1" />
     11         </div>
     12         <div class="child1">
     13         </div>
     14         <div class="child2">
     15         </div>
     16         <div class="child2 child1">
     17             <div class="child1.1">
     18                 <input class="l" value="三层测试" />
     19             </div>
     20         </div>
     21         <input type="button" value="测试2" />
     22     </div>
     23     <script type="text/javascript">
     24         
     25         (function () {
     26             var queryFunc = {
     27                 '#': function (id) {
     28                     var arr = [];
     29                     arr.push(document.getElementById(id))
     30                     return arr;
     31                 },
     32                 '.': function (className) {
     33                     var els = document.getElementsByTagName('*');
     34                     var reg = new RegExp('(^|\s)' + className + '(\s|$)');
     35                     var arr = [];
     36                     for (var i = 0, len = els.length; i < len; i++) {
     37                         if (reg.test(els[i].className)) {
     38                             arr.push(els[i]);
     39                         }
     40                     }
     41                     return arr;
     42                 },
     43                 'tag': function (tag) {
     44                     return document.getElementsByTagName(tag);
     45                 }
     46             };
     47 
     48             var filterFunc = {
     49                 '#': function (el, id) {
     50                     return this.commomFunc(el, function (p) {
     51                         if (p.id == id) {
     52                             return true;
     53                         }
     54                     });
     55                 },
     56                 '.': function (el, className) {
     57                     var reg = new RegExp('(^|\s)' + className + '(\s|$)');
     58                     return this.commomFunc(el, function (p) {
     59                         if (reg.test(p.className)) {
     60                             return true;
     61                         }
     62                     });
     63                 },
     64                 'tag': function (el, tag) {
     65                     return this.commomFunc(el, function (p) {
     66                         if (p.tagName.toLowerCase() == tag) {
     67                             return true;
     68                         }
     69                     });
     70                 },
     71                 commomFunc: function (el, callback) {
     72                     var p = el;
     73                     var end = false;
     74                     while (!end) {
     75                         p = p.parentNode || p.parentElement || null;
     76                         if (!p) return false;
     77                         var b = callback && callback(p);
     78                         if (b) return b;
     79                         if (p.tagName == 'BODY') {
     80                             end = true;
     81                         }
     82                     }
     83                     return false;
     84                 }
     85             };
     86 
     87             var getKV = function (str) {
     88                 if (!str || typeof str != 'string') return null;
     89                 var k = str.substring(0, 1);
     90                 var v = str;
     91                 if (k == '.' || k == '#') {
     92                     v = str.substring(1, str.length);
     93                 } else {
     94                     k = 'tag';
     95                 }
     96                 return {
     97                     k: k,
     98                     v: v
     99                 };
    100             }
    101 
    102             var query = function (str) {
    103                 var kv = getKV(str)
    104                 return (queryFunc[kv.k] && queryFunc[kv.k](kv.v));
    105             };
    106             var filter = function (el, str) {
    107                 var kv = getKV(str);
    108                 return (filterFunc[kv.k] && filterFunc[kv.k](el, kv.v));
    109             };
    110             var explodeSelector = function (str) {
    111                 if (!str) return [];
    112                 //第一步去掉多余的空格
    113                 str = str.replace(/s+/g, ' ');
    114                 var arr = str.split(' ');
    115                 return arr;
    116             };
    117             //筛选元素是否具有该属性
    118 
    119             var queryAll = function (str) {
    120                 var arrSelector = explodeSelector(str);
    121                 var len = arrSelector.length;
    122 
    123                 //当前索引
    124                 var index = len - 2;
    125                 var curDom = null;
    126                 var curSelector = null;
    127 
    128                 //第一轮筛选出来的元素
    129                 var els = query(arrSelector[len - 1]);
    130                 //只有一个选择器便直接返回了
    131                 if (len == 1) return els;
    132 
    133                 while (index != -1) {
    134                     //获取当前的筛选选择器
    135                     curSelector = arrSelector[index];
    136                     var tmpArr = [];
    137                     for (var i = 0, len = els.length; i < len; i++) {
    138                         var tmpEl = els[i];
    139                         if (filter(tmpEl, curSelector))
    140                             tmpArr.push(tmpEl);
    141                     }
    142                     els = tmpArr;
    143                     index--;
    144                 }
    145                 return els;
    146             };
    147             window.queryAll = queryAll;
    148         })();
    149 
    150         var selector = [
    151             'div',
    152             'div input',
    153             '#parent',
    154             '#parent .child1',
    155             '#parent .child2',
    156             '.child1     .child1.1',
    157             '#c .child1.1',
    158             '#parent .child1    .child1.1 input'
    159         ];
    160 
    161         for (var i in selector) {
    162             console.log(selector[i] + '——' + queryAll(selector[i]));
    163             var s = '';
    164         }
    165 
    166     </script>
    167 </body>
    168 </html>
    View Code
     1 <div id="parent">
     2     <div class="child1" id="c">
     3         <div>
     4         </div>
     5         <input type="button" value="测试"  class="child1.1" />
     6     </div>
     7     <div class="child1">
     8     </div>
     9     <div class="child2">
    10     </div>
    11     <div class="child2 child1">
    12         <div class="child1.1">
    13             <input class="l" value="三层测试" />
    14         </div>
    15     </div>
    16     <input type="button" value="测试2" />
    17 </div>

    简单的结果,然后我们来简单看看我丑陋的代码:

      1 (function () {
      2     var queryFunc = {
      3         '#': function (id) {
      4             var arr = [];
      5             arr.push(document.getElementById(id))
      6             return arr;
      7         },
      8         '.': function (className) {
      9             var els = document.getElementsByTagName('*');
     10             var reg = new RegExp('(^|\s)' + className + '(\s|$)');
     11             var arr = [];
     12             for (var i = 0, len = els.length; i < len; i++) {
     13                 if (reg.test(els[i].className)) {
     14                     arr.push(els[i]);
     15                 }
     16             }
     17             return arr;
     18         },
     19         'tag': function (tag) {
     20             return document.getElementsByTagName(tag);
     21         }
     22     };
     23 
     24     var filterFunc = {
     25         '#': function (el, id) {
     26             return this.commomFunc(el, function (p) {
     27                 if (p.id == id) {
     28                     return true;
     29                 }
     30             });
     31         },
     32         '.': function (el, className) {
     33             var reg = new RegExp('(^|\s)' + className + '(\s|$)');
     34             return this.commomFunc(el, function (p) {
     35                 if (reg.test(p.className)) {
     36                     return true;
     37                 }
     38             });
     39         },
     40         'tag': function (el, tag) {
     41             return this.commomFunc(el, function (p) {
     42                 if (p.tagName.toLowerCase() == tag) {
     43                     return true;
     44                 }
     45             });
     46         },
     47         commomFunc: function (el, callback) {
     48             var p = el;
     49             var end = false;
     50             while (!end) {
     51                 p = p.parentNode || p.parentElement || null;
     52                 if (!p) return false;
     53                 var b = callback && callback(p);
     54                 if (b) return b;
     55                 if (p.tagName == 'BODY') {
     56                     end = true;
     57                 }
     58             }
     59             return false;
     60         }
     61     };
     62 
     63     var getKV = function (str) {
     64         if (!str || typeof str != 'string') return null;
     65         var k = str.substring(0, 1);
     66         var v = str;
     67         if (k == '.' || k == '#') {
     68             v = str.substring(1, str.length);
     69         } else {
     70             k = 'tag';
     71         }
     72         return {
     73             k: k,
     74             v: v
     75         };
     76     }
     77 
     78     var query = function (str) {
     79         var kv = getKV(str)
     80         return (queryFunc[kv.k] && queryFunc[kv.k](kv.v));
     81     };
     82     var filter = function (el, str) {
     83         var kv = getKV(str);
     84         return (filterFunc[kv.k] && filterFunc[kv.k](el, kv.v));
     85     };
     86     var explodeSelector = function (str) {
     87         if (!str) return [];
     88         //第一步去掉多余的空格
     89         str = str.replace(/s+/g, ' ');
     90         var arr = str.split(' ');
     91         return arr;
     92     };
     93     //筛选元素是否具有该属性
     94 
     95     var queryAll = function (str) {
     96         var arrSelector = explodeSelector(str);
     97         var len = arrSelector.length;
     98 
     99         //当前索引
    100         var index = len - 2;
    101         var curDom = null;
    102         var curSelector = null;
    103 
    104         //第一轮筛选出来的元素
    105         var els = query(arrSelector[len - 1]);
    106         //只有一个选择器便直接返回了
    107         if (len == 1) return els;
    108 
    109         while (index != -1) {
    110             //获取当前的筛选选择器
    111             curSelector = arrSelector[index];
    112             var tmpArr = [];
    113             for (var i = 0, len = els.length; i < len; i++) {
    114                 var tmpEl = els[i];
    115                 if (filter(tmpEl, curSelector))
    116                     tmpArr.push(tmpEl);
    117             }
    118             els = tmpArr;
    119             index--;
    120         }
    121         return els;
    122     };
    123     window.queryAll = queryAll;
    124 })();

    基本思路

    ① 获取选择器字符串,并将之分解为一个数组

    var explodeSelector = function (str) {
        if (!str) return [];
        //第一步去掉多余的空格
        str = str.replace(/s+/g, ' ');
        var arr = str.split(' ');
        return arr;
    };

    ② 与CSS选择器一致,根据各种条件选取相关元素

     1 var queryFunc = {
     2     '#': function (id) {
     3         var arr = [];
     4         arr.push(document.getElementById(id))
     5         return arr;
     6     },
     7     '.': function (className) {
     8         var els = document.getElementsByTagName('*');
     9         var reg = new RegExp('(^|\s)' + className + '(\s|$)');
    10         var arr = [];
    11         for (var i = 0, len = els.length; i < len; i++) {
    12             if (reg.test(els[i].className)) {
    13                 arr.push(els[i]);
    14             }
    15         }
    16         return arr;
    17     },
    18     'tag': function (tag) {
    19         return document.getElementsByTagName(tag);
    20     }
    21 };

    ③ 根据选择器与获得的dom数组,判断其父元素是否具有相关属性(id,className,tag),有便留下来,没有就不管他

    过滤下来的就是我们要的元素:

     1 var filterFunc = {
     2     '#': function (el, id) {
     3         return this.commomFunc(el, function (p) {
     4             if (p.id == id) {
     5                 return true;
     6             }
     7         });
     8     },
     9     '.': function (el, className) {
    10         var reg = new RegExp('(^|\s)' + className + '(\s|$)');
    11         return this.commomFunc(el, function (p) {
    12             if (reg.test(p.className)) {
    13                 return true;
    14             }
    15         });
    16     },
    17     'tag': function (el, tag) {
    18         return this.commomFunc(el, function (p) {
    19             if (p.tagName.toLowerCase() == tag) {
    20                 return true;
    21             }
    22         });
    23     },
    24     commomFunc: function (el, callback) {
    25         var p = el;
    26         var end = false;
    27         while (!end) {
    28             p = p.parentNode || p.parentElement || null;
    29             if (!p) return false;
    30             var b = callback && callback(p);
    31             if (b) return b;
    32             if (p.tagName == 'BODY') {
    33                 end = true;
    34             }
    35         }
    36         return false;
    37     }
    38 };
     1 var queryAll = function (str) {
     2     var arrSelector = explodeSelector(str);
     3     var len = arrSelector.length;
     4 
     5     //当前索引
     6     var index = len - 2;
     7     var curDom = null;
     8     var curSelector = null;
     9 
    10     //第一轮筛选出来的元素
    11     var els = query(arrSelector[len - 1]);
    12     //只有一个选择器便直接返回了
    13     if (len == 1) return els;
    14 
    15     while (index != -1) {
    16         //获取当前的筛选选择器
    17         curSelector = arrSelector[index];
    18         var tmpArr = [];
    19         for (var i = 0, len = els.length; i < len; i++) {
    20             var tmpEl = els[i];
    21             if (filter(tmpEl, curSelector))
    22                 tmpArr.push(tmpEl);
    23         }
    24         els = tmpArr;
    25         index--;
    26     }
    27     return els;
    28 };

    ④,然后,就没有然后了。。。。

    不足与提高

    这个代码明显就是玩具,三无产品:

    ① 无测试

    ② 无子选择器/兄弟选择器

    ③ 无性能

    但是,以上东西暂时和我无关啦,因为学习嘛。。。。

    最后附上师兄选择器代码:

      1 <!doctype html>
      2 <html>
      3 <head>
      4     <title>aiQuery test</title>
      5     <script type="text/javascript">
      6         /**
      7         * aiQuery
      8         * @author ouxingzhi 
      9         */
     10         void function (window, document, undefined) {
     11             var location = window.location,
     12        Slice = [].slice,
     13        RegTrim = /(?:^s+|s+$)/,
     14        RegBlank = /s+/,
     15        RegOperate = /s*(?:s|>|+|~(?!=))s*/i,
     16        RegElement = /^([w-]+|*)?(?:#([w-]+))?(?:.([w-]+))?(?:[([w-]+)(?:([~|^|$|*||]?=)['"]?([w-]+)['"]?)?])?(?::([w-]+(?:([w-]+))?))?$/i;
     17             function AIQuery(Selector, Content) {
     18                 Selector = Selector.replace(RegTrim, '');
     19                 Content = Content || document;
     20                 if (Content.querySelectorAll) {
     21                     return Slice.call(Content.querySelectorAll(Selector));
     22                 } else {
     23                     return querySelectorAll(Selector, Content)
     24                 }
     25             }
     26             function querySelectorAll(Selector, Content) {
     27                 var Groups = Selector.split(/s*\,s*/img),
     28            Results = [];
     29                 for (var i = 0,
     30            len = Groups.length; i < len; i++) {
     31                     Results = Results.concat(Find(Groups[i], Content))
     32                 }
     33                 return Results
     34             }
     35             function Find(Selector, Content) {
     36                 var Results = [],
     37            atoms = Selector.split(RegOperate),
     38            operates = Selector.match(RegOperate);
     39                 operates = operates || [];
     40                 for (var i = 0,
     41            len = operates.length; i < len; i++) (operates[i] = /^s+$/.test(operates[i]) ? ' ' : operates[i].replace(RegTrim, ''));
     42                 var Results = EachTo(' ', atoms.shift(), operates, atoms, Content);
     43                 return Results
     44             }
     45             function EachTo(op, at, operates, atoms, Content) {
     46                 var Results = [],
     47            Median = [],
     48            operate,
     49            atom;
     50                 if (Content.constructor === Array || 'length' in Content) {
     51                     for (var i = 0,
     52                len = Content.length; i < len; i++) {
     53                         Results = Results.concat(EachTo(op, at, operates.slice(0), atoms.slice(0), Content[i]))
     54                     }
     55                 } else if (Content.constructor === String) {
     56                     Content = Find(Content, document);
     57                     Results.concat(EachTo(op, at, operates.slice(0), atoms.slice(0), Content[i]))
     58                 } else {
     59                     Median = GetElementByAny(op, at, Content);
     60                     if (Median) {
     61                         if (operates && operates.length && atoms && atoms.length) {
     62                             Results = EachTo(operates.shift(), atoms.shift(), operates, atoms, Median)
     63                         } else {
     64                             Results = Median
     65                         }
     66                     }
     67                 }
     68                 return Results
     69             }
     70             function GetElementByAny(op, at, Content) {
     71                 if (typeof OperateFunction[op] !== 'undefined') {
     72                     return OperateFunction[op](at, Content)
     73                 }
     74             }
     75             var OperateFunction = {
     76                 ' ': function (at, Content) {
     77                     var einfo = buildElementInfo(at),
     78                preNodes = [];
     79                     if (!einfo) return [];
     80                     if (einfo.Id) {
     81                         preNodes = document.getElementById(einfo.Id);
     82                         preNodes = preNodes ? [preNodes] : []
     83                     } else if (einfo.ClassName && Content.getElementsByClassName) {
     84                         preNodes = Content.getElementsByClassName(einfo.ClassName);
     85                         preNodes = preNodes || []
     86                     } else if (einfo.TagName && Content.getElementsByTagName) {
     87                         preNodes = Content.getElementsByTagName(einfo.TagName);
     88                         preNodes = preNodes || []
     89                     } else {
     90                         preNodes = Content.getElementsByTagName('*');
     91                         preNodes = preNodes || []
     92                     };
     93                     return filterNode(einfo, preNodes)
     94                 },
     95                 '>': function (at, Content) {
     96                     var einfo = buildElementInfo(at);
     97                     preNodes = Content.childNodes || [];
     98                     if (!einfo) return [];
     99                     return filterNode(einfo, preNodes)
    100                 },
    101                 '+': function (at, Content) {
    102                     if (Content === document) return [];
    103                     var einfo = buildElementInfo(at);
    104                     if (!einfo) return [];
    105                     var results = [],
    106                preNodes = (function () {
    107                    var nextNode = Content.nextSibling;
    108                    while (nextNode && nextNode.nodeType != 1) {
    109                        nextNode = nextNode.nextSibling
    110                    }
    111                    return nextNode
    112                })();
    113                     preNodes = preNodes ? [preNodes] : [];
    114                     if (preNodes.length) {
    115                         results = filterNode(einfo, preNodes)
    116                     } else {
    117                         results = []
    118                     }
    119                     return results
    120                 },
    121                 '~': function (at, Content) {
    122                     if (Content === document) return [];
    123                     var einfo = buildElementInfo(at),
    124                preNodes = [];
    125                     if (!einfo) return [];
    126                     var sibling = Content.parentNode ? Content.parentNode.childNodes : null;
    127                     if (sibling) {
    128                         for (var i = 0,
    129                    len = sibling.length; i < len; i++) if (Content !== sibling[i]) preNodes.push(sibling[i])
    130                     }
    131                     return filterNode(einfo, preNodes)
    132                 }
    133             };
    134             function buildElementInfo(at) {
    135                 var Einfo = RegElement.exec(at);
    136                 if (!Einfo) return;
    137                 return {
    138                     TagName: Einfo[1] || undefined,
    139                     Id: Einfo[2] || undefined,
    140                     ClassName: Einfo[3] || undefined,
    141                     AttrName: Einfo[4] || undefined,
    142                     AttrOper: Einfo[5] || undefined,
    143                     AttrVal: Einfo[6] || undefined,
    144                     FakeClass: Einfo[7] || undefined
    145                 }
    146             }
    147             function filterNode(Einfo, Nodes) {
    148                 var results = [],
    
    149                RegClassName,
    150                isMatch;
    151                 if (Einfo.ClassName) RegClassName = new RegExp('\b' + Einfo.ClassName + '\b', 'i');
    152                 for (var i = 0,
    153            len = Nodes.length; i < len; i++) {
    154                     isMatch = true;
    155                     if (Einfo.TagName !== undefined && Einfo.TagName.toUpperCase() !== Nodes[i].nodeName) isMatch = false;
    156                     if (Einfo.Id !== undefined && Einfo.Id !== Nodes[i].id) isMatch = false;
    157                     if (Einfo.ClassName !== undefined && !Nodes[i].className.match(RegClassName)) isMatch = false;
    158                     isMatch = isMatchAttribute(Einfo, Nodes[i], isMatch);
    159                     isMatch = isMatchFakeClass(Einfo, Nodes[i], isMatch);
    160                     if (isMatch) results.push(Nodes[i])
    161                 }
    162                 return results
    163             }
    164             function isMatchAttribute(Einfo, node, isMatch) {
    165                 if (Einfo.AttrName === undefined && Einfo.AttrOper === undefined && Einfo.AttrVal === undefined) { } else if (Einfo.AttrName !== undefined && Einfo.AttrOper === undefined && Einfo.AttrVal === undefined && node.getAttribute && node.getAttribute(Einfo.AttrName) !== null) {
    166                     isMatch = true
    167                 } else if (Einfo.AttrName !== undefined && Einfo.AttrOper !== undefined && Einfo.AttrVal !== undefined && node.getAttribute) {
    168                     switch (Einfo.AttrOper) {
    169                         case '=':
    170                             isMatch = node.getAttribute(Einfo.AttrName) === Einfo.AttrVal;
    171                             break;
    172                         case '~=':
    173                             isMatch = !!(node.getAttribute(Einfo.AttrName) && node.getAttribute(Einfo.AttrName).match(new RegExp('(?:^|\s+)' + Einfo.AttrVal + '(?:$|\s+)', 'i')));
    174                             break;
    175                         case '^=':
    176                             isMatch = !!(node.getAttribute(Einfo.AttrName) && node.getAttribute(Einfo.AttrName).match(new RegExp('^' + Einfo.AttrVal, 'i')));
    177                             break;
    178                         case '$=':
    179                             isMatch = !!(node.getAttribute(Einfo.AttrName) && node.getAttribute(Einfo.AttrName).match(new RegExp(Einfo.AttrVal + '$', 'i')));
    180                             break;
    181                         case '*=':
    182                             isMatch = !!(node.getAttribute(Einfo.AttrName) && node.getAttribute(Einfo.AttrName).match(new RegExp(Einfo.AttrVal, 'i')));
    183                             break;
    184                         case '|=':
    185                             isMatch = !!(node.getAttribute(Einfo.AttrName) && node.getAttribute(Einfo.AttrName).match(new RegExp('(?:^|\-)' + Einfo.AttrVal + '(?:$|\-)', 'i')));
    186                             break
    187                     }
    188                 }
    189                 return isMatch
    190             }
    191             function isMatchFakeClass(Einfo, node, isMatch) {
    192                 if (Einfo.FakeClass === undefined) { } else {
    193                     switch (Einfo.FakeClass) {
    194                         case 'empty':
    195                             isMatch = node.innerHTML.replace(RegTrim, '').length == 0;
    196                             break;
    197                         case 'checked':
    198                             if (node.nodeName.match(/(?:INPUT|TEXTAREA|BUTTON|SELECT|OPTION)/i)) isMatch = !!node.checked;
    199                             break;
    200                         case 'enabled':
    201                             if (node.nodeName.match(/(?:INPUT|TEXTAREA|BUTTON|SELECT|OPTION)/i)) isMatch = !!node.disabled;
    202                             break;
    203                         case 'disabled':
    204                             if (node.nodeName.match(/(?:INPUT|TEXTAREA|BUTTON|SELECT|OPTION)/i)) isMatch = !!node.disabled;
    205                             break;
    206                         case 'target':
    207                             var hash = location.hash.replace('#', '');
    208                             isMatch = hash === node.id || (node.name && hash === node.name);
    209                             break
    210                     }
    211                 }
    212                 return isMatch
    213             }
    214             window['aiQuery'] = AIQuery;
    215         } (window, document);
    216            </script>
    217 </head>
    218 <body>
    219     <div class="aaa">
    220         <div class="bbb">
    221             <label>
    222                 用户名:</label>
    223             <input type="text" id="username" />
    224         </div>
    225         <pre>
    226 //使用方法
    227 alert(aiQuery('.aaa .bbb [type=text]'));
    228 alert(aiQuery('.aaa label + input'));
    229 alert(aiQuery('#username'));
    230 </pre>
    231     </div>
    232     <script type="text/javascript">
    233         alert(aiQuery('.aaa .bbb [type=text]'));
    234         alert(aiQuery('.aaa label + input'));
    235         alert(aiQuery('#username'));
    236            </script>
    237 </body>
    238 </html>
    View Code

    然后,就没有然后了。。。

    小插曲-浮点数计算

    今天项目重遇到一个问题,我和我的小伙伴都惊呆了:

    你没有看错,33.1 * 3 就是那么多!!!

    尼玛已证明这是一个js的bug,我可以这么说么???

    解决方案,求回复(我会说我想骗点回复么???)

    parseInt(33.1 * 3 * 100) / 100
  • 相关阅读:
    ASP.NET中使用附文本框插件
    HttpModules配置事项
    ASP.NET页面缓冲
    在ASP.NET中备份数据库以及还原(不成熟)
    python List使用
    SSH登录详解
    Vue.js使用-http请求
    Vue.js使用-组件示例(实现数据的CRUD)
    Vue.js使用-组件(下篇)
    Vue.js使用-组件(上篇)
  • 原文地址:https://www.cnblogs.com/yexiaochai/p/3258279.html
Copyright © 2020-2023  润新知