Dieser Artikel vermittelt fortgeschrittene Front-End-Fähigkeiten, organisiert und fasst neun Front-End-Interviewfragen zusammen und hilft Ihnen, Wissenspunkte zu festigen. Ich hoffe, dass er für alle hilfreich ist!
Beide Methoden können zur Bestimmung des Variablentyps verwendet werden Um welchen Typ handelt es sich? Letzteres dient dazu, festzustellen, ob die Variable einen bestimmten Typ hat B. Array, Regular, Date, Object, da sie alle ein Objekt zurückgeben, aber die Funktion bestimmt werden kann. Wenn typeof in Safari und Chrome verwendet wird, wird „function“ falsch zurückgegeben, andere Browser jedoch Rückgabeobjekt.
2. Bei der Beurteilung von Null wird ein Objekt zurückgegeben. Bei der Beurteilung von NaN kann die Rückgabe von number(2) verwendet werden, um zu erkennen, ob die Variable von einem bestimmten Typ ist , und gibt einen booleschen Wert zurück und kann bestimmen, ob diese Variable eine Instanz einer bestimmten Funktion ist. Was sie erkennt, ist der Prototyp des Objekts
let num = 1 num instanceof Number // false num = new Number(1) num instanceof Number // true
Offensichtlich sind beide num und beide sind 1. Nur weil die erste nicht ist ein Objekt und ist ein Basistyp, er gibt direkt false zurück, und der zweite ist in ein Objekt gekapselt, also true.
Diesem Problem sollte hier strikt Aufmerksamkeit geschenkt werden. Wenn das __proto__ des Erkennungsziels mit dem Prototyp des Konstruktors übereinstimmt, wird dies nicht streng sein als:
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
: Verwenden Sie die Methode Object.prototype.toString.call(value), um das Objekt aufzurufen und den Konstruktornamen des Objekts abzurufen. Es kann das rahmenübergreifende Problem von „instanceof“ lösen. Der Nachteil besteht darin, dass es für benutzerdefinierte Typen nur [Objektobjekt] zurückgibt
Dritte Frage: Bereichssicherer Konstruktor – wenn wir einen neuen Konstruktor erstellen, können wir eine Instanz erhalten. Was passiert, wenn wir vergessen, einen neuen zu schreiben?Zum Beispiel
// [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__; //没找到继续向上一层原型链查找 } }
Was wird damit passieren? , wie man es löst
Direkt verwenden, es wird dem globalen Objektfenster zugeordnet. Die Lösung kann sein: Stellen Sie zunächst sicher, dass dieses Objekt eine Instanz des richtigen Typs ist. Wenn nicht, wird eine neue Instanz erstellt und zurückgegeben. Bitte schauen Sie sich das Beispiel unten an
function Person(){ this.name = "小红" } p = Person();
Vierte Frage: Lassen Sie uns über die Lazy-Loading-Funktion sprechen
function Person(){ if(this instanceof Person){ this.name = "小红" }else{ return new Person() } } p = Person();
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; } }
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); }
Die in diesem Beispiel verwendete Technik besteht darin, über verschiedene Zweige eine anonyme selbstausführende Funktion zu erstellen, um zu bestimmen, welche Funktion verwendet werden soll. Die eigentliche Logik ist dieselbe, der Unterschied besteht in der Verwendung von Funktionsausdrücken (mithilfe von var Definieren Sie die Funktion). ) und fügen Sie eine anonyme Funktion hinzu. Darüber hinaus gibt jeder Zweig eine korrekte Funktion zurück und weist diese sofort der Variablen addEvent zu.
惰性载入函数的优点只执行一次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刷新一次,多次绘制并不会在屏幕上体现出来。
白屏时间: 白屏时间指的是浏览器开始显示内容的时间。因此我们只需要知道是浏览器开始显示内容的时间点,即页面白屏结束时间点即可获取到页面的白屏时间。
计算白屏时间 因此,我们通常认为浏览器开始渲染 标签或者解析完 标签的时刻就是页面白屏结束的时间点。
因此白屏时间则可以这样计算出:
可使用 Performance API 时
:
白屏时间 = firstPaint - performance.timing.navigationStart;
不可使用 Performance API 时
:
白屏时间 = firstPaint - pageStartTime; //虽然我们知道这并不准确,毕竟DNS解析,tcp三次握手等都没计算入内。
首屏时间: 首屏时间是指用户打开网站开始,到浏览器首屏内容渲染完成的时间。对于用户体验来说,首屏时间是用户对一个网站的重要体验因素。通常一个网站,如果首屏时间在5秒以内是比较优秀的,10秒以内是可以接受的,10秒以上就不可容忍了。超过10秒的首屏时间用户会选择刷新页面或立刻离开。
通常计算首屏的方法有
首屏模块标签标记法
统计首屏内加载最慢的图片的时间
自定义首屏内容计算法
1、首屏模块标签标记法
首屏模块标签标记法,通常适用于首屏内容不需要通过拉取数据才能生存以及页面不考虑图片等资源加载的情况。我们会在 HTML 文档中对应首屏内容的标签结束位置,使用内联的 JavaScript 代码记录当前时间戳。如下所示:
此时首屏时间等于 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; ifirstScreenHeight) { 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文件内部的处理逻辑,处理完毕后,再发送消息回到当前页面,纯异步方式,不影响当前主页面渲染。
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视频教程)
Das obige ist der detaillierte Inhalt von[Fortgeschrittene Fähigkeiten] Neun Front-End-Interviewfragen, die Ihnen helfen, Ihr Wissen zu festigen!. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!