1.内存泄漏场景和解决方案
泄漏点:
1.DOM/BOM
对象泄漏
2.script
中存在对DOM/BOM
对象的引用导致
3.Javascript
对象泄漏
4.通常由闭包导致,比如事件处理回调,导致DOM
对象和脚本中对象双向引用,这个是常见的泄漏原因
1.闭包
function fn1(){
var n=1;
}
//我想取到里面的局部变量n
function fn1(){
var n=1;
function fn2(){//在加一个fn2当他的子集
alert(n);
}
}
function fn1(){
var n=1;
function fn2(){//在加一个fn2当他的子集
alert(n);
}
return fn2();
//return出来后 他就给 window了
//所以一直存在内存中。因为一直在内存中,
// 在IE里容易造成内存泄漏
}
fn1();
尽量书写的时候,避免这种情况。
2.意外的全局变量
function foo(arg) {
bar = "aaaaa";
}
// 实际上等价于
function foo(arg) {
window.bar = "aaaaa";
}
为了防止这种错误的发生,可以在你的 JavaScript 文件开头添加 ‘use strict’; 语句
3.定时器setTimeout setInterval
当不需要setInterval
或者setTimeout
时,定时器没有被clear
,定时器的回调函数以及内部依赖的变量都不能被回收,造成内存泄漏。比如:vue
使用了定时器,需要在beforeDestroy
中做对应销毁处理。js也是一样的。
clearTimeout(***)
clearInterval(***)
4.如果在mounted/created 钩子中使用了$on,需要在beforeDestroy 中做对应解绑($off)处理
beforeDestroy() {
this.bus.$off('****');
}
5.反复重写同一个属性会造成内存大量占用(但关闭IE后内存会被释放)
for(i = 0; i < 5000; i++) {
hostElement.text = "asdfasdfasdf";
}
这种方式相当于定义了5000个属性!
2. 前端性能优化篇
1)使Ajax可缓存
Ajax的目地是为突破web本质的开始—停止交互方式,向用户显示一个白屏后重绘整个页面不是一种好的用户体验。
异步与即时
Ajax的一个明显的有点就是向用户提供了即时反馈,因为它异步的从后端web服务器请求信息。
用户是否需要等待的关键因素在于Ajax请求是被动的还是主动的。被动请求是为了将来来使用而预先发起的,主动请求是基于用户当前的操作而发起的
什么样的AJAX请求可以被缓存?
POST的请求,是不可以在客户端缓存的,每次请求都需要发送给服务器进行处理,每次都会返回状态码200。(可以在服务器端对数据进行缓存,以便提高处理速度)
GET的请求,是可以(而且默认)在客户端进行缓存的,除非指定了不同的地址,否则同一个地址的AJAX请求,不会重复在服务器执行,而是返回304。
Ajax请求使用缓存
在进行Ajax请求的时候,可以选择尽量使用get方法,这样可以使用客户端的缓存,提高请求速度。
2) 配置ETag
以前浏览器缓存的就会失效。
什么是ETag?
实体标签(EntityTag)是唯一标识了一个组件的一个特定版本的字符串,是web服务器用于确认缓存组件的有效性的一种机制,通常可以使用组件的某些属性来构造它。
条件GET请求
如果组件过期了,浏览器在重用它之前必须首先检查它是否有效。浏览器将发送一个条件GET请求到服务器,服务器判断缓存还有效,则发送一个304响应,告诉浏览器可以重用缓存组件。
那么服务器是根据什么判断缓存是否还有效呢?有两种方式:
ETag(实体标签);
最新修改日期;
最新修改日期
原始服务器通过Last-Modified响应头来返回组件的最新修改日期。
3)服务器端(后端)技术
除了优化前端,服务器端技术也可以用来加速加载时间。这些技术都值得考虑,但不会在本文中介绍:
缓存HTTP重定向来加速重复访问;
合并HTTP重定向链来减少重定向;
使用HTTP压缩来减少数量的字节(Gzip或缩小)。
4)数据量过大大导致的页面卡顿
后台的接口访问速度慢,查看接口速度原因,数据量多的情况下,可以考虑分批发送和缓存处理。
3. 跨域问题
什么是跨域?
浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。
跨域限制
1、无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
2、无法接触非同源网页的 DOM
3、无法向非同源地址发送 AJAX 请求(可以发送,但浏览器会拒绝接受响应)
如何解决跨域问题?
跨域实践: Fiddle 处理前端本地开发跨域问题
目前我了解的解决跨域的几种方式: 手写过滤器,手写拦截器,jsonnp,注解方式,配置nginx反向代理
1、jsonp跨域
JSONP(JSON with Padding:填充式JSON),应用JSON的一种新方法,
JSON、JSONP的区别:
1、JSON返回的是一串数据、JSONP返回的是脚本代码(包含一个函数调用)
2、JSONP 只支持get请求、不支持post请求
(类似往页面添加一个script标签,通过src属性去触发对指定地址的请求,故只能是Get请求)
2、nginx反向代理:
www.baidu.com/index.html需要调用www.sina.com/server.php,可以写一个接口www.baidu.com/server.php,由这个接口在后端去调用www.sina.com/server.php并拿到返回值,然后再返回给index.html
3、PHP端修改header
header(‘Access-Control-Allow-Origin:*’);
//允许所有来源访问
header(‘Access-Control-Allow-Method:POST,GET’);
//允许访问的方式
4、document.domain【实现不同window之间的相互访问和操作】
跨域
分为两种:
一种xhr不能访问不同源的文档,
另一种是不同window之间不能进行交互操作;
document.domain主要是解决第二种情况,且只能适用于主域相同子域不同的情况;
document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。例如:a.b.example.com中某个文档的document.domain可以设成a.b.example.com、b.example.com 、example.com中的任意一个,但是不可以设成c.a.b.example.com,因为这是当前域的子域,也不可以设成baidu.com,因为主域已经不相同了。
兼容性:所有浏览器都支持;
优点:
可以实现不同window之间的相互访问和操作;
缺点:
只适用于父子window之间的通信,不能用于xhr;
只能在主域相同且子域不同的情况下使用;
使用方式:
不同的框架之间是可以获取window对象的,但却无法获取相应的属性和方法。比如,有一个页面,它的地址是http://www.example.com/a.html , 在这个页面里面有一个iframe,它的src是http://example.com/b.html, 很显然,这个页面与它里面的iframe框架是不同域的,所以我们是无法通过在页面中书写js代码来获取iframe中的东西的:
5、window.name
关键点:window.name在页面的生命周期里共享一个window.name;
兼容性:所有浏览器都支持;
优点:
最简单的利用了浏览器的特性来做到不同域之间的数据传递;
不需要前端和后端的特殊配制;
缺点:
大小限制:window.name最大size是2M左右,不同浏览器中会有不同约定;
安全性:当前页面所有window都可以修改,很不安全;
数据类型:传递数据只能限于字符串,如果是对象或者其他会自动被转化为字符串,如下;
4. 深拷贝实现方式
- 使用递归的方式实现深拷贝
//使用递归的方式实现数组、对象的深拷贝
function deepClone1(obj) {
//判断拷贝的要进行深拷贝的是数组还是对象,
// 是数组的话进行数组拷贝,对象的话进行对象拷贝
var objClone = Array.isArray(obj) ? [] : {};
//进行深拷贝的不能为空,并且是对象或者是
if (obj && typeof obj === "object") {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === "object") {
objClone[key] = deepClone1(obj[key]);
} else {
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
- 通过 JSON 对象实现深拷贝
//通过js的内置对象JSON来进行数组对象的深拷贝
function deepClone2(obj) {
var _obj = JSON.stringify(obj),
objClone = JSON.parse(_obj);
return objClone;
}
JSON对象实现深拷贝的一些问题
*无法实现对对象中方法的深拷贝
- 通过jQuery的extend方法实现深拷贝
var array = [1,2,3,4];
var newArray = $.extend(true,[],array);
- Object.assign(), slice, concat拷贝
当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。
- lodash函数库实现深拷贝
lodash很热门的函数库,提供了 lodash.cloneDeep()实现深拷贝
5.删除数组中某个数的方法
- splice()
- delete
6.删除一段长度的数组的方法
7.重绘和重排(repaints and reflows)
1,重排
就是渲染树的一部分必须要更新 并且节点的尺寸发生了变化。这就会触发重排操作。
2,重绘
部分节点需要更新,但是没有改变他的集合形状,比如改变了背景颜色,这就会触发重绘。
下面任何操作都会触发重绘或者重排:
增加或删除DOM节点
设置 display: none;(重排并重绘) 或者 visibility: hidden(只有重排)
移动页面中的元素
增加或者修改样式
用户 改变窗口大小,滚动页面等
css中哪些属性能避免重排
transform、opacity、filters
8.axios
Ajax(Asynchronous JavaScript and XML):异步网络请求。Ajax能够让页面无刷新的请求数据。
Axios,可以理解为ajax i/o system,这不是一种新技术,本质上还是对原生XMLHttpRequest的封装,可用于浏览器和nodejs的HTTP客户端,只不过它是基于Promise的,符合最新的ES规范。具备以下特点:
在浏览器中创建XMLHttpRequest请求
在node.js中发送http请求
支持Promise API
拦截请求和响应
转换请求和响应数据
取消要求
自动转换JSON数据
客户端支持防止CSRF/XSRF(跨域请求伪造)
拦截器interceptors
拦截器是指当发送请求或者得到响应被then或catch处理之前对它们进行拦截,拦截后可对数据做一些处理,比如给请求数据添加头部信息,或对响应数据进行序列化,然后再传给浏览器,这些都可以在拦截器中进行
//添加一个请求拦截器
axios.interceptors.request.use(config=>{
//在请求之前做一些事
return config;
},err=>{
//请求错误的时候做一些事
return Promise.reject(err);
});
//添加一个响应拦截器
axios.interceptors.response.use(response=>{
//对返回的数据做一些处理
reutrn response;
},err=>{
//对返回的错误做一些处理
return Promise.reject(err);
});
//移除拦截器
const myInterceptor = axios.interceptors.request.use(config=>{return cofig})
axios.interceptors.request.eject(myInterceptor);
//在一个axios实例中使用拦截器
var instance = axios.create();
instance.interceptors.request.use(function(){/*...*/});
在请求拦截中,错误拦截较少,通常都是配置相关的拦截
在响应拦截中,若成功,则主要是对数据进行过滤;若失败,则可以根据starus判断报错的状态码,来跳转到不同的错误提示页面
8.遍历对象方法for in 和for of的区别
for in可以遍历对象和数组
使用for in遍历时,会有以下问题
index索引为字符串型数字(注意,非数字),不能直接进行几何运算
遍历顺序有可能不是按照实际数组的内部顺序(可能按照随机顺序)
使用for-in会遍历数组所有的可枚举属性,包括原型方法method和name属性都会被遍历出来,通常需要配合hasOwnProperty()方法判断某个属性是否该对象的实例属性,来将原型对象从循环中剔除
所以for-in更适合遍历对象且配合hasOwnProperty()方法一起使用,通常是建议不要使用for-in遍历数组
for of 只能遍历数组,不能遍历对象
for of可以简单有效的遍历数组,并且不会遍历原型上的method和name
如果想要遍历,可通过Object.keys()把对象转化为数组再遍历
他有如下优点:
这是最简洁、最直接的遍历数组元素的语法
这个方法避开了for-in循环的所有缺陷
与forEach()不同的是,它可以正确响应break、continue和return语句
简单总结就是
for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值
for-in得到的是对象的key或数组、字符串的下标
for-of得到的是对象的value或数组、字符串的值,另外还可以用于遍历Map和Set
9.Object.defineProperty的用法详解
该方法是es5的方法(千万不要以为是es6的哦),作用是直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。(切记只能用在对象身上不能用在数组身上)
语法:Object.defineProperty(obj, prop, descriptor)
参数说明:
obj:必需。目标对象
prop:必需。需定义或修改的属性的名字
descriptor:必需。目标属性所拥有的特性
访问器属性
Configurable:和数据属性的 [[Configurable]] 一样,表示能否通过delete删除此属性,能否修改属性的特性,或能否修改把属性修改为访问器属性,如果直接使用字面量定义对象,默认值为true
Enumerable:和数据属性的 [[Configurable]] 一样,表示该属性是否可枚举,即是否通过for-in循环或Object.keys()返回属性,如果直接使用字面量定义对象,默认值为true
Get:一个给属性提供 getter 的方法(访问对象属性时调用的函数,返回值就是当前属性的值),如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined
Set:一个给属性提供 setter 的方法(给对象属性设置值时调用的函数),如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined