几道JS面试题-吉祥三宝

现在坐在去北京的高铁上,刚才玩了两把极品飞车,配着列车的座位和颠簸,很带感!眼睛累了,最近确实比较忙,又是拖了好久就想写的博客,在这个特殊的地方,强烈抵制自己的拖延症!

上一篇JS面试题都是基本数据类型,同一个人面试的话不会问太多,而JS中的原型、作用域和闭包是面试官问的最多的,我称之为“吉祥三宝”。

作用域

说白了就是某个函数执行时this指向谁的问题。

问题1:

1
2
3
4
5
6
7
8
var obj = {
run: function(){
function test(){ alert(this); }
test();
}
};

obj.run();

obj.run()进入函数主体时,因为调用者是obj,因此run()内部this是指向obj的。但是run()内部又定义了一个function,直接执行了test(),因此test()内部的this未显式指定,this默认指向就是window

问题2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var name = 'window';
var obj = {
name: 'obj',

child: {
// name: 'child',

getName: function () {
return this.name;
}
}

};

var getName = obj.child.getName;
alert(getName());
alert(obj.child.getName());

因为直接把obj.child.getName赋给了getName变量,因此getName就是个function,直接调用getName()时其内部this指向的是默认值window,因此alert结果是window

而调用obj.child.getName()时,指定了调用者对象,因此函数内部的this指向obj.child对象。由于child里面的name给我注释掉了,因此obj.child.getName()里访问不到this.name,就alert出undefinedobj.name是用来迷惑的)。如果将此注释去掉的话,alert结果就是child

问题3:

1
2
3
4
5
6
7
8
9
10
11
12
13
function a(x, y){
y = function(){
x = 2;
};

return function(){
var x = 3;
y();
console.log(x);
}.apply(this, arguments);
}

a();

这题是在ruanyf老师的微博上看到的,意思是很难的一道面试题,在console里试了下才想通了结果。

执行a()未传入实际参数,所以a()刚进入时,x`y都是undefined。然后对y变量赋值成一个函数,函数内部将改变x的值,这里的x就是a函数定义时的形式参数x`。

函数定义时括号里的叫形式参数,JS中函数调用时传入的实际参数可与形式参数数目不一致。arguments指的是实际参数,因此a()里面this指向window,而arguments就是空数组。

然后执行a()里面的匿名函数,重新定义了局部变量x = 3,然后调用匿名函数外部的y()。而上面已说y内部改变的是a函数的形式参数x的值,因此不会影响匿名函数内部的x,所以输出结果就是3。这里也可以用闭包来解释,console.log(x)看到的就是匿名函数内部的变量xy函数内部看不到匿名函数内部的变量。

原型

原型prototype是用来实现JS中的对象继承的,具体可看我以前这篇Javascript模式之五-代码复用模式

问题1:

1
2
3
4
5
6
7
8
var A = function(){
this.name = 'A';
};
A.prototype.say = function(){
alert(this.name);
};

var B = function(){ };

Q1:写段代码让B继承A

1
2
3
var F = function(){};
F.prototype = A.prototype;
B.prototype = new F();

我使用了圣杯模式的思想来继承,通过一个中间的空函数,使得B的原型对象实例最小,不会包含A的实例变量name

Q2:下面输出结果是多少?

1
2
var objB = new B();
objB.say();

如果按照我上面那种继承方法的话,由于B的原型对象其实F的实例(F的原型指向A的原型),F的实例中并没有name属性,所以objB.say()访问不到this.name,结果就是undefined啦。

如果上面的继承代码改成B.prototype = new A()就这一句,那么B的原型对象就是A的实例,因此B的原型中就有A的实例属性name,所以objB.say()能访问到this.name

问题2:

1
2
3
4
5
6
Function.prototype.f = function(){};
Object.prototype.o = function(){};

function Factory(){}

var car = new Factory();

Q:能调用car.f()car.o()吗?

首先car是个new出来的对象,而JS中所有对象的原型链追溯到顶层都是Object,即所有对象都继承自Object,因此car.o()肯定没问题。

typeof carobject,而car.constructorFactorytypeof Factoryfunction,而JS中所有function的constructor默认都是Function,因此Factory.f()是能调用到的。即car.constructor.f()能调用到,而car.f()是无法调用到的。

闭包

面试中问闭包问的最多的就是循环引用问题,我在使用闭包解决循环引用问题这篇文章中已经列出很多例子了。比如,有一个数组var array = [1, 2, 3, 4],请每隔1秒依次输出数组中的元素。这里就不多写了。

总结

JS中的原型、作用域和闭包是非常重要的,这是语言机制,也是这个语言的魅力。有了扎实的基础,再去理解模块化就会容易些,然后再了解一些RequireJS,就能得到面试官的喜欢,至少基础关没太大问题。