乐趣区

前端面试题分享

会了这几道面试题,恭喜你有进入 3 人创业小团队的资格了!

JS 部分:

1. 打印以下执行结果

console.log('script start');
setTimeout(() => {console.log('setTimeout');
},0)
Promise.resolve().then(function() {console.log('promise1');
}).then(function() {console.log('promise2');
});
console.log('script end')

考察的是代码执行顺序问题,定时器和主程序属于宏任务,Promise 中 then 的回调属于微任务,在每个宏任务里面都会先执行完微任务再去执行下一个宏任务。
本题执行逻辑应该是主线程中 console.log(‘script start’); 碰到定时器,把这个宏任务扔事件队列里面,继续向下,碰到 Promise.then 异步任务,继续扔队列里面,执行 console.log(‘script end’);再去执行当前宏任务里面的微任务,即 Promise.then 回调函数,最后执行第二个宏任务定时器.

执行结果:script start
script end
promise1
promise2
setTimeout

2. 打印以下执行结果

const promise = new Promise((resolve,reject) => {console.log(1);
    resolve();
    console.log(2);
});
promise.then(()=>{console.log(3);
});
console.log(4);

Promise 构造函数是同步执行的,promise.then 中的函数是异步执行的。所以

执行结果:
1
2
4
3

3. 打印以下执行结果

var funcs = [];
for(var i=0;i<10;i++) {funcs.push(function() {console.log(i);
    });
}
funcs.forEach(function(func) {func();
});

基础题,匿名函数中的 i 值只有在函数调用执行的时候才会去寻找对应变量的,for 循环结束后执行 func()时,此时 i 变量已经变为 10(最后有个 i ++),所以循环打印了十次 10, 想依次打印 0 - 9 的话可以使用立即执行函数解决或者使用 ES6 的块级作用域 let 声明变量 i.

CSS 部分:

1. 分别写出 box 的高度

<style>
    .box {
        line-height: 24px;
        background-color: lightblue;
    }
    .box span {
        line-height: 48px;
        border: 1px solid;
    }
</style>
<div class="box">
    <span>content...</span>
</div>

考察的是 inline box 模型, 它的工作就是包裹每行文字, 一个没有设置 height 属性的 div 的高度就是由一个一个 line boxes 的高度堆积而成的, 撑开 div 高度的是 line-height 不是文字内容.MDN 文档中对 line-height 的描述如下:

line-height CSS 属性用于设置多行元素的空间量, 比如文本。对于块级元素, 它指定元素行盒(line boxes)的最小高度。对于非替代的 inline 元素, 它用于计算行盒(line box)的高度。
适用元素 all elements. It also applies to ::first-letter and ::first-line.

如上面最终显示 .box 容器 height = 48px;
1.
将 <span> 标签里的内容 改为 <span>content ... <br> content ... </span> 后,.box 的 height = 96px(48px*2)。2.
将 <span> 标签里的内容 改为 content ... <br><span> content ... </span> 后,.box 的 height = 48 + 24 = 72px。

2.css 实现 0.5 像素的边框

半像素边框当然不是简单地把 1px 改为 0.5px, 浏览器中最小的像素单位为 1 像素,是不能识别 0.5 个像素,

1. 设置目标元素作为定位参照
box{position:relative}

2. 给目标元素添加一个伪元素 before 或者 after,并设置绝对定位
.box:before{content:""; position:absolute;}

3. 给伪元素添加 1px 边框
border:1px solid #000;

4. 设置伪元素的宽高为目标元素的 2 倍
width:200%; height:200%;

5. 缩小 0.5 倍(缩放到原来大小)transform-origin: 0 0;
transform:scale(0.5,0.5);

6. 把 border 边框在线框内绘制
box-sizing:border-box;

Vue 框架部分:

1.vue 组件中的 data 为什么必须是个函数

简单地说,对象是引用数据类型,那你每次导入这个组件的时候,其实引用的是同一个内存地址,componentA 改变了引用这块地址的数据后,componentB 中的这块地址对应的数据也会被改变。
那是因为在 js 中,函数的 {} 才有独立的作用域,对象的 {},if(){} 是不构成作用域的.
函数里面,每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的,这样的话,不管你同时引入同一个组件多少次,他们之间的组件属性都是独立的,互不干扰。

2. 解释 vue 的响应式原理

vue 的响应式核心是 Object.defineProperty 实现的.

Object.defineProperty(obj, key, {set:function(){},
    get:function(){}
})

被 Object.defineProperty 绑定过的数据会被监听到,改变这个对象的时候会触发 get 和 set 事件,这也是 vue 的 model 层和 view 层通信的基础,其实 vue 中的 Observer 就是基于 Object.defineProperty 来实现的。
Observer 是数据的观察者,与 model 层直接通信,当数据更改时,它会通知 dep(专门管理数据监听依赖的东西),dep 会收集到它所需要的依赖,当 get 的时候,收集订阅者 (这里的订阅者其实是观察者模式下的订阅者,简单地说,就是它需要知道具体 get 什么订阅者,什么是观察者模式和发布订阅者设计模式,具体网上很多相关资料解释), 把他添加到依赖,比如,watch 和 computed 都依赖一个 data 进行监听或者计算,那么 dep 会将这两个不同的依赖收集。当 set 的时候会发布更新,通知 watcher 更新 view 层。
一个属性可能有多个依赖,每个响应式数据都有一个 Dep 来管理它的依赖。
每一个依赖又是依靠一个中介的角色来通知变化从而更新视图的,因此 watcher 能通知变化去执行各自的代码,当然它也能区分自己所属的依赖,比如自己是属于 data 还是 watch 还是 computed.
放两张网上的图可以更明了的说明他们之间的关系,也可以看看大佬们的文章,讲的更仔细,想学会这种设计模式还是要撸源码, 应付面试已经够了。

原图链接


原图链接

3. 解释 v -model 的原理

v-model 本质上就是一个语法糖,实现原理其实就是上面说过的数据绑定加上底层的 input 事件监听,通过 v -bind 绑定一个数据传给子组件,子组件里面的 model 默认用 value 属性接受,然后子组件监听数据发生变化,emit 触发父组件的 input 事件,通过触发事件来进行传值,实现了父子组件数据的双向绑定。

4. 设计模式 - 发布订阅者模式

定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖他的对象都会得到通知。

观察者模式是由具体目标(发布者/被观察者)调度的,而发布 / 订阅模式是由独立的调度中心进行调度,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布 / 订阅模式则不会(相当于一个中介的角色,一个全局的 Event,发布者不需要知道具体要通知谁,订阅者也不需要知道具体是谁通知的);可以说发布订阅模式是观察者模式进一步解耦,在实际中被大量运用的一种模式。

持续更新~

推荐大佬的一篇关于浏览器和 js 线程的文章,炒鸡棒

退出移动版