鹅厂暑假实习前端面经

题目:给定两棵二叉树,这两棵树的部分节点是重叠的,部分没重叠。 现在要将它们合并到一个新的二叉树中。合并规则是,如果两个节点重叠,则将两个节点的值相加,作为合并节点的新值;否则,非空节点的值作为新树的节点。

例如:

/*
采用递归解决
t1 t2 都不空,则创建新节点,值为两者之和,
他的左右子结点分别递归调用,
若一者为空,则直接将此节点合并到新数上面。
*/
var mergeTrees = function (t1, t2) {
let node;
if (t1 && t2) {
// 都不为空节点,则创建新节点,并递归左右节点
node = new TreeNode(t1.val + t2.val);
node.left = mergeTrees(t1.left, t2.left);
node.right = mergeTrees(t1.right, t2.right);
} else if (t1 && !t2) {
// t2为空,则返回t1节点
node = t1
} else if (!t1 && t2) {
// t1为空,则返回t2节点
node = t2
} else {
// 均为空,则返回null
return null;
}
return node;
};

复制代码
2.子串问题

题目:给定两个字符串s1z和s2,如果s2包含s1的任一排列,则返回true。换句话说,第一个字符串的排列之一,是第二个字符串的子串。

例1:

Input: s1 = “ab”, s2 = “eidbaooo”

Output: True

说明: s2包含s1的一个排列(“ba”)

例2:

Input: s1= “ab”, s2 = “eidboaoo”

Output: False

/**

  • @param {string} s1
  • @param {string} s2
  • @return {boolean}
    */
    // 当两个字符串包含具有相同频率的相同字符时,一个字符串才是另一个字符串的排列
    var checkInclusions = function (s1, s2) {
    // 判断长度
    if (s1.length > s2.length) {
    return false
    }
    // 建立数组存放频率
    let count = new Array(26).fill(0);
    // 检测是否命中,全为0则命中
    const isAllZero = (count) => count.every(val => val === 0);
    // 初始化窗口数据
    for (let i = 0; i < s1.length; i++) {
    count[s1[i].charCodeAt(0) - 97]++;
    count[s2[i].charCodeAt(0) - 97]–;
    }
    // 判断第一个窗口数据是否命中
    if (isAllZero(count)) return true;
    // 滑动窗口,每次向右滑动一个
    for (let i = s1.length; i < s2.length; i++) {
    // 将窗口右边第一个数据作为频率放到数组里
    count[s2[i].charCodeAt(0) - 97]–;
    // 将窗口左边对应的频率删除掉
    count[s2[i - s1.length].charCodeAt(0) - 97]++;
    // 检测是否命中
    if (isAllZero(count)) return true;
    }
    return false;
    };
    复制代码
    用滑动窗口算法就可实现

3.奇偶链表

给定一个单向链表,将所有奇数节点组合在一起,然后是偶数节点。需要注意的是,这里指的是节点序号而不是节点中的值。 例:

输入1-> 2-> 3-> 4-> 5-> NULL,

输出1-> 3-> 5-> 2-> 4-> NULL。

注意:

1)偶数和奇数组内的相对顺序应与输入时保持相同。

2)第1个节点是奇数,第2个节点是偶数,以此类推

/**
1、遍历标记链表每个节点的奇偶
2、保存两个变量
lastOdd 上一个奇节点
lastNode 上一个节点
当遍历到奇节点时,将上一个奇节点lastOdd和当前节点连接起来即可
/
/
*

  • @param {ListNode} head
  • @return {ListNode}
    */
    var oddEvenList = function(head) {
    let node = head;
    let isOdd = true;
    while(node){
    // 遍历标记链表每个节点的奇偶 第一个为奇
    node.tag = isOdd;
    node = node.next;
    isOdd = !isOdd;
    }
    // lastOdd 上一个奇节点
    let lastOdd = head;
    // lastNode 上一个节点
    let lastNode = null;
    node = head;
    while(node){
    // 当遍历到奇节点时,将上一个奇节点和当前节点连接起来
    if(node.tag && lastNode!==null){
    lastNode.next = node.next;
    node.next = lastOdd.next;
    lastOdd.next = node;
    lastOdd = node;
    }
    lastNode = node;
    node = node.next;
    }
    return head
    };
    复制代码
    4.解析url

题目:页面page的主函数入口在page.js,以下为其依赖树。

为了性能要求,我们页面会组装一个combo请求: res.wx.qq.com/F.js,E.js,D… 请设计genUrl(requireTree),输出如上所示的combo url。requireTree的数据结构如下页。

const requireTreeData = {
“name”: “page.js”,
“require”: [
{
“name”: “A.js”,
“require”: [
{
“name”: “C.js”,
“require”: [
{
“name”: “F.js”
}
]
}
]
},
{
“name”: “B.js”,
“require”: [
{
“name”: “D.js”,
“require”: [
{
“name”: “F.js”
}
]
},
{
“name”: “E.js”,
“require”: []
}
]
},
]
};
复制代码
这道题类似二叉树的广度优先遍历:

/**

  • @param {object} requireTree
  • @return {string}
    */
    var genUrl = function (requireTree) {
    let data = [];
    let nodeStack = [requireTree];
    let set = new Set();
    while (nodeStack.length > 0) {
    let rowLength = nodeStack.length;
    let rowData = [];
    for (let i = 0; rowLength > i; i++) {
    let nowNode = nodeStack.shift();
    if (set.has(nowNode.name)) {
    break;
    }
    rowData.push(nowNode.name);
    if (nowNode.hasOwnProperty(‘require’) && nowNode.require.length > 0) {
    nowNode.require.forEach((item)=>{
    nodeStack.push(item)
    })
    }
    set.add(nowNode.name);
    }
    data.push(…rowData);
    }
    data = data.reverse().join(’,’);
    return http://res.wx.qq.com/${data};
    };
    复制代码
    一面(50多分钟)

聊了聊笔试的算法题

var let const 的区别,如何用 es5 实现 let const

const : Object.defineProperty

Object.defineProperty(window, “A”, {
value: 1, // 值
writable: false, // 设置属性只读
configurable: true, // 是否可删除
enumerable: true // 是否可枚举
});
复制代码
let :立即执行函数

(function(){
var a = 1;
console.log(a);
})();
复制代码
class 如何用 es5 实现

// class实现
class AClass {
constructor(name) {
this.name = name;
}

static StaticName = 'dell';

getName() {
    return 'dellyoung';
}

static StaticGetName() {
    return 'delldell'
}

}

// es5实现

var A = function A(name) {
this.name = name;
};

A.prototype.getName = function () {
return ‘dellyoung’;
};

A.StaticName = ‘dell’;

A.StaticGetName = function () {
return ‘delldell’
}
复制代码
箭头函数和普通函数的区别

箭头函数不用绑定 this ,会捕获其上下文的 this 值作为自己的 this ,用 call、apply 不能改变 this
箭头函数没有原型,是匿名函数,不能作为构造函数也就不能使用new
箭头函数不绑定 arguments ,可以用 …arg 解决
什么是跨域,如何解决跨域问题

协议、域名、端口号 之一不同就是跨域
可以用 jsonp 、 CORS 解决跨域问题,当然特殊场景可以用 document.domain 、 window.postMessage 、 WebSocket
讲解一下jsonp如何用,最简单的版本如下:

复制代码
安全问题有了解吗,讲讲cookie安全、XSS攻击、CSRF攻击 ,以及分别如何防范

cookie安全:
1、HttpOnly:使用 HttpOnly 标记的 Cookie 只能使用在 HTTP 请求过程中,所以无法通过 JavaScript 来读取这段 Cookie
2、SameSite:设置SameSite,使cookie不会随着跨域请求发送,缺点兼容性差(Chrome兼容)三个值:
Strict 最为严格:浏览器会完全禁止第三方Cookie
Lax 相对宽松一点:允许 Get 方式的表单提交或a标签携带Cookie None 不设置:是否跨域都会发送Cookie
XSS攻击、CSRF攻击
讲一下HTTPS的握手过程

开始问简历上的项目,面试官对项目很感兴趣,讲了很久。

有什么问题想问我的吗?

二面(40多分钟)

请问下面代码打印结果是?(异步、作用域、闭包相关)
for (var i = 0; i < 4; i++) {
setTimeout(function() {
console.log(i);
}, 300);
}
复制代码
结果:4 4 4 4

追问1: 解释一下为什么输出 4 个 4

每一次 for 循环的时候, setTimeOut 都执行一次,但是里面的函数没有被执行,而是被放到了任务队列里面,等待执行, setTimeOut 是延时异步执行的,只有当主线程上的任务执行完成,主线程时,才会执行任务队列的任务,所以当主线程 for 循环执行完之后 i 的值为4,这个时候再去任务队列中执行任务, i 全部为4.

追问2: setTimeOut 在浏览器上执行的原理知道吗,讲一下

每次执行到 setTimeOut ,会创建一个延时任务,并将该任务放到浏览器渲染进程的 延时任务队列 中,一个延时任务通常包含:回调函数、发起时间、延迟执行时间。当主线程上的任务执行完毕,主线程空闲时,主线程会对延迟任务队列中的任务,通过发起时间、延迟执行时间计算出已经到期的任务,然后依次执行这些到期的任务,等到期的任务全部执行完,主线程就进入到下一次循环。

正好之前画过一个示意图,我把它放这

追问3: 如果我想要输出0 1 2 3,怎么改

把 var 改成 let

追问4: 说说原因

通过 var 定义的变量是无法传入到这个函数作用域中的,通过使用 let 来声明块变量,这时候变量就能作用于这个块,所以 function 就能使用 i 这个变量了;

追问5: 我想要输出0 1 2 3,还有其他方法吗

可以使用立即执行函数

for (var i = 0; i < 4; i++) {
(function (num) {
setTimeout(function() {
console.log(num);
}, 300);
})(i)
}
复制代码
追问6: 解释一下

立即执行函数会创建一个独立的作用域,这个作用域里面的变量,外面访问不到。这样每次for循环传入参数 i ,每次都创建了一个独立的作用域,4次就创建了4个作用域,参数分别为 0 1 2 3。

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(递归相关)
function flag(n, map = new Map()) {
if (n < 1) {
return 0;
}
if (n === 1) {
return 1;
}
if (n === 2) {
return 2;
}
if (map.has(n)) {
return map.get(n);
}
const value = flag(n - 1) + flag(n - 2);
map.set(n, value);
return value;
}
复制代码
追问1: 还有别的方法嘛?

循环

function flag(n) {
if (n < 1) {
return 0;
}
if (n === 1) {
return 1;
}
if (n === 2) {
return 2;
}
let a = 1;
let b = 2;
let sum = a + b;
for (let i = 3; i <= n; i++) {
sum = a + b;
a = b;
b = sum;
}
return sum;
}
复制代码
追问2: 现在的解题思路 时间复杂度是?

O(n)

如下代码打印结果是?(链式调用相关)
function test(a,b) {
console.log(b)
return {
test:function©{
return test(c,a);
}
};
}s
var retA = test(0); retA.test(2); retA.test(4); retA.test(8);
var retB = test(0).test(2).test(4).test(8);
var retC = test(‘good’).test(‘bad’); retC.test(‘good’); retC.test(‘bad’);
复制代码
结果:

undefined 0 0 0
undefined 0 2 4
undefined good bad bad
复制代码
这个函数就是实现了打印上一个输入变量的功能,明白这个就好答了

讲讲学习历程吧
从大一进入技术社团入门web编程开始讲起,中间各种比赛和项目以及实习历程,再讲到至今大三做过的 30+ 项目。然后期间面试官提出了很多问题,比如关于对项目的优化,关于对项目的思考等等。

三面(40多分钟)

两数之和 给定一个整数数组 nums 和一个目标值target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9

所以返回 [0, 1]

var twoSum = function (nums, target) {
let map = new Map();
for (let item of nums) {
if (map.has(item)) {
return [target - item, item];
}
map.set(target - item, item);
}
return null;
};
复制代码
追问1: 目前时间复杂度和空间复杂度分别是多少?

O(n)、O(n)

追问2: 如果输入数组有序,可以有什么优化策略吗?

可以用双索引法,降低空间复杂度。

setTimeOut运行机制
正好二面有问过

内存泄露
追问1: 什么是内存泄露

由于疏忽或错误造成程序未能释放已经不再使用的内存。

追问2: 什么情况会发生内存泄露

意外的全局变量、console.log、闭包、DOM泄漏 等

追问3: 如何排查内存泄露

使用Google Chrome浏览器提供的 Heap Profiling。Heap Profiling可以记录当前的堆内存(heap)快照,并生成对象的描述文件,该描述文件给出了当时JS运行所用到的所有对象,以及这些对象所占用的内存大小、引用的层级关系等等。这些描述文件为内存泄漏的排查提供了非常有用的信息。

描述一个CSRF攻击的过程
诱导用户进入黑客的网站,该黑客网站设置Domain和Path为被攻击的域名,然后就可以拿到该域名下的cookie,通过将图片设置成被攻击域名的地址,发送请求,完成攻击。 (注意请求可以发出去,跨域只是让浏览器不能获取到服务器的返回内容)

追问1: 如何防御

SameSite(cookie) 设置SameSite,使cookie不会随着跨域请求发送,缺点兼容性差(Chrome兼容) 三个值: Strict 最为严格:浏览器会完全禁止第三方 Cookie Lax 相对宽松一点:允许 Get 方式的表单提交或a标签携带Cookie None 不设置:是否跨域都会发送Cookie

Referer 服务器检查Referer,验证请求的来源站点 Origin:因为Referer可以不带,所以还有个Origin属性,xhr、fetch、表单POST 请求都会带上Origin属性

CSRF Token 服务器下发一个Token,请求时带上Token,服务器进行验证 (表单中:可以作为value存放在一个隐藏input标签)

追问1: CSRF Token能被黑客获取到吗?

不能,浏览器可以把服务器下发到Token缓存到内存中,使得第三方网站不能获取到

问项目
面试官依然对项目很感兴趣,重点讨论了项目中的性能优化部分和组件封装

有什么需要补充的吗
聊了聊我基于 领域驱动设计(Domain-Driven Design) 思想所实践的前端架构

小结

整体面试体验很好,面试官也非常友好(等HR面中…)。
算法题比较简单,面试官是比较注重基础的,会深挖基础。
当基础讲完后,有做过几个出彩的项目也是非常重要的,特别是在项目中进行一些性能优化,面试官会非常感兴趣,要尽量将项目中的这些优点暴露出来。另外实习的期间我也主导做过一个中后台业务组件库,提高了公司前端开发的效率,面试官也很感兴趣。

有想了解更多的朋友可以去https://www.jianshu.com/p/86bb74768d12 看一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值