常见JS面试题
第一题
// 作用域、this、变量提升
var a = 10;
function test(){
a = 100;
console.log(a); // 100
console.log(this.a);// 10
var a;
console.log(a);// 100
}
test();
在JS中定义的函数内部使用var进行变量的定义,该变量存在变量提升,即在函数内部,JS解释器执行代码顺序为:
function test(){
var a;
a = 100;
console.log(a); // 100
console.log(this.a);// 10
console.log(a);// 100
}
test();
所以第一、第三个console.log(a)打印出来的值为100;
而console.log(this.a),此时要想知道这个值打印出来的结果,就得搞清楚this的指向问题,在本题中,test()函数无调用者,所以此时的this指向window,所以该处打印结果应为10;
function test(){
a=100;
console.log(a);
var a;
}
test();
function test(){
console.log(a);
var a = 100;
}
test();
前者打印出来的a值为100,而后者打印出来的结果为undefined。此时可能会有同学有疑问,不是var存在变量提升吗,为什么会出现undefined呢,var的变量提升只是将变量的定义声明放在了最顶部,此时JS解释器执行代码的顺序如下;
function test(){
var a;
console.log(a);
a = 100;
}
test();
第二题
// 自执行函数、作用域
(function(){
var a=b=3; // b = 3; var a = b;
})()
console.log(b); // 3
console.log(a); // 报错
代码中var a=b=3;的代码实际意思相当于: b = 3; var a = b;
其中b并没有使用var定义,故b属于全局变量,而a属于函数内部声明变量,所以在函数外部访问不了a,能访问b。
第三题
// 事件循环、单线程、异步、等待队列
for( var i = 0;i < 3; i++){
setTimeout(function(){
console.log(i);
},0)
}
本题输出结果为三个3,执行顺序为for主线程–setTimeout异步(放到等待队列)–继续执行for主线程直到执行完毕–执行setTimeout(此时i的值为3),故打印三个3。
若想要输出0,1,2,代码应进行如下修改:
for( let i = 0;i < 3; i++){
setTimeout(function(){
console.log(i);
},0)
}
此时将var换为let,此时的let定义的i存在块级作用域,var定义的i为全局作用域,所以在for循环进行时会将前一个i 的值给覆盖掉,而let定义的i因为其存在块级作用域则不存在这个问题,所以能输出0,1,2。
第四题
function fun(n){
console.log(n); // 123
var n = 456;
console.log(n); // 456
}
var n = 123;
fun(n);
该题考察点依旧是变量提升、作用域,逻辑比较简单,认真理解前面的题之后本题应该不成问题。以此输出为123、456。
function fun(){
console.log(fun);
fun = 456;
console.log(fun);
}
fun();
var fun = 123;
第五题
//预解析、作用域
var n = 123;
function f1(){
console.log(n);
}
function f2(){
var n = 456;
f1();
}
f2(); // 123 此时无调用者 window
console.log(n); // 123
第六题
var length = 100;
function f1(){
console.log(this.length);
}
var obj = {
x:10,
f2: function(f1){
f1(); // 100 无调用者 window
arguments[0](); // 2 arguments[0] == f1
}
}
obj.f2(f1,1);
arguments为JS内置函数,包含函数的实参,其作用域为arguments对象,故输出为arguments.length === 2;
第七题
function f(){
console.log(this.a);
}
var obj = {
a:10,
f:f
}
var f2 = obj.f;
var a = 'heelo';
f2(); // 无调用者, window this指向window heelo
obj.f(); // 10 此时调用者为obj,故this指向obj
第八题
// apply call -->第一个参数作用改变this指向,后面的参数为函数实参
function f(s){
console.log(this.a,s); // 2,3
return this.a + s; // 5
}
var obj = {
a:2
}
var f2 = function(){
return f.apply(obj,arguments);
}
var b = f2(3);
console.log(b); // 5
此题目关键在于对于apply函数的理解,使用apply函数的时候,第一个参数作用是改变调用者的this指向,后面的数组参数则是传入调用者的实参;
那么有的同学可能会有疑惑,apply和call的区别在哪里呢,其实就在于它们所需要传入的参数不同:
apply(obj, arguments):apply接收的第一个参数用于改变调用者的this指向,接收的第二个参数则是数组形式存在([1,2,3])
call(obj,…arguments):call接收的第一个参数同样用于改变调用者的this指向,但call接收的后面的参数则是直接接收(call(obj,1,2,3);这就是call和apply的区别。
那么对于这道题,如果把apply改为call的话,如果要得到相同的结果,那么需要怎么更改呢?
// apply call -->第一个参数作用改变this指向,后面的参数为函数实参
function f(s){
console.log(this.a,s); // 2,3
return this.a + s; // 5
}
var obj = {
a:2
}
var f2 = function(){
return f.apply(obj,...arguments); // return f.apply(obj,arguments[0]);
}
var b = f2(3);
console.log(b); // 5
数组去重
// Set函数去重
var a = [1,2,2,2,5,5,7,7]
console.log( new Set(a));
// 两次循环去重
var a = [1,2,2,2,5,5,7,7]
function uni(a){
for(var i = 0;i<a.length;i++){
for(var j = i + 1;j<a.length;j++){
if(a[i] === a[j]){
a.splice(j,1);
j--;
}
}
}
return a;
}
console.log(uni(a));
// indexOf去重
var a = [1,2,2,2,5,5,7,7]
function uni(a){
var arr = [];
for(var i = 0;i < a.length; i++){
if(arr.indexOf(a[i]) === -1){
arr.push(a[i]);
}
}
return arr;
}
console.log(uni(a));
// includes去重
var a = [1,2,2,2,5,5,7,7]
function uni(a){
var arr = [];
for(var i = 0;i < a.length; i++){
if(arr.includes(a[i]) === -1){
arr.push(a[i]);
}
}
return arr;
}
console.log(uni(a));