前端面试卷十

295 阅读4分钟

介绍下 HTTPS 中间人攻击

https 协议由 http + ssl 协议构成
中间人攻击过程如下:本质就是截获秘钥(生成的一个随机数),截获后攻击者就
可以代替客户端发起请求了
1.服务器向客户端发送公钥,公钥一般放在证书中。
2.攻击者截获公钥,保留在自己手上。(公钥加密,私钥解密)
3.然后攻击者自己生成一个【伪造的】公钥,发给客户端。
4.客户端收到伪造的公钥后,生成加密 hash 值发给服务器。
5.攻击者获得加密 hash 值,用自己的私钥解密获得真秘钥(随机数)。
6.同时生成假的加密 hash 值,发给服务器。
7.服务器用私钥解密获得假秘钥。
8.服务器用假秘钥加密传输信息

防范方法:

服务端在发送浏览器的公钥中加入 CA 证书,浏览器可以验证 CA 证书的有效性

已知数据格式,实现一个函数fn找出链条中所有的父级id

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" } 
            ] 
        } 
      ] 
    }
]; 

实现:

//递归写法,其实就是dfs
function getParent(data,value){
   var res= []    //返回的结果
   function dfs(arr,temp=[]){    //temp,保存结果的临时数组
       for(var item of arr){
           if(item.children){
             dfs(item.children,temp.push(item.id))
           }
           else{
             if(item.id == value){
                res = temp    //只有叶子结点是value时
                return     //结束循环
             }
           }
       }
   }
   dfs(data)
   return res
}

//非递归写法
function getParent(data,value){
    var res = []
    var str = ''
    var findArr = data
    for(var i=0;i<value.length;i++){
        str+=value[i]
        var item = findArr.find(item=>item.id==str)
        if(!item) {
            res = []
            return
        }
        res.push(item.id)
        if(item.children) findArr = item.children
        else if(i<value.length-1){    //如果还没到叶子节点就没有children了说明数据结构不满足
            res = []
            return
        }
    }
    return res
}

给定两个大小为 m 和 n 的有序数组 nums1 和nums2。请找出这两个有序数组的中位数。要求算法的时间复杂度为 O(log(m+n))。

//分析:只需要合并两个数组为一个新数组,新数组的长度为nums1.length/2+nums2.length/2
function findMedian(nums1,nums2){
    var len1 = nums1.length
    var len2 = nums2.length
    //判断数组个数之和是否是偶数
    var isOdd= (len1+len2)%2==0
    var newLen = (len1+len2)/2 + 1    //[1,2],[3,4] => [1,2,3]    //[1,2],[3] => [1,2]
    var i = 0,j = 0    //两个数组的当前下标
    var res = []    //合并后的新数组
    for(var k = 0;k< newLen;k++){
        //数组中的较小值放入新数组中
        if(i<len1&&j<len2){
            res[k++] = nums1[i]<=nums2[j]?nums1[i++]:nums2[j++]
        }
        else if(i<len1){    //如果nums1还有剩余直接把剩余元素添加到res中
            res[k++] = nums1[i++]
        }
        else if(j<len2){    //如果nums2还有剩余直接把剩余元素添加到res中
            res[k++] = nums2[j++]
        }
    }
    if(isOdd) return (res[newLen-2]+res[newLen-1])/2    //偶数就是最后两个数的平均值
    else return res[newLen-1]    //奇数就是最后一个数
}

vue 在 v-for 时给每项元素绑定事件需要用事件代理吗?为什么?

在 vue 中 vue 做了处理:如果我们自己在非 vue 中需要对很多元素添加事件的时候,可以通过将事件添加到它们的父节点而将事件委托给父节点来触发处理函数

模拟实现一个深拷贝,并考虑对象相互引用以及Symbol 拷贝的情况

分析:先要有一个判断数据类型的方法,数组和对象要进行深拷贝

//判断数组或者对象
function getEmpty(o){
    if(Object.prototype.toString.call(o) === '[object Object]'){
        return {};
    }
    if(Object.prototype.toString.call(o) === '[object Array]'){
	return [];
    }
    return o;
}

广度优先算法:

function deepCopyBFS(origin){    //origin代表原始对象
    let queue = [];
    let map = new Map(); // 记录出现过的对象,用于处理环
    let target = getEmpty(origin);    //getEmpty函数判断是否是数组或者对象,不相等就是
    if(target !== origin){    //是数组或者对象的情况
        queue.push([origin, target]);
        map.set(origin, target);
    }
    while(queue.length){
	let [ori, tar] = queue.shift();
	for(let key in ori){
	    // 处理环状
	    if(map.get(ori[key])){
	        tar[key] = map.get(ori[key]);
		continue;
	    }
	    tar[key] = getEmpty(ori[key]);
            if(tar[key] !== ori[key]){    //递归判断子对象
                queue.push([ori[key], tar[key]]);
	        map.set(ori[key], tar[key]);
            }
        }
    }
    return target;
}

介绍下前端加密的常见场景和方法

首先,加密的目的,简而言之就是将明文转换为密文、甚至转换为其他的东西,
用来隐藏明文内容本身,防止其他人直接获取到敏感明文信息、或者提高其他
人获取到明文信息的难度。通常我们提到加密会想到密码加密、HTTPS 等关键
词,这里从场景和方法分别提一些我的个人见解。
场景-密码传输
场景-密码传输前端密码传输过程中如果不加密,在日志中就可以拿到用户的明文密码,
对用户安全不太负责。这种加密其实相对比较简单,可以使用 PlanA-前端加密、后
端解密后计算密码字符串的 MD5/MD6 存入数据库;也可以 PlanB-直接前端使
用一种稳定算法加密成唯一值、后端直接将加密结果进行 MD5/MD6,全程密
码明文不出现在程序中。
PlanA 使用 Base64 / Unicode+1 等方式加密成非明文,后端解开之后再存它的
MD5/MD6 。
PlanB 直接使用 MD5/MD6 之类的方式取 Hash ,让后端存 Hash 的 Hash 。
场景-数据包加密
应该大家有遇到过:打开一个正经网站,网站底下蹦出个不正经广告——比如
X 通的流量浮层,X 信的插入式广告……(我没有针对谁)但是这几年,我们会
发现这种广告逐渐变少了,其原因就是大家都开始采用 HTTPS 了。被人插入
这种广告的方法其实很好理解:你的网页数据包被抓取->在数据包到达你手机

之前被篡改->你得到了带网页广告的数据包->渲染到你手机屏幕。而 HTTPS 进
行了包加密,就解决了这个问题。严格来说我认为从手段上来看,它不算是一
种前端加密场景;但是从解决问题的角度来看,这确实是前端需要知道的事情。
PlanC 全面采用 HTTPS
场景-展示成果加密经常有人开发网页爬虫爬取大家辛辛苦苦一点一点发布的数据成果,
有些会影响你的竞争力,有些会降低你的知名度,甚至有些出于恶意爬取你的公开数据
后进行全量公开……比如有些食谱网站被爬掉所有食谱,站点被克隆;有些求职网站被爬
掉所有职位,被拿去卖信息;甚至有些小说漫画网站赖以生存的内容也很容易被爬取。
PlanD 将文本内容进行展示层加密,利用字体的引用特点,把拿给爬虫的数据变
成“乱码”。举个栗子:正常来讲,当我们拥有一串数字“12345”并将其放在网站
页面上的时候,其实网站页面上显示的并不是简单的数字,而是数字对应的字
体的“12345”。这时我们打乱一下字体中图形和字码的对应关系,比如我们搞成
这样:图形:1 2 3 4 5 字码:2 3 1 5 4
这时,如果你想让用户看到“12345”,你在页面中渲染的数字就应该是“23154”。
这种手段也可以算作一种加密。

React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化到 O(n) ,那么 O(n^3) 和 O(n) 是如何计算出来的

三种优化来降低复杂度:
1.如果父节点不同,放弃对子节点的比较,直接删除旧节点然后添加新的
节点重新渲染;
2.如果子节点有变化,Virtual DOM 不会计算变化的是什么,而是重新渲染,
3.通过唯一的 key 策略

写出如下代码的打印结果

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 属于复合数据类型,函数参数中以地址传递,修改值会影响到原始值,
但如果将其完全替换成另一个值,则原来的值不会受到影响,结果是:

http://www.baidu.com

编程算法题

用 JavaScript 写一个函数,输入 int 型,返回整数逆序后的字符串。如:输入整型 1234,
回字符串“4321”。要求必须使用递归函数调用,不能用全局变量,输入函数必须只有一个参数传入,必须返回字符串。

function getReserveStr(num){    //假设num = 1234
    var num1 = num / 10    //123
    var num2 = num % 10    //4
    if(num1 < 1){    //就只剩个位数
        return num    //return 1
    }
    else {
        return num2+''+getReserveStr(num1)    //return '4'+'3'+'2'+'1'
    }
}

请写出如下代码的打印结果

function Foo() {    //构造函数,new的时候才会调用
    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(); //调用静态方法 => 4
let obj = new Foo(); //会调用构造函数
obj.a(); //调用属性方法 => 2
Foo.a(); //new之后静态方法被覆盖 => 1
答:4 2 1