目录
第 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 里的 标签和 标签有什么区别.....................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] =
}
第 7 题:ES5/ES6 的继承除了写法以外还有什么区别?1.
ES5 的继承实质上是先创建子类的实例对象,然后再将父类的方法添加
到 this 上(
Parent.apply(this)).
ES6 的继承机制完全不同,实质上是先创建父类的实例对象 this(所以必
须先调用父类的 super()方法),然后再用子类的构造函数修改 this。
ES5 的继承时通过原型或构造函数机制来实现。
ES6 通过 class 关键字定义类,里面有构造方法,类之间通过 extends 关
键字实现继承。
子类必须在 constructor 方法中调用 super 方法,否则新建实例报错。因
为子类没有自己的 this 对象,而是继承了父类的 this 对象,然后对其进行加工。
如果不调用 super 方法,子类得不到 this 对象。
注意 super 关键字指代父类的实例,即父类的 this 对象。
注意:在子类构造函数中,调用 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 的区别
第 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')},
-
async1()new Promise(function(resolve)
-
{
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 方法是异步执行的
第 15 题:简单讲解一下 http2 的多路复用
HTTP2 采用二进制格式传输,取代了 HTTP1.x 的文本格式,二进制格式解析更
高效。
多路复用代替了 HTTP1.x 的序列和阻塞机制,所有的相同域名请求都通过同一
个 TCP 连接并发完成。
在 HTTP1.x 中,并发多个请求需要多个 TCP 连接,浏览器为了控制资源会有 6-8
个 TCP 连接都限制
第 14 题:情人节福利题,如何实现一个 new(答案见文末)
。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
}
}
就可以自动安装对应的模块?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),以及如何
进行优化
- 浏览器渲染机制
浏览器采用流式布局模型(
Flow Based Layout)
浏览器会把 HTML 解析成 DOM,把 CSS 解析成 CSSOM,DOM 和 CSSOM 合并就
产生了渲染树(Render Tree)。
有了 RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大
小和位置,最后把节点绘制到页面上。
由于浏览器使用流式布局,对 Render Tree 的计算通常只需要遍历一次就可以完
成,但 table 及其内部元素除外,他们可能需要多次计算,通常要花 3 倍于同
等元素的时间,这也是为什么要避免使用 table 布局的原因之一。
- 重绘
由于节点的几何属性发生改变或者由于样式发生改变而不会影响布局的,称为
重绘,例如 outline, visibility, color、background-color 等,重绘的代价是高昂的,
因为浏览器必须验证 DOM 树上其他节点元素的可见性。
- 回流
回流是布局或者几何属性需要改变就称为回流。回流是影响浏览器性能的关键
因素,因为其变化涉及到部分页面(或是整个页面)的布局更新。一个元素的
回流可能会导致了其所有子元素以及 DOM 中紧随其后的节点、祖先节点元素
的随后的回流。
我的组件
错误:错误的描述…
错误纠正
- 第一步
- 第二步
在上面的 HTML 片段中,对该段落(
标签)回流将会引发强烈的回流,因为它
是一个子节点。这也导致了祖先的回流(
div.error 和 body – 视浏览器而定)。
此外,
和也会有简单的回流,因为其在 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()
所以,我们应该避免频繁的使用上述的属性,他们都会强制渲染刷新队列。
- 减少重绘与回流
CSS
1、使用 transform 替代 top
2、使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回
流(改变了布局
3、避免使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局。
4、尽可能在 DOM 树的最末端改变 class,回流是不可避免的,但可以减少其影
响。尽可能在 DOM 树的最末端改变 class,可以限制了回流的范围,使其影响
尽可能少的节点。
5、避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。
span {
color: red;
} div > a > span {
color: red;
}
对于第一种设置样式的方式来说,浏览器只需要找到页面中所有的 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、
第 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 点击事件 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 标签默认事件(这样子点击123就
不会跳转和刷新页面)
再取得跳转 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 = iteratorSymbol.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 题:实现模糊搜索结果的关键词高亮显示
name="viewport" content="width=device-width, initial-scale=1.0">
auto
complete
bdi {
color: rgb(0, 136, 255);
}
li {
list-style: none;
}
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(`
- ${cur.replace(match,
'$&')}
`);
}
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);
}))
第 91 题:介绍下 HTTPS 中间人攻击
https 协议由 http + ssl 协议构成,具体的链接过程可参考 SSL 或 TLS 握手的概
述
中间人攻击过程如下:
服务器向客户端发送公钥。
攻击者截获公钥,保留在自己手上。
然后攻击者自己生成一个【伪造的】公钥,发给客户端。
客户端收到伪造的公钥后,生成加密 hash 值发给服务器。
攻击者获得加密 hash 值,用自己的私钥解密获得真秘钥。
同时生成假的加密 hash 值,发给服务器。
服务器用私钥解密获得假秘钥。
服务器用加秘钥加密传输信息
防范方法:
服务端在发送浏览器的公钥中加入 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(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) 是如何计算出来的?
三种优化来降低复杂度:
如果父节点不同,放弃对子节点的比较,直接删除旧节点然后添加新的
节点重新渲染;
如果子节点有变化,Virtual DOM 不会计算变化的是什么,而是重新渲染,
通过唯一的 key 策略
第 98 题:写出如下代码的打印结果
function changeObjProperty(o) {
o.siteUrl = "www.baidu.com"
o = new Object()
o.siteUrl = "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();
注:资料来源:github.com/Advanced-Fr…
答:4 2 1
第14题答案:
new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实
例。new 关键字会进行如下的操作:
1.创建一个空的简单 JavaScript 对象(即{});
2.链接该对象(即设置该对象的构造函数)到另一个对象 ;(
通俗理解就是新
对象隐式原型__proto__链接到构造函数显式原型prototype上。)
3.将步骤 1 新创建的对象作为 this 的上下文 ;(
实际是执行了构造函数 并
将构造函数作用域指向新对象 )
4.如果该函数没有返回对象,则返回 this。(
实际是返回一个空对象, new
Object()就是返回一个空对象{} )
由木易杨大佬整理,已被字节面试官作为题库收录,供大家参考学习。了解更多优质内容,请关注公众号
「高级前端进阶」
Object.prototype.toString.call(res) 每一个对象都有一个自己的私有属性
[[class]],是一个字符串值,表明了该对象的类型,该私有属性不能直接获取,
只能通过Object.prototype.toString方法调用,所以toString方法获取到对象
私有属性[[class]]的值class 后, 最后返回由"[object " 、class 、"]"三个
部分组成的字符串,call改变this指向。
分的回流将导致页面的重新渲染。
回流必定会发生重绘,重绘不一定会引发回流。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()
所以,我们应该避免频繁的使用上述的属性,他们都会强制渲染刷新队列。
- 减少重绘与回流
CSS
1、使用 transform 替代 top
2、使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回
流(改变了布局
3、避免使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局。
4、尽可能在 DOM 树的最末端改变 class,回流是不可避免的,但可以减少其影
响。尽可能在 DOM 树的最末端改变 class,可以限制了回流的范围,使其影响
尽可能少的节点。
5、避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。
span {
color: red;
} div > a > span {
color: red;
}
对于第一种设置样式的方式来说,浏览器只需要找到页面中所有的 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、
第 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 点击事件 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 标签默认事件(这样子点击123就
不会跳转和刷新页面)
再取得跳转 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 = iteratorSymbol.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 题:实现模糊搜索结果的关键词高亮显示
name="viewport" content="width=device-width, initial-scale=1.0">
auto
complete
bdi {
color: rgb(0, 136, 255);
}
li {
list-style: none;
}
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(`
'$&')}
}
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);
}))
第 91 题:介绍下 HTTPS 中间人攻击
https 协议由 http + ssl 协议构成,具体的链接过程可参考 SSL 或 TLS 握手的概
述
中间人攻击过程如下:
服务器向客户端发送公钥。
攻击者截获公钥,保留在自己手上。
然后攻击者自己生成一个【伪造的】公钥,发给客户端。
客户端收到伪造的公钥后,生成加密 hash 值发给服务器。
攻击者获得加密 hash 值,用自己的私钥解密获得真秘钥。
同时生成假的加密 hash 值,发给服务器。
服务器用私钥解密获得假秘钥。
服务器用加秘钥加密传输信息
防范方法:
服务端在发送浏览器的公钥中加入 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(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) 是如何计算出来的?
三种优化来降低复杂度:
如果父节点不同,放弃对子节点的比较,直接删除旧节点然后添加新的
节点重新渲染;
如果子节点有变化,Virtual DOM 不会计算变化的是什么,而是重新渲染,
通过唯一的 key 策略
第 98 题:写出如下代码的打印结果
function changeObjProperty(o) {
o.siteUrl = "www.baidu.com"
o = new Object()
o.siteUrl = "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();
注:资料来源:github.com/Advanced-Fr…
答:4 2 1
第14题答案:
new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实
例。new 关键字会进行如下的操作:
1.创建一个空的简单 JavaScript 对象(即{});
2.链接该对象(即设置该对象的构造函数)到另一个对象 ;(
通俗理解就是新
对象隐式原型__proto__链接到构造函数显式原型prototype上。)
3.将步骤 1 新创建的对象作为 this 的上下文 ;(
实际是执行了构造函数 并
将构造函数作用域指向新对象 )
4.如果该函数没有返回对象,则返回 this。(
实际是返回一个空对象, new
Object()就是返回一个空对象{} )
由木易杨大佬整理,已被字节面试官作为题库收录,供大家参考学习。了解更多优质内容,请关注公众号
「高级前端进阶」
Object.prototype.toString.call(res) 每一个对象都有一个自己的私有属性
[[class]],是一个字符串值,表明了该对象的类型,该私有属性不能直接获取,
只能通过Object.prototype.toString方法调用,所以toString方法获取到对象
私有属性[[class]]的值class 后, 最后返回由"[object " 、class 、"]"三个
部分组成的字符串,call改变this指向。