2017年8月面试题

饿了么

1.箭头函数和普通函数的区别

箭头函数中的 this 和调用时的上下文无关;

箭头函数在定义之后,this 就不会发生改变了,无论用什么样的方式调用它,this 都不会改变;
但严格来说,这并不是“取决于定义时的上下文”, 因为箭头函数根本就没有绑定自己的 this,在箭头函数中调用 this 时,仅仅是简单的沿着作用域链向上寻找,找到最近的一个 this 拿来使用罢了;
从效果上看,这和我的理解并没有多大偏差,但它们的本质却是截然不同,箭头函数并不是普通函数新增了 this 不受调用时上下文影响的特性,而是减少了很多特性;
实际上箭头函数中并不只是 this 和普通函数有所不同,箭头函数中没有任何像 this 这样自动绑定的局部变量,包括:this,arguments,super(ES6),new.target(ES6)……
所以我个人认为箭头函数更适合函数式编程,除了它更短以外,使用箭头函数也更难被那些没有显示声明的变量影响,导致你产生意料之外的计算结果;
如果是像当初的我一样简单的考虑固定住 this 这个易变的家伙……那倒是很简单,有些常用的方法,比如这样:

1
2
3
4
5
6
function make() {
var self = this;
return function() {
console.log(self);
};
}

或者

1
2
3
4
5
function make() {
return function() {
console.log(this);
}.bind(this);
}

然而第二种方法只能固定 this 这一个变量而已,如前文所述,箭头函数中的 arguments 等变量也是从作用域链中寻找的,为了实现类似的效果,我们只有重新定义一个局部变量这一种方式,而 babel 也是使用这种方式对箭头函数进行处理的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function make() {
return () => {
console.log(this);
console.log(arguments);
};
}
//babel it...
function make() {
var _this = this,
_arguments = arguments;
return function() {
console.log(_this);
console.log(_arguments);
};
}

2.箭头函数可以用作构造函数吗?

不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
使用箭头函数时需要注意:

(1)函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。

(3)不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 Rest 参数代替。

(4)不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。

this 指向的固定化,并不是因为箭头函数内部有绑定 this 的机制,实际原因是箭头函数根本没有自己的 this,导致内部的 this 就是外层代码块的 this。正是因为它没有 this,所以也就不能用作构造函数。
除了 this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments、super、new.target。

参考

3.js 中有哪些改变函数作用域的方法?

call(),apply(),bind()
例如,有一个函数 func1 定义如下:

1
var func1 = function(arg1, arg2) {};

就可以通过 func1.call(this, arg1, arg2); 或者 func1.apply(this, [arg1, arg2]); 来调用。其中 this 是你想指定的上下文,他可以任何一个 JavaScript 对象(JavaScript 中一切皆对象),call 需要把参数按顺序传递进去,而 apply 则是把参数放在数组里
是如果我们有一个对象 whiteDog = {food:”bone”},我们不想对它重新定义 say 方法,那么我们可以通过 call 或 apply 用 blackCat 的 say 方法:blackCat.say.call(whiteDog);所以,可以看出 call 和 apply 是为了动态改变 this 而出现的,当一个 object 没有某个方法,但是其他的有,我们可以借助 call 或 apply 用其它对象的方法来操作。

bind:

1
obj.bind(thisObj, arg1, arg2, ...);

把 obj 绑定到 thisObj,这时候 thisObj 具备了 obj 的属性和方法。与 call 和 apply 不同的是,bind 绑定后不会立即执行。

同样是 add()和 sub():

add.bind(sub, 5, 3); //不再返回 8
add.bind(sub, 5, 3)(); //8
如果 bind 的第一个参数是 null 或者 undefined,等于将 this 绑定到全局对象。

4.bind()返回的是什么,写一个 bind 的 ployfill

bind()返回了一个函数,《你不知道的 javascript》93 页

5.vue 父子组件通信

vue 双向数据绑定
vue 组件通信、数组值的修改

6.position 有哪几种,分别相对谁定位,哪些脱离了文档流?

position 的值为 absolute、fixed 的元素脱离文档流,static、relative 没有脱离文档流

客如云

1.谈一谈 react 的原理和它的整个生命周期是什么样的?每个生命周期可以做些什么?

2.react 比 jQuery 好在哪?

3.浏览器的卡顿通常原因是什么?react 性能优化有哪些方法?

4.javascript 的错误类型有哪些?

Error 对象有两个属性:message,name

ECMA-262 定义了下列 7 种错误类型:

1
2
3
4
5
6
7
Error;
EvalError(eval错误);
RangeError(范围错误);
ReferenceError(引用错误);
SyntaxError(语法错误);
TypeError(类型错误);
URIError(URI错误);
1
2
3
4
5
var a = new Array(-1) // Uncaught RangeError: Invalid array length
console.log(b) // Uncaught ReferenceError: b is not defined
var 1c // Uncaught SyntaxError: Invalid or unexpected token
var d = new 10 // Uncaught TypeError: 10 is not a constructor
decodeURI('%2') // Uncaught URIError: URI malformed

URI 错误:
encodeURI()、decodeURI()、encodeURIComponent()、decodeURIComponent()、escape()和 unescape()

另外还有 try…catch 相关

参考

5.谈一谈 javascript 这门语言和其他语言的区别?

python 与 javascript 比较

6.javascript 的 function 有哪些作用?

1.关键字,声明一个函数时使用

Function 类型

2.创建一个函数作用域

涉及概念:立即执行函数,闭包

3.创建一个对象

4.匿名函数

7.translateX 和 position 调整位置有什么区别?

position 会重新渲染文档

100 课堂

1.http、https 和 websocket 区别?

2.前后端分离后的协作方式?get 请求和 post 请求区别?

3.git 你常用哪些命令?前端项目打包上线你是怎么做的?

4.对 linux 命令行有多少了解?要建立一个 a->b->c 这样一级级的目录怎么创建?

5.canvas 在什么时候会开启 GPU 渲染?

6.你现在的项目中有用到哪些 html5 标签?它们有什么含义?

7.css poisition、float 一系列相关

CSS 优先级

position:absolute; 生成绝对定位的元素,相对于 static 定位以外的第一个有定位的父元素进行定位。

z-index 相关

清除浮动:

1
2
3
4
5
6
.clearfix {
*zoom: 1; //兼容IE, 加*是hack写法,只有IE6、7识别
&:after{
clear:both;// 项目中还额外增加了一些属性
}
}

8.javascript 相关问题

1)true or false

1
2
3
4
[]==[] // false
[]===[] // false
NaN==NaN // false
NaN===NaN // false

===宽松相等,==严格相等。首先要注意并不是===做的工作更多,其实是==在发现左右两边类型不同时,先进行强制类型转换再比较。

1、2 是由于[]数组是对象,对象的==比较会比较对象的引用,即比较两个对象是否是同一个对象的引用
3、4 很特殊,因为 NaN 的特殊,它不等于自身!

2)如何判断是否为数组?如何判断数组为空?如何判断数组相等?如何判断对象相等?

3)数组有哪些方法?

4)闭包

1
2
3
4
5
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}

这是闭包的经典问题,这个问题及其答案的完整叙述如下:

写一段代码,第一秒输出 1,第二秒输出 2,以此类推,到 5。

新手会写出下面的代码

1
2
3
4
5
6
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, 1000);
}
// 1秒后一次5个6

或者稍好点的这种

1
2
3
4
5
6
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
// 每秒1个6

上面的代码之所以不能达到我们的效果,涉及到的是 javascript 的作用域这个知识点。上面每个延迟函数的回调(setTimeout 中的 timer)其实都只在一个共享的全局作用域中,因此也就共享了一个 i 的引用。本质上等价于:不使用循环,将延迟函数中的回调重复定义 5 次。

所以解决的思路是:5 次的回调,每次都用一个自己的作用域,并在这个作用域中有一个 i 的副本。这样理解之后或许会出现下面的代码,利用 IIFE(立即执行函数)来创建函数作用域

1
2
3
4
5
6
7
8
for (var i = 1; i <= 5; i++) {
(function() {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
})();
}
// 每秒1个6,用立即执行函数来创建一个函数作用域,创建了作用域,但是是空的(没有i的副本)

于是稍作修改:

1
2
3
4
5
6
7
8
for (var i = 1; i <= 5; i++) {
(function() {
var j = i;
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})();
}

改进上面的方法:

1
2
3
4
5
6
7
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
}

其实我们本质上需要的是每次循环都能创建一个新的作用域,它并不一定非要是函数作用域,我们不使用 IIFE,而使用块级作用域,let 就可以声明一个被限制在块级中的变量

1
2
3
4
5
6
for (var i = 1; i <= 5; i++) {
let j = i;
setTimeout(function timer() {
console.log(j);
}, j * 1000);
}

还有更酷的做法:

1
2
3
4
5
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}

上面的代码看上去理所应当,不过正确的解释是:
for 循环头部的 let 声明有一个特殊行为。这个行为指出变量在循环中不止被声明一次,每次迭代都会声明,随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。

其实还有个解法:

1
2
3
4
5
6
7
8
9
for (var i = 1; i <= 5; i++) {
setTimeout(
function timer(j) {
console.log(j);
},
i * 1000,
i
);
}

这是由于 setTimeout 其实还可以接收参数传给回调,而每个参数 j 其实是不同作用域中的

5)setTimeout

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log(1);
setTimeout(function() {
console.log(2);
}, 0);
console.log(3);
// 1, 3, 2

console.log(1);
setTimeout(function() {
console.log(2);
});
console.log(3);
// 1, 3, 2

不传参的话,浏览器会默认一个最小执行时间,Firefox 是 100ms,其他浏览器一般是 10ms,都比 0 大。所以,主要解释第一段代码的输出结果,大都数开发者或许就能说出两个字“异步”。

而这个题主要考察的是对 setTimenout 的理解,进而引申出对 javascript 单线程的思考:

JavaScript 引擎是基于事件驱动单线程执行的,JavaScript 引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个 JavaScript 线程在运行 JavaScript 程序。

setTimeout(…,0)的作用其实是做了一个异步调度,可以理解为“把这个函数插入到当前事件循环的队列结尾处,0 秒后执行”。

其实这个理解还需要一点补充:延迟并不会被设置成 0 秒,setTimeout 有一个最小执行时间,正如之前提到的;也并不是立即被放到队尾,而是程序自己找一个合适的时机,因此若有两个 setTimeout(…,0)并不知道谁先执行。