1. 对 this 对象的理解

this 是执行上下文的一个属性,它只想最后一次调用这个方法的对象。
this的指向可以通过四种调用模式来判断。

  1. 函数调用模式,当一个函数不是一个对象的属性时,直接作为函数调用时,this指向全局对象
  2. 方法调用模式:一个函数作为一个对象的方法调用时,this指向这个对象
  3. 构造器调用模式:一个函数用 new 调用时,函数执行前先创建一个对象,然后this指向这个对象
  4. 第四种是 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))

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐