阅读 85

玩转javaScript---知识点汇总(3)

1.浏览器从输入URL到渲染完页面的整个过程

  1. 浏览器先查看浏览器缓存-系统缓存-路由器缓存,若缓存中有,请略过中间步骤,直接跳到第9步~若没有,则按照下面的步骤进行操作。
  2. 浏览器从url中解析出服务器的主机名,并将主机名转换成服务器的IP地址(DNS查询)
  3. 浏览器建立一条与服务器的tcp连接(建立过程:三次握手)。浏览器利用IP直接与网站主机通信。浏览器发出TCP(SYN标志位为1)连接请求,主机返回TCP(SYN,ACK标志位均为1)应答报文,浏览器收到应答报文发现ACK标志位为1,表示连接请求确认。浏览器返回TCP()确认报文,主机收到确认报文,三次握手,TCP链接建立完成。
  4. 浏览器通过tcp连接向服务器发送http请求,请求数据包。
  5. 服务器处理HTTP请求,返回响应。
  6. 浏览器检查HTTP响应是否为一个重定向(3XX结果状态码)、一个验证请求(401)、错误(4XX、5XX)等等,这些都需要根据具体情况分类处理。
  7. 浏览器接收HTTP响应并且可能关掉TCP连接,或者是重新建立连接使用新情求,获得新响应。
  8. 浏览器渲染页面:1. 解析HTML 2. 构建DOM树 3. DOM树与CSS样式进行附着构造呈现树 4. 布局 5. 绘制 上述这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。

2.javascript的并发模型与事件循环

栈:函数调用形成了一个栈帧。

function foo(b) {
  var a = 10;
  return a + b + 11;
}

function bar(x) {
  var y = 3;
  return foo(x * y);
}

console.log(bar(7));
复制代码

当调用bar时,创建了第一个帧 ,帧中包含了bar的参数和局部变量。当bar调用foo时,第二个帧就被创建,并被压到第一个帧之上,帧中包含了foo的参数和局部变量。当foo返回时,最上层的帧就被弹出栈(剩下bar函数的调用帧 )。当bar返回的时候,栈就空了。

堆:对象被分配在一个堆中,即用以表示一个大部分非结构化的内存区域。

队列:一个 JavaScript 运行时包含了一个待处理的消息队列。每一个消息都有一个为了处理这个消息相关联的函数。

在事件循环时,runtime (运行时)总是从最先进入队列的一个消息开始处理队列中的消息。正因如此,这个消息就会被移出队列,并将其作为输入参数调用与之关联的函数。为了使用这个函数,调用一个函数总是会为其创造一个新的栈帧( stack frame),一如既往。 函数的处理会一直进行直到执行栈再次为空;然后事件循环(event loop)将会处理队列中的下一个消息(如果还有的话)。

事件循环:之所以称为事件循环,是因为它经常被用于类似如下的方式来实现:

while (queue.waitForMessage()) {
  queue.processNextMessage();
}复制代码

"执行至完成"

每一个消息完整的执行后,其它消息才会被执行。当你分析你的程序时,这点提供了一些优秀的特性,包括每当一个函数运行时,它就不能被抢占,并且在其他代码运行之前完全运行(且可以修改此函数操作的数据)。这与C语言不同,例如,如果函数在线程中运行,则可以在任何位置终止然后在另一个线程中运行其他代码。

这个模型的一个缺点在于当一个消息需要太长时间才能完成,Web应用无法处理用户的交互,例如点击或滚动。浏览器用“程序需要过长时间运行”的对话框来缓解这个问题。一个很好的做法是使消息处理缩短,如果可能,将一个消息裁剪成几个消息。

添加消息

在浏览器里,当一个事件出现且有一个事件监听器被绑定时,消息会被随时添加。如果没有事件监听器,事件会丢失。所以点击一个附带点击事件处理函数的元素会添加一个消息。其它事件亦然。

调用 setTimeout 函数会在一个时间段过去后在队列中添加一个消息。这个时间段作为函数的第二个参数被传入。如果队列中没有其它消息,消息会被马上处理。但是,如果有其它消息,setTimeout消息必须等待其它消息处理完。因此第二个参数仅仅表示最少的时间 而非确切的时间。

零延迟

零延迟并不是意味着回调会立即执行。在零延迟调用 setTimeout 时,其并不是过了给定的时间间隔后就马上执行回调函数。其等待的时间基于队列里正在等待的消息数量。在下面的例子中,"this is just a message" 将会在回调 (callback) 获得处理之前输出到控制台,这是因为延迟是要求运行时 (runtime) 处理请求所需的最小时间,但不是有所保证的时间。

(function() {

  console.log('this is the start');

  setTimeout(function cb() {
    console.log('this is a msg from call back');
  });

  console.log('this is just a message');

  setTimeout(function cb1() {
    console.log('this is a msg from call back1');
  }, 0);

  console.log('this is the end');

})();

// "this is the start"
// "this is just a message"
// "this is the end"
// note that function return, which is undefined, happens here 
// "this is a msg from call back"
// "this is a msg from call back1"
复制代码

多个运行时互相通信

一个 web worker 或者一个跨域的iframe都有自己的栈,堆和消息队列。两个不同的运行时只能通过 postMessage方法进行通信。如果后者侦听到message事件,则此方法会向其他运行时添加消息。

永不阻塞

事件循环模型的一个非常有趣的特性是 JavaScript,与许多其他语言不同,它永不阻塞。 处理 I/O 通常通过事件和回调来执行,所以当一个应用正等待IndexedDB查询返回或者一个 XHR 请求返回时,它仍然可以处理其它事情,如用户输入。 例外是存在的,如 alert或者同步 XHR,但应该尽量避免使用它们。

上面的内容摘自MDN,看起来是不是有点官方,其实所谓的事件循环就是javascript的主线程重复从消息队列中取消息、执行的过程。

3.WebSocket的底层原理

首先HTTP有1.1和1.0之说,也就是所谓的keep-alive,把多个HTTP请求合并为一个,但是Websocket其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是HTTP协议上的一种补充可以通过这样一张图理解

Websocket实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的。它与HTTP一样通过已建立的TCP连接来传输数据。websocket是一个持久化连接的协议,一个典型的websocket连接:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
复制代码

熟悉http协议的能够看出来,它比http协议多了几个东西,如下就是重点,用于告知服务器我们客户端使用的是websocket协议,不是那老套的http协议。

Upgrade: websocket
Connection: Upgrade
复制代码

Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
复制代码

首先,Sec-WebSocket-Key 是一个Base64 encode的值,这个是浏览器随机生成的,用于验证服务器,要求服务端必须返回一个对应加密的Sec-WebSocket-Accept应答,否则客户端会抛出Error during WebSocket handshake错误,并关闭连接。Sec_WebSocket-Protocol 是一个用户定义的字符串,用来区分同URL下,不同的服务所需要的协议。Sec-WebSocket-Version 是告诉服务器所使用的Websocket Draft(协议版本),然后服务器会返回下列东西,表示已经接受到请求, 成功建立Websocket,后续服务器与浏览器之间的通讯不需要向http协议那样重复发送httpHeader。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
复制代码

4.typeof运算符对各个类型的返回结果

1.undefined:undefined

2.null:object

3.string:string

4.number:number

5.boolean:boolean

6.function:function

7.object:object

8.array:object(Array:function)

9.NaN:number

5.判断是否为数组

1.instanceof :

var ary = [2,3];
console.log(ary instanceof Array)//true;
复制代码

2.原型链方法

var ary = [2,4];
console.log(ary.__proto__.constructor==Array);//true
console.log(ary.constructor==Array)//true 这两段代码是一样的
复制代码

上述两种方法都是有局限性的:

instanceof 和constructor 判断的变量,必须在当前页面声明的,比如,一个页面(父页面)有一个框架,框架中引用了一个页面(子页面),在子页面中声明了一个arr,并将其赋值给父页面的一个变量,这时判断该变量,Array == arr.constructor;会返回false;原因就是Array是引用型数据,1.在传递过程中,仅仅是引用地址的传递。 2.每个页面的Array原生对象所引用的地址是不一样的,在子页面声明的array,所对应的构造函数,是子页面的Array对象;父页面来进行判断,使用的Array并不等于子页面的Array,从而造成原型链的断裂。

3. Object.prototype.toString.call()

采用Object.prototype.toString.call()算是最稳定也是通用的一个方法,Object.prototype.toString()返回一个表示该对象的字符串,每个对象都有一个toString()方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 "[object type]",其中type是对象的类型。

var arr = [2,3];
function isArray(o){
return Object.prototype.toString.call(o)=='[object Array]';
}
console.log(isArray(arr));
复制代码

6.实现一个柯里化函数

场景:记录一个员工一个月的总加班时间,我们首先想到的可能会是这样实现:

var sumTime = 0;
function  overTime(time){
    return sumTime += time;
}

overTime(3.0);
overTime(7);
overTime(4.5);
......
console.log(overTime());复制代码

逐次累加,这样看起来一点毛病都没有,但是当数据量很大时,就会影响性能了,那么这时柯里化就可以解决我们的问题了。我们可以不用每次都叠加,只需要将每次加班的时间保存起来,到最后再叠加就ok了。

var overTime = (function(){  var args = [];  return function () {     if(arguments.length === 0){      var time = 0;      for(var i = 0;len = args.length,i<len;i++){        time += args[i];      }      return time;    }else{      [].push.apply(args, arguments);    }   }})()
overTime(3.0);overTime(4.5);overTime(7.0);console.log(overTime());//14.5复制代码

看到这里你应该有点理解什么叫函数柯里化了,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。