前端JS: 常见代码输出题

4 阅读4分钟

前端常见代码输出题(附解析)

一、作用域与闭包

1. 变量提升

console.log(a);
var a = 10;
console.log(a);
// 输出:undefined, 10
// 解析:变量提升,相当于 var a; console.log(a); a = 10; console.log(a);

2. 函数与变量提升

console.log(foo);
var foo = 10;
function foo() {}
console.log(foo);
// 输出:ƒ foo() {}, 10
// 解析:函数声明提升优先级高于变量

3. 块级作用域

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// 输出:3, 3, 3
// 解析:var 没有块级作用域,三次循环共用同一个i

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// 输出:0, 1, 2
// 解析:let 有块级作用域,每次循环都是新的i

4. 闭包

function outer() {
  var a = 1;
  return function inner() {
    console.log(a);
  };
}
var fn = outer();
var a = 2;
fn(); // 输出:1
// 解析:闭包保存了外部函数的作用域

二、this指向

1. 普通函数调用

var name = 'global';
function foo() {
  console.log(this.name);
}
var obj = {
  name: 'obj',
  foo: foo
};
foo();         // 输出:'global' (非严格模式) / undefined (严格模式)
obj.foo();     // 输出:'obj'
var bar = obj.foo;
bar();         // 输出:'global'

2. 箭头函数

var name = 'global';
var obj = {
  name: 'obj',
  foo: () => console.log(this.name),
  bar: function() {
    return () => console.log(this.name);
  }
};
obj.foo();       // 输出:'global' (箭头函数this指向定义时的外层作用域)
obj.bar()();     // 输出:'obj' (箭头函数在bar内定义,this指向obj)

3. 构造函数

function Person(name) {
  this.name = name;
  this.say = function() {
    console.log(this.name);
  };
}
var p1 = new Person('Tom');
p1.say(); // 输出:'Tom'

三、原型与继承

1. 原型链

function Person() {}
Person.prototype.name = 'Person';
var p = new Person();
console.log(p.name); // 输出:'Person'
console.log(p.__proto__ === Person.prototype); // 输出:true
console.log(Person.prototype.__proto__ === Object.prototype); // 输出:true
console.log(Object.prototype.__proto__); // 输出:null

2. 原型与实例属性

function Foo() {
  this.name = 'instance';
}
Foo.prototype.name = 'prototype';
var f = new Foo();
console.log(f.name); // 输出:'instance'
delete f.name;
console.log(f.name); // 输出:'prototype'

四、异步编程

1. Event Loop基础

console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
// 输出:1, 4, 3, 2
// 解析:同步任务 > 微任务 > 宏任务

2. 复杂Event Loop

console.log('start');

setTimeout(() => {
  console.log('timeout1');
  Promise.resolve().then(() => console.log('promise1'));
}, 0);

setTimeout(() => {
  console.log('timeout2');
  Promise.resolve().then(() => console.log('promise2'));
}, 0);

Promise.resolve().then(() => console.log('promise3'));

console.log('end');
// 输出:start, end, promise3, timeout1, promise1, timeout2, promise2

3. async/await

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
async function async2() {
  console.log('async2');
}
console.log('script start');
setTimeout(() => console.log('setTimeout'), 0);
async1();
new Promise(resolve => {
  console.log('promise1');
  resolve();
}).then(() => console.log('promise2'));
console.log('script end');
// 输出:script start, async1 start, async2, promise1, script end, async1 end, promise2, setTimeout

五、类型转换

1. 双等号比较

console.log([] == ![]);     // 输出:true
console.log({} == !{});     // 输出:false
console.log(null == undefined); // 输出:true
console.log('0' == false);  // 输出:true

2. 复杂类型转换

console.log([] + []);        // 输出:''
console.log([] + {});        // 输出:'[object Object]'
console.log({} + []);        // 输出:0 (在console中可能是'[object Object]')
console.log({} + {});        // 输出:'[object Object][object Object]'

六、综合题

1. 综合作用域与异步

for (var i = 0; i < 5; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(new Date(), j);
    }, 1000);
  })(i);
}
console.log(new Date(), i);
// 输出:5, 0, 1, 2, 3, 4 (先立即输出5,1秒后输出0-4)

2. 综合原型与this

function Foo() {
  getName = function() { console.log(1); };
  return this;
}
Foo.getName = function() { console.log(2); };
Foo.prototype.getName = function() { console.log(3); };
var getName = function() { console.log(4); };
function getName() { console.log(5); }

Foo.getName(); // 输出:2 — 解释:直接调用Foo函数对象的静态属性getName,该函数输出2。

getName(); // 输出:4 — 解释:全局getName在变量提升后被赋值为输出4的函数,因此输出4。

Foo().getName(); // 输出:1 — 解释:Foo()调用中修改全局getName为输出1的函数,并返回全局对象,因此调用全局getName输出1。

getName(); // 输出:1 — 解释:由于上一步全局getName已被修改,此处输出1。

new Foo.getName(); // 输出:2 — 解释:运算符优先级使new调用Foo.getName作为构造函数,执行该函数输出2。

new Foo().getName(); // 输出:3 — 解释:先new Foo()创建实例,实例调用getName从原型链找到Foo.prototype.getName,输出3。

new new Foo().getName(); // 输出:3 — 解释:先new Foo()创建实例,获取实例的getName(即原型方法),再new调用该函数作为构造函数,输出3。

七、ES6+特性

1. let的暂时性死区

console.log(a); // 报错:Cannot access 'a' before initialization
let a = 1;

2. 解构赋值

let {a, b = 2} = {a: 1};
console.log(a, b); // 输出:1, 2

let [x, ...y] = [1, 2, 3];
console.log(x, y); // 输出:1, [2, 3]

解题技巧

牢记执行顺序:同步 → 微任务 → 宏任务

理解作用域链:从内向外查找变量

掌握this指向规则:默认绑定、隐式绑定、显式绑定、new绑定

熟悉原型链查找:沿着__proto__向上查找

注意变量提升:函数声明 > 变量声明

理解闭包本质:函数保存了定义时的作用域

熟悉类型转换规则:ToPrimitive, ToNumber, ToString

掌握ES6新特性:let/const、箭头函数、解构等

建议在理解这些题目后,尝试自己修改代码,预测输出结果,再验证实际运行结果,加深理解。