字节大神前端面试
目录
第 1 题:写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?. 6
第 2 题:['1', '2', '3'].map(parseInt) what & why ?.....................................................6
第 3 题:什么是防抖和节流?有什么区别?如何实现?................................................ 7
第 4 题:介绍下 Set、Map、WeakSet 和 WeakMap 的区别?............................. 7
第 5 题:介绍下深度优先遍历和广度优先遍历,如何实现?........................................ 8
第 6 题:请分别用深度优先思想和广度优先思想实现一个拷贝函数?........................ 9
第 7 题:ES5/ES6 的继承除了写法以外还有什么区别?...............................................9
第 8 题:setTimeout、Promise、Async/Await 的区别........................................... 12
第 9 题:Async/Await 如何通过同步的方式实现异步................................................12
第 10 题:异步笔试题请写出下面代码的运行结果....................................................... 12
第 11 题:算法手写题....................................................................................................... 13
第 12 题:JS 异步解决方案的发展历程以及优缺点。..................................................13
第 13 题:Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?..... 13
第 14 题:情人节福利题,如何实现一个 new..............................................................13
第 15 题:简单讲解一下 http2 的多路复用................................................................... 13
第 16 题:谈谈你对 TCP 三次握手和四次挥手的理......................................................15
第 17 题:A、B 机器正常连接后,B 机器突然重启,问 A 此时处于 TCP 什么状态
................................................................................................................................................ 17
第 18 题:React 中 setState 什么时候是同步的,什么时候是异步的?............... 17
第 19 题:React setState 笔试题,下面的代码输出什么?...................................... 17
第 20 题:介绍下 npm 模块安装机制,为什么输入 npm install 就可以自动安装对
应的模块?1. npm 模块安装机制:.................................................................................18第 21 题:有以下 3 个判断数组的方法,请分别介绍它们之间的区别和优劣......... 18
第 22 题:介绍下重绘和回流(Repaint & Reflow),以及如何进行优化..............20
第 23 题:介绍下观察者模式和订阅-发布模式的区别,各自适用于什么场景..........22
第 24 题:聊聊 Redux 和 Vuex 的设计思想..............................................................24
第 25 题:说说浏览器和 Node 事件循环的区别.........................................................24
第 26 题:介绍模块化发展历程....................................................................................... 25
第 27 题:全局作用域中,用 const 和 let 声明的变量不在 window 上,那到底在
哪里?如何去获取?。........................................................................................................ 25
第 28 题:cookie 和 token 都存放在 header 中,为什么不会劫持 token?....27
第 29 题:聊聊 Vue 的双向数据绑定,Model 如何改变 View,View 又是如何改
变 Model 的........................................................................................................................27
第 30 题:两个数组合并成一个数组............................................................................... 27
第 31 题:改造下面的代码,使之输出 0 - 9,写出你能想到的所有解法。..............28
第 32 题:Virtual DOM 真的比操作原生 DOM 快吗?谈谈你的想法。............... 28
第 33 题:下面的代码打印什么内容,为什么?........................................................... 31
第 34 题:简单改造下面的代码,使之分别打印 10 和 20。....................................31
第 35 题:浏览器缓存读取规则....................................................................................... 32
第 36 题:使用迭代的方式实现 flatten 函数。...........................................................32
第 37 题:为什么 Vuex 的 mutation 和 Redux 的 reducer 中不能做异步操作?
................................................................................................................................................ 33
第 38 题:(京东)下面代码中 a 在什么情况下会打印 1?.....................................34
第 39 题:介绍下 BFC 及其应用。................................................................................ 34
第 40 题:在 Vue 中,子组件为何不可以修改父组件传递的 Prop.........................35
第 41 题:下面代码输出什么........................................................................................... 35
第 42 题:实现一个 sleep 函数..................................................................................... 36
第 43 题:使用 sort() 对数组 [3, 15, 8, 29, 102, 22] 进行排序,输出结果.......... 36
第 44 题:介绍 HTTPS 握手过程...................................................................................36
第 45 题:HTTPS 握手过程中,客户端如何验证证书的合法性.................................37
第 46 题:输出以下代码执行的结果并解释为什么....................................................... 37
第 47 题:双向绑定和 vuex 是否冲突...........................................................................37
第 48 题:call 和 apply 的区别是什么,哪个性能更好一些.....................................38
第 49 题:为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 gif 图
片?........................................................................................................................................ 38
第 50 题:(百度)实现 (5).add(3).minus(2) 功能。................................................39
第 51 题:Vue 的响应式原理中 Object.defineProperty 有什么缺陷?................39
第 52 题:怎么让一个 div 水平垂直居中......................................................................39
第 53 题:输出以下代码的执行结果并解释为什么....................................................... 41
第 54 题:冒泡排序如何实现,时间复杂度是多少, 还可以如何改进?................. 41
第 55 题:某公司 1 到 12 月份的销售额存在一个对象里面.................................... 47
第 56 题:要求设计 LazyMan 类,实现以下功能。...................................................47
第 57 题:分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场
景。........................................................................................................................................ 48
第 58 题:箭头函数与普通函数(function)的区别是什么?构造函数(function)
可以使用 new 生成实例,那么箭头函数可以吗?为什么?........................................ 49
第 59 题:给定两个数组,写一个方法来计算它们的交集。....................................... 50
第 60 题:已知如下代码,如何修改才能让图片宽度为 300px ?注意下面代码不可
修改。.................................................................................................................................... 50
第 61 题:介绍下如何实现 token 加密.........................................................................50
第 62 题:redux 为什么要把 reducer 设计成纯函数................................................51
第 63 题:如何设计实现无缝轮播................................................................................... 51
第 64 题:模拟实现一个 Promise.finally......................................................................52
第 65 题: a.b.c.d 和 a['b']['c']['d'],哪个性能更高?..............................................52
第 66 题:ES6 代码转成 ES5 代码的实现思路是什么................................................54
第 67 题:数组编程题....................................................................................................... 54
第 68 题: 如何解决移动端 Retina 屏 1px 像素问题.............................................. 55
第 69 题: 如何把一个字符串的大小写取反(大写变小写小写变大写),例如 ’AbC'
变成 'aBc' 。....................................................................................................................... 55
第 70 题: 介绍下 webpack 热更新原理,是如何做到在不刷新浏览器的前提下更
新页面的................................................................................................................................ 56
第 71 题: 实现一个字符串匹配算法,从长度为 n 的字符串 S 中,查找是否存在
字符串 T,T 的长度是 m,若存在返回所在位置。......................................................56
第 72 题: 为什么普通 for 循环的性能远远高于 forEach 的性能,请解释其中的原
因。........................................................................................................................................ 56
第 73 题: 介绍下 BFC、IFC、GFC 和 FFC............................................................... 58
第 74 题: 使用 JavaScript Proxy 实现简单的数据绑定...........................................59
第 75 题:数组里面有 10 万个数据,取第一个元素和第 10 万个元素的时间相差多少
................................................................................................................................................ 60
第 76 题:输出以下代码运行结果................................................................................... 61
第 77 题:算法题「旋转数组」....................................................................................... 62
第 78 题:Vue 的父组件和子组件生命周期钩子执行顺序是什么..............................62
第 79 题:input 搜索如何防抖,如何处理中文输入................................................... 62
第 80 题:介绍下 Promise.all 使用、原理实现及错误处理.......................................63
第 81 题:打印出 1 - 10000 之间的所有对称数......................................................... 63
第 82 题:周一算法题之「移动零」............................................................................... 63
第 83 题:var、let 和 const 区别的实现原理是什么................................................ 64
第 84 题:请实现一个 add 函数,满足以下功能。.................................................... 65
第 85 题:react-router 里的 <Link> 标签和 <a> 标签有什么区别.....................66
第 86 题:周一算法题之「两数之和」........................................................................... 66
第 87 题:在输入框中如何判断输入的是一个正确的网址。....................................... 67
第 88 题:实现 convert 方法,把原始 list 转换成树形结构,要求尽可能降低时间
复杂度.................................................................................................................................... 67
第 89 题:设计并实现 Promise.race().......................................................................... 69
第 90 题:实现模糊搜索结果的关键词高亮显示........................................................... 70
第 91 题:介绍下 HTTPS 中间人攻击...........................................................................71
第 92 题:已知数据格式,实现一个函数 fn 找出链条中所有的父级 idconst value =
'112'............................................................................................................................72
第 93 题:给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。请找出这两个
有序数组的中位数。要求算法的时间复杂度为 O(log(m+n))。.................................. 73
第 94 题:vue 在 v-for 时给每项元素绑定事件需要用事件代理吗?为什么?..... 74
第 95 题:模拟实现一个深拷贝,并考虑对象相互引用以及 Symbol 拷贝的情况. 75
第 96 题:介绍下前端加密的常见场景和方法............................................................... 76
第 97 题:React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化到 O(n) ,那么
O(n^3) 和 O(n) 是如何计算出来的?............................................................................77
第 98 题:写出如下代码的打印结果............................................................................... 77
第 99 题:编程算法题....................................................................................................... 78
第 100 题:请写出如下代码的打印结果......................................................................... 78
第 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] === getType(item)
}
第 7 题:ES5/ES6 的继承除了写法以外还有什么区别?
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 题:异步笔试题请写出下面代码的运行结果
第 11 题:算法手写题
实现数组扁平化的 6 种方式_从人到猿的博客-CSDN博客_数组扁平
数组的扁平化其实就是将一个嵌套多层的数组 array(嵌套可以是任何层数)转换为只有一层的数组。
数组扁平化这节课的知识点结合了数组 API、ES6,以及 JSON方法的相关知识。可以通过下面的表格再来看一下这六种方式的代码思路。
方法/问题 实现难度 编码思路
递归实现 易 递归实现,返回新数组
reduce实现 中 reduce进行累加操作
扩展运算符实现 中 筛选出数组项进行连接
split和toString 易 转成字符串再转数组
flat方法 易 特定功能方法直接操作
正则和JSON方法 中 JSON方法转成字符串转回过程中正则处理
已知如下数组,编写一个程序将数组扁平化去并除其中重复部分数据,最终得
到一个升序且不重复的数组
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, 1, 2
第 20 题:介绍下 npm 模块安装机制,为什么输入 npm install
就可以自动安装对应的模块?
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)// "[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. 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. 3 第二个参数开始不同,apply 是传入带下标的集合,数组或者类数组,
apply 把它传给函数作为参数,call 从第二个开始传入的参数是不固定的,都会传给函数作为参数。
4. call 比 apply 的性能要好,平常可以多用 call, call 传入参数的格式正是内
部所需要的格式
第 49 题:为什么通常在发送数据埋点请求的时候使用的是 1x1 素的透明 gif 图片?
1. 没有跨域问题,一般这种上报数据,代码要写通用的;(排除 ajax)
2. 不会阻塞页面加载,影响用户的体验,只要 new Image 对象就好了;(排
除 JS/CSS 文件资源方式上报)
- 在所有图片中,体积最小;(比较 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 可以劫持整个对 象,并返回一个新的对象。 8
· 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 命令,因为:
o 1.没有自己的 this,无法调用 call,apply。
o 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: 300px transform: scale(0.625,0.625)
第 61 题:介绍下如何实现 token 加密
jwt 举例:
1. 需要一个 secret(随机数)
2. 后端利用 secret 和加密算法(如:HMAC-SHA256)对 payload(如账号密码) 生成一个字符串(token),返回前端
3. 前端每次 request 在 header 中带上 token
4. 后端用同样的算法解密
第 62 题:redux 为什么要把 reducer 设计成纯函数
redux 的设计思想就是不产生副作用,数据更改的状态可回溯,所以 redux 中处处都是纯函数
第 63 题:如何设计实现无缝轮播
简单来说,无缝轮播的核心是制造一个连续的效果。最简单的方法就是复制一
个轮播的元素,当复制元素将要滚到目标位置后,把原来的元素进行归位的操
作,以达到无缝的轮播效果。
贴一段轮播的核心代码:
// scroll the notice
useEffect(() => {
const requestAnimationFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame
const cancelAnimationFrame =
window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame
const scrollNode = noticeContentEl.current
const distance = scrollNode.clientWidth / 2
scrollNode.style.left = scrollNode.style.left || 0
window.__offset = window.__offset || 0
let requestId = null
const scrollLeft = () => {
const speed = 0.5
window.__offset = window.__offset + speed
scrollNode.style.left = -window.__offset + 'px'
// 关键行:当距离小于偏移量时,重置偏移量
if (distance <= window.__offset) window.__offset = 0
requestId = requestAnimationFrame(scrollLeft)
} requestId = requestAnimationFrame(scrollLeft)
if (pause) cancelAnimationFrame(requestId)
return () => cancelAnimationFrame(requestId)
}, [notice, pause])
第 64 题:模拟实现一个 Promise.finally
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(()
=> { throw reason })
);
};
第 65 题: a.b.c.d 和 a['b']['c']['d'],哪个性能更高?
应该是 a.b.c.d 比 a['b']['c']['d'] 性能高点,后者还要考虑 [ ] 中是变量的情况,
再者,从两种形式的结构来看,显然编译器解析前者要比后者容易些,自然也
就快一点。
第 66 题:ES6 代码转成 ES5 代码的实现思路是什么
ES6 转 ES5 目前行业标配是用 Babel,转换的大致流程如下:
1. 解析:解析代码字符串,生成 AST;
2. 转换:按一定的规则转换、修改 AST;
3. 生成:将修改后的 AST 转换成普通代码。
如果不用工具,纯人工的话,就是使用或自己写各种 polyfill 了。
第 67 题:数组编程题
随机生成一个长度为 10 的整数类型的数组,例如 [2, 10, 3, 4, 5, 11, 10, 11,
20],将其排列成一个新数组,要求新数组形式如下,例如 [[2, 3, 4, 5], [10, 11],
[20]]。
function formArray(arr: any[]) {
const sortedArr = Array.from(new Set(arr)).sort((a, b) => a - b);
const map = new Map();
sortedArr.forEach((v) => {
const key = Math.floor(v / 10);
const group = map.get(key) || [];
group.push(v);
map.set(key, group);
}); return [...map.values()];}// 求连续的版本 function
formArray1(arr: any[]) {
const sortedArr = Array.from(new Set(arr)).sort((a, b) => a - b);
return sortedArr.reduce((acc, cur) => {
const lastArr = acc.slice().pop() || [];
const lastVal = lastArr.slice().pop();
if (lastVal!=null && cur-lastVal === 1)
{
lastArr.push(cur);
} else {
acc.push([cur]);
}
return acc;}, []);}function genNumArray(num: number, base = 100) {
return Array.from({length: num}, () =>
Math.floor(Math.random()*base));
}const arr = genNumArray(10, 20);
//[2, 10, 3, 4, 5, 11, 10, 11, 20];
const res = formArray(arr);console.log(`res
${JSON.stringify(res)}`);
第 68 题: 如何解决移动端 Retina 屏 1px 像素问题
1. 伪元素 + transform scaleY(.5)
2. border-image
3. background-image
4. box-shadow
第 69 题: 如何把一个字符串的大小写取反(大写变小写小写
变大写),例如 ’AbC' 变成 'aBc' 。
function processString (s) {
var arr = s.split('');
var new_arr = arr.map((item) => {
return item === item.toUpperCase() ? item.toLowerCase() :
item.toUpperCase();
});
Return
new_arr.join('');}console.log(processString('AbC'));function
swapString(str) {
var result = ''
for (var i = 0; i < str.length; i++) {
var c = str[i]
if (c === c.toUpperCase()) {
result += c.toLowerCase()
} else {
result += c.toUpperCase()
}}
return result}swapString('ADasfads123!@$!@#') // =>
'adASFADS123!@$!@#'
第 70 题: 介绍下 webpack 热更新原理,是如何做到在不刷新浏览器的前提下更新页面的
1. 当修改了一个或多个文件;
2. 文件系统接收更改并通知 webpack;
3. webpack 重新编译构建一个或多个模块,并通知 HMR 服务器进行更新;
4. HMR Server 使用 webSocket 通知 HMR runtime 需要更新,HMR 运行时
通过 HTTP 请求更新 jsonp;
5. HMR 运行时替换更新中的模块,如果确定这些模块无法更新,则触发整
个页面刷新。
第 71 题: 实现一个字符串匹配算法,从长度为 n 的字符串 S
中,查找是否存在字符串 T,T 的长度是 m,若存在返回所在位
置。
const find = (S, T) => {
if (S.length < T.length) return -1
for (let i = 0; i < S.length; i++) {
if (S.slice(i, i + T.length) === T) return i
}
return -1
第 72 题: 为什么普通 for 循环的性能远远高于 forEach 的
性能,请解释其中的原因。
for 循环没有任何额外的函数调用栈和上下文;
forEach 函数签名实际上是
array.forEach(function(currentValue, index, arr), thisValue)
它不是普通的 for 循环的语法糖,还有诸多参数和上下文需要在执行的时候考
虑进来,这里可能拖慢性能;
第 73 题: 介绍下 BFC、IFC、GFC 和 FFC
BFC(Block formatting contexts):
块级格式上下文页面上的一个隔离的渲染区域,那么他是如何产生的呢?可以
触发 BFC 的元素有 float、position、overflow、display:table-cell/
inline-block/table-caption ;BFC 有什么作用呢?比如说实现多栏布局’
IFC(Inline formatting contexts):
内联格式上下文 IFC 的 line box(线框)高度由其包含行内元素中最高的实际高
度计算而来(不受到竖直方向的 padding/margin 影响)IFC 中的 line box 一般左右
都贴紧整个 IFC,但是会因为 float 元素而扰乱。float 元素会位于 IFC 与与 line box
之间,使得 line box 宽度缩短。 同个 ifc 下的多个 line box 高度会不同 IFC 中时
不可能有块级元素的,当插入块级元素时(如 p 中插入 div)会产生两个匿名块
与 div 分隔开,即产生两个 IFC,每个 IFC 对外表现为块级元素,与 div 垂直排
列。那么 IFC 一般有什么用呢?水平居中:当一个块要在环境中水平居中时,
设置其为 inline-block 则会在外层产生 IFC,通过 text-align 则可以使其水平居中。
垂直居中:创建一个 IFC,用其中一个元素撑开父元素的高度,然后设置其
vertical-align:middle,其他行内元素则可以在此父元素下垂直居中。
GFC(GrideLayout formatting contexts):
网格布局格式化上下文当为一个元素设置 display 值为 grid 的时候,此元素将会
获得一个独立的渲染区域,我们可以通过在网格容器(grid container)上定义
网格定义行(grid definition rows)和网格定义列(grid definition columns)属性
各在网格项目(grid item)上定义网格行(grid row)和网格列(grid columns)
为每一个网格项目(grid item)定义位置和空间。那么 GFC 有什么用呢,和 table
又有什么区别呢?首先同样是一个二维的表格,但 GridLayout 会有更加丰富的
属性来控制行列,控制对齐以及更为精细的渲染语义和控制。
FFC(Flex formatting contexts):
自适应格式上下文 display 值为 flex 或者 inline-flex 的元素将会生成自适应容器
(flex container),可惜这个牛逼的属性只有谷歌和火狐支持,不过在移动端
也足够了,至少 safari 和 chrome 还是 OK 的,毕竟这俩在移动端才是王道。Flex
Box 由伸缩容器和伸缩项目组成。通过设置元素的 display 属性为 flex 或inline-flex 可以得到一个伸缩容器。设置为 flex 的容器被渲染为一个块级元素,
而设置为 inline-flex 的容器则渲染为一个行内元素。伸缩容器中的每一个子元
素都是一个伸缩项目。伸缩项目可以是任意数量的。伸缩容器外和伸缩项目内
的一切元素都不受影响。简单地说,Flexbox 定义了伸缩容器内伸缩项目该如
何布局。
第 74 题: 使用 JavaScript Proxy 实现简单的数据绑定
<div id="app">
<input type="text" id="input">
<div>
TODO:
<span id="text"></span>
</div> <div id="btn">Add To Todo List</div>
<ul id="list"></ul> </div>
const input = document.getElementById('input')
const text = document.getElementById('text')
const list = document.getElementById('list')
const btn = document.getElementById('btn')
let render
const inputObj = new Proxy({}, {
get (target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
if (key === 'text') {
input.value = value
text.innerHTML = value
}
return Reflect.set(target, key, value,
receiver)
}
})
class Render {
constructor (arr) {
this.arr = arr
}
init () {
const fragment = document.createDocumentFragment()
for (let i = 0; i < this.arr.length; i++) {
const li = document.createElement('li')li.textContent = this.arr[i]
fragment.appendChild(li)
}
list.appendChild(fragment)
}
addList (val) {
const li = document.createElement('li')
li.textContent = val
list.appendChild(li)
}
}
const todoList = new Proxy([], {
get (target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
if (key !== 'length')
{
render.addList(value)
}
return Reflect.set(target, key, value,
receiver)
}
})
window.onload = () => {
render = new Render([])
render.init()
}
input.addEventListener('keyup', e => {
inputObj.text = e.target.value
})
btn.addEventListener('click', () =>
{
todoList.push(inputObj.text)
inputObj.text = ''
})
第 75 题:数组里面有 10 万个数据,取第一个元素和第 10 万个
元素的时间相差多少数组可以直接根据索引取的对应的元素,所以不管取哪个位置的元素的时间复
杂度都是 O(1)
得出结论:消耗时间几乎一致,差异可以忽略不计
第 76 题:输出以下代码运行结果
// example 1
var a={}, b='123', c=123;
a[b]='b';a[c]='c';
console.log(a[b]);
---------------------
// example 2var a={}, b=Symbol('123'), c=Symbol('123');
a[b]='b';
a[c]='c';
console.log(a[b]);
---------------------
// example 3var a={}, b={key:'123'}, c={key:'456'};
a[b]='b';a[c]='c';
console.log(a[b]);
答:
1. 对象的键名只能是字符串和 Symbol 类型。
2. 其他类型的键名会被转换成字符串类型。
3. 对象转字符串默认会调用 toString 方法。
// example 1
var a={}, b='123', c=123;a[b]='b';
// c 的键名会被转换成字符串'123',这里会把 b 覆盖掉。a[c]='c';
// 输出 cconsole.log(a[b]);
// example 2var a={}, b=Symbol('123'), c=Symbol('123');
// b 是 Symbol 类型,不需要转换。a[b]='b';
// c 是 Symbol 类型,不需要转换。任何一个 Symbol 类型的值都是不相等的,
所以不会覆盖掉 b。a[c]='c';
// 输出 bconsole.log(a[b]);
// example 3var a={}, b={key:'123'}, c={key:'456'};
// b 不是字符串也不是 Symbol 类型,需要转换成字符串。
// 对象类型会调用 toString 方法转换成字符串 [object Object]。a[b]='b';
// c 不是字符串也不是 Symbol 类型,需要转换成字符串。// 对象类型会调用 toString 方法转换成字符串 [object Object]。这里会把
b 覆盖掉。a[c]='c';
// 输出 cconsole.log(a[b]);
第 77 题:算法题「旋转数组」
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: [1, 2, 3, 4, 5, 6, 7] 和 k = 3 输出: [5, 6, 7, 1, 2, 3, 4] 解释: 向右旋转 1 步:
[7, 1, 2, 3, 4, 5, 6] 向右旋转 2 步: [6, 7, 1, 2, 3, 4, 5] 向右旋转 3 步: [5, 6, 7, 1, 2,
3, 4]
示例 2:
输入: [-1, -100, 3, 99] 和 k = 2 输出: [3, 99, -1, -100] 解释: 向右旋转 1 步: [99,
-1, -100, 3] 向右旋转 2 步: [3, 99, -1, -100]
答:
function rotate(arr, k) {
const len = arr.length const step = k % len return
arr.slice(-step).concat(arr.slice(0, len - step))}// rotate([1, 2, 3, 4,
5, 6], 7) => [6, 1, 2, 3, 4, 5]
第 78 题:Vue 的父组件和子组件生命周期钩子执行顺序是什么
1. 父组建: beforeCreate -> created -> beforeMount
2. 子组件: -> beforeCreate -> created -> beforeMount -> mounted
3. 父组件: -> mounted
4. 总结:从外到内,再从内到外
第 79 题:input 搜索如何防抖,如何处理中文输入
<div>
<input type="text" id="ipt"></div> <script>
let ipt = document.getElementById('ipt');
let dbFun = debounce()
ipt.addEventListener('keyup', function (e) {
dbFun(e.target.value);
})
function debounce() {
let timer;
return function (value) {
clearTimeout(timer);
timer = setTimeout(() =>
{
console.log(value)
}, 500);
}
}
</script>
第 80 题:介绍下 Promise.all 使用、原理实现及错误处理
const p = Promise.all([p1, p2, p3]);
Promise.all 方法接受一个数组作为参数,p1、p2、p3 都是 Promise 实例,如果
不是,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,
再进一步处理。(Promise.all 方法的参数可以不是数组,但必须具有 Iterator 接
口,且返回的每个成员都是 Promise 实例。)
第 81 题:打印出 1 - 10000 之间的所有对称数
例如:121、1331 等
[...Array(10000).keys()].filter((x) => {
return x.toString().length > 1 && x ===
Number(x.toString().split('').reverse().join(''))
})
第 82 题:周一算法题之「移动零」
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非
零元素的相对顺序。示例:
输入: [0,1,0,3,12] 输出: [1,3,12,0,0]
复制代码说明: 必须在原数组上操作,不能拷贝额外的数组。 尽量减少操作次
数
答:
function zeroMove(array) {
let len = array.length;
let j = 0;
for(let
i=0;i<len-j;i++){
if(array[i]===0){
array.push(0);
array.splice(i,1);
i --;
j ++;
}
}
return array;
}
第 83 题:var、let 和 const 区别的实现原理是什么
三者的区别:
· var 和 let 用以声明变量,const 用于声明只读的常量;
· var 和 let 用以声明变量,const 用于声明只读的常量;
· var 声明的变量,不存在块级作用域,在全局范围内都有效,let 和 const
声明的,只在它所在的代码块内有效;
· let 和 const 不存在像 var 那样的 “变量提升” 现象,所以 var 定义变
量可以先使用,后声明,而 let 和 const 只可先声明,后使用;
· let 声明的变量存在暂时性死区,即只要块级作用域中存在 let,那么它
所声明的变量就绑定了这个区域,不再受外部的影响。
· let 不允许在相同作用域内,重复声明同一个变量;
· const 在声明时必须初始化赋值,一旦声明,其声明的值就不允许改变,
更不允许重复声明;如 const 声明了一个复合类型的常量,其存储的是一个引
用地址,不允许改变的是这个地址,而对象本身是可变的。
变量与内存之间的关系,主要由三个部分组成:
· 变量名
· 内存地址
· 内存空间JS 引擎在读取变量时,先找到变量绑定的内存地址,然后找到地址所指向的内
存空间,最后读取其中的内容。当变量改变时,JS 引擎不会用新值覆盖之前旧
值的内存空间(虽然从写代码的角度来看,确实像是被覆盖掉了),而是重新
分配一个新的内存空间来存储新值,并将新的内存地址与变量进行绑定,JS 引
擎会在合适的时机进行 GC,回收旧的内存空间。
const 定义变量(常量)后,变量名与内存地址之间建立了一种不可变的绑定
关系,阻隔变量地址被改变,当 const 定义的变量进行重新赋值时,根据前面
的论述,JS 引擎会尝试重新分配新的内存空间,所以会被拒绝,便会抛出异常。
第 84 题:请实现一个 add 函数,满足以下功能。
add(1);
// 1add(1)(2);
// 3add(1)(2)(3);
// 6add(1)(2, 3);
// 6add(1, 2)(3);
// 6add(1, 2, 3);
// 6
答:
实现 1:
function currying(fn, length) {
length = length || fn.length; // 注释 1
return function (...args) { // 注释 2 return
args.length >= length // 注释 3
? fn.apply(this, args) // 注释 4
: currying(fn.bind(this, ...args), length - args.length) // 注释
5 }}
实现 2:
const currying = fn =>
judge = (...args) =>
args.length >= fn.length
? fn(...args)
: (...arg) => judge(...args, ...arg)
其中注释部分
注释 1:第一次调用获取函数 fn 参数的长度,后续调用获取 fn 剩余参数的
长度
注释 2:currying 包裹之后返回一个新函数,接收参数为 ...args
注释 3:新函数接收的参数长度是否大于等于 fn 剩余参数需要接收的长度
注释 4:满足要求,执行 fn 函数,传入新函数的参数
注释 5:不满足要求,递归 currying 函数,新的 fn 为 bind 返回的新函数(bind
绑定了 ...args 参数,未执行),新的 length 为 fn 剩余参数的长度第 85 题:react-router 里的 <Link> 标签和 <a> 标签有什么
区别
如何禁掉 标签默认事件,禁掉之后如何实现跳转。
答:
Link 点击事件 handleClick 部分源码:
if (_this.props.onClick) _this.props.onClick(event);
if (
!event.defaultPrevented && // onClick prevented default
event.button === 0 && // ignore everything but left
clicks !_this.props.target && // let browser handle "target=_blank"
etc. !isModifiedEvent(event) // ignore clicks with modifier keys
) {
event.preventDefault();
var history = _this.context.router.history;
var _this$props = _this.props,
replace = _this$props.replace,
to = _this$props.to;
if (replace) {
history.replace(to);
} else {
history.push(to);
}
}
Link 做了 3 件事情:
· 有 onclick 那就执行 onclick
· click 的时候阻止 a 标签默认事件(这样子点击<a href="/abc">123</a>就 不会跳转和刷新页面)
· 再取得跳转 href(即是 to),用 history(前端路由两种方式之一,history
& hash)跳转,此时只是链接变了,并没有刷新页面
第 86 题:周一算法题之「两数之和」
给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。示例:
给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9 所以返
回 [0, 1]
答:
function anwser (arr, target) {
let map = {} for (let i = 0; i < arr.length; i++) {
map[arr[i]] = i }
for (let i = 0; i < arr.length; i++) {
var d = target - arr[i]
if (map[d]) {
return [i, map[d]]
}
}
return new Error('404 not found')}
第 87 题:在输入框中如何判断输入的是一个正确的网址。
function isUrl(url) {
const a = document.createElement("a");
a.href = url;
return (
[
/^(http|https):$/.test(a.protocol),
a.host,
a.pathname !== url,
a.pathname !== `/${url}`
].find(x => !x) === undefined
);
}
第 88 题:实现 convert 方法,把原始 list 转换成树形结构,
要求尽可能降低时间复杂度
以下数据结构中,id 代表部门编号,name 是部门名称,parentId 是父部门编
号,为 0 代表一级部门,现在要求实现一个 convert 方法,把原始 list 转换
成树形结构,parentId 为多少就挂载在该 id 的属性 children 数组下,结构如
下:// 原始 list 如下 let list =[
{id:1,name:'部门 A',parentId:0},
{id:2,name:'部门 B',parentId:0},
{id:3,name:'部门 C',parentId:1},
{id:4,name:'部门 D',parentId:1},
{id:5,name:'部门 E',parentId:2},
{id:6,name:'部门 F',parentId:3},
{id:7,name:'部门 G',parentId:2},
{id:8,name:'部门 H',parentId:4}];
const result = convert(list, ...);// 转换后的结果如下 let result =
[
{
id: 1,
name: '部门 A',
parentId: 0,
children: [
{
id: 3,
name: '部门 C',
parentId: 1,
children: [
{
id: 6,
name: '部门 F',
parentId: 3
}, {
id: 16,
name: '部门 L',
parentId: 3
}
]
},
{
id: 4,
name: '部门 D',
parentId: 1,
children: [
{
id: 8,
name: '部门 H',
parentId: 4
}
]
}]
},
···];
答:
function convert(list) {
const res = []
const map = list.reduce((res, v) => (res[v.id] = v, res), {})
for (const item of list) {
if (item.parentId === 0) {
res.push(item)
Continue
}
if (item.parentId in map) {
const parent = map[item.parentId]
parent.children = parent.children || []
parent.children.push(item)
}
}
return res}
第 89 题:设计并实现 Promise.race()
Promise._race = promises => new Promise((resolve, reject) => {
promises.forEach(promise => {
promise.then(resolve, reject)
})})Promise.myrace = function(iterator) {
return new Promise ((resolve,reject) => {
try {
let it = iterator[Symbol.iterator]();
while(true) {
let res = it.next();
console.log(res);
if(res.done) break;
if(res.value instanceof Promise){
res.value.then(resolve,reject);
} else{
resolve(res.value)
}
}
} catch (error) {
reject(error)
}})
}
第 90 题:实现模糊搜索结果的关键词高亮显示
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta
name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>auto
complete</title> <style>
bdi {
color: rgb(0, 136, 255);
}
li {
list-style: none;
}
</style></head><body>
<input class="inp" type="text">
<section>
<ul class="container"></ul>
</section></body><script>
function debounce(fn, timeout = 300) {
let t; return (...args) => {
if (t) {
clearTimeout(t);
}
t = setTimeout(() => {
fn.apply(fn, args);
}, timeout);
}
}
function memorize(fn) {
const cache = new Map();
return (name) => {
if (!name) {
container.innerHTML = '';
return;
}
if (cache.get(name)) {
container.innerHTML = cache.get(name);
return;
}
const res = fn.call(fn, name).join('');cache.set(name, res);
container.innerHTML = res;
}
}
function handleInput(value) {
const reg = new RegExp(`\(${value}\)`);
const search = data.reduce((res, cur) => {
if (reg.test(cur)) {
const match = RegExp.$1;
res.push(`<li>${cur.replace(match,
'<bdi>$&</bdi>')}</li>`);
}
return res;
}, []);
return search;
}
const data = ["上海野生动物园", "上饶野生动物园", "北京巷子", "
上海中心", "上海黄埔江", "迪士尼上海", "陆家嘴上海中心"]
const container = document.querySelector('.container');
const memorizeInput = memorize(handleInput);
document.querySelector('.inp').addEventListener('input',
debounce(e => {
memorizeInput(e.target.value);
}))</script></html>
第 91 题:介绍下 HTTPS 中间人攻击
https 协议由 http + ssl 协议构成,具体的链接过程可参考 SSL 或 TLS 握手的概 述
中间人攻击过程如下:
1. 服务器向客户端发送公钥。
2. 攻击者截获公钥,保留在自己手上。
3. 然后攻击者自己生成一个【伪造的】公钥,发给客户端。
4. 客户端收到伪造的公钥后,生成加密 hash 值发给服务器。
5. 攻击者获得加密 hash 值,用自己的私钥解密获得真秘钥。
6. 同时生成假的加密 hash 值,发给服务器。
7. 服务器用私钥解密获得假秘钥。
8. 服务器用加秘钥加密传输信息
防范方法:
服务端在发送浏览器的公钥中加入 CA 证书,浏览器可以验证 CA 证书的有效性
第 92 题:已知数据格式,实现一个函数 fn 找出链条中所有的
父级 idconst value = '112'
const fn = (value) => {...}fn(value) // 输出 [1, 11, 112]
答:const data = [
{
id: "1",
name: "test1",
children: [
{
id: "11",
name: "test11",
children: [
{
id: "111",
name: "test111"
},
{
id: "112",
name: "test112"
}
]
},
{
id: "12",
name: "test12",
children: [
{
id: "121",
name: "test121"
},
{
id: "122",
name:
"test122"
}
]
}
]
}];
const find = value => {
let result = [];
let findArr = data;
let skey = "";
for (let i = 0, l = value.length; i < l; i++) {
skey += value[i];
let item = findArr.find(item => {
return item.id == skey;
});
if (!item) {
return [];
}
result.push(item.id);
if (item.children) {
findArr = item.children;
} else {
if (i < l - 1) return [];
return result;
}
}
};
//调用看结果
function testFun() {
console.log("1,11,111:", find("111"));
console.log("1,11,112:", find("112"));
console.log("1,12,121:", find("121"));
console.log("1,12,122:", find("122"));
console.log("[]:", find("113"));
console.log("[]:", find("1114"));
}
第 93 题:给定两个大小为 m 和 n 的有序数组 nums1 和
nums2。请找出这两个有序数组的中位数。要求算法的时间复杂
度为 O(log(m+n))。
示例 1:
nums1 = [1, 3] nums2 = [2]
中位数是 2.0示例 2:
nums1 = [1, 2] nums2 = [3, 4]
中位数是(2 + 3) / 2 = 2.5
答:
const findMedianSortedArrays = function(
nums1: number[],
nums2: number[]
) {
const lenN1 = nums1.length;
const lenN2 = nums2.length;
const median = Math.ceil((lenN1 + lenN2 + 1) / 2);
const isOddLen = (lenN1 + lenN2) % 2 === 0;
const result = new Array<number>(median);
let i = 0; // pointer for nums1
let j = 0; // pointer for nums2
for (let k = 0; k < median; k++) {
if (i < lenN1 && j < lenN2) {
// tslint:disable-next-line:prefer-conditional-expression
if (nums1[i] < nums2[j]) {
result[i + j] = nums1[i++];
} else {
result[i + j] = nums2[j++];
}
} else if (i < lenN1) {
result[i + j] = nums1[i++];
} else if (j < lenN2) {
result[i + j] = nums2[j++];
}
}
if (isOddLen) {
return (result[median - 1] + result[median - 2]) / 2;
} else {
return result[median - 1];
}
};
第 94 题:vue 在 v-for 时给每项元素绑定事件需要用事件代
理吗?为什么?
在 vue 中 vue 做了处理如果我们自己在非 vue 中需要对很多元素添加事件的时候,可以通过将事件添 加到它们的父节点而将事件委托给父节点来触发处理函数
第 95 题:模拟实现一个深拷贝,并考虑对象相互引用以及
Symbol 拷贝的情况
一个不考虑其他数据类型的公共方法,基本满足大部分场景
function deepCopy(target, cache = new Set()) {
if (typeof target !== 'object' || cache.has(target)) {
return target
}
if (Array.isArray(target)) {
target.map(t => {
cache.add(t)
return t
})
} else {
return
[...Object.keys(target), ...Object.getOwnPropertySymbols(target)].red
uce((res, key) => {
cache.add(target[key])
res[key] = deepCopy(target[key], cache)
return res
}, target.constructor !== Object ?
Object.create(target.constructor.prototype) : {})
}
}
主要问题是
· symbol 作为 key,不会被遍历到,所以 stringify 和 parse 是不行的
· 有环引用,stringify 和 parse 也会报错
我们另外用 getOwnPropertySymbols 可以获取 symbol key 可以解决问题 1,用集
合记忆曾经遍历过的对象可以解决问题 2。当然,还有很多数据类型要独立去
拷贝。比如拷贝一个 RegExp,lodash 是最全的数据类型拷贝了,有空可以研究
一下
另外,如果不考虑用 symbol 做 key,还有两种黑科技深拷贝,可以解决环引用
的问题,比 stringify 和 parse 优雅强一些
function deepCopyByHistory(target) {const prev = history.state
history.replaceState(target, document.title)
const res = history.state
history.replaceState(prev, document.title)
return res
}
async function deepCopyByMessageChannel(target) {
return new Promise(resolve => {
const channel = new MessageChannel()
channel.port2.onmessage = ev => resolve(ev.data)
channel.port1.postMessage(target)
}).then(data => data)}
无论哪种方法,它们都有一个共性:失去了继承关系,所以剩下的需要我们手
动补上去了,故有 Object.create(target.constructor.prototype)的操作
第 96 题:介绍下前端加密的常见场景和方法
首先,加密的目的,简而言之就是将明文转换为密文、甚至转换为其他的东西,
用来隐藏明文内容本身,防止其他人直接获取到敏感明文信息、或者提高其他
人获取到明文信息的难度。通常我们提到加密会想到密码加密、HTTPS 等关键
词,这里从场景和方法分别提一些我的个人见解。
场景-密码传输
前端密码传输过程中如果不加密,在日志中就可以拿到用户的明文密码,对用
户安全不太负责。这种加密其实相对比较简单,可以使用 PlanA-前端加密、后
端解密后计算密码字符串的 MD5/MD6 存入数据库;也可以 PlanB-直接前端使
用一种稳定算法加密成唯一值、后端直接将加密结果进行 MD5/MD6,全程密
码明文不出现在程序中。
PlanA 使用 Base64 / Unicode+1 等方式加密成非明文,后端解开之后再存它的
MD5/MD6 。
PlanB 直接使用 MD5/MD6 之类的方式取 Hash ,让后端存 Hash 的 Hash 。
场景-数据包加密
应该大家有遇到过:打开一个正经网站,网站底下蹦出个不正经广告——比如
X 通的流量浮层,X 信的插入式广告……(我没有针对谁)但是这几年,我们会
发现这种广告逐渐变少了,其原因就是大家都开始采用 HTTPS 了。被人插入
这种广告的方法其实很好理解:你的网页数据包被抓取->在数据包到达你手机
之前被篡改->你得到了带网页广告的数据包->渲染到你手机屏幕。而 HTTPS 进
行了包加密,就解决了这个问题。严格来说我认为从手段上来看,它不算是一
种前端加密场景;但是从解决问题的角度来看,这确实是前端需要知道的事情。
Plan 全面采用 HTTPS
场景-展示成果加密经常有人开发网页爬虫爬取大家辛辛苦苦一点一点发布的数据成果,有些会影
响你的竞争力,有些会降低你的知名度,甚至有些出于恶意爬取你的公开数据
后进行全量公开……比如有些食谱网站被爬掉所有食谱,站点被克隆;有些求职
网站被爬掉所有职位,被拿去卖信息;甚至有些小说漫画网站赖以生存的内容
也很容易被爬取。
Plan 将文本内容进行展示层加密,利用字体的引用特点,把拿给爬虫的数据变
成“乱码”。举个栗子:正常来讲,当我们拥有一串数字“12345”并将其放在网站
页面上的时候,其实网站页面上显示的并不是简单的数字,而是数字对应的字
体的“12345”。这时我们打乱一下字体中图形和字码的对应关系,比如我们搞成
这样:
图形:1 2 3 4 5 字码:2 3 1 5 4
这时,如果你想让用户看到“12345”,你在页面中渲染的数字就应该是“23154”。
这种手段也可以算作一种加密。
第 97 题:React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化 到 O(n) ,那么 O(n^3) 和 O(n) 是如何计算出来的?
三种优化来降低复杂度:
1. 如果父节点不同,放弃对子节点的比较,直接删除旧节点然后添加新的
节点重新渲染;
2. 如果子节点有变化,Virtual DOM 不会计算变化的是什么,而是重新渲染,
3. 通过唯一的 key 策略
第 98 题:写出如下代码的打印结果
function changeObjProperty(o) {
o.siteUrl = "http://www.baidu.com"
o = new Object()
o.siteUrl = "http://www.google.com"
}
let webSite = new Object();
changeObjProperty(webSite);
console.log(webSite.siteUrl);答:
webSite 属于复合数据类型,函数参数中以地址传递,修改值会影响到原始值,
但如果将其完全替换成另一个值,则原来的值不会受到影响
第 99 题:编程算法题
用 JavaScript 写一个函数,输入 int 型,返回整数逆序后的字符串。如:输入
整型 1234,返回字符串“4321”。要求必须使用递归函数调用,不能用全局变量,
输入函数必须只有一个参数传入,必须返回字符串。
function fun(num) {
let num1 = num / 10;
let num2 = num % 10;
if (num1 < 1) {
return num;
} else {
num1 = Math.floor(num1);
return `${num2}${fun(num1)}`;
}
}
var a = fun(12345);
console.log(a);
console.log(typeof a);
第 100 题:请写出如下代码的打印结果
function Foo() {
Foo.a = function() {
console.log(1);
};
this.a = function() {
console.log(2);
};
}
Foo.prototype.a = function() {
console.log(3);
};
Foo.a = function() {
console.log(4);};
Foo.a();
let obj = new Foo();
obj.a();
Foo.a();
答:
4 2 1