FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
363 stars 39 forks source link

一些特别棒的面试题[3] #141

Open FrankKai opened 5 years ago

FrankKai commented 5 years ago

现东家的面试题,是我最欣赏的一套面试题,在这里已经工作了接近1年时间,成长许多。

题目主要分为4个部分:

答案已验证准确性并更新。 我将按照“1 year before”和“today”的角度去进行解答,也是对自己成长的一个记录。

FrankKai commented 5 years ago

HTML

1.请描述cookies, sessionStorage和localStorage的区别

1 year before: cookies,需要与后端协作,一种认证方式,expires失效时间 sessionStorage,当前会话有效 localStorage,本地缓存,expires失效时间

today:

cookies

注意:这在vue单页应用的foo.vue和新tabbar.vue之间进行通信,使用vuex,vue-router,event-bus或者eventEmitter都是不适用的。因为在新的tab,根vue实例是完全两个新的实例,emitter也是完全两个新的emitter,无法通信。但是监听window的storage事件可以做到这种复杂情况的通信。

如何清空?

localStorage.clear();
sessionStorage.clear();

2.请解释<script><script async><script defer>区别

1 year before: 常见的同步;异步;延迟加载。

today:

拓展: 1.什么是DOMContentLoaded事件? DOMContentLoaded事件,会在初始化HTML document完成加载和解析触发,无需等待stylesheets,images,以及字frame结束加载。Load事件和DOMContentLoaded事件很类似,经常有人混淆2个事件。 2.什么是Load事件? load事件,会在完成一次完全加载之后再触发。 3.如何解决同步js阻塞DOM渲染的问题,最快速度渲染DOM? 异步化js优化stylesheets加载。 4.如何检查document是否完成加载? document.readyState

所以一次加载文档资源的状态有3个,loading``interactivecomplete

3.为什么通常推荐将CSS 放置在<head></head>之间,而将js<script></script>放置在</body>之前?

1 year before: 防止阻塞浏览器渲染,先执行CSSOM,再绘制DOM,再操作DOM。主线程单线程渲染UI。 today 从技术实现的角度讲,先加载stylesheets并且绘制,DOM渲染结束后再加载并执行可能涉及DOM操作的js文件,避免js的执行导致渲染阻塞。 从用户体验的角度讲,优先加载样式表减少用户面对空白网页的时间,提升用户体验。

FrankKai commented 5 years ago

CSS

1.CSS中类(classes)和ID的区别

1 year before ①ID优先级高 ②ID唯一标识,不能重复 today ①语法不同,#idname, .classname ②影响元素不同,#idname作用单个元素,.classname作用所有元素 ③选择器权重不同,#idname权值高于.classname

2.有哪些隐藏DOM的方法

1 year before

today [译]如何隐藏DOM元素?

3.请解释*{ box-sizing: border-box }的作用,并且说明使用它有什么好处?

1 year before 怪异盒模型。 计算方便,width包含了border,响应式百分比布局。 today box-sizing的值有2种,一个是content-box,一个是border-box,content-box仅仅包含content。 border-box的width会将content, padding和border包含在内,例如width:100%指的是包含了content,padding和border的宽度,布局时更好控制。

例如子元素继承了父元素的width:100%,此时设置了子元素padding,border,若子元素的box-sizing是content-box,会导致溢出,而border-box的话,width:100%会很舒服地包含了padding和border。

因为这样的应用场景很多,所以索性就为所有标签都设置成border-box,有特殊情况的再手动设置成content-box。

image

有一篇墙推IE怪异盒模型的文章:把所有元素的box-sizing都设置成border-box吧!

4.请问在确定样式的过程中优先级是如何决定的(请举例)?如何使用此系统?

1 year before

每个选择器都有权值,权重为和,#foo>.class::before 三者和为权重,权值id>标签>class>伪类。

today 优先级由高到低

一个很优秀的说明css选择器优先级的图:https://specifishity.com/ image

通用选择器(*),组合符(+,>,〜,'',||)和否定伪类(:not())对权重没有影响。

5.请问为何要使用translate()而非absolute positioning,或反之的理由?为什么?

1 year before absolute

减少了计算,translate()不影响其他元素,性能更好,GPU计算次数更少

today 在我的这篇博问中有答案:CSS3动画卡顿性能优化解决方案

translate()涉及到的是合成器线程,与主线程是相互独立的,所以会比较快。 而absolute positioning涉及到的是主线程,会导致主线程负责的布局重绘和js执行,所以会比较慢。

FrankKai commented 5 years ago

JS

1..call和.apply的区别是什么?

1 year before

today 关于这个问题曾经产出2篇博客: 从规范去看Function.prototype.apply到底是怎么工作的? 从规范去看Function.prototype.call到底是怎么工作的? call是索取,apply是付出。 从call和apply的字面意思就可以看出,call调用,apply应用,调用函数,应用参数。 call和apply的主要区别在于,call仅仅切换this上下文到其他类,从而调用自己不存在的方法;而apply主要是为了将其他类型的参数传递到自己内部,再调用自己的方法。

假设有foo,bar。 foo.call(bar) bar调用foo的方法,实例Object.toString([1,2,3])->"[object Array]",数组实例调用了Object类的toString方法。 foo.apply(null, bar) foo的方法应用bar的参数,Math.max.apply(null, [1,2,3])->3,Math的max方法应用了[1,2,3]中的每个参数。

2.请解释JSONP的工作原理,以及它为什么不是真正的Ajax?

1 year before JSONP 原理:异步插入一个<script></script>,会有XSS问题 原因:没有调用XMR对象 today 一种比较古老的不安全的跨域请求访问方式,没有调用XMR对象,服务器允许浏览器在query parameter中传递浏览器内定义的函数,而这个函数是有概率被XSS攻击改写的。

来自StackOverflow高票答案:https://stackoverflow.com/questions/2067472/what-is-jsonp-and-why-was-it-created

JSONP不是一个很复杂的问题。 假设我们在example.com域名下,此时我们想给example.net域名发送一个请求。为了请求成功,需要跨越域名边界,这对于浏览器来说是禁忌。

绕开这个限制的一种方式是<script>标签。当你使用script标签的时候,域名限制会被忽略,但是对结果不能做任何处理,脚本是被评估过的。

开始进入JSONP。当你发送一个请求到支持JSONP的服务器,你会传一些特殊的关于你的页面的数据给服务器。这种方式下,服务器会以你的浏览器处理起来方便的方式包装响应。

例如,服务器需要一个叫做callback的参数去开启JSONP功能。然后你的请求会想下面这样: http://www.example.net/sample.aspx?callback=mycallback

没有JSONP的情况下,这可以返回一些基本的JS对象,例如: {foo: 'bar'}

然而,在支持JSONP的情况下,服务器接收到callback参数,它包裹结果的方式是不一样的,会返回下面这样的数据: mycallback({foo: 'bar'});

就如你缩减,他会调用浏览器端的方法。所以,在你的页面上callback的定义如下:

mycallback = function(data){
  alert(data.foo);
};

现在的话,在脚本加载成功后,它会被评估,然后函数执行。cross-domain请求成功!

所以JSONP存在一个很严重的问题:你失去了对请求的控制。例如,无法得知错误代码的返回。可以使用定时器去监控请求,但是这不是很好。JSONRequest是一个非常好的跨域脚本执行的方式,安全,并且获得更多对请求的控制。

2015年,CORS是一个与JSONRequest可以抗衡的方案。JSONP在老式浏览器下仍旧有用,但是不安全。 CORS是做跨域请求访问的更安全、更高效的一种方式。

3.用过javascript模板系统吗?都使用过哪些库?

1 year before jade vue angular react { ... }或者{{ ... }}

today: 一些好用的模板引擎库

常用的还是前端框架自带的以及pug,由于我只对vue.js的比较熟悉所以就没有罗列react和angular的demo。

4.== 和 === 有什么不同?

1 year before 这个问题写过博客,看过规范。 ①===是==的子集 ②==有类型转换 ③规范内实现机制不同 today 你真的理解==和===的区别吗?

5.请解释Javascript的同源策略(same-origin policy)

1 year before 浏览器安全机制

today 如何理解same-origin policy?

6.你使用过Promises及其polyfills吗?请写出Promise的基本用法(ES6)

1 year before axios

today

7.使用Promises而非回调(callbacks)优缺点是什么?

1 year before 解决了callback hell问题。

today 以一个图片资源加载绘制canvas。

class CanvasImage {
  constructor(url, width, height) {
    this.width = width || 500;
    this.height = height || 500;
    this.url = url || '';

    this.element = new Image(this.width, this.height);
  }
  get imageElement() {
    return this.element;
  }
}

callback hell方式

source.onload = function() {
  const target = new CanvasImage(url, 80, 80).imageElement;
  target.onload = function() {
    const main = new MainCanvas(source, target, 'right').canvasElement;
    context.drawImage(main, 0, 0, 500, 500);
  };
};

Promise方式

const sourcePromise = new Promise((resolve) => {
  setTimeout(() => {
    const target = new CanvasImage(url, 80, 80).imageElement;
    resolve(target);
  }, 0);
});
source.onload = function() {
  sourcePromise.then((target) => {
    const main = new MainCanvas(source, target, 'right').canvasElement;
    context.drawImage(main, 0, 0, 500, 500);
  });
};

async/await方式

 function sourceCanvasImage() {
  return new Promise((resolve) => {
    setTimeout(() => {
      const target = new CanvasImage(url, 80, 80).imageElement;
      resolve(target);
    }, 0);
  });
}

async function mergeCanvas() {
  const targetElement = await sourceCanvasImage();
  const main = new MainCanvas(source, targetElement, 'right').canvasElement;
  context.drawImage(main, 0, 0, 500, 500);
}

source.onload = function() {
  mergeCanvas();
};

8.什么是事件循环(event loop)?

1 year before

today 官方的解释更加权威:https://nodejs.org/de/docs/guides/event-loop-timers-and-nexttick/

什么是Event Loop?event loop允许Node.js执行非阻塞的I/O操作-----尽管JS是单线程的-----它尽可能地通过把操作卸载到系统内核(kernel)。

因为大多数现代的内核(kernel)是多线程的,它们可以在后台处理多个操作。当其中之一完成后,kernel会告诉Node.js一个适当的callback可以被添加到poll queue(轮询队列)中并且执行。

更多原理上的内容,可以参考:[译]Node.js Event Loop,Timers和 process.nextTick()

event loop,原理细节包括以下内容

其中前三部分属于main thread,可以阅读node源码一探究竟。最后的background thread属于libuv的部分,可以去深入libuv源码(这是一个专门处理异步的c语言库)理解其实现机制。

但是阅读源码需要非常好的基础。这里推荐一篇囊括了以上知识点的非常走心的文章:https://blog.risingstack.com/node-js-at-scale-understanding-node-js-event-loop/

FrankKai commented 5 years ago

代码

1.foo的值是什么?

var foo = 10 + '20'; 1 year before: 30 today: "1020"

2.如何实现以下函数?

add(2, 5);// 7
add(2)(5);// 7

1 year before 通过arguments类数组对象。

function foo(){
    var sum = 0;
    for(var i = 0; i<arguments.length; i++){
        sum += arguments[i]
    }
    if(arguments.length>=2){
        return sum;
    }else{
        return foo;
    }
}

add(2)(5)运行失败。 today

const add = (a, b) => {
    const result =
      a && b
        ? a + b
        : function(b) {
            return a + b;
          };
    return result;
 };

3.下面两个alert的结果是什么?

var foo = "Hello";
(function() {
    var bar = " world";
    alert(foo + bar);
})();
alert(foo + bar);

1 year before "undefined World" "Hello undefined"

today "Hello world" // 立即执行函数作用域可以访问全局变量 Uncaught ReferenceError: bar is not defined // 全局作用域不可以访问局部变量

4.下面代码的输出是什么?

console.log('one');
setTimeout(function() {
    console.log('two');
}, 0);
console.log('three');

1 year before one three two today one three two 原因:优先执行函数调用栈中任务,setTimeout排入宏任务队列延后执行。 event loop包括call stack,(宏)任务队列,微任务队列和background thread,而call stack中的普通代码优先执行,setTimeout会经由background thread进入宏任务队列,宏任务队列间隙执行微任务队列。

image

5.下面代码的输出是什么?

function getResult(value, time){
    return new Promise((resolve)=>{
        setTimeout(()=>{
            console.log(value);
            resolve();
        }, time);
    });
}
(async () => {
    const a = getResult(1, 300);
    const b = getResult(2, 200);

    await a;
    await b;
})();
(async () => {
    await getResult(1, 300);
    await getResult(2, 200);
})();

1 year before 1 2 today 2 1 1 2 这是一道考察async的题目:如何理解async函数?

6.下面的代码输出是什么?

var obj = {
    a: 1,
    name: 'world',
    objSayName: function(fn) {
         fn();
    }
}
var name = 'hello';
var arr = [1, 2, 3, 4, 5];

function foo(o) {
    var bar = arr || [6, 7, 8];
    var arr = [4, 2, 9];
    var baz = o;
    baz.a = 2;
    console.log(bar, obj.a);
}

function sayName() {
    return console.log(this.name);
}

foo(obj);
obj.objSayName(sayName);

1 year later [4,2,9] 2 'hello' today [6,7,8] 2 'hello' 原因: 变量提升。

var bar,arr,baz;
bar = arr || [6, 7, 8];// 此时arr是undefined,因此bar得到了[6, 7, 8]赋值
arr = [4, 2, 9];

baz获得了obj的引用,所有修改baz相当于修改obj,所以打印出2.

因为闭包,全局的sayName函数内的this指向全局,所以是hello。