手写系列
- 参考资料
- B站up 后盾人老师
Promise(setTimeout方式)
/* promise特点 */
// 1. 有三种状态(默认为pending)
// 2. 状态改变后不可再改变
// 3. 执行体里出问题,状态变为拒绝(try catch)
// 4. then构建(判断状态)(不传resolve或reject不报错)(then使用setTimeout变成异步)(执行体状态改变为异步的处理)
// 5. then的链式操作(then返回promise)(返回已解决也是走成功)
// 6. then的穿透传递
// 7. then返回promise的处理(对return的值作区分)
// 8. promise返回值作约束
// 9. 实现Promise.resolve()和Promise.reject()
// 10. promise的all方法
// 11. promise的race方法
class HD {
static PENDING = "pending";
static FUFILLED = "fulfilled";
static REJECTED = "rejected";
constructor(executor) {
this.status = HD.PENDING;
this.value = null;
// 储存状态未改变前需要执行的函数
this.callbacks = [];
try {
// 需要bind绑定this为HD,因为外部调用this为undefined(class模式默认遵循严格模式,指向window就为undefined)(resolve()或reject()之后的同步代码应先执行)
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
resolve(value) {
if (this.status == HD.PENDING) {
this.status = HD.FUFILLED;
this.value = value;
// resolve()或reject()之后的同步代码应先执行
setTimeout(() => {
this.callbacks.forEach((callback) => {
callback.onFulfilled(value);
});
});
}
}
reject(reason) {
if (this.status == HD.PENDING) {
this.status = HD.REJECTED;
this.value = reason;
setTimeout(() => {
this.callbacks.forEach((callback) => {
callback.onRejected(reason);
});
});
}
}
then(onFulfilled, onRejected) {
// 不传resolve或reject不报错
if (typeof onFulfilled != "function") {
// then的穿透传递
onFulfilled = () => this.value;
}
if (typeof onRejected != "function") {
// then的穿透传递
onRejected = () => this.value;
}
// 执行体状态改变为异步的处理
let promise = new HD((resolve, reject) => {
if (this.status == HD.PENDING) {
this.callbacks.push({
onFulfilled: (value) => {
this.parse(promise, onFulfilled(value), resolve, reject);
},
onRejected: (value) => {
this.parse(promise, onRejected(value), resolve, reject);
},
});
}
// 判断状态是否改变
if (this.status == HD.FUFILLED) {
// then使用setTimeout变成异步
setTimeout(() => {
this.parse(promise, onFulfilled(this.value), resolve, reject);
});
}
if (this.status == HD.REJECTED) {
// then使用setTimeout变成异步
setTimeout(() => {
this.parse(promise, onRejected(this.value), resolve, reject);
});
}
});
// then返回promise
return promise;
}
parse(promise, result, resolve, reject) {
// promise返回值作约束
if (promise == result) {
throw new TypeError("Chaining cycle detected for promise");
}
try {
// then返回promise的处理
if (result instanceof HD) {
// HD(promise)的实例
result.then(resolve, reject);
} else {
// 这里普通的
return result;
}
} catch (error) {
reject(error);
}
}
// 实现Promise.resolve()和Promise.reject()
static resolve(value) {
return new HD((resolve, reject) => {
if (value instanceof HD) {
value.then(resolve, reject);
} else {
resolve(value);
}
});
}
static reject(value) {
return new HD((resolve, reject) => {
if (value instanceof HD) {
value.then(resolve, reject);
} else {
reject(value);
}
});
}
// promise的all方法
static all(promises) {
const values = new Array(promises.length);
let count = 0;
return new HD((resolve, reject) => {
promises.forEach((promise, index) => {
// HD.promise(promise)解决了传递非promise参数的问题
HD.promise(promise).then(
(value) => {
// 这里保证了all的顺序与输出顺序一致
values[index] = value;
count++;
if (count == promises.length) {
resolve(values);
}
},
(reason) => {
reject(reason);
}
);
});
});
}
// promise的race方法
static race(promises) {
return new HD((resolve, reject) => {
promises.forEach((promise, index) => {
promise.then(
(value) => {
resolve(values);
},
(reason) => {
reject(reason);
}
);
});
});
}
}
vue2响应式原理(defineProperty + 发布订阅),不完整仅作参考
-
defineReactive函数:
利用defineProperty 劫持数据
import observe from "./observe"; export default function defineReactive(data, key, val) { if (arguments.length == 2) { // 这里的val代表下一层的 val = data[key]; } // 子元素要进行observe,至此形成递归。这个递归不是函数自己调用自己,而是多个函数、类循环调用 let childOb = observe(val); // val提供了一个闭包环境 Object.defineProperty(data, key, { // 可枚举 enumerable: true, // 可以被配置 configurable: true, get() { console.log("访问" + key + "属性"); return val; }, set(newValue) { if (newValue === val) { console.log("改变" + key + "属性"); val = newValue; // 当设置了新值,这个新值也要被observe childOb = observe(newValue) } }, }); } -
Observer类
将一个正常的object转换为每个层级都是响应式(可以被侦测的object)
import Observer from "./Observer"; // 创建observe函数,辅助判别 export default function observe(value) { // 如果不是对象,什么都不做 if (typeof value != "object") return; // 定义ob let ob; if (typeof value.__ob__ != "undefined") { ob = value.__ob__; } else { ob = new Observer(value); } return ob; } -
observe函数
辅助判别
import {def} from './utils'
import defineReactive from './defineReactive';
import { arrayMethods } from './array'
import observe from './observe';
// 将一个正常的object转换为每个层级都是响应式(可以被侦测的object)
export default class Observer{
constructor(value) {
// console.log('我是observer构造器', value);
// 给实例添加了__ob__属性,值是这次new的实例
def(value, '__ob__', this, false)
// 检查它是数组还是对象
if (Array.isArray(value)) {
// 如果是数组,要非常强行的将这个数组的原型指向这个arrayMethods
Object.setPrototypeOf(value, arrayMethods)
// 让这个数组变的observe
this.observeArray(value)
} else {
// 执行循环
this.walk(value)
}
}
// 遍历:给该对象(value)下所有属性使用defineReactive
walk(value) {
// 深度优先遍历
for (let key in value) {
defineReactive(value,key)
}
}
// 数组的特殊遍历
observeArray(arr) {
for (let i = 0, l = arr.length; i < l; i++){
// 逐项进行observe
observe(arr[i])
}
}
}
- def工具函数
使某属性(_ob_)变成不可枚举属性
// 工具函数:使某属性变成不可枚举属性
export const def = function (obj, key, value, enumerable) {
Object.defineProperty(obj, key, {
value,
enumerable,
writable: true,
configurable:true
})
}
-
array函数
重写了数组的七个方法 "push","pop","shift", "unshift", "splice","sort","reverse"
import { def } from "./utils";
// 得到Array.prototype
const arrayPrototype = Array.prototype;
// Array.prototype为原型创建arrayMethods对象
export const arrayMethods = Object.create(arrayPrototype);
// 暴露
// 要被改写的七个数组方法
const methodsNeedChange = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse",
];
methodsNeedChange.forEach((methodName) => {
// 备份原来的方法,因为push,pop等7个函数的功能不能被剥夺
const original = arrayPrototype[methodName];
// 定义新的方法
def(
arrayMethods,
methodName,
function () {
// 恢复原来的功能
let result = original.apply(this, arguments);
// 把这个数组身上的__ob__取出来,__ob__已经被添加了?因为数组肯定不是最高层
// this.指的是调用方法的数组
const ob = this.__ob__;
// push,unshift,splice能够插入新项,现在要把插入的新项也要变为observe的
let inserted = [];
switch (methodName) {
case "push":
case "unshift":
inserted = [...arguments];
break;
case "splice":
// Splice格式是splice(下标,数量,插入的新项)
inserted = [...arguments].slice(2);
break;
}
// 判断有没有插入的新项,让新项也变为响应的
if (inserted.length) {
ob.observeArray(inserted);
}
return result;
},
false
);
});
-
收集依赖
在getter中收集依赖,在setter中触发依赖
- 依赖就是watcher。只有Watcher触发的getter才会收集依赖,哪个watcher触发了getter,就把哪个watcher收集到Dep中。
- Dep使用发布订阅模式,当数据发生变化时,会循环依赖列表,把所有的watcher都通知一遍。
- 代码实现的巧妙之处: watcher把自己设置到全局的一个指定位置,然后读取数据,因为读取了数据,所以会触发这个数据的getter。在getter中就能得到当前正在读取数据的watcher,并把这个watcher收集到Dep中。
防抖节流函数
// 防抖
function debounce(callback, delay) {
// timer使用了闭包
let timer = null;
return function () {
// 记录函数的执行环境
let context = this;
let args = arguments;
// 如果此时存在定时器的话,则取消之前的定时器重新记时
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
// 在实际执行函数的环境中执行,直接执行的话this指向window
callback.apply(context, args);
}, delay);
};
}
// 节流
function throttle(callback, delay) {
// flag使用了闭包
let flag = false;
return function () {
let context = this;
args = arguments;
// 判断是否执行
if (flag) return;
flag = true;
callback.apply(context, args);
setTimeout(() => {
flag = false;
}, delay);
};
}
Promise实现并发控制
-
实现一个输入url数组与并发数的并发数量控制请求队列函数。
-
核心思路:控制并发数量,关键点就是利用promise,当一个请求完成后,去发起下一个请求。
const fn = url => {
// 实际场景这里用axios等请求库 发请求即可 也不用设置延时
return new Promise(resolve => {
setTimeout(() => {
console.log('完成一个任务', url, new Date());
resolve({ url, date: new Date() });
}, 1000);
})
};
function limitQueue(urls, limit) {
// 完成任务数
let i = 0;
// 填充满执行队列
for (let excuteCount = 0; excuteCount < limit; excuteCount++) {
run();
}
// 执行一个任务
function run() {
// 构造待执行任务 当该任务完成后 如果还有待完成的任务 继续执行任务(核心思路)
new Promise((resolve, reject) => {
const url = urls[i];
i++;
resolve(fn(url))
}).then(() => {
if (i < urls.length) run()
})
}
};
Promise封装原生Ajax
function Ajax(url, method, data) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.response);
} else {
reject(new Error("error"));
}
}
};
if (method.toUpperCase() === "GET") {
let paramsList = [];
for (let key in data) {
paramsList.push(key + "=" + data[key]);
}
let params = paramsList.join("&");
url = url + "?" + params;
xhr.open("get", url, true);
xhr.send();
} else if (method.toUpperCase() === "POST") {
xhr.open("post", url, true);
xhr.setRequestHeader(
"Content-Type",
"application/x-www-form-urlencoded;charset=utf-8"
);
xhr.send(data);
}
});
}
Promise封装一个delay函数
const delay = function (time) {
if (typeof time !== "number") return;
return new Promise((resolve, reject) => {
setTimeout(resolve, time);
});
};
手写Promise.all()
function PromiseAll(promiseArray) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promiseArray))
return reject(new Error("传入的参数不是数组!"));
const values = [];
let count = 0;
promiseArray.forEach((promise, index) => {
// 使用 Promise.resolve是因为兼容传递非promise的参数项
Promise.resolve(promise).then(
(res) => {
// 这里保证了all的顺序与输出顺序一致
values[index] = res;
count++;
if (count == promiseArray.length) resolve(values);
},
(error) => {
return reject(error);
}
);
});
});
}
手写Promise.race()
function PromiseRace(promiseArray) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promiseArray))
return reject(new Error("传入的参数不是数组!"));
promiseArray.forEach((promise, index) => {
Promise.resolve(promise).then(
((res) => {
resolve(res);
},
(error) => {
reject(error);
})
);
});
});
}
数组去重
深拷贝
function deepClone(target) {
// WeakMap作为记录对象Hash表(用于防止循环引用)
const map = new WeakMap()
// 判断是否为object类型的辅助函数,减少重复代码
function isObject(target) {
return (typeof target === 'object' && target ) || typeof target === 'function'
}
function clone(data) {
// 基础类型直接返回值
if (!isObject(data)) {
return data
}
// 日期或者正则对象则直接构造一个新的对象返回
if ([Date, RegExp].includes(data.constructor)) {
return new data.constructor(data)
}
// 处理函数对象
if (typeof data === 'function') {
return new Function('return ' + data.toString())()
}
// 如果该对象已存在,则直接返回该对象
const exist = map.get(data)
if (exist) {
return exist
}
// 处理Map对象
if (data instanceof Map) {
const result = new Map()
map.set(data, result)
data.forEach((val, key) => {
// 注意:map中的值为object的话也得深拷贝
if (isObject(val)) {
result.set(key, clone(val))
} else {
result.set(key, val)
}
})
return result
}
// 处理Set对象
if (data instanceof Set) {
const result = new Set()
map.set(data, result)
data.forEach(val => {
// 注意:set中的值为object的话也得深拷贝
if (isObject(val)) {
result.add(clone(val))
} else {
result.add(val)
}
})
return result
}
// 收集键名(考虑了以Symbol作为key以及不可枚举的属性)
const keys = Reflect.ownKeys(data)
// 利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性以及对应的属性描述
const allDesc = Object.getOwnPropertyDescriptors(data)
// 结合 Object 的 create 方法创建一个新对象,并继承传入原对象的原型链, 这里得到的result是对data的浅拷贝
const result = Object.create(Object.getPrototypeOf(data), allDesc)
// 新对象加入到map中,进行记录
map.set(data, result)
// Object.create()是浅拷贝,所以要判断并递归执行深拷贝
keys.forEach(key => {
const val = data[key]
if (isObject(val)) {
// 属性值为 对象类型 或 函数对象 的话也需要进行深拷贝
result[key] = clone(val)
} else {
result[key] = val
}
})
return result
}
return clone(target)
}
Vue Router实现原理
EventBus(含emit、on、once、off)
- 参考资料
- 注意点:
- 订阅事件如何存储?
- 如何传递参数?
- 如何给每个订阅事件添加唯一标识?
- 如何正确删除存储的订阅事件?
$on和$emit主要担任的是什么角色?- 什么时发布订阅模式?
class EventBus {
constructor() {
this.eventObj = {};
this.callbackId = 0;
}
$on(eventNanme, callback) {
if (!this.eventObj[eventNanme]) {
this.eventObj[eventNanme] = {};
}
this.callbackId++;
this.eventObj[eventNanme][this.callbackId] = callback;
return this.callbackId;
}
$emit(eventNanme, ...args) {
if (!this.eventObj[eventNanme]) {
return new Error("未监听该事件");
}
for (let id in this.eventObj[eventNanme]) {
this.eventObj[eventNanme][id](...args);
if (id.indexOf("d") != -1) {
delete this.eventObj[eventNanme][id];
}
}
if (!Object.keys(this.eventObj[eventNanme]).length) {
delete this.eventObj[eventNanme];
}
}
$once(eventNanme, callback) {
if (!this.eventObj[eventNanme]) {
this.eventObj[eventNanme] = {};
}
this.callbackId++;
this.eventObj[eventNanme]["d" + this.callbackId] = callback;
return this.callbackId;
}
$off(eventNanme, callbackId) {
if (
!(eventNanme in this.eventObj) ||
!Object.keys(eventNanme[callbackId]).length
) {
throw new Error("事件不存在或处理函数不存在");
return;
}
if (!eventNanme && !callbackId) {
// 如果没有提供参数,则移除所有的事件监听器
this.eventObj = {};
} else if (eventNanme && !callbackId) {
// 如果只提供了事件,则移除该事件所有的监听器;
delete this.eventObj[eventNanme];
} else if (eventNanme && callbackId) {
// 如果同时提供了事件与回调,则只移除这个回调的监听器。
delete this.eventObj[eventNanme][callbackId];
if (!Object.keys(this.eventObj[eventNanme]).length) {
delete this.eventObj[eventNanme];
}
} else {
throw new Error("$off调用格式错误");
return;
}
}
}
手写类型判断函数
function getType(value) {
// 判断数据是 null 的情况
if (value === null) {
return value + "";
}
// 判断数据是引用类型的情况
if (typeof value === "object") {
let valueClass = Object.prototype.toString.call(value),
type = valueClass.split(" ")[1].split("");
type.pop();
return type.join("").toLowerCase();
} else {
// 判断数据是基本数据类型的情况和函数的情况
return typeof value;
}
}
手写instanceOf
-
核心思想:
沿着原型链向上查找,检查目标对象的原型链中是否有与指定对象的原型相同的原型!
function myInstanceOf(left, right) {
if (typeof left !== "object" || left === null) return false;
if (typeof right !== "function" || !right.prototype) {
throw new TypeError(
"Right-hand side of " instanceof " is not an object"
);
}
let proto = left.__proto__;
while (proto !== null) {
if (right.prototype === proto) return true;
proto = proto.__proto__;
}
return false;
}
手写new操作符
function objectFactory() {
// 提取constructor构造函数
let constructor = Array.prototype.shift.apply(arguments);
if (typeof constructor !== "function") {
throw Error("必须为函数");
return;
}
// 创建空对象
let newObj = null;
// 将空对象的原型指向构造函数的原型
newObj = Object.create(constructor.prototype);
// 调用构造函数并让构造函数的this指向新创建的对象
let result = constructor.apply(newObj, arguments);
// 判断 return 的值类型
const flag =
result &&
(typeof result === "object" || typeof result === "function");
return flag ? result : newObj;
}
// 使用方法
objectFactory(构造函数, 初始化参数);
手写call及apply
call 函数的实现步骤:
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 处理传入的参数,截取第一个参数后的所有参数。
- 将函数作为上下文对象的一个属性。
- 使用上下文对象来调用这个方法,并保存返回结果。
- 删除刚才新增的属性。
- 返回结果。
// call函数实现
Function.prototype.myCall = function(context) {
// 判断调用对象
if (typeof this !== "function") {
console.error("type error");
}
// 获取参数
let args = [...arguments].slice(1),
result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context || window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
};
// apply 函数实现
Function.prototype.myApply = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判断 context 是否存在,如果未传入则为 window
context = context || window;
// 将函数设为对象的方法
context.fn = this;
// 调用方法
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 将属性删除
delete context.fn;
return result;
};
用call/apply实现bind
bind 函数的实现步骤:
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 保存当前函数的引用,获取其余传入参数值。
- 创建一个函数返回
- 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。
// bind 函数实现
Function.prototype.myBind = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 获取参数
var args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 根据调用方式,传入不同绑定值
return fn.apply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
};
实现数组的flat方法
function _flat(arr, depth) {
// 判断arr是否为数组
if(!Array.isArray(arr) || depth <= 0) {
return arr;
}
return arr.reduce((prev, cur) => {
// 当前项是否为数组,如果是数组则继续递归
if (Array.isArray(cur)) {
return prev.concat(_flat(cur, depth - 1))
} else {
return prev.concat(cur);
}
}, []);
}
数组扁平化
普通的递归思路很容易理解,就是通过循环递归的方式,一项一项地去遍历,如果每一项还是一个数组,那么就继续往下遍历,利用递归程序的方法,来实现数组的每一项的连接:
function flatten(arr) {
let result = [];
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
函数柯里化
// 柯里化函数
function curry (fn) {
return function nest(...args) {
// fn.length表示函数的形参个数
if (args.length === fn.length) {
// 当参数接收的数量达到了函数fn的形参个数,即所有参数已经都接收完毕则进行最终的调用
return fn(...args);
} else {
// 参数还未完全接收完毕,递归返回judge,将新的参数传入
return function (arg) {
return nest(...args, arg);
};
}
};
};
手写 reduce
function myReduce(callback, initValues) {
const arr = this;
if (!Array.isArray(arr)) {
throw new Error("error");
}
const result = initValues;
for (let i = 0; i < arr.length; i++) {
result = callback(result, arr[i], i, result);
}
return result;
}
手写 Object.create
function myCreate(proto) {
if (typeof proto !== 'object' && typeof proto !== 'function' || proto === null) {
throw new TypeError('Object prototype may only be an Object or null');
}
function F() {}
F.prototype = proto;
return new F();
}