前端常见代码输出题(附解析)
一、作用域与闭包
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、箭头函数、解构等
建议在理解这些题目后,尝试自己修改代码,预测输出结果,再验证实际运行结果,加深理解。