一 性能优化相关
1.1 有做过前端优化相关的工作吗?
做性能优化的目的
减少首屏加载时间,提高用户体验,可以从以下几个方面进行优化;
从哪里入手
1,让加载更快
1,只请求当前需要的资源
异步加载, 懒加载, polyfily等
2,缩减资源体积,进行资源合并;
打包压缩 webpack 4
gzip压缩 1.2M => 300K
图片格式的优化,压缩,根据屏幕分辨率展示不同分辨率的图片,可以采用webp格式,尽量控制cookie的大小,request header每次都会携带cookie;
3,可以做一些时序的优化
js代码方面,采用promise.all,渲染方面使用ssr,提高seo,资源下载采用prefetch, pretender,preload;
4,合理利用缓存,多使用内存或其他方法
cdn cdn预热 cdn刷新
5,减少CPU计算量,减少网络加载耗时;
2,让渲染更快
1,css放在head,js放在body最下面;
2,尽早开始执行js,用DOMContentLoaded触发;
3,采用懒加载(图片懒加载,上滑加载更多);
4,对DOM查询进行缓存
5,将频繁执行的DOM操作,合并到一起插入DOM树中;
6,截流throttle和防抖debounce;
3,具体实现如下举例
缓存
1,静态资源添加hash后缀,根据文件内容计算hash;
2,文件内容不变,则hash不变,则url不变;
3,url和文件不变,则会自动触发http缓存机制,返回304;
module.exports = {
mode: 'production',
entry: path.join(__dirname, 'src', 'index'),
output: {
filename: 'bundle.[contenthash].js',
path: path.join(__dirname, 'dist')
},
}
缓存DOM查询
//不缓存DOM查询结果
for (let i = 0; i < document.getElementsByTagName('p').length; i++) {
// 每次循环,都会计算length,频繁进行DOM操作
}
//缓存DOM查询结果
const pList = document.getElementsByTagName('p');
const length = pList.length;
for (let i = 0; i < length; i++) {
// 缓存length,只进行一次DOM查询
}
多个DOM操作,一起插入到DOM结构
const listNode = document.getElementsById('list');
//创建一个文档片段,此时还没插入到DOM树中
const flag = document.createDocumentFlagment();
//执行插入
for (let x = 0; x < 10; x++) {
const li = document.createElement("li");
li.innerHTML = "List item " + x;
flag.appendChild(li);
}
// 都完成之后,在插入到DOM中
listNode.appendChild(flag);
尽早开始执行JS
window.addEventListener('load', function() {
// 页面的全部资源加载完才会执行,包括图片,视频等
})
window.addEventListener('load', function() {
// DOM渲染完即可,此时图片,视频还可能没有加载完
})
1.2 说一下从输入URL到页面渲染的过程
这里主要考察以下三点:
1,加载资源的形式
2,加载资源的过程
3,渲染页面的过程
资源的形式:
html代码
媒体文件,如图片,视频等
JavaScript css
加载过程:
DNS解析:域名 - > IP 地址
浏览器根据IP地址向服务器发起http请求
服务器处理http请求,并返回给浏览器
渲染过程:
根据html代码生成DOM Tree
根据css代码生成CSSDOM
将DOM Tree和CSSOM整合形成Render Tree;
根据Render Tree渲染页面
遇到script标签则暂停渲染,优先加载并执行JS代码,完成再继续
直至把Render Tree渲染完成
1.3 面试题分析
1,如果一段js执行时间非常长,怎么去分析
可以采用装饰器的模式,代码实现如下
export function measure(target, name, descriptor) {
const oldvalue = descriptor.value;
descriptor.value = async function() {
console.time(name);
const ret = await oldvalue.apply(this, arguments);
console.timeEnd(name);
return ret;
}
return descriptor;
}
2,阿里云oss支持通过链接后面拼接参数来做图片的格式转换,尝试写一下,把任意图片格式转换为webp,需要注意什么?
function checkWep() {
try {
return (
document.createElement('canvas')
.toDataURL('image/webp')
.indexOf('data:image/webp') === 0
);
} catch (e) {
return false;
}
}
const supportWebp = checkWep();
export function getEebpImageUrl(url) {
if(!url) {
throw Error('url 不能为空');
}
if(url.startsWith('data:')) {
return url;
}
if(!supportWebp) {
return url;
}
return url + '?x-oss-processXXXXXX';
}
3,如果有大量的图片需要展示,除了懒加载的方式,有没有什么其他方法限制一下同时加载的图片数量?
function limitLoad(urls, handler, limit) {
}
const urls = [
{
info: '1.png',
time: '1000'
},
{
info: '2.png',
time: '500'
},
{
info: '3.png',
time: '300'
},
{
info: '4.png',
time: '400'
}
];
// 设置我们要执行的任务
function loadImg(url) {
return new Promise((resolve, reject) => {
console.log("----" + url.info + " start!");
setTimeout(() => {
console.logg(url.info + " OK!!!");
resolve();
}, url.time)
});
};
limitLoad(urls, loadImg, 3)
主要实现promise的并发控制
function limitLoad(urls, handler, limit) {
const sequence = [].concat(urls);
let promises = [];
promises = sequence.splice(0, limit).map((url, index) => {
return handler(url).then(() => {
return index;
})
});
let p = Promise.race(promises);
for (let i = 0; i < sequence.length; i++) {
p = p.then((res) => {
promises[res] = handler(sequence[i]).then(() => {
return res;
});
return Promise.race(promises);
});
}
}
二 内存管理相关
2.1 平时有关注过前端的内存处理吗?
1,内存的生命周期
内存分配:声明变量,函数,对象的时候,js会自动分配内存
内存的使用:函数调用的时候;
内存回收
2,js中的垃圾回收机制
引用计数垃圾回收:a对象对b对象有访问权限,那么a引用b对象,只要有引用存在,就不能被回收,这种方式有一个问题,就是循环引用。
标记清除法:无法到达的对象,过程如下:
1,在运行的时候给存储在内存的所有变量加上标记;
2,从根部触发,能触及的对象,把标记清楚;
3,哪些有标记的就被视为即将要删除的变量;
3,js中,有哪些常见的内存泄漏
1,全局变量
window.a = 'xxxx'
window.a = null;
2,未被清楚的定时器和回调
const timer = setTimeout(() => ....}, 1000);
clearTimeout(timer);
setInterval(());
clearInterval();
3,闭包
4,dom的引用
const elements = {
image: document.getElemetyId('image');
}
document.body.removeChild(document.getElemetyId('image'));
elements.image = null;
4,如何避免内存泄漏
减少不必要的全局变量
使用完数据后,及时清除引用;
2.2 代码实现题
1,实现sizeOf函数,传入一个参数object,计算这个Object占用了多少bytes?
const testData = {
a: 111,
b: 'cccc',
2222: false
}
function calculator(object) {
const objectType = typeof object;
switch (objectType) {
case 'string': {
return object.length * 2;
}
case 'boolean': {
return 4;
}
case 'number': {
return 8;
}
case 'object': {
if(Array.isArray(object)) {
return object.map(calculator).reduce((res, curr) => res + curr, 0);
} else {
return sizeOfObject(object);
}
}
default: {
return 0;
}
}
}
const seen = new WeakSet();
function sizeOfObject(object) {
if(object == null) return 0;
let bytes = 0;
const properties = Object.keys(object);
for (let i = 0; i < properties.length; i++) {
const key = properties[i];
bytes += calculator(key);
if(typeof object[key] === 'object' && object[key] !== null) {
if(seen.has(object[key])) {
continue;
}
seen.add(object[key]);
}
bytes += calculator(object[key]);
}
return bytes;
}
console.log(calculator(testData));
三 发布订阅模式
平时用过发布订阅模式吗?
手写一个简易的Event Bus
class EventEmitter {
constructor(maxListens) {
this.events = {};
this.maxListens = maxListens || Infinity;
}
emit(event, ...args) {
const cbs = this.events[event];
if(!cbs) {
console.log('没有这个事件');
return this;
}
cbs.forEach(cb => cb.apply(this, args));
return this;
}
on(event, cb) {
if(!this.events[event]) {
this.events[event] = [];
}
if (this.maxListens !== Infinity && this.events[event].length >= this.maxListens) {
console.log(`当前事件${event}超过最大监听数`);
return this;
}
this.events[event].push(cb);
return this;
}
once(event, cb) {
const func = (...args) => {
this.off(event, func);
cb.apply(this, args);
}
this.on(event, func);
return this;
}
off(event, cb) {
if(!cb) {
this.events[event] = null;
} else {
this.events[event] = this.events[event].filter(item => item != cb);
}
return this;
}
}
const add = (a, b) => console.log(a + b);
const log = (...args) => console.log(...args);
const event = new EventEmitter();
event.on('add', add);
event.on('log', log);
event.emit('add', 1, 2);
event.emit('log', 'hi~');
event.off('add');
event.emit('add', 1, 2);
event.once('once', add);
event.emit('add', 1, 2);
event.emit('add', 1, 2);
event.emit('add', 1, 2);
四 HTTP请求相关
聊一聊前端http请求相关
4.1 跨域相关
1,介绍一下通源策略
1,ajax请求时,浏览器要求当前网页和server必须同源(安全);
2,同源:协议,域名,端口,三者必须一致;
2,加载图片 css js,可无视同源策略
<img src = 跨域的图片地址 /> //可用于统计打点,可使用第三方统计服务
<link src = 跨域的css地址 /> // 可使用CDN,CDN一般都是外域
<script src = 跨域的js地址></script> //可使用CDN,CDN一般都是外域 ,可实现JSONP
3,平时怎么解决跨域问题的
所有的跨域,都必须经过server端允许和配合,未经过server端允许就实现跨域,说明浏览器有漏洞,危险信号;
1,jsonp
jsonp的原理,就是利用script标签可以绕过跨域限制,服务器可以任意动态拼接数据返回,所以script就可以获得跨域的数据,只要服务端愿意返回;
客户端
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
$.ajax({
url: 'http://127.0.0.1:8001/list',
method: 'get',
dataType: 'jsonp',
success: res => {
console.log(res);
}
});
</script>
服务端
let express = require('express'),
app = express();
app.listen(8001, _ => {
console.log('OK~');
});
app.get('/list', (req, res) => {
let {
callback = Function.prototype
} = req.query;
let data = {
code: 0,
message: 'hello stoney'
};
res.send(`${callback}(${JSON.stringify(data)})`);
});
2, CORS跨域资源共享
客户端
import axios = from 'axios';
import qs from 'qs';
axios.default.baseURL = baseURL = "http://127.0.0.1:3000";
axios.default.timeout = 10000;
axios.default.withCredentials = true;
/*
* 设置请求传递的数据格式(看服务器要求什么格式)
* x-www-form-urlencoded
*/
axios.default.headers['Content-Type'] = 'application/x-www-form-urlencoded';
axios.default.transformRequest = data => qs.stringify(data);
/*
* 设置请求拦截
* TOKEN校验(JWT): 接收服务器返回的token,存储到vuex/本地存储中,每一次香港服务器发请求,都要带上token
*/
axios.interceptors.request.use(config => {
let token = localStorage.getItem('token');
token && (configg.headers.Authorization = token);
return config;
}, error => {
return Promise.reject(error)
});
/*
* 相应拦截器
*/
axios.interceptors.response.use(response => {
return response.data;
}, error => {});
export default axios;
服务端
app.use(req, res, next) => {
res.header("Access-Control-Allow-Origin", "");
res.header("Access-Control-Allow-Credentials", true);
res.header("Access-Control-Allow-Headers" , "PUT, POST, GET, DELETE, OPTIONS, HEAD");
res.header("Access-Control-Allow-Methods", "Content-Type, Content-Length, Authorization, Accept, X-Requested-With");
req.method === 'OPTIONS' ? res.send('CURRENT SERVERS SUPPORT CROSS DOMAIN REQUESTS!') : next();
});
3, node 正向代理, /api -> 同域的node服务 -> /api -> 前端
4,nginx反向代理,proxy_pass
5,基于iframe的跨域解决方案
window.name
document.domain
location.hash
post message
4.2 代码实现题
2,有做过全局的请求处理吗?比如统一处理登录态?统一处理全局错误?
axios
adaptor
interceptor request response
3,代码题,你能给xhr添加hook,实现在各个阶段打日志吗?
class XhrHook {
}
new XhrHook({
open: function() {
console.log('open');
// return false;
},
onload: function () {
console.log('onload');
},
onreadystatechange: function() {
console.log('onreadystatechange');
},
onerror: function () {
console.log('hook error')
}
});
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.baidu.com', true),
xhr.send();
xhr.onreadystatechange = function (res) {
console.log('statechange');
};
xhr.onerror = function() {
console.log('error');
}
实现如下:
class XhrHook {
constructor(beforeHooks = beforeHooks = {}, afterHooks = {}) {
this.XHR = window.XMLHttpRequest;
this.beforeHooks = beforeHooks;
this.afterHooks = afterHooks;
this.init();
}
init() {
let _this = this;
window.XMLHttpRequest = function() {
this._xhr = new _this.XHR();
_this.overwrite(this);
}
}
overwrite(proxyXHR) {
for (let key in proxyXHR) {
if (typeof proxyXHR._xhr[key] === 'function') {
this.overwriteMethod(key, proxyXHR);
continue;
}
this.overwriteAttributes(key, proxyXHR);
}
}
overwriteMethod(key, proxyXHR) {
let beforeHooks = this.beforeHooks;
let afterHooks = this.afterHooks;
proxyXHR[key] = (...args) => {
if(beforeHooks[key]) {
const res = beforeHooks[key].call(proxyXHR, args);
if(res === false) {
return;
}
}
const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args);
afterHooks[key] && afterHooks[key].call(proxyXHR._xhr, args);
return res;
}
}
overwriteAttributes(key, proxyXHR) {
Object.defineProperties(proxyXHR, key, this.setProperyDescriptor(key, proxyXHR));
}
setProperyDescriptor (key, proxyXHR) {
let obj = Object.create(null);
let _this = this;
obj.set = function(val) {
if(!key.startsWith('on')) {
proxyXHR['_' + key] = val;
return;
}
if(_this.beforeHooks[key]) {
this._xhr[key] = function(...args) {
_this.beforeHooks[key].call(proxyXHR);
val.apply(proxyXHR, args);
}
return;
}
this._xhr[key] = val;
}
obj.get = function() {
return proxyXHR['_' + key] || this._xhr[key];
}
return obj;
}
}
4.3 实现ajax
function ajax(url) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
if(xhr.status === 200) {
resolve(
JSON.parse(xhr.responseText)
)
} else if(xhr.status === 404) {
reject(new Error('404 not found'));
}
}
}
xhr.send(null);
});
return p;
}
const url = '/data/test.json';
ajax(url).then(res => console.log(res))
.catch(err => console.log(err));
五 浏览器存储
1,描述cookie localStorage sessionStorage的区别
cookie:
本身用于浏览器和server通讯,被借用到本地存储来,可以用document.cookie = '..'来修改;
缺点:
1,存储大小有限制,最大4KB;
2,http请求时需要发送到服务端,增加了请求数据量
3,只能用document.cookie = '..'来修改,api太简陋;
localStorage和sessionStorage:
1,HTML5专门为存储而设计,最大可达到5M;
2,API简单易用,setItem, getItem;
3,数据不会随着http请求发送给服务端;
localStorage和sessionStorage区别:
1,localStorage数据会永久存储,除非代码或手动删除;
2,sessionStorage数据只存在当前会话中。浏览器tab页关闭则清空;
3,一般用localStorage会更多一些;
六 同步异步
6.1 同步和异步的区别
单线程和同步
1,js是单线程语言,只能同时做一件事
2,浏览器和nodeJs已经支持JS启动进程,如web worker
3,js和DOM 渲染共用同一个线程,因为js可以修改DOM结构;
4,遇到等待(网络请求和定时任务)不能卡住
5,需要异步来实现
6,回调函数callback函数形式
//异步
console.log(100);
setTimeout(function() {
console.log(200);
}, 1000);
console.log(300)
//同步
console.log(100);
alert(200);
console.log(300);
JS是单线程语言,异步不会阻塞代码执行,同步会阻塞代码执行;
应用场景
网络请求,如ajax,图片加载
定时任务,如setTimeout等
//ajax
console.log('start');
$.get('./data1.json', function(data1) {
console.log(data1);
})
console.logg('end');
//图片加载
console.logg('start');
let img = document.createElement('img');
img.onload = function() {
console.log('loaded');
}
img.src = '/xxx.png';
console.log('end');
实现形式
回调函数
//获取第一份数据
$.get(url1, (data1) => {
console.log(data1);
//获取第二份数据
$.get(url2, (data2) => {
console.log(data2);
//获取第三份数据
$.get(url3, (data3) => {
console.log(data3);
// 还可能获取更多数据
})
})
})
Promise形式
function getData(url) {
return new Promise((resolve, reject) => {
$.ajax({
url,
success(data) {
resolve(data);
},
error(err) {
reject(err);
}
})
})
}
const url1 = '/data1.json';
const url2 = '/data2.json';
const url3 = '/data3.json';
getData(url1).then(data1 => {
console.log(data1);
return getData(url2);
}).then(data2 => {
console.log(data2);
return getData(url3)
}).then(data3 => {
console.log(data3);
}).catch(err => console.log(err));
6.2 手写promise加载图片
function loadImg(src) {
const p = new Promisw((resolve, reject) => {
const img = document.createElement('img');
img.onload = () => {
resolve(img);
}
img.onerror = () => {
const err = new Error(`图片加载失败 ${err}`);
reject(err);
}
img.src = src;
})
return p;
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!