이 기사는 고급 프론트엔드 기술을 공유하고, 9가지 프론트엔드 면접 질문을 정리 및 요약하고, 지식 포인트를 통합하는 데 도움이 되기를 바랍니다.
두 방법 모두 변수 유형을 결정하는 데 사용할 수 있습니다
차이: 전자는 변수를 결정하는 것입니다. 후자는 변수가 특정 유형인지 확인하는 것입니다. 부울 값을 반환합니다
(1) typeof
Defects:
1. 배열, 일반, 날짜, 객체로 모두 객체를 반환하지만 기능을 확인할 수 있습니다. 감지 객체가 정규 표현식인 경우 Safari 및 Chrome에서 typeof를 사용하면 "함수"가 잘못 반환되며 다른 브라우저에서는 return object.
2. null을 판단할 때 객체가 반환되는 것은 js의 결함입니다. NaN을 판단할 때 반환은 number입니다
(2) 변수가 특정 유형인지 감지하는 데 사용할 수 있습니다. , 부울 값을 반환하고 이 변수가 특정 함수의 인스턴스인지 확인할 수 있습니다. 이 변수가 감지하는 것은 객체의 프로토타입입니다
let num = 1 num instanceof Number // false num = new Number(1) num instanceof Number // true
분명히 둘 다 num이고 둘 다 1입니다. 첫 번째가 아니기 때문입니다. 객체이고 기본 유형인 경우 false를 직접 반환하고 두 번째 유형은 객체로 캡슐화되므로 true입니다.
여기서 이 문제에 주의를 기울여야 합니다. 어떤 사람들은 감지 대상의 __proto__가 생성자의 프로토타입과 동일하면 true를 반환한다고 말합니다. as:
let num = 1 num.__proto__ === Number.prototype // true num instanceof Number // false num = new Number(1) num.proto === Number.prototype // true num instanceof Number // true num.proto === (new Number(1)).proto // true
게다가, instanceof 또 다른 단점이 있습니다: 페이지에 여러 프레임이 있는 경우, 즉 여러 전역 환경이 있는 경우 프레임 a에서 배열을 정의한 다음, 프레임 b에서 판단하기 위해 인스턴스of를 사용합니다. , 그러면 해당 배열의 프로토타입 체인이 프레임 b에서 배열을 찾을 수 없는 경우 해당 배열은 배열이 아닌 것으로 판단됩니다.
해결 방법: Object.prototype.toString.call(value) 메서드를 사용하여 객체를 호출하고 객체의 생성자 이름을 가져옵니다. 인스턴스오브의 프레임워크 문제를 해결할 수 있습니다. 단점은 사용자 정의 유형의 경우 [객체 객체]
// [1,2,3] instanceof Array ---- true // L instanceof R // 变量R的原型 存在于 变量L的原型链上 function instance_of(L,R){ // 验证如果为基本数据类型,就直接返回false const baseType = ['string', 'number','boolean','undefined','symbol'] if(baseType.includes(typeof(L))) { return false } let RP = R.prototype; //取 R 的显示原型 L = L.__proto__; //取 L 的隐式原型 while(true){ // 无线循环的写法(也可以使 for(;;) ) if(L === null){ //找到最顶层 return false; } if(L === RP){ //严格相等 return true; } L = L.__proto__; //没找到继续向上一层原型链查找 } }
예를 들어
function Person(){ this.name = "小红" } p = Person();
이렇게 되면 어떻게 될까요? , 해결 방법
직접 사용하면 전역 개체 창에 매핑됩니다. 해결책은 다음과 같습니다. 먼저 이 객체가 올바른 유형의 인스턴스인지 확인하세요. 그렇지 않은 경우 새 인스턴스가 생성되어 반환됩니다. 아래 예를 보세요
function Person(){ if(this instanceof Person){ this.name = "小红" }else{ return new Person() } } p = Person();
JavaScript 코드에서는 브라우저 간의 동작 차이로 인해 대부분의 JavaScript 코드에는 브라우저를 확인하기 위한 if 문이 많이 포함되어 있습니다. 다양한 브라우저의 호환성 문제를 해결하는 기능입니다. 예를 들어 이벤트를 추가하는 함수는 다음과 같습니다.
function addEvent (element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false); } else if (element.attachEvent) { element.attachEvent("on" + type, handler); } else { element["on" + type] = handler; } }
addEvent()를 호출할 때마다 브라우저에서 지원하는 기능을 주의 깊게 확인해야 합니다. 먼저 addEventListener 메소드가 지원되는지 확인하고, 아직 지원되지 않는 경우 DOM 레벨 0 메소드를 사용하여 이벤트를 추가하십시오. addEvent()를 호출하는 동안 이 프로세스는 매번 완료되어야 합니다. 실제로 브라우저가 메서드 중 하나를 지원하면 항상 지원하므로 다른 분기를 감지할 필요가 없습니다. 즉, if 문을 매번 실행할 필요가 없으며 코드가 더 빠르게 실행될 수 있습니다. . 해결책을 지연 로딩이라고 합니다. 소위 지연 로딩이란 함수의 if 분기가 한 번만 실행되고 나중에 함수가 호출될 때 지원되는 분기 코드를 직접 입력한다는 의미입니다. 지연 로딩을 구현하는 방법에는 두 가지가 있습니다. 첫 번째는 함수가 처음 호출될 때 함수 자체를 두 번 처리하여 분기 조건을 충족하는 함수로 호출하는 것입니다. 원래 함수일 필요는 없습니다. 실행 분기를 통과한 후 다음과 같은 방법으로 지연 로딩을 사용하여 addEvent()를 다시 작성할 수 있습니다.
function addEvent (type, element, handler) { if (element.addEventListener) { addEvent = function (type, element, handler) { element.addEventListener(type, handler, false); } } else if(element.attachEvent){ addEvent = function (type, element, handler) { element.attachEvent('on' + type, handler); } } else{ addEvent = function (type, element, handler) { element['on' + type] = handler; } } return addEvent(type, element, handler); }
이 지연 로드 addEvent()에서는 if 문의 각 분기가 addEvent 변수에 값을 할당하여 원래 함수를 효과적으로 처리합니다. 마지막 단계는 새로운 할당 함수를 호출하는 것입니다. 다음에 addEvent()가 호출되면 새로 할당된 함수가 직접 호출되므로 if 문을 실행할 필요가 없습니다.
지연 로딩을 구현하는 두 번째 방법은 함수 선언 시 적절한 함수를 지정하는 것입니다. 이런 방식으로 함수가 처음 호출될 때 성능 손실은 없지만 코드가 로드될 때 약간의 성능 손실만 발생합니다. 다음은 이 아이디어에 따라 다시 작성된 addEvent() 입니다.
var addEvent = (function () { if (document.addEventListener) { return function (type, element, fun) { element.addEventListener(type, fun, false); } } else if (document.attachEvent) { return function (type, element, fun) { element.attachEvent('on' + type, fun); } } else { return function (type, element, fun) { element['on' + type] = fun; } } })();
이 예에 사용된 기술은 익명의 자체 실행 함수를 생성하여 어떤 함수를 사용해야 하는지 결정하는 것입니다. 차이점은 함수 표현식을 사용한다는 것입니다(var 정의 함수 사용). ) 그리고 새로운 익명 함수를 추가합니다. 또한 각 분기는 올바른 함수를 반환하고 이를 즉시 변수 addEvent에 할당합니다.
惰性载入函数的优点只执行一次if分支,避免了函数每次执行时候都要执行if分支和不必要的代码,因此提升了代码性能,至于那种方式更合适,就要看您的需求而定了。
概念:限制一个函数在一定时间内只能执行一次。
主要实现思路 就是通过 setTimeout 定时器,通过设置延时时间,在第一次调用时,创建定时器,先设定一个变量true,写入需要执行的函数。第二次执行这个函数时,会判断变量是否true,是则返回。当第一次的定时器执行完函数最后会设定变量为false。那么下次判断变量时则为false,函数会依次运行。目的在于在一定的时间内,保证多次函数的请求只执行最后一次调用。
函数节流的代码实现
function throttle(fn,wait){ var timer = null; return function(){ var context = this; var args = arguments; if(!timer){ timer = setTimeout(function(){ fn.apply(context,args); timer = null; },wait) } } } function handle(){ console.log(Math.random()); } window.addEventListener("mousemove",throttle(handle,1000));
函数节流的应用场景(throttle)
概念:函数防抖(debounce),就是指触发事件后,在 n 秒内函数只能执行一次,如果触发事件后在 n 秒内又触发了事件,则会重新计算函数延执行时间。
函数防抖的要点,是需要一个 setTimeout 来辅助实现,延迟运行需要执行的代码。如果方法多次触发,则把上次记录的延迟执行代码用 clearTimeout 清掉,重新开始计时。若计时期间事件没有被重新触发,等延迟时间计时完毕,则执行目标代码。
函数防抖的代码实现
function debounce(fn,wait){ var timer = null; return function(){ if(timer !== null){ clearTimeout(timer); } timer = setTimeout(fn,wait); } } function handle(){ console.log(Math.random()); } window.addEventListener("resize",debounce(handle,1000));
函数防抖的使用场景 函数防抖一般用在什么情况之下呢?一般用在,连续的事件只需触发一次回调的场合。具体有:
目前遇到过的用处就是这些,理解了原理与实现思路,小伙伴可以把它运用在任何需要的场合,提高代码质量。
动画原理 : 眼前所看到图像正在以每秒60次的频率刷新,由于刷新频率很高,因此你感觉不到它在刷新。而动画本质就是要让人眼看到图像被刷新而引起变化的视觉效果,这个变化要以连贯的、平滑的方式进行过渡。 那怎么样才能做到这种效果呢?
刷新频率为60Hz的屏幕每16.7ms刷新一次,我们在屏幕每次刷新前,将图像的位置向左移动一个像素,即1px。这样一来,屏幕每次刷出来的图像位置都比前一个要差1px,因此你会看到图像在移动;由于我们人眼的视觉停留效应,当前位置的图像停留在大脑的印象还没消失,紧接着图像又被移到了下一个位置,因此你才会看到图像在流畅的移动,这就是视觉效果上形成的动画。
与setTimeout相比较:
理解了上面的概念以后,我们不难发现,setTimeout 其实就是通过设置一个间隔时间来不断的改变图像的位置,从而达到动画效果的。但我们会发现,利用seTimeout实现的动画在某些低端机上会出现卡顿、抖动的现象。 这种现象的产生有两个原因:
setTimeout的执行时间并不是确定的。在Javascript中, setTimeout 任务被放进了异步队列中,只有当主线程上的任务执行完以后,才会去检查该队列里的任务是否需要开始执行,因此 setTimeout 的实际执行时间一般要比其设定的时间晚一些。
刷新频率受屏幕分辨率和屏幕尺寸的影响,因此不同设备的屏幕刷新频率可能会不同,而 setTimeout只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相同。
以上两种情况都会导致setTimeout的执行步调和屏幕的刷新步调不一致,从而引起丢帧现象
requestAnimationFrame:与setTimeout相比,requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机。具体一点讲,如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,换句话说就是,requestAnimationFrame的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
除此之外,requestAnimationFrame还有以下两个优势:
CPU节能:使用setTimeout实现的动画,当页面被隐藏或最小化时,setTimeout 仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,完全是浪费CPU资源。而requestAnimationFrame则完全不同,当页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统步伐走的requestAnimationFrame也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了CPU开销。
函数节流:在高频率事件(resize,scroll等)中,为了防止在一个刷新间隔内发生多次函数执行,使用requestAnimationFrame可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销。一个刷新间隔内函数执行多次时没有意义的,因为显示器每16.7ms刷新一次,多次绘制并不会在屏幕上体现出来。
白屏时间: 白屏时间指的是浏览器开始显示内容的时间。因此我们只需要知道是浏览器开始显示内容的时间点,即页面白屏结束时间点即可获取到页面的白屏时间。
计算白屏时间 因此,我们通常认为浏览器开始渲染 标签或者解析完 标签的时刻就是页面白屏结束的时间点。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>白屏</title> <script type="text/javascript"> // 不兼容performance.timing 的浏览器,如IE8 window.pageStartTime = Date.now(); </script> <!-- 页面 CSS 资源 --> <link rel="stylesheet" href="common.css"> <link rel="stylesheet" href="page.css"> <script type="text/javascript"> // 白屏时间结束点 window.firstPaint = Date.now(); </script> </head> <body> <!-- 页面内容 --> </body> </html>
因此白屏时间则可以这样计算出:
可使用 Performance API 时
:
白屏时间 = firstPaint - performance.timing.navigationStart;
不可使用 Performance API 时
:
白屏时间 = firstPaint - pageStartTime; //虽然我们知道这并不准确,毕竟DNS解析,tcp三次握手等都没计算入内。
首屏时间: 首屏时间是指用户打开网站开始,到浏览器首屏内容渲染完成的时间。对于用户体验来说,首屏时间是用户对一个网站的重要体验因素。通常一个网站,如果首屏时间在5秒以内是比较优秀的,10秒以内是可以接受的,10秒以上就不可容忍了。超过10秒的首屏时间用户会选择刷新页面或立刻离开。
通常计算首屏的方法有
首屏模块标签标记法
统计首屏内加载最慢的图片的时间
自定义首屏内容计算法
1、首屏模块标签标记法
首屏模块标签标记法,通常适用于首屏内容不需要通过拉取数据才能生存以及页面不考虑图片等资源加载的情况。我们会在 HTML 文档中对应首屏内容的标签结束位置,使用内联的 JavaScript 代码记录当前时间戳。如下所示:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>首屏</title> <script type="text/javascript"> window.pageStartTime = Date.now(); </script> <link rel="stylesheet" href="common.css"> <link rel="stylesheet" href="page.css"> </head> <body> <!-- 首屏可见模块1 --> <div></div> <!-- 首屏可见模块2 --> <div></div> <script type="text/javascript"> window.firstScreen = Date.now(); </script> <!-- 首屏不可见模块3 --> <div></div> <!-- 首屏不可见模块4 --> <div></div> </body> </html>
此时首屏时间等于 firstScreen - performance.timing.navigationStart;
事实上首屏模块标签标记法 在业务中的情况比较少,大多数页面都需要通过接口拉取数据才能完整展示,因此我们会使用 JavaScript 脚本来判断首屏页面内容加载情况。
2、统计首屏内图片完成加载的时间
通常我们首屏内容加载最慢的就是图片资源,因此我们会把首屏内加载最慢的图片的时间当做首屏的时间。
由于浏览器对每个页面的 TCP 连接数有限制,使得并不是所有图片都能立刻开始下载和显示。因此我们在 DOM树 构建完成后将会去遍历首屏内的所有图片标签,并且监听所有图片标签 onload 事件,最终遍历图片标签的加载时间的最大值,并用这个最大值减去 navigationStart 即可获得近似的首屏时间。
此时首屏时间等于 加载最慢的图片的时间点 - performance.timing.navigationStart; //首屏时间尝试: //1,获取首屏基线高度 //2,计算出基线dom元素之上的所有图片元素 //3,所有图片onload之后为首屏显示时间 //
function getOffsetTop(ele) { var offsetTop = ele.offsetTop; if (ele.offsetParent !== null) { offsetTop += getOffsetTop(ele.offsetParent); } return offsetTop; } var firstScreenHeight = win.screen.height; var firstScreenImgs = []; var isFindLastImg = false; var allImgLoaded = false; var t = setInterval(function() { var i, img; if (isFindLastImg) { if (firstScreenImgs.length) { for (i = 0; i < firstScreenImgs.length; i++) { img = firstScreenImgs[i]; if (!img.complete) { allImgLoaded = false; break; } else { allImgLoaded = true; } } } else { allImgLoaded = true; } if (allImgLoaded) { collect.add({ firstScreenLoaded: startTime - Date.now() }); clearInterval(t); } } else { var imgs = body.querySelector('img'); for (i = 0; i<imgs.length; i++) { img = imgs[i]; var imgOffsetTop = getOffsetTop(img); if (imgOffsetTop > firstScreenHeight) { isFindLastImg = true; break; } else if (imgOffsetTop <= firstScreenHeight && !img.hasPushed) { img.hasPushed = 1; firstScreenImgs.push(img); } } } }, 0); doc.addEventListener('DOMContentLoaded', function() { var imgs = body.querySelector('img'); if (!imgs.length) { isFindLastImg = true; } }); win.addEventListener('load', function() { allImgLoaded = true; isFindLastImg = true; if (t) { clearInterval(t); } collect.log(collect.global); });
解释一下思路,大概就是判断首屏有没有图片,如果没图片就用domready时间,如果有图,分2种情况,图在首屏,图不在首屏,如果在则收集,并判断加载状态,加载完毕之后则首屏完成加载,如果首屏没图,找到首屏下面的图,立刻触发首屏完毕。可以想象这么做前端收集是不准的,但是可以确保最晚不会超过win load,所以应该还算有些意义。。没办法,移动端很多浏览器不支持performance api,所以土办法前端收集,想出这么个黑魔法,在基线插入节点收集也是个办法,但是不友好,而且现在手机屏幕这么多。。
3、自定义模块内容计算法
由于统计首屏内图片完成加载的时间比较复杂。因此我们在业务中通常会通过自定义模块内容,来简化计算首屏时间。如下面的做法:
实际上用performance.timing来计算首屏加载时间与白屏时间非常简单与精确。不过目前只支持IE10和chrome 贴下其API的使用
var navigationStart = performance.timing.navigationStart; //1488984540668 console.log(navigationStart); //Wed Mar 08 2017 22:49:44 GMT+0800 (中国标准时间) console.log(new Date(new Date(navigationStart))); 复制代码 redirectStart:到当前页面的重定向开始的时间。但只有在重定向的页面来自同一个域时这个属性才会有值;否则,值为0 redirectEnd:到当前页面的重定向结束的时间。但只有在重定向的页面来自同一个域时这个属性才会有值;否则,值为0 console.log(performance.timing.redirectStart);//0 console.log(performance.timing.redirectEnd);//0 fetchStart:开始通过HTTP GET取得页面的时间 console.log(performance.timing.fetchStart);//1488984540668 domainLookupStart:开始査询当前页面DNS的时间,如果使用了本地缓存或持久连接,则与fetchStart值相等 domainLookupEnd:査询当前页面DNS结束的时间,如果使用了本地缓存或持久连接,则与fetchStart值相等 console.log(performance.timing.domainLookupStart);//1488984540670 console.log(performance.timing.domainLookupEnd);//1488984540671 connectStart:浏览器尝试连接服务器的时间 secureConnectionStart:浏览器尝试以SSL方式连接服务器的时间。不使用SSL方式连接时,这个属性的值为0 connectEnd:浏览器成功连接到服务器的时间 console.log(performance.timing.connectStart);//1488984540671 console.log(performance.timing.secureConnectionStart);//0 console.log(performance.timing.connectEnd);//1488984540719 requestStart:浏览器开始请求页面的时间 responseStart:浏览器接收到页面第一字节的时间 responseEnd:浏览器接收到页面所有内容的时间 console.log(performance.timing.requestStart);//1488984540720 console.log(performance.timing.responseStart);//1488984540901 console.log(performance.timing.responseEnd);//1488984540902 unloadEventStart:前一个页面的unload事件开始的时间。但只有在前一个页面与当前页面来自同一个域时这个属性才会有值;否则,值为0 unloadEventEnd:前一个页面的unload事件结束的时间。但只有在前一个页面与当前页面来自同一个域时这个属性才会有值;否则,值为0 console.log(performance.timing.unloadEventStart);//1488984540902 console.log(performance.timing.unloadEventEnd);//1488984540903 domLoading:document.readyState变为"loading"的时间,即开始解析DOM树的时间 domInteractive:document.readyState变为"interactive"的时间,即完成完成解析DOM树的时间 domContentLoadedEventStart:发生DOMContentloaded事件的时间,即开始加载网页内资源的时间 domContentLoadedEventEnd:DOMContentLoaded事件已经发生且执行完所有事件处理程序的时间,网页内资源加载完成的时间 domComplete:document.readyState变为"complete"的时间,即DOM树解析完成、网页内资源准备就绪的时间 console.log(performance.timing.domLoading);//1488984540905 console.log(performance.timing.domInteractive);//1488984540932 console.log(performance.timing.domContentLoadedEventStart);//1488984540932 console.log(performance.timing.domContentLoadedEventEnd);//1488984540932 console.log(performance.timing.domComplete);//1488984540932 loadEventStart:发生load事件的时间,也就是load回调函数开始执行的时间 loadEventEnd:load事件已经发生且执行完所有事件处理程序的时间 console.log(performance.timing.loadEventStart);//1488984540933 console.log(performance.timing.loadEventEnd);//1488984540933
多线程技术在服务端技术中已经发展的很成熟了,而在Web端的应用中却一直是鸡肋 在新的标准中,提供的新的WebWork API,让前端的异步工作变得异常简单。 使用:创建一个Worker对象,指向一个js文件,然后通过Worker对象往js文件发送消息,js文件内部的处理逻辑,处理完毕后,再发送消息回到当前页面,纯异步方式,不影响当前主页面渲染。
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> <script type="text/javascript"> //创建线程 work对象 var work = new Worker("work.js"); //work文件中不要存在跟ui代码 //发送消息 work.postMessage("100"); // 监听消息 work.onmessage = function(event) { alert(event.data); }; </script> </head> <body> </body> </html>
onmessage = function (event) { //从1加到num var num = event.data; var result = 0; for (var i = 1; i <= num; i++) { result += i; } postMessage(result); }
(学习视频分享:web前端入门、jQuery视频教程)
위 내용은 [고급 기술] 지식을 강화하는 데 도움이 되는 9가지 프론트 엔드 인터뷰 질문!의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!