您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關JavaScript的底層知識點有哪些,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
JavaScript是一門直譯式的解釋型腳本語言,它具有動態性、弱類型、基于原型等特點。JavaScript植根于我們使用的Web瀏覽器中,它的解釋器為瀏覽器中的JavaScript引擎。這一門廣泛用于客戶端的腳本語言,最早是為了處理以前由服務器端語言負責的一些輸入驗證操作,隨著Web時代的發展,JavaScript不斷發展壯大,成為一門功能全面的編程語言。它的用途早已不再局限于當初簡單的數據驗證,而是具備了與瀏覽器窗口及其內容等幾乎所有方面交互的能力。它既是一門非常簡單的語言,又是一門及其復雜的語言,要想真正精通JavaScript,我們就必須深入的去了解它的一些底層設計原理。
數據類型
按照存儲方式,JavaScript的數據類型可以分為兩種,原始數據類型(原始值)和引用數據類型(引用值)。
原始數據類型目前有六種,包括Number、String、Boolean、Null、Undefined、Symbol(ES6),這些類型是可以直接操作的保存在變量中的實際值。原始數據類型存放在棧中,數據大小確定,它們是直接按值存放的,所以可以直接按值訪問。
引用數據類型則為Object,在JavaScript中除了原始數據類型以外的都是Object類型,包括數組、函數、正則表達式等都是對象。引用類型是存放在堆內存中的對象,變量是保存在棧內存中的一個指向堆內存中對象的引用地址。當定義了一個變量并初始化為引用值,若將它賦給另一個變量,則這兩個變量保存的是同一個地址,指向堆內存中的同一個內存空間。如果通過其中一個變量去修改引用數據類型的值,另一個變量也會跟著改變。
對于原始數據類型,除了null比較特殊(null會被認為是一個空的對象引用),其它的我們可以用typeof進行準確判斷:
表達式 | 返回值 |
typeof 123 | 'number' |
typeof "abc" | 'string' |
typeof true | 'boolean' |
typeof null | 'object' |
typeof undefined | 'undefined' |
typeof unknownVariable(未定義的變量) | 'undefined' |
typeof Symbol() | ‘symbol’ |
typeof function() {} | 'function' |
typeof {} | 'object' |
typeof [] | 'object' |
typeof(/[0-9,a-z]/) | ‘object’ |
對于null類型,可以使用全等操作符進行判斷。一個已經聲明但未初始化的變量值會默認賦予undefined (也可以手動賦予undefined),在JavaScript中,使用相等操作符==無法區分null和undefined,ECMA-262規定對它們的相等性測試要返回true。要準確區分兩個值,需要使用全等操作符===。
對于引用數據類型,除了function在方法設計上比較特殊,可以用typeof進行準確判斷,其它的都返回object類型。我們可以用instanceof 對引用類型值進行判斷。instanceof 會檢測一個對象A是不是另一個對象B的實例,它在底層會查看對象B是否在對象A的原型鏈上存在著(實例和原型鏈文章后面會講)。如果存在,則返回true,如果不在則返回false。
表達式 | 返回值 |
[1,2,3] instanceof Array | ‘true’ |
function foo(){ } instanceof Function | ‘true’ |
/[0-9,a-z]/ instanceof RegExp | ‘true’ |
new Date() instanceof Date | ‘true’ |
{name:”Alan”,age:”22”} instanceof Object | ‘true’ |
由于所有引用類型值都是Object的實例,所以用instance操作符對它們進行Object的判斷,結果也會返回true。
表達式 | 返回值 |
[1,2,3] instanceof Object | ‘true’ |
function foo(){ } instanceof Object | ‘true’ |
/[0-9,a-z]/ instanceof Object | ‘true’ |
new Date() instanceof Object | ‘true’ |
當然,還有一種更為強大的方法,可以精準的判斷任何JavaScript中的任何數據類型,那就是Object.prototype.toString.call() 方法。在ES5中,所有對象(原生對象和宿主對象)都有一個內部屬性[[Class]],它的值是一個字符串,記錄了該對象的類型。目前包括"Array", "Boolean", "Date", "Error", "Function", "Math", "Number", "Object", "RegExp", "String",“Arguments”, "JSON","Symbol”。通過Object.prototype.toString() 方法可以用來查看該內部屬性,除此自外沒有其它方法。
在Object.prototype.toString()方法被調用時,會執行以下步驟:1.獲取this對象的[[Class]]屬性值(關于this對象文章后面會講)。2.將該值放在兩個字符串”[object ” 與 “]” 中間并拼接起來。3.返回拼接完的字符串。
當遇到this的值為null時,Object.prototype.toString()方法直接返回”[object Null]”。當遇到this的值為undefined時,直接返回”[object Undefined]”。
表達式 | 返回值 |
Object.prototype.toString.call(123) | [object Number] |
Object.prototype.toString.call(“abc”) | [object String] |
Object.prototype.toString.call(true) | [object Boolean] |
Object.prototype.toString.call(null) | [object Null] |
Object.prototype.toString.call(undefined) | [object Undefined] |
Object.prototype.toString.call(Symbol()) | [object Symbol] |
Object.prototype.toString.call(function foo(){}) | [object Function] |
Object.prototype.toString.call([1,2,3]) | [object Array] |
Object.prototype.toString.call({name:”Alan” }) | [object Object] |
Object.prototype.toString.call(new Date()) | [object Date] |
Object.prototype.toString.call(RegExp()) | [object RegExp] |
Object.prototype.toString.call(window.JSON) | [object JSON] |
Object.prototype.toString.call(Math) | [object Math] |
call()方法可以改變調用Object.prototype.toString()方法時this的指向,使它指向我們傳入的對象,因此能獲取到我們傳入對象的[[Class]]屬性(使用Object.prototype.toString.apply()也能達到同樣的效果)。
JavaScript的數據類型也是可以轉換的,數據類型轉換分為兩種方式:顯示類型轉換和隱式類型轉換。
顯示類型轉換可以調用方法有Boolean()、String()、Number()、parseInt()、parseFloat()和toString() (null和undefined值沒有這個方法),它們各自的用途一目了然,這里就不一一介紹了。
由于JavaScript是弱類型語言,在使用算術運算符時,運算符兩邊的數據類型可以是任意的,不用像Java或C語言那樣指定相同的類型,引擎會自動為它們進行隱式類型轉換。隱式類型轉換不像顯示類型轉換那么直觀,主要是三種轉換方式:
1. 將值轉換為原始值:toPrimitive()
2. 將值轉換為數字:toNumber()
3. 將值轉換為字符串:toString()
一般來說,當對數字和字符串進行相加操作時,數字會被轉換成字符串;當進行真值判斷時(如if、||、&&),參數會被轉換成布爾值;當進行比較運算、算術運算或自增減運算時,參數會被轉換成Number值;當對象需要進行隱式類型轉換時,會取得對象的toString()方法或valueOf()方法的返回值。
關于NaN:
NaN是一個特殊的數值,表示非數值。首先,任何涉及NaN的運算操作都會返回NaN。其次,NaN與任何值都不相等,包括NaN本身。ECMAScript定義了一個isNaN()函數,可以用來測試某個參數是否為“非數值”。它首先會嘗試將參數隱式轉換為數值,如果無法轉換為數值則返回true。
我們可以先通過typeof判斷是否為Number類型,再通過isNaN來判斷當前數據是否為NaN。
關于字符串:
JavaScript中的字符串是不可變的,字符串一旦被創建,它們的值就不能改變。要改變某個變量保存的字符串,首先要銷毀原來的字符串,然后再用另一個包含新值的字符串填充該變量。這個過程在后臺發生,而這也是某些老版本瀏覽器在拼接字符串時速度很慢的原因所在。
其實為了便于操作基本類型值,ECMAScript還提供了3個特殊的引用類型:Boolean、Number和String。原始數據類型是沒有屬性和方法的,當我們在原始類型值上調用方法讀取它們時,訪問過程會處于一種讀取模式,后臺會創建一個相應的原始包裝類型的對象,從而讓我們能夠調用一些方法來操作這些數據。這個過程分為三個步驟:1.創建原始包裝類型的實例 2.在實例上調用指定的方法 3.銷毀這個實例。
引用類型與原始包裝類型的主要區別是對象的生存周期,自動創建的原始包裝類型對象,只存在于一行代碼的執行瞬間,然后立即被銷毀,因此我們不能在運行時為原始類型值添加屬性和方法。
在《你不知道的JavaScript》一書中作者表示過,盡管將JavaScript歸類為“動態語言”或“解釋執行語言”,但事實上它是一門編譯語言。JavaScript運行分為三個步驟:1.語法分析 2.預編譯 3.解釋執行。語法分析和解釋執行都不難理解,一個是檢查代碼是否有語法錯誤,一個則負責將程序一行一行的執行,但JavaScript中的預編譯階段卻稍微比較復雜。
任何JavaScript代碼在執行前都要進行編譯,編譯過程大部分情況下發生在代碼執行前的幾微秒內。編譯階段JavaScript引擎會從當前代碼執行作用域開始,對代碼進行RHS查詢,以獲取變量的值。接著在執行階段引擎會執行LHS查詢,對變量進行賦值。
在編譯階段,JavaScript引擎的一部分工作就是找到所有的聲明,并用合適的作用域將它們關聯起來。在預編譯過程,如果是在全局作用域下,JavaScript引擎首先會在全局作用域上創建一個全局對象(GO對象,Global Object),并將變量聲明和函數聲明進行提升。提升后的變量先默認初始化為undefined,而函數則將整個函數體進行提升(如果是以函數表達式的形式定義函數,則應用變量提升的規則),然后將它們存放到全局變量中。函數聲明的提升會優先于變量聲明的提升,對于變量聲明來說,重復出現的var聲明會被引擎忽略,而后面出現的函數聲明可以覆蓋前面的函數聲明(ES6新的變量聲明語法let情況稍稍有點不一樣,這里暫時先不討論)。
函數體內部是一塊獨立的作用域,在函數體內部也會進行預編譯階段。在函數體內部,首先會創建一個活動對象(AO對象,Active Object),并將形參和變量聲明以及函數體內部的函數聲明進行提升,形參和變量初始化為undefined,內部函數依然為內部函數體本身,然后將它們存放到活動對象中。
編譯階段結束后,就會執行JavaScript代碼。執行過程根據先后順序依次對變量或形參進行賦值操作。引擎會在作用域上查找是否有對應的變量聲明或形參聲明,如果找到了就會對它們進行賦值操作。對于非嚴格模式來說,若變量未經聲明就進行賦值,引擎會在全局環境自動隱式地為該變量創建一個聲明,但對于嚴格模式來說對未經聲明的變量進行賦值操作則會報錯。因為JavaScript執行是單線程的,所以如果在賦值操作(LHS查詢)執行前就先對變量進行獲取(RHS查詢)并輸出,會得到undefined的結果,因為此時變量還未賦值。
每個函數都是Function對象的一個實例,在JavaScript中,每個對象都有一個僅供JavaScript引擎存取的內部屬性—— [[Scope]]。對于函數來說,[[Scope]]屬性包含了函數被創建的作用域中對象的集合——作用域鏈。當在全局環境中創建一個函數時,該函數的作用域鏈便會插入一個全局對象,包含所有在全局范圍內定義的變量。
內部作用域可以訪問外部作用域,但外部作用域無法訪問內部作用域。由于JavaScript沒有塊級作用域,因此在if語句或者for循環語句中定義的變量是可以在語句外部訪問到的。在ES6之前,javascript只有全局作用域和函數作用域,ES6新增了塊級作用域的機制。
而當該函數被執行時,會為執行函數創建一個稱為執行環境(execution context,也稱為執行上下文)的內部對象。每個執行環境都有自己的作用域鏈,當執行環境被創建時,它的作用域鏈頂端先初始化為當前運行函數的[[Scope]]屬性中的對象。緊接著,函數運行時的活動對象(包括所有局部變量、命名參數、arguments參數集合和this)也會被創建并推入作用域鏈的最頂端。
函數每次執行時對應的執行環境都是獨一無二的,多次調用同一個函數就會導致創建多個執行環境。當函數執行完畢,執行環境就會被銷毀。當執行環境被銷毀,活動對象也隨之銷毀(全局執行環境會等到應用程序退出時才會被銷毀,如關閉網頁或瀏覽器)。
函數執行過程中,每遇到一個變量,都會經歷一次標識符解析過程,以決定從哪里獲取或存儲數據。標識符解析是沿著作用域鏈一級一級地搜索標識符的過程,全局變量始終都是作用域鏈的最后一個對象(即window對象)。
在JavaScript中,有兩個語句可以在執行時臨時改變作用域鏈。第一個是with語句。with語句會創建一個可變對象,包含參數指定對象的所有屬性,并將該對象推入作用域鏈的首位,這意味著函數的活動對象被擠到作用域鏈的第二位。這樣雖然使得訪問可變對象的屬性非常快,但訪問局部變量等的速度就變慢了。第二條能改變執行環境作用域鏈的語句是try-catch語句中的catch子句。當try代碼塊中發生錯誤,執行過程會自動跳轉到catch子句,然后把異常對象推入一個變量對象并置于作用域的首位。在catch代碼塊內部,函數所有局部變量將會放在第二個作用域鏈對象中。一旦catch子句執行完畢,作用域鏈就會返回到之前的狀態。
JavaScript中的構造函數可以用來創建特定類型的對象。為了區別于其它函數,構造函數一般使用大寫字母開頭。不過在JavaScript中這并不是必須的,因為JavaScript不存在定義構造函數的特殊語法。在JavaScript中,構造函數與其它函數的唯一區別,就在于調用它們的方式不同。任何函數,只要通過new操作符來調用,就可以作為構造函數。
JavaScript函數有四種調用模式:1.獨立函數調用模式,如foo(arg)。2.方法調用模式,如obj.foo(arg)。3.構造器調用模式,如new foo(arg)。4.call/apply調用模式,如foo.call(this,arg1,arg2)或foo.apply(this,args) (此處的args是一個數組)。
要創建構造函數的實例,發揮構造函數的作用,就必須使用new操作符。當我們使用new操作符實例化構造函數時,構造函數內部會執行以下步驟:
1.隱式創建一個this空對象
2.執行構造函數中的代碼(為當前this對象添加屬性)
3.隱式返回當前this對象
如果構造函數顯示的返回一個對象,那么實例為這個返回的對象,否則則為隱式返回的this對象。
當我們調用構造函數創建實例后,實例便具備構造函數所有的實例屬性和方法。對于通過構造函數創建的不同實例,它們之間的實例屬性和方法都是各自獨立的。那怕是同名的引用類型值,不同實例之間也不會相互影響。
原型和原型鏈既是JavaScript這門語言的精髓之一,也是這門語言的難點之一。原型prototype(顯式原型)是函數特有的屬性,任何時候,只要創建了一個函數,這個函數就會自動創建一個prototype屬性,并指向該函數的原型對象。所有原型對象都會自動獲得一個constructor(構造者,也可翻譯為構造函數)屬性,這個屬性包含一個指向prototype屬性所在函數(即構造函數本身)的指針。而當我們通過構造函數創建一個實例后,該實例內部將包含一個[[Prototype]]的內部屬性(隱式原型),同樣也指向構造函數的原型對象。在Firefox、Safari和Chrome瀏覽器中,每個對象都可以通過__proto__屬性訪問它們的[[Prototype]]屬性。而對其它瀏覽器而言,這個屬性對腳本則是完全不可見的。
構造函數的prototype屬性和實例的[[Prototype]]都是指向構造函數的原型對象,實例的 [[Prototype]] 屬性與構造函數之間并沒有直接的關系。要想知道實例的 [[Prototype]] 屬性是否指向某個構造函數的原型對象,我們可以使用isPrototypeOf()或者Object.getPrototypeOf() 方法。
每當讀取某個對象實例的某個屬性時,都會執行一次搜索,目標是具有給定名字的屬性。搜索首先從對象實例本身開始,如果在實例中找到了具有給定名稱的屬性,就返回該屬性的值;如果沒有找到,則繼續搜索該對象[[Prototype]]屬性指向的原型對象,在原型對象中查找給定名稱的屬性,如果找到再返回該屬性的值。
判斷對象是哪個構造函數的直接實例,可以直接在實例上訪問constructor屬性,實例會通過[[Prototype]]讀取原型對象上的constructor屬性返回構造函數本身。
原型對象中的值可以通過對象實例訪問,但卻不能通過對象實例修改。如果在實例中添加一個與實例原型對象同名的屬性,那我們就在實例中創建該屬性,這個實例屬性會阻止我們訪問原型對象中的那個屬性,但不會修改那個屬性。簡單將該實例屬性設為null并不能恢復訪問原型對象中該屬性的連接,若要恢復訪問原型對象中的該屬性,可以用delete操作符完全刪除對象實例的該屬性。
使用hasOwnProperty()方法可以檢測一個屬性是存在于實例中,還是存在于原型中。這個方法只有在給定屬性存在于對象實例中時,才會返回true。若要取得對象自身所有可枚舉的實例屬性,可以使用ES5的Object.keys() 方法。若要獲取所有實例屬性,無論是否可枚舉,可以使用Object.getOwnPropertyNames() 方法。
原型具有動態性,對原型對象所做的任何修改都能立即從實例上反應出來,但如果是重寫整個原型對象,情況就不一樣了。調用構造函數會為對象實例添加一個指向最初原型對象的 [[Prototype]] 指針,而重寫整個原型對象后,構造函數指向新的原型對象,所有的原型對象屬性和方法都存在與新的原型對象上;而對象實例還指向最初的原型對象,這樣一來構造函數與最初原型對象之間指向同一個原型對象產生的聯系就被切斷了,因為它們分別指向了不同的原型對象。
若要恢復這種聯系,可以在構造函數prototype重寫后再實例化對象實例,或者修改對象實例的__proto__屬性重新指向構造函數新的原型對象。
JavaScript將原型鏈作為實現繼承的主要方式,它利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。構造函數的實例有一個指向原型對象的 [[Prototype]] 屬性,當我們讓構造函數的原型對象等于另一個類型的實例,原型對象也將包含一個指向另一個原型的 [[Prototype]] 指針,假如另一個原型又是另一個類型的實例…如此層層遞進,就構成了實例與原型的鏈條。這就是所謂原型鏈的基本概念。
原型鏈擴展了原型搜索機制,當讀取一個實例屬性時,首先會在實例中搜索該屬性。如果沒有找到該屬性,則會繼續搜索實例[[Prototype]] 指向的原型對象,原型對象此時也變成了另一個構造函數的實例,如果該原型對象上也找不到,就會繼續搜索該原型對象[[Prototype]] 指向的另一個原型對象…搜索過程沿著原型鏈不斷向上搜索,在找不到指定屬性或者方法的情況下,搜索過程就會一環一環地執行到原型鏈末端才會停下來。
如果不對函數的原型對象進行修改,所有引用類型都有一個[[Prototype]] 屬性默認指向Object的原型對象。因此,所有函數的默認原型都是Object的實例,這也正是所有自定義類型都會繼承toString()、valueOf() 等默認方法的根本原因。可以使用instanceof操作符或isPrototypeOf() 方法來判斷實例的原型鏈中是否存在某個構造函數的原型。
原型鏈雖然很強大,但是它也存在一些問題。第一個問題是原型對象上的引用類型值是所有實例共享的,這意味著不同實例的引用類型屬性或方法都指向同一個堆內存,一個實例在原型上修改引用值會同時影響到所有其它實例在原型對象上的該引用值,這便是為何要在構造函數中定義私有屬性或方法,而不是在原型上定義的原因。原型鏈的第二個問題,在于當我們將一個構造函數的原型prototype等于另一個構造函數的實例時,如果我們在這時候給另一個構造函數傳遞參數設置屬性值,那么基于原來的構造函數所有實例的該屬性都會因為原型鏈的關系跟著被賦予相同的值,而這有時候并不是我們想要的結果。
閉包是JavaScript最強大的特性之一,在JavaScript中,閉包,是指有權訪問另一個函數作用域中的變量的函數,它意味著函數可以訪問局部作用域之外的數據。創建閉包的常見方式,就是在一個函數內部創建另一個函數并返回這個函數。
一般來講,當函數執行完畢后,局部活動對象就會被銷毀,內存中僅保存全局作用域。但是,閉包的情況有所不同。
閉包函數的[[Scope]]屬性會初始化為包裹它的函數的作用域鏈,所以閉包包含了與執行環境作用域鏈相同的對象的引用。一般來講,函數的活動對象會隨著執行環境一同銷毀。但引入閉包時,由于引用仍然存在于閉包的[[Scope]]屬性中,因此原函數的活動對象無法被銷毀。這意味著閉包函數與非閉包函數相比,需要更多的內存開銷,導致更多的內存泄漏。此外,閉包訪問原包裹函數的活動對象時,在作用域鏈上需要先跨過對自身活動對象的標識符解析,找到更上面的一層,因此閉包使用原包裹函數的變量對性能也是有很大的影響。
在定時器、事件監聽器、Ajax請求、跨窗口通信、Web Workers或者任何其他的異步或同步任務中,只要使用了回調函數,實際上就是在使用閉包。
典型的閉包問題就是在for循環中使用定時器輸出循環變量:
這段代碼,對于不熟悉JavaScript閉包的朋友來說,可能會想當然的認為結果會依次輸出0、1、2、3,然而,實際的情況是,這段代碼輸出的四個數字都是4。
這是因為,由于定時器是異步加載機制,會等for循環遍歷完畢才會執行。每次執行定時器,定時器都會在它外部作用域中查找i變量。由于循環已經結束,外部作用域的i變量早已被更新為4,所以4個定時器取得的i變量都為4,而不是我們理想中輸出0,1,2,3。
解決這個問題,我們可以創建一個包裹立即執行函數的新的作用域,將每次循環中外部作用域的i變量保存到新創建的作用域中,讓定時器每次都先從新作用域中取值,我們可以用立即執行函數來創建這個新的作用域:
這樣循環執行的結果就會依次輸出0,1,2,3了,我們還可以把這個立即執行函數再簡化一些,直接將i作用實參傳給立即執行函數,就不用在里面給j賦值了:
當然,采用立即執行函數不是必須的,你也可以創建一個非匿名的函數并在每次循環的時候執行它,只不過這樣就會多占用一些內存來保存函數聲明了。
因為在ES6之前還沒有塊級作用域的設定,所以我們只能采取手動創建一個新的作用域的方法來解決這個問題。ES6開始設定了塊級作用域,我們可以使用let定義塊級作用域的方法:
let操作符會創建塊級作用域,通過let聲明的變量保存在當前塊級作用域中,所以每個立即執行函數每次都會從它當前的塊級作用域中查找變量。
let還有一個特殊的定義,它使變量在循環過程中不止被聲明一次,每次循環都會重新聲明,并用上一個循環結束時的值來初始化新聲明的變量,所以,我們也可以直接在for循環頭部使用let:
this關鍵字是JavaScript中最復雜的機制之一,它被自動定義在所有函數的作用域中。人們很容易把this理解成指向函數自身,然而,在 ES5 中,this并不是在函數聲明時綁定的,它是在函數運行時進行綁定的,它的指向只取決于函數的調用方式,與函數聲明的位置沒有關系。(ES6新增的箭頭函數里的 this有所不同,它的指向取決于函數聲明的位置。)
還記得我前面提到的函數四種調用模式嗎: 1.獨立函數調用模式,如foo(arg)。2.對象方法調用模式,如obj.foo(arg)。3.構造器調用模式,如new foo(arg)。4.call/apply調用模式,如foo.call(this)或foo.apply(this) 。
對于獨立函數調用模式來說,在非嚴格模式下,它里面的this會默認指向全局對象。而在嚴格模式中,this不允許默認綁定到全局對象,因此會綁定為undefined。
對于對象方法調用模式來說,函數中的this會指向調用它的對象本身:
對于構造器調用模式,前面有介紹過構造函數內部的執行步驟:
1.隱式創建一個this空對象
2.執行構造函數中的代碼(為當前this對象添加屬性)
3.隱式返回當前this對象
所以,用new方式調用一個函數時,它的this是指向構造函數內部隱式獨立創建的this對象,所有通過this添加的屬性或方法最終都會添加到這個空對象中并返回給構造函數的實例。
對于call/apply調用模式,函數里的this會綁定到你傳入的第一個參數,如圖所示:
foo.apply()和foo.call()在改變this指向的功能是一樣的,區別只在于第二個參數開始傳的是數組格式的參數還是分散開來的參數格式。
關于“JavaScript的底層知識點有哪些”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。