• 从浏览器输入URL到页面加载完成到底发生了什么?


    这个问题已经有很多人回答过了,其中不乏一些大牛,接下来我作为一个草根来发表一些个人的看法:

    1、地址输入过程

    地址栏可以抽象成一个文本输入域对象,Chrome应用程序内一定给该对象绑定了键盘输入的回调,该回调将交给操作系统,操作系统会在用户按键时执行所有应用传递给它的回调队列,当操作系统监听到用户按下了回车键并且在回调队列遍历执行到Chrome浏览器传递给操作系统绑定的回调时,开始解析地址,Chrome会调用网络模块对地址进行解析,将其中的应用层协议、主机、端口、虚拟目录、文件名、参数、锚等各部分解析出来。

    2、DNS解析

    当发现主机部分不是以IP形式表示时,就会试图去寻找该主机的IP,在此说一些废话来解释下为什么要找IP,因为IP是网络中每台计算机的唯一标识,计算机采用IP作为唯一标识而没有采用域名作为唯一标识,个人感觉原因是因为计算机更擅长处理数字,而数字不易于被人类所记忆,因此诞生了DNS域名解析系统

    我们自己的PC和手机中其实都运行着DNS客户端,如果运行在用户主机上的某些应用程序(如Web浏览器或者邮件阅读器)需要将主机名转换为IP地址时,也就是我们现在的情况,这些应用程序将调用DNS的客户机端,并指明需要被转换的主机名。

    DNS客户端拿着这个主机名依次从本地hosts文件、本地DNS解析器缓存、本地DNS服务器、13台根DNS服务器、一级域名服务器、二级域名服务器...此外,DNS是应用层协议,所有DNS请求和回答报文使用的UDP数据报经过端口53发送。总之我们最终可以拿到目标主机的IP地址

    3、浏览器http数据包到客户端网卡

    内部应该是通过一个对象(类似于XMLHttpRequest对象)将要传递的数据进行封装,具体的数据包格式如下:

    调用系统函数socket,将该请求交给传输层,由于http是基于TCP来传输的,因此传输层协议是TCP协议,刚才封装好的http数据包将作为TCP段的数据部分,TCP段的头部封装了源端口和目标端口

    接下来TCP段会交给网络层,网络层使用IP协议,网络层将整个TCP段作为IP数据包的数据部分,IP数据包的头部封装了源主机IP和目标主机IP

    这个IP数据包接下来进入数据链路层,数据链路层将整个IP数据包作为数据帧数据部分,数据帧的头部封装了源主机MAC地址和下一跳路由的MAC地址,如果是第一跳,可以理解为网关的地址,源主机和各个路由器通常通过广播的方式学习到下一跳路由器的MAC地址

    封装好的数据帧通过物理层(网卡驱动)转换为二进制字节再进行传输

    4、客户端到服务器端

    电脑的网络适配器(也就是网线接口)可以连接交换器或路由器,然后再连调制解调器(猫)转变为模拟信号

    个人感觉模拟信号从电话线、光纤、网线出去之后在路由器之间做转发时在各个路由器节点中也被转变为了数字信号,然后分析处理完再转变为模拟信号再转发

    5、服务器接收到数据并将各层头部拆解再交给服务器应用程序处理

    服务器网卡接收到浏览器发来的二进制字符序列,网卡驱动程序分析其目标Mac地址,从而判断是否该接受该数据包,确定接收该包之后再交给传输层进一步取出端口信息,从而确定该包到底要转给应用层中哪个应用程序,在此,目标端口是80,如果服务器的80端口处于开启状态(监听状态)则将该数据包交给该服务器应用程序(Apache Nginx等)做进一步分析

    对于规模非常小的应用,这台主机很可能承担了所有角色(应用服务器、文件服务器、数据库服务器)

    稍微成点规模的应用都会在此布一台反向代理服务器,根据Request URL头和Remote Address头和一定的算法(例如轮寻)对请求进行转发,转发到多台服务器做负载均衡,这样一来这台做转发的服务器访问量就是转发到的那些负载均衡服务器的访问量的好多倍,这也就要求这个服务器能够承受很大的并发量,这种能够承受很大并发量的服务器的典型代表就是Nginx。代理服务器转发到的负载均衡服务器通常是应用服务器,所谓应用服务器就是我们的后台程序(Java PHP Node)所存放的服务器,后台程序访问数据库是常有的事,而数据库通常不会和应用服务器放在一起,执行这些高级语言编译成的机器指令必然对CPU和内存有着很高的要求,而数据库其实本质就是一种特殊的文件系统,因此数据库读写本质上就是对文件的读写,文件读写必然对机器的I/O接口(硬盘读写)性能有着很高的要求,可见应用服务器和数据库服务器有着不同的需求,因此术业有专攻,二者通常不会放在一起,同理应用中的图片、音频、视频等文件道理也一样,有专门的处理这些文件类型的设备,因此在有条件的情况下也会分开存放在不同的文件服务器上。

    当数据访问量大到一台数据库服务器、一台图片服务器、一台音频视频服务器...不能满足需求时,我们并不应该想着换一台性能更好的服务器,而是再配置一台和第一台服务器性能差不多的服务器,同时和多台应用服务器为用户提供服务的道理一样,分布式集群通常是解决网站高并发、海量数据存储的问题的惯用手法。

    将目光再转向数据库,例如新浪微博的服务器,那些明星大腕的微博账号中存储的数据访问量和我们凡人的数据访问量相比一定不是一个级别,这就造成了数据库访问的不均衡问题,80%的访问量都集中在20%的数据上,这时缓存就应运而生了,对于缓存有两种处理方案,一种是直接缓存到应用服务器的内存中,这样应用服务器可以非常快速的访问到数据再做出响应,但问题是本身应用服务器的内存就紧张,再加上缓存的竞争,显然不合适,于是分布式缓存服务器就诞生了,即专门拿出一台或多台内存超大的服务器做缓存存储器,目前比较流行的缓存应用有redis。

    当然这之后问题还没完,因为虽然可以通过从缓存中读取数据把压力降下来,但是对数据库的写操作依然无动于衷,通常的做法是配置数据库服务器的主从关系,写操作发生时应用服务器访问主服务器,然后做主从复制。当我们的应用涉及到的用户遍布全中国时,还可以架设CDN服务器。。。

    稍微扯的远了一些,通常应用服务器也称为主服务器,主服务器接收到http数据包时,会取出Request URL,如果Request URL有值,则去找它对应的虚拟主机,再去找虚拟主机所映射的真实路径,从而找到服务器端项目目录

    如果直接通过IP地址访问,则会找默认应用

    6、应用服务器对请求的处理过程

    在传统的后台渲染架构中,通常会根据请求的虚拟路径例如/User/User/getUserList,定位到某个服务器端脚本过程

    对于虚拟路径,服务器端有专门的入口文件和配置文件进行处理,如果后台采用典型的三层架构的话,对于index.php/User/User/getUserList,通常代表找到User模块下的UserController的getUserList方法,首先实例化UserController类,通过得到的实例化对象调用getUserList方法,该方法可能还会实例化模型UserModel类,模型类通常会读写数据,数据源有可能是缓存,也有可能是数据库,缓存和数据库都有可能放在不同的服务器中,因此还可能要建立到数据源的连接,连接过程和浏览器请求差不多,如果向redis缓存取数据的话传输层TCP段的头部源端口和目的端口应该是80和6379(可能会出于多实例和安全性的考虑将其改为7200),如果向mysql存取数据的话传输层TCP段的头部源端口和目标端口应该是80和3306,因此UserModel实例化对象应该有从数据源中取出所有user信息的方法,这样,Model拿到数据,再将其返给Controller,Controller拿到这个数据之后通常会引进来一个模板,对象拿到的数据通常会作为模板变量传进去,这样原本静态的html页面就因为模板变量的介入而变成动态页面,这个模板这样动态化了之后就会将其返回给浏览器,这种情形下浏览器拿到的将是一个处理好了部分数据的html页面,将直接塞到页面中去

    注意我们刚才提到了浏览器拿到的是一个处理好了部分数据的html页面,为什么是部分数据呢?因为浏览器是要和用户交互的,在服务器端怎么能知道用户有什么动作呢?例如我们下面列出来的这个省市联动的情况:

    注意到,我们只是把所有的省份循环出来了,但并没有把城市循环出来,原因也很简单,中国有34个省级行政单位这个毋庸置疑,但问题是城市总得是某个省下面的,具体哪个省是要根据用户的选择来加载的,而用户的选择这个操作一定发生在浏览器端,而我们渲染的这一步发生在服务器上,怎么可能知道选择了哪个省呢,所以这部分数据并没有渲染,而是等用户做出选择之后异步的通过ajax告诉服务器用户选择了哪个省,再加载对应的城市数据,我们在此提到了异步加载,所谓异步加载也就是说只是发一个请求让服务器返回城市对应的数据,浏览器拿到这个数据之后将id为city的select填充满该省下辖的城市,注意在此期间province部分的select数据并没有动,即浏览器和服务器都没有重新去渲染,在此值得一提的是,在ajax诞生之前(1998年以前),不论用户做什么操作都会向服务器发一个请求,包括我们这里的获取城市数据,这种情况下option里面可能还套了一个a链接,当选择了一个省之后直接刷新页面,服务器重新把省、市的数据重新渲染一遍一并返给浏览器

    早期业务不太复杂,数据量小的时候这么做完全没问题,但是后来浏览器端的操作越来越多,操作一次请求一次服务器,请求的次数也就越来越多,服务器压力越来越大,还有典型的场合是表单校验,浏览器端用户即使什么都没填(不需要服务器端的任何帮助就知道这表单一定是错误的)也向服务器发请求,事无巨细的请求服务器。。。终于异步的概念被提了出来,Javascript在被沉寂了遗忘了多年之后被人拾起来,在此之前Js仅仅就是在页面上弄个点击隐藏显示、图片轮换等微不足道的事情。

    刚才我们说到这是传统的后台处理方式,异步的引入的确让服务器端性能有所好转,但是用户的要求在慢慢变高,因此这样做还远远不够,我们希望尽最大可能的减少服务器的压力,能交给客户端处理的就交给客户端处理,毕竟人多力量大,假如1万人同时请求一台服务器去渲染一个所有省份的select,服务器要渲染1万次,因此近几年又诞生了一种新的处理方式就是客户端渲染,我们通过URL仅仅是请求服务器返回一个空的页面:

    这个空的页面一加载完成,马上再向服务器发一个请求,请求所有省份数据,注意这里的省份的数据是json的格式,和上面说的返回一个拼接成的html串相比数据包长度减小了很多

    因此,当同样有1万人请求服务器时服务器所做的处理要简单很多,只需单纯的返回从库里取出的数据即可(通常会转换为json形式)

    当然这样做的同时,就会发现浏览器端在ajax的success回调中仅仅拿到了一个非常轻量的json,接下来的操作(操作DOM)由浏览器自己处理,不过浏览器给提供的dom接口非常底层,而且设计极其反人类,还有诸多兼容性问题,因此浏览器端的处理逻辑将会变得很复杂,最开始的时候搞后台的大神们不以为然,感觉这些浏览器端的删删改改的处理没什么了不起的,复制粘贴抄起家伙就干,等到干的时候感觉各种不爽。。。后来的后来,在2008年左右,终于诞生了web前端工程师这个岗位,这个岗位主要的任务就是专门来处理浏览器客户端的各种交互。还拿咱们刚才的这个省市联动的例子,在获取省份或城市数据成功了之后前端拿到了省份或城市的数据,然后再调用浏览器提供的操作DOM的API创建一个个option,然后再附加到select中去,前面提到浏览器端给提供的DOM接口非常底层,而且还不太好用,兼容问题又多,更重要的是前端天天都在干这些事,写的多了自然觉得不爽,于是便出现了jquery一统前端江湖的局面,可以说做前端的人jquery都是必备的基本技能,当然jquery的学习门槛非常低,而且处理了所有的浏览器兼容问题,还提供了链式调用的方便写法,而且jquery提供的功能也特别丰富:强大的选择器、链式操作、事件绑定、ajax、动画函数,比较高级一点的API有函数队列、延迟对象、数据缓存等等。因此直到今天,jquery依然很火爆

    而随着时间的推移,前端处理的数据绝不是像省市联动这种小儿科的东西一样那么简单了,越来越多的业务逻辑放在前端,jquery慢慢显露出它的不足,例如我们需要渲染下面这种形式的一个表格:

    每趟列车的数据肯定是一个很大的json,然后放着列车的信息,用js写出来之后效果和下面这种的差不多:

    可以看到,这段代码不仅包含了dom操作,还绑定了事件,事件里面又有一些效果,有些a连接还跳到一个新页面,有些地方还需要再通过ajax获取更详细的信息。。。整个一坨代码就像一个大杂烩,乱七八糟。虽然说可以封装一个函数把各个功能打散,但是就以我个人经历来看,这种开发模式下单个文件写个三五千行、最顶上的全局变量定义个十几二十个都是常事,后期的维护更是一场噩梦。

    这样的问题不光是像我这样的草根遇到了,全世界的前端都面临这样的问题,终于,在2009年,Google公司内部某个项目在用传统的jquery开发模式下代码写了17000多行,而其中一位叫Misko Hevery的前端工程师就提出了改造它的想法,现在看来我个人认为这位大神的总体思想是把后台渲染的思路拿到前端来,也就是用Js这门语言写了一个html的编译器。当然回想起后台渲染的那个页面,我个人唯一的感觉就是不伦不类,因为页面中有一部分数据是后台渲染好的,还有一部分和用户交互的数据是前端js加上的,这样一来这个html页面就经过了后台和前端两种人之手,除了在交互方式上感觉各种不爽之外,在后期维护中这是典型的灰色地带,出现问题责任无法明确分清、相互推脱。而这位大神的伟大之处就在于把数据渲染和用户交互进行了统一,一并划到前端来处理,从处理的方式上看感觉非常流畅,这个思路就是Angular框架的前身

    之前我的一位40多岁高龄的老大曾经这样跟我说过使用Angular和使用Jquery的区别:Jquery仅仅是把原生的API封装了一下,说白了就是个库函数,并没有达到框架的作用,而Angular是真正改善Js代码结构的真正意义上的框架。用了Angular之后,上面12306的那个例子就可以写成:

    和之前的Jquery开发方式相比实在是好了太多

    Angular的出现我个人认为具有划时代的意义,因为它把前端再一次推上了一个新的高峰,此后的2013年Facebook发布了React,2015年尤雨溪发布了Vue,实际上都是对于DOM渲染提供的一种新的处理思路,例如React推崇通过组件化的方式来组合web项目,而Vue则是把Angular和React结合在了一起,但是Angular绝对是具有开创性意义的一个框架

    当然Angular并不是一点缺点都没有,首先使用Angular的项目几乎都做成了单页面应用,而单页面应用的首屏加载了所有的Controller Directive Services以及各种库,因此首屏性能极差;其次,由于数据都通过Js渲染,对于SEO来说非常不友好;再者,Angular1对于移动端支持的不太好,我个人之前在公司做过一个Angular+Ionic的webapp,最后的体验是卡到爆

    对于Angular和React的学习成本,门槛的确非常高;而Vue以其简单易学的API、上手较快的特点在github上的star数正在猛增,但是我个人还是认为每个框架的出现必然有它的历史意义,肯定是应运而生的,因此无所谓哪个框架好,哪个不好,只是适用的场合不太一样而已,当前前端的框架相当之多,就以我工作的这几年内接触过的大大小小的库和框架绝对超过二三十种,库和框架永远都学不完,因此我个人感觉学习框架内部的API设计思路,学习它通过什么方式解决了什么问题,这才是学习框架的关键所在

    对于Angular的缺点我个人觉得不太重要,因为适合做单页面应用的项目往往对SEO的要求不太高,而性能的问题通过按需加载以及压缩、合并代码减少请求次数,降低请求量也可以从一定程度上避免,而且首页性能差换来的是接下来各个页面良好的用户体验,Angular2、React和Vue已经提出了SSR(服务器端渲染)的思路来解决这些问题了。

    Angular2已经对这些问题有所改善,期待ECMAScript6普及的那一天,Angular会再次辉煌起来,当然Angular2的学习门槛更高,Angular1和Angular2的区别基本上是象牙和象牙塔的区别。

    7、数据到浏览器中的处理过程

    事实上我们可以猜测一下浏览器内部的工作流程:应该是在地址栏中的值改变时(由于用户的输入或者页面的跳转)调用相关的接口去获取数据,例如如果地址栏中输入了file:///C:/Users/lenovo/Desktop/attrtest.html,浏览器应该就会去本地对应目录下寻找attrtest.html文件,地址栏中如果输入了https://www.baidu.com/,浏览器使用http或https等应用层协议对数据进行解析,再去网络中寻找对应的数据,根据响应头中提供的数据类型(Content-Type响应头)分别做不同的处理,接下来解释一下当通过http或https协议得到的数据中Content-Type响应头得到的类型是text/html时或通过file协议取到了html类型的文件时所做的处理:

    根据HTML结构构建DOM树,根据CSS结构构件render树,遍历这两棵树各个节点然后向页面中渲染

    遍历两棵树的节点过程中,如果发现链接属性,例如DOM树中link标签的href,img的src,script标签的src,video和audio标签的,css3自定义字体加载的文件,以及render树种background属性的url,会再开新的请求去请求对应的资源

    个人感觉:link标签的href、script标签的src加载完之后触发DOMReady事件(DOMContentLoaded和onreadystatechange),当图片、视频、音频等资源加载完时触发onload事件

    实际上浏览器拿到一个html页面之后解析所有的html、css、js的操作其实就是对一组DOM节点对象及其属性(其中典型的就是style属性)的增删改查操作

    至于说浏览器内部到底是如何渲染页面的,如何将一个个div根据匹配的css样式生成一个个DOM对象,这些DOM对象在C++类或结构体中是怎么表示的,都有哪些属性哪些方法,如何渲染到页面中的,我个人还在学习中,等后期学完C++,看了一定的webkit和V8的源码之后再来补充。在此极力推荐这篇文章:how browsers work

    在此格外要提一下JavaScript的执行流程,我们从宏观到微观的顺序来说明:

    浏览器拿到一个html页面之后可能会遇到好多个script标签,不管这些script标签放到哪里,总是落后于css的执行,换句话说,如果css和Js分别对某个节点的某个样式进行操作,不管css写在了Js的前面还是后面,CSS总是会优先于Js执行:

    结果发现div是绿色的

    浏览器在解析每个script标签时会做判断,判断它有没有src属性,如果有的话就重新发起请求,请求src值对应的脚本资源,否则查看script标签内部是否有脚本,找到脚本之后执行,如果在一个script标签上既加了src又在标签内部写了一段脚本,则只去加载远程脚本资源,而标签内的脚本不会执行,以下代码myj变量找不到,因此会报错:

    可以猜测,浏览器在加载执行js的时候是同步的,例如对于下面这段代码

    最开始遇到第一个script标签,发现它带有src,那么浏览器会将其下载完,紧接着再解析该段脚本,然后再往下走,发现第二个script标签,没有src属性,于是就直接执行里面的脚本,由于阻塞加载,后面的script才可以用到前面从远程加载来的jquery脚本中定义的变量$

    这种阻塞加载的机制在html5中做出一定改观:html5为script标签新增加了一个属性:async,该属性代表script标签里面的内容是否要异步加载

    如果 async 属性为 true,则脚本会相对于文档的其余部分异步执行,这样脚本会在页面继续解析的过程中执行。

    如果 async 属性为 false,而 defer 属性为 true,则脚本会在页面完成解析后执行。

    如果 async 和 defer 属性均为 false,那么脚本会立即执行,页面会在脚本执行完毕后继续解析。

    JavaScript是一个单线程执行的指令序列,每个script标签内的代码会分别被浏览器解释,虽然有变量的提升,但是后面的script标签中定义的变量不会提升到前面的script标签中,这一点要格外注意,例如下面的代码中第一个script标签里面console.log(a);是要报错的

    但是后面的script代码块可以使用前面的script代码块中定义的变量,因为它们都作为属性挂在了全局对象window上,例如:

    函数的提升也是同样的道理

    Js中一切接对象,数组、json、自己定义的构造函数实例化之后的对象自然不必多说,函数也是对象,就连数字、字符串、布尔这些基本类型的值也可以看成是对象,我个人对对象的理解是对象是一个属性和方法的集合,我们可以通过点和中括号的形式来对对象进行set或get的操作,我们可以通过下面这几个例子来证明这个观点:

    这个例子中我们把js中几种典型的对象列了出来,并分别定义了各种类型的对象,而且通过点或中括号的方式访问或设置了对象的属性或方法,这里有几点需要我们注意:

    1、对于基础类型的对象(数字 字符串 布尔),在set/get属性和方法时浏览器会生成一个临时对象,set/get操作结束后该对象被回收,而复合类型的对象会在其所处的作用域可用的时间段内常驻于堆内存中,因此复合类型的对象在js中又叫引用类型,也就是说在其所处的作用域可用的时间段内通过指向它的各个指针对其进行修改时修改的都是一个东西,同时,如果指向该对象的指针被赋值为其他值,该指针就不指向该对象了,也就不能通过该指针访问对象中的属性和方法了,同时我们也可以猜测,浏览器内部的垃圾回收机制中有可能会监听指向该堆空间的指针,如果没有指针指向该堆空间了,那该对象也该被回收了

    2、我们像上面那个样子写,比如var a = 1;  var arr = [1,2,3]; function fn(){},其实本质上就是在创建对象,分别创建数字类型的、数组类型的、函数类型的对象。而我们同时也可以看出,对象是分“类型”的,而实际上我们在创建每个对象的时候,都会默认创建一个名为constructor的属性,该属性值为函数类型。其实,JavaScript给我们提供了一种通过构造函数创建对象的机制,例如我想创建一个数组类型的对象除了可以通过var arr = [1,2,3];的方式,还可以通过var arr = new Array(1,2,3);这种构造函数的方式;再如,我们创建一个普通对象除了可以通过var obj = {a: 1};的方式,还可以通过var obj = new Object();obj.a = 1;这种构造函数的方式,而构造函数这种方式实际上是更为通用的一种方式,因为我们可以自定义一个构造函数来创建我们自己想要的一种对象类型,再去实例化该类型从而创建该类型的对象。而我们同时也可以猜测,浏览器内部实际上是把var obj = {a: 1};和var obj = new Object();obj.a = 1;这两种创建对象的方式做了相同的处理,可能在词法分析阶段不同,但是在语法分析构成AST、中间代码、目标代码生成阶段的处理应该是完全一致的,而之所以出现var obj = {a: 1};这种定义对象的方式是因为我们经常需要定义这种对象,而每次new Object这样写太过于繁琐,因此这其实就是浏览器给我们提供的一种创建对象的简便写法而已

    3、通过上面的分析,相信大家对对象已经有了一个初步认识,那么对象与对象之间必然不是独立的关系,它们之间一定存在着某种关联,没错,确实是这样,首先我们考虑这样一种情景:对于数组,所有数组对象都有push pop等方法,如果在每个对象所属的堆内存空间中都给他们分配各自的push pop空间,这就有点太浪费了,因为不管是哪个数组对象,push和pop的操作时完全一样的,根本没必要一人一份,因此这些东西一定要共享才可以。实际上当我们每次创建一个对象时,该对象会自动创建一个__proto__属性,这个__proto__属性就指向该对象的原型对象,通过刚才的分析,相信大家已经猜到,所谓原型对象,其主要功能就是存放一类对象的公共部分属性和方法的对象,其实我们还可以猜测,实际上所有的对象是不是都有一些公共的方法呢,没错,实际上所有的对象都有两个方法:valueOf和toString,我们不可能在每个对象的堆空间中存放这两个方法,如果在各种类型对象的原型对象所处的堆空间中存放valueOf和toString这两个方法的话似乎也有一些浪费,毕竟原型对象有可能也挺多的,于是,更进一步的抽象思路来了:原型链,原型对象也是对象,原型对象也可以有原型,因此原型对象的__proto__还可以再往上指,那么最上面的那个对象是谁呢?就是Object.prototype,这个对象就没原型了,因为已经到头了。

    可以看出原型是同类对象的一个公共对象,这些对象的__proto__属性都指向它,而构造这一类对象的也是同一个构造函数,由此我们可以猜测:构造函数和原型对象之间有什么关系吗?肯定是有的:构造函数有一个prototype属性,这个prototype属性就指向通过这个类实例化创建出来的对象的原型对象

    对象可以通过constructor访问到创建自己的构造函数

    综上,联系对象之间的桥梁是——原型(即对象的__proto__属性)

    其实Js中继承的原理就是我们刚才分析的这些:

    在inheritPrototype函数内部我们明显发现我们将子类subType的prototype指向F实例化对象,而F类的prototype已经被我们修正成父类的prototype了,因此相当于我们的子类指向了父类的prototype,自然可以访问父类prototype上的方法了

    当然对象自身身上的属性继承的时候还需要借助call和apply

    4、在浏览器的环境中实际上已经创建了很多内置对象了,具体有哪些内置对象就不一一阐述了,可以参考下图:

    接下来是我个人对函数对象的一些见解:由于函数也是对象,那么函数本身也一定是属性和方法的集合,理解这一点可以参考经典的面向对象语言——Java的设计:Java中类也有方法和属性,被称为类属性和类方法(或者静态属性和静态方法),既然如此,可以猜测在Java虚拟机内部对类和类的实例必然有一些相同的处理手段,再进一步抽象一下,可以猜测类也是一个特殊的对象,当然等后期对Java比较熟悉之后再看一下深入理解Java虚拟机这本书之后再过来补充

    关于对象的话题暂且先说这么多,接下来进入下一个话题,作用域:

    可以猜测在浏览器引擎内部,作用域是实实在在的对象,只不过在Js中它为我们隐藏了作用域的细节

    2017年6月3日编辑==================== 

    在ECMAScript5之前,js中只有两种作用域:全局作用域和局部作用域(函数在执行时生成的作用域),函数执行时会生成一个新的作用域对象,并在栈上开一块新的私有空间用于存放当前该函数中各个变量,当函数执行结束时,作用域对象被回收,栈上的私有空间也被释放,这是正常的函数执行的结果。但是有种不正常的情况——闭包:当一个函数在执行完之后最后返回来一个函数供外部调用,而且该返回值函数中还访问到了父函数中的某个变量,那么即使父函数执行完了父函数中的这个变量也还会驻留在内存中不会释放,例如:

    这个案例中的变量a就在函数closure执行结束之后一直驻留在内存中,因为外面innerFn函数还会调用到它,而正因为一直在内存中,所以每次都可以对同一块内存空间操作,所以console出来的a的值每次都会变

    实际上函数内部并不一定非得返回函数类型的值才叫闭包,只要返回回来的东西用到了父函数的变量那都叫闭包,例如可以返回回来一个对象,对象的某个方法调用到了父函数的某个变量,这种情况其实也是闭包,例如:

    callback函数内部_callbackCache变量其实在callback执行完之后也不会被释放,因为返回的对象在外部调用其add方法时会用到这个变量,实际上这种思路在jQuery以及Angular内部用的非常多,例如jQuery内部的数据缓存管理、队列管理、回调函数、延迟对象等等,Angular里面把所有注册的模块都放到一个闭包变量中,把每个模块下绑定的constroller、directive、service、provider也放大对应的闭包变量中等等,就不一一举例了

    参考链接:

    1、DNS解析的过程是什么? https://www.zhihu.com/question/23042131

    2、当时发生了什么? https://github.com/skyline75489/what-happens-when-zh_CN

    3、网卡(网络适配器),猫(调制解调器),路由器三者有什么区别? https://www.zhihu.com/question/27142839

    4、WAN口和LAN口有何不同 https://zhidao.baidu.com/question/180687224.html

    5、路由器与交换机的本质区别 http://jingyan.baidu.com/article/8065f87fe57f372330249851.html

    6、常见的网站服务器架构有哪些? https://www.zhihu.com/question/20657269

    7、图解正向代理、反向代理、透明代理 http://z00w00.blog.51cto.com/515114/1031287

    8、从输入 URL 到页面加载完成的过程中都发生了什么事情? http://fex.baidu.com/blog/2014/05/what-happen/

    9、浏览器内部工作原理 http://kb.cnblogs.com/page/129756/

    10、Javascript的执行过程详细研究 http://blog.csdn.net/cxiaokai/article/details/7552653

    11、HTML属性与DOM属性的区别? https://segmentfault.com/q/1010000004100696?_ea=489898

  • 相关阅读:
    Orchard源码分析(4.3):Orchard.Events.EventsModule类(Event Bus)
    Orchard源码分析(4.2):Orchard.Logging.LoggingModule类
    Orchard源码分析(4.1):Orchard.Environment.CollectionOrderModule类
    Orchard源码分析(4):Orchard.Environment.OrchardStarter类
    Orchard源码分析(3):Orchard.WarmupStarter程序集
    Orchard源码分析(2):Orchard.Web.MvcApplication类(Global)
    MSSQL
    MSSQL
    C#、WinForm、ASP.NET
    C#、WinForm、ASP.NET
  • 原文地址:https://www.cnblogs.com/zhaohuiziwo901/p/6706832.html
Copyright © 2020-2023  润新知