前端JavaScript应知应会

JavaScript作为前端三剑客之一,在前端领域有着非比寻常的地位,本文介绍了一些常见知识点和面试考点,当作对自己前端生涯中的一些总结。

标签

  • meta:对页面的元数据进行描述和定义,包括关键词、描述、作者、字符编码、视口大小、缩放比例等
  • href:超文本引用,用来建立当前元素和文档之间的链接,常用的有link、a
  • src:指向的内容会嵌入到文档当前标签所在位置,常用的有img、script、iframe
  • script:所在位置会阻塞HTML的解析,可以使用defer或async异步加载外部JS脚本;带有defer标签的脚本加载完后会在所有元素解析完成后执行,按加载顺序执行;带有async标签的脚本加载完后会直接执行,阻塞其他元素的解析,不保证加载顺序执行

数据类型与声明

  1. 变量声明
  • var存在变量提升,在函数作用域中生效
  • let和const存在暂时性死区,在块级作用域中生效
  • 函数是一等公民,函数提升优先级高于变量提升优先级
  1. 包装类型
    基本类型没有属性和方法,但JavaScript提供了包装类型,在调用基本类型的属性或方法时会在后台隐性地将基本类型转换为对象
  2. 类型判断
  • typeof
  • instanceof
  • Object.prototype.toString.call
  • 手写instanceof
js 复制代码
function myInstanceOf(child, father) {
  if (
    child === null ||
    (typeof child !== "object" && typeof child !== "function")
  ) {
    return false;
  }
  let temp = Object.getPrototypeOf(child);
  while (temp) {
    if (temp === father.prototype) {
      return true;
    }
    temp = Object.getPrototypeOf(temp);
  }
  return false;
}
  1. 深浅拷贝
    看复杂类型拷贝的是值还是指针
    手写深拷贝:
js 复制代码
function deepClone(obj) {
  if (obj === null || typeof obj !== "object") return obj;
  let cloneObj = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (Object.hasOwn(obj, key)) {
      cloneObj[key] = deepClone(obj[key]);
    }
  }
  return cloneObj;
}
  1. 循环遍历
  • for in:遍历对象、数组、字符串等可枚举数据,会遍历整个原型链,获取的是键名
  • for of:遍历Map、Set等可迭代数据,只会遍历当前对象,获取的是键值
  • map:有返回值,会生成一个新数组
  • forEach:没有返回值,会改变原数组
  1. new关键字
    过程:
  • 创建一个新的空对象,将这个对象的原型指向构造函数的原型对象
  • 将该对象作为this上下文,调用构造函数
  • 如果返回对象,则返回这个对象,否则返回新创建的对象
    手写new:
js 复制代码
function myNew(constructor, ...args) {
  const obj = Object.create(constructor.prototype);
  const result = constructor.apply(obj, args);
  return result instanceof Object ? result : obj;
}
  1. 函数
  • 高阶函数:接收函数作为参数的函数
js 复制代码
function compose(...fn) {
  if (fn.length === 0) {
    return num => num
  }
  if (fn.length === 1) {
    return fn[0]
  }
  return fn.reduce((pre, next) => {
    return num => {
      return next(pre(num))
    }
  })
}

const add1 = num => num + 1
const add2 = num => num + 2
const add3 = num => num + 3
const add4 = num => num + 4
const add5 = num => num + 5

compose(add1, add2, add3, add4, add5)(1) // 16
  • 箭头函数:不可以作为构造函数,没有自己的this,没有arguments对象,没有原型对象
  • length:第一个具有默认值之前的参数个数
js 复制代码
function curry(fn) {
  const len = fn.length
  return function curried(...args) {
    if (args.length < len) {
      return (...rest) => curried(...args, ...rest)
    }
    return fn(...args)
  }
}

const sum = (a, b, c, d, e) => {
  return a + b + c + d + e
}

const curriedSum = curry(sum)

curriedSum(1, 2, 3, 4, 5)
curriedSum(1)(2)(3)(4)(5)
  • 手写LRU缓存函数
js 复制代码
class LRUCache {
  constructor(size) {
    this.size = size;
    this.cache = new Map();
  }

  get(key) {
    const hasKey = this.cache.has(key);
    if (hasKey) {
      const val = this.cache.get(key);
      this.cache.delete(key);
      this.cache.set(key, val);
      return val;
    } else {
      return -1;
    }
  }

  put(key, val) {
    const hasKey = this.cache.has(key);
    if (hasKey) {
      this.cache.delete(key);
    }
    this.cache.set(key, val);
    if (this.cache.size > this.size) {
      this.cache.delete(this.cache.keys().next().value);
    }
  }
}

原型和原型链

每一个对象都有一个隐式原型_proto_,指向构造函数的原型对象,查找属性或方法时,会沿着原型链就近查找,原型链最关键的三条路径为:

  • 实例对象-proto ->构造函数的prototype-proto->Object.prototype
  • 构造函数-proto ->Function.prototype-proto->Object.prototype
  • Object/Function-proto ->Function.prototype-proto->Object.prototype

继承

  • 原型链继承:将父类实例作为子类原型,来自原型对象的所有属性被所有实例共享
  • 构造函数继承:使用父类的构造器创建子类实例,不能继承父类的原型属性和方法
  • class:通过extends实现继承,构造函数中super用来调用父类函数,指向父类原型
  • 寄生组合继承
js 复制代码
function Animal(name) {
  this.name = name;
}
Animal.prototype.say = function (name) {
  console.log(`My name is ${name}`);
};
function Dog(name) {
  Animal.call(this, name);
}
var Super = function () {};
Super.prototype = Animal.prototype;
Dog.prototype = new Super();

const test = new Dog("dog");
test.say("dog"); // My name is dog

this

  1. this指向
  • new操作符指向创建实例
  • 作为对象方法调用时指向该对象
  • 直接作为函数调用时指向全局对象
  • 箭头函数在创建时this指向根据上下文确定
  • call、apply、bind会改变this指向
  1. 手写call
js 复制代码
Function.prototype.myCall = function (obj, ...args) {
  obj = obj || window;
  const fn = Symbol("fn");
  obj[fn] = this;
  return obj[fn](...args);
};
  1. 手写apply
js 复制代码
Function.prototype.myApply = function (obj, args) {
  obj = obj || window;
  const fn = Symbol("fn");
  obj[fn] = this;
  return obj[fn](...args);
};
  1. 手写bind
js 复制代码
Function.prototype.myBind = function (obj, ...args) {
  obj = obj || window;
  const self = this;
  const res = function (...newArgs) {
    if (this instanceof res) {
      return new self(...args, ...newArgs);
    } else {
      return self.call(obj, ...args, ...newArgs);
    }
  };
  res.prototype = Object.create(this.prototype);
  return res;
};

Promise

  1. 手写Promise
js 复制代码
class MyPromise {
  constructor(executor) {
    this.PromiseResult = null;
    this.PromiseState = "pending";
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
    this.resolve = this.resolve.bind(this);
    this.reject = this.reject.bind(this);
    try {
      executor(this.resolve, this.reject);
    } catch (e) {
      this.reject(e);
    }
  }

  resolve(value) {
    if (this.PromiseState !== "pending") return;
    this.PromiseState = "fulfilled";
    this.PromiseResult = value;
    while (this.onFulfilledCallbacks.length) {
      this.onFulfilledCallbacks.shift()(this.PromiseResult);
    }
  }
  
  reject(reason) {
    if (this.PromiseState !== "pending") return;
    this.PromiseState = "rejected";
    this.PromiseResult = reason;
    while (this.onRejectedCallbacks.length) {
      this.onRejectedCallbacks.shift()(this.PromiseResult);
    }
  }
  
  then(onFulfilled, onRejected) {
    onFulfilled =
      typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (reason) => {
            throw reason;
          };
    var thenPromise = new MyPromise((resolve, reject) => {
      const resolvePromise = (cb) => {
        setTimeout(() => {
          try {
            const x = cb(this.PromiseResult);
            if (x === thenPromise) {
              throw new Error("不能返回自身");
            }
            if (x instanceof MyPromise) {
              x.then(resolve, reject);
            } else {
              resolve(x);
            }
          } catch (e) {
            reject(err);
            throw new Error(e);
          }
        });
      };

      if (this.PromiseState === "fulfilled") {
        resolvePromise(onFulfilled);
      } else if (this.PromiseState === "rejected") {
        resolvePromise(onRejected);
      } else if (this.PromiseState === "pending") {
        this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled));
        this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected));
      }

      return thenPromise;
    });
  }
}
  1. 手写Promise.all
js 复制代码
function all(promises) {
  const result = [];
  let count = 0;
  return new Promise((resolve, reject) => {
    const addData = (index, value) => {
      result[index] = value;
      count++;
      if (count === promises.length) resolve(result);
    };
    promises.forEach((promise, index) => {
      if (promise instanceof Promise) {
        promise.then(
          (res) => addData(index, res),
          (err) => reject(err)
        );
      } else {
        addData(index, promise);
      }
    });
  });
}
  1. 手写Promise.race
js 复制代码
function race(promises) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(
        (val) => resolve(val),
        (err) => reject(err)
      );
    }
  });
}
  1. 手写Promise.any
js 复制代码
function any(promises) {
  return new Promise((resolve, reject) => {
    let count = 0;
    promises.forEach((promise) => {
      promise.then(
        (val) => resolve(val),
        (err) => {
          count++;
          if (count === promises.length)
            reject(new AggregateError("All promises were rejected"));
        }
      );
    });
  });
}
  1. 使用Promise手写一个异步调度器
js 复制代码
class Scheduler {
  constructor(limit) {
    this.queue = [];
    this.limit = limit;
    this.count = 0;
  }

  add(time, order) {
    const promiseCreator = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(order);
          resolve();
        }, time);
      });
    };
    this.queue.push(promiseCreator);
  }

  taskStart() {
    for (let i = 0; i < this.limit; i++) {
      this.request();
    }
  }

  request() {
    if (!this.queue.length || this.count >= this.limit) return;
    this.count++;
    this.queue
      .shift()()
      .then(() => {
        this.count--;
        this.request();
      });
  }
}

// test
const scheduler = new Scheduler(2);
const addTask = (time, order) => {
  scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();

模块化

  • commonjs:module.exports导出,require导入,导入值可修改,支持动态导入,运行时加载
  • esmodule:export/export default导出,import导入,导入值不可修改,支持动态导入(返回的是一个Promise),编译时加载

防抖和节流

防抖:频繁触发一个事件,但只触发最后一次

js 复制代码
function debounce(fn, delay = 500) { 
  let timer = null; 
  return function () { 
    if (timer) { 
      clearTimeout(timer); 
    } 
    const args = arguments; 
    timer = setTimeout(() => { 
      fn.apply(this, args); 
    }, delay); 
  }; 
}

节流:频繁触发一个事件,但只能隔一段时间触发一次,只触发第一次

js 复制代码
function throttle(fn, delay = 500) { 
  let flag = true; 
  return function () { 
    if (!flag) return; 
    flag = false; 
    const args = arguments; 
    setTimeout(() => { 
      fn.apply(this, args); 
      flag = true; 
    }, delay); 
  };
}

设计模式

  1. 单例模式
js 复制代码
class SingleClass {
  constructor() {
    if (!SingleClass.instance) {
      this.prototype1 = "value1";
      SingleClass.instance = this;
    }
    return SingleClass.instance;
  }
}
  1. 发布订阅模式
js 复制代码
class EventEmitter {
  constructor() {
    this.cache = {};
  }

  on(name, fn) {
    const tasks = this.cache[name];
    if (tasks) {
      this.cache[name].push(fn);
    } else {
      this.cache[name] = [fn];
    }
  }

  off(name, fn) {
    const tasks = this.cache[name];
    if (tasks) {
      const index = tasks.findIndex((item) => item === fn);
      if (index >= 0) {
        this.cache[name].splice(index, 1);
      }
    }
  }

  emit(name, ...args) {
    const tasks = this.cache[name].slice();
    if (tasks) {
      tasks.forEach((fn) => fn(...args));
    }
  }

  once(name, cb) {
    function fn(...args) {
      cb(args);
      this.off(name, fn);
    }
    this.on(name, fn);
  }
}
相关推荐
那小孩儿几秒前
?? 、 || 、&&=、||=、??=这些运算符你用对了吗?
前端·javascript
菜鸟码农_Shi9 分钟前
Node.js 如何实现 GitHub 登录(OAuth 2.0)
javascript·node.js
没资格抱怨13 分钟前
如何在vue3项目中使用 AbortController取消axios请求
前端·javascript·vue.js
总之就是非常可爱39 分钟前
🚀 使用 ReadableStream 优雅地处理 SSE(Server-Sent Events)
前端·javascript·后端
ᖰ・◡・ᖳ1 小时前
Web APIs阶段
开发语言·前端·javascript·学习
stoneSkySpace1 小时前
算法——BFS
前端·javascript·算法
H5开发新纪元1 小时前
基于 Vue3 + TypeScript + Vite 的现代化移动端应用架构实践
前端·javascript
快乐的小前端1 小时前
class 类基础知识
前端·javascript
kovli2 小时前
红宝书第十讲:「构造函数与原型链」入门及深入解读:用举例子+图画理解“套娃继承”
前端·javascript
就是我2 小时前
如何用lazy+ Suspense实现组件延迟加载
javascript·react native·react.js