ホームページ >ウェブフロントエンド >jsチュートリアル >[高度なスキル] 知識を定着させるのに役立つ 9 つのフロントエンド面接の質問!
この記事では、高度なフロントエンド スキルを共有し、フロントエンドの面接での 9 つの質問を整理して要約し、知識ポイントを統合するのに役立ちます。
これら 2 つの方法は両方とも可能です変数の型を決定するために使用されます。
違い: 前者は変数の型を決定し、後者は変数が特定の型であるかどうかを決定し、戻り値を決定します。 value はブール値です
(1) typeof
Defects:
1. 変数の特定のデータ型を決定できません。オブジェクトが返されるため、配列、正規、日付、オブジェクトのいずれかが返されますが、関数の判定は可能です 検出対象が正規表現の場合、SafariやChromeでtypeofを使用すると関数が誤って返され、その他のブラウザでは関数が返されます。 return object.
2. 判定 null が返された場合はオブジェクトが返される これは js の欠陥です NaN が判定された場合は number
が返されます (2)instanceof は以下のようになります変数が特定の型であるかどうかを検出するために使用され、ブール値を返します。また、この変数が特定の関数のインスタンスであるかどうかを判断できます。検出されるのはオブジェクトのプロトタイプです。
let num = 1 num instanceof Number // false num = new Number(1) num instanceof Number // true
は最初のものはオブジェクトではなく基本型であるため、明らかに num であり、両方とも 1 であるため、直接 false を返し、2 番目のものはオブジェクトにカプセル化されているため、true になります。
ここではこの問題に細心の注意を払う必要があります。検出対象の __proto__ がコンストラクターのプロトタイプと同じであれば true を返すという意見もあります。これは厳密ではありません。検出は
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 に配列を定義してから、instanceof を使用します。フレーム b. で判定すると、配列のプロトタイプ チェーン上で b フレームの配列を見つけることができず、その配列は配列ではないと判定されます。
解決策: Object.prototype.toString.call(value) メソッドを使用してオブジェクトを呼び出し、オブジェクトのコンストラクター名を取得します。これは、instanceof のクロスフレームワークの問題を解決できますが、欠点は、ユーザー定義型の場合、[object Object]
// [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 メソッドがサポートされているかどうかを確認します。サポートされていない場合は、attachEvent メソッドがサポートされているかどうかを確認します。まだサポートされていない場合は、DOM レベル 0 メソッドを使用してイベントを追加します。 addEvent() の呼び出し中に、このプロセスは毎回完了する必要があります。実際、ブラウザがいずれかのメソッドをサポートしている場合、常にそれをサポートしており、他のブランチを検出する必要はありません。つまり、if ステートメントを毎回実行する必要がなく、コードの実行が高速になります。 。この解決策は遅延読み込みと呼ばれます。いわゆる遅延読み込みとは、関数の if 分岐が 1 回だけ実行され、後で関数が呼び出されたときに、サポートされている分岐コードに直接入力されることを意味します。遅延読み込みを実装するには 2 つの方法があり、1 つは、初めて関数が呼び出されたときに関数自体を 2 回処理し、分岐条件を満たす関数として上書きされ、元の関数は必要ありません。実行ブランチを渡した後、次の方法で遅延読み込みを使用して 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 ステートメントを実行する必要はありません。
遅延読み込みを実装する 2 番目の方法は、関数の宣言時に適切な関数を指定することです。この方法では、関数が初めて呼び出されるときにパフォーマンスが低下することはありませんが、コードがロードされるときはわずかなパフォーマンスが低下するだけです。以下は、この考え方に従って 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 中国語 Web サイトの他の関連記事を参照してください。