无限滚动(Infinite Scroll)是一种很常见的用户体验模式,它建议用户在Web页面或应用程序加载显示很少的内容。当用户开始向下滚动页面时,会加载更多内容。这些内容是通过向负责提供内容的服务器发出请求来异步加载的。在这篇文章中,我将讨论JavaScript的异步操作以及Vue如何实现无限滚动效果。在这个过程中,我们将看到一个使用无限增发动的简单页面。
理解异步操作
在程序中编写一段同步代码,比如下面的例子,有两行代码: L1
和 L2
。如果 L1
未结速, L2
是不会执行的:
console.log('quavo'); console.log('takeoff');
通常情况下,我们会看到上面的代码执行的顺序,那是因为 $http.get
请求需要一些时间才能从 our_url
获取 data
,JavaScript并不会花时间去等,而是在等待 $http.get
时执行下一行代码。完成它所做的事情,这样就可以在控制台上进行日志记录。异步写代码的方法还包括:
setTimeout()
函数,它可以先执行后面的一些事情,然后再执行 setTimeout()
中的代码。比如:
console.log('我先执行') setTimeout(() => { console.log('是的,我等待3s后才执行') },3000) console.log('对了,我会第二个执行')
高阶函数(也称为回调函数)是作为参数传递给其他函数的函数。让我们假设有一个名为 function X
的回调函数,它被当做一个参数传递给另一个函数 function Y
。最终,函数 X
被执行或调用内部 Y
函数。比如下面的代码:
function Y(X) { $.get("our_url", X); }
来看一个实例:
var add = function (a, b) { return a + b; } function math (func, array) { return func(array[0] , array[1]); } console.log(math(add, [1, 2])) // => 3
上面的例子中传进去的 add
是一个参数,而在 return
的时候刚是一个函数。
高阶函数存在于不同的模式中,很有可能你在不知道的情况下就使用它们。比如 window.onclick
、 setTimeout()
和 setInterval()
。除此之外,还有一些常见的高阶函数例子。比如 jQuery ajax
回调函数:
$.ajax({ url: '//localhost:8000/api/v1/entry/1', type: 'GET', dataType: 'json', success: function (data) { // success 接收回调函数 console.log(data) } })
setTimeout()
和 setInterval()
这样的计时器函数也是高阶函数:
setTimeout(function (){ console.log('是的,我会在3s后执行') },3000) var i = 0; setInterval(function (){ i++; console.log(`我现在的值是:${i}`) }, 100)
在数组中的 sort()
、 map()
、 reduce()
和 filter()
等函数都是高阶函数的示例:
var arr = [2, 8, 20, 5, 17, 38, 21]; // 升序 arr.sort(function (x, y) { return x - y }) // 降序 arr.sort(function (x, y) { return y - x }) // 数据所有元素进行求平方得到新数组 arr.map(function (item) { return Math.pow(item, 2) }) // 数组求和 arr.reduce(function (x, y) { return x + y }) // 过滤掉数组中的偶数,只留奇数,返回一个新数组 arr.filter(function (x) { return x % 2 !== 0 })
有关于高阶函数更多介绍,可以阅读下面的内容:
- JavaScript 高阶函数介绍
- 高阶函数(Higher-order function)
- JavaScript高阶函数
- Higher-Order Functions
- Higher Order Functions
- Higher-Order Functions in JavaScript
- Higher order functions
- Understand JavaScript Callback Functions and Use Them
什么是观察者
Vue观察者允许我们在更改数据时执行异步操作。它们就像是在Vue实例中对数据做更改,而视图做出相应的反应。在我们的Vue实例中,观察者用 watch
关键词表示,并因此被使用:
let app = new Vue({ el: '#app', data () { return { // 和view绑定的数据 } }, watch: { // 将在app中使用的异步操作 } })
扩展观察者的异步操作
让我们看看Vue如何使用观察者来监视异步操作。使用Vue构建一个具有无限滚动特性的应用程序,一旦用户到达页面的底部,就会执行 GET
请求,并检索更多的数据。这篇文章的示例是来自于 @Sarah Drasner 的原始案例(我是她的超级粉丝)。接下来看它是如何工作的。
首先使用 <script>
标签导入必要的库。在这里,我们将导航Vue和 Axios ,这是一个基于HTTP客户端的浏览器。
<head> <script src="https://unpkg.com/vue"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> </head>
如果你在Codepen上写的话,可以直接在JavaScript设置中导入相关的脚本,如下图所示:
创建一个新的Vue实例:
let app = new Vue({ // el属性是一个挂载器,指向index.html中的#app的DOM元素 el: '#app', })
创建 data()
函数,并附加需要绑定到DOM的数据属性。最初我们是不会在页面的底部,因此 bottom
的值设置为 false
。另外设置 beers
属性,先设置为一个空数组。
let app = new Vue({ el: '#app', data () { return { bottom: false, beers: [] } } })
接下来使用 methods
属性创建所需要的方法。 methods
允许我们创建函数,并将事件绑定到这些函数以及处理相关的事件。在 bottomVisible()
函数中,我们使用三个只读属性手动创建无限滚动相关的特性:
-
scrollY
:返回滚动条距离viewport
顶部边缘的Y
坐标。如果没有离开viewport
,返回的值为0
-
clientHeight
:无素可视区高度,包括padding
,但不包括水平滚动条高度、border
或margin
-
scrollHeight
:元素内容的高度,包括由于溢出在屏幕上不可见的内容
有关于这方面的相关属性的介绍,可以阅读前段时间整理的一篇笔记《 视口宽高、位置与滚动高度 》。
let app = new Vue({ el: '#app', data () { return { bottom: false, beers: [] } }, methods: { bottomVisible () { const scrollY = window.scrollY const visible = document.documentElement.clientHeight const pageHeight = document.documentElement.scrollHeight const bottomOfPage = visible + scrollY >= pageHeight return bottomOfPage || pageHeight < visible } } })
在 methods
中继续添加另一个函数 addBeer()
,我们使用Axios执行 GET
请求。使用 Promises
和 callback
,创建一个 apiInfo
对象,并从API中调用检索值传给它。我们的Vue实例中的每个函数都可以使用 this
访问 data
属性。
let app = new Vue({ el: '#app', data () { return { bottom: false, beers: [] } }, methods: { bottomVisible () { const scrollY = window.scrollY const visible = document.documentElement.clientHeight const pageHeight = document.documentElement.scrollHeight const bottomOfPage = visible + scrollY >= pageHeight return bottomOfPage || pageHeight < visible }, addBeer () { axios.get('https://api.punkapi.com/v2/beers/random') .then(response => { let api = response.data[0] let apiInfo = { name: api.name, desc: api.description, img: api.image_url, tips: api.brewers_tips, tagline: api.tagline, food: api.food_pairing } this.beers.push(apiInfo) if (this.bottomVisible()) { this.addBeer() } }) } } })
有关于Vue中的 methods
相关的知识可以阅读前段时间整理的相关学习笔记《Vue的Methods》、《 Vue的Methods和事件处理 》和《 在Vue中何时使用方法、计算属性或观察者 》。
在 watch
属性中添加相应的观察者,用来监视应用程序状态的变化,并相应的更新DOM:
let app = new Vue({ el: '#app', data () { return { bottom: false, beers: [] } }, methods: { bottomVisible () { const scrollY = window.scrollY const visible = document.documentElement.clientHeight const pageHeight = document.documentElement.scrollHeight const bottomOfPage = visible + scrollY >= pageHeight return bottomOfPage || pageHeight < visible }, addBeer () { axios.get('https://api.punkapi.com/v2/beers/random') .then(response => { let api = response.data[0] let apiInfo = { name: api.name, desc: api.description, img: api.image_url, tips: api.brewers_tips, tagline: api.tagline, food: api.food_pairing } this.beers.push(apiInfo) if (this.bottomVisible()) { this.addBeer() } }) } }, watch: { bottom (bottom) { if (bottom) { this.addBeer() } } } })
有关于Vue中的观察者相关的知识,可以阅读前段时间整理的学习笔记:《Vue的观察者》。
接下来再使用Vue的生命周期的钩子 created
添加一个 scroll
滚动事件,每次调用 bottomVisible()
函数时触发一个滚动事件。为了实现无限滚动特性,将 data
函数中的 bottom
值设置为 bottomVisible()
函数。 created
钩子允许我们访问反应性数据和Vue实例中的函数。
let app = new Vue({ el: '#app', data () { return { bottom: false, beers: [] } }, methods: { bottomVisible () { const scrollY = window.scrollY const visible = document.documentElement.clientHeight const pageHeight = document.documentElement.scrollHeight const bottomOfPage = visible + scrollY >= pageHeight return bottomOfPage || pageHeight < visible }, addBeer () { axios.get('https://api.punkapi.com/v2/beers/random') .then(response => { let api = response.data[0] let apiInfo = { name: api.name, desc: api.description, img: api.image_url, tips: api.brewers_tips, tagline: api.tagline, food: api.food_pairing } this.beers.push(apiInfo) if (this.bottomVisible()) { this.addBeer() } }) } }, watch: { bottom (bottom) { if (bottom) { this.addBeer() } } }, created () { window.addEventListener('scroll', () => { this.bottom = this.bottomVisible() }) this.addBeer() } })
有关于Vue实例和生命周期相关的知识可以阅读前段时间整理的学习笔记《Vue实例和生命周期》。
现在把注意力集中到DOM中,将使用Vue指令让DOM和Vue实例之间实现数据的双向绑定:
-
v-if
:根据条件的布尔值,有条件的渲染DOM元素 -
v-for
:基于数组循环遍历出项目列表,比如beer in beers
,其中beer
是被迭代的数组元素的别名
有关于Vue的指令相关的介绍,可以阅读:
示例中的DOM这样写:
<div id="app"> <section> <h1>Make yourself some Punk Beers</h1> <!-- beers数组值为空时,显示正在加载中的状态 --> <div class="beer-container"> <div v-if="beers.length === 0" class="loading">Loading...</div> <!-- 对beers数组进行迭代 --> <div v-for="beer in beers" class="beer-contain"> <div class="beer-img"> <img :src="beer.img" height="350" /> </div> <div class="beer-info"> <h2>{{ beer.name }}</h2> <div class="beer-description"> <p><span class="bright">Description:</span> {{ beer.desc }}</p> </div> </div> </div> </div> </section> </div>
这个时候你的页面可以看到这样的结果:
因为还没有添加任何样式,看上去丑丑的。如果添加了样式之后,就可以看到下面这样的效果:
注意,我在@Sarah Drasner的示例上删除了一些字段,只是为了样式上看上去好看一点。原始示例的效果和源码, 点击这里可以看到 。
这个时候你滚动到页面的底部的时候就可以无限加载内容。这个效果也就是我们所说的无限滚动效果:
总结
有人可能会问,为什么我们不直接使用Vue的 computed
属性呢?原因是 computed
属性是同步的,必须返回一个值。当执行类似 timeout
函数之类的异步操作,或者像上面示例中 GET
请求时,最好使用Vue的 watch
,因为Vue会侦听函数的返回值。使用事件监听器也很酷,但这些都有手工处理事件和调用方法而不是只监听数据更改的缺点。无论如何,当你看到或想要在Vue中使用它们时,你现在知道如何处理异步操作了吧。
特别声明,本文整个思路是跟着 @Chris Nwamba 的博文《 Simple Asynchronous Infinite Scroll with Vue Watchers 》学习整理的。文章有关于Vue的示例代码,都源于此文。
由说作者是Vue相关的初学者,如果文章中有不对之处,还请各路大婶拍正。如果你有更好的建议或者想法,欢迎在下面的评论中与我们一起分享。

大漠
常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《 图解CSS3:核心技术与案例实战 》。
如需转载,烦请注明出处: https://www.w3cplus.com/vue/simple-asynchronous-infinite-scroll-with-vue-watchers.html
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。