既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
原理
- Vue双向绑定原理是通过数据劫持结合发布订阅者模式来实现的
- 数据和视图同步,视图变化,数据也就变化;数据变化,视图也就变化
const obj={}
Object.defineProperty(obj,'hello',{
get:function(){
console.log("调用get方法")
},
set:function(newVal){
console.log("调用set方法"+newVal)
}
})
obj.hello; // 调用get方法
obj.hello='hi'; // 调用set方法hi
watch与computed,methods三者区别
作用机制上
- 侦听属性watch与计算属性computed都是以vue的依赖追踪机制(订阅者模式)为基础的,只有当一个函数变化时,才会调用相关的函数
- methods里面是用来定义函数的,需要手动调用才会执行
作用性质上
- methods中定义的是函数,你需要进行函数调用(func())
- computed是计算属性,他在使用了和data是对象中的数据类型是同一类
- watch类似于监听机制+事件机制
watch: {
firstName: function (val) { this.fullName = val + this.lastName }
}
/\*
\* 只有当firstName触发后才执行,而firstName对应的函数就相当于监听到事件发生后执行的方法
\*/
watch与computed异同
相同
- 他们都是在依赖数据发生改变的情况下,被依赖的数据根据预先定义好的函数,发生自动的变化
不同
- watch擅长处理
- 一个数据影响多个数据
- 当需要在数据变化时执行异步或开销较大的操作时
- watch可以监听新值与旧值
- watch属性还可以用来监听路由router的变化,只是这里的监听元素是固定的
- computed擅长处理(计算属性不能异步,异步函数return是没有任何意义的)
- 多个数据影响一个数据
- 当存在复杂逻辑、需要用到缓存时
methods
- methods与watch和computed是一种不同的概念,methods指的是方法也就是定义要用的函数,他不会主动触发需要在被调用时才会触发
- methods与computed看不出来差别,但要注意computed是存在缓存的
Vue组件为啥需要name属性
- 当项目使用keep-alive缓存时,可用组件name属性进行缓存过滤
- 当组件有name时,就可以组件自己调用自己(递归调用)
- 使用vue-tool时,工具中组件名就是name属性决定的
Vue传值方式
props与$emit(在封装的组件中很常用)
- 在父组件中通过属性传值与子组件,子组件在props接收
- 子组件通过this.$emit(‘事件名’,值),父组件通过@事件名="事件处理函数"接受父组件传递的值
Vue.prototype.$bus
- 在mian.js中定义
new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
}
})
- 要传值的组件定义this. b u s . bus. bus.emit(‘事件名’,值)
- 接收值的组件定义this. b u s . bus. bus.on(‘事件名’,回调函数),也可以通过this. b u s . bus. bus.off(‘事件名’)解绑事件
VueX
- state 用来存储数据
- mutation 用来对state进行成员操作
- getters 加工state成员给外界
- actions 也是用来对state进行成员操作不过是异步操作而且需要提交给mutation,在action中不能直接更改
- modules 模块化状态管理
ref传值
- 在父组件中在子组件上绑定ref,通过this.$refs.ref名字.子组件接受方法(数据)
<Test ref="test"/>
...
this.$refs.test.childMethod('val12343');
- 子组件定义方法接受子组件接受方法(val){ // val就是传过来的值}
methods: {
childMethod(val) {
console.log(val); // val就是传过来的值 val12343
},
},
LoaclStorage(存储于本地)
- 先将需要的值存储于本地localStorage.setItem(“存储名”,存储值)
- 获取值通过localStorage.getItem(“存储名”)
- 不需要时也可以删除localStorage.removeItem(“存储名”)
Vue-router路由实现原理
Hash路由模式
- hash虽然标记在url中,但他不会发送网络请求,对服务端完全无用
- 通过window.location.hash实现的
History路由模式
- 通过window.history实现的
- 分别通过调用history.pushState()和history.replaceState()方法来实现push和replace方法
- pushState和replaceState方法修改浏览器历史记录栈后,虽然当前URL改变了,但浏览器不会立即发送请求该URL,这为更新视图但不重新发送请求提供了基础
- Hash与History两种模式的go、back和forward三个方法都是通过window.history.go()来实现的
Abstract模式
vue-router为非浏览器环境准备了一个abstract模式,其原理为用一个数组stack模拟出浏览器历史记录栈的功能
Vue-router传参
router-link传参
params
<router-link :to="{name:'home',params:{id:1}}" />
<router-link :to="{path:'home',params:{id:1}}" />
// 取参 this.$route.params.id
query
<router-link :to="{name:'home',query:{id:1}}" />
<router-link :to="{path:'home',query:{id:1}}" />
// 取参 this.$route.query.id
this.$router.push()传参
params
this.$router.push({name:'home',params:{id:1,age:2}})
query
this.$router.push({path:'/home',query:{id:1,age:2}})
this.$router.push(`/home?id=1`);
Vue-router有那些守卫
全局前置守卫beforeEach
- 在router中进行配置
- router.beforeEach((to,from,next)=>{})
- to要到哪个路由去
- from从哪个路由来
- next下一步(无论失败与否都要调用,否则会阻止程序继续执行)
全局后置守卫 afterEach
- router.afterEach((to,from)=>{})
组件内守卫(相当于给组件增加生命周期)
beforeRouteEnter 进入组件之前
beforeRouterEnter(to,from,next){}
beforeRouteUpdate 组件被复用时调用
beforeRouterUpdate(to,from,next){}
beforeRouteLeave 离开组件时调用
beforeRouteLeave(to,from,next){}
移动端防止事件穿透
事件穿透原理
- 假如页面上有两个元素A和B,B元素在A元素之上,B为遮罩层mask。我们在B元素上绑定 touchstart事件,作用是隐藏B元素;A元素上绑定click事件。我们发现,当我们点击B元素,B元素被隐藏了,随后,也触发了A元素的click事件
- 事件执行的顺序是 touchstart >touchmove>touched ==>click,click事件有300ms的延迟,当 touchstart事件把B元素隐藏之后,隔了300ms,浏览器触发了click事件,但是此时B元素不见了,所以该事件被派发到了A元素身上。如果A元素是一个链接,那此时页面就会意外地跳转了
解决方案
禁用缩放
click事件的延迟300ms是为了移动端缩放
<!-- 1.禁用缩放 user-scalable=no -->
<meta name="viewport" content="width=device-width, initial-scale=1.0,user-scalable=no">
延迟上层元素消失
touchstart后延迟350ms后再隐藏,先把透明度设为0,解决视觉层面效果,在设置定时器延迟,让元素消失
$('.mask').on('touchstart',function() {
console.log('mask-touchstart');
$(this).css('opacity', 0); // 设置视觉层面消失
setTimeout(function() {
$('.mask').css('dispaly', 'none'); // DOM节点消失
},350);
})
pointer-events,让被覆盖元素短时间内无法触发click
- pointer-events两个属性
- auto默认值,鼠标默认值不会穿透当前层
- none使元素无法触发事件
$('.mask').on('touchstart',function() {
console.log('mask-touchstart');
$(this).css('display', 'none');
$('.box').css('pointer-events', 'none'); // 让被覆盖元素无法响应click
setTimeout(function() {
$('.box').css('pointer-events', 'auto'); // 恢复被覆盖元素
},300);
})
使用fastclick库
使用fastclick库后,所有点击事件都使用click,没有350ms延时,也没有样式穿透问题
<script type='application/javascript' src='/path/to/fastclick.js'></script>
...
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
针对安卓的,android:clickable=“true”
在上层的布局中增加android:clickable="true"的属性,这样下层的事件就不会被触发了
移动端适配
viewport适配
//获取meta节点
var metaNode = document.querySelector('meta[name=viewport]');
//定义设计稿宽度为375
var designWidth = 375;
//计算当前屏幕的宽度与设计稿比例
var scale = document.documentElement.clientWidth/designWidth;
//通过设置meta元素中content的initial-scale值达到移动端适配
meta.content="initial-scale="+scale+",minimum-scale="+scale+",maximum-scale="+scale+",user-scalable=no";
借助media实现rem适配
//对屏幕大小划分了html不同的font-size
@media screen and (min-width: 320px) {html{font-size:50px;}}
@media screen and (min-width: 360px) {html{font-size:56.25px;}}
@media screen and (min-width: 375px) {html{font-size:58.59375px;}}
@media screen and (min-width: 400px) {html{font-size:62.5px;}}
@media screen and (min-width: 414px) {html{font-size:64.6875px;}}
@media screen and (min-width: 440px) {html{font-size:68.75px;}}
@media screen and (min-width: 480px) {html{font-size:75px;}}
@media screen and (min-width: 520px) {html{font-size:81.25px;}}
@media screen and (min-width: 560px) {html{font-size:87.5px;}}
@media screen and (min-width: 600px) {html{font-size:93.75px;}}
@media screen and (min-width: 640px) {html{font-size:100px;}}
@media screen and (min-width: 680px) {html{font-size:106.25px;}}
@media screen and (min-width: 720px) {html{font-size:112.5px;}}
@media screen and (min-width: 760px) {html{font-size:118.75px;}}
@media screen and (min-width: 800px) {html{font-size:125px;}}
@media screen and (min-width: 960px) {html{font-size:150px;}}
JS配合修改配合rem适配
var designWidth = 375; // 设计稿宽度
var remPx = 100; // 在屏幕宽度375px,的时候,设置根元素字体大小 100px
var scale = window.innerWidth / designWidth; //计算当前屏幕的宽度与设计稿比例
// 根据屏幕宽度 动态计算根元素的 字体大小
document.documentElement.style.fontSize = scale\*remPx + 'px';
JS动态修改配合CSS预处理器(less)
// 计算屏幕宽度
var screen = document.documentElement.clientWidth;
// 计算字体大小,取屏幕宽度的16分之一
var size = screen / 16;
// 设置根元素字体大小
document.documentElement.style.fontSize = size + 'px';
js获取当前屏幕的宽度,将屏幕宽度的16分之一设置给html的font-size
// 此时设计稿的宽度为375,定义一个less变量等于16分之一的设计稿宽度
@rem: 375/16rem;
JS动态修改配合rem适配
// 计算屏幕宽度
var screen = document.documentElement.clientWidth;
// 设置根元素字体大小
document.documentElement.style.fontSize = screen + 'px';
借助插件适配
- 让不同设备的视口取得最佳CSS像素
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
- 安装 postcss,postcss-pxtorem,postcss-loader,postcss-import,postcss-url一系列插件
npm install postcss@8.2.6 --save
npm install postcss-import@14.0.0 --save
npm install postcss-loader@5.0.0 --save
npm install postcss-pxtorem@5.1.1 --save
npm install postcss-url@10.1.1 --save
- 在项目根目录下添加 .postcssrc.js 文件
module.exports = {
plugins: {
'postcss-pxtorem': {
rootValue: 32, //根目录的字体大小设为32px
propList: ['\*'], //proplist:是那些属性需要转换成rem,全部
minPixelValue: 2 //最小转换单位.2px
}
}
};
- 在项目中index.html的头部添加下面这段js代码:
<script>(function () {
function autoRootFontSize() {
document.documentElement.style.fontSize = Math.min(screen.width, document.documentElement
.getBoundingClientRect().width) / 470 \* 32 + 'px';
// 取screen.width和document.documentElement.getBoundingClientRect().width的最小值;
// 除以470,乘以32;意思就是相对于470大小的32px;
}
window.addEventListener('resize', autoRootFontSize);
autoRootFontSize();
})();</script>
注:设计师制作的效果图常为750px,为了方便直接读取UI标好的像素大小,根目录的字体大小就设置为32px;
详情
适配低版本浏览器
Babel
Babel默认只转换新的JavaScript句法(syntax),而不转换新的API.(不转换新的API.的意思是:对极少部分浏览器无法支持的api,转换之后的也无法正常的使用,比如:import,export)
使用
单文件转换
- 全局安装babel的命令行工具
cnpm install babel-cli -g
- 执行命令,安装babel-preset-env到本地
cnpm install babel-preset-env --save-dev
- 在项目根目录下创建 .babelrc 文件,文件中的内容如下
{
"presets": ["env"]
}
- 执行命令将es6转换为es5
文件到文件:babel js文件路径 -o 转译后生成的文件路径
在package.json文件的脚本中配置转义命令
- 安装babel-cli命令行工具
cnpm install babel-cli babel-preset-env --save-dev
- 创建 .babelrc 文件,写入以下代码
{
"presets": ["env"]
}
- 在你的 package.json 中添加一个 “scripts” 属性并将 babel 命令放在它的 build 属性中
"scripts": {
"build": "babel src -d lib"
},
- 终端运行命令开始转译
npm run build
前端项目优化
普通项目优化
页面加载阶段
- dns预解析
- 使用cdn
- 静态资源压缩与合并
- 减少http请求
- 异步加载defer(页面解析完成后执行代码),async(当前JS解析完成后执行代码)
- 服务端渲染ssr
- 多使用内存和缓存
页面渲染阶段
- css放前面,js放后面
- 减少DOM查询,多次使用的保存为变量
- 减少DOM操作,统一通过DOM片段操作
- 事件函数的防抖和节流
- 图片懒加载
Vue项目优化
代码层面优化
- Object.freeze(data)(方法可以冻结对象的属性)对于一些查询类的页面,调取接口回来后的数据可 不进行数据劫持
- v-if和v-for不要在一起使用。v-if的条件通过函数来处理
- v-for中加上key,对于虚拟dom树查找提高性能
- computed和watch注意区分使用场景。前者是有缓存的。后者是监听到数据变化后的回调无缓存
- created中发起请求,mounted钩子中代表页面dom加载完成可以进行dom操作
- 长列表性能优化,只渲染可视区域的列表
- 长表格性能优化,通过canvers来绘制表格
- 合理使用$nextTick去操作dom,适用于更新了数据马上就要操作dom的场景
- 操作dom不要使用js原生的方式来操作。用vue提供的三种方式来操作 比如,ref、自定义指令el、事件中的话用e.target获取dom
- 尽量不要在前端进行大量的数据处理
- 合理使用keep-alive来缓存页面数据,跳过created,mounted钩子,他有自己特定的钩子activted等
- 路由懒加载通过import配合箭头函数,还有其他的方式require
- 组件懒加载,异步加载
- 尽量少用float,可以用flex布局
- 频繁切换的使用v-show,不频繁的使用v-if
- 不要在模板中写过多的样式
- 服务端渲染ssr,优化seo,与首屏白屏问题
- 通过addEventListenr添加的事件,需要自行销毁
- 把组件中的css提取成单独的文件
- 少使用闭包与递归,递归可做尾递归的优化
- 使用字体图标或者svg来代替传统的Png等格式的图片
- 在Js中避免“嵌套循环”和“死循环”
webpack优化
- 去除无用代码treeShaking
- babel编译es6到es5的时候,会有多余代码产生
- 减小app.js的体积,提取公共代码
- 减少vendor.js的体积,通过按需引入第三方库,或者有些资源可以通过script标签引入
- 代码切割,有一些组件没必要都打包到一起
- 使用chunck
- 使用SouceMap,来还原线上代码,更方便的去定位线上问题
- 构建结果,通过可视化插件,进行分析
- webpack对图片进行压缩等处理
- 图片可以使用webp,优雅降级处理
- 编译优化
- 模板预编译,使用vue-template-loader,把模板编译成渲染函数
- thread-loader多线程打包
// webpack.base.js
{
test: /\.js$/,
use: [
'thread-loader',
'babel-loader'
],
}
WEB层面
- 浏览器缓存的使用
- 开启gzip压缩
- CDN的使用,减少路由转发的次数,就近访问资源
- 使用chrome的性能分析工具,查找性能瓶颈
- dns预解析
- 静态资源的压缩与合并
- 减少https请求
- 异步加载defer,async
- 静态资源和服务不要放在同一台机器上。多个域名去并行加载解析
安全层面
XSS跨站请求攻击
XSS 前端替换关键字,建议后端也替换,如<>的替换,避免脚本的执行
XSRF跨站请求头伪造
XSRF增加验证流程,比如输入密码,指纹,短信验证码,人脸识别等
模块化
概念
- 有组织的将一个大文件拆分成多个独立并相互依赖的小文件
- 模块内部有许多私有属性,只向外暴露一部分公开的接口
模块化方法
- AMD
- CMD
- CommonJS(Node JS中采用该规范)通过module.exports暴露模块,通过require引入模块
- 代码运行在模块作用域,不会污染全局
- 加载模块顺序是按照词法解析顺序来加载
- 加载模块是同步的
- 单例加载:模块会被缓存起来,再次运行直接加载,除非手动清除
- 加载模块得到的结果是深拷贝
- ES6模块export暴露模块,import … from … 引入模块
- 尽量静态化,编译时就确定模块的依赖关系
- 加载模块的值是引用,所以在全局只有一份
- 加载模块也是异步的
小程序生命周期
应用的生命周期,在app.js中进行调用
生命周期 | 说明 |
---|---|
onLanch | 小程序初始化完成时触发,全局只触发一次 |
onShow | 小程序启动,后者后台进入前台触发 |
onHide | 小程序从前台进入后台触发 |
onError | 小程序发生脚本错误或者API调用时报错时触发 |
onPageNotFound | 小程序打开时页面不存在时触发 |
onUnhandledRejection | 有未处理的promise时触发 |
onThemeChange | 系统切换主题时触发 |
页面生命周期,当你进入\切换到一个新页面时触发
生命周期 | 说明 | 作用 |
---|---|---|
onLoad | 监听页面加载 | 发送请求获取数据 |
onShow | 监听页面显示 | 发送请求获取数据 |
onReady | 监听页面初次渲染完成 | 获取页面元素 |
onHide | 监听页面隐藏 | 终止任务,如定时器或播放音乐 |
onUnLoad | 页面卸载 | 终止任务 |
组件生命周期
生命周期 | 说明 |
---|---|
created | 监听页面加载 |
attached | 监听页面显示 |
ready | 页面初次渲染完成 |
moved | 监听页面隐藏 |
datached | 监听页面卸载 |
error | 组件方法抛出错误时执行 |
微信小程序如何分包
为何要分包
- 在第一本版本的小程序中,小程序代码不能超过1M,所以小程序可以秒开;而后面随着业务发展小程序分包大小不能超过8M,8M不能秒开,于是就分包加载
- 小程序所有分包不能超过8M,单个分包,主包不能超过2M
- 小程序在启动时,默认下载主包,并启动主包内页面;当用户进入分包页面时,客户端下载分包并展示
app.json中配置
{
"pages":[
"pages/index",
"pages/logs"
],
"subpackages": [
{
"root": "packageA", // 分包根目录
"pages": [
"pages/cat",
"pages/dog"
]
}, {
"root": "packageB",
"name": "pack2", // 分包别名,分包预下载时可以使用
"pages": [ // 分包页面路径,相对于分包根目录
"pages/apple",
"pages/banana"
],
independent:false // 是否独立分包
}
]
}
分包注意事项
- 主包无法加载分包的资源
- 分包可以加载主包的资源
- 分包与分包之间无法相互加载资源
微信小程序获取用户信息
获取用户的头像,昵称
wx.getUserProfile
获取用户手机号
- 小程序端调用 wx.login() 方法,获取 code 后,将 code 通过后台 api 接口传递到后台
- 后台 api 接口收到 code 后,调用微信接口 jscode2session , 换取 openid 、 session_key 、 unionid
- openid同一用户不同应用下不相同
- unionid同一用户不同应用下相同
- session_key为应用安全,session_key应该存储在服务端
- 小程序端提交 code 调用后台 api 接口后 , 获取 api 接口返回的 userToken ,通过页面 button 点击调用后台接口 , 传递参数 userToken 、 iv 、 encryptedData 到后台进行数据解密,得到 phoneNumber
<button
class="btn"
open-type="getPhoneNumber"
bindgetphonenumber="getPhoneNumber"
>
{{phoneNumber==null?'获取手机号':phoneNumber}}
</button>
...
getPhoneNumber: function (e) {
var that = this;
if (e.detail.errMsg == "getPhoneNumber:ok") {
wx.request({
url: app.serverUrl+'/api/login/decodePhone',
data: {
encryptedData: e.detail.encryptedData,
iv: e.detail.iv,
userToken: that.data.userToken,
},
header: {
'content-type': 'application/json'
},
success: function (res) {
that.setData({
phoneNumber:res.data.phoneNumber
})
}
})
}
},
后端给的二进制流如何渲染到页面
// 调用方法准换为图片路径
window.URL.createObjectURL(res)
后端提供10W条数据,如何渲染到页面
setTimeout进行分页渲染
<template>
<div class='testDate'>
<div v-for="item in newList" :key="item.id">
<span>{{item.text}}</span>
</div>
</div>
</template>
<script>
export default {
name: 'testDate',
components: {},
props: {},
data() {
return {
list: [], // 用来模拟后端给的10W条数据
newList: [], // 用于渲染的数据
total: 100000, // 一共多少条数据
page: 0, // 第几页,默认0为第一页
limit: 200, // 一页200条数据
};
},
computed: {},
watch: {},
created() {
for (let i = 0; i < 100000; i += 1) {
this.list.push({
id: i,
text: `测试数据${i}`,
});
}
},
mounted() {
this.render();
},
methods: {
render() {
if (this.page >= Math.ceil(this.total / this.limit)) return; // 如果第几页的页数已经大于总共的页数,说明数据已经完了,直接return返回
setTimeout(() => {
for (let i = this.page \* this.limit; i < this.page \* this.limit + this.limit; i += 1) {
this.newList.push(this.list[i]); // 添加到newList用于渲染的数据列表中
}
this.page += 1;
this.render(); // 递归调用
}, 0);
},
},
};
</script>
requestAnimationFrame,这样可以减少reflow次数(浏览器重新计算集合属性),提高性能
<template>
<div class='testDate'>
<div v-for="item in newList" :key="item.id">
<span>{{item.text}}</span>
</div>
</div>
</template>
<script>
export default {
name: 'testDate',
components: {},
props: {},
data() {
return {
list: [], // 用来模拟后端给的10W条数据
newList: [], // 用于渲染的数据
total: 100000, // 一共多少条数据
page: 0, // 第几页,默认0为第一页
limit: 200, // 一页200条数据
};
},
computed: {},
watch: {},
created() {
for (let i = 0; i < 100000; i += 1) {
this.list.push({
id: i,
text: `测试数据${i}`,
});
}
},
mounted() {
this.render();
},
methods: {
render() {
if (this.page >= Math.ceil(this.total / this.limit)) return;
requestAnimationFrame(() => {
![img](https://img-blog.csdnimg.cn/img_convert/1c1f97c32af7e01df031dbee93061b0c.png)
![img](https://img-blog.csdnimg.cn/img_convert/376162d6c285af403c2d2e1bab02e1c9.png)
![img](https://img-blog.csdnimg.cn/img_convert/551bc1ab0674b75ad5a011c33f7e8295.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**
性),提高性能