最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • interview准备3- 持续更新中

    正文概述 掘金(Stoney_S)   2021-03-15   478

    一  性能优化相关

    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;
    }
    

    起源地下载网 » interview准备3- 持续更新中

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元