常规题目
1.渐进增强和优雅降级
优雅降级:一开始就构建整个网站的完整功能,然后针对低版本浏览器进行兼容。
渐进增强:则是从浏览器支持的基本功能开始,然后再针对各浏览器追加功能。
都关注于同一网站在不同设备里不同浏览器下的表现程度。
区别:
“优雅降级”观点认为应该针对那些最高级、最完善的浏览器来设计网站. 而将那些被认为“过时”或有功能缺失的浏览器下的测试工作安排在开发周期的最后阶段,并把测试对象限定为主流浏览器(如 IE、Mozilla 等)的前一个版本。
“渐进增强”观点则认为应关注于内容本身, 首先为所有设备准备好清晰且语义化的html及完整内容, 然后再以无侵入的方法向页面增加无害于基础浏览器的额外样式和功能。
打个不太恰当的比方,就是要先解决广大劳苦大众的温饱问题(即“渐进”),再这个基础上尽量的往小康方向发展(谓“增强”)。
渲染机制
浏览器怎么渲染页面
什么是DOCTYPE及作用
DTD:HTML或XML是不同类型的文档。DTD主要用于定义(X)HTML或XML的文档类型,浏览器根据DTD选择什么JS引擎来对文档解析,渲染。
DOCTYPE是告诉浏览器当前的文档是包含哪个DTD,也就是哪个文档类型。
常见的DOCTYPE有哪些?
HTML4.01分严格模式和传统宽松模式,主要区别在于是否包含弃用的元素。
浏览器是怎么渲染的
在浏览器的地址栏输入一个URL,描述之后经过哪些环节,页面才能渲染完毕。
浏览器向DNS服务器查找输入URL对应的IP地址。
DNS服务器返回网站的IP地址。
浏览器根据IP地址与目标web服务器在80端口上建立TCP连接
浏览器获取请求页面的html代码。
浏览器在显示窗口内渲染HTML。
窗口关闭时,浏览器终止与服务器的连接。
DNS解析-->发送到服务器 -->响应到客户端-->开始渲染
DNS解析:通过网站的域名找到对应的IP地址的过程。我们输入的网址(域名)是IP地址的一个别名, 在一个DNS内,一个域名对应一个IP地址。
浏览器渲染涉及HTML、CSS、JS,三个都会影响页面的最后呈现形式。
HTML-->DOM树,CSS-->CSS树,两个树一整合会形成Render Tree,Render Tree只告诉浏览器渲染的树的结构,但不包含HTML元素的具体内容和位置,通过Layout可以精确的计算要显示的DOM真正要显示的位置、宽、高、颜色等。最后都会在Render Tree呈现出来,然后浏览器通过JUI开始画图,浏览器可以看到最终效果。
重排Reflow
重绘Repaint
常见问题:如何避免最小程度的Repaint?
Repaint是无法避免的
布局Layout
浏览器的布局方式
JS运行机制
JS引擎
JS和浏览器如何进行交互
如何理解JS的单线程
题目1:
控制台的输出结果是?1 2 3
JS是单线程的,就是在同一时间只能做一件事,那么现在就有一个问题,既然是单线程的,为什么会有异步呢???
什么是任务队列
执行的优先顺序:同步任务执行完成后,才会响应异步任务。
同步任务和异步任务
同步任务,比如console.log()。
异步任务,比如setTimeout。
任务队列
JS是单线程的,从上到下执行,异步任务要挂起,先不执行,待同步任务全部执行完毕,才会响应异步任务。
题目2:
控制台的输出结果是?A
控制台的输出结果是?依旧是A
while是同步任务
题目3:
控制台的输出过程是怎样的?4 4 4 4
异步任务的推入任务队列的时间和执行时间。
同步任务执行完毕,会立即将异步任务推入队列中执行吗?不会,浏览器有一个time模块,主要就是用来处理setTimeout和setInterval的,到它们设定的时间才会把异步任务推入任务队列。
for循环是一个同步任务,遇到setTimeout,定时器会记住这个语句,但并没有去执行,然后i加1了,然后又遇到setTimeout,还是没有执行,i又加1了,4个for循环很快就会执行完毕,这时异步队列中并没有异步任务,只有当setTimeout到时间了才会推入任务队列,事件循环。
什么是Event Loop(事件循环)
比如这个例子,JS引擎遇到同步任务,首先会把console.log(1)和console.log(2)放入运行栈中,遇到setTimeout,timer模块拿走setTimeout先放着。等待运行栈中的同步任务执行完毕后,timer模块开始处理setTimeout,在时间到了之后,会将setTimeout推入异步任务队列,等待执行。JS引擎发现运行栈中没有任务执行,就拿过来异步队列中setTimeout执行,这时setTimeout里边的函数体变成了运行栈中的同步任务去执行。执行完毕如果还有异步任务,就又会推入运行栈中执行,这样一个循环的过程就是Event Loop。
哪些语句会执行异步任务
哪些语句会执行异步任务,这些任务放入异步任务队列的时机?
- setTimeout和setInterval
时机:当运行栈中的同步任务执行完毕,timer模块开始处理setTimeout,在设定的时间到了之后,会将推入异步任务队列。 - DOM事件
比如我们用AddEventListner()在按钮上注册了一个事件,浏览器的某个模块接收后先放着。当事件被触发时,浏览器的某个模块就开始把我们注册的事件的函数体扔到异步队列当中,如果执行栈中没有正在执行的任务,就会到异步队列中拿过来执行。
这就可以解释,当JS在执行时,点按钮卡着了,因为执行栈中有任务在执行,根本就没有去读取任务队列。
- ES6中的Promise
页面性能
卡不卡,使用什么方法更流畅
提升页面性能的方法有哪些???
资源压缩合并,减少HTTP请求
开启压缩,把资源文件变小
非核心代码异步加载
延伸问题:
1.异步加载的方式有哪些?(不谈框架,只谈原理)
1)动态脚本加载
用JS去动态创建一个标签,然后加到body上,document.creatElement('script')
2)defer
在script标签上加入这个属性
3)async
2.这些方式有哪些区别?
defer是在HTMl解析完之后才会执行,如果是多个,按照加载的顺序依次执行
执行结果是:
async时脚本加载完后立即执行,如果是多个,执行顺序和加载顺序无关,哪个先回来哪个先执行。
执行结果是:
利用浏览器缓存(最重要的方式)
什么是缓存?缓存就是资源文件在浏览器中的副本,请求回来的资源保存在本地磁盘上,浏览器下次再请求时可以直接读本地磁盘上的。
延伸问题:
缓存的分类
1)强缓存
强制缓存,不咨询服务器,直接用。
HTTP响应头会携带两个:
Expirs(过期时间):服务器下发的绝对时间(可能跟客户端的时间不一致)
表示客户端当前的时间是不是这个绝对时间,在这个时间之前不会跟服务器通信,直接拿缓存来用。
Cache=Control:相对客户端时间(秒)
3600秒表示在拿到这个资源后,在3600秒之内不会再去请求服务器了,而是直接在浏览器拿缓存。
如果两个都下发了,以相对时间为准。下发哪个要看服务器的配置。
2)协商缓存
浏览器发现本地有缓存,就咨询一下服务器,用不用这个文件。
上次修改时间 上次之后这个资源有没有变化
强缓存失效后,浏览器要开始请求了,会携带这两个参数再去请求(上次修改时间是服务器上次下发的)。
延伸:跟浏览器相关的HTTP头有哪些?
缓存的原理
使用CDN
CDN加载资源很快,属于网络优化,比如,网站中使用了N个JS、图片、CSS,网络快速到达服务端把文件下载下来。尤其是页面第一次打开,并没有缓存,这时使用CDN效果是非常明显的。
预解析DNS
在浏览器输入一个URL后第一步要做的就是DNS解析,尤其页面中涉及多个域名时,DNS预解析提升性能效果非常明显。
页面中的a标签在一些高级浏览器里默认是打开预解析的,如果HTTP协议默认开头是HTTPS开头的,很多浏览器默认是关闭预解析的。
下面这句可以强制打开a标签的预解析:
错误监控
代码质量体系,通过代码提交时来控制
线上环境的错误收集,监听你开发的客户端在某个用户那出了什么错误。主要考察怎么实施监控。
如何检测JS错误?如何保证你的产品质量
错误的分类及捕获方式
即时运行错误:代码错误
1)try catch
2)window.onerror
error是DOM0事件,也可以用addEventLister()去注册DOM2级事件资源加载错误
1)object.onerror
比如图片、script标签,资源的onerror事件不会向上冒泡到window,所以window.onerror无法捕获资源加载错误
2)performance.getEntries()
返回一个数组,然后遍历数据。
performance.getEntries().forEach( item => { console.log(item.name) })
通过document.getElementsByTagName( 'img' )拿到所有的图片的集合,然后减去上边成功加载的集合,就得到错误的。
通过获取所有资源的加载时长,可以间接的拿到没有加载的资源错误。
3)事件捕获中拿到Error
在window上阻止了冒泡,但并没有阻止捕获。
比如:引入一个不存在的文件test.js
如果事件冒泡,将false改为true,
延伸:
跨域的js运行错误可以捕获吗,错误提示什么,应该怎么处理?
跨域错误只能捕获错误,无法拿到出错文件的具体信息
处理步骤:
1)在客户端,script便签增加crossorigin属性
2)在服务端的响应头,设置js资源的响应头Access-Control-Allow-Origin:*或者指定的域名
上报错误的基本原理
1)采用Ajax通信的方式上报
能上报,但所有的错误监控都不是采用这种方式。
2)利用Image对象上报
原型链继承
缺点:父类的构造函数如果有引用类型的属性时,继承后会变成子类的原型中的属性,会被子类的所有实例共享。
借助构造函数实现继承
缺点:只能继承父类构造函数中的属性,原型对象中的属性和方法无法继承。无法实现函数的复用。
组合方式:最常用
两种方式的组合:原型链实现对共享属性、方法的继承,借用构造函数实现对实例属性的继承。
当父类的构造函数如果有引用类型的属性时,
结果
缺点:
问题1:父类的构造函数执行了两次,这是不必要的。
怎么来优化这个问题1呢???
子类的原型和父类的原型使用一个对象。
问题2:
s3.constructor===Parent3,为什么不是Child3捏???
constructor可以来判断对象实例是不是该构造函数的直接实例,这个有待验证。
因为Child3.prototype被重写了,自身没有constructor属性
我们可以使用Object.creat()在子类原型和父类原型搭建一个中间原型,并且重新指定子类的constructor属性。
组合继承的完美写法:寄生组合式继承
DOM事件
DOM事件的级别
DOM3和DOM2的写法一样,只是增加了很多的事件类型
DOM事件模型
捕获
冒泡
DOM事件流
分为三个阶段,捕获阶段,目标阶段,冒泡阶段。
描述DOM事件捕获的流程
body节点如何获取:document.body
html节点如何获取:document.documentElement
event对象的常见属性和方法
event.type
event.target:当前被操作的元素
event.currentTarget:事件是注册在哪个元素上的。
event.preventDefault():阻止默认事件,比如a标签,阻止链接默认会跳转到href指定的URL
event.stopPropagation():阻止事件冒泡,比如父级元素和子元素上注册了不同的click事件,我们在单击子元素时,如果不阻止事件冒泡,父级元素的单击事件也会被触发。
event.stopImmediatePropagation():
DOM3新增,事件响应优先级。同一元素上依次注册了两个事件A、B,我们想让A执行后阻止B的执行。
自定义事件(模拟事件)----与高程上讲的不同
用法:
步骤:声明一个自定义事件,注册该事件,触发该事件。
算法类
题目2:统计数组中每个元素出现的次数
var names = ['A', 'B', 'T', 'B', 'A', 3, 1, 3];
方法一:使用for of
var nums = {}
for (let name of names) {
if (name in nums) {
nums[name]++;
} else {
nums[name] = 1;
}
}
console.log(nums);
方法二:使用reduce()
var nums = names.reduce((pre, cur) => {
if (cur in pre) {
pre[cur]++;
} else {
pre[cur] = 1;
}
return pre;
}, {})
console.log(nums);// {1: 1, 3: 2, A: 2, B: 2, T: 1}