Javascript高級程序語言設計知識點(干貨)

1. 背景

1.1. Javascript 誕生于1995年

1.2. Javascript 實現(xiàn)組成部分

- ECMAScript  提供語言核心功能
- BOM 提供與瀏覽器交互的方法和接口
- DOM 提供訪問和操作網(wǎng)頁內容的方法與接口

2. html中的引用

  • 內部引用
<head>
  <script>
      function () {
        alert('begin')
      }
  </script>
</head>
  • 外部引用
<script type="text/javascript" src="外部js鏈接"></script>

3. Javascript 基本概念

3.1 語法

image.png

3.2 關鍵字和保留字

image.png

3.3 變量 (松散型 - 可保存任何類型的數(shù)據(jù))

var 定義變量僅為該變量作用域下的內部變量

function test () {
  var message = ''
}
alert (message) // 報錯

3.4 數(shù)據(jù)類型

3.4.1 typeof檢驗數(shù)據(jù)類型

image.png

3.4.2 Boolean() 轉換

image.png

3.4.3 Number() 轉換

image.png

3.4.4 parseInt() 轉換

image.png

3.4.5 Object類型

image.png

3.4.6 其他知識點

  • 保存浮點數(shù)值所需的內容是保存整數(shù)的兩倍
alert(undefined == null) // true
alert(NaN == NaN) // false

3.5 操作符

3.5.1 一元操作符

  • ++ / -- (前置后置)
var a = 1
var b = 2 
var c = ++a - 2 // 0
var d = a - 2  // 0

var a = 1
var b = 2 
var c = a++ - 2 // -1
var d = a - 2  // 0
    • (轉數(shù)字) / - (負數(shù))
var a = ‘a’
var c = +a // NaN
var d = '6'
var e = +d // 6
var f = -e // -6

3.5.2 位操作符

image.png

3.5.3 布爾操作符

image.png
var found = true
var result = found && a // false , a未定義
var result1 = found ||| a // true

3.5.4 乘性操作符

Infinity * 0 = NaN
Infinity * Infinity = Infinity
0 / 0 = NaN

3.5.5 加性操作符

var a = "5" + "5" // 55
var b = 1
var c = 2
var d = 'hahah' + c + b // hahah21
var e = 'haha' + (c + b) // haha3
var f = 5- null // 5
var g = 5 - true // 4

3.5.6 關系操作符

  • 字符串按照字母編碼比較
var a = "51" < "5" // true

var b = NaN > 3 // false
var c = NaN <= 3 // false

3.5.7 相等操作符

null ==  undefined // true
"NaN" == NaN // false
NaN == NaN // false
null == 0 // false
undefined == 0 // false

3.5.8 條件操作符

var a = b ? c : d

3.6 語句

if , do-while , while , for , for-in , break , continue , label , switch

for (var i = 0; i < 10; i++) {
  alert(i) // 0-9
}
alert(i) // 10

var num = 0
add: 
  for (var i = 0; i < 10; i++) {
    for (var j = 0; j < 10; j++) {
      if (i == 5 && j == 5) {
        break add
      }
      num++
    }
  }
alert(num) // 55

var num = 0
add: 
  for (var i = 0; i < 10; i++) {
    for (var j = 0; j < 10; j++) {
      if (i == 5 && j == 5) {
        continue add
      }
      num++
    }
  }
alert(num) // 95

3.7 函數(shù)

3.7.1 參數(shù) arguments

function howManyArgs () {
  alert (arguments.length) // 2
}
howManyArgs ("1", "2")
  • 函數(shù)沒有簽名,無法重載
  • 未指定返回值的函數(shù)返回的是undefined

4. 變量,作用域和內存

4.1 基本類型和引用類型的值

4.1.1 變量

  • 基本類型值
var name = 'haha'
name.age = 27
alert(name.age) // undefined
  • 引用類型值
var person = new Object()
person.age = 27
alert(person.age) // 27

4.1.2 復制變量

  • 基本類型,互不影響
var num1 = 1
var num2 = num1 = 1
  • 引用類型,兩個變量引用同一個對象,會改變
var obj1 = new Object()
var obj2 = obj1
obj2.name = 'haha'
alert(obj1.name) // haha 被賦值了

4.1.2 傳遞參數(shù)

  • 基本類型 - 保存在棧內存
function addTen (num) {
  num += 10
  return num
}
var count = 20
var res = addTen(count)
alert(count) // 20
alert(res) // 30
  • 引用類型 - 保存在堆內存
function setName(obj) {
  obj.name = 'hah'
  obj = new Object ()  // 局部對象在函數(shù)執(zhí)行完后摧毀
  obj.name = 'hello'
}
var person = new Object()
setName(person)
alert(person.name) // hah

4.1.3 檢測類型

  • typeof 基本類型檢測(見上)
  • instanceOf 引用類型檢測
alert(colors instanceOf Array/Object/RegExp)

4.2 執(zhí)行環(huán)境和作用域

4.2.1 當代碼在一個環(huán)境執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈

  • 作用域鏈:保證對執(zhí)行環(huán)境有權訪問的所有變量和函數(shù)的有序訪問

4.2.2沒有塊級作用域

a. if語句中的變量聲明會將變量添加到當前的執(zhí)行環(huán)境

if (true) {
  var color = 'blue'
}
alert(color) // true

b. for 語句創(chuàng)建的變量即使在for循環(huán)執(zhí)行環(huán)境結束后,也依舊會存在于循環(huán)外部的執(zhí)行環(huán)境中

for (var i = 0; i < 10; i++) {
  alert(i) 
}
alert(i) // 10

4.2.3 局部變量只在函數(shù)執(zhí)行時存在

4.2.4 垃圾收集

  • 標記清除 - JavaScript中最常用的垃圾收集方式
  • 解除引用 - 一旦數(shù)據(jù)不再有用,可通過將其值設置為null來釋放其引用

5. 引用類型

構造函數(shù): 創(chuàng)建新對象定義

5.1 Object類型

5.1.1 創(chuàng)建實例

  • new
var person = new Object ()
person.name = 'hahah'
  • 字面量方式
var person = {
  name: 'hahah'
}

5.1.2 讀屬性

alert (person.name)
alert (person['name']) // 可為變量

5.2 Array類型

5.2.1 lenth屬性

  • 數(shù)組的length屬性設置,可以從數(shù)組的末尾移除項或向數(shù)組中添加新項
var colors = ['blue','red']
colors.length = 1
alert(colors[1]) // undefined

5.2.2 檢驗數(shù)組

  • Array.isArray()

5.2.3 屬性

image.png
var person1 = {
  toLocaleString: function () {
    return 'John'
  },
  toString: function () {
    return 'Lily'
  }
}
alert(person1) // Lily
alert(person1.toString) // Lily
alert(person1.toLocaleString) // John

5.2.4 棧方法 (后進后出)

  • push() 接受參數(shù),設置尾部,并修改長度
  • pop() 從數(shù)組末尾移除最后一項,并返回
var colors = ['red', 'blue']
colors.push('black')
alert(colors.length) // 3
var item = colors.pop()
alert(item) // 'black'

5.2.5 隊列方法 (后進先出)

  • shift() 移除數(shù)組中的第一項并返回該項
  • unshift() 添加任意值,并返回長度
var colors = ['red', 'blue']
var item = colors.shift()
alert(colors.length) // 1
alert(item) // 'red'

5.2.6 重排序

  • reverse() 反轉
  • sort() 升序排列
    先調用toString(), 比較排序

5.2.7 操作方法

  • concat() 復制當前數(shù)組,有參數(shù)的話,將參數(shù)添加到結果數(shù)組
var size = [1, 2, 3]
var newSize = size.concat([4, 5])
alert(newSize) // 1, 2, 3, 4, 5
  • slice() 截取
var size = [1, 2, 3]
var size1 = size.slice(1) // [2, 3] 一個參數(shù),從參數(shù)位置到末尾均返回
var size2 = size.slice(1, 2) // [2] 兩個參數(shù),從參數(shù)1位置到參數(shù)2位置之間的數(shù),但不包含位置2
  • splice()
    刪除 - 2個參數(shù),第一個參數(shù)為第一項的位置,第2個參數(shù)代表要刪除的項數(shù)
    插入 - 3個參數(shù),起始位置,要刪除的項數(shù),需要插入的項
    替換
var size = [1, 2, 3]
var size1 = size.splice(0, 1) // 1
var size2 = size.splice(1, 0 ,4, 5) // 2, 4, 5, 3

5.2.8 位置方式

  • iindexOf() 頭查
  • lastIndexOf() 尾查
var numbers = [1,2,3,4,5,6]
alert(numbers.indexOf(4)) // 5

5.2.9 迭代方式

  • every 所有的為true, 則為true
var nums = [1,3,4,5]
var everyRes = nums.every(function (item, index, array) {
  return (item > 2)
})
everyRes() // false
  • filter 過濾符合條件的數(shù)據(jù)
var nums = [1, 2, 3, 4]
var everyRes = nums.filter(function (item, index, array) {
  return (item > 2)
})
everyRes() // [3, 4]
  • forEach 遍歷執(zhí)行
  • map 遍歷數(shù)據(jù)
var nums = [1, 2, 3, 4]
var everyRes = nums.map(function (item, index, array) {
  return (item * 2)
})
everyRes() // [2, 4, 6, 8]

5.2.10 歸并方式

  • reduce 小到大
  • reduceRight 大到小
var nums = [1, 2, 3, 4]
var everyRes = nums.reduce(function (prev, cur, index,array) {
  return (prev + cur)
})
everyRes() // 10

5.3 Date類型

var start = new Date() // 時間戳
var start1 = Date.now() // 方法前
...function
var stop1 = Date.now() // 方法后
var res = stop1 - start1 // 執(zhí)行時間

5.4 RegExp類型 - 正則表達式

可見文章http://www.itdecent.cn/p/7a63f40e8f41

5.5 Function類型

5.5.1 函數(shù)聲明

  • 函數(shù)聲明提升
alert(num(10, 10)) // 20
funtion sum (sum1, sum2) {
  return sum1 + sum2
}
  • 函數(shù)表達式必須等到解析器執(zhí)行到它所在的代碼行
alert(num(10, 10)) // 報錯
var sum = sum (sum1, sum2) {
  return sum1 + sum2
}

5.5.2 內部屬性(arguments、this)

  • callee屬性 - 是一個指針 - 指向擁有arguments對象
function test (num) {
  if (num <= 1) {
    return 1
  } else {
    return num * test(num - 1) 等價于 return num * arguments.callee(num - 1)
  }
}

好處: 運用callee與函數(shù)名無關,均可以調用

var realTest = test()
function test (num) {
 return 0
}
realTest (5) // 120
test (0)
  • this 引用的是函數(shù)執(zhí)行的環(huán)境對象(當全局調用時,是windows對象)
window.color = 'red'
var obj = {
  color: 'blue'
}
func0tion sayColor () {
     alert(this.color)
}
sayColor() // red
obj.sayColor = sayColor
obj.sayColor() // blue
  • caller 屬性保存著調用當前函數(shù)的函數(shù)的引用
func0tion outer() {
     inner()
}
func0tion inner() {
     alert(inner.caller) // outer()
}
outer()

inner.caller == auguments.callee.caller

5.5.3 屬性和方法

  • prototype 不可枚舉,無法for in 發(fā)現(xiàn)
  • apply / call 設置函數(shù)體內this對象的值, apply(在其中運行函數(shù)的作用域, 參數(shù)數(shù)組),call(在其中運行函數(shù)的作用域, {...傳遞參數(shù) 或者arguments})

    用武之地: 可以擴充函數(shù)賴以運行的作用域

window.color = 'red'
var obj = {
  color: 'blue'
}
func0tion sayColor () {
     alert(this.color)
}
sayColor() // red
sayColor.call(this) // red
sayColor().call(window) // red
sayColor().call(obj) // blue
  • bind() 這個方法會創(chuàng)建一個函數(shù)的實例,其this值會被綁定到傳給bind()函數(shù)
window.color = 'red'
var obj = {
  color: 'blue'
}
func0tion sayColor () {
     alert(this.color)
}
sayColor() // red
sayColor.bind(obj) // blue

5.6 基本包裝類型 Boolean , Number , String

image.png

5.6.1 使用new調用基本包裝類型的構造函數(shù)

var value = '25'
var obj = new Number(value)
alert(typeof obj) // object
alert(obj.instanceOf (String)) // true 

5.6.2 String類型

image.png

5.7 單體內置對象

不依賴宿主環(huán)境的對象,程序執(zhí)行前就已存在

5.7.1 global

不屬于任何其他對象的屬性和函數(shù),都是全局global對象所有

var global = function () {
  return this
} // 全局對象

5.7.2 Math對象

image.png

6. 面向對象的程序設計

6.1 理解對象

6.1.1 屬性類型 - 為了實現(xiàn)JavaScript引擎用的,不能直接訪問

  • 數(shù)據(jù)屬性 - 包含數(shù)據(jù)值


    image.png

Object,defineProperty()可修改屬性的特性

var person = {}
Object.defineProperty(person, "name", {
  writable: false, // 不可修改
  value: 'hahah'
})
person.name = 'lalala'
person.name // hahah'

一旦改變特性,無法更改

Object.defineProperty(person, "name", {
  writable: true, // 報錯
})
  • 訪問器屬性 - 不包含數(shù)據(jù)值,有getter和setter


    image.png

只有getter不能寫入,只有setter無法讀取

var book = {
  _year: 2021, // _表示只能通過對象方法訪問的屬性
  edition: 1
}
Object.defineProperty(book, "year", {
  get: function () {
    return this.year  
  },
  set: function (value) {
    if (value > 2021) {
       this.year = value
       editiion += value - 2021
    }
  }
})
book.year = 2022
alert(book.edition) // 2

6.1.2 定義多個屬性 - Object.defineProperties()

var book = {}
Object.defineProperty(book, {
  _year: {
    writable: false, // 不可修改
    value: 'hahah'
  },
  year: {
    get: function () {
      return this.year  
    },
    set: function (value) {
      if (value > 2021) {
         this.year = value
         editiion += value - 2021
      }
    }
  } 
})
book.year = 2022
alert(book.edition) // 2

6.1.3 讀取屬性的特性 - Object.getOwnPropertyDescriptor()

var descriptor = Object.getOwnPropertyDescriptor(book, 'year')
alert(descriptor.value) // hahah

6.2 創(chuàng)建對象

6.2.1 工廠模式

為了解決使用同一個接口創(chuàng)建許多對象,會產生大量的代碼,抽象創(chuàng)建具體對象的過程如下

function createPerson (name, age, job) {
  var o = new Object()
  o.name = name
  o.age = age
  o.job = job
  o.sayName = function () {
    alert(this.name)
  }
  return o
}
ver person1 = createPerson ('haha', 17, 'nurse')
ver person2 = createPerson ('hello', 17, 'doctor')

無法解決對象識別問題,引出構造函數(shù)模式

6.2.2 構造函數(shù)模式

  • 構造函數(shù)本身也是函數(shù),用來創(chuàng)建對象
function createPerson (name, age, job) {
  this.name = name
  this.age = age
  this.job = job
  this.sayName = function () {
    alert(this.name)
  }
}
ver person1 = new createPerson ('haha', 17, 'nurse')
ver person2 = new createPerson ('hello', 17, 'doctor')
  • 構造new操作符步驟
    ① 創(chuàng)建一個新對象
    ② 將構造函數(shù)的作用域賦值給新對象,this指向新對象
    ③ 執(zhí)行構造函數(shù)中的代碼(為這個對象添加屬性和方法
    ④ 返回新對象
alert(person1.constructor == createPerson) // true
  • 構造函數(shù)勝過工廠函數(shù)
    創(chuàng)建自定義的構造函數(shù)意味著將來可以將它的實例標識為一種特定的類型
  • 構造函數(shù)與其他函數(shù)的區(qū)別
    調用方式不同(new)
  • 構造函數(shù)的問題
    每個方法都要在每一個實例上重新創(chuàng)建一遍(Funtion實例)
  • 不同實例上的同名函數(shù)是不相等的
person1.sayName == person2.sayName  // false

6.2.3 原型模式

  • prototype 屬性是一個指針,指向一個對象,好處是:可以讓所有對象實例共享它所包含的屬性和方法
function person () {}
person.prototype.name = 'ha'
person.prototype.sayName = function () {
  alert(this.name)
}
ver person1 = new person ()
ver person2 = new person ()
person1.sayName == person2.sayName  // true
person1.sayName = person2.sayName  // ha

person.prototype.isPrototypeOf(person1) // true
Object.getPrototypeOf(person1)
image.png

image.png
  • 可以通過對象實例訪問保存原型的值,但是無法通過實例修改原型的值
function person () {}
person.prototype.name = 'ha'
ver person1 = new person ()
ver person2 = new person ()
person1.name = 'hello'
person2.name // ha
person1.name // hello
  • 使用delete符可以完全刪除實例屬性
delete person1.name
person1.name = 'ha'
  • hasOwnProperty(Object繼承) 在實例 - 相反 - hasPropertypeProperty() 在原型
    判斷屬性存在原型中還是實例中
person1.name = 'hello'
person1.hasOwnProperty('name') // true
person2.hasPropertypeProperty('name') // false
  • 原型與In操作符
"name" in person1 // true
"name" in person2 // true
  • Object.keys 取屬性集合
var keys = Object.keys(person, Prototype)
keys['name']
  • 原型語法更簡潔話(對象字面量)
person.prototype = {
  name: 'hello'
}
// 以上方法,constructor指向了Object對象。而非person
var person1 = new person()
person1.instantceOf(Object) // true
person1.instantceOf(person) // true
person1.constructor(Object) // true
person1.constructor(person) // false

可以通過設置constructor改回指向

person.prototype = {
  constructor: person,
  name: 'hello'
}

但更改后,無法通過for in 讀取,[[Enumerable]] = true

  • 動態(tài)性
    重寫原型對象切斷了現(xiàn)有原型與任何之前已經存在的對象實例之間的聯(lián)系,他們引用的仍是最初的原型
function person () {}
var person1 = new person()
person.prototype = { // 新的person.newProperty
  constructor: person,
  name: 'hello'
}
person1.name // error 讀取的是person的property
  • 原生對象的原型
    ① 通過原生對象的原型, 不僅可以取得所有默認方法的調用,也可以定義新方法
alert(typeof Array.prototype.sort) // true
String.propotype.startWith = function (text) {
  return this.indexOf(text) == 0
}
var msg = 'Hello'
alert(msg.startWith('Hello')) // true

② 構造函數(shù)可以與原型模式組合
③ 構造函數(shù)模式用于定義實例屬性,而原型模式用于定義方法和共享的屬性

6.2.4 穩(wěn)妥的構造函數(shù)

指的是沒有公共屬性,而且其方法也不引用this對象

function createPerson (name, age, job) {
  var o = new Object()
  o.sayName = function () {
    alert(name)
  }
}
ver person1 = new createPerson ('haha', 17, 'nurse')
person1.sayName // haha , 唯一輸出name的調用

6.3 繼承

image.png

6.3.1 原型鏈

基本思想:利用原型讓一個引用類型繼承另一個引用類型的屬性和方法

function SuperType () {
  this.property = true
}
SuperType.prototype.getSuperValue = function () {
  return this.property
}
function SubType () {
  this.subProperty = false
}
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function () {
  return this.subProperty
}
var instance = new SubType()
alert(instance.getSuperValue()) // true

SubType繼承了SuperType,原來存在于SuperType的實例中的所有屬性和方法,現(xiàn)在也存在于SubType的prototype中

  • instance.constructor 已經變成了SuperType
  • 所有的函數(shù)默認都是Object實例
    instance -> SubType -> SuperType -> Object
  • 通過原型鏈繼承時,不能使用字面量創(chuàng)建原型方法,否則會重寫原型鏈
  • 原型鏈繼承有個問題:通過原型鏈繼承時,原先的實例屬性會變成現(xiàn)在的原型屬性(引用類型值)

6.3.2 借用構造函數(shù)

基本思想: 在子類型構造函數(shù)的內部調用超類型構造函數(shù)

function SuperType () {
  this.colors = ['red','blue','black']
}
function SubType () {
  SuperType.call(this)
}
var instantce1 = new SubType()
instantce1.colors.push('green')
var instantce2 = new SubType()
instantce2.colors // ['red','blue','black']
instantce1.colors // ['red','blue','black','green']
  • 可傳遞參數(shù)
function SuperType (color) {
  this.colors = color
}
function SubType () {
  SuperType.call(this, 'black')
  this.age = 17
}
var instantce1 = new SubType()
instantce1.colors // black
instantce1.age // 17
  • 問題:方法都在構造函數(shù)中,無法復用

6.3.3 組合繼承(原型鏈+借用構造函數(shù))

function SuperType (name) {
  this.name = name
  this.colors = ['blue', 'red']
}
SuperType.prototype.sayName = function () {
  return this.name
}
function SubType () {
  this.subProperty = false
}
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function () {
  return this.subProperty
}
var instance = new SubType()
alert(instance.getSuperValue()) // true

6.3.4 原型式繼承:一個對象作為另一個對象的基礎

function object (o) {
  function F () {}
  F.prototype = o
  return new F()
}
var person = {
  friends: ["Lily']
}
var person1 = object(person)
var person2 = object(person)
person1.friends.push('Bob')
person2.friends.push('Amy')
alert(person.friends) // ["Lily","Bob","Amy"]

6.3.5 寄生式函數(shù)

基本思想:創(chuàng)建一個僅用于封裝繼承過程的函數(shù),該函數(shù)在內部以某種方式來增強對象,最后再像是真地是它做了所有工作一樣返回對象

function createAnother(original){
  var clone = object(original) // 通過調用函數(shù)來創(chuàng)建一個新對象
  clone.sayHi = function () {  // 強化對象
    alert('hi')
  }
  return clone // 返回對象
}
var person = {
  name: 'hi',
  friends: ['lily', 'lucy']
}
var anotherPerson = new createAnother(person )
anotherPerson.sayHi() // hi
  • 使用寄生式函數(shù)繼承時,會由于不能做到函數(shù)復用而降低效率,這一點與構造函數(shù)模式類似

6.3.6 寄生組合式繼承,js最常用的繼承模式

基本思想:通過借用構造函數(shù)來繼承屬性,通過原型鏈的混成形式來繼承方法

function inheritPrototype (SubType, SuperType) {
  var prototype = object(SuperType.prototype) // 創(chuàng)建對象
  prototype.constructor = SubType // 增加對象
  SubType.constructor = prototype // 指定對象
}
function SuperType (name) {
  this.name = name
  this.colors = ['blue', 'red']
}
SuperType.prototype.sayName = function () {
  return this.name
}
function SubType (name, age) {
  SuperType.call(this, name) // 第二次調用SuperType
  this.age = age
}
inheritPrototype(SubType,SuperType)
SubType.prototype.sayAge = function () {
  alert(this.age)
}

7. 函數(shù)表達式

function關鍵字后面沒有跟標識符,稱為匿名函數(shù)

7.1 遞歸

一個函數(shù)通過名字調用自身

function test (num) {
  if (num <= 1) {
    return 1
  } else {
    return num * test(num - 1) 等價于 return num * arguments.callee(num - 1) 
  }
}

arguments.callee嚴格模式無法訪問,可以使用命名函數(shù)來達成相同效果

7.2 閉包

  • 指有權訪問另一個函數(shù)作用域中的變量的函數(shù)

7.2.1 閉包與變量

閉包所保存的是整個變量對象,而不是某個特殊的變量

function createFunction () {
  var result = new Array()
  for (var i = 0; i < 10; i++) {
    result[i] = function () {
      return i
    }
  }
  return result 
}

實際上,每個函數(shù)都返回10,因為每個函數(shù)的作用域鏈都保存著
function createFunction函數(shù)的活動對象,所以他們引用的是一個變量,當函數(shù)返回后,i的變量均為10,可通過創(chuàng)建另一個匿名函數(shù)強制讓閉包的行為符合預期。

function createFunction () {
  var result = new Array()
  for (var i = 0; i < 10; i++) {
    result[i] = function (num) {
      return function () {
        return num
      }
    }(i)
  }
  return result 
}

函數(shù)是按值傳參,i賦值給num,匿名函數(shù)會存在num的副本,因為會返回不同i值

7.2.2 關于this對象

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容