淺談JS包裝對象

定義

對象是 JavaScript 語言最主要的數據類型,三種原始類型的值——數值、字符串、布爾值——在必定條件下,也會自動轉爲對象,也就是原始類型的「包裝對象」(wrapper)。javascript

所謂「包裝對象」,指的是與數值、字符串、布爾值分別相對應的NumberStringBoolean三個原生對象。這三個原生對象能夠把原始類型的值變成(包裝成)對象。java

var v1 = new Number(123);
var v2 = new String('abc');
var v3 = new Boolean(true);

typeof v1 // "object"
typeof v2 // "object"
typeof v3 // "object"

v1 === 123 // false
v2 === 'abc' // false
v3 === true // false

上面代碼中,基於原始類型的值,生成了三個對應的包裝對象。能夠看到,v1v2v3都是對象,且與對應的簡單類型值不相等。正則表達式

包裝對象的設計目的,首先是使得「對象」這種類型能夠覆蓋 JavaScript 全部的值,整門語言有一個通用的數據模型,其次是使得原始類型的值也有辦法調用本身的方法。數組

NumberStringBoolean這三個原生對象,若是不做爲構造函數調用(即調用時不加new),而是做爲普通函數調用,經常用於將任意類型的值轉爲數值、字符串和布爾值。app

// 字符串轉爲數值
Number('123') // 123

// 數值轉爲字符串
String(123) // "123"

// 數值轉爲布爾值
Boolean(123) // true

總結一下,這三個對象做爲構造函數使用(帶有new)時,能夠將原始類型的值轉爲對象;做爲普通函數使用時(不帶有new),能夠將任意類型的值,轉爲原始類型的值。函數

實例方法

三種包裝對象各自提供了許多實例方法。這裏介紹兩種它們共同具備、從Object對象繼承的方法:valueOf()toString()工具

valueOf()

valueOf()方法返回包裝對象實例對應的原始類型的值。this

new Number(123).valueOf()  // 123
new String('abc').valueOf() // "abc"
new Boolean(true).valueOf() // true
toString()

toString()方法返回對應的字符串形式。編碼

new Number(123).toString() // "123"
new String('abc').toString() // "abc"
new Boolean(true).toString() // "true"

原始類型與實例對象的自動轉換

某些場合,原始類型的值會自動看成包裝對象調用,即調用包裝對象的屬性和方法。這時,JavaScript 引擎會自動將原始類型的值轉爲包裝對象實例,並在使用後馬上銷燬實例。prototype

好比,字符串能夠調用length屬性,返回字符串的長度。

'abc'.length // 3

上面代碼中,abc是一個字符串,自己不是對象,不能調用length屬性。JavaScript 引擎自動將其轉爲包裝對象,在這個對象上調用length屬性。調用結束後,這個臨時對象就會被銷燬。這就叫原始類型與實例對象的自動轉換。

var str = 'abc';
str.length // 3

// 等同於
var strObj = new String(str)
// String {
//   0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"
// }
strObj.length // 3

自動轉換生成的包裝對象是隻讀的,沒法修改。因此,字符串沒法添加新屬性。

var s = 'Hello World';
s.x = 123;
s.x // undefined

上面代碼爲字符串s添加了一個x屬性,結果無效,老是返回undefined

另外一方面,調用結束後,包裝對象實例會自動銷燬。這意味着,下一次調用字符串的屬性時,實際是調用一個新生成的對象,而不是上一次調用時生成的那個對象,因此取不到賦值在上一個對象的屬性。若是要爲字符串添加屬性,只有在它的原型對象String.prototype上定義。

自定義方法

除了原生的實例方法,包裝對象還能夠自定義方法和屬性,供原始類型的值直接調用。

好比,咱們能夠新增一個double方法,使得字符串和數字翻倍。

String.prototype.double = function () {
  return this.valueOf() + this.valueOf();
};

'abc'.double() // abcabc

Number.prototype.double = function () {
  return this.valueOf() + this.valueOf();
};

(123).double() // 246

上面代碼在StringNumber這兩個對象的原型上面,分別自定義了一個方法,從而能夠在全部實例對象上調用。注意,最後的123外面必需要加上圓括號,不然後面的點運算符(.)會被解釋成小數點。

Boolean 對象

概述

Boolean對象是 JavaScript 的三個包裝對象之一。做爲構造函數,它主要用於生成布爾值的包裝對象實例。

var b = new Boolean(true);

typeof b // "object"
b.valueOf() // true

上面代碼的變量b是一個Boolean對象的實例,它的類型是對象,值爲布爾值true

注意,false對應的包裝對象實例,布爾運算結果也是true

if (new Boolean(false)) {
  console.log('true');
} // true

if (new Boolean(false).valueOf()) {
  console.log('true');
} // 無輸出

上面代碼的第一個例子之因此獲得true,是由於false對應的包裝對象實例是一個對象,進行邏輯運算時,被自動轉化成布爾值true(由於全部對象對應的布爾值都是true)。而實例的valueOf方法,則返回實例對應的原始值,本例爲false

Boolean 函數的類型轉換做用

Boolean對象除了能夠做爲構造函數,還能夠單獨使用,將任意值轉爲布爾值。這時Boolean就是一個單純的工具方法。

Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean('') // false
Boolean(NaN) // false

Boolean(1) // true
Boolean('false') // true
Boolean([]) // true
Boolean({}) // true
Boolean(function () {}) // true
Boolean(/foo/) // true

上面代碼中幾種獲得true的狀況,都值得認真記住。

順便提一下,使用雙重的否運算符(!)也能夠將任意值轉爲對應的布爾值。

!!undefined // false
!!null // false
!!0 // false
!!'' // false
!!NaN // false

!!1 // true
!!'false' // true
!![] // true
!!{} // true
!!function(){} // true
!!/foo/ // true

最後,對於一些特殊值,Boolean對象前面加不加new,會獲得徹底相反的結果,必須當心。

if (Boolean(false)) {
  console.log('true');
} // 無輸出

if (new Boolean(false)) {
  console.log('true');
} // true

if (Boolean(null)) {
  console.log('true');
} // 無輸出

if (new Boolean(null)) {
  console.log('true');
} // true

Number 對象

概述

Number對象是數值對應的包裝對象,能夠做爲構造函數使用,也能夠做爲工具函數使用。

做爲構造函數時,它用於生成值爲數值的對象。

var n = new Number(1);
typeof n // "object"

上面代碼中,Number對象做爲構造函數使用,返回一個值爲1的對象。

做爲工具函數時,它能夠將任何類型的值轉爲數值。

Number(true) // 1

靜態屬性

Number對象擁有如下一些靜態屬性(即直接定義在Number對象上的屬性,而不是定義在實例上的屬性)。

  • Number.POSITIVE_INFINITY:正的無限,指向Infinity
  • Number.NEGATIVE_INFINITY:負的無限,指向-Infinity
  • Number.NaN:表示非數值,指向NaN
  • Number.MIN_VALUE:表示最小的正數(即最接近0的正數,在64位浮點數體系中爲5e-324),相應的,最接近0的負數爲-Number.MIN_VALUE
  • Number.MAX_SAFE_INTEGER:表示可以精確表示的最大整數,即9007199254740991
  • Number.MIN_SAFE_INTEGER:表示可以精確表示的最小整數,即-9007199254740991
Number.POSITIVE_INFINITY // Infinity
Number.NEGATIVE_INFINITY // -Infinity
Number.NaN // NaN

Number.MAX_VALUE // 1.7976931348623157e+308
Number.MAX_VALUE < Infinity // true

Number.MIN_VALUE // 5e-324
Number.MIN_VALUE > 0 // true

Number.MAX_SAFE_INTEGER // 9007199254740991
Number.MIN_SAFE_INTEGER // -9007199254740991

實例方法

Number對象有4個實例方法,都跟將數值轉換成指定格式有關。

Number.prototype.toString()

Number對象部署了本身的toString方法,用來將一個數值轉爲字符串形式。

(10).toString() // "10"

toString方法能夠接受一個參數,表示輸出的進制。若是省略這個參數,默認將數值先轉爲十進制,再輸出字符串;不然,就根據參數指定的進制,將一個數字轉化成某個進制的字符串。

(10).toString(2) // "1010"
(10).toString(8) // "12"
(10).toString(16) // "a"

上面代碼中,10必定要放在括號裏,這樣代表後面的點表示調用對象屬性。若是不加括號,這個點會被 JavaScript 引擎解釋成小數點,從而報錯。

10.toString(2)
// SyntaxError: Unexpected token ILLEGAL

只要可以讓 JavaScript 引擎不混淆小數點和對象的點運算符,各類寫法都能用。除了爲10加上括號,還能夠在10後面加兩個點,JavaScript 會把第一個點理解成小數點(即10.0),把第二個點理解成調用對象屬性,從而獲得正確結果。

10..toString(2) // "1010"

// 其餘方法還包括
10 .toString(2) // "1010"
10.0.toString(2) // "1010"

這實際上意味着,能夠直接對一個小數使用toString方法。

10.5.toString() // "10.5"
10.5.toString(2) // "1010.1"
10.5.toString(8) // "12.4"
10.5.toString(16) // "a.8"

經過方括號運算符也能夠調用toString方法。

10['toString'](2) // "1010"

toString方法只能將十進制的數,轉爲其餘進制的字符串。若是要將其餘進制的數,轉回十進制,須要使用parseInt方法。

Number.prototype.toFixed()

toFixed()方法先將一個數轉爲指定位數的小數,而後返回這個小數對應的字符串。

(10).toFixed(2) // "10.00"
10.005.toFixed(2) // "10.01"

上面代碼中,1010.005先轉成2位小數,而後轉成字符串。其中10必須放在括號裏,不然後面的點會被處理成小數點。

toFixed()方法的參數爲小數位數,有效範圍爲0到20,超出這個範圍將拋出RangeError錯誤。

因爲浮點數的緣由,小數5的四捨五入是不肯定的,使用的時候必須當心。

(10.055).toFixed(2) // 10.05
(10.005).toFixed(2) // 10.01
Number.prototype.toExponential()

toExponential方法用於將一個數轉爲科學計數法形式。

(10).toExponential()  // "1e+1"
(10).toExponential(1) // "1.0e+1"
(10).toExponential(2) // "1.00e+1"

(1234).toExponential()  // "1.234e+3"
(1234).toExponential(1) // "1.2e+3"
(1234).toExponential(2) // "1.23e+3"

toExponential方法的參數是小數點後有效數字的位數,範圍爲0到20,超出這個範圍,會拋出一個RangeError錯誤。

Number.prototype.toPrecision()

toPrecision方法用於將一個數轉爲指定位數的有效數字。

(12.34).toPrecision(1) // "1e+1"
(12.34).toPrecision(2) // "12"
(12.34).toPrecision(3) // "12.3"
(12.34).toPrecision(4) // "12.34"
(12.34).toPrecision(5) // "12.340"

toPrecision方法的參數爲有效數字的位數,範圍是1到21,超出這個範圍會拋出RangeError錯誤。

toPrecision方法用於四捨五入時不太可靠,跟浮點數不是精確儲存有關。

(12.35).toPrecision(3) // "12.3"
(12.25).toPrecision(3) // "12.3"
(12.15).toPrecision(3) // "12.2"
(12.45).toPrecision(3) // "12.4"

自定義方法

與其餘對象同樣,Number.prototype對象上面能夠自定義方法,被Number的實例繼承。

Number.prototype.add = function (x) {
  return this + x;
};

8['add'](2) // 10

上面代碼爲Number對象實例定義了一個add方法。在數值上調用某個方法,數值會自動轉爲Number的實例對象,因此就能夠調用add方法了。因爲add方法返回的仍是數值,因此能夠鏈式運算。

Number.prototype.subtract = function (x) {
  return this - x;
};

(8).add(2).subtract(4) // 6

咱們還能夠部署更復雜的方法。

Number.prototype.iterate = function () {
  var result = [];
  for (var i = 0; i <= this; i++) {
    result.push(i);
  }
  return result;
};

(8).iterate() // [0, 1, 2, 3, 4, 5, 6, 7, 8]

上面代碼在Number對象的原型上部署了iterate方法,將一個數值自動遍歷爲一個數組。

注意,數值的自定義方法,只能定義在它的原型對象Number.prototype上面,數值自己是沒法自定義屬性的。

var n = 1;
n.x = 1;
n.x // undefined

上面代碼中,n是一個原始類型的數值。直接在它上面新增一個屬性x,不會報錯,但毫無做用,老是返回undefined。這是由於一旦被調用屬性,n就自動轉爲Number的實例對象,調用結束後,該對象自動銷燬。因此,下一次調用n的屬性時,實際取到的是另外一個對象,屬性x固然就讀不出來。

String 對象

概述

String對象是 JavaScript 原生提供的三個包裝對象之一,用來生成字符串對象。

var s1 = 'abc';
var s2 = new String('abc');

typeof s1 // "string"
typeof s2 // "object"

s2.valueOf() // "abc"

字符串對象是一個相似數組的對象(很像數組,但不是數組)。

new String('abc')
// String {0: "a", 1: "b", 2: "c", length: 3}

(new String('abc'))[1] // "b"

上面代碼中,字符串abc對應的字符串對象,有數值鍵(012)和length屬性,因此能夠像數組那樣取值。

除了用做構造函數,String對象還能夠看成工具方法使用,將任意類型的值轉爲字符串。

String(true) // "true"
String(5) // "5"

靜態方法

String.fromCharCode()

String對象提供的靜態方法(即定義在對象自己,而不是定義在對象實例的方法),主要是String.fromCharCode()。該方法的參數是一個或多個數值,表明 Unicode 碼點,返回值是這些碼點組成的字符串。

String.fromCharCode() // ""
String.fromCharCode(97) // "a"
String.fromCharCode(104, 101, 108, 108, 111) // "hello"

上面代碼中,String.fromCharCode方法的參數爲空,就返回空字符串;不然,返回參數對應的 Unicode 字符串。

注意,該方法不支持 Unicode 碼點大於0xFFFF的字符,即傳入的參數不能大於0xFFFF(即十進制的 65535)。

String.fromCharCode(0x20BB7) // "ஷ"
String.fromCharCode(0x20BB7) === String.fromCharCode(0x0BB7)
// true

上面代碼中,String.fromCharCode參數0x20BB7大於0xFFFF,致使返回結果出錯。0x20BB7對應的字符是漢字𠮷,可是返回結果倒是另外一個字符(碼點0x0BB7)。這是由於String.fromCharCode發現參數值大於0xFFFF,就會忽略多出的位(即忽略0x20BB7裏面的2)。

這種現象的根本緣由在於,碼點大於0xFFFF的字符佔用四個字節,而 JavaScript 默認支持兩個字節的字符。這種狀況下,必須把0x20BB7拆成兩個字符表示。

String.fromCharCode(0xD842, 0xDFB7) // "𠮷"

上面代碼中,0x20BB7拆成兩個字符0xD8420xDFB7(即兩個兩字節字符,合成一個四字節字符),就能獲得正確的結果。碼點大於0xFFFF的字符的四字節表示法,由 UTF-16 編碼方法決定。

實例屬性

String.prototype.length

字符串實例的length屬性返回字符串的長度。

'abc'.length // 3

實例方法

String.prototype.charAt()

charAt方法返回指定位置的字符,參數是從0開始編號的位置。

var s = new String('abc');

s.charAt(1) // "b"
s.charAt(s.length - 1) // "c"

這個方法徹底能夠用數組下標替代。

'abc'.charAt(1) // "b"
'abc'[1] // "b"

若是參數爲負數,或大於等於字符串的長度,charAt返回空字符串。

'abc'.charAt(-1) // ""
'abc'.charAt(3) // ""
String.prototype.charCodeAt()

charCodeAt方法返回字符串指定位置的 Unicode 碼點(十進制表示),至關於String.fromCharCode()的逆操做。

'abc'.charCodeAt(1) // 98

上面代碼中,abc1號位置的字符是b,它的 Unicode 碼點是98

若是沒有任何參數,charCodeAt返回首字符的 Unicode 碼點。

'abc'.charCodeAt() // 97

若是參數爲負數,或大於等於字符串的長度,charCodeAt返回NaN

'abc'.charCodeAt(-1) // NaN
'abc'.charCodeAt(4) // NaN

注意,charCodeAt方法返回的 Unicode 碼點不會大於65536(0xFFFF),也就是說,只返回兩個字節的字符的碼點。若是遇到碼點大於 65536 的字符(四個字節的字符),必需連續使用兩次charCodeAt,不只讀入charCodeAt(i),還要讀入charCodeAt(i+1),將兩個值放在一塊兒,才能獲得準確的字符。

String.prototype.concat()

concat方法用於鏈接兩個字符串,返回一個新字符串,不改變原字符串。

var s1 = 'abc';
var s2 = 'def';

s1.concat(s2) // "abcdef"
s1 // "abc"

該方法能夠接受多個參數。

'a'.concat('b', 'c') // "abc"

若是參數不是字符串,concat方法會將其先轉爲字符串,而後再鏈接。

var one = 1;
var two = 2;
var three = '3';

''.concat(one, two, three) // "123"
one + two + three // "33"

上面代碼中,concat方法將參數先轉成字符串再鏈接,因此返回的是一個三個字符的字符串。做爲對比,加號運算符在兩個運算數都是數值時,不會轉換類型,因此返回的是一個兩個字符的字符串。

String.prototype.slice()

slice方法用於從原字符串取出子字符串並返回,不改變原字符串。它的第一個參數是子字符串的開始位置,第二個參數是子字符串的結束位置(不含該位置)。

'JavaScript'.slice(0, 4) // "Java"

若是省略第二個參數,則表示子字符串一直到原字符串結束。

'JavaScript'.slice(4) // "Script"

若是參數是負值,表示從結尾開始倒數計算的位置,即該負值加上字符串長度。

'JavaScript'.slice(-6) // "Script"
'JavaScript'.slice(0, -6) // "Java"
'JavaScript'.slice(-2, -1) // "p"

若是第一個參數大於第二個參數,slice方法返回一個空字符串。

'JavaScript'.slice(2, 1) // ""
String.prototype.substring()

substring方法用於從原字符串取出子字符串並返回,不改變原字符串,跟slice方法很相像。它的第一個參數表示子字符串的開始位置,第二個位置表示結束位置(返回結果不含該位置)。

'JavaScript'.substring(0, 4) // "Java"

若是省略第二個參數,則表示子字符串一直到原字符串的結束。

'JavaScript'.substring(4) // "Script"

若是第一個參數大於第二個參數,substring方法會自動更換兩個參數的位置。

'JavaScript'.substring(10, 4) // "Script"
// 等同於
'JavaScript'.substring(4, 10) // "Script"

上面代碼中,調換substring方法的兩個參數,都獲得一樣的結果。

若是參數是負數,substring方法會自動將負數轉爲0。

'JavaScript'.substring(-3) // "JavaScript"
'JavaScript'.substring(4, -3) // "Java"

因爲這些規則違反直覺,所以不建議使用substring方法,應該優先使用slice

String.prototype.substr()

substr方法用於從原字符串取出子字符串並返回,不改變原字符串,跟slicesubstring方法的做用相同。

substr方法的第一個參數是子字符串的開始位置(從0開始計算),第二個參數是子字符串的長度。

'JavaScript'.substr(4, 6) // "Script"

若是省略第二個參數,則表示子字符串一直到原字符串的結束。

'JavaScript'.substr(4) // "Script"

若是第一個參數是負數,表示倒數計算的字符位置。若是第二個參數是負數,將被自動轉爲0,所以會返回空字符串。

'JavaScript'.substr(-6) // "Script"
'JavaScript'.substr(4, -1) // ""

上面代碼中,第二個例子的參數-1自動轉爲0,表示子字符串長度爲0,因此返回空字符串。

String.prototype.indexOf(),String.prototype.lastIndexOf()

indexOf方法用於肯定一個字符串在另外一個字符串中第一次出現的位置,返回結果是匹配開始的位置。若是返回-1,就表示不匹配。

'hello world'.indexOf('o') // 4
'JavaScript'.indexOf('script') // -1

indexOf方法還能夠接受第二個參數,表示從該位置開始向後匹配。

'hello world'.indexOf('o', 6) // 7

lastIndexOf方法的用法跟indexOf方法一致,主要的區別是lastIndexOf從尾部開始匹配,indexOf則是從頭部開始匹配。

'hello world'.lastIndexOf('o') // 7

另外,lastIndexOf的第二個參數表示從該位置起向前匹配。

'hello world'.lastIndexOf('o', 6) // 4
String.prototype.trim()

trim方法用於去除字符串兩端的空格,返回一個新字符串,不改變原字符串。

'  hello world  '.trim() // "hello world"

該方法去除的不只是空格,還包括製表符(\t\v)、換行符(\n)和回車符(\r)。

'\r\nabc \t'.trim() // 'abc'
String.prototype.toLowerCase(),String.prototype.toUpperCase()

toLowerCase方法用於將一個字符串所有轉爲小寫,toUpperCase則是所有轉爲大寫。它們都返回一個新字符串,不改變原字符串。

'Hello World'.toLowerCase() // "hello world"

'Hello World'.toUpperCase() // "HELLO WORLD"
String.prototype.match()

match方法用於肯定原字符串是否匹配某個子字符串,返回一個數組,成員爲匹配的第一個字符串。若是沒有找到匹配,則返回null

'cat, bat, sat, fat'.match('at') // ["at"]
'cat, bat, sat, fat'.match('xt') // null

返回的數組還有index屬性和input屬性,分別表示匹配字符串開始的位置和原始字符串。

var matches = 'cat, bat, sat, fat'.match('at');
matches.index // 1
matches.input // "cat, bat, sat, fat"

match方法還可使用正則表達式做爲參數。

String.prototype.search(),String.prototype.replace()

search方法的用法基本等同於match,可是返回值爲匹配的第一個位置。若是沒有找到匹配,則返回-1

'cat, bat, sat, fat'.search('at') // 1

search方法還可使用正則表達式做爲參數。

replace方法用於替換匹配的子字符串,通常狀況下只替換第一個匹配(除非使用帶有g修飾符的正則表達式)。

'aaa'.replace('a', 'b') // "baa"

replace方法還可使用正則表達式做爲參數。

String.prototype.split()

split方法按照給定規則分割字符串,返回一個由分割出來的子字符串組成的數組。

'a|b|c'.split('|') // ["a", "b", "c"]

若是分割規則爲空字符串,則返回數組的成員是原字符串的每個字符。

'a|b|c'.split('') // ["a", "|", "b", "|", "c"]

若是省略參數,則返回數組的惟一成員就是原字符串。

'a|b|c'.split() // ["a|b|c"]

若是知足分割規則的兩個部分緊鄰着(即兩個分割符中間沒有其餘字符),則返回數組之中會有一個空字符串。

'a||c'.split('|') // ['a', '', 'c']

若是知足分割規則的部分處於字符串的開頭或結尾(即它的前面或後面沒有其餘字符),則返回數組的第一個或最後一個成員是一個空字符串。

'|b|c'.split('|') // ["", "b", "c"]
'a|b|'.split('|') // ["a", "b", ""]

split方法還能夠接受第二個參數,限定返回數組的最大成員數。

'a|b|c'.split('|', 0) // []
'a|b|c'.split('|', 1) // ["a"]
'a|b|c'.split('|', 2) // ["a", "b"]
'a|b|c'.split('|', 3) // ["a", "b", "c"]
'a|b|c'.split('|', 4) // ["a", "b", "c"]

split方法還可使用正則表達式做爲參數。

String.prototype.localeCompare()

localeCompare方法用於比較兩個字符串。它返回一個整數,若是小於0,表示第一個字符串小於第二個字符串;若是等於0,表示二者相等;若是大於0,表示第一個字符串大於第二個字符串。

'apple'.localeCompare('banana') // -1
'apple'.localeCompare('apple') // 0

該方法的最大特色,就是會考慮天然語言的順序。舉例來講,正常狀況下,大寫的英文字母小於小寫字母。

'B' > 'a' // false

上面代碼中,字母B小於字母a。由於 JavaScript 採用的是 Unicode 碼點比較,B的碼點是66,而a的碼點是97。

可是,localeCompare方法會考慮天然語言的排序狀況,將B排在a的前面。

'B'.localeCompare('a') // 1

上面代碼中,localeCompare方法返回整數1,表示B較大。

localeCompare還能夠有第二個參數,指定所使用的語言(默認是英語),而後根據該語言的規則進行比較。

'ä'.localeCompare('z', 'de') // -1
'ä'.localeCompare('z', 'sv') // 1

上面代碼中,de表示德語,sv表示瑞典語。德語中,ä小於z,因此返回-1;瑞典語中,ä大於z,因此返回1

做者:oWSQo
連接:https://www.jianshu.com/p/324... 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

相關文章
相關標籤/搜索