不积跬步无以至千里,不积小流无以成江海
前端工程的价值体现
-
为简化用户使用提供技术支持(交互部分)
-
为多个浏览器兼容性提供支持
-
为提高用户浏览速度(浏览器性能)提供支持
-
为跨平台或者其他基于webkit或其他渲染引擎的应用提供支持
-
为展示数据提供支持(数据接口)
面试准备:
1、请简述XMLHttpRequest、JSONP的适用场景,并针对两种请求形式简述如何检测请求错误。
A:(1). XMLHttpRequest用于浏览器端与服务器端异步请求数据从面实现对页面的无刷新修改,支持GET/POST请求,一般用于非跨域的场景。如果需要使用XMLHttpRequest跨域请求数据,需要通过CORS头支持。 JSONP用于跨域请求数据的场景,只支持GET请求。
(2). XMLHttpRequest异常判断一般通过该对象的readystate和http状态码status来判断,JSONP的异常判断一般是onerror事件和超时timer来判断
2、请简述浏览器渲染页面的过程,并给出下方script代码中对哪些CSS属性的"修改"会触发重绘(repaint)和重排(reflow)?
<style>.sg-container {padding: 10px;margin-bottom: 10px;width: 100px; height: 100px;}</style>
<div class="sg-container">
<p style="line-height:20px">2019搜狗校园招聘</p >
<script>
document.querySelector('p').style.cssText +='height:10px; line-height:24px;font-size:20px;visibility:hidden;background-color:#00f;border:1px solid #f00';
</script>
</div>
A:浏览器渲染过程因不同内核可能会有差异,现以webkit为例描述浏览器渲染原理,浏览器渲染过程主要分为三个阶段,先详述如下:
第一阶段:
1. 用户输入URL时,webkit依赖网络模块加载网页或资源数据
2. 网页被交给HTML解释器转变成一系列的词语
3. 解释器根据词语构建节点并形成DOM树
4. 如果节点是CSS、图片、视频等资源,会调用资源加载器加载他们,因该类资源加载是异步的,不会阻塞当前DOM树的继续创建
5. 如果节点是javascript,停止当前DOM树的创建,直到javascript资源加载完成并被javascript引擎执行后才继续进行DOM的创建
第二阶段:
1. CSS解释器解析CSS文件成内部表示结构,并在DOM树上附加样式信息形成RenderObject树
2. RenderObject节点在创建的同时,webkit会根据网页的层次结构创建RenderLayer树,同时创建一个虚拟的绘图上下文
第三阶段:
1.根据生成的绘图上下文和2D或3D图形库生成最终的图像
对于包含动画和用户交互的动态网页,浏览器的渲染过程会重复的执行,可能会触发不同程度的重排和重绘。
重排属性:height、line-height、font-size、border
重绘属性:height、line-height、font-size 、border、background-color、visibility
3、异步编程的实现方式?
-
回调函数
- 优点:简单、容易理解
- 缺点:不利于维护,代码耦合高
-
事件监听(采用时间驱动模式,取决于某个事件是否发生):
- 优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数
- 缺点:事件驱动型,流程不够清晰
-
发布/订阅(观察者模式)
- 类似于事件监听,但是可以通过‘消息中心’,了解现在有多少发布者,多少订阅者
-
Promise对象
- 优点:可以利用then方法,进行链式写法;可以书写错误时的回调函数;
- 缺点:编写和理解,相对比较难
-
Generator函数
- 优点:函数体内外的数据交换、错误处理机制
- 缺点:流程管理不方便
-
async函数
- 优点:内置执行器、更好的语义、更广的适用性、返回的是Promise、结构清晰。
- 缺点:错误处理机制
4、JS 数组和对象的遍历方式,以及几种方式的比较
-
for-in
循环 -
forEach
- 这里的
forEach
回调中两个参数分别为value
,index
forEach
无法遍历对象- IE不支持该方法;
Firefox
和chrome
支持 forEach
无法使用break
,continue
跳出循环,且使用return
是跳过本次循环
- 这里的
-
这两种方法应该非常常见且使用很频繁。但实际上,这两种方法都存在性能问题
-
在方式一中,
for-in
需要分析出array
的每个属性,这个操作性能开销很大。用在key
已知的数组上是非常不划算的。所以尽量不要用for-in
,除非你不清楚要处理哪些属性,例如JSON
对象这样的情况 -
在方式2中,循环每进行一次,就要检查一下数组长度。读取属性(数组长度)要比读局部变量慢,尤其是当
array
里存放的都是DOM
元素,因为每次读取都会扫描一遍页面上的选择器相关元素,速度会大大降低
5、常见校验规则
-
是否为手机号码:
/^1[3|4|5|6|7|8][0-9]{9}$/
-
是否全为数字:
/^[0-9]+$/
-
是否为邮箱:
/^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/
-
是否为身份证:
/^(^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$)|(^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[Xx])$)$/
-
是否为Url:
/[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/
-
是否为IP:
/((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}/
6、vue获取select option的值
<select ref="select" @change="changeSelect">
<option></option>
</select>
//方案一:ref方式找到dom元素获取
let option = this.$refs.select.value;
//方案二:
let option = document.getElementById("select").value
console.log(option)
7、如下哪种前端构建技术可以剔除 Javascript 中没有被使用的代码?
A Tree Shaking
B Code Splitting
C Hot Module Replacement
D Source Mapping
Answer:
Tree Shaking用于移除 JavaScript 上下文中的未引用代码(dead-code)。
Code Splitting可以将代码分成多个捆绑包,然后可以按需或并行加载。
模块热替换(HMR - hot module replacement)会在应用程序运行过程中,替换、添加或删除模块,而无需重新加载整个页面。
Source Mapping是从已转换的代码映射到原始源的文件,使浏览器能够重构原始源并在调试器中显示重建的原始源。
8、理解:及实现call、apply及bind函数
1、call 方法第一个参数是要绑定给this的值,后面传入的是一个参数列表。当第一个参数为null、undefined的时候,默认指向window。
2、apply接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组。当第一个参数为null、undefined的时候,默认指向window。
3、bind 第一个参数是this的指向,从第二个参数开始是接收的参数列表。区别在于bind方法返回值是函数以及bind接收的参数列表的使用。
4、bind返回对应函数, 便于稍后调用; apply, call则是立即调用。
call方法的实现:
/**
* 实现思路:
* 1. 当不传入第一个参数时,则默认上下文环境为window
* 2. 改变this 指向,让新的对象可以执行该函数,并能接受参数
*
*/
Function.prototype.myCall=function(context){
//如果调用者不是函数则抛出异常
if(typeof this!=='function'){
throw new TypeError('Error');
}
//如果context,没有传,或者传入undefined null 则this 执行 window
context=context || window;
context.fn=this;
const args=[...arguments].slice(1);
const result=context.fn(...args);
delete context.fn;
return result;
}
apply方法的实现:
/**
* apply实现思路与call相同,只是参数处理方式不同
*/
Function.prototype.myApply=function(context){
if(typeof this !=='function'){
throw new TypeError('Error');
}
context=context||window;
context.fn=this;
let result=null;
//如果传入参数则出入
if(arguments[1]){
result=context.fn(...arguments[1]);
}else{
result=context.fn();
}
//释放内存空间
delete context.fn;
return result;
}
bind方法的实现:
/**
* 实现思路如下:
* 1. 对传入context的处理,如果不传或传null、undefined 则赋值为window
* 2. 对于返回函数调用形式处理:
* 2.1 普通函数调用
* 对于该形式我们应该处理 f.bind(obj,2)(4)形式参数的处理
* 2.2 new的方式调用
* 对于该形式来说,this不会被外界传入改变
*/
Function.prototype.myBind=function(context){
if(typeof this !=='function'){
throw new TypeError('error');
}
const that=this;
const args=[...arguments].slice(1);
context=context||window;
return function F(){
if(this instanceof F){
return new that(...args,...arguments);
}
return that.apply(context,args.concat(...arguments));
}
}
9、函数防抖和节流
防抖(debounce)
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
节流(throttle)
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。
区别:
函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
防抖的实现:
// 防抖
function debounce(fn, wait) {
var timeout = null;
return function() {
if(timeout !== null) clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
// 处理函数
function handle() {
console.log(Math.random());
}
// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));
当持续触发scroll事件时,事件处理函数handle只在停止滚动1000毫秒之后才会调用一次,也就是说在持续触发scroll事件的过程中,事件处理函数handle一直没有执行。
节流的实现
函数节流主要有两种实现方法:时间戳和定时器。接下来分别用两种方法实现throttle
节流throttle代码(时间戳):
//节流throttle代码(时间戳)
var throttle = function(func, delay) {
var prev = Date.now();
return function() {
var context = this;
var args = arguments;
var now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
//处理函数
function handle() {
console.log(Math.random());
}
//滚动事件
window.addEventListener('scroll', throttle(handle, 1000));
节流throttle代码(定时器):
// 节流throttle代码(定时器):
var throttle = function(func, delay) {
var timer = null;
return function() {
var context = this;
var args = arguments;
if (!timer) {
timer = setTimeout(function() {
func.apply(context, args);
timer = null;
}, delay);
}
}
}
//处理函数
function handle() {
console.log(Math.random());
}
//滚动事件
window.addEventListener('scroll', throttle(handle, 1000));
不断更新中....