答:是的
答:脚手架创建vue-cli 创建 根据项目需求去选择条件选项创建
答: 1.默认进行懒观察
在2.x版本里 由于数据过大 都会在一开始就创建观察者,这可能会导致一个问题就是在页面载入时造成明显的性能压力.
在3.x版本里 做出了改变 只会对被用于渲染初始可见部分的数据 创建观察者,减缓了页面载入时候的性能压力,而且3.x观察者更高效
2.更精准的变更通知
2.x版本中 使用Vue.set给对象新增一个属性时,这个对象所有的watcher都会重新运行.
3.x版本中 只有依赖那个属性的watcher才会重新运行.
3.3.0版本加入了TypeScript以及PWA的支持
a.下载安装npm install -g vue@cli
b.删除了vue list
c.创建项目 vue create
d.启动项目 npm run serve
a.移除了配置文件目录,config 和build文件夹
b.移除了static文件夹,新增public文件夹,并且index.html移动到public中
c.在src文件夹中新增了views文件夹,用于分类视图组件和公共组件
vue-cli 3.0的创建方式和vue-cli 2.0不同,这是因为vue-cli 3.0是构建在 webpack 和 webpack-dev-server 之上的,可以理解更高一级于2.0,并具备有新的配置和功能。
3.0最强大之处提供了 vue ui 命令,通过可视化界面来使用 GUI 安装和管理插件(当然也可以通过vue add plugin添加插件),例如使用 iView 按需引入的时候,可以直接在可视化界面中安装 vue-cli-plugin-iview,则可以实现按需引入。
答:有过了解的. 前端工程的环境变量,通常会使用cross-env在package.json的scripts中设置,但是这样设置有缺点:
1.script命令过长,书写不便.
2.变量混杂在一起,查看不方便!
3.多环境的环境变量引入,需要添加多个额外命令.
解决办法:使用env-cmd这个组件 设置环境变量,集中在一个环境文件中,简单方便.
"scripts": {
"dev": "env-cmd -e dev -f ./.env-cmdrc.json node index.js"
}
env-cmdrc.json
{
"dev": {
"ENV1": "Thanks dev",
"ENV2": "For All dev"
},
"test": {
"ENV1": "Thanks test",
"ENV2": "For All test"
},
"prod": {
"ENV1": "Thanks prod",
"ENV2": "For All prod"
},
"hw:prod": {
"ENV1": "Thanks hw:prod",
"ENV2": "For All hw:prod"
}
}
环境变量使用,与package.json的script命令中-e后面的环境对应,上面配置的为dev环境。
let env = process.env;
console.log('env1 ',env.ENV1);
console.log('env2 ',env.ENV2);
答:我做过这个微信公众号的分享功能
1.首先这个分享功能是通过微信自身的分享按钮来实现的
2.目前主流做法是 进入相关页面后通过加载一个提示图片来提示用户( 具体为一个箭头指向右上角的分享, 并辅以文字说明)
3.具体的微信自有分享的实现步骤
a.接入JSSDK
– 1. 绑定域名 : 公众号设置 -> 功能设置 -> JS接口安全域名
– 2. 引入JS文件 : 在需要调用JS接口的页面引入如下JS文件:http://res.wx.qq.com/open/js/jweixin-1.0.0.js
– 3. 通过config接口注入权限验证配置
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名,见附录1
jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
– 4. 通过ready接口处理成功验证
wx.ready(function(){
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
});
– 5. 通过error接口处理失败验证
wx.error(function(res){
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});
b.分享接口
1.获取"分享"到朋友圈按钮点击状态及自定义分享内容接口
wx.onMenuShareTimeline({
title: '', // 分享标题
link: '', // 分享链接
imgUrl: '', // 分享图标
success: function () {
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
2.获取"分享到QQ"按钮点击状态及自定义分享内容接口
wx.onMenuShareQQ({
title: '', // 分享标题
desc: '', // 分享描述
link: '', // 分享链接
imgUrl: '', // 分享图标
success: function () {
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
3.获取"分享到QQ空间"按钮点击状态及自定义分享内容接口
wx.onMenuShareQZone({
title: '', // 分享标题
desc: '', // 分享描述
link: '', // 分享链接
imgUrl: '', // 分享图标
success: function () {
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
}
});
6.类似于从后台获取图片,获取相关信息自动生成海报的功能有没有做过?
7.兼容性问题?
答:Pc端和移动端,下面我分别说一下这两端的兼容问题.
1.**webSocket兼容低浏览器**
Adobe Flash Socket
ActiveX HTMLFile (IE)
基于 multipart 编码发送 XHR
基于长轮询的 XHR
2.浏览器的兼容性有哪些,解决方案?
png24位的图片在iE6浏览器上出现背景,解决方案是做成PNG8.
浏览器默认的margin和padding不同。解决方案是加一个全局的*{margin:0;padding:0;}
来统一。
IE下,可以使用获取常规属性的方法来获取自定义属性,也可以使用getAttribute()
获取自定义属性;Firefox下,只能使用getAttribute()
获取自定义属性。解决方法:统一通过getAttribute()
获取自定义属性。
IE下,even对象有x
,y
属性,但是没有pageX
,pageY
属性;Firefox下,event对象有pageX
,pageY
属性,但是没有x
,y
属性。
//解决方案:
var page = {
};
page.x = event.x ? event.x : event.pageX;
page.y = event.y ? event.y:event.pageY;
3. Chrome 中文界面下默认会将小于12px的文本强制按照12px显示,可通过加入CSS属性-webkit-text-size-adjust: none;
解决。
1.Weinre 调试
weinre工具优点:
所有调试工具中唯一不以其他第三方制定软件为依赖的方式,这种方式的好处就是跨平台,其次是跨浏览器,如果你是在微信或者其他phoneGap外壳方式上开发的,你就会知道,这个方式是比其他方式更有用。
weinre缺点:
在自己项目中引入js文件,需要手动删除,这对代码的控制和版本的控制造成了一定影响。颇为不便。但是最大的鸡肋却不是这个。weinre最大的鸡肋是只能调试样式和元素,js无法调试!!是的,所以它基本上是一只腿走路的。
2.UC开发者浏览器:
UC手机开发工具的优点:
简单,容易上手。只要求一个UC开发者浏览器,其余的软件不强制规定。不得不说国内的移动浏览器中UC走在了前面。
UC手机开发工具的缺点
依赖其他软件设备,开发者必须下载UC开发者工具。不跨平台,无视了苹果机。这种模式无法调试微信或者其他app内置浏览器。
3.chrome真机调试
Chrome手机调试工具优点:
在熟悉的开发模式下调试,操作比较简单。个人觉得UC在这方面胜过chrome。可以跨平台,在ios上也可以用。
Chrome手机调试工具缺点:
有目共睹,比起UC来,它步骤比较繁琐,不能wifi调试,必须指定手机和PC浏览器的类型(都是chrome),这种模式无法调试微信或者其他app内置浏览器。
4.Safari开发者工具
手机端:设置 → Safari → 高级 → Web 检查器 → 开。
mac端:Safari → 偏好设置 → 高级 → 在菜单栏中显示“开发”菜单。
在 OS X 中启动 Safari 之后,以 USB 电缆正常接入 iOS 设备,并在此移动设备上启动 Safari。此时点击计算机上的 Safari 菜单中的“开发”,可以看到有 iOS 设备的名称显示,其子菜单项即为移动设备上 Safari 的所有标签页,点击任意一个开始调试。
苹果系列优点:
便捷,简单,高端大气上档次,可以调试外壳包裹的浏览器如微信。
苹果系列缺点:
不用说,设备限严重依赖其公司产品,不给没钱买它产品的人活路啊。
5.Browsersync
BrowserSync能让PC、各移动设备上的页面同时实时地响应文件的更改,而不用刷新操作。而且,当在其他一个设备上进行点击等行为时,该行为也会同步到其他浏览器中
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>JavaScript AJAX原生写法 面试题</title>
</head>
<body>
<script type="text/javascript">
var Ajax = {
get: function(url, fn) {
//创建XMLHttpRequest对象
var xhr = new XMLHttpRequest();
//true表示异步
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
// readyState == 4说明请求已完成
if(xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 304)){
//responseText:从服务器获得数据
fn.call(this, xhr.responseText);
}
};
xhr.send();
},
post: function(url, data, fn) {
//data应为'a=a1&b=b1'这种字符串格式
var xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
// 添加http头,发送信息至服务器时内容编码类型
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if(xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
fn.call(this, xhr.responseText);
}
};
xhr.send(data);
}
}
</script>
</body>
子组件props 接收数据, 并且在子组件定义一个方法$emit触发,父组件绑定这个方法, $on监听这个方法改变
答:router.beforeEach((to,from,next)=>{}) 前置导航守卫
router.afterEach((to,form)=>{}) 后置导航守卫
// 组件间常用的交互方式 父子之间交互方式,兄弟组件交互方式
//单向数据流
//所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
// 父子组件通信
父组件 A 通过 props 的方式向子组件 B 传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。子组件props接收父组件传过来的数据类型多样:字符串 数字 布尔值 数组 对象 函数 Promise 自定义构造函数
// 兄弟组件通信
通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案 vuex。
var Event=new Vue();
Event.$emit(事件名,数据); // 传给谁?
Event.$on(事件名,data => {
}); // 谁触发?
总结: 1.父组件通过 props 向下传递数据给子组件。注:组件中的数据共有三种形式:data、props、computed
A组件:{
{name}}
B组件:{
{age}}
C组件:{
{name}},{
{age}}
$on 监听了自定义事件 data-a 和 data-b,因为有时不确定何时会触发事件,一般会在 mounted 或 created 钩子中来监听。
区别 : 2.X放于static;3.X放入public中
要注意报404问题,vue-cli3中内置的webpack会把图片当做一个模块引用,然后打包等等,路径就不对了,我们的静态资源是不需要打包的,
解决: 1.引入项目的根路径前缀 baseUrl
baseUrl: process.env.BASE_URL
2.所用到的动态图片img放到public目录下
3.修改动态路径图片地址
fullUrl: function() {
// `this` 指向 vm 实例
return `${
this.baseUrl}cond-icon-heweather/${
this.twCode}.png`;
}
4.将图片路径绑定到页面上去
流程: 根页面提供routers注入方法—调用根页面注入方法–store中存储routers–取出routers进行动态组件匹配.
Vue中懒加载的各种使用地方:
1.路由懒加载
export default new Router({
routes:[
{
path: '/my',
name: 'my',
//懒加载
component: resolve => require(['../page/my/my.vue'], resolve),
},
]
})
2.组件懒加载
components: {
historyTab:resolve => {
require(['../../component/historyTab/historyTab.vue'],resolve)
},
},
3.全局懒加载
Vue.component('mideaHeader', () => {
System.import('./component/header/header.vue')
})
按需加载原因:首屏优化,第三方组件库依赖过大,会给首屏加载带来很大的压力,一般解决方式是按需求引入组件。
SPA 是一种特殊的 Web 应用,是加载单个 HTML 页面并在用户与应用程序交互时动态更新该页面的。在 SPA 应用中,应用加载之后就不会再有整页刷新。相反,展示逻辑预先加载,并有赖于内容Region(区域)中的视图切换来展示内容。
1. 优点
1) 有良好的交互体验
能提升页面切换体验,用户在访问应用页面是不会频繁的去切换浏览页面,从而避免了页面的重新加载;
2) 前后端分离开发
单页Web应用可以和 RESTful 规约一起使用,通过 REST API 提供接口数据,并使用 Ajax 异步获取,这样有助于分离客户端和服务器端工作。更进一步,可以在客户端也可以分解为静态页面和页面交互两个部分;
3) 减轻服务器压力
服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍;
4) 共用一套后端程序代码
不用修改后端程序代码就可以同时用于 Web 界面、手机、平板等多种客户端;
2. 缺点:
1) SEO难度较高
由于所有的内容都在一个页面中动态替换显示,所以在SEO上其有着天然的弱势,所以如果你的站点对SEO很看重,且要用单页应用,那么就做些静态页面给搜索引擎用吧;
2) 前进、后退管理
由于单页Web应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理,当然此问题也有解决方案,比如利用URI中的散列+iframe实现;
3) 初次加载耗时多
为实现单页Web应用功能及显示效果,需要在加载页面的时候将JavaScript、CSS统一加载,部分页面可以在需要的时候加载。所以必须对JavaScript及CSS代码进行合并压缩处理;
3. 性能优化
1) 加载优化
a. 当首屏加载完毕后,设备&网络处于空闲状态,可以对其他路由组件进行预加载,以便提升页面切换性能
b.根据路由拆分减少初始加载体积,利用异步加载方式,在路由注册时提供异步拉取组件的方法,仅在需要进入对应路由时,对应组件才会被加载进来。
2) SEO优化
解决的方法是用 #!号代替#号,因为谷歌会抓取带有#!的URL。(Google规定,如果你希望Ajax生成的内容被浏览引擎读取,那么URL中可以使用"#!"(这种URL在一般页面一般不会产生定位效果)),这样我们可以解决ajax的不被搜索引擎抓取的问题。
3) 前进后退功能优化
配置好路由信息,通过记录浏览过的历史路由信息,可以很好的记录或历史查看过的界面,也可以独立写个足迹功能实现。
4. 体验优化
1) 构建骨架图
配合 PWA 首屏缓存,骨架图可实现瞬间加载&展示,首屏视觉上有冲击性地提升;
消除页面初始加载因多次重绘&资源加载导致的”抖动”需要注意的是,骨架图应尽量保持足够小巧与简单,以确保不会严重影响页面后续加载;
2) 页面切换
在确保组件&数据加载完毕前,可保证页面可交互性,减少用户阻塞感;
给一个转场动画
1.后台更改header
header('Access-Control-Allow-Origin:*');//允许所有来源访问
header('Access-Control-Allow-Method:POST,GET');//允许访问的方式
2.在vue中引入jquery 使用jquery提供的jsonp
methods: {
getData () {
var self = this
$.ajax({
url: 'http://f.apiplus.cn/bj11x5.json',
type: 'GET',
dataType: 'JSONP',
success: function (res) {
self.data = res.data.slice(0, 3)
self.opencode = res.data[0].opencode.split(',')
}
})
}
}
3.使用http-proxy-middleware 代理解决(项目使用vue-cli脚手架搭建)
a.打开config/index.js,在proxyTable中添写如下代码:
proxyTable: {
'/api': {
//使用"/api"来代替"http://f.apiplus.c"
target: 'http://f.apiplus.cn', //源地址
changeOrigin: true, //改变源
pathRewrite: {
'^/api': 'http://f.apiplus.cn' //路径重写
}
}
}
b.使用axios请求数据时直接使用“/api”:
getData () {
axios.get('/api/bj11x5.json', function (res) {
console.log(res)
})
c.打包部署时还会这种方法会出问题。解决方法如下:
let serverUrl = '/api/' //本地调试时
// let serverUrl = 'http://f.apiplus.cn/' //打包部署上线时
export default {
dataUrl: serverUrl + 'bj11x5.json'
}
//canvas后台获取字体、图片 用画一个海报
downPoster(){
const _self = this;
_self.disabled = true;
html2canvas(document.getElementsByClassName('festival-body')[0], {
allowTaint: true,scale: 2, useCORS: true}).then(canvas=> {
var dataURL = canvas.toDataURL(""image/png"");
if(!dataURL){
_self.toast = true;
_self.msg = '海报生成失败,请重新操作';
setTimeout(() => {
_self.toast = false;
_self.disabled = false;
}, 1000);
return;
}else{
setTimeout(() => {
let param={
'base64':dataURL};
localStorage.setItem('picUrl', '');
localStorage.setItem('picUrl', dataURL);
_self.$router.push('/shopkeeper/imgDetail');
}, 1000);
}
}).catch(error=> {
console.log(error)
_self.toast = true;
_self.msg = error;
setTimeout(() => {
_self.toast = false;
_self.disabled = false;
}, 1000);
return;
});
},"
query要用path来引入,params要用name来引入
query更加类似于我们ajax中get传参,params则类似于post,
说的再简单一点,前者在浏览器地址栏中显示参数,后者则不显示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L6TsQDe9-1585733015742)(C:\Users\dell\AppData\Roaming\Typora\typora-user-images\image-20200317200751355.png)]
1.有时你想向已有对象上添加一些属性,例如使用 Object.assign() 或 _.extend() 方法来添加属性。但是,添加到对象上的新属性不会触发更新。在这种情况下可以创建一个新的对象,让它包含原对象的属性和新的属性:
// 代替 Object.assign(this.someObject, { a: 1, b: 2 })
this.someObject = Object.assign({
}, this.someObject, {
a: 1, b: 2 })
2.替换方法,返回新数组那种
3.解构,直接赋值新对象
4.watch的deep属性
受现代 JavaScript 的限制 (以及废弃 Object.observe),Vue 不能检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。deep默认是false,需要手动设置为true
vue的插件:vue-lazyload img标签的src被替换成v-lazy
500:服务器遇到了不知道如何处理的情况(服务器类型错误)
404:请求失败,请求所希望得到的资源未被在服务器上发现。
400:1、语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。
2、请求参数有误。
1.1 网络状态
我们可以通过window. navigator.onLine来检测,用户当前的网络状况,返回一个布尔值,这个不能实时的通知。
addEventListener 进行绑定online从没网络到有网络的时候调用
addEventListener 进行绑定offline从有网络到没网的时候调用
事件是给window绑订的
1.2 地理定位
HTML规范中,增加了获取用户地理信息的API,这样使得我们可以基于用户位置开发互联网应用,即基于位置服务 (Location Base Service)
navigator.geolocation.getCurrentPosition(successCallback, errorCallback, options) 获取当前地理信息
navigator.geolocation.watchPosition(successCallback, errorCallback, options) 重复获取当前地理信息
1.3 Web存储
1.3.1 特性
1、设置、读取方便
2、容量较大,sessionStorage约5M、localStorage约20M
3、只能存储字符串,可以将对象JSON.stringify() 编码后存储
1.3.2 window.sessionStorage
1、生命周期为关闭浏览器窗口
2、在同一个窗口下数据可以共享
1.3.3 window.localStorage
1、永久生效,除非手动删除
2、可以多窗口共享
window.localStorage.setItem("username","feifei");
window.sessionStorage.setItem("username2","feifei2");
1.3.4 方法详解
setItem(key, value) 设置存储内容
getItem(key) 读取存储内容
removeItem(key) 删除键值为key的存储内容
clear() 清空所有存储内容 //window.localStorage.clear();
key(n)以索引值来获取存储内容 //window.localStorage.key(0);
length 获取数据的长度 // window.localStorage.length;
1.4 全屏
HTML5规范允许用户自定义网页上任一元素全屏显示。
requestFullScreen() 开启全屏显示
cancelFullScreen() 关闭全屏显示
1.5拖拽
在HTML5的规范中,我们可以通过为元素增加draggable="true"来设置此元素是否可以进行拖拽操作,其中图片、链接默认是开启的。
vuex是状态管理仓库,可以在页面通过 this.$store.state来获取我们定义的数据;前提是需要在el里面挂载上
一、computed 和 watch 都可以观察页面的数据变化。当处理页面的数据变化时,我们有时候很容易滥用watch。 而通常更好的办法是使用computed属性,而不是命令是的watch回调。 例子如下
html:
我们要实现 第三个表单的值 是第一个和第二个的拼接,并且在前俩表单数值变化时,第三个表单数值也在变化
<div id="myDiv">
<input type="text" v-model="firstName">
<input type="text" v-model="lastName">
<input type="text" v-model="fullName">
div>
js:
用watch方法来实现
new Vue({
el: '#myDiv',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
js: 利用computed 来写
new Vue({
el:"#myDiv",
data:{
firstName:"Den",
lastName:"wang",
},
computed:{
fullName:function(){
return this.firstName + " " +this.lastName;
}
}
})
二 、 详解 comouted 计算属性。
在vue的 模板内({ {}})是可以写一些简单的js表达式的 ,很便利。但是如果在页面中使用大量或是复杂的表达式去处理数据,对页面的维护会有很大的影响。这个时候就需要用到computed 计算属性来处理复杂的逻辑运算。
1.优点:
在数据未发生变化时,优先读取缓存。computed 计算属性只有在相关的数据发生变化时才会改变要计算的属性,当相关数据没有变化是,它会读取缓存。而不必想 motheds方法 和 watch 方法是的每次都去执行函数。
2.setter 和 getter方法:(注意在vue中书写时用set 和 get)
setter 方法在设置值是触发。
getter 方法在获取值时触发。
computed:{
fullName:{
//这里用了es6书写方法
set(){
alert("set");
},
get(){
alert("get");
return this.firstName + " " +this.lastName;
},
}
}
3.计算结果并返回,只有当被计算的值发生改变时才会触发
(即:计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算)
4.watch
监听某一个值,当被监听的值发生变化时,执行对应的操作
(与computed的区别是,watch更加适用于监听某一个值的变化并做对应的操作,比如请求后台接口等,而computed适用于计算已有的值并返回结果)
new Vue({
el: '#id',
template: `
// ...
`,
data: {
firstName: 'Leo',
lastName: 'Alan',
obj1: {
a: 0
}
},
watch: {
// 监听firstName,当firstName发生变化时就会执行该函数
firstName () {
// 执行需要的操作...
// 注:初始化不会执行,只有当被监听的值(firstName)发生变化时才会执行
},
// 监听lastName
lastName: {
handler (newName, oldName) {
// 执行需要的操作...
},
immediate: true // true: 初始化时就会先执行一遍该监听对应的操作
},
obj1: {
handler () {
// 执行需要的操作...
},
deep: true // 该属性默认值为false.
// 当被监听的值是对象,只有deep为true时,对应属性的值(obj1.a)发生变化时才能触发监听事件,但是这样非常消耗性能
},
// 监听对象具体的属性, deep就不需要设置为true了
'obj1.a': {
handler () {
// 执行需要的操作...
}
}
}
})
vue.js中创建组件有三个步骤:创建组件构造器,注册组件以及使用组件。组件分为全局组件和局部组件
全局注册使用方法:
<div id="app">
<my-component></my-component>
</div>
<script src="vue.js"></script>
<script>
/*创建组件构造器*/
var MyComponent = Vue.extend({
template: 'This is a component
'
});
/*全局注册组件*/
Vue.component('my-component', MyComponent);
/*使用组件*/
var vm = new Vue({
el: '#app'
})
</script>
局部注册使用方法:
<div id="app">
<my-component></my-component>
</div>
<script src="vue.js"></script>
<script>
var child = Vue.extend({
template: 'This is a component
'
});
var vm = new Vue({
el: '#app',
components: {
'my-component': child
}
});
</script>
优化做法: vue.js对于以上两种注册方法提供了简化方法,我们可以在注册组件的时候定义组件构造器。
<div id="app">
<my-component></my-component>
</div>
<script src="vue.js"></script>
<script>
/**全局注册
Vue.component('my-component', {
template: 'This is a component
'
});
new Vue({
el: '#app'
});*/
//局部注册
new Vue({
el: '#app',
components: {
'my-component': {
template: 'This is a component
}'
}
}
})
</script>
使用插件查看项目所有包及体积大小
webpack外部扩展
列出了项目中较大的包,剩下的事情就是想办法如何减小这些包的体积(将一个大包拆成多个小包)。
项目中产生较大的包的原因可以从两个方面去考虑:
1. 项目中引入的依赖包过于庞大;
2. 业务代码集中在一块写,或者是业务代码写的比较繁琐;
对于这两个问题,我们可以从两个方面着手解决:
// 总结需要抽离的公共依赖。
// 使用CDN引入资源
// 配置webpack.conf.js
// 配置vendor.js
DLL方式
dll 全称是:dynamic link library(动态链接库)
dll方式也就是通过配置,告诉webpack指定库在项目中的位置,从而直接引入,不将其打包在内。
上面介绍的方式是将包放到cdn上,build的时候不在引入对应的包;
dll方式就是指定包在项目中,build的时候不在打包对应的包,使用的时候引入。
webpack
通过webpack.DllPlugin
与webpack.DllReferencePlugin
两个内嵌插件实现此功能。
1.在典型的oop的语言中,如java,都存在类的概念,类就是对象的模板,对象就是类的实例。但在js中不存在类的概念,js不是基于类,而是通过**构造函数(constructor)和原型链(prototype chains)**实现的。但在ES6中引入了类(class)这个概念,作为对象的模板,新的class写法知识让原型对象的写法更加清晰,
2.在JavaScript中,创建对象的方式包括两种:对象字面量和使用new表达式。对象字面量是一种灵活方便的书写方式,例如:
var o1 = {
p:"I'm in Object literal",
alertP:function(){
alert(this.p);
}
}
// 缺点:每创建一个新的对象都需要写出完整的定义语句,不便于创建大量相同类型的对象,不利于使用继承等高级特性。
new表达式是配合构造函数使用的
function CO(){
p:"I'm in Object literal",
this.alertP = function(){
alert(this.p);
}
}
var o2 = newCO();
3.构造函数的特点:
a:构造函数的首字母必须大写,用来区分于普通函数
b:内部使用的this对象,来指向即将要生成的实例对象
c:使用New来生成实例对象
function Person(name,age){
this.name = name;
this.age = age;
this.sayHello = function(){
console.log(this.name +"say hello");
}
}
var boy = new Person("bella",23);
boy.sayHello(); // bella say hello
4.构造函数的缺点:
所有的实例对象都可以继承构造器函数中的属性和方法。但是,同一个对象实例之间,无法共享属性
解决思路:
a:所有实例都会通过原型链引用到prototype
b:prototype相当于特定类型所有实例都可以访问到的一个公共容器
c:那么我们就将重复的东西放到公共容器就好了
function Person(name,age){
this.name = name;
this.age = age;
this.sayHello = function(){
console.log(this.name + "say hello");
}
}
var girl = new Person("bella",23);
var boy = new Person("alex",23);
console.log(girl.name); //bella
console.log(boy.name); //alex
console.log(girl.sayHello === boy.sayHello); //false
一个构造函数Person生成了两个对象实例girl和boy,并且有两个属性和一个方法。但是sayHello方法是不一样的。如上图(图画得很丑)。也就是说当New一个实例对象的时候,都会去创建一个sayHello方法,这就浪费了内存资源,因为sayHello方法使一样的行为的,完全可以被两个实例对象共享。
所以,缺点就是:同一个构造函数的对象实例之间无法共享属性和方法。
为了解决构造函数的这个缺点,js提供了prototype属相来解决该问题。
prototype属性的作用
js中每个数据类型都是对象,除了null 和 undefined(这个可以参考另一篇将null 和 undefined的博客),而每个对象都是继承自一个原型对象,只有null除外,它没有自己的原型对象,最终的Object的原型为null
function Person(name,age){
this.name = name;
this.age = age;
}
Person.propotype.sayHello = function(){
console.log(this.name + "say hello");
}
var girl = new Person("bella",23);
var boy = new Person("alex",23);
console.log(girl.name); //bella
console.log(boy.name); //alex
console.log(girl.sayHello === boy.sayHello); //true
由上图可以看出,prototype是构造函数的属性,而consructor则是构造函数的prototype属性所指向的那个对象,也就是说constuctor是原型对象的属性。
constructor属性是定义在原型对象上面,意味着也可以被实例对象继承
function Person(name,age){
this.name = name;
this.age = age;
}
Person.propotype.sayHello = function(){
console.log(this.name + "say hello");
}
var girl = new Person("bella",23);
console.log(girl.construcotr); //Person()
console.log(girl.construcotr == Person.propotype.construcotr); //true
constructor属性的作用
a:分辨原型对象到底是哪个构造函数
function Person(){
};
var person1 = new Person();
console.log(person1.construcotr === Person); //true
b.从实例新建另一个实例
function Person(){
};
var person1 = new Person();
var person2 = new person1.construcotr();
console.log(person2 instanceof Person); //true
c:由于constructor属性是一种原型对象和构造函数的关系,所以在修改原型对象的时候,一定 要注意construtor的指向问题,避免instanceof失真
5.JS中万物都是对象,但是对象也分为:普通对象和函数对象,也就是Object 和 Function.
那么怎么区分普通对象和函数对象呢? —凡是通过New Function()创建的对象都是函数对象,其他的都是普通对象.
需要注意的是:普通对象没有propotype(prototype即是属性也是对象),但是有__proto__属性。
console.log(girl.__proto__ === Person.protype);//true
console.log(Persion.propotype.__proto__ === Object.propotype);//true
console.log(Object.porpotype.__proto__); //null
通过__proto__串起来直到Object.propotype.__proto__为null的链叫做原型链(矩形表示函数对象,椭圆形表示普通对象)
Computed
本质是一个具备缓存的watcher,依赖的属性发生变化就会更新视图。适用于计算比较消耗性能的计算场景。当表达式过于复杂时,在模板中放入过多逻辑会让模板难以维护,可以将复杂的逻辑放入计算属性中处理。
Watch
没有缓存性,更多的是观察的作用,可以监听某些数据执行回调。当我们需要深度监听对象中的属性时,可以打开deep:true
选项,这样便会对对象中的每一项进行监听。这样会带来性能问题,优化的话可以使用字符串形式
监听,如果没有写到组件中,不要忘记使用unWatch手动注销
哦。
v-model
本质就是一个语法糖,可以看成是value + input
方法的语法糖。可以通过model属性的prop
和event
属性来进行自定义。原生的v-model,会根据标签的不同生成不同的事件和属性。
原生事件绑定是通过addEventListener
绑定给真实元素的,组件事件绑定是通过Vue自定义的$on
实现的。
简单说,Vue的编译过程就是将template
转化为render
函数的过程。会经历以下阶段:
首先解析模版,生成AST语法树
(一种用JavaScript对象的形式来描述整个模板)。使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。
Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对
,对运行时的模板起到很大的优化作用。
编译的最后一步是将优化后的AST树转换为可执行的代码
。
简单来说,diff算法有以下过程
正常Diff两个树的时间复杂度是O(n^3)
,但实际情况下我们很少会进行跨层级的移动DOM
,所以Vue将Diff进行了优化,从O(n^3) -> O(n)
,只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。
Vue2的核心Diff算法采用了双端比较
的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。
Vue3.x借鉴了ivi算法和 inferno算法
在创建VNode时就确定其类型,以及在mount/patch
的过程中采用位运算
来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升。(实际的实现可以结合Vue3.x源码看。)
该算法中还运用了动态规划
的思想求解最长递归子序列。
由于在浏览器中操作DOM是很昂贵的。频繁的操作DOM,会产生一定的性能问题。这就是虚拟Dom的产生原因
。
Vue2的Virtual DOM借鉴了开源库snabbdom
的实现。
Virtual DOM本质就是用一个原生的JS对象去描述一个DOM节点。是对真实DOM的一层抽象。
(也就是源码中的VNode类,它定义在src/core/vdom/vnode.js中。)
VirtualDOM映射到真实DOM要经历VNode的create、diff、patch等阶段。
「key的作用是尽可能的复用 DOM 元素。」
新旧 children 中的节点只有顺序是不同的时候,最佳的操作应该是通过移动元素的位置来达到更新的目的。
需要在新旧 children 的节点中保存映射关系,以便能够在旧 children 的节点中找到可复用的节点。key也就是children中节点的唯一标识。
keep-alive
可以实现组件缓存,当组件切换时不会对当前组件进行卸载。
常用的两个属性include/exclude
,允许组件有条件的进行缓存。
两个生命周期activated/deactivated
,用来得知当前组件是否处于活跃状态。
keep-alive的中还运用了LRU(Least Recently Used)
算法。
组件的调用顺序都是先父后子
,渲染完成的顺序是先子后父
。
组件的销毁操作是先父后子
,销毁完成的顺序是先子后父
。
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted
父beforeUpdate->子beforeUpdate->子updated->父updated
父 beforeUpdate -> 父 updated
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
父子组件通信
父->子props
,子->父 $on、$emit
获取父子组件实例 $parent、$children
Ref
获取实例的方式调用组件的属性或者方法
Provide、inject
官方不推荐使用,但是写组件库时很常用
兄弟组件通信
Event Bus
实现跨组件通信 Vue.prototype.$bus = new Vue
Vuex
跨级组件通信
Vuex
$attrs、$listeners
Provide、inject
SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端
。
SSR有着更好的SEO、并且首屏加载速度更快等优点。不过它也有一些缺点,比如我们的开发条件会受到限制,服务器端渲染只支持beforeCreate
和created
两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境。还有就是服务器会有更大的负载需求。
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。
location.hash
的值实际就是URL中#
后面的东西。
history实际采用了HTML5中提供的API来实现,主要有history.pushState()
和history.replaceState()
。
Vue在初始化数据时,会使用Object.defineProperty
重新定义data中的所有属性,当页面使用对应属性时,首先会进行依赖收集(收集当前组件的watcher
)如果属性发生变化会通知相关依赖进行更新操作(发布订阅
)。
(还好我有看,这个难不倒我)
Vue3.x改用Proxy
替代Object.defineProperty。因为Proxy可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。
使用了函数劫持的方式,重写了数组的方法,Vue将data中的数组进行了原型链重写,指向了自己定义的数组原型方法。这样当调用数组api时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。
在下次 DOM 更新循环结束之后执行延迟回调。nextTick主要使用了宏任务和微任务。根据执行环境分别尝试采用
定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。
1、使用v-if代替v-show
两者的区别是:v-if不渲染DOM,v-show会预渲染DOM
除以下情况使用v-show,其他情况尽量使用v-if
有预渲染需求
需要频繁切换显示状态
2、v-for必须加上key,并避免同时使用v-if
一般我们在两种常见的情况下会倾向于这样(v-for 比 v-if 优先级高):
为了过滤一个列表中的项目
比如 v-for=“user in users” v-if=“user.isActive”。在这种情形下,请将 users替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表
为了避免渲染本应该被隐藏的列表
比如 v-for=“user in users” v-if=“shouldShowUsers”。这种情形下,请将 v-if 移动至容器元素上 (比如 ul, ol)
3、事件及时销毁
Vue组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。
也就是说,在js内使用addEventListener等方式是不会自动销毁的,我们需要在组件销 毁时手动移除这些事件的监听,以免造成内存泄露,如:
created() {
addEventListener('touchmove', this.touchmove, false)
},
beforeDestroy() {
removeEventListener('touchmove', this.touchmove, false)
}
4、长列表性能优化
Vue 会通过 Object.defineProperty 对数据进行劫持,来实现视图响应数据的变化,然而有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要 Vue 来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,那如何禁止 Vue 劫持我们的数据呢?可以通过 Object.freeze 方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。
export default {
data: () => ({
users: {}
}),
async created() {
const users = await axios.get("/api/users");
this.users = Object.freeze(users);
}
};
1、图片裁剪、使用webp
Vue.use(VueLazyload, {
error: require('./assets/img/defaultpic_small.png'),
filter: {
webp (listener: any, options: any) {
if (!options.supportWebp) return
// listener.src += '.webp'
}
}
});
2、资源提前请求
经测试,Vue项目中各文件的加载顺序为:router.js、main.js、App.vue、[-page-].vue、[component].vue
其中,router的加载时间相比于page.vue快近100ms,如果page.vue的文件较多,时间差异会更大
所以,可以在页面挂载、渲染的同时去请求接口数据,如在router.js中请求数据:
import Router from 'vue-router'
import store from './store'
store.dispatch('initAjax')
3、异步路由
使用异步路由可以根据URL自动加载所需页面的资源,并且不会造成页面阻塞,较适用于移动端页面
建议主页面直接import,非主页面使用异步路由
{
path: '/order',
component: () => import('./views/order.vue')
}
4、异步组件
不需要首屏加载的组件都使用异步组件的方式来加载(如多tab),包括需要触发条件的动作也使用异步组件(如弹窗)
使用方式为:v-if来控制显示时机,引入组件的Promise即可
5、使用轻量级插件、异步插件
使用webpack-bundle-analyzer查看项目所有包的体积大小,较大的插件包尽量寻找轻量级的替代方案
首屏用不到的插件、或只在特定场景才会用到的插件使用异步加载(如定位插件,部分情况可以通过URL传递经纬度;或生成画报插件,需要在点击时触发);插件第一次加载后缓存在本地,使用方式为:
// 以定位插件为例
const latitude = getUrlParam('latitude')
const longitude = getUrlParam('longitude')
// 如果没有经纬度参数,则使用定位插件来获取经纬度
if (!latitude || !longitude) {
// 首次加载定位插件
// webpack4写法,若使用webpack3及以下,则await import('locationPlugin')即可
if (!this.WhereAmI) this.WhereAmI = (await import('locationPlugin')).default
// do sth...
}
6、公用CDN
使用公用的CDN资源,可以起到缓存作用,并减少打包体积
7.第三方插件的按需引入
我们在项目中经常会需要引入第三方插件,如果我们直接引入整个插件,会导致项目的体积太大,我们可以借助 babel-plugin-component
,然后可以只引入需要的组件,以达到减小项目体积的目的。以下为项目中引入 element-ui 组件库为例:
(1)首先,安装 babel-plugin-component
:
npm install babel-plugin-component -D
(2)然后,将 .babelrc 修改为:
{
"presets": [["es2015", {
"modules": false }]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
(3)在 main.js 中引入部分组件:
import Vue from 'vue';
import {
Button, Select } from 'element-ui';
Vue.use(Button)
Vue.use(Select)
1、减少网络请求
浏览器对同一时间针对同一域名下的请求有一定数量限制(一般是6个),超过限制数目的请求会被阻塞
首屏尽可能减少同域名的请求,包括接口和js;按需减少首屏的chunk.js,合并接口请求
2、合理使用preload、dns-prefetch、prefetch
preload具有较高的加载优先级,它可以利用间隙时间预加载资源,将加载和执行分离开,不阻塞渲染和document的onload事件
每次与域名连接都需要进行DNS解析,使用dns-prefetch可以预解析域名的DNS
prefetch会预加载页面将来可能用到的一些资源,优先级较低;对首屏渲染要求较高的项目不建议使用
三者的使用方式,在head标签中添加(vue-cli-3已经做了相应配置):
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="icon" href="/dist/favicon.ico" rel="external nofollow" />
<link rel="dns-prefetch" href="//www.dpfile.com" rel="external nofollow" />
<title>md-configtitle>
<link href="/dist/css/app.52dd885e.css" rel="external nofollow" rel="preload" as="style" />
<link href="/dist/js/app.05faf3b5.js" rel="external nofollow" rel="preload" as="script" />
<link href="/dist/js/chunk-vendors.04343b1f.js" rel="external nofollow" rel="external nofollow" rel="preload" as="script" />
<link href="/dist/js/chunk-vendors.04343b1f.js" rel="external nofollow" rel="external nofollow" rel="prefetch" />
head>
3、PWA
PWA支持缓存HTML文档、接口(get)等,降低页面白屏时间
这样即使在弱网甚至断网情况下,也能迅速展示出页面
1、升级Vue-Cli-3
vue-cli-3采用webpack4+babel7,对编译打包方面做了很多优化(成倍的提升),使用yarn作为包管理工具,并且对很多优化的最佳实践做了默认配置
经测试,将项目从vue-cli-2迁移到vue-cli-3之后,速度变化为:
编译时间:44s --> 7s
打包时间:55s --> 11s
2、SSR
对加载性能要求较高的项目建议升级SSR
服务端渲染是指 Vue 在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的 html 片段直接返回给客户端这个过程就叫做服务端渲染。
(1)服务端渲染的优点:
更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
更快的内容到达时间(首屏加载更快):SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;
(2)服务端渲染的缺点:
更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源,因此如果你预料在高流量环境下使用,请准备相应的服务器负载,并明智地采用缓存策略。
变量声明:let、const
解构赋值
字符串扩展:includes、startsWith/endsWith、repeat(n)、字符串模板
数组遍历:for…of
合并对象:Object.assign(obj1,obj2,…objN)
箭头函数
Symbol数据类型
Set集合
Map映射
s/app.05faf3b5.js" rel=“external nofollow” rel=“preload” as=“script” />
**3、PWA**
PWA支持缓存HTML文档、接口(get)等,降低页面白屏时间
这样即使在弱网甚至断网情况下,也能迅速展示出页面
#### D.编译打包优化
**1、升级Vue-Cli-3**
vue-cli-3采用webpack4+babel7,对编译打包方面做了很多优化(成倍的提升),使用yarn作为包管理工具,并且对很多优化的最佳实践做了默认配置
经测试,将项目从vue-cli-2迁移到vue-cli-3之后,速度变化为:
编译时间:44s --> 7s
打包时间:55s --> 11s
**2、SSR**
对加载性能要求较高的项目建议升级SSR
服务端渲染是指 Vue 在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的 html 片段直接返回给客户端这个过程就叫做服务端渲染。
**(1)服务端渲染的优点:**
更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
更快的内容到达时间(首屏加载更快):SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;
**(2)服务端渲染的缺点:**
更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源,因此如果你预料在高流量环境下使用,请准备相应的服务器负载,并明智地采用缓存策略。
#### 45.用过哪些ES6语法?
变量声明:let、const
解构赋值
字符串扩展:includes、startsWith/endsWith、repeat(n)、字符串模板
数组遍历:for…of
合并对象:Object.assign(obj1,obj2,…objN)
箭头函数
Symbol数据类型
Set集合
Map映射