zoukankan      html  css  js  c++  java
  • 前端筑基篇(一)->ajax跨域原理以及解决方案

    跨域主要是由于浏览器的“同源策略”引起,分为多种类型,本文主要探讨Ajax请求跨域问题

    前言

    强烈推荐阅读参考来源中的文章,能够快速帮助了解跨域的原理

    参考来源

    本文参考了以下来源

    什么是跨域

    为了更了解跨域的原理,可以阅读参考来源中的文章,里面对跨域的原理讲解很详细到位

    ajax跨域的表现

    ajax请求时,如果存在跨域现象,并且没有进行解决,会有如下表现

    • 第一种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且The response had HTTP status code 404 ,如图

      出现这种情况的原因如下

      • 本次ajax请求是“非简单请求”,所以请求前会发送一次预检请求(OPTIONS)
      • 服务器端后台接口没有允许OPTIONS请求,导致无法找到对应接口地址
    • 第二种现象:No 'Access-Control-Allow-Origin' header is present on the requested resourceThe response had HTTP status code 405 ,如图

      这种现象和第一种有区别,这种情况下,后台方法允许OPTIONS请求,但是一些配置文件中(如安全配置),阻止了OPTIONS请求,才会导致这个现象

    • 第三种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,如图

      这种现象和第一种和第二种有区别,这种情况下,服务器端后台允许OPTIONS请求,并且接口也允许OPTIONS请求,但是头部匹配时出现不匹配现象 比如origin头部检查不匹配,比如少了一些头部的支持(如常见的X-Requested-With头部),然后服务端就会将response返回给前端,前端检测到这个后就触发XHR.onerror,导致前端控制台报错

    • 注意,一般进行跨域分析的请求都是在ajax请求出现跨域情况的,而且用普通的http请求不会出现问题的

    跨域的原理

    之所以ajax出现请求跨域错误问题,主要原因就是因为浏览器的“同源策略”,可以参考 浏览器同源政策及其规避方法(阮一峰)

    如何解决跨域问题

    一般ajax跨域解决就是通过JSONP解决或者CROS解决,如以下(注意,ajax跨域只是属于浏览器"同源策略"中的一部分,其它的还有Cookie跨域iframe跨域,LocalStorage跨域等这里不做介绍)

    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

    • 可以总结为如图所示(去除干扰因素)

     
     
  • 相关阅读:
    Using a custom AxisRenderer object
    进度条
    flex 自定义tooltip
    深入理解JAVA虚拟机 垃圾收集器和内存分配策略
    深入理解JAVA虚拟机 自动内存管理机制
    oracle pl/sql 程序设计 历史笔记整理
    oracle sql 高级编程 历史笔记整理
    JAVA并发编程的艺术 Java并发容器和框架
    JAVA并发编程的艺术 JMM内存模型
    Java并发编程实战 第16章 Java内存模型
  • 原文地址:https://www.cnblogs.com/libin-1/p/5972211.html
Copyright © 2011-2022 走看看