以下题目来自掘金等其它博客,但是问题的答案都是根据笔者自己的理解做出的。如果你最近想要换工作或者巩固一下自己的前端知识基础,不妨和我一起参与到每日刷题的过程中来,如何?
第16天要刷的手写题如下:
- 实现一个函数,能够将传入的函数promisify
- 多种方式实现对象私有化属性
- 封装异步函数以防止await操作符作用的时候其出错溢出
下面是我的一些理解:
1. 实现一个函数,能够将传入的函数promise化
原理:将输入的函数A映射称为函数B, B函数的返回值是一个Promise对象,在此Promise对象内部的同步代码中会执行原来的函数A并为其构造一个临时的回调函数,在此临时回调函数内部改变包裹的Promise对象的状态,从而实现数据的传递。
下面就以fs.readFile演化成fs.readFileSync为例解释上面的原理:
1.1 fs.readFile的使用
js
fs.readFile('data.json', 'utf8', function(err, data){
if (err) {
handleError(err);
} else {
handleData(data);
}
});
!上述代码中的handleError
和handleData
是外部提供的将错误/数据传递出去的接口。
1.2 使用一个函数包裹这个过程,将其中使用到的量参数化
js
const _f = (...args) => {
fs.readFile(...args, function(err, data){
if (err) {
handleError(err);
} else {
handleData(data);
}
});
}
_f('data.json', 'utf8');
1.3 使用柯里化的思想将fs.readFile也作为参数
js
const _c = (exec) => {
return (...args) => {
exec(...args, function(err, data){
if (err) {
handleError(err);
} else {
handleData(data);
}
});
}
}
_c(fs.readFile)('data.json', 'utf8');
1.4 使用Promise的resolve和reject作为handleError和handleData
js
const _c = (exec) => {
return new Promise ((handleData, handleError)=>{
(...args) => {
exec(...args, function(err, data){
if (err) {
handleError(err);
} else {
handleData(data);
}
});
}
})
}
const _p = _c(fs.readFile)('data.json', 'utf8');
1.5 使用void、箭头函数和三目运算对上面的函数进行简化
js
const _c = exec => new Promise ((handleData, handleError)=>void (...args) => void exec(...args, (err, data) => void err ? handleError(err) : handleData(data)));
最后的答案就是:exec => new Promise ((handleData, handleError)=>void (...args) => void exec(...args, (err, data) => void err ? handleError(err) : handleData(data)));
如果想要继续装一下,写成e => new Promise((a, b)=> (...c) => e(...c, (n, d) => n ? b(n) : a(d)))
1.6 总结
所谓promisify,其本质就是使用promise两种修改状态的函数作为处理失败和成功的函数。
2. 使用多种方式实现对象私有化属性
常见的实现方法有三种:
- 使用代理,即Proxy
- 使用闭包和Symbol
- 使用闭包和哈希表
2.1 使用代理,对带有特殊标记的属性做特殊的处理
需要劫持的是get和ownKeys两种操作:
js
const getProxyObj = obj => new Proxy ( obj, {
get (target, key) {
if(key.startsWith('_')) throw new Error();
return Reflect.get(target, key);
},
ownKeys (target) {
return Reflect.ownKeys(target).filter(v=>!v.startsWith('_'));
}
})
2.2 使用闭包提供一个独一无二的属性名
由于在闭包的外部拿不到这个独一无二的属性名,也无法将其生产出来,所以可以在一定程度上保证私有性;但是如果使用键的遍历方法还是可以找到的。
js
const MC = (
function () {
const _age = Symbol('age');
return class MC {
constructor(name){
this._name = name;
}
}
}
)();
2.3 使用闭包提供一个哈希表,哈希表中存放的是实例和实例的私有属性的对应关系
- 在闭包外面拿不到这个哈希表所以可以在一定程度上保证私有性;
- 使用WeakMap而不是Map解决内存泄漏的问题
js
const MC = (
function () {
const _wm = new WeakMap();
return class MC {
constructor(name, age){
_wm.set(this, {name, age});
}
get(key){
return _wm.get(this)[key];
}
}
}
)();
此外还可以在编译阶段解决此问题,比如使用ts中的private修饰符。
3. 封装异步函数以防止await操作符作用的时候其出错溢出
原理:将异步函数A通过此函数映射成为异步函数B,在B中执行A,同时对错误进行捕获并输出
js
const useWrapper = async originFunc => {
try {
const rst = await originFunc();
return [rst, null];
} catch (err) {
return [null, err];
}
};
// 原来的调用方式
let data;
try {
data = await originFunc();
} catch (e) {
data = null;
}
// 包装之后的调用方式
const [data, err] = await useWrapper(originFunc);