MVC设计模式简化了开发,降低代码的耦合性。
var model = {
data: null,
init(){}
fetch(){}
save(){}
update(){}
delete(){}
}
view = {
init() {}
template: 'hi
>
}
controller = {
view: null,
model: null,
init(view, model){
this.view = view
this.model = model
this.bindEvents()
}
render(){
this.view.querySelector('name').innerText = this.model.data.name
},
bindEvents(){}
}
MVVM 由 Model、View、 ViewModel 三部分构成
MVP 模式将 Controller 改名为 Presenter(呈现),同时改变了通信方向。它切断View跟Model之间的联系,由Presenter充当桥梁,做到View-Model之间通信的完全隔离,而其他各部分的的通信是双向的。
viewModel是把 model 和 view 连起来的连接器
。1.相同点是都是保存在浏览器端、且同源的。
2.cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递
,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存
。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下。
3.存储大小限制也不同,cookie数据不能超过4K
,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。这里需要注意的是cookie的数据大小是指单个cookie的大小,而localStorage的大小是单个域名下localStorage所占用的大小总和。
4.数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效; localStorage:始终有效,窗口或浏览器关闭也—直保存,因此用作持久数据,除非用户主动删除; cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭。
5.作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面; localstorage在所有同源窗口中都是共享的; cookie也是在所有同源窗口中都是共享的。
所谓的“同源策略”,最早是由 Netscape公司提出的一个安全策略,后来这就成为了浏览器最核心也最基本的安全功能,现在所有支持 JavaScript 的浏览器都会使用这个策略。
在同源策略中,要求 域名、协议、端口 3部分都要相同!!!
跨域:从一个网页去请求另一个网页的资源,只要协议、域名、端口其中一个不同,就被当作是跨域
在请求时,如果出现了以下情况中的任意一种,那么它就是跨域请求:
1.协议不同,如 http 和 https;
2.域名不同;
3.端口不同。
解决方法
1、Proxy(代理) vue.config.js
可配置多个不同的proxy
devServer: {
proxy: {
'/api': {//代理标识,一般是每个接口前的相同部分
target: 'http://23.15.11.15:8000', // 这里写的是访问接口的域名和端口号
changeOrigin: true, // 允许跨域请求
pathRewrite: { // 重写路径,替换请求地址中的指定路径
'^/api': '/user'
}
},
'/login': {
target: 'http://23.15.11.15:8000',
changeOrigin: true,
pathRewrite:{
'^/login':'' //替换成空
}
}
}
},
2、jsonp处理跨域
单页 Web 应用 (single-page application 简称为 SPA) 是一种特殊的Web 应用。它将所有的活动局限于一个 Web 页面中
,仅在该 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成了,SPA 不会因为用户的操作而进行页面的重新加载或跳转。取而代之的是利用 JavaScript 动态的变换 HTML 的内容
,从而实现 UI 与用户的交互。由于避免了页面的重新加载, SPA 可以提供较为流畅的用户体验。得益于 ajax,我们可以实现无跳转刷新,又多亏了浏览器的 histroy 机制,我们可以用 hash 的变化来推动界面变化,从而模拟元素客户端的单页面切换效果。
只有一个WEB主页面的应用,公共资源(js、css等)仅需加载一次,所有的内容都包含在主页面,对每一个功能模块组件化。单页应用跳转,就是切换相关组件,仅刷新局部资源。常用于PC端官网、购物等网站。
优点: 页面切换快
缺点: 首屏时间稍慢,SEO差
常用于PC端官网、购物等网站。
有多个独立的页面的应用,每个公共资源(js、css等)需选择性重新加载,多页面跳转刷新所有资源。常用于 app 或 客户端等。
页面跳转: 返回HTML
优点: 首屏时间快,SEO效果好
缺点: 页面切换慢
常用于 app 或 客户端等
当然任何东西都有两面性,以下是总结目前 SPA 的一些优缺点:
它的优点有三点:
(1)良好的交互式体验,内容的改变不需要重新加载整个页面
,避免了不必要的跳转和重复渲染
;
(2)基于上面所说,SPA 对服务器的压力小
;
(3)前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理。
它的缺点有四点:
(1)初次加载耗时多
,为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载(可以使用路由懒加载解决)
(2)由于单页应用在一个页面中显示,所以不可以使用浏览器自带的前进后退功能
,想要实现页面切换需要自己进行管理
(3)SEO 难度较大
,由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
(4)有兼容性问题,部分平台不支持pushstate
(注:需了解History.pushState())
浮动的定义: 非IE浏览器下,容器不设高度且子元素浮动时,容器高度不能被内容撑开。 此时,内容会溢出到容器外面而影响布局。这种现象被称为浮动(溢出)。
浮动的工作原理:
浮动元素脱离文档流
,不占据空间(引起“高度塌陷”现象)
浮动元素碰到包含它的边框或者其他浮动元素的边框停留
浮动元素可以左右移动,直到遇到另一个浮动元素或者遇到它外边缘的包含框。浮动框不属于文档流中的普通流,当元素浮动之后,不会影响块级元素的布局,只会影响内联元素布局。此时文档流中的普通流就会表现得该浮动框不存在一样的布局模式。当包含框的高度小于浮动框的时候,此时就会出现“高度塌陷”。
浮动元素引起的问题?
父元素的高度无法被撑开,影响与父元素同级的元素
与浮动元素同级的非浮动元素会跟随其后
若浮动的元素不是第一个元素,则该元素之前的元素也要浮动,否则会影响页面的显示结构
清除浮动的方式如下:
清除浮动
给父级div定义height属性
最后一个浮动元素之后添加一个空的div标签,并添加clear:both样式
包含浮动元素的父级标签
添加overflow:hidden
或者overflow:auto
使用 :after 伪元素。由于IE6-7不支持 :after,使用 zoom:1 触发 hasLayout**
通过new 一个 FormData() form对象
import axios from 'axios'
// 添加请求头
update (e) { // 上传照片
let self = this
let file = e.target.files[0]
/* eslint-disable no-undef */
let param = new FormData() // 创建form对象
param.append('file', file) // 通过append向form对象添加数据
param.append('chunk', '0') // 添加form表单中其他数据
console.log(param.get('file')) // FormData私有类对象,访问不到,可以通过get判断值是否传进去
let config = {
headers: {'Content-Type': 'multipart/form-data'}
}
// 添加请求头
axios.post('http://172.19.26.60:8081/rest/user/headurl', param, config)
.then(res => {
if (res.data.code === 0) {
self.ImgUrl = res.data.data
}
console.log(res.data)
})
}
AJAX 是一种用于创建快速动态网页的技术。
通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
优点
:减轻服务器的负担,按需取数据,最大程度的减少冗余请求,局部刷新页面,减少用户心理和实际的等待时间,带来更好的用户体验。
缺点
:AJAX大量的使用了javascript和ajax引擎,这些取决于浏览器的支持.在编写的时候考虑对浏览器的兼容性。”
行内元素和块级元素的区别:行内元素不会自动换行,设置宽高无效;对margin设置左右方向有效,而上下无效,padding设置都无效。块级元素可以自动换行,可以设置宽高;设置margin和padding都有效。
语义化的含义就是用正确的标签做正确的事情,html语义化就是让页面的内容结构化,便于对浏览器、搜索引擎解析;
在没有样式CCS情况下也以一种文档格式显示,并且是容易阅读的。
搜索引擎的爬虫依赖于标记来确定上下文和各个关键字的权重,利于 SEO。
使阅读源代码的人对网站更容易将网站分块,便于阅读维护理解。
构成: 结构层(HTML)、表示层(CSS)、行为层(JavaScript)
作用:HTML实现页面结构、CSS实现页面样式、JS实现业务逻辑功能
js 放在 中,如果不添加 async 或者 defer 时,当浏览器遇到 script 时,会阻塞 DOM 树的构建,进而影响页面的加载。当 js 文件较多时,页面白屏的时间也会变长。
在这个过程中,如果解析器遇到了一个脚本(script),它就会停下来,并且执行这个脚本,然后才会继续解析 HTML。如果遇到了一个引用外部资源的脚本(script),它就必须停下来等待这个脚本资源的下载,而这个行为会导致一个或者多个的网络往返,并且会延迟页面的首次渲染时间。
把 js 放到 里(一般在 的上面)时,由于 DOM 时顺序解析的,因此 js 不会阻塞 DOM 的解析。对于必须要在 DOM 解析前就要加载的 js,我们需要放在 中。
CSS
alt : 图片加载失败时,显示在网页上的替代文字
title :鼠标悬浮在图片上显示的文字
alt是必要属性,title非必要
css的样式规则是:由
选择器和声明块
两个基本部分组成的。选择器决定为哪些元素应用样式;声明块定义相应的样式,它包含在一对花括号内,有一条或多条声明组成,而每一条声明则由一个属性和一个值组成,中间用冒号隔开
伪类:将特殊的效果添加到特定选择器上。它是已有元素上添加类别的,不会产生新的元素。例如:
a:hover{color:#FF00FF;}
伪元素:在内容元素的前后插入额外的元素或样式,但是这些元素实际上并不在文档中生成。它们只在外部显示可见,但不会在文档的源代码中找到它们。例如:
p::before{content:"第一章";}
总结:伪类是用过在元素选择器上加入伪类改变元素状态,而伪元素通过对元素的操作进行元素的改变。
后面几种是不常用
盒子模型是css中的一种思维模型,即把网页内容看成一个盒子,这个盒子由外到内包括margin、border、padding、content。根据浏览器计算方式不同分为两种模型:标准模型(w3c模型)和怪异模型(又称IE模型)。
主要区别:宽高的定义不同
标准盒模型:box-sizing:content-box;
(盒子的宽高不包括margin和padding)
怪异盒模型:内容的宽 = 设置的宽 - padding的宽 - border的宽box-sizing:border-box; (盒子的宽高包括margin和padding)
display 属性规定元素应该生成的框的类型。使段落生出行内框,常用于页面布局,比较常见的属性值包括none、block、inline和inline-block
2.flex-direction设置主轴的方向
1.主轴与侧轴
2.属性值
1.row
2.row-reverse
3.column
4.column-reverse
3.justify-content设置主轴的子元素排列方式
属性值:
1.flex-start
2.flex-end
3.center
4.space-around
5.space-between
6.space-evenly
4.align-items设置侧轴上的子元素排列方式(单行)
1.flex-start
2.flex-end
3.center
4.stretch
5.flex-wrap设置子元素是否换行
1.nowrap
2.wrap
6.align-contnet设置侧轴上的子元素的排列方式(多行)
1.flex-star
2.flex-end
3.center
4.space-around
5.space-between
6.stretch
7.space-evenly
vue2.0核心api是Object.defineProperty,vue3.0是启用provy实现响应式
1、实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。并且可以拿到最新值通知订阅者。
2、实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
3、实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
4、实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
Object.defineProperty
let obj = {};
let data='default';
//第一个参数:定义属性的对象。
//第二个参数:要定义或修改的属性的名称。
//第三个参数:将被定义或修改的属性描述符。
Object.defineProperty(obj, "data", {
//获取值
get: function () {
console.log('调用了一次get');
return data;
},
//设置值
set: function (val) {
data = val;
console.log(val);
document.getElementById('result').innerHTML=val;
}
})
// 获取值调用get
document.getElementById('result').innerHTML = obj.data;
//赋值调用set
let flag = true;
document.getElementById("inputing").addEventListener('keyup',function(){
if(flag) obj.data = this.value;
})
// 中文输入法下,仅在选词后触发input事件
document.getElementById("inputing").addEventListener('compositionstart', function(e) {
flag = false;
});
document.getElementById("inputing").addEventListener('compositionend', function(e) {
flag = true;
});
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>forvuetitle>
head>
<body>
<input type="text" id="textInput">
输入:<span id="textSpan">span>
<script>
var obj = {},
textInput = document.querySelector('#textInput'),
textSpan = document.querySelector('#textSpan');
Object.defineProperty(obj, 'foo', {
set: function (newValue) {
textInput.value = newValue;
textSpan.innerHTML = newValue;
}
});
textInput.addEventListener('keyup', function (e) {
obj.foo = e.target.value;
});
script>
body>
html>
相同点:控制元素的 显示/隐藏
不同点:
(1)原理不同:
v-if 原理是动态设置元素 新增/删除
v-show 原理是设置元素样式,display
(2)编译区别
v-if 满足条件才编译
v-show 一定会编译
(3)性能不同
频繁切换 v-show 性能高于 v-if
作用:keep-alive可以在组件切换时,保存其包裹的组件的状态,使其不被销毁,防止多次渲染。
目的:可以使被包含的组件保留状态,或避免重新渲染。
顾名思义,单向数据流就是从一个组件单方向将数据流向它的内部组件
,也就是父组件的数据流向子组件中,但子组件不能将这个数据修改掉,只能请求父组件修改数据再传给子组件
,如要返回到父组件中修改然后重新流向子组件,从而达到更新数据的原理
虚拟dom
其实就是一个普通的JavaScript对象
,用来描叙试图上有哪些界面结构,并不生成界面
,我们可以在生命周期【mounted阶段】打印this._vnode,如下:
它描叙了该阶段是div,有 哪些子节点,哪些属性,它是采用一个js对象来描叙这些,但是它并不会显示在页面上。
- 可以针对不同的终端平台 输出不同的页面展示节点
比如:网页、微信小程序、原生应用- 在生成的时候只需要修改render方法渲染出不同的节点标签即可
浏览器在生成dom树的时候,非常消耗资源,因此引入虚拟dom概念通过一定的算法优化之后能够非常快捷的根据数据生成真实的html节点现在vue和react都是使用的虚拟dom
v-model主要提供了两个功能:view层输入值影响data的属性值;data属性值发生改变会更新view层的数值变化。
其核心就是,一方面modal层通过defineProperty来劫持每个属性,一旦监听到变化通过相关的页面元素更新。另一方面通过编译模板文件,为控件的v-model绑定input事件,从而页面输入能实时更新相关data属性值。
vnode 就是一个普通对象 对象属性代表着一些dom结构比如
tag代办是的当前的标签名
,data代表的是蛋清标签上的属性
,key 代办的是唯一 用户可以传递
function $createElement(tag,data,...children){
let key = data.key;
delete data.key;
children = children.map(child=>{
if(typeof child === 'object'){
return child
}else{
return vnode(undefined,undefined,undefined,undefined,child)
}
})
return vnode(tag,props,key,children);
}
export function vnode(tag,data,key,children,text){
return {
tag, // 表示的是当前的标签名
data, // 表示的是当前标签上的属性
key, // 唯一表示用户可能传递
children,
text
}
}
computed:计算属性
支持缓存,只有依赖数据发生变化时,才会重新计算函数。
不支持异步操作
自动监听依赖值的变化,从而动态返回内容。
watch:侦听属性
不支持缓存,只要数据变化,就会执行侦听函数
支持异步操作
侦听属性的值可以是一个对象,接受handler回调,deep,immediate三个属性
watch: {
isHot: {
// 设置为true时会立刻执行以表达式的当前值触发回调
inmediate: true,
handler接收两个参数(newVal:新值,oldVal:旧值
handler(newvalue, oldvalue) {
console.log('修改了', newvalue, oldvalue);
},
//deep设置为true时会监听对象内部值的变化
deep:true
}
}
computed的值一般被用在渲染中。
watch:(一个数据影响多个数据)一个数据执行相应操作会影响多个数据,当需要在数据变化时候执行异步操作或者开销较大的操作时使用。
例如:搜索数据
原因
v_for渲染的优先级比v-if 要高
所以会导致每次渲染都会先循环再进行条件判断
如果避免出现这种情况
,则在外层嵌套template
(页面渲染不生成dom节点),在这一层进行v-if判断,然后在内部进行v-for循环。
父子组件通信
、隔代组件通信
、兄弟组件通信
(1)props / $emit适用 父子组件通信
(2)ref与$parent / $children适用 父子组件通信
(3)EventBus ($emit / $on)适用于 父子、隔代、兄弟组件通信
加载渲染过程:
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
父组件更新过程:
父 beforeUpdate -> 父 updated
子组件更新过程:
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
销毁过程:
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
总结:子的生命周期都会被先结束,父的才结束。先由父到子,再从子到父
。
平时使用模板时,可以在模板中使用变量、表达式或者指令等,这些语法在html中是不存在的,那vue中为什么可以实现?这就归功于模板编译功能。
模板编译的作用是生成渲染函数,通过执行渲染函数生成最新的vnode,最后根据vnode进行渲染
。那么,如何将模板编译成渲染函数?
此过程可以分成两个步骤:先将模板解析成AST(abstract syntax tree,抽象语法树),然后使用AST生成渲染函数。 由于静态节点不需要总是重新渲染,所以生成AST之后,生成渲染函数之前这个阶段,需要做一个优化操作:遍历一遍AST,给所有静态节点做一个标记,这样在虚拟DOM中更新节点时,如果发现这个节点有这个标记,就不会重新渲染它。 所以,在大体逻辑上,模板编译分三部分内容: 1、将模板解析成AST 2、遍历AST标记静态节点 3、使用AST生成渲染函数 这三部分内容在模板编译中分别抽象出三个模块实现各自的功能:解析器、优化器和代码生成器。
beforeCreate:通常用于插件开发中执行一些初始化任务
create:组件初始化完毕,可以访问各种数据和获取接口数据
mounted:dom已创建可用以访问数据和dom元素,访问子组件
beforeUpdate:此时view层还未更新,用于获取更新前各种状态
update:完成view更新,更新后所有状态已经是最新
beforeunmounted:实例被销毁钱调用,用于取消定时器或者订阅
unmanned:销毁实例,他可以清理与实例的链接解绑的全部指令和事件监听
可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。但是推荐在 created 钩子函数中调用异步请求
,因为在 created 钩子函数中调用异步请求有以下优点:
能更快获取到服务端数据,减少页面 loading 时间;
ssr渲染模式的话 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;
beforeCreate, created, beforeMount, mounted
总共分为三钟:全局导航钩子、路由独享钩子、组件内的导航钩子
全局导航钩子
router.beforeEach(to, from, next): 路由改变前的钩子
const router = new Router({ ... })
router.beforeEach((to, from, next) => {
//
})
to: 即将要进入的目标路由对象
from: 当前正要离开的路由,也是一个路由对象
next: 一定要调用该方法来resolve这个钩子
next 方法必须要调用,否则钩子函数无法 resolved
router.beforeResolve : 在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,该钩子函数就被调用
router.afterEach : 路由改变后的钩子
路由独享钩子
可以在路由配置上直接定义 beforeEnter
cont router = new Router({
routes: [
{
path: '/file',
component: File,
beforeEnter: (to, from ,next) => {
// do someting
}
}
]
});
3、组件内的导航钩子
有三种:
beforeRouteEnter 在进入当前组件对应的路由前调用
beforeRouteUpdate 在当前路由改变,但是该组件被复用时调用
beforeRouteLeave 在离开当前组件对应的路由前调用
export default {
data() { ... },
beforeRouteEnter(to, from, next) {
... ...
}
}
注意:
beforeRouteEnter 不能获取组件实例 this,因为当守卫执行前,组件实例被没有被创建出来,剩下两个钩子则可以正常获取组件实例 this
vue-loader作用
解析和转换.vue文件。提取出其中的逻辑代码 script,样式代码style,以及HTML 模板template,再分别把他们交给对应的loader去处理
hash模式和history模式
hash模式
就是指 url 后面的 # 号以及后面的字符。每次刷新页面时是直接更改 # 后的东西。
由于 hash 值变化不会导致浏览器向服务器发出请求,而且 hash 改变会触发 hashchange事件(hashchange只能改变 # 后面的 url片段);虽然 hash路径出现在URL中,但是不会出现在 HTTP请求中,对后端完全没有影响,因此改变 hash值不会重新加载页面,基本都是使用 hash 来实现前端路由的。
history模式
包含 back、forward、go方法;history 模式 URL就要和后端进行一致,所以要改为 history也需要后端的配合,否则会报错;history 每次刷新会重新向后端请求整个网址,也就是重新请求服务器。如果后端没有及时响应,就会报错404!。
二、原理
hash 原理:灵活运用了 html的瞄点功能
,改变 # 后的路径本质上是更换了当前页面的瞄点,所以不会刷新页面。通过监听浏览器的 onhashchange()事件变化,查找对应的路由规则。
history 原理: 利用 H5的 history中新增的两个API
:pushState()和 replaceState()和一个事件onpopstate监听URL变化。
三、优缺点
hash模式
优点:
兼容性强,达到 IE8;
除发送 ajax和资源请求外不会发送其他多余请求;
改变 # 后的路径不会自动刷新页面;
无需服务器进行配合;
缺点:
访问路径上包含 # ,不美观;
对于需要锚点功能的需求会与当前路由机制发生冲突;
重定向操作时,后段无法获取 url完整路径;
history模式
优点:
符合 url 地址规范,没有 # ,使用起来比较美观;
后端可以获取到完整的路由信息;
可以使用 history.state获取完整的路由信息;
可以进行修改历史记录,并且不会立刻向后端发起请求;
缺点:
兼容性只到IE10;
改变 url路径后会重新请求资源;
若访问的路由地址不存在时会报404,需服务端配合支持重定向返回统一的404页面;
如果对于项目没有硬性标准要求,可以直接使用 hash模式开发。
Vue 在更新 DOM 时是
异步执行的
。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。
一、动态路由的理解
动态路由 就是把匹配某种模式下的路由映射到同一个组件中,其实本质就是通过url进行传参,比如说:有一个商Goods的组件,我们需要让不同的商品id都映射到这个组件中,此时就,需要用到动态路由了。
二、动态路由的配置
可以通过两种方式来传递动态参数
1.params
2.query
以上代码的演示都是在history的路由模式下
先说下基本概念:
ssr 的全称是 server side render,服务端渲染
,vue ssr 的意思就是在服务端进行 vue 的渲染
,直接对前端返回带有数据,并且是渲染好的HTML页面;
而不是返回一个空的HTML页面,再由vue 通过异步请求来获取数据,再重新补充到页面中。
这么做的最主要原因,就是搜索引擎优化,也就是SEO
,这更利于网络爬虫去爬取和收集数据
。
应许必填
key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点
key的主要作用就是用来提高渲染性能的!
Key值的存在保证了唯一性,Vue在执行时,会对节点进行检查,如果没有key值,那么vue检查到这里有 dom节点,就会对内容清空并赋新值如果有key值存在,那么会对新老节点进行对比比较两者key是否相同,进行调换位置或删除操作
v-model.lazy="value"
...
<div @click.capture="shout(1)">
obj1
<div @click.capture="shout(2)">
obj2
<div @click="shout(3)">
obj3
<div @click="shout(4)">
obj4
</div>
</div>
</div>
</div>
// 输出结构: 1 2 4 3
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click. prevent.self
会阻止所有的点击
,而 v-on:click.self.prevent 只会阻止对元素自身的点击
<button @click.left="shout(1)">ok</button> //左键
<button @click.right="shout(1)">ok</button> //右键
<button @click.middle="shout(1)">ok</button> //中键
键盘修饰符是用来修饰键盘事件(
onkeyup
,onkeydown
)的,有如下:
// 只有按键为enter的时候才触发
<input type="text" @keyup.enter="shout()">
//父组件
<comp :myMessage.sync="bar"></comp>
//子组件
this.$emit('update:myMessage',params);
相当于
//父亲组件
<comp :myMessage="bar" @update:myMessage="func"></comp>
func(e){
this.bar = e;
}
//子组件js
func2(){
this.$emit('update:myMessage',params);
}
<input id="uid" title="title1" value="1" :index.prop="index">
camel
将命名变为驼峰命名法,如将 view-Box属性名转换为 viewBox
数据驱动和组件化
vuex 是一个专门为 vue 构建的状态管理工具,主要是为了解决 多组间之间状态共享问题。强调的是集中式管理,(组件与组件之间的关系变成了组件与仓库之间的关系)
vuex 的核心包括:state(存放状态)、mutations(同步的更改状态)、actions(发送异步请求,拿到数据)、getters(根据之前的状态派发新的状态)、modules(模块划分)
state 发布一条新的数据,在 getters 里面根据状态派发新的状态,actions 发送异步请求获取数据,然后在 mutations 里面同步的更改数据
应用场合:购物车的数据共享、登入注册
diff算法并非vue所专有(凡是涉及到虚拟DOM的,多半都要用到diff算法);
组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data。如果单纯的写成对象形式,就使得所有组件实例共用了一份data,造成了数据污染。
虚拟DOM重写
优化插槽生成
在当前的 Vue2 版本中,当父组件重新渲染时,其子组件也必须重新渲染。 使用 Vue 3 ,可以单独重新渲染父组件和子组件。
静态树提升
使用静态树提升,这意味着 Vue 3 的编译器将能够检测到什么是静态组件,然后将其提升,从而降低了渲染成本。它将能够跳过未整个树结构打补丁的过程。
静态属性提升
Vue 3 将跳过不会改变节点的打补丁过程。
基于Proxy的响应式系统
目前,Vue 的反应系统是使用 ObectDefineProperty 的 getter 和 setter。 但是,Vue 3 将使用 ES2015 Proxy 作为其观察者机制。 这消除了以前存在的警告,使速度加倍,并节省了一半的内存开销。
1、更容易维护
TypeScript + 模块化 ,在vscode上的提示跟友好,代码提示类型检测。
2、Proxy(代理)响应式
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
问题
在vue3中的响应式
3、三个新组件
Fragment(片断)
Teleport(瞬移)
Suspense(不确定的)
在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。
一些简单的展示组件
,也就所谓的dumb组件,例如button组件,pills,tags,cards,甚至整个页面都是静态文本,比如about页面Vue.component('my-component', {
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
// ...
}
}
)
const data = ref({
name:"小明",
age:12
})
let obj = {
name:"小明"
}
let obj2 = JSON.parse(JSON.stringify(obj));
obj2.name = "小红";
console.log(obj.name) // 小明
console.log(obj2.name) // 小红
2、使用递归的方式实现深拷贝
deepClone(obj){
// 首先如果不是对象并且为空 直接等于返回
debugger
console.log(typeof obj)
var clone;
if (obj && typeof obj !== 'object') clone = obj;
else {
clone = Array.isArray(obj) ? [] : {};
for (let k in obj) {
if (obj.hasOwnProperty(k)) {
if (obj[k] && typeof obj[k] === 'object') {
clone[k] = this.deepClone(obj[k]);
} else {
clone[k] = obj[k];
}
}
}
}
return clone;
}
事件代理(Event Delegation),又称之为事件委托。是JavaScript中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定在子元素的响应事件(click、keydown…)委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。
<div id="box">
<input type="button" id="add" value="添加" />
<input type="button" id="remove" value="删除" />
<input type="button" id="move" value="移动" />
<input type="button" id="select" value="选择" />
</div>
window.onload = function(){
var oBox = document.getElementById("box");
oBox.onclick = function (ev) {
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLocaleLowerCase() == 'input'){
switch(target.id){
case 'add' :
alert('添加');
break;
case 'remove' :
alert('删除');
break;
case 'move' :
alert('移动');
break;
case 'select' :
alert('选择');
break;
}
}
}
}
1.event对象里记录的有“事件源”,它就是发生事件的子元素。它存在兼容性问题,在老的IE下,事件源是 window.event.srcElement,其他浏览器是 event.target
2.同时增加if判断,通过事件源的nodeName判断是不是li,是的话,才做出反应,不是的话,不理它。为了防止nodeName在不同浏览器获取的字母大小写不同,加个toLowerCase()
基本数据类型是直接存储在栈中的简单数据段,占据空间小、大小固定,属于被频繁使用的数据。栈是存储基本类型值和执行代码的空间。
引用数据类型是存储在堆内存中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆 中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。
两种数据类型的区别:堆比栈空间大,栈比堆运行速度快。堆内存是无序存储,可以根据引用直接获取。基础数据类型比较稳定,而且相对来说占用的内存小。引用数据类型大小是动态的,而且是无限的。
的有哪? 写出他们的用处
大体分为
1、节点node、Element
2、class
3、对象
Node.appendChild(node) //向节点添加最后一个子节点
Node.hasChildNodes() //返回布尔值,表示当前节点是否有子节点
Node.cloneNode(true); // 默认为false(克隆节点), true(克隆节点及其属性,以及后代)
Node.insertBefore(newNode,oldNode) // 在指定子节点之前插入新的子节点
Node.removeChild(node) //删除节点,在要删除节点的父节点上操作
Node.replaceChild(newChild,oldChild) //替换节点
Node.contains(node) //返回一个布尔值,表示参数节点是否为当前节点的后代节点。
Node.compareDocumentPosition(node) //返回一个7个比特位的二进制值,表示参数节点和当前节点的关系
Node.isEqualNode(noe) //返回布尔值,用于检查两个节点是否相等。所谓相等的节点,指的是两个节点的类型相同、属性相同、子节点相同。
Node.normalize() //用于清理当前节点内部的所有Text节点。它会去除空的文本节点,并且将毗邻的文本节点合并成一个。
//ChildNode接口
Node.remove() //用于删除当前节点
Node.before() //
Node.after()
Node.replaceWith()
(1)特性属性
Element.attributes //返回当前元素节点的所有属性节点
Element.id //返回指定元素的id属性,可读写
Element.tagName //返回指定元素的大写标签名
Element.innerHTML //返回该元素包含的HTML代码,可读写
Element.outerHTML //返回指定元素节点的所有HTML代码,包括它自身和包含的的所有子元素,可读写
Element.className //返回当前元素的class属性,可读写
Element.classList //返回当前元素节点的所有class集合
Element.dataset //返回元素节点中所有的data-*属性。
(2)尺寸属性
Element.clientHeight //返回元素节点可见部分的高度
Element.clientWidth //返回元素节点可见部分的宽度
Element.clientLeft //返回元素节点左边框的宽度
Element.clientTop //返回元素节点顶部边框的宽度
Element.scrollHeight //返回元素节点的总高度
Element.scrollWidth //返回元素节点的总宽度
Element.scrollLeft //返回元素节点的水平滚动条向右滚动的像素数值,通过设置这个属性可以改变元素的滚动位置
Element.scrollTop //返回元素节点的垂直滚动向下滚动的像素数值
Element.offsetHeight //返回元素的垂直高度(包含border,padding)
Element.offsetWidth //返回元素的水平宽度(包含border,padding)
Element.offsetLeft //返回当前元素左上角相对于Element.offsetParent节点的垂直偏移
Element.offsetTop //返回水平位移
Element.style //返回元素节点的行内样式
属性方法
Element
.getAttribute():读取指定属性
Element.setAttribute():设置指定属性
Element.hasAttribute():返回一个布尔值,表示当前元素节点是否有指定的属性
Element.removeAttribute():移除指定属性
获取元素
document.getElementsByClassName ('class'); //通过类名获取元素,以伪数组形式存在。
var Node = document.querySelector('li.className'); //通过CSS选择器获取元素,符合匹配条件的第1个元素。
document.querySelectorAll('ul li'); //查询id需要加#号
Node.classList.add('class'); //添加class
Node.classList.remove('class'); //移除class
Node.classList.toggle('class'); //切换class,有则移除,无则添加
Node.classList.contains('class'); //检测是否存在class号
window.onpopstate
(1)类名操作
//ie8以下
Element.className //获取元素节点的类名
Element.className += ' ' + newClassName //新增一个类名
//判断是否有某个类名
function hasClass(element,className){
return new RegExp(className,'gi').test(element.className);
}
//移除class
function removeClass(element,className){
element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'),'');
}
//ie10
element.classList.add(className) //新增
element.classList.remove(className) //删除
element.classList.contains(className) //是否包含
element.classList.toggle(className) //toggle class
(1)生成实例对象
var a = new Array()
(2)属性
a.length //长度
(3)Array.isArray()
Array.isArray(a) //用来判断一个值是否为数组
(4)Array实例的方法
a.valueof() //返回数组本身
a.toString() //返回数组的字符串形式
a.push(value,vlaue....) //用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度。
pop() //用于删除数组的最后一个元素,并返回该元素
join() //以参数作为分隔符,将所有数组成员组成一个字符串返回。如果不提供参数,默认用逗号分隔。
concat() //用于多个数组的合并。它将新数组的成员,添加到原数组的尾部,然后返回一个新数组,原数组不变。
shift() //用于删除数组的第一个元素,并返回该元素。
unshift(value) //用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度。
reverse() //用于颠倒数组中元素的顺序,返回改变后的数组
slice(start_index, upto_index); //用于提取原数组的一部分,返回一个新数组,原数组不变。第一个参数为起始位置(从0开始),第二个参数为终止位置(但该位置的元素本身不包括在内)。
如果省略第二个参数,则一直返回到原数组的最后一个成员。负数表示倒数第几个。
splice(index, count_to_remove, addElement1, addElement2, ...); //用于删除原数组的一部分成员,并可以在被删除的位置添加入新的数组成员,返回值是被删除的元素。
第一个参数是删除的起始位置,第二个参数是被删除的元素个数。如果后面还有更多的参数,则表示这些就是要被插入数组的新元素。
sort() //对数组成员进行排序,默认是按照字典顺序排序。排序后,原数组将被改变。如果想让sort方法按照自定义方式排序,可以传入一个函数作为参数,表示按照自定义方法进行排序。
该函数本身又接受两个参数,表示进行比较的两个元素。如果返回值大于0,表示第一个元素排在第二个元素后面;其他情况下,都是第一个元素排在第二个元素前面。
map() //对数组的所有成员依次调用一个函数,根据函数结果返回一个新数组。
map(elem,index,arr) //map方法接受一个函数作为参数。该函数调用时,map方法会将其传入三个参数,分别是当前成员、当前位置和数组本身。
forEach() //遍历数组的所有成员,执行某种操作,参数是一个函数。它接受三个参数,分别是当前位置的值、当前位置的编号和整个数组。
filter() //参数是一个函数,所有数组成员依次执行该函数,返回结果为true的成员组成一个新数组返回。该方法不会改变原数组。
some() //用来判断数组成员是否符合某种条件。接受一个函数作为参数,所有数组成员依次执行该函数,返回一个布尔值。该函数接受三个参数,依次是当前位置的成员、当前位置的序号和整个数组。
只要有一个数组成员的返回值是true,则整个some方法的返回值就是true,否则false。
every() //用来判断数组成员是否符合某种条件。接受一个函数作为参数,所有数组成员依次执行该函数,返回一个布尔值。该函数接受三个参数,依次是当前位置的成员、当前位置的序号和整个数组。
所有数组成员的返回值都是true,才返回true,否则false。
reduce() //依次处理数组的每个成员,最终累计为一个值。从左到右处理(从第一个成员到最后一个成员)
reduceRight() //依次处理数组的每个成员,最终累计为一个值。从右到左(从最后一个成员到第一个成员)
indexOf(s) //返回给定元素在数组中第一次出现的位置,如果没有出现则返回-1。可以接受第二个参数,表示搜索的开始位置
lastIndexOf() //返回给定元素在数组中最后一次出现的位置,如果没有出现则返回-1。
1、变量声明
let表示声明变量,而const表示声明常量,两者都为块级作用域;const 声明的变量都会被认为是常量,意思就是它的值被设置完成后就不能再修改了:
2、模板字符串
在ES6之前,我们往往这么处理模板字符串:
通过“\”和“+”来构建模板
$("body").html("This demonstrates the output of HTML \
content to the page, including student's\
" + name + ", " + seatNumber + ", " + sex + " and so on.");
而对ES6来说
基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定;
ES6反引号(``)直接搞定;
$("body").html(`This demonstrates the output of HTML content to the page,
including student's ${name}, ${seatNumber}, ${sex} and so on.`);
3、箭头函数
let fun = () => {
}
4、函数的参数默认值
// ES6之前,当未传入参数时,text = 'default';
function printText(text) {
text = text || 'default';
console.log(text);
}
// ES6;
function printText(text = 'default') {
console.log(text);
}
printText('hello'); // hello
5、展开运算符(用三个连续的点 (…) 表示)是 ES6 中的新概念,使你能够将字面量对象展开为多个元素
function foo(x,y,z) {
console.log(x,y,z);
}
let arr = [1,2,3];
foo(...arr); // 1 2 3
6、对象和数组解构
// 对象
const student = {
name: 'Sam',
age: 22,
sex: '男'
}
// 数组
// const student = ['Sam', 22, '男'];
// ES5;
const name = student.name;
const age = student.age;
const sex = student.sex;
console.log(name + ' --- ' + age + ' --- ' + sex);
// ES6
const { name, age, sex } = student;
console.log(name + ' --- ' + age + ' --- ' + sex);
7、for…of 和 for…in
let letters = ['a', 'b', 'c'];
letters.size = 3;
for (let letter of letters) {
console.log(letter);
}
// 结果: a, b, c
let stus = ["Sam", "22", "男"];
for (let stu in stus) {
console.log(stus[stu]);
}
// 结果: Sam, 22, 男
find();查找数组某个元素
findIndex();查找某个元素的索引值
some();数组中是否有元素符合条件
every();数组中是否所有的元素都符合条件
Object.assign(); 复制一个对象
Object.keys(); 得到一个对象的所有属性;
Object.values(); 得到一个对象的所有可枚举属性值;
Object.entries(); 得到一个对象的键值对数组;
Object.fromEntries(); entries的逆操作;
单列模式:一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
在js中所有的引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象。
而在js中的引用类型包括:Object,Array,Date,Function
而所有函数都有一个prototype(原型)属性,属性值是一个普通的对象,也被称为原型对象。
所有引用类型的__proto__属性指向它构造函数的prototype:
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。
注意: 如果最顶层还找不到的话就会返回null
事件循环,即 Event Loops。用于协调事件、用户交互、JavaScript 脚本、DOM 渲染、网络请求等等的执行顺序问题。
事件循环是由一个或以上的任务队列组成的。
什么是任务队列?
由于 JavaScript 是 单线程 语言,所以在 JS 中所有的任务都需要排队执行,这些任务共同组成了任务队列,依次排队执行的过程,形成一个执行栈(Execution Context Stack)。
在任务队列中最先执行是同步任务。
事件循环基础
宏任务: script (整体代码) 、setTimeout、setlnterval、setlmmediate、I/0、Ul rendering微任务: promise、Object.observe、MutationObserver任务的优先级: process.nextTick > promise.then > setTimeout > setlmmediate
微任务是跟屁虫,一直跟着当前宏任务后面代码执行到一个微任务就跟上,一个接着一个
微任务 > DOM 渲染 > 宏任务
闭包就是在一个函数里可以访问另外一个函数内部的变量
会 内存泄漏其实就是因为变量的值在内存中,并且这个变量因为闭包一直被引用,垃圾回收机制不能回收。
let 可以解决闭包
如果是var 定义的变量,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变。
let声明的变量,仅在块级作用域内有效,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量。而JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。
将是闭包的函数进行重新赋值成 null
let x = 10;
function fn(){
function f(){}
// fn执行时,fn内部的f会被外部的window引用,形成不销毁的EC(fn)
window.f = f;
}
// fn执行产生一个EC(fn)且不被销毁
fn();
// 取消堆中函数的引用,fn指向的堆内存释放
fn = null;
// f仍然能正常执行,虽然fn存储在堆内存中的描述字符串被回收了所以不能fn()了,但EC(fn)还存在
f();
// f堆内存释放,EC(fn)栈内存不存在闭包区域,则栈内存释放
// window.f = null
fetch是一种HTTP数据请求的方式
fetch不是ajax的进一步封装,而是原生js。Fetch函数就是原生js,没有使用XMLHttpRequest对象
由于Fetch底层是用Promise实现
利用对象属性key排除重复项
遍历数组,每次判断新数组中是否存在该属性,不存在就存储在新数组中
并把数组元素作为key,最后返回新数组
这个方法的优点是效率高,缺点是使用了额外空间
var newArr = [];
arr.forEach((key,index)=>{
if(newArr.indexOf(key) === -1){
newArr.push(key)
} })
console.log(newArr);// [1, '1', 17, true, false, 'true', 'a', {}, {}]
const newArr = [...new Set(arr)];
console.log(newArr);// [1, '1', 17, true, false, 'true', 'a', {}, {}]
const newArr= arr.filter(function(item,index,self){
return self.indexOf(item) === index;
})
console.log(newArr);// [1, '1', 17, true, false, 'true', 'a', {}, {}]
队列先进先出
,栈先进后出
。栈
是限定只能在表的一端进行插入和删除
操作的线性表。队列
是限定只能在表的一端进行插入
和在另一端进行删除
操作的线性表。三次握手是客户端跟服务器之间建立连接,并且进行通信的过程。相当于客户端和服务器之前你我来往的3个步骤;
三次握手的目的:
双方确认自己与对方的发送与接收双方正常四次挥手表示当前这次连接请求已经结束,要断开这次连接
四次挥手的目的:
由于TCP的半关闭的特性;都是匿名函数
而普通函数即可以匿名也可以具名
。父级的this
prototype原型对象
,不具有super,也不具有new.target,不能写Generator函数。一、指代不同
1、document对象:代表给定浏览器窗口中的HTML文档,document是window的一个对象属bai性。
2、window对象:表示浏览器中打开的窗口。
二、作用不同
1、document对象:使用document对象可以对HTML文档进行检查、修改或添加内容,并处理该文档内部的事件。
2、window对象:浏览器会为HTML文档创建一个window对象,并未每个框架创建一个额外的window对象。
三、使用方式不同:
1、document对象:在Web页面上,document对象可通过window对象的document属性引用,或者直接引用。
2、window对象:没有应用于window对象的公开标准,不过所有浏览器都支持该对象。
let _this = this; // 声明一个 _this 指向当前的this
// 定义一个类名为 myLike 的类
class myLike {
// 定义一个 JS 构造器
constructor(type) {
_this.type = type;
}
// 创建实例方法
sayType() {
console.log('我喜欢' + _this.type);
}
}
// 创建一个类名为 Programmer 的类的继承 myLike 类
class Programmer extends myLike {
constructor(type) {
// 直接调用父类构造器进行初始化操作
super(type);
}
program() {
console.log("我是一个写代码的游戏主播");
}
}
// 测试我刚创建的类
var goPlay = new myLike('打游戏'), // 声明一个打游戏的对象
writeCode = new Programmer('写代码'); // 声明一个写代码的对象
// 开始测试程序结果
goPlay.sayType(); // 输出 我喜欢打游戏
writeCode.sayType(); // 输出 我喜欢写代码
writeCode.program(); // 输出 我是一个写代码的游戏主播
Array.from()方法
var arrLike = {
"0": "1",
"1": "2",
"length": 2
};
var newArr = Array.from(arrLike);
console.log(newArr);
slice.call()方法
var arrLike = {
"0": "1",
"1": "2",
"length": 2
};
var newArr = [].slice.call(arrLike);
console.log(newArr);
相同点:
在弱类型语言中,数据类型可以被忽略,一个变量可以赋不同数据类型的值;而javascript变量在不同的场合可以解释为不同的类型,它允许变量类型的隐式转换和强制转换。在JavaScript中,不必事先声明变量的数据类型就可以使用变量,这时JavaScript解释器会根据情况做出他认为正确的判断。
1、jsonp的原理:就是利用浏览器可以动态地插入一段js并执行的特点完成的。
2、为什么不是真正的 ajax?
ajax的核心是 : 通过XmlHttpRequest获取非本页内容,
jsonp的核心 : 动态添加
3、ajax和jsonp的调用方式很像,目的一样,都是请求url,然后把服务器返回的数据进行处理,因此jquery和ext等框架都把jsonp作为ajax的一种形式进行了封装;
还是有不同点的:
4、实质不同
ajax的核心是通过xmlHttpRequest获取非本页内容
jsonp的核心是动态添加script标签调用服务器提供的js脚本
6 、jsonp是一种方式或者说非强制性的协议
ajax也不一定非要用json格式来传递数据
7、jsonp只支持get请求,ajax支持get和post请求
new 关键字运算过程会进行如下的操作:
onLoad() {
console.log('页面加载')
},
onShow() {
console.log('页面显示')
},
onReady(){
console.log('页面初次显示')
},
onHide() {
console.log('页面隐藏')
},
onUnload() {
console.log('页面卸载')
},
onBackPress(){
console.log('页面返回...')
},
onShareAppMessage() {
console.log('分享!')
},
onReachBottom() {
console.log('下拉加载...')
},
onPageScroll(){
console.log('页面滚动...')
},
onPullDownRefresh() {
console.log('上拉刷新...')
uni.stopPullDownRefresh();
},
推荐文章:https://blog.csdn.net/xgangzai/article/details/111243836