F-One 2021/03/17 面试

一轮(电话面)

先问简历中的项目,问在项目中做了些什么,用到了哪些技术。然后问了一些基础题:

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 的时候可以传入两个参数: resolvereject 函数,resolve 用于解析流程,reject 用于拒绝流程。Promise 实例有 thencatch 方法,then 方法接受成功和失败回调这两个参数,失败回调非必传。then 的第 1 个参数,即成功回调,该回调的参数就是 resolve 解析出来的结果,而 then 的第 2 个参数,即失败回调,它的参数是 reject 出来的结果。then 方法可链式调用,在调用 catch 后仍可调用 then 方法。Promisependingfullfilledrejected 这 3 种状态,从 pending 变为 fullfilledrejected 中的 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 布局需要指定父元素的 displayflexinline-flex ,开启 Flex 布局以后,子元素的 floatclearvertical-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.问常用的排序算法及实现;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值