面试题

转自腾讯前端面试篇(一)

JS 总结之对象

王玉略的个人网站

一.有一个类如下:

1
2
3
4
functionPerson(name) {
this.name = name
}
let p = new Person('Tom');

  1. p.__proto__等于什么?

答案:
Person.prototype

  1. Person.__proto等于什么?

答案:
Function.prototype

解析:

1,2两问其实问的是同一个问题,都是考察原型链相关的知识,我们只需要记住一句话就可以迎刃而解。实例的proto属性(原型)等于其构造函数的prototype属性。实例p的构造函数为Person,而Person的构造函数为Function,结果就一目了然了。

触类旁通

1
2
3
4
5
6
7
8
9
var foo = {},
F = function(){};
Object.prototype.a = 'value a';
Function.prototype.b = 'value b';

console.log(foo.a)
console.log(foo.b)
console.log(F.a)
console.log(F.b)

这里就不给答案了,大家自己分析一下,然后再去控制台运行一下吧!冬天到了,动动手,暖一暖,有木有觉得笔者还是相当的贴心的!!!

  1. 若将题干改为
    1
    2
    3
    4
    5
    functionPerson(name) {
    this.name = name
    return name;
    }
    let p = new Person('Tom');

实例化Person过程中,Person返回什么(或者p等于什么)?

答案:

1
{name: 'Tom'}

  1. 若将题干改为
    1
    2
    3
    4
    5
    functionPerson(name) {
    this.name = name
    return {}
    }
    let p = new Person('Tom');

实例化Person过程中,Person返回什么(或者p等于什么)?

答案:

{}

解析

构造函数不需要显示的返回值。使用new来创建对象(调用构造函数)时,如果return的是非对象(数字、字符串、布尔类型等)会忽而略返回值;如果return的是对象,则返回该对象(注:若return null也会忽略返回值)。

  1. typeof和instanceof的区别

答案:

在 JavaScript 中,判断一个变量的类型尝尝会用 typeof 运算符,在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 “object”。

instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
语法:object instanceof constructor
参数:object(要检测的对象.)constructor(某个构造函数)
描述:instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。

答案是我整理后的,可能觉得我回答的并不准确,面试官又举了一个例子给我。

  1. 如果Student inherit from Person(Student类继承Person,需是基于原型的继承),let s = new Student(‘Lily’),那么s instanceof Person返回什么?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    functionPerson (name) {
    this.name = name;
    }

    functionStudent () {

    }

    Student.prototype = Person.prototype;
    Student.prototype.constructor = Student;

    let s = new Student('Tom');
    console.log(s instanceof Person); // 返回 true

答案:
true

  1. new和instanceof的内部机制

答案

  1. 创建一个新对象,同时继承对象类的原型,即Person.prototype;
  2. 执行对象类的构造函数,同时该实例的属性和方法被this所引用,即this指向新构造的实例;
  3. 如果构造函数return了一个新的“对象”,那么这个对象就会取代整个new出来的结果。如果构造函数没有return对象,那么就会返回步骤1所创建的对象,即隐式返回this。(一般情况下构造函数不会返回任何值,不过在一些特殊情况下,如果用户想覆盖这个值,可以选择返回一个普通的对象来覆盖。)

用代码来阐述

1
2
3
4
5
6
// let p = new Person()let p = (function () {
let obj = {};
obj.__proto__ = Person.prototype;

// 其他赋值语句...return obj;
})();

下面通过代码阐述instanceof的内部机制,假设现在有 x instanceof y 一条语句,则其内部实际做了如下判断:

1
2
3
4
5
6
7
8
while(x.__proto__!==null) {
if(x.__proto__===y.prototype) {
returntrue;
break;
}
x.__proto__ = x.__proto__.proto__;
}
if(x.__proto__==null) {returnfalse;}

x会一直沿着隐式原型链proto向上查找直到x.__proto__.__proto__......===y.prototype为止,如果找到则返回true,也就是x为y的一个实例。否则返回false,x不是y的实例。

触类旁通

1
2
3
4
5
6
7
functionF() {}
functionO() {}

O.prototype = new F();
var obj = new O();

console.log(obj instanceof O); // trueconsole.log(obj instanceof F); // trueconsole.log(obj.__proto__ === O.prototype); // trueconsole.log(obj.__proto__.__proto__ === F.prototype); // true

根据new 的内部机制改写上面代码

1
2
3
4
5
6
7
8
9
10
11
functionF() {}
functionO() {}

var obj = (function () {
var obj1 = {};
obj1.__proto__ = F.prototype; // new F();
O.prototype = obj1; // O.prototype = new F();
obj.__proto__ = O.prototype; // new O();
obj.__proto__ = obj1;
return obj;
})

结合instanceof内部机制很容易得出正确答案。

如果稍微调整一下代码顺序,结果将迥然不同

1
2
3
4
5
6
7
8
functionF() {}
functionO() {}

var obj = new O();
O.prototype = new F();


console.log(obj instanceof O); // falseconsole.log(obj instanceof F); // falseconsole.log(obj.__proto__ === O.prototype); // falseconsole.log(obj.__proto__.__proto__ === F.prototype); // false

具体原因,请读者自行分析,如果还是有疑问,可以在评论区提出!

其实上面很多问题都是考察原型链相关的知识,这里给出一张必须理解的原型链图,原谅我盗了一张图。

问到这里我的脑袋已经有点浆糊了,原谅我太菜了!!

8.下面代码输出什么?

1
2
3
4
5
for(var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i)
}, 0)
}

答案: 10个10

若要输出从0到9,怎么办?

答案:
将var改为let,或者使用闭包。

1
2
3
4
5
6
7
// 使用闭包for(var i = 0; i < 10; i++) {
(function (i) {
setTimeout(() => {
console.log(i)
}, 0);
})(i);
}

  1. 刚刚我们用到了箭头函数,说一下箭头函数This指向问题?

答案:
默认指向在定义它时,它所处的对象,而不是执行时的对象,定义它的时候,可能环境是window(即继承父级的this)。

如果对This还有不清楚的地方,可以参考我的另一篇文章彻底理解JavaScript中的this

  1. for…in迭代和for…of有什么区别?

答案:for…in和for…of的区别

11.说一下你对generator的了解?

答案: 建议大家查看阮一峰老师的generator相关章节

12.使用过flex布局吗?flex-grow和flex-shrink属性有什么用?

答案: flex-grow:项目的放大比例,默认为0,即如果存在剩余空间,也不放大。flex-shrink:项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。

想彻底理解flex,可以查看阮一峰老师的Flex布局教程:语法篇

  1. 说一下macrotask 和 microtask?并说出下面代码的运行结果。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    console.log('a');
    setTimeout(() => {
    console.log('b');
    }, 0);
    console.log('c');
    Promise.resolve().then(() => {
    console.log('d');
    })
    .then(() => {
    console.log('e');
    });

    console.log('f');

答案: 输出结果为 acfdeb,而关于macrotask和microtask可以继续留意笔者后篇文章,亦可自行搜索。不过可以看一下盗的一张图。

  1. Http请求中的keep-alive有了解吗。

答案:

在http早期,每个http请求都要求打开一个tpc socket连接,并且使用一次之后就断开这个tcp连接。
使用keep-alive可以改善这种状态,即在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用keep-alive机制,可以减少tcp连接建立次数,也意味着可以减少TIME_WAIT状态连接,以此提高性能和提高httpd服务器的吞吐率(更少的tcp连接意味着更少的系统内核调用,socket的accept()和close()调用)。
但是,keep-alive并不是免费的午餐,长时间的tcp连接容易导致系统资源无效占用。配置不当的keep-alive,有时比重复利用连接带来的损失还更大。所以,正确地设置keep-alive timeout时间非常重要。

  1. React中的controlled component 和 uncontrolled component区别(受控组件和不受控组件)。

答案: : 请查阅React官网受控组件非受控组件

  1. 了解过react-router内部实现机制吗?

答案:请看这篇文章react-router的实现原理

17.数组扁平化处理:实现一个flatten方法,使得输入一个数组,该数组里面的元素也可以是数组,该方法会输出一个扁平化的数组。

1
2
3
4
5
// Examplelet givenArr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10];
let outputArr = [1,2,2,3,4,5,5,6,7,8,9,11,12,12,13,14,10]

// 实现flatten方法使得
flatten(givenArr)——>outputArr

年轻的我是用递归实现的QAQ,我的答案

1
2
3
4
5
6
7
8
9
10
11
functionflatten(arr){
var res = [];
for(var i=0;i<arr.length;i++){
if(Array.isArray(arr[i])){
res = res.concat(flatten(arr[i]));
}else{
res.push(arr[i]);
}
}
return res;
}

其实你还可以这样

1
2
3
4
5
functionflatten(arr){
return arr.reduce(function(prev,item){
return prev.concat(Array.isArray(item)?flatten(item):item);
},[]);
}

还可以使用ES6拓展运算符

1
2
3
4
5
6
functionflatten(arr){
while(arr.some(item=>Array.isArray(item)){
arr = [].concat(...arr);
}
return arr;
}

18.如果在17问的前提下,要做去重和排序处理又该怎么做(不用给出具体代码)。

答案:最好封装一个数组方法的类,该类包含flatten(扁平化)、sort(排序)和unique(去重)等方法。


vue生产环境清除打印

第一种方法
我们可以直接在 src/main.js 里添加如下代码:

1
2
3
4
window.log = console.log;
if (process.env.NODE_ENV === 'production') {
window.log = ()=>{};
}

它会判断当前环境是开发环境还是生产环境,如果是生产环境,则将window.log 赋值给一个空函数。

需要注意的是,为了不影响生产环境原生window.console.log的使用,所以这里定义了一个window.log方法用来打印,所以以后代码里的打印用 log 而不是用 console.log。

第二种方法
第二种方法则更加简单,其实在vue的手脚架中可以添加相应的配置项,

在 build/webpack.config.js 的 plugins 的 UglifyJsPlugin 里面添加如下配置项:

1
2
3
4
5
6
new webpack.optimize.UglifyJsPlugin({
compress:{
warnings: false,
drop_console: true //添加这行配置项
}
})

JS实现复制到粘贴板

1
2
3
4
5
6
7
8
9
10
<script type="text/javascript">
function copyUrl()
{
var Url2=document.getElementById("biao");
Url2.select(); // 选择对象
document.execCommand("Copy"); // 执行浏览器复制命令
}
</script>
<textarea cols="20" rows="10" id="biao">用户定义的代码区域</textarea>
<input type="button" onClick="copyUrl()" value="点击复制代码" />