返回介绍

JS 基础

发布于 2024-09-16 00:13:10 字数 9712 浏览 0 评论 0 收藏 0

变量类型和计算

typeof 能判断哪些类型

  • 识别所有值类型
  • 识别函数
  • 判断是否是引用类型(不可再细分)

何时使用 === 何时使用 ==

除了 == null(null == undefined) 外,其他都一律用 ===

值类型和引用类型的区别

值类型:栈

引用类型:栈 + 堆

值类型 / 引用类型的拷贝

手写深拷贝 ⭐️

  • 判断值类型和引用类型
  • 判断是数组还是对象
  • 递归
function deepClone(obj = {}) {
  if (typeof obj !== "object" || obj == null) {
    return obj;
  }

  let result = Array.isArray(obj) ? [] : {};

  for (let key in obj) {
    // 自己的属性
    if (obj.hasOwnProperty(key)) {
      // 递归调用
      result[key] = deepClone(obj[key]);
    }
  }

  return result;
}

原型和原型链

如何准确判断一个变量是不是数组

  • a instanceof Array
  • Array.isArray()

class 的原型本质,怎么理解

class 实际上是函数,语法糖

  • 每个 class 都有显示原型 prototype
  • 每个实例都有隐式原型 proto
  • 实例的 proto 指向对应 class 的 prototype

手写一个简易的 jQuery,考虑插件和扩展性

class jQuery {
  constructor(selector) {
    const result = document.querySelectorAll(selector);
    const length = result.length;
    for (let i = 0; i < length; i++) {
      this[i] = result[i];
    }
    this.length = length;
    this.selector = selector;
  }

  get(index) {
    return this[index];
  }

  each(fn) {
    for (let i = 0; i < this.length; i++) {
      const elem = this[i];
      fn(elem);
    }
  }

  on(type, fn) {
    return this.each((elem) => {
      elem.addEventListener(type, fn, false);
    });
  }
}

// 插件
jQuery.prototype.dialog = function (info) {
  alert(info);
};

// "造轮子"
class MyJQuery extends jQuery {
  constructor(selector) {
    super(selector);
  }
  // 扩展自己的方法
  addClass(className) {
    // ...
  }
}

作用域和闭包

this 的不同应用场景,如何取值

this 的取值是在函数执行时确定,不是在函数定义时确定!

  • 当做普通函数被调用(window)
  • 使用 call、apply、bind(传入的对象)
  • 作为对象方法被调用(对象)
  • 在 class 的方法中调用
  • 箭头函数(定义时的上级作用域)

手写 bind 函数

Function.prototype.bind = function () {
  // 将参数拆解为数组
  const args = Array.prototype.slice.call(arguments);

  // 获取 this(数组第一项)
  const t = args.shift();

  // fn.bind(...) 中的 fn
  const self = this;

  // 返回一个函数
  return function () {
    return self.apply(t, args);
  };
};

实际开发中闭包的应用场景,举例说明

  • 隐藏数据,只提供 API
function createCache() {
  const data = {};
  return {
    set: function (key, val) {
      data[key] = val;
    },
    get: function (key) {
      return data[key];
    },
  };
}

闭包:所有的自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方(静态作用域)!!!

答案:100, 100

异步

同步和异步的区别是什么?

  • 基于 JS 是单线程语言
  • 异步不会阻塞代码执行
  • 同步会阻塞代码执行

手写用 Promise 加载一张图片 ⭐️

function loadImg(src) {
  return new Promise((resolve, reject) => {
    const img = document.createElement("img");
    img.onload = () => {
      resolve(img);
    };
    img.onerror = () => {
      reject(new Error("图片加载失败"));
    };
    img.src = src;
  });
}

Callback Hell

前端使用异步的场景有哪些?

  • 网络请求,如 ajax 图片加载
  • 定时任务,如 setTimeout、setInterval

场景题

答案:1、3、5、4、2

异步进阶

请描述 event loop 的机制,可画图 ⭐️

  • 同步代码,一行一行放在 Call Stack 中执行
  • 遇到异步,会先 “记录” 下,等待时机(定时、网络请求等)
  • 时机到了,就移动到 Callback Queue
  • 如果 Call Stack 为空(即同步代码执行完),Event Loop 开始工作
  • 轮询查找 Callback Queue,如有则移动到 Call Stack 执行
  • 然后继续轮询查找(永动机)

Promise 有哪三种状态,如何变化

三种状态:

  • pending、resolved、rejected
  • pending → resolved 或 pending → rejected
  • 变化不可逆

状态表现:

  • pending 状态,不会触发 then 和 catch
  • resolved 状态,会触发后续的 then 回调函数
  • rejected 状态,会触发后续的 catch 回调函数

async/await 和 Promise 的关系

  • 执行 async 函数,返回的是 Promise 对象
  • await 相当于 Promise 的 then
  • try…catch 可捕获异常,代替了 Promise 的 catch

Event Loop 和 DOM 渲染

  • JS 是单线程,而且和 DOM 渲染共用一个线程
  • JS 执行时,得留一些时机供 DOM 渲染
  • 每次 Call Stack 清空,即同步任务执行完
  • 都是 DOM 重新渲染的机会,DOM 结构如有改变则重新渲染
  • 然后再去触发下一次 Event Loop

什么是宏任务和微任务,两者有什么区别

  • 宏任务:setTimeout、setInterval、ajax、dom 事件
  • 微任务:Promise、async/await
  • 微任务执行时机比宏任务要早
  • 宏任务在 DOM 渲染后触发,如 setTimeout
  • 微任务在 DOM 渲染前触发,如 Promsie

手写 Promise⭐️

  • 初始化 & 异步调用
  • then catch 链式调用
  • API .resolve .reject .all .race
class MyPromise {
  state = "pending";
  value = undefined;
  reason = undefined;

  resolveCallbacks = []; // pending 状态下,存储成功的回调
  rejectCallbacks = []; // pending 状态下,存储失败的回调

  constructor(fn) {
    const resolveHandler = (value) => {
      if (this.state === "pending") {
        this.state = "fulfilled";
        this.value = value;
        this.resolveCallbacks.forEach((fn) => fn(this.value));
      }
    };

    const rejectHandler = (reason) => {
      if (this.state === "pending") {
        this.state = "rejected";
        this.reason = reason;
        this.rejectCallbacks.forEach((fn) => fn(this.reason));
      }
    };

    try {
      fn(resolveHandler, rejectHandler);
    } catch (err) {
      rejectHandler(err);
    }
  }

  then(fn1, fn2) {
    // pending 状态下,fn1、fn2 被保存
    fn1 = typeof fn1 === "function" ? fn1 : (v) => v;
    fn2 = typeof fn2 === "function" ? fn2 : (e) => e;

    if (this.state === "pending") {
      return new MyPromise((resolve, reject) => {
        this.resolveCallbacks.push(() => {
          try {
            const newValue = fn1(this.value);
            resolve(newValue);
          } catch (err) {
            reject(err);
          }
        });

        this.rejectCallbacks.push(() => {
          try {
            const newReason = fn2(this.reason);
            reject(newReason);
          } catch (err) {
            reject(err);
          }
        });
      });
    }

    if (this.state === "fulfilled") {
      return new MyPromise((resolve, reject) => {
        try {
          const newValue = fn1(this.value);
          resolve(newValue);
        } catch (err) {
          reject(err);
        }
      });
    }

    if (this.state === "rejected") {
      return new MyPromise((resolve, reject) => {
        try {
          const newReason = fn2(this.reason);
          reject(newReason);
        } catch (err) {
          reject(err);
        }
      });
    }
  }

  catch(fn) {
    return this.then(null, fn);
  }
}

MyPromise.resolve = function (value) {
  return new MyPromise((resolve, reject) => resolve(value));
};

MyPromise.reject = function (reason) {
  return new MyPromise((resolve, reject) => reject(reason));
};

MyPromise.all = function (promiseList = []) {
  return new MyPromise((resolve, reject) => {
    const result = [];
    const length = promiseList.length;
    let resolveCount = 0;
    promiseList.forEach((p) => {
      p.then((data) => {
        result.push(data);
        // resolveCount 必须在 then 里面做 ++
        resolveCount++;
        if (resolveCount === length) {
          resolve(result);
        }
      }).catch((err) => {
        reject(err);
      });
    });
  });
};

MyPromise.race = function (promiseList = []) {
  let resolved = false;
  return new MyPromise((resolve, reject) => {
    promiseList.forEach((p) => {
      p.then((data) => {
        if (!resolved) {
          resolve(data);
          resolved = true;
        }
      }).catch((err) => {
        reject(err);
      });
    });
  });
};

场景题

then 和 catch 改变状态:

  • then 正常返回 resolved promise,里面有报错则返回 rejected promise
  • catch 正常返回 resolved promise,里面有报错则返回 rejected promise

第一题:1、3

第二题:1、2、3

第三题:1、2

第一题:a → Promise 对象、b → 100

第二题:‘start’、100、200、报错…

答案:100、400、300、200

答案:‘script start’ → ‘async1 start’ → ‘async2’ → ‘promise1’ → ‘script end’ → ‘async1 end’ → ‘promise2’ → ‘setTimeout’

执行时机: 同步代码执行完毕 → 执行微任务 → 触发 DOM 渲染 → 触发 Event Loop,执行宏任务。

要点: 初始化 promise 时,传入的函数会立刻被执行;await 后面的都作为回调内容(微任务)。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文