目录:
-
初级开发者相关问题【共计 1 道题】
-
31.JS 中继承方式有哪些?【JavaScript】
-
中级开发者相关问题【共计 3 道题】
-
226.前端动画有哪些实现方式?【JavaScript】【出题公司: 阿里巴巴】
-
227.进程、线程、协程分别是什么概念?【JavaScript】【出题公司: 小米】
-
228.单线程的 nodejs 是如何充分利用计算机 CPU 资源的呢?【Nodejs】
-
高级开发者相关问题【共计 4 道题】
-
141.如何组织 monorepo 工程?【工程化】
-
235.[webpack] 什么情况下 webpack treeShaking 会失效?【工程化】【出题公司: 小米】
-
236.babel 的工作流程是如何的?【工程化】
-
237.canvas 与 svg 在可视化领域优劣如何【web应用场景】【出题公司: 腾讯】
-
资深开发者相关问题【共计 2 道题】
-
231.不用使用 vue-cli ,如何创建一个完整的 vue 工程?【工程化】
-
232.使用同一个链接, 如何实现 PC 打开是 web 应用、手机打开是一个 H5 应用?【web应用场景】【出题公司: 小米】
初级开发者相关问题【共计 1 道题】
31.JS 中继承方式有哪些?【JavaScript】
1、借助构造函数实现继承
call和apply改变的是JS运行的上下文:
/*借助构造函数实现继承*/
function Parent(name) {
this.name = name;
this.getName = function () {
console.log(this.name);
}
}
function Child(name) {
Parent.call(this, name);
this.type = 'child1'
}
let child = new Child('yanle');
child.getName();
console.log(child.type);
父类的this指向到了子类上面去,改变了实例化的this 指向,导致了父类执行的属性和方法,都会挂在到 子类实例上去;
缺点:父类原型链上的东西并没有被继承;
2、通过原型链实现继承
/*通过原型链实现继承*/
function Parent2(){
this.name='parent2'
}
function Child2(){
this.type='child2'
}
Child2.prototype=new Parent2();
console.log(new Child2());
Child2.prototype是Child2构造函数的一个属性,这个时候prototype被赋值了parent2的一个实例,实例化了新的对象Child2()的时候, 会有一个proto属性,这个属性就等于起构造函数的原型对象,但是原型对象被赋值为了parent2的一个实例, 所以new Child2的原型链就会一直向上找parent2的原型
var s1=new Child2();
var s2=new Child2();
s1.proto===s2.proto;//返回true
缺点:通过子类构造函数实例化了两个对象,当一个实例对象改变其构造函数的属性的时候, 那么另外一个实例对象上的属性也会跟着改变(期望的是两个对象是隔离的赛);原因是构造函数的原型对象是公用的;
3、组合方式
/*组合方式*/
function Parent3(){
this.name='parent3';
this.arr=[1,2,3];
}
function Child3(){
Parent3.call(this);
this.type='child';
}
Child3.prototype=new Parent3();
var s3=new Child3();
var s4=new Child3();
s3.arr.push(4);
console.log(s3,s4);
优点:这是最通用的使用方法,集合了上面构造函数继承,原型链继承两种的优点。
缺点:父类的构造函数执行了2次,这是没有必要的,
constructor指向了parent了
4、组合继承的优化
/*组合继承的优化1*/
function Parent4(){
this.name='parent3';
this.arr=[1,2,3];
}
function Child4(){
Parent4.call(this);
this.type='child5';
}
Child4.prototype=Parent4.prototype;
var s5=new Child4();
var s6=new Child4()
缺点:s5 instaceof child4 //true, s5 instanceof Parent4//true
我们无法区分一个实例对象是由其构造函数实例化,还是又其构造函数的父类实例化的
s5.constructor 指向的是Parent4;//原因是子类原型对象的constructor 被赋值为了父类原型对象的 constructor,所以我们使用constructor的时候,肯定是指向父类的
Child3.constructor 也有这种情况
5、组合继承的优化2
function Parent5() {
this.name = 'parent5';
this.play = [1, 2, 3];
}
function Child5() {
Parent5.call(this);
this.type = 'child5'
}
Child5.prototype = Object.create(Parent5.prototype);
//这个时候虽然隔离了,但是constructor还是只想的Parent5的,因为constructor会一直向上找
Child5.prototype.constructor=Child5;
var s7=new Child5();
console.log(s7 instanceof Child5,s7 instanceof Parent5);
console.log(s7.constructor);
通过Object.create来创建原型中间对象,那么这么来的话,chiild5的对象prototype获得的是parent5 父类的原型对象;
Object.create创建的对象,原型对象就是参数;
6、ES 中的继承
Class 可以通过extends关键字实现继承,让子类继承父类的属性和方法。extends 的写法比 ES5 的原型链继承,要清晰和方便很多。
class Point { /* ... */ }
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
中级开发者相关问题【共计 3 道题】
226.前端动画有哪些实现方式?【JavaScript】【出题公司: 阿里巴巴】
主要的实现方式
JS 的实现方式
-
通过定时器(
setTimeout
,setInterval
)来间隔改变元素样式 -
requestAnimationFrame
CSS 3
-
过度动画:transition
-
animation 动画
HTML 5
-
Canvas
-
WebGL
-
svg
requestAnimationFrame
window.requestAnimationFrame()
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。 该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
当你准备更新动画时你应该调用此方法。这将使浏览器在下一次重绘之前调用你传入给该方法的动画函数 (即你的回调函数)。回调函数执行次数通常是每秒 60 次,但在大多数遵循 W3C 建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。
回调函数会被传入 DOMHighResTimeStamp 参数,DOMHighResTimeStamp指示当前被 requestAnimationFrame()
排序的回调函数被触发的时间。在同一个帧中的多个回调函数,它们每一个都会接受到一个相同的时间戳,即使在计算上一个回调函数的工作负载期间已经消耗了一些时间。该时间戳是一个十进制数,单位毫秒,最小精度为 1ms(1000μs)。
使用语法: window.requestAnimationFrame(callback);
参数: 下一次重绘之前更新动画帧所调用的函数 (即上面所说的回调函数)。该回调函数会被传入 DOMHighResTimeStamp
参数,该参数与 performance.now()
的返回值相同,它表示 requestAnimationFrame()
开始去执行回调函数的时刻。
使用示范:
<div id="demo"
style="position: absolute;width: 100px;height: 100px;background-color: #ccc;left: 0;top: 0;">
</div>
<script>
var demo = document.getElementById("demo");
function reader() {
demo.style.left = parseInt(demo.style.left) + 1 + "px";// 每一帧向右移动1px
}
requestAnimationFrame(function() {
reader();
// 当超过300px 后才停止
if (parseInt(demo.style.left) > 1300) demo.style.left = 0;
requestAnimationFrame(arguments.callee);
});
</script>
transition
属性名 | 说明 |
---|---|
transition | 用于简写设置四个过渡属性,包括:transition-property, transition-duration, transition-timing-function 和 transition-delay |
transition-property | 规定应用过渡效果的 CSS 属性的名称,多个属性用逗号分隔,如:transition-property: width, height;,表示在 width 和 height 发生改变时会应用过渡效果 |
transition-duration | 规定过渡效果的持续时间,如:transition-duration: 1s;,表示过渡效果持续时间为 1 秒 |
transition-timing-function | 规定过渡效果的时间曲线,即过渡效果的速度变化。常用的值包括:linear, ease, ease-in, ease-out, ease-in-out, cubic-bezier(n,n,n,n) |
transition-delay | 规定过渡效果何时开始,如:transition-delay: 1s;,表示在 1 秒之后开始应用过渡效果 |
可以通过设置不同的 transition 属性,来实现各种各样的过渡效果。例如:
cssCopy codediv {
width: 100px;
height: 100px;
background-color: red;
transition: width 1s ease-in-out;
}
div:hover {
width: 200px;
}
上述代码表示当鼠标悬停在 div 元素上时,它的宽度会从 100px 变为 200px,过渡效果会持续 1 秒,且速度曲线为缓进缓出。
animation
下面是 CSS3 animation 的属性表格:
属性 | 描述 |
---|---|
animation-name | 规定需要绑定到选择器的 keyframe 名称。 |
animation-duration | 规定动画完成一个周期所花费的秒或毫秒。 |
animation-timing-function | 规定动画的速度曲线。 |
animation-delay | 规定在动画开始之前的延迟。 |
animation-iteration-count | 规定动画应该播放的次数。 |
animation-direction | 规定是否应该轮流反向播放动画。 |
animation-fill-mode | 规定当动画不播放时(当动画完成之前,或当动画被暂停时),要应用到元素的样式。 |
animation-play-state | 规定动画是否正在运行或已暂停。 |
animation | 是 animation 属性的一个简写属性,包含六个独立属性。 |
其中,animation-name、animation-duration、animation-timing-function 是必须要指定的属性。其他属性都是可选的。通过这些属性,我们可以实现更加灵活的动画效果。
一个使用 animation
的例子是实现旋转动画。例如,可以创建一个带有 CSS 类名 .rotate
的 div
元素,并使用以下样式:
cssCopy code.rotate {
animation: spin 2s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
这将在 div
元素上应用一个旋转动画,持续时间为 2 秒,并且以线性方式无限循环。在 @keyframes
规则中定义了旋转动画的动画过程。在 from
和 to
关键帧中,定义了元素旋转的起始和结束状态。在 animation
属性中,指定了动画名称、持续时间、时间函数和动画播放次数等参数。
Canvas 实现动画
Canvas 可以通过一帧帧的绘制来实现动画。具体来说,可以通过 requestAnimationFrame
方法在浏览器下一次重绘之前执行指定的回调函数来不断地更新 Canvas 上的内容,从而实现动画效果。
以下是 Canvas 实现动画的一般流程:
- 获取 Canvas 对象和上下文对象
首先,需要获取 Canvas 对象和上下文对象。
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
- 设置动画帧数和初始状态
为了实现动画,需要对 Canvas 进行重绘。重绘的次数由动画的帧数决定,通常设置为每秒 60 帧。
同时,还需要设置 Canvas 的初始状态,包括背景颜色、形状、大小等。
- 定义动画函数
动画函数中主要包含两个部分:更新状态和绘制图形。更新状态指更新 Canvas 上的图形的位置、大小、颜色等属性,绘制图形指将更新后的图形绘制到 Canvas 上。
function animate() {
// 更新状态
// ...
// 绘制图形
// ...
}
- 使用 requestAnimationFrame 方法执行动画
最后,可以使用 requestAnimationFrame
方法不断执行动画函数,从而实现动画效果。
function animate() {
// 更新状态
// ...
// 绘制图形
// ...
// 递归调用 requestAnimationFrame 方法执行动画
requestAnimationFrame(animate);
}
// 启动动画
requestAnimationFrame(animate);
在动画函数中更新状态和绘制图形后,调用 requestAnimationFrame
方法递归执行动画函数,从而实现不断更新和绘制的动画效果。
svg 实现动画
SVG(可缩放矢量图形)是一种使用 XML 描述 2D 图形的格式,它可以使用 CSS 和 JavaScript 进行动画操作。在 SVG 中,可以使用两种技术实现动画,分别是 SMIL(Synchronized Multimedia Integration Language)和 JavaScript。
下面举一个使用 JavaScript 实现 SVG 动画的例子。假设有一个圆形,当鼠标悬停在圆形上时,圆形会变为红色并且向右移动:
SVG 代码:
<svg width="200" height="200">
<circle id="circle" cx="50" cy="50" r="20" fill="blue" />
</svg>
CSS 代码:
#circle {
transition: fill 0.3s ease;
}
JavaScript 代码:
var circle = document.getElementById('circle');
circle.addEventListener('mouseover', function() {
circle.setAttribute('fill', 'red');
circle.setAttribute('cx', '70');
});
上面的代码中,通过给圆形添加 mouseover 事件监听器,当鼠标悬停在圆形上时,修改圆形的 fill 属性为红色,并将圆心的 x 坐标改为 70。由于圆形在 CSS 中定义了过渡效果,因此圆形会平滑地变为红色并向右移动。
227.进程、线程、协程分别是什么概念?【JavaScript】【出题公司: 小米】
进程(Process)和 线程(Thread)
进程(Process)和 线程(Thread)是操作系统中的重要概念。
进程是指计算机中已经运行的程序,它是操作系统资源分配的最小单位。进程拥有独立的内存空间和系统资源,如打开的文件、网络连接等。在操作系统中,每个进程都拥有一个唯一的标识符,称为进程ID。
线程是进程中的执行单元,一个进程可以包含多个线程,它们共享进程的内存空间和系统资源。线程是CPU调度的最小单位,它可以看作是进程中的一个独立执行流程。与进程不同的是,线程没有自己的系统资源,只有一部分与进程共享的资源。在操作系统中,每个线程都拥有一个唯一的标识符,称为线程ID。
可以将进程和线程的关系类比为一家工厂。工厂代表一个进程,工厂中的工人代表线程。每个工人负责自己的一部分工作,但是他们共享工厂的资源,如原材料、设备等。
总的来说,进程和线程都是操作系统资源分配和调度的基本单位,它们之间的关系是多对一的,即多个线程可以属于同一个进程,共享进程的资源。
协程(Coroutine)
协程(Coroutine)是一种用户态的轻量级线程,也称为协作式多任务处理,与传统的抢占式多任务处理方式不同,协程的调度不由系统来控制,而是由程序员自己控制。在协程内部,程序可以自己决定在何处挂起、何时恢复执行。协程可以有效地避免多线程并发操作时出现的死锁、竞争、状态同步等问题,同时协程又可以充分利用 CPU 资源,提高程序执行效率。
在协程中,所有任务共享一个线程,通过在任务之间切换来实现并发,这种方式可以避免线程切换时的性能损耗,也可以避免线程之间的同步问题。协程主要有以下特点:
-
协程是一种轻量级的线程,其切换过程不需要操作系统介入,而是在用户态实现的。
-
协程是一种非抢占式调度方式,需要程序员显式地让出执行权。
-
协程可以共享全局变量等状态信息,但是需要程序员自己管理状态同步。
协程在很多语言中都得到了广泛的应用,例如 Python 中的 asyncio、Lua 中的 coroutine 等。在前端领域中,JavaScript 的 Generator 函数就是一种协程实现方式。
228.单线程的 nodejs 是如何充分利用计算机 CPU 资源的呢?【Nodejs】
虽然 Node.js 是单线程的,但是它能够充分利用计算机的 CPU 资源的原因在于其采用了事件驱动和异步 I/O 的方式来处理请求,而不是采用阻塞式 I/O 的方式。这使得 Node.js 能够在处理一个请求时不会因为等待 I/O 操作而阻塞,从而可以处理更多的请求。
具体来说,当 Node.js 启动一个程序时,会创建一个事件循环,不断地从事件队列中取出一个事件,然后调用相应的回调函数来处理该事件。当有新的请求到来时,Node.js 会将其添加到事件队列中,等待事件循环处理。同时,Node.js 还采用了非阻塞式 I/O 的方式,即在等待 I/O 操作时不会阻塞其他代码的执行,从而能够更好地利用 CPU 资源。
此外,Node.js 还采用了基于事件的回调机制来处理异步请求,这种机制可以避免线程切换和上下文切换带来的开销,提高 CPU 利用率。因此,虽然 Node.js 是单线程的,但是它能够充分利用计算机 CPU 资源,处理更多的请求。
高级开发者相关问题【共计 4 道题】
141.如何组织 monorepo 工程?【工程化】
参考文档:
235.[webpack] 什么情况下 webpack treeShaking 会失效?【工程化】【出题公司: 小米】
以下是一些可能导致 webpack tree shaking 失效的情况
- 代码中使用了动态引入(Dynamic Imports)的语法,这种情况下,webpack 无法确定哪些代码会被使用,因此不会进行 tree shaking。
- 代码使用了函数式编程的方式,比如使用了 map、filter、reduce 等高阶函数,而这些函数很难通过静态分析确定代码的执行路径,所以可能会导致 tree shaking 失效。
- 代码中使用了 webpack 无法识别的模块系统,比如使用了 AMD 或者 CommonJS 的语法,这种情况下 webpack 也无法进行 tree shaking。
- 代码使用了 side effect,比如改变全局变量或者函数的参数,这种情况下 webpack 也无法进行 tree shaking。
函数式编程的方式 filter 为何会导致无法 tree shaking
函数式编程中常常使用高阶函数来组合函数,这种组合方式常常需要使用传递函数作为参数的方式,例如 map、filter 等高阶函数。这种情况下,如果参数传递的是一个函数表达式或者函数声明,那么无法进行 treeshaking。
举个例子:
// 代码中定义了一个 sum 函数
function sum(a, b) {
return a + b;
}
// 调用了 lodash 库的 filter 函数,传递一个匿名函数表达式作为参数
import { filter } from 'lodash';
const arr = [1, 2, 3, 4, 5];
const result = filter(arr, item => {
if (item > 10) return sum(item, 1)
else return item;
});
上述代码中,使用了 lodash 库的 filter 函数,并且传递了一个匿名函数表达式作为参数。由于函数表达式无法被静态分析,不知道 sum 是否会被调用,因此无法进行 treeshaking,最终导致整个 sum 函数也被打包进了最终的代码中。
为什么 commonjs 模块化会导致无法 tree shaking
CommonJS 模块化语法是 Node.js 中的模块化规范,其使用了 require()
导入模块,使用 module.exports
或 exports
导出模块。它采用的是动态导入(require())和同步加载的方式,这种导入方式无法在编译时确定所依赖的模块,因此在 Webpack 进行 Tree Shaking 时,这种导入方式的模块会被认为无法被静态分析,因而会被排除掉。
相反,ES6 模块化语法采用的是静态导入的方式,例如 import foo from './foo.js'
,可以在编译时分析出所依赖的模块,因此支持 Tree Shaking。
因此,如果要使用 Tree Shaking,建议采用 ES6 模块化语法。如果必须使用 CommonJS 模块化规范,可以尝试使用动态导入 (import())
语法,或者采用其他工具或手动实现 Tree Shaking。
side effect 是什么,为何会导致无法 tree shaking
在编写 JavaScript 代码时,如果一个函数除了返回值外,还对外部的变量产生了其他的影响,比如修改了全局变量、读写了文件等操作,那么这个函数就被称为有“副作用”(side effect)。因为这种函数并不是纯函数,它可能会影响其他部分的代码执行结果,不便于优化和调试。
在 Tree Shaking 的过程中,webpack 将模块打包成单独的 JavaScript 文件,它会从模块中找出哪些代码没有被使用到,并删除这些代码。但是,如果模块中存在带有副作用的代码,这些代码虽然没有被使用到,但它们仍然会被保留下来,因为这些代码可能会对其他部分的代码产生影响,因此不能简单地删除。这也是为什么带有副作用的代码会导致无法 Tree Shaking 的原因。
236.babel 的工作流程是如何的?【工程化】
Babel 是一个 JavaScript 编译器,它的主要功能是将新版本的 JavaScript 代码转换成向后兼容的代码。Babel 的工作流程可以简单概括为以下几个步骤:
- 解析:将 JavaScript 代码解析成 AST(抽象语法树)。
- 转换:对 AST 进行遍历,进行代码转换。
- 生成:将转换后的 AST 生成 JavaScript 代码。
具体来说,Babel 的工作流程如下:
- Babel 使用 babylon 解析器将 JavaScript 代码解析成 AST,babylon 是一个基于 AST 的 JavaScript 解析器。
- Babel 使用 babel-traverse 遍历器对 AST 进行遍历,找到需要转换的节点,进行转换。
- Babel 使用 babel-core 转换器将 AST 转换成 JavaScript 代码。babel-core 是 babel 的核心模块,它包含了所有的转换器和插件。
- Babel 使用 babel-generator 生成器将转换后的 AST 生成 JavaScript 代码。babel-generator 是一个将 AST 转换成 JavaScript 代码的工具。
在整个流程中,Babel 还会使用 babel-preset-env、babel-plugin-transform-runtime、babel-polyfill 等插件和工具来完成更加复杂的任务,如将 ES6 模块转换成 CommonJS 模块,使用 Polyfill 来实现一些新的 API 等。
需要注意的是,Babel 的转换过程是有损的,转换后的代码不一定与原始代码完全相同,也可能存在性能问题。因此,在使用 Babel 进行转换时,需要谨慎选择转换的规则和插件,以确保转换后的代码正确、高效。
237.canvas 与 svg 在可视化领域优劣如何【web应用场景】【出题公司: 腾讯】
Canvas和SVG都可以用于可视化,但它们的优缺点不同。
Canvas: 是一个基于像素的渲染引擎,使用JavaScript API在画布上绘制图像,它的优点包括:
-
Canvas渲染速度快,适合处理大量图像和高度动态的图像。
-
可以直接操作像素,能够创建高质量、流畅的动画效果。
-
Canvas可用于实现复杂的游戏、3D效果等。
但它也存在一些缺点:
-
Canvas只能绘制位图,如果需要绘制矢量图形需要使用其他工具转换成像素图像。
-
Canvas不支持事件处理器,无法为绘制的对象绑定事件。
-
由于Canvas是基于像素的渲染,它的缩放比较困难,可能会影响图像的质量。
SVG: 是一种基于矢量的图形格式,可以使用XML和JavaScript API在浏览器中绘制图像,它的优点包括:
-
SVG是基于矢量的图形格式,图像可以无限放大而不失真。
-
可以为SVG图像添加事件处理器,实现交互效果。
-
可以通过CSS进行样式控制,使得SVG图像更加灵活。
-
SVG图像可以直接嵌入到HTML文档中,不需要另外下载。
但它也存在一些缺点:
-
SVG渲染速度较慢,适合处理少量图像和少量动态的图像。
-
SVG图像在处理复杂图形时可能会导致性能问题。
-
由于SVG是基于矢量的图形格式,它的复杂度比较高,可能会导致文件大小较大。
表格对比
特性 | Canvas | SVG |
---|---|---|
图形质量 | 像素级别的图形,适合绘制大量复杂动态的图形 | 矢量图,图形不会失真,适合绘制静态图形 |
图形渲染 | 快速渲染,适合处理大量图形数据 | 慢速渲染,适合处理小规模静态图形 |
交互性 | 事件处理复杂,需要手动编写交互逻辑 | 事件处理简单,内置事件处理机制 |
动画效果 | 动画效果需要手动实现,实现复杂动画困难 | 内置 SMIL 动画支持,可实现较复杂动画效果 |
浏览器支持 | 除 IE8 及以下版本外,其他浏览器都支持 | 除 IE9 及以下版本外,其他浏览器都支持 |
适用场景 | 处理大量动态图形,如游戏开发、数据可视化等 | 绘制简单静态图形,如图标、线条、文字等 |
资深开发者相关问题【共计 2 道题】
231.不用使用 vue-cli ,如何创建一个完整的 vue 工程?【工程化】
这个一个较为复杂和庞大的话题, 不能称之为问题, 只能说它是一个话题。
主要涉及到的话题如下:
- vue 工程初始化
- 测试集成
- UI 库绑定、基础组件使用
- 开发流程
- 代码规范(甚至包含 commit 规范)
- 多人协作与工作流
- 构建问题
- 上线流程
- 线上日志与用户反馈问题排查
- 性能保证
232.使用同一个链接, 如何实现 PC 打开是 web 应用、手机打开是一个 H5 应用?【web应用场景】【出题公司: 小米】
可以通过根据请求来源(User-Agent)来判断访问设备的类型,然后在服务器端进行适配。例如,可以在服务器端使用 Node.js 的 Express 框架,在路由中对不同的 User-Agent 进行判断,返回不同的页面或数据。具体实现可以参考以下步骤:
- 根据 User-Agent 判断访问设备的类型,例如判断是否为移动设备。可以使用第三方库如 ua-parser-js 进行 User-Agent 的解析。
- 如果是移动设备,可以返回一个 H5 页面或接口数据。
- 如果是 PC 设备,可以返回一个 web 应用页面或接口数据。
具体实现方式还取决于应用的具体场景和需求,以上只是一个大致的思路。