1 手写Promise系列
在Promise的学习中,之前也写过相关的分享文章,敬请参见《【前端】从小白视角上手Promise、Async/await和手撕代码》。
1.1 Promise.all
//手写promise.all
Promise.prototype._all = promiseList => {
// 当输入的是一个promise列表
const len = promiseList.length;
const result = [];
let count = 0;
//
return new Promise((resolve,reject)=>{
// 循环遍历promise列表中的promise事件
for(let i = 0; i < len; i++){
// 遍历到第i个promise事件,判断其事件是成功还是失败
promiseList[i].then(data=>{
result[i] = data;
count++;
// 当遍历到最后一个promise时,结果的数组长度和promise列表长度一致,说明成功
count === len && resolve(result);
},error=>{
return reject(error);
})
}
})
}
复制代码
1.2 Promise.race
// 手写promise.race
Promise.prototype._race = promiseList => {
const len = promiseList.length;
return new Promise((resolve,reject)=>{
// 循环遍历promise列表中的promise事件
for(let i = 0; i < len; i++){
promiseList[i]().then(data=>{
return resolve(data);
},error=>{
return reject(error);
})
}
})
}
复制代码
1.3 Promise.finally
Promise.prototype._finally = function(promiseFunc){
return this.then(data=>Promise.resolve(promiseFunc()).then(data=>data)
,error=>Promise.reject(promiseFunc()).then(error=>{throw error}))
}
复制代码
2 手写Aysnc/Await
function asyncGenertor(genFunc){
return new Promise((resolve,reject)=>{
// 生成一个迭代器
const gen = genFunc();
const step = (type,args)=>{
let next;
try{
next = gen[type](args);
}catch(e){
return reject(e);
}
// 从next中获取done和value的值
const {done,value} = next;
// 如果迭代器的状态是true
if(done) return resolve(value);
Promise.resolve(value).then(
val=>step("next",val),
err=>step("throw",err)
)
}
step("next");
})
}
复制代码
3 深拷贝
深拷贝:拷贝所有的属性值,以及属性地址指向的值的内存空间。
3.1 丢失引用的深拷贝
当遇到对象时,就再新开一个对象,然后将第二层源对象的属性值,完整地拷贝到这个新开的对象中。
// 丢失引用的深拷贝
function deepClone(obj){
// 判断obj的类型是否为object类型
if(!obj && typeof obj !== "object") return;
// 判断对象是数组类型还是对象类型
let newObj = Array.isArray(obj) ? [] : {};
// 遍历obj的键值对
for(const [key,value] of Object.entries(obj)){
newObj[key] = typeof value === "string" ? deepClone(value) : value;
};
return newObj;
}
复制代码
3.2 终极方案的深拷贝(栈和深度优先的思想)
其思路是:引入一个数组 uniqueList
用来存储已经拷贝的数组,每次循环遍历时,先判断对象是否在 uniqueList
中了,如果在的话就不执行拷贝逻辑了。
function deepCopy(obj){
// 用于去重
const uniqueList = [];
// 设置根节点
let root = {};
// 遍历数组
const loopList = [{
parent: root,
key: undefined,
data: obj
}];
// 遍历循环
while(loopList.length){
// 深度优先-将数组最后的元素取出
const {parent,key,data} = loopList.pop();
// 初始化赋值目标,key--undefined时拷贝到父元素,否则拷贝到子元素
let result = parent;
if(typeof key !== "undefined") result = parent[key] = {};
// 数据已存在时
let uniqueData = uniqueList.find(item=>item.source === data);
if(uniqueData){
parent[key] = uniqueData.target;
// 中断本次循环
continue;
}
// 数据不存在时
// 保存源数据,在拷贝数据中对应的引用
uniqueList.push({
source:data,
target:result
});
// 遍历数据
for(let k in data){
if(data.hasOwnProperty(k)){
typeof data[k] === "object"
?
// 下一次循环
loopList.push({
parent:result,
key:k,
data:data[k]
})
:
result[k] = data[k];
}
}
}
return root;
}
复制代码
4 手写一个单例模式
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。实现方法一般是先判断实例是否存在,如果存在直接返回,如果不存在就先创建再返回。
// 创建单例对象,使用闭包
const getSingle = function(func){
let result;
return function(){
return result || (result = func.apply(this,arguments));
}
}
// 使用Proxy拦截
const proxy = function(func){
let reuslt;
const handler = {
construct:function(){
if(!result) result = Reflect.construct(func,arguments);
return result;
}
}
return new Proxy(func,hendler);
}
复制代码
5 手写封装一个ajax函数
/*
封装自己的ajax函数
参数1:{string} method 请求方法
参数2:{string} url 请求地址
参数2:{Object} params 请求参数
参数3:{function} done 请求完成后执行的回调函数
*/
function ajax(method,url,params,done){
// 1.创建xhr对象,兼容写法
let xhr = window.XMLHttpRequest
? new XMLHttpRequest()
: new ActiveXObject("Microsoft.XMLHTTP");
// 将method转换成大写
method = method.toUpperCase();
// 参数拼接
let newParams = [];
for(let key in params){
newParams.push(`${key}=${params[k]}`);
}
let str = newParams.join("&");
// 判断请求方法
if(method === "GET") url += `?${str}`;
// 打开请求方式
xhr.open(method,url);
let data = null;
if(method === "POST"){
// 设置请求头
xhr.setRequestHeader(("Content-Type","application/x-www-form-urlencoded"));
data = str;
}
xhr.send(data);
// 指定xhr状态变化事件处理函数
// 执行回调函数
xhr.onreadystatechange = function(){
if(this.readyState === 4) done(JSON.parse(xhr.responseText));
}
}
复制代码
6 手写“防抖”和“节流”
在“防抖”和“节流”的学习中,之前也写过相关的分享文章,敬请参见《一网打尽──前端必会的“防抖”和“节流”方法》。
6.1 防抖
/*
func:要进行防抖处理的函数
delay:要进行延时的时间
immediate:是否使用立即执行 true立即执行 false非立即执行
*/
function debounce(func,delay,immediate){
let timeout; //定时器
return function(arguments){
// 判断定时器是否存在,存在的话进行清除,重新进行定时器计数
if(timeout) clearTimeout(timeout);
// 判断是立即执行的防抖还是非立即执行的防抖
if(immediate){//立即执行
const flag = !timeout;//此处是取反操作
timeout = setTimeout(()=>{
timeout = null;
},delay);
// 触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
if(flag) func.call(this,arguments);
}else{//非立即执行
timeout = setTimeout(()=>{
func.call(this,arguments);
},delay)
}
}
}
复制代码
6.2 节流
// 节流--定时器版
function throttle(func,delay){
let timeout;//定义一个定时器标记
return function(arguments){
// 判断是否存在定时器
if(!timeout){
// 创建一个定时器
timeout = setTimeout(()=>{
// delay时间间隔清空定时器
clearTimeout(timeout);
func.call(this,arguments);
},delay)
}
}
}
复制代码
7 手写apply、bind、call
7.1 apply
- 传递给函数的参数处理,不太一样,其他部分跟call一样。
- apply接受第二个参数为类数组对象, 这里用了《JavaScript权威指南》中判断是否为类数组对象的方法。
Function.prototype._apply = function (context) {
if (context === null || context === undefined) {
context = window // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
} else {
context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
}
// JavaScript权威指南判断是否为类数组对象
function isArrayLike(o) {
if (o && // o不是null、undefined等
typeof o === 'object' && // o是对象
isFinite(o.length) && // o.length是有限数值
o.length >= 0 && // o.length为非负值
o.length === Math.floor(o.length) && // o.length是整数
o.length < 4294967296) // o.length < 2^32
return true
else
return false
}
const specialPrototype = Symbol('特殊属性Symbol') // 用于临时储存函数
context[specialPrototype] = this; // 隐式绑定this指向到context上
let args = arguments[1]; // 获取参数数组
let result
// 处理传进来的第二个参数
if (args) {
// 是否传递第二个参数
if (!Array.isArray(args) && !isArrayLike(args)) {
throw new TypeError('myApply 第二个参数不为数组并且不为类数组对象抛出错误');
} else {
args = Array.from(args) // 转为数组
result = context[specialPrototype](...args); // 执行函数并展开数组,传递函数参数
}
} else {
result = context[specialPrototype](); // 执行函数
}
delete context[specialPrototype]; // 删除上下文对象的属性
return result; // 返回函数执行结果
};
复制代码
7.2 bind
-
拷贝源函数:
- 通过变量储存源函数
- 使用Object.create复制源函数的prototype给fToBind
-
返回拷贝的函数
-
调用拷贝的函数:
- new调用判断:通过instanceof判断函数是否通过new调用,来决定绑定的context
- 绑定this+传递参数
- 返回源函数的执行结果
Function.prototype._bind = function (objThis, ...params) {
const thisFn = this; // 存储源函数以及上方的params(函数参数)
// 对返回的函数 secondParams 二次传参
let fToBind = function (...secondParams) {
const isNew = this instanceof fToBind // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用
const context = isNew ? this : Object(objThis) // new调用就绑定到this上,否则就绑定到传入的objThis上
return thisFn.call(context, ...params, ...secondParams); // 用call调用源函数绑定this的指向并传递参数,返回执行结果
};
if (thisFn.prototype) {
// 复制源函数的prototype给fToBind 一些情况下函数没有prototype,比如箭头函数
fToBind.prototype = Object.create(thisFn.prototype);
}
return fToBind; // 返回拷贝的函数
};
复制代码
7.3 call
- 根据call的规则设置上下文对象,也就是this的指向。
- 通过设置context的属性,将函数的this指向隐式绑定到context上
- 通过隐式绑定执行函数并传递参数。
- 删除临时属性,返回函数执行结果
Function.prototype._call = function (context, ...arr) {
if (context === null || context === undefined) {
// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
context = window
} else {
context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
}
const specialPrototype = Symbol('特殊属性Symbol') // 用于临时储存函数
context[specialPrototype] = this; // 函数的this指向隐式绑定到context上
let result = context[specialPrototype](...arr); // 通过隐式绑定执行函数并传递参数
delete context[specialPrototype]; // 删除上下文对象的属性
return result; // 返回函数执行结果
};
复制代码
8 手写继承
8.1 构造函数式继承
构造函数式继承并没有继承父类原型上的方法。
function fatherUser(username, password) {
let _password = password
this.username = username
fatherUser.prototype.login = function () {
console.log(this.username + '要登录父亲账号,密码是' + _password)
}
}
function sonUser(username, password) {
fatherUser.call(this, username, password)
this.articles = 3 // 文章数量
}
const yichuanUser = new sonUser('yichuan', 'xxx')
console.log(yichuanUser.username) // yichuan
console.log(yichuanUser.username) // xxx
console.log(yichuanUser.login()) // TypeError: yichuanUser.login is not a function
复制代码
8.2 组合式继承
function fatherUser(username, password) {
let _password = password
this.username = username
fatherUser.prototype.login = function () {
console.log(this.username + '要登录fatherUser,密码是' + _password)
}
}
function sonUser(username, password) {
fatherUser.call(this, username, password) // 第二次执行 fatherUser 的构造函数
this.articles = 3 // 文章数量
}
sonUser.prototype = new fatherUser(); // 第二次执行 fatherUser 的构造函数
const yichuanUser = new sonUser('yichuan', 'xxx')
复制代码
8.3 寄生组合继承
上面的继承方式有所缺陷,所以写这种方式即可。
function Parent() {
this.name = 'parent';
}
function Child() {
Parent.call(this);
this.type = 'children';
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
复制代码
参考文章
《js基础-面试官想知道你有多理解call,apply,bind?[不看后悔系列]》
写在最后
我是前端小菜鸡,感谢大家的阅读,我将继续和大家分享更多优秀的文章,此文参考了大量书籍和文章,如果有错误和纰漏,希望能给予指正。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END