JavaScript作为前端三剑客之一,在前端领域有着非比寻常的地位,本文介绍了一些常见知识点和面试考点,当作对自己前端生涯中的一些总结。
标签
- meta:对页面的元数据进行描述和定义,包括关键词、描述、作者、字符编码、视口大小、缩放比例等
- href:超文本引用,用来建立当前元素和文档之间的链接,常用的有link、a
- src:指向的内容会嵌入到文档当前标签所在位置,常用的有img、script、iframe
- script:所在位置会阻塞HTML的解析,可以使用defer或async异步加载外部JS脚本;带有defer标签的脚本加载完后会在所有元素解析完成后执行,按加载顺序执行;带有async标签的脚本加载完后会直接执行,阻塞其他元素的解析,不保证加载顺序执行
数据类型与声明
- 变量声明
- var存在变量提升,在函数作用域中生效
- let和const存在暂时性死区,在块级作用域中生效
- 函数是一等公民,函数提升优先级高于变量提升优先级
- 包装类型
基本类型没有属性和方法,但JavaScript提供了包装类型,在调用基本类型的属性或方法时会在后台隐性地将基本类型转换为对象 - 类型判断
- 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;
}
- 深浅拷贝
看复杂类型拷贝的是值还是指针
手写深拷贝:
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;
}
- 循环遍历
- for in:遍历对象、数组、字符串等可枚举数据,会遍历整个原型链,获取的是键名
- for of:遍历Map、Set等可迭代数据,只会遍历当前对象,获取的是键值
- map:有返回值,会生成一个新数组
- forEach:没有返回值,会改变原数组
- 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;
}
- 函数
- 高阶函数:接收函数作为参数的函数
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
- this指向
- new操作符指向创建实例
- 作为对象方法调用时指向该对象
- 直接作为函数调用时指向全局对象
- 箭头函数在创建时this指向根据上下文确定
- call、apply、bind会改变this指向
- 手写call
js
Function.prototype.myCall = function (obj, ...args) {
obj = obj || window;
const fn = Symbol("fn");
obj[fn] = this;
return obj[fn](...args);
};
- 手写apply
js
Function.prototype.myApply = function (obj, args) {
obj = obj || window;
const fn = Symbol("fn");
obj[fn] = this;
return obj[fn](...args);
};
- 手写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
- 手写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;
});
}
}
- 手写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);
}
});
});
}
- 手写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)
);
}
});
}
- 手写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"));
}
);
});
});
}
- 使用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);
};
}
设计模式
- 单例模式
js
class SingleClass {
constructor() {
if (!SingleClass.instance) {
this.prototype1 = "value1";
SingleClass.instance = this;
}
return SingleClass.instance;
}
}
- 发布订阅模式
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);
}
}