JS中的同步与异步问题

JS中的同步与异步,解释的最准确的一段话:

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。于是就有一个概念,任务队列。

如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。

JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。

于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行.

外加一幅图理解程序的执行顺序:
在这里插入图片描述

同步与异步的区别总结:

//1. 执行时机不同: 同步代码会一步一步的执行,而异步代码不会等后面的后面的代码执行,会自己先执行
//2. 获取结果的方式不一样,同步是函数的返回值,异步是回调函数

一 异步

在JS中那些是异步操作呢?

//js中的异步操作(语句)
1. 定时器
2. ajax
3. 事件函数
4. 回调函数
5. promise
6. Generator 函数

1.1 定时器

js中的定时器,是异步代码,也就是说程序会先执行同步代码,当定时器时间到了之后,通知主线程来执行同步代码.(相当于开启了一个线程)

function fun() {
		var name = '李四';
		setTimeout(function () {
			name = '张三';
		},2000)
		return name;
}
	var name = fun();
	console.log(name);

我们希望代码是先打印出张三,但是最终只会打印李四.因为定时器是异步的,当定时器时间到来时,函数都已经调用完了.所以打印的一直是默认值李四.

1.2 ajax

ajax同样是异步代码,本例打印出来的结果同样是李四,道理与定时器一样.

function fun() {
		var name = '李四';
		$.ajax({
			url:'url',
			success:function (data) {
				name = data;
			}
		})
		return name;
	}
	var name = fun();
	console.log(name);

1.3 事件函数

事件同样具有异步性,因为事件只有等待触发之后,才会执行,事件触发形式通常会用于表示某些状态变化(加载、出错、进度变化、收到消息等等)
如果一开始主线程上就有同步代码,同样会先执行同步代码,直到事件触发后才会执行事件函数

这个例子里面: 函数调用时先于事件触发的,函数调用完后,便不会再去调用了,所以打印的还是李四.

function fun() {
	var name = '李四';
	div.addEventListener('click', function () {
		name = '张三';
	});
	return name;
}
var name = fun();
console.log(name);

1.4 怎样访问异步代码里面的变量(回调函数)

回调函数: 把一个函数作为参数传入到另一个函数中,那么这个作为参数的函数就叫做回调函数,形式是为了获取某一个函数的运行结果

如果我就要拿上面代码里的name变量,我们可以利用回调函数来实现.因为异步函数里面是不能使用return的.

//callback是一个回调函数
function fun(callback) {
	var name;
	setTimeout(function () {
		name = '张三';
		callback(name);
	},1000)

}
//fun函数的参数就是一个callback的实际参数
fun(function (name) {
	console.log(name);
});

上述代码相当于:

//callback是一个回调函数
function fun() {
	var name;
	setTimeout(function () {
		name = '张三';
		//调用callback函数
		callback(name);
		function callback  (name) {
			console.log(name);
		}
	},1000)
}
//调用函数
fun();

例如: W3C的File System API中,在请求虚拟文件系统实例、读写文件等接口中,都采用了回调函数的形式:

requestFileSystem(TEMPORARY, 1024 * 1024, function(fs) {
    // 异步获取虚拟文件系统实例fs
	fs.root.getFile("already_there.txt", null, function (f) {
	        // 获取文件already_there.txt
	         getAsText(f.file());
	}, function(err) {
     		 // 获取文件出错
	});
	}, function(err) {
	        // 获取虚拟文件系统失败
	});

1.5 promise对象

Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。

简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。比如,f1的回调函数f2,可以写成:

f1().then(f2)

function f1() {
	return new Promise((resolve,reject) => {
		console.log("f1")
		setTimeout(() => {
			resolve("f2")	
		},1000)
	})
}

function f2(res) {
	console.log(res)
}

先打印f1,间隔一秒后打印f2

这样写的优点在于,回调函数变成了链式写法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以实现许多强大的功能。

比如,指定多个回调函数:

 f1().then(f2).then(f3);

再比如,指定发生错误时的回调函数:

f1().then(f2).fail(f3);

1.6 Generator 函数

关于Generator 函数更多知识

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

总结一下在同步与异步中关于变量的操作:

// 1. 什么时候使用全局变量 : 就是这个变量在后面很多地方都要用到就会使用全局变量
// 2. 什么时候使用return返回值 : 这个函数是同步执行能够拿到返回值
// 3. 什么时候使用回调函数  :  这个函数在异步执行的时候就要使用回调函数来获取私
有变量

案例:回调函数加ajax的事件响应函数

function sendAjax (pageId,callback){
		var baseURL = 'http://localhost:9090/api/';
		$.ajax({
			url : baseURL + 'getmoneyctrl',
			dataType : 'json',
			data : {
				pageid : pageId
			},
			success : function(obj) {
				//obj是ajax响应成功返回的数据
				callback(obj);
			} 
		})
	}
}

//回调函数
function callback (obj) {
	doSomething.....
}

上面的代码用在,发送ajax的路径一定,但是对于返回数据操作却不同的地方.

注意: 数组遍历的API(forEach,map)虽然利用回调来取值,但是却不是异步的,程序是同步执行的

[1,2,3,4].forEach(_ => {
	setTimeout(() => {
		console.log(_)
	},5000)
})

上面的代码5秒后一次性打印数组里的每一项

改造成递归的方式就可以,每隔随机的时间打印对应的元素

function recurTest(j, length){
		setTimeout(function(){
			console.log("第"+(j+1)+"次循环");
			if(++j < length){
				recurTest(j, length);
			}
		}, Math.random() * 3000);
}
recurTest(0, 5)

例题1 :

function fn() {
	console.log('frist')
	setTimeout(function(){
		console.log('second')
	},0)
}

for(let i = 0; i < 999; i++) {
	fn()
}

这段程序里面for循环每次执行都会遇到同步程序console,以及异步程序settTimeout那么实际的代码执行是:

  1. 先打印999次first
  2. 再打印999次second
    程序会先执行完同步程序再执行异步程序
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值