编码与实现
约 2330 字大约 8 分钟
2025-11-24
变量提升与优先级
下面的代码执行结果是什么?
这段代码考察变量提升与优先级:函数声明会被提升到作用域顶部,var 也会被提升,但函数优先级更高,不会覆盖已经存在的同名函数声明。所以上面的代码在执行上下文阶段等效以下代码:
function foo() {
console.log(1);
}
// var foo; ← 被忽略,因为 foo 已由函数声明定义
// 调用函数
foo();foo 调用时指向的是函数声明。
函数实例
下面的代码执行结果是什么?
obj 包含两个方法属性,其中 fn1 是箭头函数,fn2 是普通函数。随后分别执行和实例化则两个函数。
对于箭头函数而言,没有自己的 this ,只能捕获上下文中的 this 值,所以 fn1 中的 this 指向 window(严格模式下为 undefined)。 fn2 是一个普通函数,this 在运行时根据调用方式动态绑定,因此 fn2 的 this 为 obj 对象。
随后的函数实例,因为箭头函数 fn1 没有自己的 this 与 prototype,不能被当作构造函数使用,因此 new obj.fn1() 报错。由于上面的 js 语句报错, const y = new obj.fn2(); 不会执行。若若注释上一行,因为普通函数能够作为构造函数使用,因此 this 指向创建的实例对象。
typeof 的返回
下面的代码执行结果是什么?
JS code example
在 JavaScript 中,null 是一种 原始值,表示有意缺少任何对象值(空对象值),因此 typeof null 为 "object"。 而 typeof 返回一个字符串,表示操作数的类型。因此 typeof typeof null 返回 "string"。
console.log() 是一个函数输出 1 ,而没有返回值,因此 typeof console.log() 返回 "undefined"。
函数的方法
下面的代码执行结果是什么?
JS code example
1. 执行前的内存状态
function Foo() {
// 修改静态方法(当Foo被调用时才执行)
Foo.a = function () {
console.log(1);
};
// 创建实例方法
this.a = function () {
console.log(2);
};
}
// 在原型上添加方法
Foo.prototype.a = function () {
console.log(3);
};
// 定义静态方法(立即执行)
Foo.a = function () {
console.log(4);
};此时:
Foo.a指向function() { console.log(4); }(静态方法)Foo.prototype.a指向function() { console.log(3); }(原型方法)
2. 第一次调用 Foo.a()
Foo.a(); // 输出 4直接调用构造函数上的静态方法 Foo.a。
3. 执行 new Foo()
let obj = new Foo();new 操作执行构造函数 Foo():
- 执行
Foo.a = function() { console.log(1); }- 修改了静态方法
Foo.a的指向 - 现在
Foo.a指向function() { console.log(1); }
- 修改了静态方法
- 执行
this.a = function() { console.log(2) }- 为实例
obj添加实例方法a
- 为实例
此时内存状态:
// 静态方法(已被修改)
Foo.a = function () {
console.log(1);
};
// 实例方法
obj = {
a: function () {
console.log(2);
},
// __proto__ 指向 Foo.prototype
};
// 原型方法(保持不变)
Foo.prototype.a = function () {
console.log(3);
};4. 调用 obj.a()
obj.a(); // 输出 2查找顺序(原型链):
- 先查找
obj自身的属性:找到实例方法a()→ 执行输出2 - 不会查找到原型上的
a(),因为自身已有该方法
5. 第二次调用 Foo.a()
Foo.a(); // 输出 1调用的是已被构造函数修改后的静态方法。
验证原型链
// 验证原型链方法
console.log(obj.__proto__.a()); // 3(原型方法仍然存在)
// 删除实例方法后
delete obj.a;
console.log(obj.a()); // 3(现在使用原型方法)
console.log(obj.a === Foo.prototype.a); // true
// 验证静态方法只能通过构造函数访问
console.log(obj.a === Foo.a); // false(一个是实例/原型方法,一个是静态方法)
console.log(Foo.a()); // 1关键点
- 静态方法 vs 实例方法 vs 原型方法 是三个不同的东西
- 构造函数内部的
Foo.a = ...修改的是静态方法(函数执行时修改) this.a = ...创建的是实例方法(覆盖同名原型方法)- 实例方法查找优先级:实例属性 > 原型属性
var 的作用域
下面的代码执行结果是什么?
JS code example
JavaScript 采用 词法作用域,函数的作用域在定义时就确定了,而不是调用时。 fun 函数在定义时能访问的作用域链已经固定。
// 定义阶段(编译阶段)
var a = 1; // 全局作用域
function fun() {
// fun 定义在全局作用域
console.log(a); // 此处 a 指向全局作用域的 a
}
// 执行阶段
(function main() {
var a = 2; // main 的局部作用域,只在 main 内部有效
fun(); // 调用 fun
})();拓展阅读:
this 指向
下面的代码执行结果是什么?
JS code example
想要确定最终输出什么,就要确定 print 中 a() 的 this 指向。普通函数的 this 指向由调用它的方式动态确定,被直接调用时指向 window。 因此即使 a() 嵌套在 print 中,this 仍然指向 window。obj.print() 引起的 a() 被调用,输出结果为 123。如果想结果为 456, 那么就需要让 print 函数的 this 指向 obj,直接将 print 函数改为普通函数输出 this.name 则可以输出 456。
print: function(){
// 此时print 是一个普通函数,其内部this动态绑定
console.log(this.name)
},那么如果改为下面的代码又会是什么结果呢?
print: () => {
console.log(this.name);
};由于箭头函数没有自己的 this,它的 this 在定义时就"锁死"了,指向外层作用域的 this。这里定义在全局对象的字面量中,外层 this 是 window,所以输出 123。
那么,下面的代码执行结果是什么?
JS code example
宏任务 微任务
下面代码执行结果是什么?
JS code example
此问题是经典的事件循环问题,事件循环机制阅读文档:JavaScript 事件循环机制。
上面代码的执行顺序为:
开始执行
├─ 遇到 Promise.resolve().then()
│ ├─ Promise 立即完成
│ └─ then 回调放入【微任务队列】
├─ 遇到 setTimeout()
│ ├─ 交给 Web API 计时
│ └─ 0ms 后回调放入【宏任务队列】
├─ 执行 console.log(3) → 输出 "3"
└─ 【同步代码执行完毕】
检查【微任务队列】
├─ 执行 Promise.then 回调
│ └─ console.log(1) → 输出 "1"
└─ 【微任务队列清空】
执行下一个【宏任务】
├─ 执行 setTimeout 回调
│ └─ console.log(2) → 输出 "2"
└─ 结束实现虚拟列表
问题
如何实现虚拟列表?
虚拟列表是一种用来优化长列表渲染的技术,它可以实现只渲染可见区域的列表,降低浏览器的渲染压力。
实现方式:实现虚拟列表
捕获 Promise 的错误
下面代码执行结果是什么?
JS code example
Promise.reject 会进入失败处理,.then 第二个参数可以捕获上一个 Promise 的错误,.catch 可以捕获任何未处理的 reject 与抛错。
上面的代码中,Promise.reject 会进入失败处理,进入 .then 第二个参数,输出 "error!!" "error!"。
拓展阅读:捕获 Promise 的错误
catch() 后的 .then()
下面代码执行结果是什么?
JS code example
在 Promise中,catch 用来处理 reject 的情况,并且会返回一个新的 Promise。 因为实际上 .catch 内部调用了 .then(undefined, onRejected)。
- 如果
catch(onRejected)的 onRejected 的回调显示返回了一个 reject 状态的 Promise 或者 在里面抛出异常,那么.catch返回的 Promise 实例的状态则为 rejected。 - 如果 catch 的回调没有显式 return,则返回 undefined。undefined 会让 catch 返回的 Promise 实例状态变为 reject。 简而言之,如果
catch的回调没有显式返回,则返回了Promise.resolve(undefined)。
分析上面的代码:
Promise.reject() 进入失败处理,所以进入catch,首先执行 console.log(err),输出 "Promise error"。随后显示返回一个 reject 状态的 Promise, 因此进入后面 .then() 的错误处理。输出 catch 返回的新 Promise 实例 reject 的内容:"catch error"。
如果 catch 返回的内容为 return 'catch',那么 catch 函数返回的 Promise 状态为 resolve,则会进入 .then() 的成功处理,最终输出 "Promise error" "catch"。 如果 catch ,没有返回内容,那么也会进入 .then() 的成功处理,最终输出 "Promise error" "undefined",因为 catch 没有返回成功的内容。