JavaScript高級


 

 

1.面向過程與面向對象

 1.1面向過程
  面向過程就是分析出解決問題所需要的步驟,然后用函數把這些步驟一步一步實現,使用的時候再一個一個的依次調用就可以了。
  面向過程是 實現代碼邏輯的 步驟,主要是把重心點放在實現功能上,不太去考慮封裝
  優點: 效率高,但是維護起來太麻煩  
  1.2面向對象
  面向對象是把事務分解成一個個對象,然后由對象之間分工合作。
  主要的重心點,先放在功能模塊的划分,然后想着什么功能模塊可以去進行復用(可以進行抽取)
  優點: 會讓我們功能具備模塊化開發,一個模塊就負責一個功能,后續方便我們的維護
   三大特性:繼承性、封裝性、多態性(抽象性)
 

2.對象與類

  2.1 對象  
  對象是由屬性和方法組成的:是一個無序鍵值對的集合,指的是一個具體的事物
  屬性:事物的特征,在對象中用屬性來表示(常用名詞)
  方法:事物的行為,在對象中用方法來表示(常用動詞)
  創建對象的兩種方法
    1.字面量創建對象    
      var ldh = {
      name: '劉德華',
      age: 18
      }
      console.log(ldh);
 
 
    2.構造函數創建對象
      function Star(uname,age){
        this.uname = name;
        this.age = age;
      }
      var ldh = new Star('劉德華',18); //實例化對象
      console.log(ldh);
 2.2 類 
  在 ES6 中新增加了類的概念,可以使用 class 關鍵字聲明一個類,之后以這個類來實
  例化對象。類抽象了對象的公共部分,它泛指某一大類(class)對象特指某一個,
  通過類實例化一個具體的對象
   
  2.2.1創建類
     語法:  
       //步驟1 使用class關鍵字
    class name {
    // class body
    }
    //步驟2使用定義的類創建實例 注意new關鍵字
    var xx = new name();
  2.2.2類創建添加屬性和方法  
    // 1. 創建類 class 創建一個類
    class Star {
      // 類的共有屬性放到 constructor 里面 constructor是 構造器或者構造函數
      constructor(uname, age) {
      this.uname = uname;
      this.age = age;
      }//‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐>注意,方法與方法之間不需要添加逗號
      sing(song) {
      console.log(this.uname + '唱' + song);
      }
    }
  // 2. 利用類創建對象 new 實例化
  var ldh = new Star('劉德華', 18);
  console.log(ldh);   // Star {uname: "劉德華", age: 18}
  ldh.sing('冰雨');     // 劉德華唱冰雨
 
   注意點:
    1. 通過class 關鍵字創建類, 類名我們還是習慣性定義首字母大寫
    2. 類里面有個constructor 函數,可以接受傳遞過來的參數,同時返回實例對象
    3. constructor 函數 只要 new 生成實例時,就會自動調用這個函數, 如果我們不寫這個函數,類也會自動生成這個函數
    4. 多個函數方法之間不需要添加逗號分隔
    5. 生成實例 new 不能省略
    6. 語法規范, 創建類 類名后面不要加小括號,生成實例 類名后面加小括號, 構造函數不需要加function
 
 2.2.3 類的繼承
  語法 :   class Father { }; // 父類
      class Son extends Father { } // 子類繼承父類 extends
  示例 :   
      class Father {
        constructor(surname) {
        this.surname= surname;
        }
        say() {
          console.log('你的姓是' + this.surname);
        }
      }
      class Son extends Father{     // 這樣子類就繼承了父類的屬性和方法
      }
      var damao= new Son('劉');
      damao.say(); // 輸出結果是   你的姓是劉
  
  子類使用super關鍵字訪問父類的方法
   
    //定義了父類
    class Father {
      constructor(x, y) {
      this.x = x;
      this.y = y;
      }
      sum() {
        console.log(this.x + this.y);
      }
    }
    //子元素繼承父類
    class Son extends Father {
      constructor(x, y) {
        super(x, y); //使用super調用了父類中的構造函數
      }
    }
    var son = new Son(1, 2);
    son.sum();     //結果為3
 
  注意:
    1. 繼承中,如果實例化子類輸出一個方法,先看子類有沒有這個方法,如果有就先執行子類的
    2. 繼承中,如果子類里面沒有,就去查找父類有沒有這個方法,如果有,就執行父類的這個方法(就近原則)
     3. 如果子類想要繼承父類的方法,同時在自己內部擴展自己的方法,利用super 調用父類的構造函數,super 必須在子類this之前調用
 
 2.2.4 時刻注意this的指向問題
    我們在類中,去使用屬性或者是調用方法千萬不要忘記了用 this,
    需要去關注的就是this的指向問題:
        默認情況下,類中的this都指向的是當前實例化的對象,
        除非 綁定事件之后this指向的就是觸發事件的事件源
 

3.構造函數和原型

  3.1 靜態成員和實例成員

    實例成員 :實例成員就是構造函數內部通過this添加的成員,實例成員只能通過實例化的對象來訪問

     以下代碼里的 uname  age sing就是實例成員

function Star(uname, age) {
     this.uname = uname; 
     this.age = age; 
     this.sing = function() { 
            console.log('我會唱歌'); 
      } 
}
var ldh = new Star('劉德華', 18); 
console.log(ldh.uname);    //實例成員只能通過實例化的對象來訪問    

 

     靜態成員 :在構造函數本身上添加的成員 靜態成員只能通過構造函數來訪問    在ES5中只要被static修飾的屬性和方法都是靜態成員

     下列代碼中 sex 就是靜態成員

   

function Star(uname, age) { 
         this.uname = uname; 
         this.age = age; 
         this.sing = function() { 
               console.log('我會唱歌'); 
         } 
}
Star.sex = '男'; 
var ldh = new Star('劉德華', 18);
 console.log(Star.sex);          //靜態成員只能通過構造函數來訪問

  3.2構造函數的問題

    存在浪費內存的問題

    

function Star(uname , age) {
      this.uname = uname;
      this.age = age;        
      this.sing = function(){
             console.log('我會唱歌')
      }
}
var ldh = new Star("劉德華",18);
var shuji= new Star("書記",22);
ldh.sing();
shuji.sing();

   以上代碼在調用sing方法時都會在內存開辟一個新的空間,如果有多個對象來調用sing方法就會占用過多內存,所以我們就用到了構造函數的原型 prototype。

  3.3 構造函數原型prototype

    構造函數通過原型分配的函數是所有對象所共享的。

     JavaScript 規定,每一個構造函數都有一個prototype 屬性,指向另一個對象。注意這個prototype就是一個對象,這個對象的所有屬性和方法,都會被構造函數所擁有。

     我們可以把那些不變的方法,直接定義在 prototype 對象上,這樣所有對象的實例就可以共享這些方法。

     構造函數.prototype就可以獲取原型對象

function Star(uname, age) { 
         this.uname = uname; 
         this.age = age;
 }
Star.prototype.sing = function() {
         console.log('我會唱歌'); 
}
var ldh = new Star('劉德華', 18); 
var zxy = new Star('張學友', 19); 
ldh.sing();  //我會唱歌 
zxy.sing();  //我會唱歌

 

   3.4 對象原型

    實例化對象都會有一個__proto__屬性指向構造函數的原型對象,而我們的實例化對象可以使用對象原型的屬性和方法就是因為有__proto__屬性的存在

    實例化對象.__proto__ ===  構造函數.prototype

    它們的三角關系如下圖:

      

    __proto__對象原型的意義就在於為對象的查找機制提供一個方向,或者說一條路線,但是它是一個非標准屬性,因此實際開發中,不可以使用這個屬性,它只是內部指向原型對象prototype

   3.5 constructor構造函數

     對象原型( __proto__)和構造函數(prototype)原型對象里面都有一個屬性constructor 屬性

     constructor 我們稱為構造函數,因為它指回構造函數本身。

     constructor 主要用於記錄該對象引用於哪個構造函數,它可以讓原型對象重新指向原來的構造函數。

    

    如果我們修改了原來的原型對象,給原型對象賦值的是一個對象,則必須手動的利用constructor指回原來的構造函數
function Star(uname, age) {
  this.uname = uname;
  this.age = age;
}
// 很多情況下,我們需要手動的利用constructor 這個屬性指回 原來的構造函數
Star.prototype = {
// 如果我們修改了原來的原型對象,給原型對象賦值的是一個對象,則必須手動的利用constructor指回原來的構造函數
  constructor: Star, // 手動設置指回原來的構造函數
  sing: function() {
    console.log('我會唱歌');
  },
  movie: function() {
    console.log('我會演電影');
  }
}
var zxy = new Star('張學友', 19);
console.log(zxy)

   3.6 原型鏈和查找機制

    每一個實例對象又有一個proto屬性,指向的構造函數的原型對象,構造函數的原型對象也是一個對象,也有proto屬性,這樣一層一層往上找就形成了原型鏈。

    

  

     查找機制:

      當訪問一個對象的屬性(包括方法)時,首先查找這個對象自身有沒有該屬性,如果自身有,就有自身的屬性和方法。

      如果沒有就查找它的原型(也就是 __proto__指向的 prototype 原型對象)。

      如果還沒有就查找原型對象的原型(Object的原型對象)。依此類推一直找到 Object 為止(null)。

      如果找到盡頭(null)都沒找到,是屬性則返回undefined,是方法則報錯

   

  3.7 原型對象中this的指向

    構造函數中的this和原型對象的this,都指向我們new出來的實例對象

   3.8 通過原型對象為數組擴展內置方法

    

var arr = [3,6,9,8,5]
arr.prototype.sum = function() { 
        var sum = 0; 
        for (var i = 0; i < this.length; i++) {
                  sum += this[i]; 
        }
        return sum; 
};              //此時數組對象中已經存在sum()方法了 可以用於數組.sum()進行數據的求和

  

4.繼承

  4.1 call()

    call()可以調用函數   可以修改this的指向,使用call()的時候,()里的第一個參數是修改的this指向,參數2 參數3 使用逗號隔開

    

function fn(x, y) {
     console.log(this); 
     console.log(x + y); 
} 
var o = { 
    name: 'andy' 
};
fn.call(o, 1, 2); //調用了函數此時的this指向了對象o, 

    4.2 構造函數繼承屬性   

    1. 先定義一個父構造函數
    2. 再定義一個子構造函數
    3. 子構造函數繼承父構造函數的屬性(使用call方法)
// 1. 父構造函數 
function Father(uname, age) { 
// this 指向父構造函數的對象實例
      this.uname = uname; 
      this.age = age; 
}
// 2 .子構造函數 
function Son(uname, age, score) {
     // this 指向子構造函數的對象實例 
//3.使用call方式實現子繼承父的屬性 
      Father.call(this, uname, age);
      this.score = score; 
}
var son = new Son('劉德華', 18, 100); 
console.log(son);

   

   4.3 借用原型對象繼承方法

    (1). 先定義一個父構造函數
    (2). 再定義一個子構造函數
    (3). 子構造函數繼承父構造函數的屬性(使用call方法)
// 1. 父構造函數 
function Father(uname, age) { 
// this 指向父構造函數的對象實例 
  this.uname = uname; 
  this.age = age; 
}
Father.prototype.money = function() { 
  console.log(100000); 
};
 //子構造函數
  function Son(uname, age , score){ 
     // this 指向子構造函數的對象實例
    Father.call(this, uname, age);
    this.score = score;
  };
// Son.prototype = Father.prototype; 這樣直接賦值會有問題,如果修改了子原型對 象,父原型對象也會跟着一起變化 
Son.prototype = new Father(); 
// 如果利用對象的形式修改了原型對象,別忘了利用constructor 指回原來的構造函數 
Son.prototype.constructor = Son; 
// 這個是子構造函數專門的方法 
Son.prototype.exam = function() { 
  console.log('孩子要考試'); 
}
var son = new Son('劉德華', 18, 100); 
console.log(son);

  

 5.ES5新增方法

  5.1 forEach()遍歷數組  沒有返回值

    array.forEach(function(value , index , array){....})

    value是每個數組元素

    index是每個元素索引值

    array是當前數組

  5.2 filter()篩選數組  其實也會遍歷數組

    array.filter(function(value , index , array){

      return  條件表達式

    })

    返回的是一個新數組  括號跟的參數都是一樣的

  5.3 some()   查找數組中是否有滿足條件的元素  其實也會遍歷數組

    array.some(function(value , index , array){

      return  條件表達式

    })

     返回值是布爾值,只要查找到第一個滿足條件的元素就會終止循環 效率高  

 

   5.4 trim方法去除字符串兩端的空格

    字符串的特性 不可變性

var str = ' hello ' ;
console.log(str.trim())  //hello 去除兩端空格 
var str1 = ' he l l o ' ;
console.log(str1.trim())  //he l l o 去除兩端空格

  5.5 獲取對象的屬性名 

    Object.keys(對象) 獲取到當前對象中的屬性名 ,返回值是一個數組

var obj = { 
    id: 1, 
    pname: '小米', 
    price: 1999,
    num: 2000 
};
var result = Object.keys(obj);
console.log(result)   //返回的新數組[id,pname,price,num]

  5.6 Object.defineProperty設置或修改對象中的屬性

Object.defineProperty(對象,修改或新增的屬性名,{ 
  value:修改或新增的屬性的值, 
  writable:true/false,  //如果值為false 不允許修改這個屬性值 
  enumerable: false,    //enumerable 如果值為false 則不允許遍歷 
  configurable: false   //configurable 如果為false 則不允許刪除這個屬性 屬性是否可以被刪除或是否可以再次修改特性 
})

 

 6.函數的定義和調用

  6.1 函數的定義方式

    (1) function關鍵字(命名函數)

      function fn(){......}

    (2) 函數表達式(匿名函數)

       var fn = function(){......}

    (3) 利用new function('參數1','參數2','函數體')

      var f = new Function('a','b','console.log(a +b)');   //參數都必須是字符串格式

      f( 3, 2);   //輸出結果是5 

  所有函數都是Function的實例,函數也屬於對象

  6.2 函數的調用方式

 1     // 1.普通函數
 2 function fn(){
 3     console.log('hello world')    
 4 }
 5 fn();
 6 
 7 // 2.對象的方法
 8 var o = {
 9     sayHi : function(){
10         console.log('hello world')
11      }
12 }
13 o.sayHi();
14 
15 // 3.構造函數
16 function Star(){};
17 new Star();
18 
19 
20 // 4.綁定函數事件
21 btn.onclick = function(){}  // 點擊了按鈕就可以調用這個函數
22 
23 // 5.定時器函數
24 setInterval(function(){},1000);  // 這個函數定時器自動一秒鍾調用一次
25 
26 //6.立即執行函數(自調用函數)
27 (function(){
28      console.log('hello world')
29 })();

 

7.this

  7.1 函數內部的this指向

    一般情況下,this指向函數的調用者

    

  7.2 改變函數內部this的指向

    1.call()方法

      可以直接調用函數,也可以改變this的指向     

      var o = {
        name: 'andy'
      }
      function fn(a, b) {
        console.log(this);
        console.log(a+b)
      };
      fn(1,2)            // 此時的this指向的是window 運行結果為3
      fn.call(o,1,2)  //此時的this指向的是對象o,參數使用逗號隔開,運行結果為3

        應用:經常做繼承

    2.apply()方法

      可以直接調用函數,也可以改變this的指向  

      var o = {
        name: 'andy'
      }
      function fn(a, b) {
        console.log(this);
        console.log(a+b)
      };
      fn()                      // 此時的this指向的是window 運行結果為3
      fn.apply(o,[1,2])  //此時的this指向的是對象o,參數使用數組傳遞 運行結果為3

       應用:跟數組有關

// 利用apply借助數學內置對象求最大值等
var arr = [1,56,55,4,33]; var max = Math.max.apply(Math,arr); console.log(max) // 輸出結果應該是56

    3.  bind()方法

       不會調用函數,但是可以改邊this的指向,返回的是一個新函數    

      var o = {
        name: 'andy';
      };
      function fn(a, b) {
        console.log(this);
        console.log(a + b);
      };
      var f = fn.bind(o, 1, 2);    //此處的f是bind返回的新函數
      f();                                   //調用新函數 this指向的是對象o 參數使用逗號隔開

      應用:不調用函數,但是還想改變this指向

     4. call、apply、bind三者區別

      call和apply都是直接調用函數,可以改變this的指向

         call后面跟的參數是列表形式,apply跟的是數組形式

      bind不會直接調用函數,也可以改變ths的指向

8. 嚴格模式(strict mode)

    ES5 的嚴格模式是采用具有限制性 JavaScript變體的一種方式,即在嚴格的條件下運行 JS 代碼。

    8.1 為腳本開啟嚴格模式


    (function (){
 //在當前的這個自調用函數中有開啟嚴格模式,當前函數之外還是普通模式 
         "use strict"; 
          var num = 10; 
          function fn() {
          } 
     })(); 
//或者 
<script> 
    "use strict";
      //當前script標簽開啟了嚴格模式 
</script>
<script> 
     //當前script標簽未開啟嚴格模式
 </script>   

    8.2 為函數開啟嚴格模式

      要給某個函數開啟嚴格模式,需要把“use strict”; (或 'use strict'; ) 聲明放在函數體所有語句之前。

function fn(){
     "use strict";
      return "123"; 
}
//當前fn函數開啟了嚴格模式

    8.3 嚴格模式中的變化

'use strict';
 num = 10 ;
console.log(num)//嚴格模式后使用未聲明的變量 
‐‐‐ ‐‐‐‐‐‐‐ ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
var num2 = 1;
delete num2;//嚴格模式不允許刪除變量 
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ‐‐‐‐‐‐‐ 
function fn() { 
    console.log(this); // 嚴格模式下全局作用域中函數中的 this 是 undefined 
}
fn();
 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ‐‐‐‐‐‐‐‐
 function Star() {
 this.sex = '男'; 
}
// Star();嚴格模式下,如果 構造函數不加new調用, this 指向的是undefined 如果給 他賦值則 會報錯. 
var ldh = new Star(); 
console.log(ldh.sex); 
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ ‐‐‐‐‐‐‐‐‐ 
setTimeout(function() { 
console.log(this); //嚴格模式下,定時器 this 還是指向 window
 }, 2000);

 

9.高階函數

  高階函數是對其他函數進行操作的函數,可以接收函數作為參數或者將函數作為返回值輸出。

  函數也是一種數據類型,同樣可以作為參數,傳遞給另外一個參數使用。最典型的就是作為回調函數。同理函數也可以作為返回值傳遞回來

10.閉包(clsure)

  閉包是指有權訪問另一個函數作用域中變量的函數。簡單理解就是一個作用域可以訪問另外一個函數內部的局部變量。

  作用: 延伸變量的作用范圍,增長了變量的生命周期。

  弊端: 造成內存泄漏。(內存泄漏,就是本該銷毀的數據沒有銷毀,造成內存浪費)

  閉包里的經典面試題:

    1. 遍歷注冊li的點擊事件,利用閉包的實行,點擊每一個li輸出對應的索引

for (var i = 0; i < lis.length; i++) { 
// 利用for循環創建了4個立即執行函數 
// 立即執行函數也成為小閉包因為立即執行函數里面的任何一個函數都可以使用它的i這個變量        
  (function(i) {
    lis[i].onclick = function() {        console.log(i);      }   })(i);
}

    2. 遍歷 1 ~ 5 5個數值,利用閉包,在setTimeout里面輸入 1 ~ 5

  

for (var i = 0; i < 5; i++) {
     (function(i) { 
         setTimeout(function() { 
        console.log(lis[i].innerHTML);
     }, 3000)
   })(i);
}

  

11.遞歸

   如果一個函數在內部可以調用其本身,那么這個函數就是遞歸函數。

   簡單理解:函數內部自己調用自己, 這個函數就是遞歸函數

  遞歸函數的作用和循環效果一樣,很容易發生‘棧溢出’錯誤(stack overflow),所以必須要加退出條件return。

  1. 利用遞歸求1~n的階乘

function fn(n){
    if (n == 1) {
         return 1;
    }
    return n * fn(n -1)
}
console.log(fn(3))

   2.  利用遞歸求斐波那契數列(斐波那契數列就是前兩個數的和等於第三個數字,也叫兔子數)

// 利用遞歸函數求斐波那契數列(兔子序列) 1、1、2、3、5、8、13、21... 
// 用戶輸入一個數字 n 就可以求出 這個數字對應的兔子序列值
// 我們只需要知道用戶輸入的n 的前面兩項(n‐1 n‐2)就可以計算出n 對應的序列值

function fb(n){
    if ( n== 2 || n== 1) {
         return 1;
    }
    return fb( n -1) + fb (n-2);
} 
console.log(fb(3))

  

12.正則表達式

  12.1 什么是正則表達式?

    正則表達式(Regular Expression)是用於匹配字符串中字符組合的模式。在JavaScript中,正則表達式也是對象

  12.2 作用

     1.匹配  2. 替換 3.提取

13.正則表達式在js中的使用

  1. 正則表達式的創建 

    方式一 :通過調用RegExp對象的構造函數創建

      var reg = new RegExp(/123/);

      console.log(reg);    // 輸出結果 /123/

    方式二 : 利用字面量創建

      var rg = /123/;

  2. 測試正則表達式

    test()正則對象方法,用於檢測字符串是否符合規則,該方法返回值是布爾類型,true或false,括號里的參數是字符串

    var rg = /123/;

    console.log(rg.test(123)); // 返回值是true

    console.log(rg.test('abc')); // 返回值是false

  3. 正則表達式中的特殊字符

    3.1邊界符(正則表達式中的邊界符(位置符)用來提示字符所處的位置)

      

      如果 ^和 $ 在一起,表示必須是精確匹配

    3.2 字符類 

        [ ]方括號,表示有一系列字符可供選擇,只要匹配到其中一個就可以了

         

         

    3.3 量詞符

      量詞符用來設定某個模式出現的次數,都是寫在某個模式的后面

      

      逗號前后都不能有空格

 

    3.4 括號總結

      大括號{}  量詞符,里面表示重復次數

      中括號[]  字符集合,匹配中括號里面的任意字符

      小括號() 表示優先級

    3.5 預定義類

      指的是某些常見模式的簡寫方式

      

    3.6 正則替換和正則提取

      replace()方法可以實現替換字符串操作,用來替換的參數可以是一個字符串,也可以是一個正則表達式

      注意:replace()是字符串的方法,並不是正則對象的方法

      語法: obj.replace(參數1,參數2)

       obj是要選擇的目標字符串,參數1是要被替換的字符串,可以是字符串也可以是正則表達式,參數2是替換之后的字符串

var str = "abcd";
// 把字符串里的a替換成 哈哈
var str1 = str.replace("a","哈哈");
var str2 = str.replace(/a/,"哈哈");
console.log(str1);   //輸出 哈哈bcd  
console.log(str2);   //輸出 哈哈bcd

      match()可以進行提取,返回值是一個數組

      語法 : 字符串.match(正則表達式)

      str.match(/\w+@\w+\.\w+/g)  可以提取字符串里的所有郵箱

 

     3.7 正則表達式參數

        后面可以跟三種形式的參數

        /表達式/g     表示全局匹配,就是可以將所有的某個字符都進行轉換

        /表達式i/    忽略大小寫

        /表達式/gi  全局匹配+忽略大小寫

        

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM