javascript
1. 原型/构造函数/实例
原型(prototype):一个简单的对象,用于实现对象的 属性继承。可以简单的理解成对象的爹。
每个JavaScript对象中都包含一个 __proto__的属性指向它爹(该对象的原型),可用 obj.__proto__访问。
构造函数:可以通过new来新建一个对象的函数。
实例:通过构造函数和new创建出来的对象,便是实例。实例通过__proto__指向原型,通过constructor指向构造函数。
例如:
//实例
const instance = new Object();
//原型
const prototype = Object.prototype;
三者关系:
实例.__proto__ === 原型
原型.constructor === 构造函数
构造函数.prototype === 原型
2、原型链
原型链是由原型对象组成,每个对象都有 __proto__属性,指向了创建该对象的构造函数的原型,__proto__将对象连接起来组成了原型
链。
2.1、属性查找机制:当查找对象的属性时,如果实例对象自身不存在该属性,则沿着原型链往上一级查找,找到时则输出,不存在时,则继续
沿着原型链往上一级查找,直至最顶级的原型对象 Object.prototype,如还是没找到,则输出 undefined 。
2.2、属性修改机制:只会修改实例对象本身的属性,如果不存在,则进行添加该属性,如果需要修改原型的属性时,则可以用:
b.prototype.x = 2;但是这样会造成所有继承该对象的实例的属性发生改变。
3、执行上下文(EC)
执行上下文可以理解为一个对象:
它包含三个部分:变量对象(VO) , 作用域链(词法作用域) , this指向 。
它的类型:全局执行上下文 , 函数执行上下文 , eval执行上下文 。
代码执行过程:
创建全局上下文(global EC) , 全局执行上下文(caller)逐行 自上而下 执行。遇到函数时,函数执行上下文被 push 到执行栈顶层。
函数执行上下文被激活,成为 active EC,开始执行函数中的代码,caller被挂起。
函数执行完后,callee 被 pop移除出执行栈,控制权交还全局上下文(caller),继续执行。
变量对象:它是执行上下文中的一部分,可以抽象为一种 数据作用域,其实也可以理解为就是 一个简单的对象,它存储着该执行上下文中的所有变量
和函数声明(不包含函数表达式)。
活动对象(AO):当变量对象所处的上下文为 active EC 时,称为 活动对象。
作用域:执行上下文中还包含作用域链。作用域可以理解为该上下文中声明的 变量和声明的作用范围。可分为块级作用域和函数作用域。
特性:
声明提前:一个声明在函数体内都是可见的,函数优于变量。
非匿名自执行函数,函数变量为 只读 状态,无法修改。
作用域链:可以理解为一组对象列表,包含父级和自身的变量对象。因此我们可以通过作用域链访问到父级里声明的变量或者函数。
由两部分组成:[[scope]]属性:指向父级变量对象和作用域链,也就是包含了父级的[[scope]]和AO。
AO:自身活动对象。
5、闭包
它属于一种特殊的作用域,称为 静态作用域。他的定义理解为:父函数被销毁的情况下,返回出的子函数的[[scope]]中仍然保留着父级的单变量
对象和作用域链,因此可以继续访问到父级的变量对象,这样的函数称为闭包。
闭包会产生一个很经典的问题:
多个子函数[[scope]]都是同时指向父级,是完全共享的。因此当父级的变量对象被修改时,所有子函数都受到影响。
解决:
变量可以通过函数参数的形式传入,避免使用默认的[[scope]]向上查找。
使用setTimeout包裹,通过第三个参数传入。
使用 块级作用域 ,让变量成为自己上下文的属性,避免共享。
6、script引入方式
html静态<script>引入。
js动态插入<script>
<script defer>:异步加载,元素解析完成后执行
<script async>:异步加载,但执行时会阻塞元素渲染
7、对象的拷贝
浅拷贝:以赋值的形式拷贝对象,仍指向同一个地址,修改时原对象也会受到影响。
Object.assign , 展开运算符(...)
深拷贝:完全拷贝一个新对象,修改时原对象不再收到任何影响。
JSON.parse(JSON.stringify()):性能最快,具有循环引用的对象时,报错。 当值为函数,undefined,symbol时,无法拷贝。
递归进行逐一赋值。
8、new运算符的执行过程
新生成一个对象
链接到原型: obj.__proto__ = Con.prototype
绑定 this: apply
返回新对象(如果构造函数有自己 return时,则返回该值)
9、instanceof 原理
能在实例的 原型对象链中找到该构造函数的prototype属性所指向的原型对象,就返回true。
//__proto__:代表原型对象链
instance.[__proto__...] === instance.constructor.prototype
//return ture
10、代码的复用(再次提醒自己,任何代码开始写第二遍时,就要开始考虑如何复用)
函数封装
继承
复制extend
混入 mixin
借用 apply/call
11、继承
继承通常指的是:原型链继承,通过指定原型,并可以通过原型链继承原型上的属性或者方法。
最优化:圣杯模式
var inherit = (function(c,p){
var F = function(){};
return function(c,p){
F.prototype = p.prototype;
c.prototype = new F();
c.uber = p.prototype;
c.prototype.constructor = c;
}
})()
ES6语法糖 class/extends
12、类型转换
- , * , / ,% :一律转换成数值后计算
+:
数字 + 字符串 = 字符串,运算顺序是从左到右。
数字 + 对象 ,优先调用对象的 valueOf > toString
数字 + boolean/null : 数字
数字 + undefined : NaN
[1].toString() === '1'
{}.toString() === '[object object]'
NaN !== NaN , +undefined 为 NaN
13.模块化
分类:
es6: import / export
commonjs: require / module.exports / exports
amd:require / defined
require 与 import 的区别:
require支持动态导入,import不支持。
require 是 同步 导入,import 属于 异步 导入。
require 是 值拷贝,导出值变化不会影响导入值,import 指向 内存地址,导入值会随着导出值而变化。
14、防抖与节流
它们是一种常用的 高频触发优化方式,能对性能有较大的帮助。
防抖(debounce):将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可。
function debounce(fn,wait,immediate){
let timer = null;
return function(){
let args = arguments;
let context = this;
if(immediate && !timer){
fn.apply(context,args);
}
if(timer) clearTimeout(timer);
timer = setTimeout(()=>{
fn.apply(context,args);
},wait)
}
}
节流(throttle):每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景:滚动条事件或者resize事件,
每隔100-500ms执行一次即可。
function throttle(fn,wait,immediate){
let timer = null;
let callNow = immediate;
return function(){
let context = this,args = arguments;
if(callNow){
fn.apply(context,args);
callNow = false;
}
if(!timer){
timer = setTimeout(()=>{
fn.apply(context,args)
timer = null;
},wait)
}
}
}
15、函数执行改变this
三种方式手动修改this指向:
call:fn.call(target,1,2)
apply:fn.apply(target,[1,2]);
bind:fn.bind(target)(1,2)
16、ES6/ES7
声明:
let/const: 块级作用域,不存在变量提升,暂时性死区,不允许重复声明
const:声明常量,无法修改
解构赋值
class/extend:类声明与继承
Set/Map:新的数据解构
异步解决方案:
promise的使用与实现
generator:
yield:暂停代码
next():继续执行代码
await/async 是 generator 的语法糖,babel是基于 promise 实现。
浏览器:
1、跨标签页通讯
不同标签页间的通讯,本质原理就是去运用一些可以 共享的中间介质,如下面方法:
1.1、通过父页面 window.open() 和子页面 postMessage
异步下,通过 window.open('about:blank') 和 tab.location.href='*'
1.2、设置同域下共享的 localStorage 与 监听 window.onstorage
重复写入相同的值无法触发。
会受到浏览器隐身模式等的限制
1.3、设置共享 cookie 与不断轮询脏检查(setInterval)
1.4、借助服务端或者中间层实现
2、浏览器架构
用户界面
主进程
内核:
渲染引擎
JS引擎:
执行栈
事件触发线程:
消息队列:
微任务
宏任务
网络异步线程
定时器线程
3、浏览器下事件循环(Event Loop)
事件循环是指:执行一个宏任务,然后执行清空微任务列表,循环再执行宏任务,再清微任务列表。
微任务 microtask(jobs): promise / ajax / Object.observe(已废弃)
宏任务 macrotask(jobs): setTimeout / script / IO / UI Rendering
4、从输入url 到展示的过程
DNS解析
TCP三次握手
发送请求,分析url,设置请求报文(头,主体)
服务器返回请求的文件(html)
浏览器渲染
HTML parser -->DOM Tree
标记化算法,进行元素状态的标记
dom树构建
CSS parser -->Style Tree
解析css代码,生成样式树
attachment -->Render Tree
结合dom树与style树,生成渲染树
layout:布局
GPU painting:像素绘制页面
5、重绘与回流
当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。
重绘(repaint):当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少。
回流(reflow):当元素的尺寸,结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此
是较重的操作。会触发回流的操作:
页面初次渲染
浏览器窗口大小改变
元素尺寸,位置,内容发生改变
元素字体大小变化
添加或者删除可见的dom元素
激活css伪类(例如: :hover)
查询某些属性或调用某些方法:
clientWidth,clientHieght,clientTop,clientLeft
offsetWidth,offsetHeight,offsetTop,offsetLeft
scrollWidth,scrollHeight,scrollTop,scrollLeft
getComputedStyle()
getBoundingClientRect()
scrollTo()
回流必定触发重绘,重绘不一定触发回流。重绘的开销较小,回流的代价较高。
最佳实践:
css:
避免使用table布局
将动画效果应用到position属性为absolute 或 fixed的元素上
js:
避免频繁操作样式,可汇总后统一 一次修改。
尽量使用class进行样式修改
减少dom的增删次数,可使用字符串或者 documentFragment 一次性插入。
极限优化时,修改样式可将其 display:none 后修改
避免多次触发上面提到的那些会触发回流的方法,可以的话尽量用变量存住。
6、存储(可分为:短暂性存储和持久性存储)
短暂性的时候,我们只需要将数据存在内存,只在运行时可用。
持久性存储,可以分为 浏览器端 与 服务器端。
浏览器:
cookie:通常用于存储用户身份,登录状态等。
http中自动携带,体积上限为4k,可自行设置过期时间。
locaLStorage/sessionStorage:长久储存/窗口关闭删除,体积限制为4-5M
indexDB
服务器:
分布式缓存 redis
数据库
7、Web Worker
现代浏览器为 JavaScript 创造的多线程环境。可以新建并将部分任务分配到 worker 线程并行运行,两个线程可 独立运行,互不干扰,可通过自带的
消息机制 相互通信。
用法:
//创建 worker
const worker = new Worker('work.js');
//向 worker 线程推送消息
worker.postMessage('hello world');
//监听 worker 线程发送过来的消息
worker.onmessage = function(event){
console.log('received message'+event.data);
}
限制:
同源限制
无法使用 document / window / alert / confirm
无法加载本地资源
8、内存泄露
意外的全局变量:无法被回收
定时器:未被正确关闭,导致所引用的外部变量无法被释放
事件监听:没有正确销毁
闭包:会导致父级中的变量无法被释放
dom引用:dom元素被删除时,内存中的引用未被正确清空
chrome中的timeline进行内存标记,可视化查看内存的变化情况,找出异常点。
服务端与网络
1、http / https 协议
1.0、协议缺陷:
无法复用链接,完成即断开,重新慢启动 和 TCP 3次握手
head of line blocking:线头阻塞,导致请求之间互相影响
1.1、改进:
长连接(默认 keep-alive),复用
host字段指定对应的虚拟站点
新增功能:
断点续传
身份认证
状态管理
cache缓存
Cache-Control
Expires
last-Modified
Etag
2.0、
多路复用
二进制分帧层:应用层和传输层之间
首部压缩
服务端推送
https:较为安全的网络传输协议:
证书(公钥)
SSL加密
端口443
TCP:
三次握手
四次挥手
滑动窗口:流量控制
拥塞处理:
慢开始
拥塞避免
快速重传
快速恢复
缓存策略:可分为 强缓存 和协商缓存
2、常见状态码
1xx:接受,继续处理
200:成功,并返回数据
201:已创建
202:已接受
203:成功,但未授权
204:成功,无内容
205:成功,重置内容
206:成功,部分内容
301:永久移动,重定向
302:临时移动,可使用原有url
304:资源未修改,可使用缓存
305:需代理访问
400:请求语法错误
401:要求身份认证
403:拒绝请求
404:资源不存在
500:服务器错误
3、get/post
get:缓存,请求长度受限,会被历史保存记录
post:安全,大数据,更多编码类型
4、websocket
websockt是一个 持久化的协议,基于http,服务端可以主动push
new Websocket(url)
ws.onerror = fn
ws.onclose = fn
ws.onopen = fn
ws.onmessage = fn
ws.send()
5、TCP三次握手
建立连接前,客户端和服务端需要通过握手来确认对方:
客户端发送 syn(同步序列编号)请求,进入 syn_send 状态,等待确认
服务端接收并确认 syn包后发送 syn+ack 包,进入 syn_recv 状态
客户端接收 syn+ack包后,发送 ack包,双方进入 established 状态
6、TCP四次挥手
客户端 -- FIN --> 服务端,FIN-WAIT
服务端 -- ACK --> 客户端,CLOSE-WAIT
服务端 -- ACK,FIN --> 客户端,LAST-ACK
客户端 -- ACK --> 服务端,CLOSED
7、node 的 event loop:6阶段
timer阶段:执行到期的 setTimeout / setInterval 队列回调
I/O 阶段:执行上轮循环残流的 callback
idle,prepare
poll:等待回调
1、执行回调
2、执行定时器
2.1、如有到期的 setTimeout / setInterval ,则返回 timer阶段
2.2、如有 setImmediate,则前往check阶段
check:
执行 setImmediate
close callbacks
8、跨域:
JSONP:利用<script> 标签不受跨域限制的特点,缺点是只能支持get请求
function jsonp(url,jsonpCallback,success){
const script = document.createElement('script');
script.src = url;
script.async = true;
script.type = 'text/javascript';
window[jsonpCallback] = function(data){
success && success(data)
}
document.body.appendChild(script);
}
设置 CORS: Access-Control-Allow-Origin:'*'
postMessage
9、安全
XSS 攻击:注入恶意代码
cookie 设置 httpOnly
转义页面上的输入内容和输出内容
CSRF:跨站请求伪造,防护:
get不修改数据
不被第三方网站访问到用户的cookie
设置白名单,不被第三方网站请求
请求校验
vue
1、nextTick
在下次dom更新循环结束之后执行延迟回调,可用于获取更新后的dom状态。
macrotasks 任务的实现: setImmediate / MessageChannel / setTimeout
2、生命周期
_init_:
initLifecyle / Event,往 vm 上挂载各种属性
callHook: beforeCreated --实例刚创建
initInjection/initState: 初始化注入和data响应性
created: 创建完成,属性已经绑定,但还未生成真实dom
进行元素的挂载: $el / vm.$mount()
是否有 template: 解析成 render function
*.vue文件:vue-loader 会将 <template> 编译成 render function
beforeMount: 模板编译 / 挂载之前
执行 render function,生成真实的 dom,并替换到 dom tree 中
mounted:组件已挂载
update:
执行diff算法,比对改变是否需要触发UI更新
flushScheduleQueue:
watcher.before: 触发 beforeUpdate 钩子 - watcher.run():执行watcher中的notify,通知所有依赖项更新UI
触发 updated 钩子:组件已更新
actived / deactivated(keep-alive):不销毁,缓存,组件激活与失活。
destroy:
beforeDestroy:销毁开始
销毁自身且递归销毁子组件以及事件监听:
remove():删除节点
watcher.teardown():清空依赖
vm.$off():解绑监听
destroyed:完成后触发钩子
哇哦,下面见代码,看vue的初始化
new Vue({})
//初始化 vue 实例
function _init(){
}
3、数据响应(数据劫持)
数据响应的实现由两部分构成:观察者 watcher 和 依赖收集器 dep, 其核心是 defineProperty 这个方法,它可以重写属性的 get 与 set
方法,从而完成监听数据的改变。
Observe(观察者)观察 props 与 state:
遍历 props与state,对每个属性创建独立的监听器(watcher)。
使用 defineProperty 重写每个属性的 get/set(defineReactive):
get:收集依赖
Dep.depend():
watcher.addDep()
set:派发更新
Dep.notify()
watcher.update()
queenWatcher()
nextTick
flushScheduleQueue
watcher.run()
updateComponent()
见代码梳理:
let data = {a:1};
//数据响应性
observe(data)
//初始化观察者
new Watcher(data,'name',updateComponent)
data.a = 2;
//简单表示用于数据更新后的操作
function updateComponent(){
vm._update() // patchs
}
//监视对象
function observe(obj){
//遍历对象,使用 get/set 重新定义对象的每个属性值
Object.keys(obj).map(key => {
defineReactive(obj,key,obj[key])
})
}
function defineReactive(obj,k,v){
//递归子属性
if(type(v)=='object') observe(v)
//新建依赖收集器
let dep = new Dep()
//定义 get/set
Object.defineProperty(obj,k,{
enumerable:true,
configurable:true,
get:function reactiveGetter(){
//当有获取该属性时,证明依赖于该对象,因此被添加进收集器中
if(Dep.target){
dep.addSub(Dep.target)
}
return v
},
//重新设置值时,触发收集器的通知机制
set:function reactiveSetter(nV){
v = nV
dep.notify()
}
})
},
//依赖收集器
class Dep{
construtor(){
this.subs = [];
}
addSub(sub){
this.subs.push(sub)
}
notify(){
this.subs.map(sub=>{
sub.update()
})
}
}
Dep.target = null
//观察者
class Watcher{
constructor(obj,key,cb){
Dep.target = this;
this.cb = cb
this.obj = obj
this.key = key
this.value = obj[key]
Dep.target = null
}
addDep(Dep){
Dep.addSub(this)
}
update(){
this.value = this.obj[this.key]
this.cb(this.value)
}
before(){
callHook('beforeUpdate')
}
}
4、virtual dom 原理实现
创建dom树
树的diff,同层对比,输入 patchs(listDiff/diffChildren/diffProps):
没有新的节点,返回
新的节点 tagName 与 key 不变,对比props,继续递归遍历子树:
对比属性(对比新旧属性列表):
旧属性是否存在与新属性列表中
都存在的是否有变化
是否出现旧列表中没有的新属性
tagName 和 key值变化了,则直接替换成新节点
渲染差异:
遍历 patchs,把需要更改的节点取出来
局部更新 dom
见代码梳理:
//diff算法的实现
function dff(oldTree,newTree){
//差异收集
let pathchs = {}
dfs(oleTree,newTree,0,patchs)
return pathchs
}
function dfs(oldNode,newNode,index,pathchs){
let curPathchs = [];
if(newNode){
//当新旧节点的tagName和key值完全一致时
if(oldNode.tagName === newNode.tagName && oldNode.key === newNode.key){
//继续比对属性差异
let props = diffProps(oldNode.props,newNode.props)
curPathchs.push({type:'changeProps',props})
//递归进入下一层级的比较
diffChildrens(oldNode.children,newNode.children,index,pathchs)
}else{
//当tagName 或者 key 修改了后,表示已经时全新节点,无需再比
curPathchs.push({type:'replaceNode',node:newNode})
}
}
//构建出整颗差异树
if(curPathchs.length){
if(pathchs[index]){
pathchs[index] = pathchs[index].concat(curPathchs)
}else{
pathchs[index] = curPathchs
}
}
}
//属性对比实现
function diffProps(oldProps,newProps){
let propsPathchs = []
//遍历新旧属性列表,查找删除项,查找修改项,查找新增项
forin(oldProps,(k,v)=>{
if(!newProps.hasOwnProperty(k)){
propsPathchs.push({type:'remove',prop:k})
}else{
if(v !== newProps[k] ){
propsPathcchs.push({type:'chage',prop:k,value:newProps[k]})
}
}
})
forin(newProps,(k,v)=>{
if(!oldProps.hasOwnProperty(k)){
propsPathchs.push({type:'add',prop:k,value:v})
}
})
return propsPathchs
}
//对比子级差异
function diffChildrens(oldChild,newChild,index,pathchs){
//标记子级的删除、新增、移动
let {change,list} = diffList(oldChild,newChild,index,pathchs)
if(change.length){
if(pathchs[index]){
pathchs[index] = pathchs[index].concat(chage)
}else{
pathchs[index] = change
}
}
//根据key获取原本匹配的节点,进一步递归从头开始对比
oldChild.map((item,i)=>{
let keyIndex = list.indexOf(item,key)
if(keyIndex){
let node = newChild[keyIndex]
//进一步递归对比
dfs(item,node,index,pathchs)
}
})
}
//列表对比,主要也是根据key 值查找匹配项
//对比出新旧列表的新增,删除,移动
function diffList(oldList,newList,index,pathchs){
let chage = []
let list = []
const newKeys = getKey(newList)
oldList.map(v=>{
if(newKeys.indexOf(v.key) > -1){
list.push(v.key)
}else{
list.push(null)
}
})
//标记删除
for(let i=list.length-1;i>=0;i++){
if(!list[i]){
list.splice(i,1)
change.push({type:'remove',index:1})
}
}
//标记新增和移动
newList.map((item,i)=>{
const key = item.key
const index = list.indexOf(key)
if(index===-1 || key==null){
//新增
change.push({type:'add',node:item,index:i})
list.splice(i,0,key)
}else{
//移动
if(index !==i){
change.push({
type:'move',
form:index,
to:i
})
move(list,index,i)
}
}
})
return { change , list }
}
5、proxy 相比于 defineProperty 的优势
数组变化也能监听到。
不需要深度遍历监听
let data = {a:1}
let reactiveData = new Proxy(data,{
get:function(target,name){
//....
}
})
6、vue-router
mode:
hash
history
跳转
this.$router.push()
<router-link to=""></router-link>
占位
<router-view></router-view>
7、vuex
state:状态中心
mutations:更改状态
actions:异步更改状态
getters:获取状态
modules:将 state 分成 多个 modules ,便于管理
vue.js 源码揭秘网址:
https://ustbhuangyi.github.io/vue-analysis/v2/prepare/build.html#%E6%9E%84%E5%BB%BA%E8%84%9A%E6%9C%AC
算法:
//五大算法
贪心算法:局部最优解法
分治算法:分成多个小模块,与原问题性质相同
动态规划:每个状态都是过去历史的一个总结
回溯法:发现原先选择不优时,退回重新选择
分支限界法
基础排序算法:
冒泡排序:两两比较
function bubleSort(arr){
let len = arr.length;
for(let i=len;i>=2;i--){ //这个地方也可以直接写 i>=1
for(let j=0;j<=i-1;j++){ //对应的这里改成 j<=i 即可
if(arr[j] > arr[j+1]){
[arr[j],arr[j+1]] = [arr[j+1],arr[j]]
}
}
}
return arr;
}
插入排序:即将元素插入到已排序好的数组中
function insertSort(arr){
for(let i=0;i<arr.length;i++){
for(let j=i;j>0;j--){
if(arr[j] < arr[j-1]){
[arr[j],arr[j-1]] = [arr[j-1],arr[j]];
}else{
break;
}
}
}
return arr;
}
递归运用(费波那契数列):爬楼梯问题
初始在第一级,到第一级有1种方法( s(1)=1),到第二级也只有一种方法( s(2)=1 ),第三级(s(3) = s(1)+s(2))
function cStairs(n){
if(n ===1 || n===2){
return 1;
}else{
return cStairs(n-1) + cStairs(n-2)
}
}
数据树:
二叉树:最多只有两个子节点
完全二叉树
满二叉树:
深度为h,有n个节点,且满足 n=2^h -1
二叉查找树:是一种特殊的二叉树,能有效地提高查找效率 BST
小值在左,大值在右
节点n的所有左子树值小于n,所有右子树值大于n。
...
天平找次品
有n个硬币,其中1个为假币,假币重量较轻,有一把天平,需要称多少次能保证一定找到假币?
三等分算法:
1、将硬币分成3组,随便取其中两组天平称重
平衡,假币在剩余那组,取其继续1循环
不平衡,假币在轻的那一组,取其继续1循环。
//记录小程序里面有个api获取滚动距离
wx.createSelectorQuery().select('#container').boundingClientRect(function(rect){ //container这个容器一定
//是最大那个div。
if(rect){
bottom = Math.abs(rect.bottom);
if(parseInt(bottom) == windowHeight){ //这里的windowHeight是通过wx.getSystemInfo()获取的。
//然后这里可以做处理了
//这里表示 滚动到底部了
}else{
}
}
})
参考链接:https://juejin.im/post/6844903776512393224#heading-13