前端築基篇(一)->ajax跨域原理以及解決方案

跨域主要是因爲瀏覽器的「同源策略」引發,分爲多種類型,本文主要探討Ajax請求跨域問題javascript

前言

強烈推薦閱讀參考來源中的文章,可以快速幫助瞭解跨域的原理html

參考來源

本文參考瞭如下來源前端

什麼是跨域

爲了更瞭解跨域的原理,能夠閱讀參考來源中的文章,裏面對跨域的原理講解很詳細到位java

ajax跨域的表現

ajax請求時,若是存在跨域現象,而且沒有進行解決,會有以下表現git

  • 第一種現象:No 'Access-Control-Allow-Origin' header is present on the requested resource,而且The response had HTTP status code 404 ,如圖

    出現這種狀況的緣由以下github

    • 本次ajax請求是「非簡單請求」,因此請求前會發送一次預檢請求(OPTIONS)
    • 服務器端後臺接口沒有容許OPTIONS請求,致使沒法找到對應接口地址
  • 第二種現象:No 'Access-Control-Allow-Origin' header is present on the requested resourceThe response had HTTP status code 405 ,如圖

    這種現象和第一種有區別,這種狀況下,後臺方法容許OPTIONS請求,可是一些配置文件中(如安全配置),阻止了OPTIONS請求,纔會致使這個現象web

  • 第三種現象:No 'Access-Control-Allow-Origin' header is present on the requested resource,如圖

    這種現象和第一種和第二種有區別,這種狀況下,服務器端後臺容許OPTIONS請求,而且接口也容許OPTIONS請求,可是頭部匹配時出現不匹配現象 好比origin頭部檢查不匹配,好比少了一些頭部的支持(如常見的X-Requested-With頭部),而後服務端就會將response返回給前端,前端檢測到這個後就觸發XHR.onerror,致使前端控制檯報錯ajax

  • 注意,通常進行跨域分析的請求都是在ajax請求出現跨域狀況的,並且用普通的http請求不會出現問題的

跨域的原理

之因此ajax出現請求跨域錯誤問題,主要緣由就是由於瀏覽器的「同源策略」,能夠參考 瀏覽器同源政策及其規避方法(阮一峯)chrome

如何解決跨域問題

通常ajax跨域解決就是經過JSONP解決或者CROS解決,如如下(注意,ajax跨域只是屬於瀏覽器"同源策略"中的一部分,其它的還有Cookie跨域iframe跨域,LocalStorage跨域等這裏不作介紹)json

JSONP方式解決跨域問題

jsonp解決跨域問題是一個比較古老的方案(實際中不推薦使用),這裏作簡單介紹

實際項目中若是要使用JSONP,通常會使用JQ等對JSONP進行了封裝的類庫來進行ajax請求

實現原理

JSONP之因此可以用來解決跨域方案,主要是由於 <script> 腳本擁有跨域能力,而JSONP正是利用這一點來實現。具體原理如圖

實現流程

JSONP的實現步驟大體以下(參考了來源中的文章)

  • 客戶端網頁網頁經過添加一個<script>元素,向服務器請求JSON數據,這種作法不受同源政策限制
    function addScriptTag(src) {
      var script = document.createElement('script');
      script.setAttribute("type","text/javascript");
      script.src = src;
      document.body.appendChild(script);
    }
    
    window.onload = function () {
      addScriptTag('http://example.com/ip?callback=foo');
    }
    
    function foo(data) {
      console.log('response data: ' + JSON.stringify(data));
    };						
    					

    請求時,接口地址是做爲構建出的腳本標籤的src的,這樣,當腳本標籤構建出來時,最終的src是接口返回的內容

  • 服務端對應的接口在返回參數外面添加函數包裹層
    foo({
      "test": "testData"
    });						
    					
  • 因爲<script>元素請求的腳本,直接做爲代碼運行。這時,只要瀏覽器定義了foo函數,該函數就會當即調用。做爲參數的JSON數據被視爲JavaScript對象,而不是字符串,所以避免了使用JSON.parse的步驟。

    注意,通常的JSONP接口和普通接口返回數據是有區別的,因此接口若是要作JSONO兼容,須要進行判斷是否有對應callback關鍵字參數,若是有則是JSONP請求,返回JSONP數據,不然返回普通數據

使用注意

基於JSONP的實現原理,因此JSONP只能是「GET」請求,不能進行較爲複雜的POST和其它請求,因此遇到那種狀況,就得參考下面的CROS解決跨域了

CROS解決跨域問題

CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。它容許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。

基本上目前全部的瀏覽器都實現了CORS標準,其實目前幾乎全部的瀏覽器ajax請求都是基於CORS機制的,只不過可能平時前端開發人員並不關心而已(因此說其實如今CROS解決方案主要是考慮後臺該如何實現的問題)。

強烈推薦閱讀 跨域資源共享 CORS 詳解(阮一峯)

CROS請求原理

實現原理以下圖(簡化版本)

  • 如何判斷是不是簡單請求?

    瀏覽器將CORS請求分紅兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。只要同時知足如下兩大條件,就屬於簡單請求。

    • 請求方法是如下三種方法之一:

      HEAD,GET,POST

    • HTTP的頭信息不超出如下幾種字段:

      Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain

PHP後臺配置

PHP後臺得配置幾乎是全部後臺中最爲簡單的,遵循以下步驟便可

  • 第一步:配置Php 後臺容許跨域

    主要爲跨域CROS配置的兩大基本信息,Origin和headers

  • 第二步:配置Apache web服務器跨域

    通常每個web服務器安裝好都得配置下跨域信息

    以上這張圖片是在某一個網站上看到的,直接用來。可是具體的源地址已經找不到了...

JAVA後臺配置

JAVA後臺相對來講配置也比較簡單,只須要遵循以下步驟便可

  • 第一步:獲取依賴jar包

    下載 cors-filter-1.7.jarjava-property-utils-1.9.jar 這兩個庫文件放到lib目錄下。(放到對應項目的webcontent/WEB-INF/lib/下)

  • 第二步:若是項目用了Maven構建的,請添加以下依賴到pom.xml中:(非maven請忽視)
    <dependency>
    	<groupId>com.thetransactioncompany</groupId>
    	<artifactId>cors-filter</artifactId>
    	<version>[ version ]</version>
    </dependency>
    						
    					

    其中版本應該是最新的穩定版本,CROS過濾器

  • 第三步:添加CROS配置到項目的Web.xml中( App/WEB-INF/web.xml)
    <!-- 跨域配置-->	
    <filter>
    		<!-- The CORS filter with parameters -->
    		<filter-name>CORS</filter-name>
    		<filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
    		
    		<!-- Note: All parameters are options, if omitted the CORS 
    		     Filter will fall back to the respective default values.
    		  -->
    		<init-param>
    			<param-name>cors.allowGenericHttpRequests</param-name>
    			<param-value>true</param-value>
    		</init-param>
    		
    		<init-param>
    			<param-name>cors.allowOrigin</param-name>
    			<param-value>*</param-value>
    		</init-param>
    		
    		<init-param>
    			<param-name>cors.allowSubdomains</param-name>
    			<param-value>false</param-value>
    		</init-param>
    		
    		<init-param>
    			<param-name>cors.supportedMethods</param-name>
    			<param-value>GET, HEAD, POST, OPTIONS</param-value>
    		</init-param>
    		
    		<init-param>
    			<param-name>cors.supportedHeaders</param-name>
    			<param-value>Accept, Origin, X-Requested-With, Content-Type, Last-Modified</param-value>
    		</init-param>
    		
    		<init-param>
    			<param-name>cors.exposedHeaders</param-name>
    			<!--這裏能夠添加一些本身的暴露Headers   -->
    			<param-value>X-Test-1, X-Test-2</param-value>
    		</init-param>
    		
    		<init-param>
    			<param-name>cors.supportsCredentials</param-name>
    			<param-value>true</param-value>
    		</init-param>
    		
    		<init-param>
    			<param-name>cors.maxAge</param-name>
    			<param-value>3600</param-value>
    		</init-param>
    
    	</filter>
    
    	<filter-mapping>
    		<!-- CORS Filter mapping -->
    		<filter-name>CORS</filter-name>
    		<url-pattern>/*</url-pattern>
    	</filter-mapping>
    						
    					

    請注意,以上配置文件請放到web.xml的前面,做爲第一個filter存在(能夠有多個filter的)

NET後臺配置

.NET後臺配置相比前面二者,複雜一點,能夠參考以下步驟

  • 第一步:網站配置

    打開控制面板,選擇管理工具,選擇iis;右鍵單擊本身的網站,選擇瀏覽;打開網站所在目錄,用記事本打開web.config文件添加下述配置信息,重啓網站

    請注意,以上截圖較老,若是配置仍然出問題,能夠考慮增長更多的headers容許,好比

    "Access-Control-Allow-Headers":"X-Requested-With,Content-Type,Accept"							
    						

    或者添加更多的自定義頭部

  • 第二步:其它更多配置

    若是第一步進行了後,仍然有跨域問題,多是由於後臺的接口沒有運行OPTIONS請求的緣由,請將[System.Web.Mvc.HttpPost]去掉,讓接口支持更多請求

FAQ

multi value '*,*' 的問題

  • 出現緣由

    這個問題出現的緣由是因爲後臺響應的http頭部信息有兩個Access-Control-Allow-Origin:*。常見於.net後臺(通常在web.config中配置了一次origin,而後代碼中又手動添加了一次origin)

  • 表現現象

    如圖

  • 解決方法

    將代碼中手動添加的Origin:*去掉(注意,若是去除config中的配置,會致使跨域問題,只有去除代碼中本身加上的才行...)

 

 

說明

介紹JavaScript數據類型

目錄

前言

參考來源

前人栽樹,後臺乘涼,本文參考瞭如下來源

前置技術要求

閱讀本文前,建議先閱讀如下文章

JavaScript的6種數據類型

var 變量 = 值; //其中只有兩種類型,一種是基本類型(相似於常量數據),一種是引用類型(對象)					
			

首先,咱們要明確一點JavaScript的數據類型即爲值的數據類型。JavaScript中有6種數據類型(5種基本數據類型,1種引用類型)

哪6種數據類型

  • 五種基本數據類型(其實也統稱爲基本型或原始型)

    undefined,null,number,boolean,string

  • 一種複雜數據類型(引用型)

    Object

undefined 類型

undefined型只有一個值,即特殊的undefined。使用var聲明變量但未對其加以初始化時,這個變量的值就就是undefined。例如

var a;
console.log(a===undefined);//true
			

null 類型

null型也只有一個值,即null,從邏輯角度來看,null值表示一個空指針(這也是 使用typeof操做符檢測返回object的緣由)。若是定義的變量準備在未來用於保存對象,那麼最好將該變量初始化爲null而不是其它值。這樣只要直接檢測null值就能夠知道相應的變量是否已經保存了一個對象的引用了。

var a = null;
console.log(typeof a);//"object"
			

實際上,ECMAScript中,能夠認爲undefined值是派生自null值的,因此undefined==null可是undefined!==null

console.log(undefined==null);//true
console.log(undefined===null);//false
			

注意,必定要區分undefined和null的使用。通常來講,變量的值初始設置爲undefined(記得別顯示設置,由解釋器默認設置便可)。而引用型對象的初始值通常能夠顯式的設置爲null。或者能夠這樣理解undefined做爲基本數據類型的變量的初始值(默認設置),null做爲引用類型的變量的初始值(顯式設置)

boolean 類型

boolean型只有兩個字面值true,false。可是這兩個值和數字值不是一回事,所以true不必定等於1,而false也不必定等於0。

要將一個值轉爲boolean型等價的值,有兩種方案:

  • 一種是顯式轉換-調用類型轉換函數Boolean()
  • 一種是自動轉換-如if(value)中的value就會自動轉化成爲boolean值

各種型與boolean型之間值得轉換關係以下表

數據類型 轉換爲true的值 轉換爲false的值
boolean true false
string 任何非空字符串 "" (空字符串)
bumber 任何非零數字值(包括無窮大) 0和NaN
undefined undefined
null null
Object 任何對象

number 類型

number類型用來表示整型和浮點數字,還有一種特殊的數值(NaN-not a number,這個數值用於表示一個原本要返回數值的操做數未返回數值得狀況-防止拋出錯誤)。

好比在其它語言中數值÷0都會致使錯誤,中止運行,可是在JS中。0/0、NaN/0會返回NaN,其它數字/0會返回Infinity,不會報錯。

任何涉及與NaN的操做都會返回NaN,JS有一個isNaN()函數,能夠判斷接收的參數是否爲NaN,或者參數轉化爲數字後是否爲NaN

console.log(NaN + 1); //NaN,任何涉及到NaN的操做都會返回NaN
console.log(NaN === NaN); //false,NaN與任何值都不相等,包括NaN自己
console.log(isNaN(NaN)); //true,是NaN
console.log(isNaN('10')); //false,被轉爲數字10
console.log(isNaN(true)); //false,被轉爲數字1
console.log(isNaN(null)); //false,被轉爲數字0
console.log(isNaN(undefined)); //true,返回NaN
console.log(isNaN('hello')); //true,沒法轉換爲數字
console.log(0/0);//NaN,0/0返回NaN
console.log(NaN/0);//NaN,NaN/0返回NaN
console.log(1/0);//Infinity,其它數字/0返回Infinity
console.log('1'/0);//Infinity,'1'成功轉爲數字
console.log('1a'/0);//NaN,'1a'轉爲數字失敗,變爲NaN
console.log(Infinity/0);//Infinity,其它數字/0返回Infinity
			

注意:Infinity的類型是Number(不是基礎數據類型)

有兩種方法能夠將非number類型的值轉換爲number類型

  • 一種是隱式轉換,如進行(*、/)操做時,會自動其他類型的值轉爲number類型
    console.log("1"*2);//12
    console.log("1"/2);//0.5
    console.log("1a"/2);//NaN
    					
  • 一種是顯示轉換-調用Number()、parseInt()、parseFloat()方法轉換

    Number()函數的轉換規則以下:(引自參考來源)

    • 若是是boolean值,true和false將分別被替換爲1和0
    • 若是是數字值,只是簡單的傳入和返回
    • 若是是null值,返回0
    • 若是是undefined,返回NaN
    • 若是是字符串,遵循下列規則:
      • 若是字符串中只包含數字,則將其轉換爲十進制數值,即」1「會變成1,」123「會變成123,而」011「會變成11(前導的0被忽略)
      • 若是字符串中包含有效的浮點格式,如」1.1「,則將其轉換爲對應的浮點數(一樣,也會忽略前導0)
      • 若是字符串中包含有效的十六進制格式,例如」0xf「,則將其轉換爲相同大小的十進制整數值
      • 若是字符串是空的,則將其轉換爲0
      • 若是字符串中包含除了上述格式以外的字符,則將其轉換爲NaN
    • 若是是對象,則調用對象的valueOf()方法,而後依照前面的規則轉換返回的值。若是轉換的結果是NaN,則調用對象的toString()方法,而後再依次按照前面的規則轉換返回的字符串值。
      console.log(Number(''));//0
      console.log(Number('a'));//NaN
      console.log(Number(true));//1
      console.log(Number('001'));//1
      console.log(Number('001.1'));//1.1
      console.log(Number('0xf'));//15
      console.log(Number('000xf'));//NaN
      var a = {};
      console.log(Number(a));//NaN
      a.toString = function(){return 2};
      console.log(Number(a));//2
      a.valueOf = function(){return 1};
      console.log(Number(a));//1								
      							

    parseInt()經常用於將其它類型值轉化爲整形。parseInt轉換與Number()有區別,具體規則以下

    • parseInt(value,radius)有兩個參數,第一個參數是須要轉換的值,第二個參數是轉換進制(該值介於 2 ~ 36 之間。若是該參數小於 2 或者大於 36,則 parseInt() 將返回 NaN。),若是不傳(或值爲0),默認以10爲基數(若是value以 「0x」 或 「0X」 開頭,將以 16 爲基數)
    • 注意在第二個參數默認的狀況下,若是須要轉換的string值以0開頭,如'070',有一些環境中,會自動轉化爲8進制56,有一些環境中會自動轉化爲10進制70。因此爲了統一效果,咱們在轉換爲10進制時,會將第二個參數傳10
    • parseInt轉換示例
      console.log(parseInt(''));//NaN
      console.log(parseInt('a'));//NaN
      console.log(parseInt('1234blue'));//1234
      console.log(parseInt(true));//NaN
      console.log(parseInt('070'));//70,可是有一些環境中會自動轉換爲8進制56
      console.log(parseInt('070',8));//56
      console.log(parseInt('001.1'));//1
      console.log(parseInt('0xf'));//15,16進制
      console.log(parseInt('AF',16));//175,16進制
      console.log(parseInt('AF'));//NaN
      console.log(parseInt('000xf'));//0
      var a = {};
      console.log(parseInt(a));//NaN
      a.toString = function(){return 2};
      console.log(parseInt(a));//2
      a.valueOf = function(){return 1};
      console.log(parseInt(a));//2							
      							

    parseFloat()轉換規則基本與parseInt()一致,只有以下不一樣點

    • parseFloat()遇到浮動數據時,浮點有效(可是隻有第一個.有效),如"10.1"會被轉爲10.1;'10.1.1'會被轉爲10.1
    • parseFloat()只會默認處理爲10進制,並且會忽略字符串前面的0,因此不會有在默認狀況下轉爲8進制的狀況
    • 示例
      console.log(parseFloat('1234blue'));//1234
      console.log(parseFloat('1234blue',2));//1234
      console.log(parseFloat('0xA'));//0
      console.log(parseFloat('10.1'));//10.1
      console.log(parseFloat('10.1.1'));//10.1
      console.log(parseFloat('010'));//10								
      							

    因爲Number()函數在轉換字符串時比較複雜並且不夠合理,所以在處理整數的時候更經常使用的是parseInt()函數-需注意最好第二個參數傳10,處理浮點數時更經常使用parseFloat()

    另外注意,浮點數直接的計算存在偏差,因此兩個浮點數沒法用"="進行判斷

    var a=10.2;
    var b= 10.1;
    console.log(a - b === 0.1);//false
    console.log(a - 10.1 === 0.1);//false,實際是0.09999999999999964
    console.log(a - 0.1 === 10.1);//true						
    					

string 類型

string類型用於表示由零或多個16位Unicode字符組成的字符序列,即字符串。字符串能夠由單引號(')或雙引號(")表示。任何字符串的長度均可以經過訪問其length屬性取得。

要把一個值轉換爲一個字符串有三種方式。

  • 第一種是使用幾乎每一個值都有的toString()方法(除去null和undefined沒有)
    • toString(radius)有一個參數-基數,當須要toString的值爲number時,參數能夠生效(能夠轉換爲對應進制輸出,如10.toString(8)輸出爲12)
  • 第二種是隱式轉換,好比字符串+ number(null,undefined,object等),會默認轉爲字符串(若是後面相加的是object對象,會返回object對象的toString或valueOf值)
  • 第三種是經過轉換函數String(),String()轉換規則以下
    • 若是值有toString()方法,則調用該方法(沒有參數)並返回相應的結果(注意,valueOf()方法沒用)
    • 若是值是null,則返回"null"
    • 若是值是undefined,則返回」undefined「
  • 示例
    var a = 10;
    var b = '10'
    var c = {};
    console.log(a.toString());//10
    console.log(a.toString(8));//12
    console.log(b.toString(8));//10,字符串基數沒用
    console.log(String(c));//[object Object]
    console.log(c);//[object Object]
    console.log(c + '1');//[object Object]1
    			
    c.valueOf = function(){return 2};
    console.log(String(c));//[object Object]
    console.log(c);//[object Object],valueOf沒用
    console.log(c + '1');//21,隱式轉換時,valueOf起做用了
    			
    c.toString = function(){return 2};
    console.log(String(c));//2
    console.log(c);//2,toString起做用了
    			
    console.log(String(null));//"null",null和undefined能夠String()輸出
    console.log(null.toString());//報錯,null和undefined不能toString						
    					

複雜 類型

複雜 類型即引用型,也就是咱們常說的JS對象(包括普通的object型對象和function型對象)

對象其實就是一組數據和功能的集合。對象能夠經過執行new操做符後跟要建立的對象類型的名稱來建立。而建立Object的實例併爲其添加屬性和(或)方法,就能夠建立自定義對象。如

var o = new Object();//建立一個新的自定義對象{}			
			

也就是說,除去基本類型,剩餘的就是引用型(包括內置對象,自定義對象等)都是基於Object進行拓展的

Object的每一個實例都具備下列屬性和方法:

  • constructor——保存着用於建立當前對象的函數
  • hasOwnProperty(propertyName)——用於檢查給定的屬性在當前對象實例中(而不是在實例的原型中)是否存在。其中,做爲參數的屬性名(propertyName)必須以字符串形式指定(例如:o.hasOwnProperty("name"))
  • isPrototypeOf(object)——用於檢查傳入的對象是不是另外一個對象的原型
  • propertyIsEnumerable(propertyName)——用於檢查給定的屬性是否可以使用for-in語句來枚舉
  • toString()——返回對象的字符串表示
  • valueOf()——返回對象的字符串、數值或布爾值表示。一般與toString()方法的返回值相同。

參考 JS原型和原型鏈的理解

基本型和引用型的不一樣

基本型和引用型最大的不一樣就是二者的存儲方式不一樣,如圖:

  • 也就是說,上圖中,若是變量1的值變爲102,實際中棧內存中的101是不會變的,只是在棧內存中新開闢出一處,用來存放102這個常量。而後將變量1指向102。

  • 而變量2因爲棧內存中存放的是指針,實際執行的是堆內存中的數據,因此變量2的值是能夠隨便改的(堆內存中的數據能夠更改)

關於數據類型的一些常見疑問

爲何typeof null === 'object'

這個問題有不少人提出過,由於按理說,null做爲JS的五大基本數據類型之一,那麼typeof null 爲和會===object呢?這與ECMAScript的歷史緣由有關。緣由以下:

  • JS中的五大基本數據類型,除了null外,其他的類型存放在棧區的都是常量值(如undefined存放的是undefined值,number類型能夠存放0,1,2...等等)
  • 與其他四種類型相同,null的值也是存放在棧區的,並且也只有一個值null。而恰巧從邏輯上認爲這是一個空對象的指針(機器碼NULL空指針),因此typeof時會返回object。 (具體緣由以下,引自知乎同名回答)
    • JS類型值是存在32 BIT 單元裏,32位有1-3位表示TYPE TAG,其它位表示真實值
    • 而表示object的標記位正好是低三位都是0 (000: object. The data is a reference to an object.)
    • 而js 裏的null 是機器碼NULL空指針, (0x00 is most platforms).因此空指針引用 加上 對象標記仍是0,最終體現的類型仍是object..
    • 這也就是爲何Number(null)===0吧...
  • 曾有提案嘗試修復typeof === 'null',可是被拒絕了(如在V8引擎中會致使大量問題)

string,String,object,Object,function,Function的關係

請區分Object,Function,String與object,function,string。

  • Object,Fucntion,String是JS內置對象(都是引用型),object,function,string是typeof檢查類型後的返回值。
  • 通常狀況下,咱們把後面的object,undefined,function等稱之爲對應值的類型(null用typeof 沒法識別的,另外函數對象返回function)。
  • 因此到了這一步,應該是全部的引用類型typeof都返回object的。可是在引用類型中,有一個比較特殊的類型"fucntion"。它的出現形成了引用類型中函數對象typeof返回'function'。

    具體參考: function類型與object類型的區別

  • 如今又回到了最初的數據類型劃分的時候了
    • JS中的基本數據類型有五種:undefined,null,number,boolean,string
    • JS中的引用類型中包含兩種:object、function(fucntion類型是Function對象typeof檢測後的返回值,Function對象是基於Object拓展的)
    • JS中有一些內置對象:Object,Function,String。這些對象用typeof返回值都是function。可是new Object()出來的新對象的typeof返回值就是object(除了new Function()返回function之外)
    • 因此其實這些類型名稱都是不一樣人本身定義出來的,不要被他們限制。

      好比有的人會說JS中有7中類型:5中基本數據類型和object與function(但其實咱們這這裏就將後面兩種之前算成引用型了)

      或者用一句話總結更爲合適:"JS中有對象,每個對象都有一個本身的類型"。就比如每個動物都有屬於本身的類型同樣(人類,猴子...)。另外基本類型能夠認爲是一個不會改變的對象(便於理解)

      至於爲何基本類型明明不是引用型,卻能像引用型同樣去使用一些基本數據操做(如toFixed,toString等)。請參考 基本數據類型爲何可以使用toString等操做

關於String類型與string類型的疑問

JavaScript中,基本類型有string,number等等,複雜類型中也拓展有String,Number等等。那麼這二者的區別是什麼呢?以下圖,簡單描述了基本類型中的string與複雜類型中的String的區別。

也就是說,string類型都是放在棧內存中的(相似於常量),若是string類型的變量的值改成另外一個string,那麼棧內存中原有的string並無變化,只不過是在棧內存中新開闢出一個string,而後改變變量的引用而已

而Strign的型的棧內存中只有指針,指向堆內存的數據。因此若是值進行了改變,是會直接修改堆內存中的數據內容,而棧內存中的指針並不會變。

function類型與object類型的區別

這個一個知識點也是不少人疑惑的地方,明明只有一種複雜對象Object,但爲何一些函數類型的typeof 返回function,其它對象返回 object呢?

  • 簡單點能夠這樣理解:Object,Function,String等都是JavaScript內置的函數對象,typeof是獲取函數對象的類型,因此返回fucntion。而new Object()、new String()等是構造出一個新的對象,因此typeof返回object。而new Fucntion()構造處理的仍然是一個函數對象,因此返回fucntion

    Function是最頂層的構造器。它構造了系統中全部的對象,包括用戶自定義對象,系統內置對象,甚至包括它自已。

  • 關於Object和Function能夠這樣理解

    null是天地之始,而後null生了Object,Object是萬物之母。而後Object有一個屬性constructor,恰巧能夠返回function的值,因此typeof Object爲function。而後Function是基於Object派生的。Function.prototype._proto_指向Object.prototype。(Function.constructor也返回function值)

  • 若是要深刻理解,須要對JS中的原型和原型鏈有必定了解

    參考 JS原型和原型鏈的理解

  • 示例
    			function a(){};
    			var b = function(){};
    			var c = new Function();
    			var d = new Object();
    			var e = new String();
    			var f = new Date();
    			console.log(typeof a);//function
    			console.log(typeof b);//function
    			console.log(typeof c);//function
    			console.log(typeof Function);//function
    			console.log(typeof Object);//function
    			console.log(typeof d);//object
    			console.log(typeof String);//function
    			console.log(typeof e);//object
    			console.log(typeof Date);//function
    			console.log(typeof f);//object	
    			
    			console.log(Object instanceof Function);//true
    			console.log(Function instanceof Object);//true
    			console.log(new Object() instanceof Object);//true
    			console.log(new Object() instanceof Function);//false
    			console.log(new Function() instanceof Function);//true
    			console.log(new Function() instanceof Object);//true
    			
    			function Foo(){};
    			var foo = new Foo();
    			console.log(foo instanceof Foo);//true
    			console.log(foo instanceof Function);//false
    			console.log(foo instanceof Object);//true
    			console.log(Foo instanceof Function);//true
    			console.log(Foo instanceof Object);//true
    					

==和===的區別

==和===在JS中都有比較的意思,可是二者有着很大的不一樣,二者區別以下:

  • 對於string,number,boolean等基礎簡單類型而言,==和===是有區別的

    由於不一樣類型的值比較,==會將比較值轉換爲同一類型的值後 在看值是否相等。===的話會先判斷類型,若是類型不一樣,結果就是不等。

  • 對於引用類型而言,==和===是沒有區別的

    由於這類值得比較都是「指針地址」比較,不一樣的值,確定爲false

  • 對於基礎類型和引用類型比較而言,==和===是有區別的

    對於==會將複雜類型轉換爲基礎類型,進行值比較,對於===,因爲類型不一樣,直接爲false

  • 示例
    var a = 1;
    var b = true;
    console.log(a == b); //true,轉換爲同一類型後值相等
    console.log(a === b); //false,先比較類型不能,直接爲false
    
    var a = {
    	'test': '1'
    };
    var b = {
    	'test': '1'
    };
    console.log(a == b); //false,比較指針地址,不等
    console.log(a === b); //false,比較指針地址,不等
    
    var a = '11';
    var b = new String('11');
    console.log(a == b); //true,將高級類型String轉化爲基礎類型,值相等
    console.log(a === b); //false,由於類型不一樣,直接爲false						
    					

typeof的做用

JS的變量的值是鬆散類型的(弱類型),能夠保存任何類型的數據,JS內置的typeof能夠檢查給定變量的數據類型,可能的返回值以下:

  • undefined:undefined類型
  • boolean:boolean類型
  • string:string類型
  • number:number類型
  • object:null類型或者其它的引用型(object,去除function)
  • function:這個值是函數對象,引用類型中的特殊類型(能夠認爲引用型中除去了function就是object)
console.log(typeof 'test'); //'string'
console.log(typeof 101); //'number'
console.log(typeof true); //'boolean'
console.log(typeof undefined); //'undefined'
console.log(typeof null); //'object'
console.log(typeof function() {}); //'function'
console.log(typeof {}); //object
console.log(typeof new Date()); //object				
			

instanceof的做用

instanceof用於判斷一個變量是不是某個對象的實例,主要是判斷某個構造函數的prototype屬性是否存在另外一個要檢查對象的原型鏈上。

  • Instanceof能夠判斷內置的對象類型(基於Obejct對象拓展的,如Array,Date等)。
  • 能夠判斷自定義對象類型,以下述中的Child和Parent
  • 可是不能判斷簡單類型(由於本質是經過原型來判斷,可是簡單類型只是一個常量,並非引用對象)
			//識別內置對象 - Array, Date等
			console.log([] instanceof Array); //true
			console.log(new String('11') instanceof String); //true
			console.log('11'
				instanceof String); //false,由於11是簡單類型
			//識別自定義對象類型以及父子類型
			function Parent(x) {
				this.x = x;
			}

			function Child(x, y) {
				Parent.call(this, x);
				this.y = y;
			}
			//將Child的原型指向Parent,代表繼承關係,此時Child的構造變爲了Parent的構造
			Child.prototype = new Parent();
			//而後將構造函數換爲Child本身的
			Child.prototype.constructor = Child;
			console.log(Child.prototype.constructor); //輸出構造函數是Child本身的
			var person = new Child(1, 2);
			console.log(person.x + ',' + person.y); //1,2
			console.log(person instanceof Child); //true
			console.log(person instanceof Parent); //true

			//不能識別簡單類型,由於instanceof後面只能是基於Object對象拓展的類型
			console.log(101 instanceof number); //報錯,number is not defined				
			

Object.prototype.toString的做用

Object.prototype.toString的存在主要是爲了解決typeof和instanceof的不足,好比typeof沒法識別內置類型(Array Date等),而instanceof沒法識別簡單類型。因此纔有了這個。

Object.prototype.toString能夠識別5種簡單類型,以及所有內置類型(Array.Date等一些內置類型),可是沒法識別自定義對象類型

/**
			 * @description 經過Object.prototype.toString來判斷傳入對象類別
			 * @param {Object} obj
			 */
			function type(obj) {
				//slice的做用,例如原本返回[object number],slice篩選出number
				return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
			}
			console.log(type(1)); //number
			console.log(type('1')); //string
			console.log(type(true)); //boolean
			console.log(type(undefined)); //undefined
			console.log(type(null)); //null
			console.log(type(new Date())); //date
			console.log(type([])); //array
			console.log(type({})); //object
			function Test(a) {
				this.a = a;
			}
			console.log(type(new Test('1'))); //object,自定義類別只能識別爲object				
			

基本數據類型爲何可以使用toString等操做

前面說到JS中有基本類型和引用類型(對象類型、複雜類型各類說法都行)。請注意二者是有本質區別的。

  • 基本類型的值進行toString操做時,並非用自身進行操做的,而是有相似與「裝箱」、「拆箱」的操做
    var a = 10.1;
    console.log(a.toFixed(2));//10.10,臨時構建了個Number對象進行操做,操做完後銷燬了
    			
    a.foo = 'test';
    console.log(a.foo); // undefined,由於a的值並非一個對象,沒法綁定屬性						
    					

    上述代碼中,對基本類型的a進行a.xx操做時,會在內部臨時建立一個對應的包裝類型(好比number類型對應Number類型)的臨時對象。並把對基本類型的操做代理到對這個臨時對象身上,使得對基本類型的屬性訪問看起來像對象同樣。可是在操做完成後,臨時對象就扔掉了,下次再訪問時,會從新創建臨時對象,固然對以前的臨時對象的修改都不會有效了。

 

 

 

JavaScript築基篇(三)->JS原型和原型鏈的理解

 

說明

JS中原型和原型鏈是很重要的知識點,本文內容則是我對於它的理解。建議讀本文時已經有了一點的JS基礎。

目錄

前言

參考來源

前人栽樹,後臺乘涼,本文參考瞭如下來源

前置技術要求

閱讀本文前,建議先閱讀如下文章

楔子

學習是有瓶頸的,JS學習也一樣,基本上JS的學習中,到了原型與原型鏈這一步,就會遇到瓶頸,只有真正理解它,才能跨過去,進入新的領域

曾經看過不少網上關於JS原型的介紹,基本上每完整的看完一篇,就會「噢,恍然大悟的樣子」。而後仔細想一想,發現本身並無真正的理解。因此這裏將本身對應原型的理解寫下來,但願能幫助他人快速理解原型。

起由

簡單的描述下,原型是什麼,如何誕生的。

null開天闢地

"無,名天地之始;有,名萬物之母" -引自 《道德經》

  • (無)在JS中,原本也是什麼都沒有的,只有一個 null
  • (道生一)而後出現了Object.prototype對象
  • (一輩子二)而後基於Object.prototype產生出了Function.prototype對象

    注意,JS中全部對象都不是函數構造出來的,對象是由"JavaScript運行時環境"以原型對象爲模板,直接產生出來的,構造函數只是以新生的對象爲this,作一些初始化操做。(-引自參考來源)

  • (二生三)而後基於兩個prototype,產生出了兩個構造器Object和Function。這時候出現了函數對象的概念,實例對象,原型對象的概念
    • Object,Function都屬於函數對象
    • new Object()、new Function()出來的是實例對象

      因此,後面咱們通常會認爲實例對象是由函數對象構造出來的

    • Object.prototype,Function.prototype是原型,原型對象的做用就是以它爲模板產生其它對象,它也和普通實例對象同樣,擁有constructor,__proto__屬性

      原型對象之因此要有constructor,__proto__屬性,能夠認爲是在產生實例對象時,方便實例對象指向具體的構造函數以及完成一個原型鏈的做用。

    參考 函數對象、實例對象用原型對象

    參考 constructor、__proto__與prototype

  • (三生萬物)而後基於前面的Object,Object.prototype,Function,Function.prototype,產生出了其它各類對象

    能夠先簡單的理解以上步驟爲JS對象的誕生過程(排除基本型的值,由於它們並不屬於對象-沒有對象的這些特性)

    固然,裏面具體Object.prototype、Object、Function.prototype這些內置對象的產生過程是很複雜的,上述只是爲了便於理解的一種簡單粗暴的概念。

來龍去脈

函數對象、實例對象與原型對象

再開始理解原型以前咱們得先明確一個概念:"函數對象","實例對象","原型對象"

  • 原型對象是對象的原型。原型對象有constructor,__proto__屬性

    能夠認爲全部對象都是原型產生的(萬物之母能夠認爲是Object.prototype)。

  • 函數對象有 prototype,__proto__屬性

    如JS內置的Object,Function對象都是函數對象。其中prototype的值就就是它對應的原型對象

  • 實例對象有 constructor,__proto__屬性

    如new Object(),new Function()出來的都是實例對象。其中constructor指向它的構造函數,__proto__指向產生它的原型

  • 關係如圖

constructor、__proto__與prototype

在咱們有了上述概念後,再來分析JS中的constructor,__proto__與prototype

複製代碼
            //Object,Function都是函數對象
            var 實例對象 = new 函數對象();    

            //Person也是函數對象
            function Person(name) {
                this.name = name;
                this.say = function() {
                    console.log(this.name);
                }
            };
            var one = new Person('test');
            
            console.log(Person.prototype);//{constructor:Person函數,__proto__:Object.prototype}
            console.log(Person.prototype.constructor === Person);//true
            console.log(Person.prototype.__proto__ === Object.prototype);//true
            console.log(Person.prototype.__proto__ === Function.prototype);//false
            console.log(Person.__proto__.__proto__ === Object.prototype);//true
            console.log(Person.__proto__.constructor === Function);//true
            console.log(Person.__proto__.constructor.prototype === Object.__proto__);//true
            console.log(Person.__proto__.constructor.__proto__ === Object.__proto__);//true
            console.log(Object.__proto__ === Person.__proto__);//true
            console.log(Person.__proto__.constructor.prototype === Person.__proto__);//true,至關於本身構建了本身...
            
            console.log(one.prototype); //undefined
            console.log(one.constructor === Person); //true
            console.log(one.__proto__ === Person.prototype); //true
        
            console.log(Object.prototype.constructor);//Object函數:function Object() { [native code] }
            
            console.log(Function.prototype.__proto__ === Object.prototype);//true
            console.log(Function.prototype.constructor === Function);//true
            
            
            console.log(Object.__proto__);//Function.prototype
            console.log(Function.prototype);//Function.prototype
            console.log(Function.__proto__);//Function.prototype
            console.log(Object.prototype.__proto__);//undefined
            
            console.log(Function.prototype === Function.__proto__);//true
            console.log(Object.__proto__ === Function.__proto__);//true
            console.log(Object.__proto__ === Function.prototype);//true
            console.log(Function.prototype.constructor === Function);//true
            console.log(Function.__proto__.constructor === Function);//true
            
            
            console.log(Object.__proto__ === undefined);//false
            console.log(Object.prototype.__proto__ === undefined);//true,原型鏈的盡頭爲null
            
            var two = {};
            console.log(two.constructor === Object);//true
            console.log(two.prototype);//undefined
            console.log(two.__proto__ === Object.prototype);//true				
複製代碼

如上代碼所示,有以下總結

  • 函數對象Person有一個prototype屬性,有一個__proto__屬性。prototype屬性的值是一個prototype對象。prototype有一個constructor屬性(爲了方便稱爲 $constructor),有一個__proto__屬性(爲了方便稱爲 $__proto__)。

    $constructor屬性的值是一個constructor對象。而這個constructor對象偏偏就是這個函數對象自己(Person自己)。

    $__proto__屬性的值是Object.prototype(至關於就是函數對象Object的prototype屬性)原型鏈就是基於__proto__字段不斷往上找,直到遇到null爲止

    __proto__屬性的值是Function.prototype,Function.prototype和其它原型對象同樣,有一個__proto__屬性和constructor屬性

    • __proto__屬性的值是Object.prototype
    • constructor的值是一個constructor對象。這個對象即爲Function
      • Function對象和Object對象同樣,也有它的prototype和__proto__。Function的prototype與__proto__的值都是Function.prototype

    注意,__proto__屬性的名稱並不規範,如Chrome中叫__proto__,但IE中不必定叫這個名字,可是咱們通常習慣把它叫成__proto__(但注意是__proto__並非_proto_)

  • 實例對象one沒有prototype屬性(因此one.prototype===undefined)。示例對象one有一個constructor屬性(爲了方便稱爲 $constructor),有一個__proto__屬性(爲了方便稱爲 $__proto__)。

    $constructor屬性的值是一個constructor對象。而這個constructor對象偏偏就是這個函數對象自己(Person自己)。

    $__proto__屬性的值是Person.prototype(至關於就是函數對象Person的prototype屬性)因此如今就構成了一個原型鏈 one.__proto__ ->Person.prototype;Person.prototype.__proto__->Object.prototype;Object.prototype.__proto__ ->null

  • 如上圖中,能夠清晰的看到Person函數對象的實例one是由Person構造的,因此one.constructor===Person。一樣,普通的object對象two是由Object構造的,因此two.constructor===Object

    由此能夠看出,Function對象 原型鏈上有Function.prototype和Object.prototype,因此 Function是Function類型的,也是Object類型的。另外Object對象的原型鏈上有Function.prototype和Object.prototype,因此Object是Function類型的,也是Object類型的

    從上,咱們還能夠獲得一個概念: "Object.prototype是全部對象的原始原型對象(全部對象的原型鏈最終都會指向它);Function.prototype是全部函數對象的原始原型對象(全部函數對象的原型鏈最終都會指向它),而Function.prototype的原型鏈指向Object.prototype"

    注意,Object,Function對象的產生是很複雜的,裏面甚至涉及到了本身構建本身,這裏只是簡化版本,詳情請參考MDN...

原型與原型鏈

區分原型對象與原型鏈

原型對象

  • 如前面提到的JS內置對象Object.prototype,Function.prototype等就是原型對象,原型對象的做用是能夠以它爲原型產生其它對象。每個原型對象都有一個隱藏的屬性(如在chrome中是__proto__,不一樣瀏覽器實現不一樣),這個屬性的值是另外一個原型對象

原型鏈

  • 原型鏈是一個概念
  • 每個JS的實例對象都有一個__ptoto__屬性,這個屬性指向產生它的原型對象,而後就像前面提到的,每個原型對象也有一個__proto__屬性,指向與產生它的原型
  • 就這樣,從實例->原型1->...->原始原型(Object.prototype)->null。這樣就組成了一條鏈,這個就是原型鏈
  • JavaScritp引擎在訪問對象的屬性時,若是在對象自己中沒有找到,則會去原型鏈中查找,若是找到,直接返回值,若是整個鏈都遍歷且沒有找到屬性,則返回undefined

原型鏈的做用

原型鏈的一個最大的做用就是,能夠基於它,實現JS中的繼承。

由於JS在ES6以前,是沒有Class這個概念的,只能經過原型鏈來進行實現。

原型鏈的流程與示例

原型鏈的原理就是基於__proto__屬性不斷的往上查找,下面介紹下一些原型鏈的用法示例

示例一

複製代碼
            var base = {
                name: 'base',
                say: function(){
                    console.log(this.name);
                }
            };
            var one = {
                name: 'one',
                __proto__:base
            };
            var two = {
                __proto__:base
            };
            console.log(one.name);//one
            one.say();//one
            console.log(two.name);//base
            two.say();//base        		
複製代碼

代碼分析:

  • 以上代碼中的base是由Object.prototype產生的,因此base.__proto__的值爲Object.prototype
  • one和two本來也是由Object.prototype產生的,因此原本__proto__也是指向Object.prototype的,可是這裏手動修改了這個指向,變爲指向base了
  • 因此就有了兩個原型鏈:(one -> base ->Object.prototype),(two -> base -> Object.prototype)
  • 而後根據原形鏈的規則,如今本對象上找屬性,沒有的話再根據原形鏈指向一層一層往上找,直到找到null返回undefined爲止。

    因此纔會有以上的輸出結果。one.name是one自身的屬性,one.say()是上一級原型鏈base的屬性,two.name,say()都是上一級base的屬性。

  • 能夠總結爲如圖所示(去除__ptoto__以外的干擾因素)

示例二

複製代碼
            function Base(name){
                this.sex = 0;
                this.name = name || 'base';
                this.hello = function(){
                    console.log("hello " + name);
                };
            }
            Base.prototype.say = function(){
                console.log('name:'+this.name);
            };
            function Extend(name,num){
                //讓Base能初始化屬性
                Base.call(this,name);
                this.num = num || 0;
            }
            //注意,這裏是new Base()而不是Base.prototype
            //由於new Base()是新建了一個對象,這樣能夠不會影響到Base.prototype
            //不然若是直接操做Base.prototype,會污染Base.prototype
            Extend.prototype = new Base();
            //前面因爲將prototype變爲了new Base()因此構造方法默認是Base的
            //這裏須要手動替換回來
            Extend.prototype.constructor = Extend;

            var one = new Extend('one',2);
            
            console.log(Extend.__proto__);
            console.log(one instanceof Extend);//true
            console.log(one instanceof Base);//true
            console.log(one.constructor === Extend);//true
            console.log(one.__proto__ === Extend.prototype);//true
            
            
            console.log(one.name);//one
            console.log(one.sex);//0
            console.log(one.num);//2
            one.say();//name:one
            one.hello();//hello one		
複製代碼

代碼分析:

  • 上述代碼在進行原型鏈修改前,有以下原型鏈

    一條鏈爲:new Extend() -> Extend.prototype ->Object.prototype -> null

    一條鏈爲: new Base() -> Base.prototype ->Object.prototype -> null

    一條鏈爲: Extend -> Function.prototype ->Object.prototype -> null

    一條鏈爲: Base -> Function.prototype ->Object.prototype -> null

  • 修改原型後,有了一條完整的繼承鏈(針對於實例對象而言,至關於上述的第一條拓展了)

    new Extend() -> Extend.prototype -> Base.prototype ->Object.prototype -> null

  • 而根據原形鏈,因此上述的代碼會有這些輸出

    one是Extend的實例對象,因此one自身找不到時,會沿着原型鏈往上找,知道原型鏈的盡頭都沒有找到,則返回null

  • 能夠總結爲如圖所示(去除干擾因素)

 
 
相關文章
相關標籤/搜索