一轮(电话面)
先问简历中的项目,问在项目中做了些什么,用到了哪些技术。然后问了一些基础题:
1.JS 的数据类型有哪些?
在 ES5 中有 6 种,分别是:Boolean 、Number 、String 、Object 、Null 和 Undefined ,在 ES6+ 中新增了一种 Symbol 类型。
2.var,let,const 的区别?
1.var 声明的变量作用在函数级作用域,let 和 const 声明的变量作用在块级作用域;
2.var 声明的变量可以被提升到函数体的头部(只能提升声明,不能提升初始化的值),let 和 const 声明的变量不能被提升,let 和 const 会发生“暂时性死区”现象;
3.var 和 let 在声明变量时可不必初始化,且变量可以被改变,const 在声明时就必须为初始化,并且该常量不能被修改(引用类型可以修改它的属性或方法);
4.var 声明的变量可以重复声明,let 和 const 声明的变量不能重复声明;
var 作用范围:
function test() {
var a = 1;
console.log(a); // 1
function inner() {
console.log(a); // 1
}
inner();
}
test();
console.log(a) // ReferenceError: a is not defined
let 和 const 作用范围:
{
let a = 1;
const b = 2;
function inner() {
{
let a = 3;
const b = 4;
console.log(a); // 3
console.log(b); // 4
}
console.log(a); // 1
console.log(b); // 2
}
inner();
console.log(a); // 1
console.log(b); // 2
}
console.log(a) // ReferenceError: a is not defined
console.log(b);
var 的变量提升:
(function test() {
a = 1;
console.log(a); // 1
console.log(b); // undefined
var a;
var b = 2;
})();
let 和 const 的暂时性死区(变量无法提升 & 无法访问外层作用域同名变量,报错):
let a = 1;
const b = 2;
(function test() {
try {
console.log(b); // 未 try catch 前打印:ReferenceError: b is not defined
} catch (e) {
console.log(a); // ReferenceError: a is not defined
}
let a;
const b = 3;
})();
var 在自己的作用范围内重复声明变量(不报错)
(function test() {
var a;
var a;
})()
let 和 const 在自己的作用范围内重复声明已声明的变量(报错)
(function test() {
var a;
let a;
})()
3. ES6 中 Map 和 JS 中普通 Object 的区别?
普通对象的键最后都会变成字符串,而 Map 中的键可以是任意类型(包括对象,键是它的地址值)
var objkey = {};
var objkey2 = {};
var obj = {
[objkey]: 0,
[objkey2]: 1
};
console.log(obj[objkey]); // 1
var map = new Map();
map.set(objkey, 0);
map.set(objkey2, 1);
console.log(map.get(objkey)); // 0
4.ES6 中 类的构造器中 super 是什么?
super 指向父类构造器,必须放在类的构造函数的第一行,实例化子类前会先实例化父类,调用 super 就是在实例化父类。
5.数组有哪些常用方法?
数组分为变异方法(会修改原数组)和非变异方法(不会修改原数组)。
变异方法:push、push、shift、unshift、splice、sort、reserve 和 fill 等。
非变异方法:join、isArray、forEach、filter、map、reduce、reduceRight、concat、slice、find、findIndex、indexOf、lastIndexOf 和 includes 等。
6.字符串有哪些常用方法?
replace、trim、toLowerCase、toUpperCase、substr、substring、slice、indexOf、lastIndexOf、charAt、charCodeAt、match、search、split 和 repeat 等。
7.Promise的作用和使用?
Promise 的出现解决了 JS 的“回调地狱”问题,它可以将嵌套的回调函数调用变为链式调用,使代码更加的优雅。在实例化 Promise 的时候可以传入两个参数: resolve 和 reject 函数,resolve 用于解析流程,reject 用于拒绝流程。Promise 实例有 then 和 catch 方法,then 方法接受成功和失败回调这两个参数,失败回调非必传。then 的第 1 个参数,即成功回调,该回调的参数就是 resolve 解析出来的结果,而 then 的第 2 个参数,即失败回调,它的参数是 reject 出来的结果。then 方法可链式调用,在调用 catch 后仍可调用 then 方法。Promise 有 pending 、fullfilled 、rejected 这 3 种状态,从 pending 变为 fullfilled 、 rejected 中的 1 种状态后,其状态就不会再改变了。
Promise 的正确使用:
function asyncTask(timeout, cb) {
setTimeout(cb, timeout*100||0)
}
asyncTask(1, () => {
console.log(1);
asyncTask(2, () => {
console.log(2);
asyncTask(3, () => {
console.log(3);
})
})
});
function asyncTask$(timeout) {
return new Promise(resolve => {
setTimeout(() => {
console.log(timeout);
resolve(timeout);
}, timeout*100||0)
})
}
asyncTask$(7).then(() => {
return asyncTask$(8);
}).then((data) => {
console.log('return', data);
asyncTask$(9)
})
8.样式优先级?
!important 样式 > 内联样式 > 内部样式 > 外部样式
9.标签的顺序?
块级元素里可以放块级元素和内联元素,但内联元素里只能放内联元素。
1.<html> 下一级里面只会有 <head> 、 <body> 、<frameset>、 <noframes> 等,可视化元素只会出现在 <body> 里;
2.<ul>、<ol>、<dl>、<table>,它们的子一层必须是指定元素:<ul>、<ol> 的子一级必须是 <li>;<dl> 的子一级必须是 <dt> 或者 <dd>;<table> 的子一层必须是 <caption> 或 <thead> 、<tfoot> 、<tbody> 等,而再子一层必须是 <tr>,之后才是可放内容的 <td> 或者 <th>;
3.<h1> 、<h2> 、<h3>、<h4>、<h5>、<h6>、<caption>、<p> 等标签里面只能放内联元素。
引用自:https://blog.csdn.net/lbPro0412/article/details/82177790
10.Flex布局?
开启 Flex 布局需要指定父元素的 display 为 flex 或 inline-flex ,开启 Flex 布局以后,子元素的 float 、 clear 和 vertical-align 属性将失效。Flex 布局有主轴和交叉轴之分,当父元素的 flex-direction 为 ‘row’ 时,主轴就是行方向,交叉轴就是列方向,当父元素的 flex-direction 为 ‘column’ 时,主轴就是列方向,交叉轴就是行方向。justify-content属性定义了主轴上的对其方式,而 align-items 定义了交叉轴上的对齐方式。
详情:http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
二轮(现场笔试)
1.普通相等和严格相等(这里数字和布尔比较是我自己加上去的)
function test(v) {
if (v != null) {
console.log('a');
}
if (v !== null) {
console.log('b');
}
if (v !== undefined) {
console.log('c');
}
if (v != 0) {
console.log('d');
}
if (v >= 0) {
console.log('e');
}
if (v != false) {
console.log('f')
}
}
test(''); // a b c e
test(0); // a b c e
test(false); // a b c e
test(null); // c d e f
test(undefined); // b d f
1.普通相等比较的是字面量,严格相等先比较类型再比较字面量;
2.判断是否普通相等时,除了 null 和 undefined ,如果有一方是布尔类型或数字类型,且二者的类型不等时,那么将它们都转为数值后再比较;
3.对象和其他非对象值比较时,对象会调用 valueOf 和 toString 方法(没有 valueOf 就调 toString)再比较;
4.判断是否普通相等时,null 和 undefined 相等,但它们与其他值不相等;
5.比较大小时,null 会转为数值后再比较,而 undefined 则不会转为数值,它与其他值比大小都返回 false 。
参考:https://blog.csdn.net/qq_36605165/article/details/115922235#t15
参考:https://blog.csdn.net/me_never/article/details/102810512
2.变量提升和暂时性死区(略)
3.考察对引用类型的理解(略)
4.常见的内存泄露
全局未使用的变量或常量,闭包,循环引用(老的 IE 6 是无法处理循环引用的),移除 DOM 但未解绑事件,未清除的定时器,未捕获的异常/console.error,未清理的观察者回调等等。
5.实现对象的深拷贝方法
1.JSON.parse(JSON.stringify(obj));
2.参考网上资料写的一个深克隆:
/**
* 深克隆
* @param {*} obj 目标值
* @returns
*/
function deepClone(obj) {
// 记录每个属性值在根对象的路径
var MAP = {};
// 匹配函数
var FUN_REG = /\((.*?)\)(.|\r|\n)*?\{((.|\r|\n)*)\}/m;
/**
* 需要将目标值的路径记录到 MAP 中
* @param {*} o 目标值
* @returns
*/
function needRecord(o) {
return o && (typeof o === 'object' || typeof o === 'function');
}
/**
* 克隆
* @param {*} obj 目标值
* @param {*} path 属性的路径栈,如 var o = {a:{b:1}},
* 则 1 对应的 path 为 ['a', 'b'],它存储到 MAP 上的键值
* 对为:{'a.b': 1}
* @returns
*/
function clone(obj, path) {
if (obj == null) { return obj; }
var type = typeof obj;
// 其他非引用类型
if (type !== 'object' && type !== 'function') {
return obj;
}
// 克隆结果
var o;
// 对象构造器的名称
var cn = obj.constructor && obj.constructor.name;
// 已经克隆过,直接找到已经克隆过的对象并返回
// 将“源对象的自身属性引用”这中引用关系也克隆一下
if (obj.__cloned__) {
delete obj.__cloned__;
return MAP[path.join('.')];
}
// 标记已克隆,防止引用自身属性造成死循环克隆
Object.defineProperty(obj, '__cloned__', {
value: true,
enumerable: false,
configurable: true
});
// 当path不存在时,path 是根对象的路径栈
if (path == undefined) { path = []; }
// 克隆函数、正则、基本类型的包装类型、日期、数组和对象
if (type === 'function') {
// 函数
var fstr = obj.toString();
var matches = fstr.match(FUN_REG);
var fargs = matches[1].split(',');
var fbody = matches[3];
if (fbody) { fargs.push(fbody); }
o = Function.apply(obj, fargs);
} else if (cn === 'String' || cn === 'Boolean' || cn === 'Number') {
// 基本类型的包装类型
o = new obj.constructor(cn.valueOf());
} else if (obj instanceof RegExp) {
// 正则
var i = obj.ignoreCase;
var m = obj.multiline;
var g = obj.global;
var options = [];
if (i) { options.push('i'); }
if (m) { options.push('m'); }
if (g) { options.push('g'); }
var regStr = (obj.source).replace(/\\/g, '\\');
o = new RegExp(regStr, options.join(''));
} else if (obj instanceof Date) {
// 日期
var t = obj.getTime();
o = new Date(t);
} else if (Array.isArray(obj)) {
// 数组
o = [];
// 根对象
if (!MAP['']) { MAP[''] = o; }
obj.forEach((e, k) => {
path.push(k)
o[k] = clone(e, path);
if (needRecord(o[k])) { MAP[path.join('.')] = o[k]; }
path.pop();
});
} else {
// 对象
o = {};
// 根对象
if (!MAP['']) { MAP[''] = o; }
for (var k in obj) {
path.push(k)
o[k] = clone(obj[k], path);
if (needRecord(o[k])) { MAP[path.join('.')] = o[k]; }
path.pop();
}
}
if (obj.__cloned__) {
delete obj.__cloned__;
}
return o;
}
return clone(obj);
}
6.给出一段代码观察是否存在问题(面试官遇到的一个小BUG)
/**
* 其中 numToTix 是整数
* @param {*} obj
* @param {*} numToFix
*/
function setNum(obj, numToFix) {
if (obj.num && numToFix) {
boj.num = numToFix;
}
}
调用函数的时候,没有对参数进行校验,obj 可能是不存在的值,如 null 、undefined 等等,numToFix 也不一定是数字。
总结:调用方法时,需评估是否要对入参进行校验,如果有潜在的问题就必须校验入参!
7.函数提升(略)
8.后台数据返回了组树形结构数据,如下所示,要求将用户 ID 在 ‘uid1’、 ‘uid2’、‘uid3’ 中的用户数据的 checked 属性设为 false 。
给的数据:
var users = [
{
id: 'uid1',
name: 'xx',
checked: true,
children: [{
id: 'uid1',
name: 'xx',
checked: true,
// ...
}]
},
{
id: 'uid1',
name: 'xx',
checked: true,
children: [{
id: 'uid1',
name: 'xx',
checked: true,
// ...
}]
}
]
题解:
var uids = ['uid1', 'uid2', 'uid3'];
var blackList= new Set(uids);
function setChecked(users, blackList, flag) {
if (!users || !users.length) {return; }
users.forEach((user) => {
if (blackList.has(user.id)) {
user.checked = flag;
}
if (!user.children || !user.children.length) {return; }
setChecked(user.children, blackList, flag);
});
}
setChecked(users, blackList, false);
console.log(users);
9.在有 100 个元素的数组中随机找出 5 个不重复的元素
function getRandomNum(min, max) {
if (min > max) { return NaN; }
if (min === max) {return min; }
if (parseInt(min) === min && parseInt(max) === max) {
return ~~(Math.random() * (max - min + 1)) + min;
}
return Math.random() * (max - min) + min;
}
function get100Ele() {
var arr = [];
while(arr.length < 100) {
arr.push(getRandomNum(0, 50));
}
return arr;
}
function getNoRepeat5Elm(arr) {
arr = arr.slice();
var resArr = [];
var min = 0;
var max = arr.length - 1;
while(resArr.length < 5) {
var index = getRandomNum(min, max);
var num = arr[index];
if (resArr.includes(num)) {continue; }
resArr.push(num);
arr.splice(index, 1);
max--;
}
return resArr;
}
var res = getNoRepeat5Elm(get100Ele());
console.log(res);
10.给定一个二维网格和一个单词,找出该单词是否存在于网格中。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用(leetCode 第 79 题)。
board = [
[‘A’,‘B’,‘C’,‘E’],
[‘S’,‘F’,‘C’,‘S’],
[‘A’,‘D’,‘E’,‘E’]
]
给定 word = “ABCCED”, 返回 true
给定 word = “SEE”, 返回 true
给定 word = “ABCB”, 返回 false
给出 leetCode 上用户答案
1.用时最少
/**
* @param {character[][]} board
* @param {string} word
* @return {boolean}
*/
var exist = function (board, word) {
const dfs = function (i, j, cnt) {
if (board[i][j] != word[cnt]) return false;
if (cnt == word.length - 1) return true;
if (i > 0 && !seen[i - 1][j]) {
seen[i - 1][j] = 1;
if (dfs(i - 1, j, cnt + 1)) return true;
seen[i - 1][j] = 0;
}
if (j > 0 && !seen[i][j - 1]) {
seen[i][j - 1] = 1;
if (dfs(i, j - 1, cnt + 1)) return true;
seen[i][j - 1] = 0;
}
if (i < board.length - 1 && !seen[i + 1][j]) {
seen[i + 1][j] = 1;
if (dfs(i + 1, j, cnt + 1)) return true;
seen[i + 1][j] = 0;
}
if (j < board[0].length - 1 && !seen[i][j + 1]) {
seen[i][j + 1] = 1;
if (dfs(i, j + 1, cnt + 1)) return true;
seen[i][j + 1] = 0;
}
return false;
};
const seen = Array.from(new Array(board.length), () =>
new Array(board[0].length).fill(0)
);
for (let i = 0; i < board.length; i++) {
for (let j = 0; j < board[0].length; j++) {
seen[i][j] = 1;
if (dfs(i, j, 0)) return true;
seen[i][j] = 0;
}
}
return false;
};
2.内存最少
/**
* @param {character[][]} board
* @param {string} word
* @return {boolean}
*/
var exist = function(board, word) {
for(let i=0;i<board.length;i++){
for(let j=0;j<board[i].length;j++){
if(board[i][j] == word[0]) {
if(findWord(i,j,0)) return true
}
}
}
return false
function findWord(row,col,n){
if(row<0 || col < 0 || row >= board.length || col >= board[row].length) return false
let flag = false;
if(board[row][col] == word[n]){
if(n == word.length-1) return true
let tmp = board[row][col];
board[row][col] = '0';
flag = flag || findWord(row+1, col, n+1) || findWord(row-1, col, n+1) || findWord(row, col+1, n+1) || findWord(row, col-1, n+1);
board[row][col] = tmp;
}
return flag;
}
};
三轮(略)
1.问简历上写的东西;
2.问原型链和作用域链;
3.问 Angular 的 DI 依赖注入机制;
4.问 Angular 父子组件通信方法;
5.问 Angular 和 Vue 的优缺点;
6.问在不分页的情况下如何处理大量数据,采用虚拟滚动;
7.问如何用 CSS 实现等分布局;
8.问在线文档如何优化编辑输入,全局只存在一个 input 元素,用户点击某个格子就将该 input 元素加入这个格子;
9.问常用的数据结构,如链表和数组的区别;
10.问常用的排序算法及实现;