线程机制与事件机制

进程与线程

进程:程序的一次执行,它占有一片独有的内存空间,他是资源调度和分配的基本单位。
可以通过windows任务管理器查看进程
线程:是进程内的一个独立执行单元
是程序执行的一个完整流程
是cpu的最小的调度单元
图解:
在这里插入图片描述在这里插入图片描述
相关知识
1.应用程序必须运行在某个进程的某个线程上
2.一个进程中至少有一个运行的线程:主线程,进程启动后自动创建
3.一个进程中也可以同时运行多个线程,我们会说程序是多线程运行的
4.一个进程内的数据可以供其中的多个线程直接共享
5.多个进程之间的数据是不能直接共享的
5.线程池(threed pool):保存多个线程对象的容器,实现线程对象的反复利用
相关问题
1.何为多进程和多进程?
多进程运行:一应用程序可以同时启动多个实例运行
多线程:在一个进程内,同时有多个线程运行
2.比较单线程与多线程?
多线程
优点:能有效提升cpu利用率
缺点:创建多线程开销
线程间切换开销
死锁与状态同步问题
单线程
优点:顺序编程简单易懂
缺点:效率低
3.js是单线程还是多线程?
js是单线程运行的
但是用h5中的web workers可以多线程运行
4.浏览器运行是单线程还是多线程?
多线程
5.浏览器是单进程还是多进程?
有的单进程(firefox 老版ie)有的多进程(chrome 新版ie)

浏览器内核

1.支撑浏览器运行的最核心的程序
2.不同浏览器内核和前缀不一样
旧版本chrome,safari: Webkit -webkt-
新版本chrome,新版本Opera:blink -webkit
旧版本Opera:Presto -o-
firefox:Gecko -moz-
ie:Trident -ms-
360,搜狗等:Trident+webkit
3.浏览器内核由很多模块组成
(主线程)
js引擎模块:负责js程序的解析与执行
html,css文档解析模块:负责页面文本的解析
dom/css模块:负责dom/css在内存中的相关处理
布局和渲染模块: 负责页面的布局和效果的绘制(内存中的对象)
。。。。。
(分线程)
定时器模块:负责定时器的管理
dom事件响应模块:负责事件的管理
网路请求模块:负责ajax请求

定时器引发的思考

1.定时器真的是定时执行的吗?
定时器并不能保证真正的定时执行
一般会延迟一点(可以接受),也有可能延长很长时间(不能接受)
2.定时器回调函数是在分线程执行的吗?
在主线程执行的(js是单线程的)
3.定时器是如何实现的?
事件循环模型
测试代码:

<body>
		<button type="button" id="btn">启动</button>
		<script type="text/javascript">
			var btn = document.getElementById('btn')
			btn.onclick = function(){
				var start = Date.now()
				console.log('启动定时器前')
				setTimeout(function() {
					console.log('定时器执行了',Date.now()-start)
				}, 200);
				console.log('启动定时器后')
				
				//做一个长期工作
				for (var i=0;i<1000000;i++){}
					
			}
			/*
			打印
			启动定时器前
			启动定时器后
			定时器执行了211
			*/
		</script>
	</body>

js是单线程的

1.如何证明js是单线程执行的?
setTimeout()的回调函数是在主线程执行的
定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行

setTimeout(function(){
				console.log('定时器2')
				alert('-------2')
			},2000)
			setTimeout(function() {
				console.log('定时器1')
				alert('-------1')
			}, 1000)
			function fn(){
				console.log('fn')
			}
			fn()
			console.log('alert前')
			alert('------')   //暂停当前主线程的执行.同时暂停计时,点击确定后,恢复程序执行和计时
			console.log('alert后')
			
			/*
			运行:
			打印fn
			  alert前
			弹出------
			点击确定后
			打印alert后
			  定时器1
			弹出-------1
			打印定时器2
			弹出-------2
			*/

2.为什么js要用单线程模式,而不用多线程模式?
js的单线程与他的用途有关
作为浏览器脚本语言,javascript的主要用途是与用户互动以及操作DOM
这决定了他只能是单线程,否则会带来很复杂的同步问题
3、js是单线程的,为了避免代码解析阻塞使用了异步执行,那么它的异步执行机制是怎么样的?
通过事件循环(Event Loop),理解了事件循环的原理就理解了js的异步执行机制
4、js是单线程的,那么是否代表参与js执行过程的线程就只有一个?
不是的,会有四个线程参与该过程,但是永远只有JS引擎线程在执行JS脚本程序,其他的三个线程只协助,不参与代码解析与执行。参与js执行过程的线程分别是:
(1)JS引擎线程: 也称为JS内核,负责解析执行Javascript脚本程序的主线程(例如V8引擎)。
(2)事件触发线程: 归属于浏览器内核进程,不受JS引擎线程控制。主要用于控制事件(例如鼠标,键盘等事件),当该事件被触发时候,事件触发线程就会把该事件的处理函数推进事件队列,等待JS引擎线程执行。
(3)定时器触发线程:主要控制计时器setInterval和延时器setTimeout,用于定时器的计时,计时完毕,满足定时器的触发条件,则将定时器的处理函数推进事件队列中,等待JS引擎线程执行。
注:W3C在HTML标准中规定setTimeout低于4ms的时间间隔算为4ms。
(4)HTTP异步请求线程:通过XMLHttpRequest连接后,通过浏览器新开的一个线程,监控readyState状态变更时,如果设置了该状态的回调函数,则将该状态的处理函数推进事件队列中,等待JS引擎线程执行。
注:浏览器对同一域名请求的并发连接数是有限制的,Chrome和Firefox限制数为6个,ie8则为10个

js引擎的执行过程

js引擎执行过程主要分为三个阶段,分别是语法分析,预编译和执行阶段。
语法分析(分析js脚本代码块的语法是否正确,出现不正确会向外抛出错误,如果正确会进入预编译)
预编译(1.js全局运行环境创建全局执行上下文,函数运行环境创建函数执行上下文,js引擎会以栈的方式对这些执行上下文进行处理,栈的处理方式是先进后出。栈底永远是全局执行上下文,栈顶是当前执行上下文
2.创建执行上下文的过程中,主要是做了三件事:创建变量对象、创建作用域链、确定this的指向)

执行
js的任务分为宏任务(macro-task)和 微任务(micro-task)。宏任务包括 整体代码script ,setTimeout, setInterval等 微任务包括:Promise,process.nextTick等
宏任务(macro-task)可分为同步任务和异步任务:
1、同步任务指的是在JS引擎主线程上按顺序执行的任务,只有前一个任务执行完毕后,才能执行后一个任务,形成一个执行栈(函数调用栈)。
2、异步任务指的是不直接进入JS引擎主线程,而是满足触发条件时,相关的线程将该异步任务推进任务队列(task queue),等待JS引擎主线程上的任务执行完毕,空闲时读取执行的任务,例如异步Ajax,DOM事件,setTimeout等。


  • 在JS引擎执行过程中,进入执行阶段后,代码的执行顺序如下:
    宏任务(同步任务) --> 微任务 --> 宏任务(异步任务)
    .事件循环
    事件循环可以理解成由三部分组成,分别是:
    1、主线程执行栈
    2、异步任务等待触发
    3、任务队列
    任务队列(task queue)就是以队列的数据结构对事件任务进行管理,特点是先进先出,后进后出。
    在JS引擎主线程执行过程中:
    1、首先执行宏任务的同步任务,在主线程上形成一个执行栈,可理解为函数调用栈。
    2、当执行栈中的函数调用到一些异步执行的API(例如异步Ajax,DOM事件,setTimeout等API),则会开启对应的线程(Http异步请求线程,事件触发线程和定时器触发线程)进行监控和控制。
    3、当异步任务的事件满足触发条件时,对应的线程则会把该事件的处理函数推进任务队列(task queue)中,等待主线程读取执行。
    4、当JS引擎主线程上的任务执行完毕,则会读取任务队列中的事件,将任务队列中的事件任务推进主线程中,按任务队列顺序执行
    5、当JS引擎主线程上的任务执行完毕后,则会再次读取任务队列中的事件任务,如此循环,这就是事件循环(Event Loop)的过程。
console.log('script start');
 
setTimeout(function() {
  console.log('setTimeout');
}, 0);
 
Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});
 
console.log('script end');
/*
1、代码块通过语法分析和预编译后,进入执行阶段,当JS引擎主线程执行到console.log('script start');,JS引擎主线程认为该任务是同步任务,所以立刻执行输出script start,然后继续向下执行。

2、JS引擎主线程执行到setTimeout(function() { console.log('setTimeout'); }, 0);,JS引擎主线程认为setTimeout是异步任务API,则向浏览器内核进程申请开启定时器线程进行计时和控制该setTimeout任务。由于W3C在HTML标准中规定setTimeout低于4ms的时间间隔算为4ms,那么当计时到4ms时,定时器线程就把该回调处理函数推进任务队列中等待主线程执行,然后JS引擎主线程继续向下执行。

3、JS引擎主线程执行到Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); });,JS引擎主线程认为Promise是一个微任务,这把该任务划分为微任务,等待执行。

4、JS引擎主线程执行到console.log('script end');,JS引擎主线程认为该任务是同步任务,所以立刻执行输出script end。

5、主线程上的宏任务执行完毕,则开始检测是否存在可执行的微任务,检测到一个Promise微任务,那么立刻执行,输出promise1和promise2

6、微任务执行完毕,主线程开始读取任务队列中的事件任务setTimeout,推入主线程形成新宏任务,然后在主线程中执行,输出setTimeout

*/

两种基本的事件模型
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。
事件冒泡
: IE 的事件流叫做事件冒泡,即事件开始时由最具体的元素 (文档中嵌套层次最深的那个节点) 接收,然后逐级向上传播到较为不具体的节点(文档)。比如单击了页面中的

元素,那么这个click事件会按照如下顺序逐级向上传播,直至传播到 document 对象。
事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。

  • 事件委托可以解决页面中事件处理程序过多的问题。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以处理某一类型的所有事件。
EventUtil.addHandler(document.body, 'click', function (event) {
  event = EventUtil.getEvent(event);
  var target = EventUtil.getTarget(event);
  switch (target.id) {
  case 'site_nav_top':
    alert('口号');
    break;
  case 'nav_menu':
    alert('点击菜单');
    EventUtil.preventDefault(event);
    break;
  case 'editor_pick_lnk':
    alert('推荐区');
    EventUtil.preventDefault(event);
    break;
  }
});

关于事件处理程序
事件就是用户或浏览器自身执行的某种动作。而响应某个事件的函数就叫做事件处理程序 (或事件侦听器)。事件处理程序的名字以 “on” 开头,比如 click 事件的事件处理程序就是 onclick。为事件指定处理程序的方式有以下几种
1、HTML事件处理程序
在HTML中定义的事件处理程序可以包含要执行的具体动作,也可以调用在页面其他地方定义的脚本。

<input type=“button” value=“Click Me” οnclick=“alert(this.value)” />

或者

<script type="text/javascript">    
    function showMessage(){    
        alert("Hello world!");    
    }    
  </script>    
  <input type="button" value="Click Me" οnclick="showMessage()" />  

注意:不能再其中使用未经转义的HTML语法字符,例如和号(&)、双引号("")、小于号(<)或大于号(>)。
在HTML中指定事件处理程序有三个缺点。1、存在一个时差问题。因为用户可能会在HTML元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。为此,很多HTML事件处理程序都会被封装在一个try-catch块中,以便错误不会浮出水面。2、这样扩展事件处理程序的作用域在不同浏览器中会导致不同结果。不同js引擎遵循的标识符解析规则略有差异,很可能会在访问非限定对象成员时出错。3、html与js代码紧密耦合,更换事件处理程序改动较大。
2、DOM0级事件处理程序
通过 JavaScript 指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。这种为事件处理程序赋值的方法是在第四代Web浏览器中出现的,而且至今仍然为所有现代浏览器所支持。原因一是简单,二是具有跨浏览器的优势。
每个元素都有自己的事件处理程序属性,如onclick等。可以通过js将一个函数赋值给元素的事件处理程序属性。在DOM0事件处理程序中,事件处理程序里的this指向当前元素。

var objlogo=document.getElementById("site_nav_top");
objlogo.οnclick=function(){
alert(this.innerHTML);//代码改变世界
}

删除DOM0事件,只需将事件处理程序的值赋为null即可。

objlogo.οnclick=null;

3、DOM2级事件处理程序

“DOM2级事件”定义了两个方法,addEventListener() 和 removeEventListener(),它们都接受3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。addEventListener()可以为元素添加多个事件处理程序,触发时会按照添加顺序依次调用。removeEventListener()不能移除匿名添加的函数。  

var btn = document.getElementById("myBtn");  
btn.addEventListener("click", function(){  
alert(this.id);  
}, false);  
var handler = function(){  
alert("Hello world!");  
};  
btn.addEventListener("click", handler, false);  
// 这里省略了其他代码  
btn.removeEventListener("click", handler, false); // 有效!

以上代码会先显示id,再显示hello world。大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。IE9、Opera、Firefox、Chrome和Safari都支持DOM2级事件处理程序。

4、IE事件处理程序
IE 实现了与DOM中类似的两个方法:attachEvent() 和 detachEvent()。这两个方法接受相同的两个参数:事件处理程序名称与事件处理程序函数。由于IE只支持事件冒泡,所以通过 attachEvent() 添加的事件处理程序都会被添加到冒泡阶段。
在使用DOM0级方法的情况下,事件处理程序会在其所属元素的作用域内运行;在使用 attachEvent() 方法的情况下,事件处理程序会在全局作用域中运行,因此 this 等于 window 。并且,与DOM 方法不同的是,这些事件处理程序不是以添加它们的顺序执行,而是以相反的顺序被触发。
注意:1、事件名前面有on前缀;2、在事件处理程序的函数中,this不再指向当前元素,而是指向window对象。

var btn = document.getElementById("myBtn");  
var handler = function(){  
alert("Clicked");  
};  
btn.attachEvent("onclick", handler);  
// 这里省略了其他代码  
btn.detachEvent("onclick", handler); 

支持IE事件处理程序的浏览器有IE和Opera。
5、跨浏览器的事件处理程序

第一个要创建的方法是 addHandler(),它的职责是视情况分别使用DOM0级方法、DOM2级方法或IE方法来添加事件,它属于一个名叫 EventUtil 的对象。addHandler()方法接受3个参数:要操作的元素、事件名称和事件处理程序函数。EventUtil的用法如下所示:

var EventUtil = {    
    addHandler: function(element, type, handler){    
        if (element.addEventListener){    
            element.addEventListener(type, handler, false);    
        } else if(element.attachEvent) {    
            element.attachEvent("on" + type, handler);    
        } else {    
            element["on" + type] = handler;    
        }    
    },    
    removeHandler: function(element, type, handler){    
        if (element.removeEventListener){    
            element.removeEventListener(type, handler, false);    
        } else if(element.detachEvent){    
            element.detachEvent("on" + type, handler);    
        } else {    
            element["on" + type] = null;    
        }    
    }    
}; 

使用EventUtil对象的实例如下:

var btn = document.getElementById("myBtn");  
var handler = function(){  
    alert("Clicked");  
};  
EventUtil.addHandler(btn, "click", handler);  
// 这里省略了其他代码  
EventUtil.removeHandler(btn, "click", handler); 

h5web workers

1.h5规范提供了js分线程的实现,取名为web workers
可以将一些计算量大的代码交给web worker运行而不冻结用户界面
但是子线程完全受主线程控制,且不得不操作dom
所以这个新标准并没有改变js单线程本质
只能是主线程更新界面
2.相关api:
worker:构造函数,加载分线程执行的js文件
worker.prototype.onmessage:用于接收另一个线程的回调函数
worker.prototype.postMessage:向另一个线程发送消息
3.不足
worker内代码不能操作dom
不能跨域加载js
不是每个浏览器都支持它
4.使用:
创建分线程执行的js文件
在主线程中的js中发消息并设置回调

  • 一个小例子斐波拉契数列
    不使用web worker:


<input type="text" id="number" placeholder="数值"/><button type="button" id="btn">计算</button>
		<script type="text/javascript">
			//0 1 1 2 3 5 8  f(n) = f(n-1) +f(n-2)
			function fibanacci(n){
				return n<2 ? n: fibanacci(n-1) + fibanacci(n-2)  //递归调用
			}
			
			var input = document.getElementById("number")
			document.getElementById("btn").onclick = function(){
				var number = input.value
				var result = fibanacci(number)
				alert(result)
			}
			
		</script>

使用web worker:

<input type="text" id="number" placeholder="数值"/><button type="button" id="btn">计算</button>
			<script type="text/javascript">
				//0 1 1 2 3 5 8  f(n) = f(n-1) +f(n-2)斐波拉契数列
	
				var input = document.getElementById("number")
				document.getElementById("btn").onclick = function(){
					var number = input.value
					
					//创建一个worker对象
					var worker = new Worker('worker.js')
					//绑定接收消息的监听
					worker.onmessage = function(event){
						console.log('主线程接收分线程返回的数据'+event.data)
						alert(event.data)
					}
					//向分线程发送消息
					worker.postMessage(number)
					console.log("主线程向分线程发送消息"+number)
				}
				
				
			</script>

worker.js中

//1 1 2 3 5 8  f(n) = f(n-1) +f(n+1)
var onmessage=function(event){
	var number = event.data
	console.log("分线程接收到主线程发送的数据"+ number)
	
	//计算
	var result =fibanacci(number)
	postMessage(result)
	console.log("分线程向主线程返回数据"+result)
}
//0 1 1 2 3 5 8  f(n) = f(n-1) +f(n-2)
 /* function fibanacci(n){
	return n<=2 ? 1: fibanacci(n-1) + fibanacci(n-2)  //递归调用(当数字较大时会生栈溢出,一般使用尾递归和循环解决栈溢出)
} */

/* 
function fibanacci(n,ret1=0,ret2=1){
	if(n==0)
	   return ret1
	else return fibanacci(n-1,ret2,ret1+ret2 )//尾递归
} */
function fibanacci(n){
	if(n<=2)
	   return n
	var x=1,y=1,y_tmp=0;
	for(var i=0;i<n-2;i++){
		y_tmp=y
		y=x+y
		x=y_tmp
	}
	return y  //循环实现
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值