JS进阶篇(前端面试题整合)(三)

本文最后更新于:6 个月前

最近刷面试题遇到了一些比较有意思的题目,做个记录(题目均来源于牛客网,解析均是本人的理解,有任何问题欢迎在评论区指出)

Q1:请问以下两次检测对象constructor是否拥有属性名1的结果分别是什么?

1 in Object(1.0).constructor;
Number[1] = 123;
1 in Object(1.0).constructor;

A:false、true

解析:

首先可以认识一下 “in” :用于检查对象(数组)及其原型链中是否含有某属性,如

const arr = [0,2]
1 in arr // true

这里表示的是arr这个数组中存在 1 这个属性,即arr[1]

回归正题: Object(1.0) 相当于 new Number(1.0) ,new Number(1.0).constructor就拿到了Number构造函数,此时Number上还没有 1 这个属性,所以使用 in 返回false,而下一步中增加了这个属性,所以返回true

Q2:下面这段程序的显示结果是?

var x = new Boolean(false);
if (x) {
  alert('hi'); 
}
var y = Boolean(0);
if (y) {
  alert('hello');  
}

A:hi

解析:

Boolean(0)返回一个布尔类型的false
而new Boolean(false)返回一个布尔对象

Q3:以下哪些对象是Javascript内置的可迭代对象?

Array Map String Object

A:Array Map String

解析:

ES6中对可迭代的定义:一种数据结构存在Symbol.iterator属性,就表示可迭代
阮一峰的博客中还提到了以下可迭代的数据类型

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

Q4:以下代码执行后,输出结果为

let x = 10;
let foo = () => {
    console.log(x);
    let x = 20;
    x++;
}
foo();

A:抛出ReferenceError

解析:使用let定义变量会形成 “暂时死区” 即foo作用域内获取的x就是等于20的那个,然而使用let去定义变量,变量不会得到提升,所以在定义前获取x会报错

Q5:请问以下JS代码会输出什么

var a = 10; 
(function a() {
    a = 20;
    console.log(a); 
})()

A:输出函数a的内容

解析: 立即执行函数中的a访问到的是函数a,当函数为IIFE的函数表达式时,与使用const类似(即无法修改这个值),与函数声明( function fun(){} )不相同,当执行a=20时,无法修改a的值

Q6:以下代码执行后,输出结果为

var a = 10;
function a(){}
console.log(typeof a)

A:输出 “number”

解析:我的理解是var会将声明提升到作用域的最高点,function会在js编译时加载在内存中
可以简单理解为:函数声明大于变量声明大于变量赋值,所以原式转换成了下面这段代码

function a(){}
var a;
a = 10;
console.log(typeof a)

Q7:以下JavaScript代码,在浏览器中运行的结果是

var foo = {n:1};
(function(foo){
    console.log(foo.n);
    foo.n = 3;
    var foo = {n:2};
    console.log(foo.n);
})(foo);
console.log(foo.n);

A: 1 2 3

解析:根据var的变量提升,可以得到以下代码;

var foo = {n:1};
(function(foo){
    var foo = foo // 这一步是参数(形参)的声明,赋值
    var foo // 此操作优先级没有参数高,所以不生效
    console.log(foo.n);// 此时foo指代参数,n为1
    foo.n = 3; // 参数引用了全局的foo,所以全局的foo此时已经是3了
    foo = {n:2}; // 给参数重新赋值,相当于指向了一块新内存
    console.log(foo.n); // 打印新内存的值:2
})(foo);
console.log(foo.n);

Q8:请问以下JS代码的输出结果以及变量i的值是?

var i = 100;
function foo() {
    bbb: try {
        console.log("position1");
        return i++;  }
    finally {
        break bbb;
    }
    console.log("position2");
    return i;
}
foo();

A:position1、position2、101

解析:在try中执行return不会打断finally的执行,但是没有打断i++执行,因此执行完try中的语句后,通过break bbb跳出到foo中的bbb级代码块,继续打印下面的position2

Q9:请问以下JS代码输出的结果是什么?

let obj = {
  num1: 117
}
let res = obj;// -----------1
obj.child = obj = { num2: 935 };// -----------2
var x = y = res.child.num2;// ----------3
console.log(obj.child);
console.log(res.num1);
console.log(y);

A:undefined、117、935

解析:这道题主要考察两点:

1.引用类型赋值

2.连续赋值机制

下面我对1-3步做一个分析:

第1步在栈中新建res变量使其引用地址指向obj,即与其共用一个堆地址(这一步可以看做是题目中对第二步的一个伏笔);

第2步是一个连续复制,赋值从右往左看,理解为以下代码,因为在赋值时obj指向了新的堆地址,obj.child中的obj已经不是赋值后的obj了,可以把它暂时看作是res

obj = { num2: 935 };
res.child = obj;

那么此时 res 就是{child: {num2:935}, num1: 117 },obj 就是{ num2: 935 };

第3步,连续复制,理解为以下代码

window.y = res.child.num2;
var x = window.y

此时res.child.num2是935,所以x和y都是935

Q10:在浏览器控制台中执行以下代码,输出的结果是

function test() {
    var n = 4399;
    function add(){
        n++;
        console.log(n);
    }
    return {n:n,add:add}
}
var result = test();// ------1
var result2 = test();// ------2
result.add();// ------3
result.add();// ------4
console.log(result.n);// ------5
result2.add();// ------6

A:4400 4401 4399 4400

解析:这道题考察闭包和函数作用域,首先咱得理解的是通过步骤1和2,产生的result和result2是两个不同对象(两个对象指向的堆地址不是一个,虽然内容一样),其次要了解的是test函数和add函数之间产生了闭包关系,所以执行3和4步骤后,n发生了变化,然而值得注意的是,此时的result.n却没有发生变化,这是因为在test函数作用域中,n作为一个基本类型的值随着函数执行完后随着return返回出来了,此时在add中修改的n与result.n已没有关联了。为了更好理解上述说法,我修改了代码,得出以下代码:

function test() {
    var n = {num:4399};
    function add(){
        n.num++;
        console.log(n);
    }
    return {n:n,add:add}
}
var result = test();
var result2 = test();
result.add() // {num: 4400}
result.add() // {num: 4401}
console.log(result.n) // {num: 4401}

这样写应该会有助于理解

言归正传,执行第5步时就是打印最早传入的n(4399),第六步由于上面解析中提到的步骤1和2,产生的result和result2是两个不同对象,所以,可以理解为result2也来了一次步骤3