因为公众号上最近没有那么多面试相关的话题了,但又不能拿之前的文章再说一遍车轱辘话,所以整理下面试相关的内容归档在这里,欢迎大家补充。
整体面试题目可分为两类,第一类是专业知识,我会把专业知识看的很重,如果满分100分,大约80分会在专业知识范畴里,剩下20分放在通用能力范畴里
专业知识决定了候选人能做哪些工作,不能做哪些工作,同时现在的基础也决定了未来的发展方向和未来可以做的工作。
JavaScript、Html、CSS都是必备的基础,但是其中JS还是最重的一块,MVVM也可以算是工具使用,但它在前端中的地位实在是太特别了,所以下面这些中JS和MVVM额外重要一些
函数、模块作用域,面向对象(类、继承)、Promise是我比较关注的点,因为它们让很多事情变得简单,你只要不把简单的事情搞得复杂就是高效率了,关注新语法和新api则是加分项
var a = 5
var b = 6
function test(){
var a = 7
b = 8
console.log({a,b})
}
test()
console.log({a,b})
这里test中的新定义的a就和外层的a没有关系了,而b则会被修改,其实var还存在声明提前的问题,大家可以改造下题目让它有更多的坑,个人觉得var不是未来所以变量声明提前的考察也不应该是重点
console.log(a,b)
var a = 5
console.log(a,b)
function b(){}
-
最早是没有const的,所以第一个问题是旧版本浏览器不支持const语法,如果你的用户会使用IE6、IE7那么使用const一定要编译(babel)
-
const是不可变的,和新语法中的let对应,试运行以下代码
var a=1
a=2
const a=1
a=2
let a=1
a=2
- 那么是不是var变量不修改赋值的话就和const一样呢?请尝试分别运行下面两段代码,let又会是怎样的结果?
(()=>{console.log(a);var a=1})()
(()=>{console.log(a);const a=1})()
const、let只在声明后才存在,而var会提前声明
- 重复声明
var a=1;var a=2;
const a=1;const a=2;
- 总结:新语法规避了之前var语法中提前声明和重复声明容易导致的问题,同时约束了变量let和不可变量const让程序更加容易理解,能用const的时候尽量使用const,其次是let,只有你的代码需要不经过编译直接跑在旧版本得了浏览器的时候才去用var
- 闭包是什么?闭包首先还是要先说函数作用域开始,简单说就是一个函数使用了外部作用域的变量,官方解释:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
function getCounter(){
let a = 1
return ()=>a++
}
const counter = getCounter()
console.log(counter()) //1
console.log(counter()) //2
console.log(counter()) //3
console.log(counter()) //4
- 什么场景下我们需要闭包?闭包可能会导致一些问题所以尽量不用闭包,但是它还是有存在的意义,那就是绝对的私有。我们知道即使是新语法中的类定义变量也是没有私有变量的(TypeScript是有的,也只是在编译到JS的过程中去检测),请尝试运行以下代码
class A {
constructor(){
this.a=5
}
getA(){
return this.a
}
}
const a = new A()
console.log(a.getA()) //5
console.log(a.a) //5
function A() {
var a=5
this.getA=function(){
return a
}
}
const a = new A()
console.log(a.getA()) //5
console.log(a.a) // undefined
- 闭包存在的问题?最大的问题在于内存管理,正常来讲你的函数运行完了除了返回值不销毁其他的函数内申请的东西都可以清理,现在用了闭包就不一定了,试着运行以下代码并关注你的内存占用
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing) // a reference to 'originalThing'
console.log("hi");
};
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log("message");
}
};
};
setInterval(replaceThing, 1000);
例子来自 https://blog.sessionstack.com/how-javascript-works-memory-management-how-to-handle-4-common-memory-leaks-3f28b94cfbec ,其中也详细讲解了内存泄露的原因
应该尽量使用箭头函数,除非你需要像jq、vue一类的为了方便使用this,因为
-
即使是绑定this方便使用,不论是bind还是apply都需要理解成本,没有文档的话别人看到你的代码理解都要比一般代码费事一些
-
使用function会导致this丢失,容易出bug
$('a').click(function(e){
e.preventDefault()
$(this).text('binded')
setTimeout(function(){
console.log(this) // this 变成了 window
})
})
- 有同学会反对说react项目里很多都用bind(this),我觉得多数还是没有箭头函数时代留下来的习惯,箭头函数还是舒服的多
<a onClic={this.handleClick.bind(this)}>点我点我</a>
<a onClic={()=>this.handleClick()}>点我点我</a>
总的来说箭头函数的主要意义还是优化可读性和规避bug
这道题已经过时了。估计大家写代码的时候应该都用的Node了吧(CommonJS),然后基于webpack或者browserfy一类的打包转换成浏览器端代码,当然也应该有些历史遗留的requireJS/seaJS项目也绝对是少数,趋势上还是Node的,只有Node才能ssr。
还是想了解具体的异的同直接百度
我想从字面的意思来讲就是模块化了,我就稍微扯远一点
跨项目共享代码
- 暴露接口,比如http服务,暴露一个网址
项目内共享代码
这个题目考察的不仅仅是继承的实现方式,而核心要考的是面向对象(OOP),要说面向对象就要提到三大特性:继承、封装、多态。如果JS还停留在JQ的年代,仅仅是为了做一些点击事件的绑定,做一些ajax请求,那是用不到了解面向对象的,但现在年代不同了,页面可以做到和一个客户端应用一样复杂(比如说Pinterest),除了业务的逻辑还包含了数据存储、按需加载、增量更新等,算起来比客户端可能还复杂,所以拿JS当脚本语言去做网页就直接沦为二流三流选手了
继承可能是减少重复代码最有效的办法了,所以才需要考察候选人能不能实现它,如果不能就可以直接归档到二流三流的池子了
- 最好的方式就是新语法中的class了,既干净,有表现出了解新语法,一般而言对编译的过程也有所了解
class B extends A {}
- 其次的是最原始的prototype继承方法,但是要想覆盖方法的话要小心prototype被修改会影响到父类,会这种方法至少是能实现继承的
function B(){
A.call(this)
}
B.prototype = A.prototype
- 更多的方法,其他的方法大多是通过一个函数,用类似2中的逻辑来处理继承,根据具体实现的不同可能性就变多了,性能上,结果上都有差异,甚至可以实现多重继承,具体大家可以去百度谷歌
前面已经提到过闭包,这里重贴一下代码
class A {
constructor(){
this.a=5
}
getA(){
return this.a
}
}
const a = new A()
console.log(a.getA()) //5
console.log(a.a) //5
function A() {
var a=5
this.getA=function(){
return a
}
}
const a = new A()
console.log(a.getA()) //5
console.log(a.a) // undefined
- 我们为什么需要Promise?首先是避免回调地狱的问题
const mongoose = require('mongoose');
const Store = mongoose.model('Store');
const StoreB = mongoose.model('StoreB');
exports.createStore = (req, res) => {
const store = new Store(req.body)
storeB.findOne({
id: req.params._id
}, function(err, storeB) {
if (!err) {
store.storeb_id = storeB._id
store.save(function(err, store) {
if (!err) {
// 成功
res.redirect('/successPath', store);
} else {
res.redirect('/someOtherPathB', errors: err)
}
} else {
res.redirect('/someOtherPathA', errors: err)
}
}
});
};
看似正常的代码,唯一的不正常就是难以阅读和难以维护
const mongoose = require('mongoose');
const Store = mongoose.model('Store');
const StoreB = mongoose.model('StoreB');
// 使用es6 promise
mongoose.Promise = global.Promise;
exports.createStore = (req, res, next) => {
const store = new Store(req.body);
const itemB = StoreB
.findOne({ id: req.params._id})
.catch(next);
itemB
.then(record => {
store.storeb_id = record._id;
return store.save()
.then(stores => Store.find())
.then(stores => res.render('index', {stores: stores}))
})
// 错误处理
.catch(next);
}
使用promise之后逻辑的层次变浅,好理解一些了,async/await在逻辑上还是promise的逻辑,但是更简洁也更易于阅读
const mongoose = require('mongoose');
const Store = mongoose.model('Store');
const StoreB = mongoose.model('StoreB');
// 使用es6 promise
mongoose.Promise = global.Promise
exports.createStore = async (req, res) => {
const store = new Store(req.body);
// 直到await执行完才会继续
const record_we_want_to_associate = await StoreB.findOne({_id: req.params.id});
store.storeb_id = record_we_want_to_associate._id
// 直到await执行完才会继续
await store.save();
res.render('index', { stores: stores });
};
以上例子来自 https://medium.com/@ThatGuyTinus/callbacks-vs-promises-vs-async-await-f65ed7c2b9b4 里面还有更多的解释说明
function delay(miliSeconds){
// your code
}
delay(5000).then(()=>console.log('已经过了5秒钟'))
这个题目考察的是编写代码的熟练度,这么简单的逻辑写的慢了都要扣分的,为什么这么解释呢?
- 没有自己定制过Promise,等价于没有写过复杂的异步逻辑
- 如果有写过复杂的异步逻辑,都使用回调来实现,那代码质量是不合格的,学习能力也是不合格的
- 这个题目有多简单,可以对比下官方Promise示例 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
function delay(miliSeconds){
return new Promise((reject,resolve)=>{
setTimeout(resolve,miliSeconds)
})
}
这个题目考察的是异步逻辑的使用经验,知道Promise.all
和Promise.race
可能是对新知识保持了学习,也可能是工作中有用到复杂一点的异步逻辑,如果不知道也可以用笨的方法实现就是另一个层次,至少还有解决问题的能力,笨的办法也想不出来就没有分了
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
笨的方法的另一层意义在于理解Promise.all
和Promise.race
的底层实现,建议大家尝试自己实现一下这两个方法
单说这个题目有两个考察点,主要的考察点是事件绑定,其次是移动端开发经验。
- 事件绑定
target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);
target.addEventListener(type, listener[, useCapture, wantsUntrusted ]); // Gecko/Mozilla only
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
这里要注意的就是useCapture
了,如果父元素和子元素同时绑定了事件,useCapture
决定了它们触发的顺序,useCapture
默认为false
为冒泡是从里向外,如果设置为true
则表示捕获从document
开始从外到里
事件绑定只是DOM接口的一个细节动作了,更多DOM操作也可以做许多题目了
- 移动端开发经验
function startup() {
var el = document.getElementsByTagName("canvas")[0];
el.addEventListener("touchstart", handleStart, false);
el.addEventListener("touchend", handleEnd, false);
el.addEventListener("touchmove", handleMove, false);
log("initialized.")
}
https://developer.mozilla.org/zh-CN/docs/Web/API/Touch_events
在pc中大部分都是click事件,移动版则都是touch,你要说都是用别人库里的rotate
、zoom
但是不知道原生的touch
那也只是比完全不会强了一点点
接下来就要考察怎么用touch
去实现一个zoom
了,首先还是你是否理解别人封装的手势库的实现方式,其次就是对touch事件的了解和解决问题的思路问题了,不到50行代码
https://codepen.io/postor/pen/GYrYQM
$(document).ready(() => {
const scaleMin = 0.1, scaleMax = 2.5
let zooming = false, distanceStart = 0, scale = 1, scaleStart = 1, logCounter = 0
let $el = $('.box'), $logs = $('.logs')
$(window)
.on("touchstart touchend touchcancle", function (e) {
if (e.originalEvent.touches.length == 2) {
zooming = true
distanceStart = getDistance(e.originalEvent.touches);
scaleStart = scale
return
}
zooming = false
})
window.addEventListener('touchmove', ev => {
ev.preventDefault()
ev.stopImmediatePropagation()
if (!zooming) return
const newDistance = getDistance(ev.touches);
scale = scaleStart * newDistance / distanceStart
if (scale < scaleMin) {
scale = scaleMin
}
if (scale > scaleMax) {
scale = scaleMax
}
$logs.prepend(`[${logCounter++}]touchmove|scale=${scale}<br />`)
$el.css('transform', `scale(${scale})`)
}, { passive: false })
})
function getDistance(touches) {
const [t1, t2] = touches
const [p1, p2] = [t1, t2].map(p => {
return {
x: p.screenX,
y: p.screenY
};
});
return Math.hypot(p1.x - p2.x, p1.y - p2.y);
}
这个题目考察的是对浏览器新api的跟进和了解了,其实也并不算新,早就有很多项目在用localStorage了,比如多国语言就可以用localStorage存下来所有已经用到的翻译,IndexDB则被用来做前端日志(用于出错诊断统计等),WebSocket实时双向通信、WebGL图形图像3D等,WebRTC网页流媒体,WebAssembly则可以用其他语言写页面逻辑
当然这些只是举例子,有个什么理论来着是未知就是知道东西的周长,反正是知道的东西越多才能发现有更多不知道的东西
以下按常用程度排序
- map
map的使用场景主要是数组转化,举个例子,我有几个用户放在数组里,但我现在只想要他们的名字逗号分隔的字符串
const arr = [{id:1,name:'josh'},{id:2,name:'postor'}]
console.log(arr.map(x=>x.name).join(','))
- reduce
当我们需要遍历并计算出一个结果的时候一般都可以用reduce,举个例子,获取数组中的最大值
const arr = [1,5,2,4,3]
console.log(arr.reduce((result,next)=>result>next?result:next))
- filter
过滤器,只保留返回是true的元素
const arr = [1,5,2,4,3]
console.log(arr.filter(x=>x>2))
- includes
包含,以前都要用indexOf的
const arr = [1,5,2,4,3]
console.log(arr.includes(2))
- some
有一个返回true就可以
const arr = [1,5,2,4,3]
console.log(arr.some(x=>x>2))
- every
全返回true才可以
const arr = [1,5,2,4,3]
console.log(arr.every(x=>x>2))
简单地说就是直接的绑定不能绑定到动态产生的元素上,基于事件冒泡的原理可以使用一个上层元素来代理事件,这个功能在jQuery.on中的实现可能是大家最常用的
$( "#dataTable tbody" ).on( "click", "tr", function() {
console.log( $( this ).text() );
});
虽然同样可以解决动态元素事件绑定的问题,这里还是推荐使用MVVM的事件机制来解决
为什么默认的事件绑定无法解决动态元素事件绑定的问题?这里举例子就沿用前面代码的逻辑,当tr被点击的时候显示其中的文本
$( "#dataTable tbody tr" ).on( "click", function() {
console.log( $( this ).text() );
});
然后假如我需要ajax一段数据,然后补充到table中
$( "#dataTable tbody" ).append('<tr><td>click on me and nothing will happen!</td></tr>');
现在你去点击它,控制台不会有任何输出,但如果你使用的是事件代理的方式,就会有输出了,说回来事件代理也是比较陈旧的技术,在复杂的项目里代理会影响到所有下级内容会变得很难控制,MVVM解决的就更优雅了,MVVM还是推荐react,至少也要会用vue
this在浏览器中默认是window对象,在对象中,在类中,在apply、call中、在箭头函数中可能都是不同的东西,这个问题很大程度上是OOP(面向对象编程)的范畴,深入理解可以参考我之前的翻译 《在JavaScript中深入探讨this:为什么编写好的代码至关重要》
这个问题还是OOP范畴的,你是否知道JavaScript继承机制的基础呢?《javascript 类的封装和类的继承及原型和原型链详解》
为什么不能呢,因为运行都不会通过,不要被可能存在的全局污染忽悠了,运行出错都没机会污染,出错的原因在于引擎认为这是两句话
function foo(){};();
所以小括号里是期待有个值的,或者是值的表达式,结果有没有找到值就报错了
function foo(){}();
// Uncaught SyntaxError: Unexpected token )
要使其变为IIFE最简单就是加小括号
(function foo(){})();
或者
(function foo(){}());
或者没有foo
(function (){})();
(function (){}());
或者箭头函数也可以
(()=>{})();
为什么有foo也可以?不会造成全局污染么?
不会,因为小括号限制了它的作用域
两种小括号的括法那种更好一些?是函数括起来还是整个括起来?
(function (){})();
(function (){}());
都一样,看自己喜欢的风格,个人倾向于第一种
考点在于IIFE是一种模式,而且是经常用得到的,这个就参考MDN官方吧《立即执行函数表达式》
我个人觉得null
和undefined
的区别并没有什么意义
let a = null;
let b;
console.log(typeof a);
// object
console.log(typeof b);
// undefined
类型不同,然而并没有什么意义,我为什么要去比较两个对象的类型呢?
null !== undefined
// true
!==
和===
是先比较类型的,所以他们肯定不相等,按照推荐的编程规范,只有相同类型的变量才用来作比较
let logHi = (str = 'hi') => {
console.log(str);
}
logHi(undefined);
// hi
logHi(null);
// null
函数默认值是判断undefined
来决定赋值的,实际用的一般也是logHi()
,传入null
也是相当的脑抽了
所以undefined
和未定义的比较才是有意义的,比如在Node.js环境下运行以下代码
var b
console.log(b)
//undefined
console.log(window)
//ReferenceError: window is not defined
要知道,Node.js的服务异常如果没有处理的话服务就挂掉了
简单的只要知道map
是为了构造一个新数组,而forEach
只是单纯的循环,对应的还有for
、for...in
等,了解更多可以参考 《JavaScript完全手册》
首先要说尽量不要用,因为这会影响this的绑定,导致代码难以阅读
经常为了使用便利恰好需要绑定this,尤其是在造轮子的场景中,比如jquery、vue都有使用一些绑定
了解更多请参考《在JavaScript中深入探讨this:为什么编写好的代码至关重要》
这个是用来做浏览器兼容性的,直接检测最靠谱,UA判断最不靠谱,比如某奇葩国产完全照搬chrome的UA
属性检测(feature detection) 直接判断是否存在
if (window.XMLHttpRequest) {
new XMLHttpRequest();
}
属性推理(feature inference) 基于浏览器的厂商实现,特性都是成组的支持,特定的检测几乎可以断定一些其他特性是否支持
if (document.getElementsByTagName) {
element = document.getElementById(id);
}
使用UA字符串 直接根据UA判断浏览器版本,基于版本推理特性支持
if (navigator.userAgent.indexOf("MSIE 7") > -1){
//do something
}
AJAX不是JavaScript的规范,它只是一个哥们“发明”的缩写:Asynchronous JavaScript and XML,意思就是用JavaScript执行异步网络请求。
如果你想把标准写法和IE写法混在一起,可以这么写:
var request;
if (window.XMLHttpRequest) {
request = new XMLHttpRequest();
} else {
request = new ActiveXObject('Microsoft.XMLHTTP');
}
XMLHttpRequest对象的open()方法有3个参数,第一个参数指定是GET还是POST,第二个参数指定URL地址,第三个参数指定是否使用异步,默认是true,所以不用写。
注意,千万不要把第三个参数指定为false,否则浏览器将停止响应,直到AJAX请求完成。如果这个请求耗时10秒,那么10秒内你会发现浏览器处于“假死”状态。
最后调用send()方法才真正发送请求。GET请求不需要参数,POST请求需要把body部分以字符串或者FormData对象传进去。
从Ajax、JSONP 到 SSE、Websocket http://www.52im.net/thread-1038-1-1.html
Ajax虽然是最古老的免刷新交互但它现在也依然是主流;一个阻塞可能是考点,IO、网络的逻辑都可能阻塞,避免阻塞才能写出高性能高质量的程序;一个同源策略可能成为考点,这是浏览器安全机制的一部分,从安全和浏览器熟悉的角度都应该掌握;一个技术选型可能是考点,比如简单ajax还是long polling,还是JSONP、SSE、WebSocket他们各有各自的特点,各自适合的场景
例如jQuery.template、lodash.template
在MVVM之前,jQuery直接操作DOM之后,有这样一个模板库活跃的阶段,虽然解决了构造html时候拼接字符串的问题,事件绑定也可以使用事件代理解决,但比起MVVM还是个很low的方案
如果单说他们俩的区别非常容易了,三等号需要提前比较一下类型,后面的比较和双等号一样的,用代码表示下
//三等号比较
function triple(a,b){
if(typeof a != typeof b){
return false
}
return double(a,b)
}
//双等号比较
function double(a,b){
if(typeof a == typeof b){
//返回值/引用比较
...
}
//如果不同类型则尝试转换为同类型比较
...
}
JS中有个特例就是NaN
,它怎么比都不等于自己
NaN===NaN
//false
NaN==NaN
//false
其他的问题主要出在双等号比较上,举个WTFJS的例子吧
[] == '' // -> true
[] == 0 // -> true
[''] == '' // -> true
[0] == 0 // -> true
[0] == '' // -> false
[''] == 0 // -> true
[null] == '' // true
[null] == 0 // true
[undefined] == '' // true
[undefined] == 0 // true
[[]] == 0 // true
[[]] == '' // true
[[[[[[]]]]]] == '' // true
[[[[[[]]]]]] == 0 // true
[[[[[[ null ]]]]]] == 0 // true
[[[[[[ null ]]]]]] == '' // true
[[[[[[ undefined ]]]]]] == 0 // true
[[[[[[ undefined ]]]]]] == '' // true
我并不要求大家深究到多深,最主要的问题还是比较的时候应当尽量使用三等号,因为双等号的自动转换类型经常有奇特的效果等着你
这个考点是实际应用经验,同源策略主要是对资源使用的限制,实际的网站应用还是很有可能使用一个其他域名下的接口数据的,代理就不算是和JavaScript相关的解决方案了(即使你强词夺理说用Node做代理),其中比较流行的就是JSONP了,其实JSONP就是利用JavaScript来绕过同源策略的一个方案
随便找的JSONP原理,百度搜应该有许多的 https://blog.csdn.net/hansexploration/article/details/80314948
另一个是js降域的,同样是绕过同源策略,这个方案只能用于同一个根域名的情况
同样百度找的降域解释 https://blog.csdn.net/wu_xiaozhou/article/details/52901374
最后我想提一下CSP,同源策略(CROS)和内容安全策略(CSP)可以简单理解一个控制访问,一个控制使用,事实上也是很少有CSP的题目,但我还是很看好CSP的,也希望这是你能给面试官一个超出预期惊喜的点
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP
duplicate([1,2,3,4,5]); // [1,2,3,4,5,1,2,3,4,5]
这个题的实现方法有很多种,首先推荐spread语法
function duplicate(arr){
return [...arr,...arr]
}
其次concat也行
function duplicate(arr){
return arr.concat(arr)
}
其他的不推荐,也不很需要知道
//copyWithin会改变原数组,在需要immutable的场景不要用
function duplicate(arr){
const len = arr.length
arr.length=len*2
arr.copyWithin(len,0,len)
return arr
}
//push、unshift相当于旧语法中的concat,会改变原数组,在需要immutable的场景不要用
function duplicate(arr){
Array.prototype.unshift.apply(arr,arr)
return arr
}
//flat实验中暂时浏览器不支持,还有个flatMap
function duplicate(arr){
return [arr,arr].flat()
}
//reduce、forEach的实现方法略过,还有splice的
条件(三元)运算符是 JavaScript 仅有的使用三个操作数的运算符。本运算符经常作为if语句的简短形式来使用。
condition ? expr1 : expr2
condition
计算结果为true或false的表达式。
expr1
, expr2
值可以是任何类型的表达式。
通过将三元表达式使用额外的空格,拆分写在多行,使得三元运算符能干净利落地替代一个很长的if/else 表达式。在语法上,它使用了一种更明快的方式来表达了相同的逻辑:
var func1 = function( .. ) {
if (condition1) { return value1 }
else if (condition2) { return value2 }
else if (condition3) { return value3 }
else { return value4 }
}
var func2 = function( .. ) {
return condition1 ? value1
: condition2 ? value2
: condition3 ? value3
: value4
}
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Conditional_Operator
ECMAScript 5的严格模式是采用具有限制性JavaScript变体的一种方式,从而使代码显示地 脱离“马虎模式/稀松模式/懒散模式“(sloppy)模式。
严格模式通过抛出错误来消除了一些原有静默错误。 严格模式修复了一些导致 JavaScript引擎难以执行优化的缺陷:有时候,相同的代码,严格模式可以比非严格模式下运行得更快。 严格模式禁用了在ECMAScript的未来版本中可能会定义的一些语法。
严格模式是推荐的,但是对旧的代码开启严格模式可能会导致系统瘫痪,曾经亚马逊也中过招
https://bugzilla.mozilla.org/show_bug.cgi?id=627531
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode
不用解释吧
- 使用全局变量容易导致bug,
//a.js
var x=5
$('#el').click(function(){
alert(x)
})
//b.js
for(x=0;x<10;x++){
console.log(x)
}
//当你点击el的时候期待的是5,实际会显示10,而且你根本找不到问题出在哪
- 有利于垃圾自动回收
//x不会回收,因为在window对象上
var x = new Array(100000).fill(0);
//x会自动回收
(()=>{
var x = new Array(100000).fill(0);
})()
简单地说就是同步函数会卡住无响应,而异步函数不会,深入一点说又不是这么简单,异步函数是Promise的简化版,Promise又是解决回调地狱问题的方案,就好比面向对象编程和面向函数编程对比一样,功能都是能做的,但整洁合理复用扩展这些东西在复杂项目中尤其重要,使用回调同样不卡,但是不如异步函数可读性好,复用性好; 扩展一点和题目没啥关系的,promise和异步可以不卡看起来好像是多线程,实际上并不是,只是合理利用单进程的资源而已,service worker可以做到真的多线程
参考链接:
他们的差异就是函数声明提前和变量声明提前之间的差异,直接用代码解释吧
//前面的代码
foo(){}
//后面的代码
等价于(函数声明提前)
var foo=function(){}
//前面的代码
//后面的代码
//前面的代码
var foo=function(){}
//后面的代码
等价于(变量声明提前)
var foo
//前面的代码
foo=function(){}
//后面的代码
var foo=function(){}
- class对遗留浏览器支持的问题,但目前来讲编译基本是前端必备,并不是大问题
- class必须使用new来构造实例,否则报错,构造函数不会,所以构造函数更容易出错误
其实class并没有和构造函数在本质上有什么不同,从底层实现上成员、静态等所有都还是原型链继承那一套东西,只是一些语法糖,写代码的时候更舒服了
要知道以前JS可不是老大哥的地位,最初的网页并没有什么脚本,关于HTML我更关注SEO,SEO和语义化是相辅相成的,这里所有标签的理解就很重要
- 最好当然是ssr+pushstate,又是单页应用,又可以SEO
- ssr比较费事,用puppeteer、phatomjs一类生成静态页,又是单页应用,又可以SEO
- puppeteer、phatomjs也太难了,以前可以使用谷歌支持的那一套hash路由规则,但现在已经没有用了
什么是服务端渲染 https://www.jianshu.com/p/4acde8b6e5e3
puppeteer服务端渲染 https://github.com/xiamx/ssr-proxy
谷歌基于hash的路由规则 https://developers.google.com/search/docs/ajax-crawling/docs/getting-started
CSS很大的一块在知识面,我很少考察浏览器兼容的问题,但是类似盒模型、布局、动画GPU加速等都是好题目,当然还有SASS、LESS这种工具CSS变量grid布局这种新功能OOCSS、BEM这一类的标准也都可以考察一下
MVVM为什么这重要,因为它解决了很多问题,比如JS中的DOM操作和事件绑定,比如css全局作用域干扰问题,总之很重要,对应的一些生态环境也应该划在MVVM范畴里,比如redux、ionic
[使用]假如需要做一个问卷页面,包含所有问题的字符串数组可以通过一个后台接口/questions获得,所有问题都是问答题,用户回答后将答案post到另一个后台接口/subimit,请实现该页面逻辑,仅实现功能即可,无须样式,尽量使用MVVM
#react
<Question title={'1+1='} answers={[0,1,2,3]} onChange={(val)=>console.log(`selected ${val}`)} />
#angular
<question [title]="q.title" [answers]="q.answers" [(selected)]="q.selected" ></question>
#vue
<question :title="q.title" :answers="q.answers" :selected.sync="q.selected" ></question>
mutable逻辑
function mutation(originalArray) {
// 直接修改原数组
originalArray[0] = "new value";
return originalArray;
}
var array = ["some value", "another value"];
alert("Return from mutation " + mutation(array));
alert("Array: " + array + " (original array has been altered).");
immutable逻辑
function immutable(originalArray) {
// 不修改原数组,
// 创建一个原数组的拷贝,并修改这个拷贝
// 这样避免了原数组的更改.
var newArray = [...originalArray];
newArray[0] = "new value";
return newArray;
}
var array = ["some value", "another value"];
alert("Return from immutable " + immutable(array));
alert("Array: " + array + " (original array stay unchanged).");
主流的store和mvvm都是推荐immutable的,因为这样就可以直接对比引用来发现变更了,速度非常快,如果mutable的代码就只能使用深对比来检查数据变化了
必须有webpack了,当然gulp、gurant也有他们的空间,文档工具(jsdoc)、lint工具(eslint)、测试工具(jest、selenium、puppeteer)也都是工具,还有TypeScript这种比较特别的,他们有的优化你的结果,有的优化你的生产过程,当然了都不会用也一样可以写代码
- chrome debugbar,功能非常强大,出错定位到行,能够断点可以使用代码
debugger
解决手工不好断点的地方 - 移动端调试工具,例如
vorlon.js
,这一类还有别的库,他们通过引入一个库来绑定到远端,通过他们的网页来远程调试 - 一些框架使用的工具,比如react开发者工具,redux开发者工具
- android你也可以通过USB调试的方式绑定到PC的chrome debugbar,类似的还有iphone safari
- Node.js、phonegap、ionic、elctron的调试绑定也是chrome debugbar
- 其他的,比如fiddler这种可以给手机做代理,甚至用插件替换一些内容创造环境或者验证修复,比如线上问题调试的时候就比较有用,k6或者ab这种模拟高并发环境重现服务端问题等
暂时想到这么多
这个话题和许多其范畴都有重叠,比如html不使用table布局、CSS的GPU加速、MVVM的虚拟DOM/shadowdom,但我觉得这个重叠区域不必较真到底归谁,只是从不同的维度来看同一个事情而已,比较专属的还是有的,比如资源加载流程、js并发限制、资源管道限制、webkit/v8的一些优化等
面试官可在回答过程中选择略过不关注的点和深挖关注的知识点
DOM层次结构完全构建后,DOMContentLoaded事件将立即触发,当所有image和subframe完成加载时,load事件将触发。
【DOMContentLoaded 来自MDN】
当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。另一个不同的事件 load 应该仅用于检测一个完全加载的页面。 在使用 DOMContentLoaded 更加合适的情况下使用 load 是一个令人难以置信的流行的错误,所以要谨慎。注意:DOMContentLoaded 事件必须等待其所属script之前的样式表加载解析完成才会触发。
DOMContentLoaded可以在大多数现代浏览器上运行,但IE支持不好。 有一些变通方法可以在旧版本的IE上模仿这个事件,比如在jQuery库上使用它们,它们附加了IE特定的onreadystatechange事件。
查看DOMContentLoaded支持情况 https://caniuse.com/domcontentloaded
【load 来自MDN】
当一个资源及其依赖资源已完成加载时,将触发load事件。
当初jQuery流行起来也有一部分原因是它使用了DOMContentLoaded,比其他框架使用load事件的效率要高一些
参考上一题
虽然网络安全大多是后端同学的事情,但信息安全的重要性是大家都懂的,尤其是当面试官本身不是前端专业的情况,在网站开发中后台开发做团队领导是绝大多数的情况,而他们对JS、浏览器之类了解较少更关注安全、网络协议、算法结构、周边能力等
有线无线、3g4g、TCP/IP、UDP、http、https、http2、websocket、webrtc,虽然很少有候选人能答几个,答总是会问这么一道题
简单的说不懂就容易觉得都是别人的事,最要命的是做Web前端没有美感
这个不解释了
通过面试过程的问答来判断
通过对问题的深挖来判断候选人是否有夸大或虚构