单线程、异步、Promise、Vdom

249 阅读3分钟

问题思考

  • 什么是单线程,和异步什么关系
  • 什么是event-loop
  • 是否用过jQuery的Defferred
  • Promise的基本使用和原理
  • 介绍一下async/await(和promise的区别、联系)
  • 总结当前JS解决异步的方案

什么是单线程,和异步的关系

  • 单线程:只有一个线程,同时只能做一件事,两段js不能同时执行
  • 原因:避免DOM渲染的冲突
  • 解决方案:异步

event-loop (事件轮询)

  • 事件轮询,JS实现异步的具体解决方案
  • 同步的代码,直接执行
  • 异步函数先放在异步队列
  • 待同步函数执行完毕,轮询执行异步队列的函数

jQuery 的 Deferred (延迟)

  • 无法改变JS异步和单线程的本质
  • 只能从写法上杜绝callback这种形式
  • 他是一种语法糖形式,但是解耦了代码
  • 很好的体现:开放封闭原则(对扩展开放,对修改封闭)
//jQuery1.5之前版本的ajax请求
var ajax = $.ajax({
    url: './data.json',
    success: function () {
        console.log('success1')
        console.log('success2')
        console.log('success3')
    },
    error: function () {
        console.log('fail')
    }
})   
console.log(ajax)//返回的是一个XHR对象

//jQuery1.5之后版本的ajax请求
var ajax = $('./data.json')
ajax.done(function () {
    console.log('success a')
}).fail(function () {
    console.log('error')
}).done(function () {
    console.log('success b')
})
console.log(ajax) //返回一个deferred对象

//当然1.5之后还可以按以下方式书写,类似今后的promise书写方式
var ajax = $('./data.json')
ajax.then(function () {
    console.log('success a')
}, function () {
    console.log('error1')
}).then(function () {
    console.log('success b')
}, function () {
    console.log('error2')
})

如何简单封装,使用deferred

function waitHandle() {
    var dtd = $.Deferred()
    var wait = function (dtd) {
        var task = function () {
            console.log('执行完成')
            dtd.resolve()
            //console.log('执行失败')
         	//dtd.reject()	   
        }
        setTimeout(task, 2000)
        return dtd.promise() //注意这里返回的是primise对象而不是deferred对象
    }
    return wait(dtd)
}

//使用
var w = waitHandle()
$.when(w)
.then(function () {
    console.log('ok 1')
})
.then(function () {
    console.log('ok 2')
})

Promie的基本使用和原理

  • promise语法
function loadImg(src) {
    const promise = new Promise(function (resolve, reject) {
        var img = document.createElement('img')
        img.onload = function () {
            resolve(img)
        }
        img.onerror = function () {
            reject()
        }
        img.src = src
    })
    return promise
}
var src = './logo.png'
var result = loadImg(src)
result.then(function (img) {
    console.log(img.width)
    return img
}, function () {
    console.log('fail')
}).then(function (img) {
    console.log(img.height)
})
  • 如果在低级浏览器上不支持promise,则在之前引入bluebird.js,这样高低级浏览器都会支持promise

    cdn.bootcss.com/bluebird/3.…



问题

  • vdom是什么?为什么会存在
  • vdom如何应用,核心API是什么
  • 介绍一下diff算法

什么是vdom

  • virtual dom ,虚拟DOM
  • 用js模拟DOM结构
  • DOM变化的对比,放在js层来做(图灵完备语言)
  • 提高重绘性能
<ul id="list">
	<li class="item">item1</li>
	<li class="item">item2</li>
</ul>
//例子1:
//用js模拟以上DOM结构
{
    tag: 'ul',
    attrs: {
        id: 'list'
    },
    children: [
        {
            tag: 'li',
            attrs: { className: 'item' },
            children: ['item1']
        }, {
            tag: 'li',
            attrs: { className: 'item' },
            children: ['item2']
        }
     ]
}

用jQuery渲染一段数据成表格,改变数据内容,表格跟着改变

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<div id="container"></div>
	<button id="btn-change">change</button>
	<script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
	<script type="text/javascript">
		var data1 = [
			{
				name: '张三',
				age: '20',
				address: '北京'
			}, {
				name: '李四',
				age: '21',
				address: '上海'
			}, {
				name: '王五',
				age: '24',
				address: '北京'
			}
		]

		function render(data) {
			var $container = $('#container');
			$("#container").html('');
			var $table = $('<table>');
			$table.append($('<tr><th>name</th><th>age</th><th>address</th></tr>'));
			data.forEach(function (item) {
				$table.append($('<tr><td>' + item.name + '</td><td>' + item.age + '</td><td>' + item.address + '</td></tr>'));
			})
			$container.html($table);
		}
		render(data1);
		$("#btn-change").click(function () {
			data1[0].age = 29;
			data1[1].address = '湖南';
			render(data1)
		})
	</script>
</body>
</html>

以上操作遇到的问题

  • DOM操作是昂贵的,js运行效率高
  • 尽量减少DOM操作,而不是推倒重来
  • 项目越复杂,影响越严重
  • vdom能解决此问题

vdom如何应用,核心API是什么

snabbdom
  • 是可实现vdom操作的方法之一
  • 俩重要函数:h函数,patch函数
//用h函数表示例子1的js树结构
var vnode = h('ul#list', {}, [
	h('li.item', {}, 'item1'),
	h('li.item', {}, 'item2')
])

//参数1:标签名+id+class
//参数2:标签的属性,例如{on: {clcik: someFn}} 或者 {props:{href: '/foo'}} 或者 {style: {fontWeight: 'normal', fontSize: 'italic'}} 等;
//参数3:标签里面内容
//patch函数
var container = document.getElementById('container')
patch(container, vnode)

//模拟改变
var btnChange = document.getElementById('btn_change')
btnChange.addEventListener('click', function () {
    var newVnode = h('ul#list', {}, [
		h('li.item', {}, 'item1'),
		h('li.item', {}, 'itemB'),
		h('li.item', {}, 'item3')
	])
    patch(vnode, newVnode)
})
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>使用snabbdom来渲染</title>
</head>
<body>
	<div id="container"></div>
	<button id="btn-change">change</button>
	<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script>
	<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script>
	<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script>
	<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script>
	<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.js"></script>
	<script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script>
	<script type="text/javascript">
		var snabbdom = window.snabbdom

		// 定义 patch
		var patch = snabbdom.init({
			snabbdom_class,
			snabbdom_props,
			snabbdom_style,
			snabbdom_eventlisteners
		})

		// 定义 h
		var h = snabbdom.h

		var container = document.getElementById('container')
		// 生成vnode
		var vnode = h('ul#list', {}, [
			h('li.item', {}, 'item1'),
			h('li.item', {}, 'item2')
		])
		patch(container, vnode)

		var btnChange = document.getElementById('btn-change')
		btnChange.addEventListener('click', function () {
		    var newVnode = h('ul#list', {}, [
				h('li.item', {}, 'item1'),
				h('li.item', {}, 'itemB'),
				h('li.item', {}, 'item3')
			])
		    patch(vnode, newVnode)
		})
	</script>

</body>
</html>

介绍下diff算法

  • 是linux的基础命令
  • vdom中使用siff算法是为了找出需要更新的节点
  • 实现:pctch(container, vnode) 和 patch(vnode, newVnode)