web前端宝藏题库,适用于0-3年前端工程师...

前些天意外认识了一个前端大佬,某大厂在职,我怕打扰就没敢多聊,没想到大佬挺平易近人,很亲切的给我出谋划策,还给了我一套面试题,难度不是很高,但是胜在全面十分契合我现在的状况,所以出一篇文章感谢大佬。

祝大佬在新的一年事业扬帆启航,生活顺心美满!
也祝大家新年快乐,财源广进

ok,现在前端的状况大家行动能力强的应该也体会了一下,前端因坑位不足再加上全栈的普及,基本生存环境大大降低,很多人被迫转行,能力不足就只能卷全栈,能力强的多半都在大厂,所以大家啊,量力而行,悬崖勒马也未尝不可。题库资源【点击此处

题库+答案:

第 1 题:写 React / Vue 项目时为什么要在列表组件中写 key,

其作用是什么?

key 是给每一个 vnode 的唯一 id,可以依靠 key,更准确,更快的拿到 oldVnode 中对 应的 vnode 节点

第 2 题:['1', '2', '3'].map(parseInt) what & why ?

第一眼看到这个题目的时候,脑海跳出的答案是 [1, 2, 3],但是真正的答案是[1, NaN, NaN]。

首先让我们回顾一下,map 函数的第一个参数 callback。这个 callback 一共可以 接收三个参数,其中第一个参数代表当前被处理的元素,而第二个参数代表该元素的索引。

arr.map(callback: (value: T, index: number, array: T[]) => U, thisArg?: any); 
而 parseInt 则是用来解析字符串的,使字符串成为指定基数的整数。接收两个 参数,第一个表示被处理的值(字符串),第二个表示为解析时的基数。 
parseInt(string, radix) 
了解这两个函数后,我们可以模拟一下运行情况 
parseInt('1', 0) //radix 为 0 时,且 string 参数不以“0x”和“0”开头时, 按照 10 为基数处理。这个时候返回 1parseInt('2', 1) //基数为 1(1 进制)
表示的数中,最大值小于 2,所以无法解析,返回 NaNparseInt('3', 2) //基数为 2(2 进制)表示的数中,最大值小于 3,所以无法解析,返回 NaN

第 3 题:什么是防抖和节流?有什么区别?如何实现?

防抖——触发高频事件后 n 秒后函数只会执行一次,如果 n 秒内高频事件再 次被触发,则重新计算时间;

function debounce(fn) {
let timeout = null
// 创建一个标记用来存放定时器的返回值
return function() {
clearTimeout(timeout)
// 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(() => {
// 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的
interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
fn.apply(this, arguments)
}, 500)
}}function sayHi() {
console.log('防抖成功')}var inp =
document.getElementById('inp')inp.addEventListener('input',
debounce(sayHi)) // 防抖
节流——高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。
function throttle(fn) {
let canRun = true // 通过闭包保存一个标记
return function() {
if (!canRun) return
// 在函数开头判断标记是否为 true,不为 true 则 return
canRun = false // 立即设置为 false
setTimeout(() => {
// 将外部传入的函数的执行放在 setTimeout 中
fn.apply(this, arguments)
// 最后在 setTimeout 执行完毕后再把标记设置为 true(关键) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被 return 掉
canRun = true
}, 500)
}}function sayHi(e) {
console.log(e.target.innerWidth,
e.target.innerHeight)}window.addEventListener('resize',
throttle(sayHi))

第 4 题:介绍下 Set、Map、WeakSet 和 WeakMap 的区别?

Set——对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用

WeakSet——成员都是对象;成员都是弱引用,可以被垃圾回收机制回收,可以 用来保存 DOM 节点,不容易造成内存泄漏;

Map——本质上是键值对的集合,类似集合;可以遍历,方法很多,可以跟各 种数据格式转换。

WeakMap——只接受对象最为键名(null 除外),不接受其他类型的值作为键 名;键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收, 此时键名是无效的;不能遍历,方法有 get、set、has、delete。

第 5 题:介绍下深度优先遍历和广度优先遍历,如何实现?

深度优先遍历——是指从某个顶点出发,首先访问这个顶点,然后找出刚访问 这个结点的第一个未被访问的邻结点,然后再以此邻结点为顶点,继续找它的 下一个顶点进行访问。重复此步骤,直至所有结点都被访问完为止。

广度优先遍历——是从某个顶点出发,首先访问这个顶点,然后找出刚访问这 个结点所有未被访问的邻结点,访问完后再访问这些结点中第一个邻结点的所有结点,重复此方法,直到所有结点都被访问完为止。

//1.深度优先遍历的递归写法 function deepTraversal(node) {
let nodes = []
if (node != null) {
nodes.push[node]
let childrens = node.children
for (let i = 0;
i < childrens.length; i++) deepTraversal(childrens[i])
} return nodes}
//2.深度优先遍历的非递归写法 function deepTraversal(node) {
let nodes = []
if (node != null) {
let stack = []
//同来存放将来要访问的节点
stack.push(node)
while (stack.length != 0) {
let item = stack.pop()
//正在访问的节点
nodes.push(item)
let childrens = item.children
for (
let i = childrens.length - 1;
i >= 0;
i--
//将现在访问点的节点的子节点存入 stack,供将来访问 )
stack.push(childrens[i])
}
}
return nodes}
//3.广度优先遍历的递归写法 function wideTraversal(node) {
let nodes = [],
i = 0
if (node != null) {
nodes.push(node)
wideTraversal(node.nextElementSibling)
node = nodes[i++]
wideTraversal(node.firstElementChild)
}
return nodes}//4.广度优先遍历的非递归写法 function
wideTraversal(node) {
let nodes = [],
i = 0 while (node != null) {
nodes.push(node)
node = nodes[i++]
let childrens = node.children
for (let i = 0;
i < childrens.length;
i++) {
nodes.push(childrens[i])
}
}
return nodes
}

第 6 题:请分别用深度优先思想和广度优先思想实现一个拷贝 函数?

let _toString = Object.prototype.toStringlet map = {
array: 'Array',
object: 'Object',
function: 'Function',
string: 'String',
null: 'Null',
undefined: 'Undefined',
boolean: 'Boolean',
number: 'Number'}let getType = (item) => {
return _toString.call(item).slice(8, -1)}let isTypeOf = (item, type)
=> {
return map[type] && map[type] =
}

第 7 题:ES5/ES6 的继承除了写法以外还有什么区别?1.

1.ES5 的继承实质上是先创建子类的实例对象,然后再将父类的方法添加 到 this 上(Parent.apply(this)).

2. ES6 的继承机制完全不同,实质上是先创建父类的实例对象 this(所以必 须先调用父类的 super()方法),然后再用子类的构造函数修改 this。

3. ES5 的继承时通过原型或构造函数机制来实现。

4. ES6 通过 class 关键字定义类,里面有构造方法,类之间通过 extends 关 键字实现继承。

5. 子类必须在 constructor 方法中调用 super 方法,否则新建实例报错。因为子类没有自己的 this 对象,而是继承了父类的 this 对象,然后对其进行加工。

如果不调用 super 方法,子类得不到 this 对象。

6. 注意 super 关键字指代父类的实例,即父类的 this 对象。

7. 注意:在子类构造函数中,调用 super 后,才可使用 this 关键字,否则 报错。

function 声明会提升,但不会初始化赋值。Foo 进入暂时性死区,类似于 let、

const 声明变量。

const bar = new Bar();
// it's ok
function Bar() {
this.bar = 42;
}const foo = new Foo();
// ReferenceError: Foo is not definedclass Foo {
constructor() {
this.foo = 42;
}
}
class 声明内部会启用严格模式。
// 引用一个未声明的变量 function Bar() {
baz = 42;
// it's ok}const bar = new Bar();
class Foo {
constructor() {
fol = 42;
// ReferenceError: fol is not defined
}
}const foo = new Foo();
class 的所有方法(包括静态方法和实例方法)都是不可枚举的。
// 引用一个未声明的变量 function Bar() {
this.bar = 42;}Bar.answer = function()
{ return 42;
};
Bar.prototype.print = function() {
console.log(this.bar);
};
const barKeys = Object.keys(Bar);
// ['answer']const barProtoKeys = Object.keys(Bar.prototype);
// ['print']class Foo {
constructor() {
this.foo = 42;
}
static answer() {
return 42;
}
print() {
console.log(this.foo);
}}const fooKeys = Object.keys(Foo);
// []const fooProtoKeys = Object.keys(Foo.prototype);
// []
class 的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所
以也没有[[construct]],不能使用 new 来调用。
function Bar() {
this.bar = 42;
}Bar.prototype.print = function() {
console.log(this.bar);
};
const bar = new Bar();
const barPrint = new bar.print();
// it's okclass Foo {
constructor() {
this.foo = 42;
} print() {
console.log(this.foo);
}}const foo = new Foo();
const fooPrint = new foo.print();
// TypeError: foo.print is not a constructor
必须使用 new 调用 class。
function Bar() {
this.bar = 42;
}const bar = Bar();
// it's okclass Foo {
constructor() {
this.foo = 42;
}}const foo = Foo();
// TypeError: Class constructor Foo cannot be invoked without 'new'
class 内部无法重写类名。
function Bar() {
Bar = 'Baz';
// it's ok this.bar = 42;
}const bar = new Bar();
// Bar: 'Baz'
// bar: Bar {bar: 42}
class Foo {
constructor() {
this.foo = 42;
Foo = 'Fol';
// TypeError: Assignment to constant variable
}}const foo = new Foo();
Foo = 'Fol';
// it's ok

第 8 题:setTimeout、Promise、Async/Await 的区别

https://blog.csdn.net/yun_hou/article/details/88697954

第 9 题:Async/Await 如何通过同步的方式实现异步

async 起什么作用——输出的是一个 Promise 对象

第 10 题:异步笔试题请写出下面代码的运行结果

async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')}async function async2()
{
console.log('async2')}console.log('script
start')setTimeout(function()
{
console.log('setTimeout')},
0) async1()new Promise(function(resolve)
1) {
console.log('promise1')
resolve()}).then(function()
{
console.log('promise2')})console.log('script end')
//输出
//script start
//async1 start
//async2
//promise1
//script end
//async1 end
//promise2
//setTimeout

第 11 题:算法手写题

已知如下数组,编写一个程序将数组扁平化去并除其中重复部分数据,最终得 到一个升序且不重复的数组

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];

答:使用 Set 方法去重,flat(Infinity)扁平化

Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b})//[1,
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

第 12 题:JS 异步解决方案的发展历程以及优缺点。

1、回调函数(callback)

优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队 等着,会拖延整个程序的执行。)

缺点:回调地狱,不能用 try catch 捕获错误,不能 return

2、Promise

优点:解决了回调地狱的问题

缺点:无法取消 Promise ,错误需要通过回调函数来捕获

3、Generator

特点:可以控制函数的执行,可以配合 co 函数库使用

4、Async/await

优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题

缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使 用 await 会导致性能上的降低。

第 13 题:Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?

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

执行结果是:1243,promise 构造函数是同步执行的,then 方法是异步执行的

第 14 题:情人节福利题,如何实现一个 new

第 15 题:简单讲解一下 http2 的多路复用

HTTP2 采用二进制格式传输,取代了 HTTP1.x 的文本格式,二进制格式解析更 高效。

多路复用代替了 HTTP1.x 的序列和阻塞机制,所有的相同域名请求都通过同一 个 TCP 连接并发完成。

在 HTTP1.x 中,并发多个请求需要多个 TCP 连接,浏览器为了控制资源会有 6-8 个 TCP 连接都限制。HTTP2 中

同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗。

单个连接上可以并行交错的请求和响应,之间互不干扰

第 16 题:谈谈你对 TCP 三次握手和四次挥手的理第 17 题:A、B 机器正常连接后,B 机器突然重启,问 A 此时

处于 TCP 什么状态

如果 A 与 B 建立了正常连接后,从未相互发过数据,这个时候 B 突然机器重

启,问 A 此时处于 TCP 什么状态?如何消除服务器程序中的这个状态?(超

纲题,了解即可)

因为 B 会在重启之后进入 tcp 状态机的 listen 状态,只要当 a 重新发送一个数据

包(无论是 syn 包或者是应用数据),b 端应该会主动发送一个带 rst 位的重置

包来进行连接重置,所以 a 应该在 syn_sent 状态

第 18 题:React 中 setState 什么时候是同步的,什么时候是异步的?

1、由 React 控制的事件处理程序,以及生命周期函数调用 setState 不会同步更 新 state 。

2、React 控制之外的事件中调用 setState 是同步更新的。比如原生 js 绑定的事件,setTimeout/setInterval 等。

第 19 题:React setState 笔试题,下面的代码输出什么?

class Example extends React.Component {
constructor() {
super()
this.state = {
val: 0
}
}
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
// 第 1 次 log
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
// 第 2 次 log
setTimeout(() => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
// 第 3 次 log
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
// 第 4 次 log
}, 0)
}
render() {
return null
}
}

答:

0, 0, 2, 3

第 20 题:介绍下 npm 模块安装机制,为什么输入 npm instal1就可以自动安装对应的模块?1.npm 模块安装机制:

  • 发出 npm install命令1查询 node_modules目录之中是否已经存在指定模块
  • 若存在,不再重新安装
  • 若不存在
  • npm 向 registry 查询模块压缩包的网址
  • 下载压缩包,存放在根目录下的.npm目录里
  • 解压压缩包到当前项目的 node modules 目录

第 21 题:有以下 3 个判断数组的方法,请分别介绍它们之间 的区别和优劣

Object.prototype.toString.call() 、 instanceof 以及 Array.isArray()

Object.prototype.toString.call()

每一个继承 Object 的对象都有 toString 方法,如果 toString 方法没有重写的 话,会返回 [Object type],其中 type 为对象的类型。但当除了 Object 类型的 对象外,其他类型直接使用 toString 方法时,会直接返回都是内容的字符串, 所以我们需要使用 call 或者 apply 方法来改变 toString 方法的执行上下文。

const an = ['Hello','An'];an.toString();
// "Hello,An"Object.prototype.toString.call(an);
// "[object Array]" 这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。
Object.prototype.toString.call('An')
// "[object String]"Object.prototype.toString.call(1)
// "[object Number]"Object.prototype.toString.call(Symbol(1))
// "[object Symbol]"Object.prototype.toString.call(null)
答:
0, 0, 2, 3
第 20 题:介绍下 npm 模块安装机制,为什么输入 npm install
// "[object Null]"Object.prototype.toString.call(undefined)
// "[object Undefined]"Object.prototype.toString.call(function(){})
// "[object Function]"Object.prototype.toString.call({name: 'An'})
// "[object Object]" Object.prototype.toString.call() 常用于判断浏览器内置对象时。
 instanceof
instanceof 的内部机制是通过判断对象的原型链中是不是能找到类型的
prototype。
使用 instanceof 判断一个对象是否为数组,instanceof 会判断这个对象的原型
链上是否会找到对应的 Array 的原型,找到返回 true,否则返回 false。
[] instanceof Array; // true
但 instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型
instanceof Object 都是 true。
[] instanceof Object; // true
 Array.isArray()
功能:用来判断对象是否为数组
instanceof 与 isArray
当检测 Array 实例时,Array.isArray 优于 instanceof ,因为 Array.isArray 可以
检测出 iframes
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);xArray =
window.frames[window.frames.length-1].Array;
var arr = new xArray(1,2,3);
// [1,2,3]// Correctly checking for ArrayArray.isArray(arr);
// trueObject.prototype.toString.call(arr);
// true
// Considered harmful, because doesn't work though iframesarr instanceof
Array;
// false
Array.isArray() 与 Object.prototype.toString.call()
Array.isArray()是 ES5 新增的方法,当不存在 Array.isArray() ,可以用
Object.prototype.toString.call() 实现。
if (!Array.isArray) { Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}

第 22 题:介绍下重绘和回流(Repaint & Reflow),以及如何

进行优化

1. 浏览器渲染机制

浏览器采用流式布局模型(Flow Based Layout)

浏览器会把 HTML 解析成 DOM,把 CSS 解析成 CSSOM,DOM 和 CSSOM 合并就 产生了渲染树(Render Tree)。

有了 RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大 小和位置,最后把节点绘制到页面上。

由于浏览器使用流式布局,对 Render Tree 的计算通常只需要遍历一次就可以完 成,但 table 及其内部元素除外,他们可能需要多次计算,通常要花 3 倍于同 等元素的时间,这也是为什么要避免使用 table 布局的原因之一。

2. 重绘

由于节点的几何属性发生改变或者由于样式发生改变而不会影响布局的,称为 重绘,例如 outline, visibility, color、background-color 等,重绘的代价是高昂的, 因为浏览器必须验证 DOM 树上其他节点元素的可见性。

3. 回流

回流是布局或者几何属性需要改变就称为回流。回流是影响浏览器性能的关键因素,因为其变化涉及到部分页面(或是整个页面)的布局更新。一个元素的回流可能会导致了其所有子元素以及 DOM 中紧随其后的节点、祖先节点元素的随后的回流。

<body><div class="error">
<h4>我的组件</h4>
<p><strong>错误:</strong>错误的描述…</p>
<h5>错误纠正</h5>
<ol>
<li>第一步</li>
<li>第二步</li>
</ol></div></body>

在上面的 HTML 片段中,对该段落(<p>标签)回流将会引发强烈的回流,因为它是一个子节点。这也导致了祖先的回流(div.error 和 body – 视浏览器而定)。

此外,<h5>和<ol>也会有简单的回流,因为其在 DOM 中在回流元素之后。大部分的回流将导致页面的重新渲染。

回流必定会发生重绘,重绘不一定会引发回流。

4. 浏览器优化

现代浏览器大多都是通过队列机制来批量更新布局,浏览器会把修改操作放在队列中,至少一个浏览器刷新(即 16.6ms)才会清空队列,但当你获取布局信息的时候,队列中可能有会影响这些属性或方法返回值的操作,即使没有,浏览器也会强制清空队列,触发回流与重绘来确保返回正确的值。

主要包括以下属性或方法:

1、offsetTop、offsetLeft、offsetWidth、offsetHeight

2、scrollTop、scrollLeft、scrollWidth、scrollHeight

3、clientTop、clientLeft、clientWidth、clientHeight

4、width、height

5、getComputedStyle()

6、getBoundingClientRect()

所以,我们应该避免频繁的使用上述的属性,他们都会强制渲染刷新队列。

5. 减少重绘与回流

CSS

1、使用 transform 替代 top

2、使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局

3、避免使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局。

4、尽可能在 DOM 树的最末端改变 class,回流是不可避免的,但可以减少其影响。尽可能在 DOM 树的最末端改变 class,可以限制了回流的范围,使其影响尽可能少的节点。

5、避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。

<div>
<a> <span></span> </a></div><style>
span {
color: red;
} div > a > span {
color: red;
}</style>

对于第一种设置样式的方式来说,浏览器只需要找到页面中所有的 span 标签然后设置颜色,但是对于第二种设置样式的方式来说,浏览器首先需要找到所有的 span 标签,然后找到 span 标签上的 a 标签,最后再去找到 div 标签,然后给符合这种条件的 span 标签设置颜色,这样的递归过程就很复杂。所以我们应该尽可能的避免写过于具体的 CSS 选择器,然后对于 HTML 来说也尽量少的添加无意义标签,保证层级扁平。

将动画效果应用到 position 属性为 absolute 或 fixed 的元素上,避免影响其他元素的布局,这样只是一个重绘,而不是回流,同时,控制动画速度可以选择 requestAnimationFrame,详见探讨 requestAnimationFrame。

避免使用 CSS 表达式,可能会引发回流。将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点,例如 will-change、video、iframe 等标签,浏览器会自动将该节点变为图层。

CSS3 硬件加速(GPU 加速),使用 css3 硬件加速,可以让 transform、opacity、filters 这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color 这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。

JavaScript

1、避免频繁操作样式,最好一次性重写 style 属性,或者将样式列表定义为 class并一次性更改 class 属性。

2、避免频繁操作 DOM,创建一个 documentFragment,在它上面应用所有 DOM 操作,最后再把它添加到文档中。

3、避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。

4、对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

第 23 题:介绍下观察者模式和订阅-发布模式的区别,各自适 用于什么场景

观察者模式中主体和观察者是互相感知的,发布-订阅模式是借助第三方来实现调度的,发布者和订阅者之间互不感知

第 24 题:聊聊 Redux 和 Vuex 的设计思想

不管是 Vue,还是 React,都需要管理状态(state),比如组件之间都有共享 状态的需要。什么是共享状态?比如一个组件需要使用另一个组件的状态,或者一个组件需要改变另一个组件的状态,都是共享状态。

父子组件之间,兄弟组件之间共享状态,往往需要写很多没有必要的代码,比如把状态提升到父组件里,或者给兄弟组件写一个父组件,听听就觉得挺啰嗦。

如果不对状态进行有效的管理,状态在什么时候,由于什么原因,如何变化就会不受控制,就很难跟踪和测试了。如果没有经历过这方面的困扰,可以简单理解为会搞得很乱就对了。

在软件开发里,有些通用的思想,比如隔离变化,约定优于配置等,隔离变化就是说做好抽象,把一些容易变化的地方找到共性,隔离出来,不要去影响其他的代码。约定优于配置就是很多东西我们不一定要写一大堆的配置,比如我们几个人约定,view 文件夹里只能放视图,不能放过滤器,过滤器必须放到filter 文件夹里,那这就是一种约定,约定好之后,我们就不用写一大堆配置文件了,我们要找所有的视图,直接从 view 文件夹里找就行。

根据这些思想,对于状态管理的解决思路就是:把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测。根据这个思路,产生了很多的模式和库,我们来挨个聊聊。

第 25 题:说说浏览器和 Node 事件循环的区别

其中一个主要的区别在于浏览器的 event loop 和 nodejs 的 event loop 在处理异步事件的顺序是不同的,nodejs 中有 micro event;其中 Promise 属于 micro event 该异步事件的处理顺序就和浏览器不同.nodejs V11.0 以上 这两者之间的顺序就相同了

function test () {
console.log('start')
setTimeout(() => {
console.log('children2')
Promise.resolve().then(() =>
{console.log('children2-1')})
}, 0)
setTimeout(() => {
console.log('children3')
Promise.resolve().then(() =>
{console.log('children3-1')})
}, 0)
Promise.resolve().then(() =>
{console.log('children1')})
console.log('end')
}test()// 以上代码在 node11 以下版本的执行结果(先执行所有的宏任
务,再执行微任务)// start// end// children1// children2// children3//
children2-1// children3-1// 以上代码在 node11 及浏览器的执行结果(顺序执
行宏任务和微任务)// start// end// children1// children2// children2-1//
children3// children3-1

第 26 题:介绍模块化发展历程

可从 IIFE、AMD、CMD、CommonJS、UMD、webpack(require.ensure)、ES Module、 <script type="module"> 这几个角度考虑。

https://blog.csdn.net/dadadeganhuo/article/details/86777249

第 27 题:全局作用域中,用 const 和 let 声明的变量不在 window 上,那到底在哪里?如何去获取?。

在 ES5 中,顶层对象的属性和全局变量是等价的,var 命令和 function 命令声明的全局变量,自然也是顶层对象。

var a = 12;
function f(){};
console.log(window.a);
// 12console.log(window.f);
// f(){}

但 ES6 规定,var 命令和 function 命令声明的全局变量,依旧是顶层对象的属性,但 let 命令、const 命令、class 命令声明的全局变量,不属于顶层对象的属性。

let aa = 1;
const bb = 2;
console.log(window.aa);
// undefinedconsole.log(window.bb);
// undefined

在哪里?怎么获取?通过在设置断点,看看浏览器是怎么处理的:

通过上图也可以看到,在全局作用域中,用 let 和 const 声明的全局变量并没有在全局对象中,只是一个块级作用域(Script)中 怎么获取?在定义变量的块级作用域中就能获取啊,既然不属于顶层对象,那 就不加 window(global)呗。

let aa = 1;
const bb = 2;
console.log(aa);
// 1console.log(bb);
// 2

第 28 题:cookie 和 token 都存放在 header 中,为什么不会 劫持 token?

1、攻击者通过 xss 拿到用户的 cookie 然后就可以伪造 cookie 了。

2、或者通过 csrf 在同个浏览器下面通过浏览器会自动带上 cookie 的特性

在通过 用户网站-攻击者网站-攻击者请求用户网站的方式 浏览器会自动带上 cookie

但是 token

1、不会被浏览器带上 问题 2 解决

2、token 是放在 jwt 里面下发给客户端的 而且不一定存储在哪里 不能通过 document.cookie 直接拿到,通过 jwt+ip 的方式 可以防止 被劫持 即使被劫持 也是无效的 jwt

第 29 题:聊聊 Vue 的双向数据绑定,Model 如何改变 View,

View 又是如何改变 Model 的

1、从 M 到 V 的映射(Data Binding),这样可以大量节省你人肉来 update View 的代码

2、从 V 到 M 的事件监听(DOM Listeners),这样你的 Model 会随着 View 触发事件而改变

第 30 题:两个数组合并成一个数组

请把两个数组 ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2'] 和 ['A', 'B', 'C', 'D'],合并 为 ['A1', 'A2', 'A', 'B1', 'B2', 'B', 'C1', 'C2', 'C', 'D1', 'D2', 'D']。

function concatArr (arr1, arr2) { const arr = [...arr1];
let currIndex = 0;
for (let i = 0; i < arr2.length; i++) {
const RE = new RegExp(arr2[i])
while(currIndex < arr.length) {
++currIndex
if (!RE.test(arr[currIndex])) {
arr.splice(currIndex, 0, a2[i])
break;
}
}
}
return arr } var a1 = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1',
'D2']
var a2 = ['A', 'B', 'C', 'D'] const arr = concatArr(a1, a2)
console.log(a1)
// ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2'] console.log(a2)
// ['A', 'B', 'C', 'D'] console.log(arr)
// ['A1', 'A2', 'A', B1', 'B2', 'B', C1', 'C2', 'C', D1', 'D2', 'D']

第 31 题:改造下面的代码,使之输出 0 - 9,写出你能想到的 所有解法。

for (var i = 0;
i< 10; i++){
setTimeout(() => {
console.log(i);
}, 1000)}
答:
// 解法一:for (let i = 0;
i< 10;
i++){
setTimeout(() => {
console.log(i);
}, 1000)}
// 解法二:for (var i = 0;
i< 10; i++){ ((i) => {
setTimeout(() => {
console.log(i);
}, 1000) })(i)}

第 32 题:Virtual DOM 真的比操作原生 DOM 快吗?谈谈你的想法

1. 原生 DOM 操作 vs. 通过框架封装操作。

这是一个性能 vs. 可维护性的取舍。框架的意义在于为你掩盖底层的 DOM 操 作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层 需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。针对任何一 个 benchmark,我都可以写出比任何框架更快的手动优化,但是那有什么意义 呢?在构建一个实际应用的时候,你难道为每一个地方都去做手动优化吗?出 于可维护性的考虑,这显然不可能。框架给你的保证是,你在不需要手动优化 的情况下,我依然可以给你提供过得去的性能。

2. 对 React 的 Virtual DOM 的误解。

React 从来没有说过 “React 比原生操作 DOM 快”。React 的基本思维模式是 每次有变动就整个重新渲染整个应用。如果没有 Virtual DOM,简单来想就是 直接重置 innerHTML。很多人都没有意识到,在一个大型列表所有数据都变了 的情况下,重置 innerHTML 其实是一个还算合理的操作... 真正的问题是在 “全部重新渲染” 的思维模式下,即使只有一行数据变了,它也需要重置整个 innerHTML,这时候显然就有大量的浪费。

我们可以比较一下 innerHTML vs. Virtual DOM 的重绘性能消耗:

innerHTML: render html string O(template size) + 重新创建所有 DOM 元 素 O(DOM size)

Virtual DOM: render Virtual DOM + diff O(template size) + 必要的 DOM 更新 O(DOM change)

Virtual DOM render + diff 显然比渲染 html 字符串要慢,但是!它依然是纯 js 层面的计算,比起后面的 DOM 操作来说,依然便宜了太多。可以看到, innerHTML 的总计算量不管是 js 计算还是 DOM 操作都是和整个界面的大小 相关,但 Virtual DOM 的计算量里面,只有 js 计算和界面大小相关,DOM 操 作是和数据的变动量相关的。前面说了,和 DOM 操作比起来,js 计算是极其 便宜的。这才是为什么要有 Virtual DOM:它保证了 1)不管你的数据变化多 少,每次重绘的性能都可以接受;2) 你依然可以用类似 innerHTML 的思路去 写你的应用。

3. MVVM vs. Virtual DOM

相比起 React,其他 MVVM 系框架比如 Angular, Knockout 以及 Vue、Avalon 采用的都是数据绑定:通过 Directive/Binding 对象,观察数据变化并保留对实 际 DOM 元素的引用,当有数据变化时进行对应的操作。MVVM 的变化检查是 数据层面的,而 React 的检查是 DOM 结构层面的。

MVVM 的性能也根据变动检测的实现原理有所不同:Angular 的脏检查使得任 何变动都有固定的 O(watcher count) 的代价;Knockout/Vue/Avalon 都采用了依 赖收集,在 js 和 DOM 层面都是 O(change):

脏检查:scope digest O(watcher count) + 必要 DOM 更新 O(DOM change)

依赖收集:重新收集依赖 O(data change) + 必要 DOM 更新 O(DOM change)可以看到,Angular 最不效率的地方在于任何小变动都有的和 watcher 数量相关的性能代价。但是!当所有数据都变了的时候,Angular 其实并不吃 亏。依赖收集在初始化和数据变化的时候都需要重新收集依赖,这个代价在小 量更新的时候几乎可以忽略,但在数据量庞大的时候也会产生一定的消耗。

MVVM 渲染列表的时候,由于每一行都有自己的数据作用域,所以通常都是每 一行有一个对应的 ViewModel 实例,或者是一个稍微轻量一些的利用原型继 承的 "scope" 对象,但也有一定的代价。所以,MVVM 列表渲染的初始化几乎 一定比 React 慢,因为创建 ViewModel / scope 实例比起 Virtual DOM 来说要 昂贵很多。

这里所有 MVVM 实现的一个共同问题就是在列表渲染的数据源变动时,尤其 是当数据是全新的对象时,如何有效地复用已经创建的 ViewModel 实例和 DOM 元素。

假如没有任何复用方面的优化,由于数据是 “全新” 的,MVVM 实际上需要销 毁之前的所有实例,重新创建所有实例,最后再进行一次渲染!这就是为什么 题目里链接的 angular/knockout 实现都相对比较慢。相比之下,React 的变动 检查由于是 DOM 结构层面的,即使是全新的数据,只要最后渲染结果没变, 那么就不需要做无用功。

Angular 和 Vue 都提供了列表重绘的优化机制,也就是 “提示” 框架如何有效 地复用实例和 DOM 元素。比如数据库里的同一个对象,在两次前端 API 调用 里面会成为不同的对象,但是它们依然有一样的 uid。这时候你就可以提示 track by uid 来让 Angular 知道,这两个对象其实是同一份数据。那么原来这份 数据对应的实例和 DOM 元素都可以复用,只需要更新变动了的部分。或者, 你也可以直接 track by $index 来进行 “原地复用”:直接根据在数组里的位置 进行复用。在题目给出的例子里,如果 angular 实现加上 track by $index 的话, 后续重绘是不会比 React 慢多少的。甚至在 dbmonster 测试中,Angular 和 Vue 用了 track by $index 以后都比 React 快: dbmon (注意 Angular 默认版本 无优化,优化过的在下面)

顺道说一句,React 渲染列表的时候也需要提供 key 这个特殊 prop,本质上 和 track-by 是一回事。

4. 性能比较也要看场合

在比较性能的时候,要分清楚初始渲染、小量数据更新、大量数据更新这些不 同的场合。Virtual DOM、脏检查 MVVM、数据收集 MVVM 在不同场合各有不 同的表现和不同的优化需求。Virtual DOM 为了提升小量数据更新时的性能, 也需要针对性的优化,比如 shouldComponentUpdate 或是 immutable data。

初始渲染:Virtual DOM > 脏检查 >= 依赖收集

小量数据更新:依赖收集 >> Virtual DOM + 优化 > 脏检查(无法优化) >

Virtual DOM 无优化

大量数据更新:脏检查 + 优化 >= 依赖收集 + 优化 > Virtual DOM(无 法/无需优化)>> MVVM 无优化

不要天真地以为 Virtual DOM 就是快,diff 不是免费的,batching 么 MVVM 也 能做,而且最终 patch 的时候还不是要用原生 API。在我看来 Virtual DOM 真 正的价值从来都不是性能,而是它 1) 为函数式的 UI 编程方式打开了大门;2) 可以渲染到 DOM 以外的 backend,比如 ReactNative。

5. 总结

以上这些比较,更多的是对于框架开发研究者提供一些参考。主流的框架 + 合 理的优化,足以应对绝大部分应用的性能需求。如果是对性能有极致需求的特 殊情况,其实应该牺牲一些可维护性采取手动优化:比如 Atom 编辑器在文件 渲染的实现上放弃了 React 而采用了自己实现的 tile-based rendering;又比如 在移动端需要 DOM-pooling 的虚拟滚动,不需要考虑顺序变化,可以绕过框架 的内置实现自己搞一个。

第 33 题:下面的代码打印什么内容,为什么?

var b = 10;
(function b(){
b = 20;
console.log(b);
})();
答:
ƒ b(){
b = 20;
console.log(b);
}

首先函数声明比变量要高,其次 b = 20 没有 var 获取其他,说明是 window 最 外层定义的变量。

js 作用域中,先找最近的 那就是 b fn ,直接打印了,如果 b = 20 有 var 那就 是打印 20

第 34 题:简单改造下面的代码,使之分别打印 10 和 20。

var b = 10;
(function b(){
b = 20;
console.log(b);
})();
答:
var b = 10;
(function (){
b = 20;
console.log(b);
// 20})();
var b = 10;
(function (){
console.log(b);
// 10 b = 20;
})();

第 35 题:浏览器缓存读取规则

可以分成 Service Worker、Memory Cache、Disk Cache 和 Push Cache,那请求 的时候 from memory cache 和 from disk cache 的依据是什么,哪些数据什么 时候存放在 Memory Cache 和 Disk Cache 中?

https://www.jianshu.com/p/54cc04190252

第 36 题:使用迭代的方式实现 flatten 函数。

var arr=[1,2,3,[4,5],[6,[7,[8]]]]/** * 使用递归的方式处理 * wrap 内保
存结果 ret * 返回一个递归函数 * * @returns */function wrap(){
var ret=[];
return function flat(a){
for(var item of
a){ if(item.constructor===Array){
ret.concat(flat(item))
}else{
ret.push(item)
}
}
return ret
}}console.log(wrap()(arr));

第 37 题:为什么 Vuex 的 mutation 和 Redux 的 reducer 中 不能做异步操作?

Mutation 必须是同步函数一条重要的原则就是要记住 mutation 必须是同步 函数。为什么?请参考下面的例子:

mutations: { someMutation (state) { api.callAsyncMethod(() =>
{ state.count++ }) }}

现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。 每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。 然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因 为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时 候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是 不可追踪的。 在组件中提交 Mutation 你可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。

import { mapMutations } from 'vuex'export default {
// ... methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为
`this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为
`this.$store.commit('incrementBy',
amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为
`this.$store.commit('increment')`
})
}
}

第 38 题:(京东)下面代码中 a 在什么情况下会打印 1?

var a = ?;
if(a == 1 && a == 2 && a == 3){
console.log(1);
}
答:
var a = {
i: 1,
toString() {
return a.i++;
}}if( a == 1 && a == 2 && a == 3 ) {
console.log(1);
}let a = [1,2,3];
a.toString = a.shift;if( a == 1 && a == 2 && a == 3 ) {
console.log(1);}

第 39 题:介绍下 BFC 及其应用。

BFC 就是块级格式上下文,是页面盒模型布局中的一种 CSS 渲染模式,相当于 一个独立的容器,里面的元素和外部的元素相互不影响。

创建 BFC 的方式有:

html 根元素

float 浮动

绝对定位

overflow 不为 visiable

display 为表格布局或者弹性布局

BFC 主要的作用是:

清除浮动

防止同一 BFC 容器中的相邻元素间的外边距重叠问题

第 40 题:在 Vue 中,子组件为何不可以修改父组件传递的 Prop

如果修改了,Vue 是如何监控到属性的修改并给出警告的。

1、子组件为何不可以修改父组件传递的 Prop 单向数据流,易于监测数据的流 动,出现了错误可以更加迅速的定位到错误发生的位置。

2、如果修改了,Vue 是如何监控到属性的修改并给出警告的。

if (process.env.NODE_ENV !== 'production') {
var hyphenatedKey = hyphenate(key);
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
("\"" + hyphenatedKey + "\" is a reserved attribute and cannot
be used as component prop."),
Vm
);
}
defineReactive$$1(props, key, value, function () {
if (!isRoot && !isUpdatingChildComponent)
{
warn(
"Avoid mutating a prop directly since the value will be " +
"overwritten whenever the parent component re-renders. " +
"Instead, use a data or computed property based on the prop's " +
"value. Prop being mutated: \"" + key + "\"",
Vm
);
}
});
}

在 initProps 的时候,在 defineReactive 时通过判断是否在开发环境,如果是开 发环境,会在触发 set 的时候判断是否此 key 是否处于 updatingChildren 中被修 改,如果不是,说明此修改来自子组件,触发 warning 提示。

需要特别注意的是,当你从子组件修改的 prop 属于基础类型时会触发提示。这 种情况下,你是无法修改父组件的数据源的, 因为基础类型赋值时是值拷贝。 你直接将另一个非基础类型(Object, array)赋值到此 key 时也会触发提示(但实 际上不会影响父组件的数据源), 当你修改 object 的属性时不会触发提示,并 且会修改父组件数据源的数据。

第 41 题:下面代码输出什么

var a = 10;(function () {
console.log(a)
a = 5
console.log(window.a)
var a = 20;
console.log(a)})()

分别为 undefined 10 20,原因是作用域问题,在内部声名 var a = 20;相当于 先声明 var a;然后再执行赋值操作,这是在IIFE内形成的独立作用域,如果 把 var a=20 注释掉,那么 a 只有在外部有声明,显示的就是外部的A变量的值 了。结果A会是 10 5 5

第 42 题:实现一个 sleep 函数

比如 sleep(1000) 意味着等待 1000 毫秒,可从 Promise、Generator、Async/Await 等角度实现

const sleep = (time) => {
return new Promise(resolve => setTimeout(resolve,
time))}sleep(1000).then(() => {
// 这里写你的骚操作})

第 43 题:使用 sort() 对数组 [3, 15, 8, 29, 102, 22] 进 行排序,输出结果

输出:[102, 15, 22, 29, 3, 8]

解析:根据 MDN 上对 Array.sort()的解释,默认的排序方法会将数组元素转换 为字符串,然后比较字符串中字符的 UTF-16 编码顺序来进行排序。所以'102' 会 排在 '15' 前面。

第 44 题:介绍 HTTPS 握手过程

1、clientHello

2、SeverHello

3、客户端回应

4、服务器的最后回应

第 45 题:HTTPS 握手过程中,客户端如何验证证书的合法性

1. 校验证书的颁发机构是否受客户端信任。

2. 通过 CRL 或 OCSP 的方式校验证书是否被吊销。

3 对比系统时间,校验证书是否在有效期内。

4. 通过校验对方是否存在证书的私钥,判断证书的网站域名是否与证书颁发的域名一致。

第 46 题:输出以下代码执行的结果并解释为什么

var obj = {
'2': 3,
'3': 4,
'length': 2,
'splice': Array.prototype.splice,
'push':
Array.prototype.push}obj.push(1)obj.push(2)console.log(obj)
结果:[,,1,2], length 为 4
伪数组(ArrayLike)

第 47 题:双向绑定和 vuex 是否冲突

在严格模式下直接使用确实会有问题。解决方案:
<input v-model="message" />computed: {
message: {
set (value)
{
this.$store.dispatch('updateMessage', value);
},
get () {
Return
this.$store.state.obj.message
}
}}mutations: {
UPDATE_MESSAGE (state, v) {
state.obj.message = v;
}}actions: {
update_message ({ commit }, v) {
commit('UPDATE_MESSAGE', v);
}
}

第 48 题:call 和 apply 的区别是什么,哪个性能更好一些

1. Function.prototype.apply 和 Function.prototype.call 的作用是一样的,区 别在于传入参数的不同;

2. 第一个参数都是,指定函数体内 this 的指向;

3 第二个参数开始不同,apply 是传入带下标的集合,数组或者类数组, apply 把它传给函数作为参数,call 从第二个开始传入的参数是不固定的,都会传给函数作为参数。

4. call 比 apply 的性能要好,平常可以多用 call, call 传入参数的格式正是内部所需要的格式

第 49 题:为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 gif 图片?

1. 没有跨域问题,一般这种上报数据,代码要写通用的;(排除 ajax)

2. 不会阻塞页面加载,影响用户的体验,只要 new Image 对象就好了;(排除 JS/CSS 文件资源方式上报)

3. 在所有图片中,体积最小;(比较 PNG/JPG)

第 50 题:(百度)实现 (5).add(3).minus(2) 功能。

例: 5 + 3 - 2,结果为 6

答:Number.prototype.add = function(n)
{ return this.valueOf() + n;
};Number.prototype.minus = function(n) {
return this.valueOf() - n;
};

第 51 题:Vue 的响应式原理中 Object.defineProperty 有什么缺陷?

为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?

Object.defineProperty 无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应;

Object.defineProperty 只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。Proxy 可以劫持整个对象,并返回一个新的对象。

Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

第 52 题:怎么让一个 div 水平垂直居中

<div class="parent">
<div class="child"></div></div> 一、
div.parent {
display: flex;
justify-content: center;
align-items: center;
}
二、
div.parent {
position: relative;
}div.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}/* 或者 */div.child {
width: 50px;
height: 10px;
position: absolute;
top: 50%;
left: 50%;
margin-left: -25px;
margin-top: -5px;}/* 或 */div.child {
width: 50px;
height: 10px;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
}
三、
div.parent {
display: grid;
}div.child {
justify-self: center;
align-self: center;
}
四、
div.parent {
font-size: 0;
text-align: center;
&::before {
content: "";
display: inline-block;
width: 0;
height: 100%;
vertical-align: middle;
}}div.child{
display: inline-block;
vertical-align: middle;
}

第 53 题:输出以下代码的执行结果并解释为什么

var a = {n: 1};
var b = a;a.x = a = {n: 2};
console.log(a.x)
console.log(b.x)

结果:undefined{n:2}

首先,a 和 b 同时引用了{n:2}对象,接着执行到 a.x = a = {n:2}语句,尽管赋值 是从右到左的没错,但是.的优先级比=要高,所以这里首先执行 a.x,相当于为 a(或者 b)所指向的{n:1}对象新增了一个属性 x,即此时对象将变为 {n:1;x:undefined}。之后按正常情况,从右到左进行赋值,此时执行 a ={n:2}的时 候,a 的引用改变,指向了新对象{n:2},而 b 依然指向的是旧对象。之后执行 a.x = {n:2}的时候,并不会重新解析一遍 a,而是沿用最初解析 a.x 时候的 a, 也即旧对象,故此时旧对象的 x 的值为{n:2},旧对象为 {n:1;x:{n:2}},它被 b 引用着。后面输出 a.x 的时候,又要解析 a 了,此时的 a 是指向新对象的 a,而 这个新对象是没有 x 属性的,故访问时输出 undefined;而访问 b.x 的时候,将 输出旧对象的 x 的值,即{n:2}。

第 54 题:冒泡排序如何实现,时间复杂度是多少, 还可以如何改进?

冒泡算法的原理:

升序冒泡: 两次循环,相邻元素两两比较,如果前面的大于后面的就交换位置

降序冒泡: 两次循环,相邻元素两两比较,如果前面的小于后面的就交换位置

js 实现:
// 升序冒泡 function maopao(arr){
const array = [...arr] for(let i = 0, len = array.length; i < len -
1; i++){
for(let j = i + 1; j < len; j++) {
if (array[i] > array[j]) {
let temp = array[i]
array[i] = array[j]
array[j] = temp
}
}
}
return array
}

看起来没问题,不过一般生产环境都不用这个,原因是效率低下,冒泡排序在 平均和最坏情况下的时间复杂度都是 O(n^2),最好情况下都是 O(n),空间复 杂度是 O(1)。因为就算你给一个已经排好序的数组,如[1,2,3,4,5,6] 它也会走 一遍流程,白白浪费资源。所以有没有什么好的解决方法呢? 答案是肯定有的:加个标识,如果已经排好序了就直接跳出循环。

优化版:
function maopao(arr){
const array = [...arr]
let isOk = true for(let i = 0, len = array.length;
i < len - 1; i++){
for(let j = i + 1; j < len; j++) {
if (array[i] > array[j]) {
let temp = array[i]
array[i] = array[j]
array[j] = temp
isOk = false
}
}
if(isOk){
Break
}
}
return array}
测试: 数组:[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
从测试结果来看: 普通冒泡排序

时间:0.044ms 优化后冒泡排序时间:0.018ms

第 55 题:某公司 1 到 12 月份的销售额存在一个对象里面

如下:{1:222, 2:123, 5:888},请把数据处理为如下结构:[222, 123, null, null, 888, null, null, null, null, null, null, null]。

let obj = {1:222, 2:123, 5:888};
const result = Array.from({ length: 12 }).map((_, index) => obj[index +
1] || null);
console.log(result)

 第 56 题:要求设计 LazyMan 类,实现以下功能。

LazyMan('Tony');
// Hi I am Tony
LazyMan('Tony').sleep(10).eat('lunch');
// Hi I am Tony
// 等待了 10 秒...
// I am eating
lunchLazyMan('Tony').eat('lunch').sleep(10).eat('dinner');
// Hi I am Tony
// I am eating lunch// 等待了 10 秒...
// I am eating
dinerLazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(5).sleep(1
0).eat('junk food');
// Hi I am Tony// 等待了 5 秒...
// I am eating lunch
// I am eating dinner
// 等待了 10 秒...
// I am eating junk food
答:
class LazyManClass {
constructor(name) {
this.name = name
this.queue = []
console.log(`Hi I am ${name}`)
setTimeout(() => {
this.next()
},0)
}
sleepFirst(time) {
const fn = () => {
setTimeout(() => {
console.log(`等待了${time}秒...`)
this.next()
}, time)
}
this.queue.unshift(fn)
return this
}
sleep(time) {
const fn = () => {
setTimeout(() => {
console.log(`等待了${time}秒...`)
this.next()
},time)
}
this.queue.push(fn)
return this
}
eat(food) {
const fn = () => {
console.log(`I am eating ${food}`)
this.next()
}
this.queue.push(fn)
return this
}
next() {
const fn = this.queue.shift()
fn && fn() }}function LazyMan(name) {
return new LazyManClass(name)}

第 57 题:分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场景。

display: none (不占空间,不能点击)(场景,显示出原来这里不存在的结构)

visibility: hidden(占据空间,不能点击)(场景:显示不会导致页面结构发生变动,不会撑开)

opacity: 0(占据空间,可以点击)(场景:可以跟 transition 搭配)

第 58 题:箭头函数与普通函数(function)的区别是什么?构 造函数(function)可以使用 new 生成实例,那么箭头函数可以吗?为什么?

箭头函数是普通函数的简写,可以更优雅的定义一个函数,和普通函数相比,

有以下几点差异:

函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。

不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。

不可以使用 new 命令,因为:

1.没有自己的 this,无法调用 call,apply。

2.没有 prototype 属性 ,而 new 命令在执行时需要将构造函数 的 prototype 赋值给新的对象的 __proto__

new 过程大致是这样的:
function newFunc(father, ...rest) {
var result = {};
result.__proto__ = father.prototype;
var result2 = father.apply(result, rest);
if (
(typeof result2 === 'object' || typeof result2 === 'function') &&
result2 !== null
) {
return result2;
}
return result;
}

第 59 题:给定两个数组,写一个方法来计算它们的交集。

例如:给定 nums1 = [1, 2, 2, 1],nums2 = [2, 2],返回 [2, 2]。
var nums1 = [1, 2, 2, 1], nums2 = [2, 2, 3, 4];
// 1.
// 有个问题,
[NaN].indexOf(NaN) === -1var newArr1 = nums1.filter(function(item) {
return nums2.indexOf(item) > -1;
});
console.log(newArr1);
// 2.
var newArr2 = nums1.filter((item) => {
return nums2.includes(item);
});
console.log(newArr2);

第 60 题:已知如下代码,如何修改才能让图片宽度为 300px ? 注意下面代码不可修改。

<img src="1.jpg" style="width:480px!important;”>
答:
max-width: 300pxtransform: scale(0.625,0.625)

结尾:

写的不少了,后面还有一堆实在写不动了,如果看着合适可以扣我【点击此处】备注来源,直接分享,最后希望小伙伴们可以拿到offer,然后事业有成,加油!!!

  • 18
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值