JavaScript面试
✨♻️ JavaScript Visualized: Event Loop
Oh boi the event loop. It’s one of those things that every JavaScript developer has to deal with in o...


动态原型链
function Animal(name) { this.name = name; } Animal.prototype.speak = function() { console.log(this.name + " makes a noise."); }; const dog = new Animal("Buddy"); dog.speak(); // ① 输出? Animal.prototype.speak = function() { console.log(this.name + " barks!"); }; dog.speak(); // ② 输出? Animal.prototype = { speak: function() { console.log("This is a new prototype!"); } }; const cat = new Animal("Whiskers"); cat.speak(); // ③ 输出? dog.speak(); // ④ 输出?
A
1)buddy make a noise 2)buddy barks 3)this is a new prototype 4)buddy barks
Q1: const alice = new Person("Alice")做了什么?
1)alice={}
2)alice.__proto__ = Person.prototype
3) Person.call(alice, "Alice")
4) return alice
Q2: this指向问题
//普通函数的this在被调用时决定
1) foo()//this->window(browser)/global(node)/undefined(use strict)
2) obj.foo()//this->obj(实例对象调用方法,this指向实例,无论方法定义在实例上还是prototype上)
//变式1
Obj.prototype.foo()//this->Obj.prototype
//变式2
foo2=Obj.prototype.foo
foo2//this->global
3)call(),apply(),bind()
//箭头函数的this在被定义时决定//被定义时=加载进内存时
4)在定义时由外层作用域(函数)的this决定,不会改变
->不能使用箭头函数作为构造函数,因为箭头函数的this不能动态绑定/和new关键字不兼容
setTimeout 是在 全局作用域(window / global) 执行的
setTimeout 不会创建新的作用域,只是延迟执行函数。
-setTimeout(普通函数)会使作用域变成global
-setTimeout(箭头函数)作用域还是定义时候决定
this指向问题
var name = "Global"; function Person(name) { this.name = name; this.greet1 = function() { console.log(this.name); }; this.greet2 = () => { console.log(this.name); }; } Person.prototype.greet3 = function() { console.log(this.name); }; const obj = { name: "Object" }; const alice = new Person("Alice"); alice.greet1(); // ① 输出? alice.greet2(); // ② 输出? alice.greet3(); // ③ 输出? const greet1Copy = alice.greet1; greet1Copy(); // ④ 输出? const greet2Copy = alice.greet2; greet2Copy(); // ⑤ 输出? const greet3Copy = alice.greet3; greet3Copy(); // ⑥ 输出? alice.greet1.call(obj); // ⑦ 输出? alice.greet2.call(obj); // ⑧ 输出? alice.greet3.call(obj); // ⑨ 输出? setTimeout(alice.greet1, 1000); // ⑩ 输出? setTimeout(alice.greet2, 1000); // ⑪ 输出?
A
1)alice 2)alice 3)alice 4)global 5)alice 6)global 7)object 8)alice 9)object 10)global 11)alice
Q3: 继承写法
1)Animal.call(this, name) 继承 Animal 的实例属性。
2)Dog.prototype = Object.create(Animal.prototype) 继承了 Animal 的原型方法。
3)Dog.prototype.constructor = Dog 纠正 constructor 指向。
function Animal(name) {
this.name = name;
}
function Dog(name, breed) {
Animal.call(this, name); // 1)继承 Animal 的属性
this.breed = breed;
}
// 2)继承 Animal 的方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 3)修正 constructor 指向
Q4: 作用域和闭包
闭包:当一个函数访问其外部作用域的变量时,就形成了闭包。
作用域链:当前作用域找不到变量时,会沿着作用域链向上查找,直到找到变量或者报错 (ReferenceError)。
变量提升(Hoisting):
var 声明的变量 和 函数声明 移动到其作用域的顶部,
但let 和 const 不会被提升到可访问的状态。
let 和 const 变量 会被提升(Hoisting),但不会初始化,因此在赋值前访问会导致 ReferenceError(处于暂时性死区)。
Hoisting | 初始化前访问 | Scope | |
let/const | yes | reference error | block |
var | yes | undefined | function |
函数声明 | yes | yes |
Var的function scope和hoisting,导致了一些意想不到的行为,尤其是在循环、闭包、异步操作等场景中。例如,for
循环中的 var
变量可能在所有迭代中共享同一个变量,导致结果不如预期。
ES6+使用const/let,更加明了。
for...in
vs for...of
:适用对象 vs 适用数组
for(let idx in nums)
: 遍历键 | 对象 (Object
) | 坑:arr的非索引属性/obj的原型链上的可枚举属性
for(let num of nums)
: 遍历值 | 可迭代对象 (Array
, Map
, Set
, String
)
for...in
会遍历对象自身和原型链上的可枚举属性。
Object.keys()
只返回对象自身的可枚举属性。
异步:
- callback hell:回调嵌套层数太多,难以维护
- event loop:执行所有同步代码 -> (清空微任务 -> 执行一个宏任务)循环
- micro task:Promise.then、process.nextTick
- macro task:setTimeout、setImmediate、I/O
- 创建Promise的代码是同步执行的
- await:
- Promise的语法糖
- const result = await Promise.resolve(val)
- 等待Promise resolved,返回Promise的解析值
- 使用try{}catch(error){} 处理rejected
Promise:pending->fulfilled/rejected
Promise实例的方法:
then(),fulfilled执行
catch(),rejected执行
finally(),最后一定被执行
then/catch 会返回 fulfilled 状态的 Promise(async的返回值也是一个Promise)
Promise类的方法:
resolve(val),状态pending->fulfilled,并返回解析值
reject(val),状态pending->rejected,并返回错误原因
race(),接收一个Promise数组,返回状态最先发生变化的Promise对象的结果/原因。
all(),接收一个Promise数组,生成一个新的Promise,所有参数Promise全部fulfilled才解决
Promise.resolve(val)创造一个立即解决的Promise
new Promise(executor):参数为executor函数
executor函数有resolve,reject两个参数,这两个参数由js引擎提供,需要手动传入
Const p = new Promise((resolve,reject)=>{ })
工厂模式 是一种封装对象创建逻辑的设计模式,用一个工厂函数来创建对象,而不是在代码中直接使用 new
。
单例模式 是一种 创建型设计模式,它确保一个类 只有一个实例,并提供一个全局访问点。(日志,全局配置管理)
代理模式的核心思想是在访问某个对象时,引入一个“代理”对象来控制、修改或延
观察者模式让多个observer“观察”一个对象(subject),当这个对象变化时,所有观察者都会收到通知。(addEventListener
)
防抖:在事件触发后
等待一段时间,如果在等待时间内事件又被触发,则重新计时。适用于用户输入框搜索等场景。
var debounce = function(fn, t) {
let timer
return function(...args) {
clearTimeout(timer)
timer = setTimeout(()=>fn.apply(this, args), t)
}
};
节流:限制单位时间内函数的执行次数,适用于滚动监听、按钮点击等场景。
Other:
