JavaScript核心基础篇(面试必备)
1. 对 this 对象的理解
this 是执行上下文的一个属性,它只想最后一次调用这个方法的对象。
this的指向可以通过四种调用模式来判断。
- 函数调用模式,当一个函数不是一个对象的属性时,直接作为函数调用时,this指向全局对象
- 方法调用模式:一个函数作为一个对象的方法调用时,this指向这个对象
- 构造器调用模式:一个函数用 new 调用时,函数执行前先创建一个对象,然后this指向这个对象
- 第四种是 apply 、 call 和 bind 调用模式,三个方法都可以显式指定调用函数的this指向
2. apply、bind、call 的区别
首先三个都可以改变函数 this 的指向,三者的第一个参数都是 this 要指向的那个对象,如果没有这个参数就指向 window
三者都可以传参,但是 apply 传递的是数组,call 传递的是参数列表,并且 apply 和 call 是一次性传入参数,而 bind 可以分为多次传入
bind 是返回绑定 this 之后的函数,便于稍后操作,而 call 和 apply 是立即执行
这四种方式,使用构造器调用模式的优先级最高,然后是 apply、call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式
3. 实现 apply、call、bind函数
3.1 apply 函数的实现步骤:
/*
1. 判断调用对象是否是函数
2. 判断传入上下文是否存在,不存在则为window
3. 函数作为上下文对象的一个属性
4. 判断参数值是否传入
5. 使用上下文对象调用方法,保存返回结果
6. 删除新增的临时属性
7. 返回结果
*/
Function.prototype.myApply = function(context, args){
// 判断调用对象是否是函数
if(typeof this !== "function") {
throw new Error("调用对象者不是函数")
}
let result = null;
// 判断 context 是否存在,不存在则为 window
context = context || window;
// 将函数设为对象的方法
context.fn = this;
// 调用方法
if(args && Array.isArray(args)) {
result = context.fn(...args)
} else {
result = context.fn()
}
// 将属性删除
delete context.fn;
return result;
}
3.2 bind 的实现步骤
/*
1. 判断调用对象是否为函数
2. 保存当前函数引用,获取其余传入参数值
3. 创建一个函数返回
4. 函数内部使用 apply 来绑定函数调用,这个时候要传入 this 给 apply 调用,也就是为什么 bind 不会自动执行 fn
*/
Function.prototype.myBind = function(context, args) {
// 判断调用者是否为函数
if(typeof this !== "function") {
throw new Error("调用者不是函数")
}
// 获取参数
var newArgs = [...args].slice(1), fn = this;
return function Fn(){
// 根据调用方式,传入不同绑定的值
return fn.apply(this instanceof Fn ? this : context, newArgs.concat(...args))
}
}
call 的实现步骤
/*
1. 判断调用者是否为函数
2. 判断上下文对象是否存在,不存在则为window
3. 处理传入的参数,截取第一个参数之后的(所有参数)
4. 将函数作为上下文对象的一个属性
5. 使用上下文来调用这个方法
6. 删除刚才新增的属性
7. 返回结果
*/
Function.prototype.myCall = function(context,args){
// 判断调用对象是否为函数
if(typeof this !== "function") {
throw new Error("调用者不是函数")
}
// 获取参数
let newArgs = [...args].slice(1)
let result = null;
// 判断 context 是否传入,未传入则为 window
context = context || window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
result = context.fn(...newArgs);
// 将属性删除
delete context.fn;
return result;
}
4. 原型链 & proto
原理:原型链就是 js 作者在开发 js 的时候,在每个对象里面都初始化了一个属性,叫做原型 prototype,那么对于原型链呢,就是对于对象属性查找机制的描述,当前对象身上找不到,就去他的原型对象的 proto 上找,最后找到 null,那么这个一层一层往上找形成的这个链式结构,就叫做原型链
5. 闭包
闭包的描述:“闭包就是一个作用域可以访问另外一个作用域的局部变量,闭包本质是作用域链未释放,用来保持状态或封装私有变量。”
举例:在一个函数中return出来另外一个函数,在这个函数中使用了外层函数的变量,外层执行之后,本该销毁的变量由于引用的关系没有被销毁
闭包的优势:延长作用域链、防止变量污染、保存变量常驻内存。场景:立即执行函数,setTimeout
闭包的缺点:很多人说闭包会引起内存泄露,我认为观念不太对。大量使用闭包确实会导致内存溢出内存泄漏,但是在我们毫不知情的情况下,无法预期的情况下导致内存泄漏,该销毁的没有销毁,这个时候才算得上是内存泄漏。但是如果我们明确就是要使用闭包保护变量的特性,去主动实现一些功能,那么这种情况下就不能称为内存泄漏了。当然,尽管这样说,在项目中还是要避免大量使用闭包。
解决内存泄漏:把引用的值赋值为 null,根据机制,就不会在去引用这个变量。或者就是每次使用完毕之后及时清理
6. 异步 & 事件循环(Promise / async/await / setTimeout)
JS 是单线程,异步任务通过时间循环机制排队执行。宏任务队列包含 setTimeout、serInterval、IO 等;微任务队列包含 Promise.then、async/await 后续任务
JS事件循环机制保证单线程非阻塞运行。每次执行栈清空后,会先执行微任务队列再执行宏任务队列。
🌟:微任务 优先于 宏任务
JavaScript 中的异步机制可以分为几种:
1. 回调函数的方式,多个回调函数嵌套会造成回调函数地狱
2. Promise 的方式,可以将嵌套的回调函数作为链式调用
3. async函数的方式,将异步逻辑转成同步顺序书写
7. 深浅拷贝
浅拷贝一层,深拷贝全拷
深拷贝我主要是用递归来做,里面涉及一个引用类型的知识点,通过 instanedof 来判断是数组还是对象,然后通过递归的方式来循环遍历,还有一种方式就是 序列化和反序列化,但是如果其中有方法,就不可以使用了。浅拷贝使用 Object.assign({}, obj) 和 扩展运算符来拷贝。
structuredClone() 是JavaScript 的一个全局方法,它是一种高效且可靠的方式来深拷贝 JavaScript 对象,适用于需要处理复杂数据结构的场景,或者使用 lodash 的 cloneDeep 函数。
8. new 操作符
8.1 new操作符的执行过程:
new 的核心流程:创建对象 → 绑定原型 → 执行函数 → 返回对象
/*
1. 创建新的空对象
2. 设置原型,将原型设置为函数的 prototype 对象
3. 函数的 this 指向这个对象,执行构造函数代码,为新对象添加属性
4. 判断函数的返回值类型,值类型返回创建的对象,引用类型,返回这个引用类型的对象
*/
// 具体实现:
function objectNew(){
let newObj = null;
let constructor = Array.prototype.shift.call(arguments);
let result = null;
// 判断参数是否是一个函数
if(typeof constructor !== "function") {
return console.error("type not function")
}
// 新建一个空对象,对象的原型为构造函数的 prototype 对象
newObj = Object.create(constructor.prototype);
// 将 this 指向新建对象,执行函数
result = constructor.apply(newObj,arguments);
// 判断返回对象
let flag = result && (typeof result === "object" || typeof result == "function")
return flag ? result : newObj
}
// 创建
objectNew(构造函数,初始化参数);
9. 手写 Object.create()
// 思路:返回一个新对象,原型指向目标对象,即中间函数继承目标对象原型。
function create(obj) {
function F(){}
F.prototype = obj
return new F()
}
10. Promise 手写
状态 + 回调队列,核心是状态管理和异步执行队列
Promise 是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息。promise有三种状态: pending(等待态),fulfiled(成功态),rejected(失败态);状态一旦改变,就不会再变。
它的方法,我主要常用的就是 promise.all 这个方法,这个方法只有当该数组中的所有Promise完成后才会由
pendding状态变为resolve执行then里面的回调函数,若数组中有任意一个promise被拒绝则会执行失败回调,catch方法会捕获到首个被执行的 reject函数。
/*手写 Promise*/
class promise {
constructor(fn) {
this.status = "pending"
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
// 更改成功后的状态
const resolve = value => {
if(this.status === "pending") {
this.status = "fulfilled";
this.onFulfilledCallbacks.forEach(fn => fn(value))
}
}
// 更改失败后的状态
const reject = value => {
if(this.status === "pending") {
this.status = "rejected";
this.onRejectedCallbacks.forEach(fn => fn(value))
}
}
try {
fn(resolve,reject)
} catch(err) {
reject(err)
}
}
then(onFulfilled,onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : err => {throw new Error(err)}
// 为链式调用,返回一个 Promise
return new Promise((resolve,reject) => {
const fulfilledMicrotask = value => {
queueMicrotask(() => {
try {
const res = onFulfilled(value)
res instanceof Promise ? res.then(resolve,reject) : resolve(res)
} catch (err) {
reject(err)
}
})
}
const rejectedMicrotask = value => {
queueMicrotask(() => {
try {
const res = onRejected(value)
res instanceof Promise ? res.then(resolve,reject) : reject(res)
} catch(err) {
reject(err)
}
})
}
this.onFulfilledCallbacks.push(fulfulledMicrotask)
this.onRejectedCallbacks.push(rejectedMicrotask)
})
}
static resolve(value) {
if(value instanceof Promise) {
return value
}
return new Promise(resolve => {
resolve(value)
})
}
static reject(err) {
return new Promise((resolve,reject) => {
reject(err)
})
}
}
11.防抖 & 节流
优化频繁操作、防抖延迟触发,节流按间隔触发、常用于输入搜索和滚动事件优化
11.1 防抖
防抖是指在事件被触发 n 秒后再执行回调,如果再 n 秒内事件又被触发,则重新计时
最终是要返回函数供事件执行
// 函数防抖的实现 function debounce(fn, delay = 100) { let timer = null; return function(...args) { if(timer) clearTimeout(timer); timer = setTimeout(() => { fn.call(this, ...args) timer = null },delay) } } // 使用 input.addEventlistener("keyup", debounce(function(){ console.log(input.value) }))
节流是指在规定的时间内,只能有一次触发事件的回调被执行,如果事件被触发多次,只有一次回调执行
// 函数节流的实现 function throttle(fn, delay = 100) { let flag = true; return function(...args) { if(!flag) return' flag = false; setTimeout(() => { fn,call(this, ...args) flag = true },delay) } } // 使用 div.addEventListener("drag", throttle(function(e) { console.log(e.offsetX, e.offsetY) }, 300))
更多推荐


所有评论(0)