Hawei

Say Hi.


  • Home

  • About

  • Tags

  • Categories

  • Archives

简历.md

Posted on 2020-04-12 | In interview

期望薪资: 12k以上

工作经验

2017-11-24至今
职位: 中级工程师(二级)-HW/工程师B-MAG(web方向)
深圳软通动力信息技术有限公司
工作职责描述: 作为前端负责人, 在项目中根据用户故事进行开发, 并负责一定的管理工作, 组织项目组内晨会,技术方案定制, 同时对部分需求进行开发, 不定期组织总结和分享.
团队人数: 5人 直接上级: 项目经理
在职荣誉:

  • 18年十一月, 荣获华南区2018年优秀应届生奖项(有证书)
  • 18年十二月, 荣获优秀新员工奖项(有证书)
  • 2019年绩效为: S/A/S
  • 在职期间经历二次调级

在职业绩:

  • 2019年6月: 支撑华为营销系统BCM的开发, 在时间少任务重的情况下, 有质量的完成了开发任务, 得到华为方的好评.
  • 2019年年初: 能力突出, 在esales2.0版本的时候作为前端负责人, 负责需求的评估分析/技术方案定制. 完成了脚手架的搭建,营销经理模块的开发, 项目从0-1.
  • 2018年5月: 负责esales手机端H5大赛答题系统的开发.
  • 2017至2018年: 在负责华为面向销售人员的电子商务系统,esales平台开发, 完成了其中管理文档平台的性能优化和重构的任务.得到华为方的好评.

自我评价:

  • 对开发工作有浓厚的兴趣
  • 持续精进, 实现自我价值
  • 关注前端领域的技术趋势
  • 能很快的融入团队, 学习能力强
  • 性格开朗, 与团队成员相处和睦
  • 有敏捷开发迭代经验,能把控开发进度, 并发现风险, 并且处理风险的能力

项目经验
华为BCM营销平台/lead模块 2019年5月-至今
项目描述:BCM营销平台是管理营销方案的一个平台. 主要负责对营销活动的管理.lead模块基于线索,根据线索进行流程化的管理.
项目基础: UI框架基于Vue和elementUI, 使用Vuex进行状态管理, 使用VueRouter进行路由以及权限控制, 使用git进行版本控制。
岗位/职责: 前端开发负责人

  • 负责前端项目版本需求分析评估
  • 任务分配, 需求方案定制
  • 代码规范管理, git版本控制
  • 组织codeReview
  • 并对需求进行开发, 解决部分开发问题.

esales2.0 2019年1月-5月
项目描述: esales2.0是提供给华为销售人员使用的电子商务系统.已经迭代2个大版本.该系统为门户类系统.主要为销售人员提供数据可视化, 流程可视化的功能.一站式销售平台.
项目基础: UI框架基于Vue的AUI(华为内部自研框架)与ElementUI,使用Vuex进行状态管理,使用VueRouter进行路由控制,图表库使用了Echart进行开发,使用Git进行版本控制.
岗位/职责: 前端开发负责人

  • 负责前端项目脚手架的搭建
  • 需求分析, 项目风险评估,方案定制
  • 代码协作管理
  • 对需求进行开发.

esales2.0APP H5 竞赛答题 2018.6月-12月
项目描述:esales2.0是基于esalesPC2.0开发的APP, 有ios端和安卓端.功能是PC端的缩小版.
项目基础: UI框架基于Vue的ElementUI, 图表库使用Echart.与客户端的同学协作开发混合应用.
岗位/职责: 核心开发

  • 需求分析,方案定制
  • 完成与原生APP的交互(路由和状态)
  • 解决部分兼容性问题

esales1.0PC 真实性文档管理平台2.0 2017.12-2018.6
项目描述: 文档管理平台是esales1.0下的一个子应用, 主要功能是提供给销售完成单子之后相关的文件管理.
项目基础: 基于谷歌的dojo与jquery进行开发.图表库使用Echart进行开发.
岗位/职责: 核心开发

  • 基于原有的项目进行开发和维护
  • 对已有的工程进行性能优化
  • 对当前较为混乱的工程进行部分重构优化
  • 跟踪当前存在的生产问题并且修复

技能特长:

  • 前端基础(HTML5/CSS/CSS3/JS)扎实, 熟悉开发必要流程
  • 熟练使用CSS编译器SCSS, 模块化css
  • 熟练掌握JS, 日常ES6进行开发
  • 掌握css3动画特性
  • 有前后端分离, 前后端联调经验
  • 有前端工程化的经验, 熟悉Webpack构建
  • 熟练掌握Vue,以及Vue相关的生态系统(Vuex/VueRouter)
  • 基本掌握React以及相关生态
  • 熟悉node.js有开发RESTAPI的经验
  • 会使用Linux命令行,了解Nginx服务器
  • 日常使用vscode作为IDE, postman作为接口调试工具
  • 工作细致, 对自己的任务负责到底, 有较强的适应/沟通/表达能力
  • 熟悉项目各阶段软件实施的工作流程
  • 有敏捷开发团队及迭代开发的经验

教育经历
2014/09- 2018/06
桂林电子科技大学
本科 | 机械设计制造及其自动化

3 Practical Uses of Object Destructuring in JavaScript.md

Posted on 2020-04-12 | In translation

3 Practical Uses of Object Destructuring in JavaScript(翻译)

现在你可能非常熟悉js中解构的概念了!解构这个概念来自2015年ES6草案, 但如果你需要更深一步的了解它, Mozilla有一篇更深入的文章说明它是如何工作的。(文章底部)

然而, 了解解构如何工作并不等于我们了解了如何使用它。使用这三个解构模式可以让你的代码更加清晰, 更强大, 更具可读性。

Named Function Arguments (命名函数参数)

相较于我们通过单个传参, 通过传入参数的位置来控制传参,解构模式用于形式参数是一个很好的代替方式.你只需按名称指定参数,而不是按照与函数签名相同的顺序排序参数。例如,在Python中:

1
2
3
4
def sum(a=1, b=2, c=3):
return a + b + c

sum(b=5, c=10)

就像你所看到的一样, 参数的顺序并不是问题, 你通过名字指定了他们。命名参数相较于基于位置的参数命名有以下的好处:

  1. 调用函数时, 可以省略一个或者多个参数
  2. 当传参的时候,顺序并不是问题
  3. 调用可能存在于其他地方的函数时,代码更具可读性

虽然JavaScript中不存在真正的命名参数,但我们可以使用解构模式来实现所有3个相同的好处。这是和上面python相同功能的代码, 但是在js中我们可以这样:

1
2
3
4
5
const sum = ({a=1, b=2, c=3}) => {
return a + b + c;
}

sum({b: 5, a: 1}); // 9

这种模式符合我们命名参数的所有目标。我们能够省去参数c,顺序无关紧要,我们通过名称引用它们来分配我们的参数。这一切都可以通过对象解构来实现。

Cleanly Parse a Server Response (清晰解析服务响应)

通常我们只关注服务响应内容里data块的东西,或者只关注在data中的一个特定值。在这个例子中,你可以使用解构来仅获取该值,同时忽略服务器通常发回的许多其他内容。这是一个代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const mockServer = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
'status': 200,
'content-type': 'application/json',
'data': {
dataInfo: 42
}
})
}, 1000);
})
}

mockServer().then(({data: {dataInfo = 100 }}) => {
console.log(dataInfo);
})

此模式允许您在解析参数时从对象中提取值。您还可以自由的设置默认值!

Setting Default Values During Assignment (在赋值的时候给默认值)

分配变量或常量时的常见情况是,如果范围中当前不存在某些其他值,则给他一个默认值。

在没有解构之前, 你可能通过以下代码来实现这个功能:

1
var nightMode = userSettings.nightMode || false;

但这需要为每个赋值写一行代码。通过解构,您可以同时处理所有的赋值及提供默认值。

1
2
3
4
5
6
7
8
const userSettings = { fontSize: 'large', nightMode: true };
const {
nightMode = false,
language = 'en',
fontSize = 'normal'
} = userSettings;

console.log(nightMode, language, fontSize);

解构模式能应用于react组件中的state.

我希望你能够将一些这些模式应用到你的代码中!查看下面的链接,了解有关解构的更多信息。
ES6 In Depth: Destructuring - Mozilla Hacks - the Web developer blog

Learn the basics of destructuring props in React

(完)

原文链接

如何正确判断this的指向.md

Posted on 2020-04-12 | In interview

如何正确判断this指向

首先,我们知道js运行时, 会产生维护一个调用栈。 在js引擎逐行执行代码的时候,会动态的去对这个栈进行维护(进入一个函数,产生一个调用栈, 也就是我们说的产生一个局部作用域, 函数运行完成,退出函数, 这个栈跟着也就弹出, 把控制权回到上一层)。那说this的时候,为什么要扯调用栈呢?

这是因为,this是在运行时绑定的,而不是在编写时绑定的, 它是什么只取决于调用方式, 与函数声明的位置无关.

那问题来了,什么是调用位置?

调用位置指的是代码执行到这个地方的时候, 他的调用上下文是什么?当前环境是什么?让我们来看一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
a();

function a () {
console.log('a');
// 在window下调用
// 当前在a的执行环境

b();
}

function b () {
console.log('b');
// 在a下调用
// 当前在b的执行环境
c();
}

function c () {
// 在b下调用
// 当前在c的执行环境
console.log('c')
}

(这也是一个调用栈的关系, 脑补一下栈的结构, 先进后出。global - a -> b -> c -> c(done) -> b(done) -> a(done) -> global)

在我们理解了调用位置后,获取调用位置。在调用过程中如何绑定this对象呢?

四个规则:

规则1: 默认绑定

该规则优先级最低.

在全局环境下, this会被默认的指向全局对象.(浏览器的话就是window)
看一个例子:

1
2
3
4
var b = 1;
function a () {
console.log(this.b); // 此时, this 指向了window, 那么相当于直接访问全局变量b, 所以该行等价于-》 console.log(b)的效果, 然后这就变成了作用域的问题.....
}

But, 这里有个问题是, 如果你使用了严格模式,this不会默认绑定到全局对象,而是被绑定到undefind

规则2: 隐式绑定

当我的this, 存在上下文环境, 并且该环境下this有指向的时候, 那么this, 指向该上下文.看个例子:

1
2
3
4
5
6
7
8
9
10
var student = {
name: 'Nancy',
getName
}

function getName () {
console.log(this.name)
}

student.getName() // Nancy

规则3: 显式绑定

call/apply/bind绑定

啊, 这里是不是要说这三个函数的不同之处?

  1. call, 绑定第一个参数为this, 后调用, 参数不以数组的形式传入
  2. apply, 绑定第一个参数为this, 后调用,参数以数组的形式传入
  3. bind, 绑定第一个参数为this, 后不调用

这里的第二步就是显式绑定, 显示绑定时最常见的情况,也是我们预料中this的合理行为。这个不多说了。

规则4: new关键字绑定

啊, 这里是不是要说new关键字了。我记得有道面试题,问new做了什么事?

  1. 创建一个object
  2. 创建object到prototype的链接
  3. 显示绑定this到object
  4. 执行函数体
  5. 返回这个object

判定顺序:

1. 你用new关键字了吗?
    ->嗯(绑定到new后返回的对象里)
    ->2
2. 你用显式绑定了吗?
    ->嗯(...)
    ->3
3. 有没有存在上下文绑定的情况?
    ->嗯(...)
    -> 4
4. 只有默认绑定给你选了(注意严格模式下)

PS: ES6要格外注意箭头函数, 箭头函数的this指向,大家可以看一下这个issues

反正我只记住了一句话: 所有的箭头函数都没有自己的this,都指向外层。然后放一个例子:

1
2
3
4
5
var obj = {
fn: x => console.log(this)
}

obj.fn() // 浏览器环境下: window

Here are some practical JavaScript objects that have encapsulation.md

Posted on 2020-04-12 | In translation

Here are some practical JavaScript objects that have encapsulation (翻译)

(渣渣翻译,如果看翻译不开心可以看->原文)

封装意味着隐藏信息.意思是尽可能的隐藏对象内部的部分, 同时暴露出最小公共接口。

最简单和最优雅的方式创建一个封装是使用闭包.可以将闭包创建为具有私有状态的函数.当创建了许多的闭包共享相同的私有状态, 我们就会创建一个Object.

我将开始创建一些在我们开发应用中比较实用的Object: Stack, Queue, Event Emitter, and Timer.以上Object的都使用工厂函数创建。

让我们开始吧。

Stack

栈,是一种数据结构, 它有两个主要操作: push, 往这个集合里增加一个元素; pop, 移除最近添加的元素.其中,push和pop元素依据先进后出原则。

让我们看下一个例子:

1
2
3
4
5
6
let stack = Stack();
stack.push(1);
stack.push(2);
stack.push(3);
stack.pop(); //3
stack.pop(); //2

让我们使用工厂函数实现一个stack:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Stack(){  
let list = [];

function push(value){
list.push(value);

}
function pop(){
return list.pop();
}

return Object.freeze({
push,
pop
});
}

这个栈对象有两个公共方法push()和pop().内部的状态只能通过这些方法去改变.

1
stack.list; // undefined

我不能直接修改内部状态:

1
stack.list = 0; //Cannot add property list, object is not extensible

使用class实现Stack结构

如果我使用类完成相同的实现,则没有实现封装

1
2
3
4
5
let stack = new Stack();
stack.push(1);
stack.push(2);
stack.list = 0; //corrupt the private state
console.log(stack.pop()); //this.list.pop is not a function

这是我使用class实现的stack

1
2
3
4
5
6
7
8
9
10
11
12
13
class Stack {  
constructor(){
this.list = [];
}

push(value) {
this.list.push(value);
}

pop() {
return this.list.pop();
}
}

如果要看更深的对比(class和工厂函数), 可以看一下这篇class vs Factory function: exploring the way forward

Queue

队列是一种数据结构, 有两个主要操作:入队和出队。

入队: 往我们的集合里增加元素。

出队: 移除在集合里最早加入的元素。

出队和入队操作遵循, 先进先出的原则。

这是使用队列的一个例子

1
2
3
4
5
6
let queue = Queue();
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
queue.dequeue(); //1
queue.dequeue(); //2

以下是队列的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Queue(){  
let list = [];
function enqueue(value){
list.push(value);
}
function dequeue(){
return list.shift();
}
return Object.freeze({
enqueue,
dequeue
});
}

就像我们之前看到的, 这个对象的内部不能从外部直接访问。

1
queue.list; //undefined

Event emitter

一个发布订阅机制是一个有发布和订阅的API的一个对象.它常用于一个应用里两个不同的部分的通信.

来看一个使用的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 初始化事件对象
let eventEmitter = EventEmitter();
// 订阅事件
eventEmitter.subscribe("update", doSomething);
eventEmitter.subscribe("update", doSomethingElse);
eventEmitter.subscribe("add", doSomethingOnAdd);

// 发布(触发之前的订阅)
eventEmitter.publish("update", {});

function doSomething(value) { };
function doSomethingElse(value) { };
function doSomethingOnAdd(value) { };

首先, 我为update事件订阅了两个函数,并为add事件添加了一个函数.当事件触发发布“updata”事件的时候, doSomething和doSomethingElse都会被调用.

这是事件对象的一个简单实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function EventEmitter(){  
let subscribers = [];
function subscribe(type, callback){
subscribers[type] = subscribers[type] || [];
subscribers[type].push(callback);
}
function notify(value, fn){
try {
fn(value);
} catch(e) {
console.error(e);
}
}
function publish(type, value){
if(subscribers[type]){
let notifySubscriber = notify.bind(null, value);
subscribers[type].forEach(notifySubscriber);
}
}

return Object.freeze({
subscribe,
publish
});
}

订阅者的状态和通知方法是私有的。

Timer

众所周知, js中有两个计时器函数:setTimeout 和 setInterval.我想以面向对象的方式与计时器一起工作,最终将以如下的方式调用:

1
2
3
let timer = Timer(doSomething, 6000);
timer.start();
function doSomething(){}

但是setInterval函数有一些限制,在进行新的回调之前,它不会等待之前的回调执行完成。即使前一个尚未完成,也会进行新的回调。更糟糕的是,在AJAX调用的情况下,响应回调可能会出现故障。

递归setTimeout模式可以解决这个问题.使用这个模式, 一个新的回调形成只能等前一个回到完成之后。

让我们创建一个TimerObject:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function Timer(fn, interval){
let timerId;
function startRecursiveTimer(){
fn().then(function makeNewCall(){
timerId = setTimeout(startRecursiveTimer, interval);
});
}

function stop(){
if(timerId){
clearTimeout(timerId);
timerId = 0;
}
}

function start(){
if(!timerId){
startRecursiveTimer();
}
}

return Object.freeze({ start, stop});
}

let timer = Timer(getTodos, 2000);timer.start();

只有start和stop方法是公共,除此之外, 所有的方法和变量都是私有的.调用setTimeout(startRecursiveTimer,interval)时,没有this(指向问题)丢失的上下文问题,因为工厂函数不使用this。

计时器使用返回promise的回调。

现在, 我们可以很简单的实现,当浏览器的tab隐藏的时候, 计时器停止, 当浏览器的tab显示的时候, 计时器继续计时.

1
2
3
4
5
6
7
8
9
10
11
document.addEventListener("visibilitychange", toggleTimer);

function toggleTimer(){
if(document.visibilityState === "hidden"){
timer.stop();
}

if(document.visibilityState === "visible"){
timer.start();
}
}

#总结
JavaScript提供了一种使用工厂函数创建封装对象的独特方法。对象封装状态。

Stack和Queue可以创建为基本数组功能的包装器。

事件对象是在应用程序中的不同部分之间进行通信的对象。

计时器对象易于使用。它有一个清晰的接口start()和stop()。您不必处理管理计时器标识(timerId)的内部部分。

您可以在我的Discover Functional JavaScript一书中找到有关JavaScript中的功能和面向对象编程的更多内容。

How to deal with nested callbacks and avoid “callback hell”.md

Posted on 2020-04-12 | In translation

How to deal with nested callbacks and avoid “callback hell” (翻译)

(渣渣小怪兽翻译,如果看翻译不开心可以看->原文)

js是一门强大语言。有时候,你必须处理另一个回调中的回调,而这个回调这是另一个回调中的回调。

人们形象的描述这种模式为回调地狱。

它有点像这样:

1
2
3
4
5
6
7
firstFunction(args, function() {
secondFunction(args, function() {
thirdFunction(args, function() {
// And so on…
});
});
});

这是js中的回调。当你看到这样的嵌套回调, 它可能令你的大脑难以置信,但我不认为这是“地狱”.这个“地狱”可以更好的管理,如果你知道怎么处理它的话。

关于回调

我假设当你在阅读这篇文章的时候你知道回调的概念.如果你不知道, 请阅读这篇文章, 在我们继续往下走之前这篇文章会介绍什么是回调。在那里,我们讨论回调是什么以及为什么在JavaScript中使用它们。

回调的处理方案

这是四个处理回调地狱的方案

  1. 写注释
  2. 拆分函数成为更小的函数
  3. 使用Promise
  4. 使用Async/await

在我们拆分讲解这个解决方案之前, 让我们一起构造一个回调地狱. 为什么?因为它真的太抽象了,当我们看到上面的例子: firstFunction, secondFunciton 和 thirdFunction.我们让构造一个真是的回调, 让我们的例子更具体。

构造一个回调地狱

让我们想象, 现在我们要做一个汉堡.为了制作一个汉堡, 我们需要执行以下步骤来达到目的:

  1. 获取配料
  2. 烹饪牛肉
  3. 获取汉堡的面包
  4. 把煮好的牛肉放在面包之间
  5. 供应汉堡

如果这些步骤都是同步的, 你将看到一个函数像下面这样:

1
2
3
4
5
6
7
8
9
const makeBurger = () => {
const beef = getBeef();
const patty = cookBeef(beef);
const buns = getBuns();
const burger = putBeefBetweenBuns(buns, beef);
return burger;
};
const burger = makeBurger();
serve(burger);

然而, 在我们的步骤中,我们不能自己制作汉堡。我们必须指导助手制作汉堡的步骤。在我们指示助手之后,我们必须等待助手完成步骤,然后才开始下一步。

如果我们想在js中等待一会再执行, 我们就需要使用回调。为了制作汉堡, 我们不得不先获取牛肉。我们只能在获取牛肉后再煮牛肉。

1
2
3
4
5
const makeBurger = () => {
getBeef(function(beef) {
// We can only cook beef after we get it.
});
};

为了煮牛肉, 我们需要传递牛肉进入cookBeef函数内.否则我们将没有东西煮。因此我们不得不等待牛肉煮熟.

一旦牛肉煮好了,我们就要获取面包

1
2
3
4
5
6
7
8
9
const makeBurger = () => {
getBeef(function(beef) {
cookBeef(beef, function(cookedBeef) {
getBuns(function(buns) {
// Put patty in bun
});
});
});
};

在我们获取面包后,我们需要放肉饼在两个面包之间。这就是汉堡形成的地方.

1
2
3
4
5
6
7
8
9
10
11
const makeBurger = () => {
getBeef(function(beef) {
cookBeef(beef, function(cookedBeef) {
getBuns(function(buns) {
putBeefBetweenBuns(buns, beef, function(burger) {
// Serve the burger
});
});
});
});
};

最终, 我们可以提供汉堡了。但是我们不能从makeBurger中返回burger, 因为这是异步的。我们需要用一个回调去接收汉堡.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const makeBurger = nextStep => {
getBeef(function (beef) {
cookBeef(beef, function (cookedBeef) {
getBuns(function (buns) {
putBeefBetweenBuns(buns, beef, function(burger) {
nextStep(burger)
})
})
})
})
}
// Make and serve the burger
makeBurger(function (burger) => {
serve(burger)
})

制作这个回调的例子很有趣啊

解决方案1: 写注释

这个makeBurger回调是简单易于理解。我们可以读懂。它只是有一些不好看。

如果你第一次读到makeBurger这个函数, 你可能在想“为什么我们需要这么多回调才能制作汉堡呢?这没有意义!”

在这种情况下,您需要留下注释来解释您的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Makes a burger
// makeBurger contains four steps:
// 1. Get beef
// 2. Cook the beef
// 3. Get buns for the burger
// 4. Put the cooked beef between the buns
// 5. Serve the burger (from the callback)
// We use callbacks here because each step is asynchronous.
// We have to wait for the helper to complete the one step
// before we can start the next step
const makeBurger = nextStep => {
getBeef(function(beef) {
cookBeef(beef, function(cookedBeef) {
getBuns(function(buns) {
putBeefBetweenBuns(buns, beef, function(burger) {
nextStep(burger);
});
});
});
});
};

现在,不会是在想“WTF”, 当你看到这个回调地狱, 你可以理解为什么要用这个方式去写它了。

解决方案2:将回调拆分为不同的函数

我们的回调地狱的例子已经是一个很棒拆分的例子。让我给你一步步的拆分代码, 就会明白为什么我这样说了。

拿getBeef, 我们的第一个回调来说, 我们为了拿到牛肉不得不先去冰箱。厨房里有两个冰箱,我们需要去右手边的冰箱拿牛肉。

1
2
3
4
5
const getBeef = nextStep => {
const fridge = leftFright;
const beef = getBeefFromFridge(fridge);
nextStep(beef);
};

为了烹饪牛肉,我们需要把牛肉放进烤箱里, 把烤箱开到200度, 并且等20分钟

1
2
3
4
5
6
const cookBeef = (beef, nextStep) => {
const workInProgress = putBeefinOven(beef);
setTimeout(function() {
nextStep(workInProgress);
}, 1000 * 60 * 20);
};

现在想象一下, 如果你不得不写每个步骤在makeBurger ,那么这个函数就会非常的庞大。

有关将回调拆分为较小函数的具体示例,您可以在我的回调文章中阅读这一小部分。

解决方案3: 使用Promise

我猜你应该知道什么是Promise.如果你不知道, 请先阅读这一篇文章

Promise能让回调地狱更易于管理.而不是像上面的嵌套回调.你将看到像这样:

1
2
3
4
5
6
7
8
const makeBurger = () => {
return getBeef()
.then(beef => cookBeef(beef))
.then(cookedBeef => getBuns(beef))
.then(bunsAndBeef => putBeefBetweenBuns(bunsAndBeef));
};
// Make and serve burger
makeBurger().then(burger => serve(burger));

如果你更倾向于单参数风格的promise, 你能把上面的例子转换成这样

1
2
3
4
5
6
7
8
const makeBurger = () => {
return getBeef()
.then(cookBeef)
.then(getBuns)
.then(putBeefBetweenBuns);
};
// Make and serve burger
makeBurger().then(serve);

更易读和管理。

但是问题是, 如何把回调地狱转换成Promise?

把回调转成Promise

为了转成Promise, 我们需要为每个回调先new一个Promise.当这个回调成功执行,那么给这个Promise使用resolve返回。或当这个回调失败或者抛出错误的时候,我们就要使用reject。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const getBeefPromise = _ => {
const fridge = leftFright;
const beef = getBeefFromFridge(fridge);
return new Promise((resolve, reject) => {
if (beef) {
resolve(beef);
} else {
reject(new Error(“No more beef!”));
}
});
};
const cookBeefPromise = beef => {
const workInProgress = putBeefinOven(beef);
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve(workInProgress);
}, 1000 * 60 * 20);
});
};

在我们的练习中,可能已经为您编写了回调函数。如果使用Node,则包含回调的每个函数将具有相同的语法:

  1. 回调函数将是最后一个参数
  2. 这个回调函数将一直有两个参数。这些参数都有相同的顺序(error是第一个, 随后是你感兴趣的任何东西)
1
2
3
4
5
6
7
8
9
10
11
12
// The function that’s defined for you
const functionName = (arg1, arg2, callback) => {
// Do stuff here
callback(err, stuff);
};
// How you use the function
functionName(arg1, arg2, (err, stuff) => {
if (err) {
console.error(err);
}
// Do stuff
});

如果你的回调已经有了相同的语法, 你可以使用像ES6 Promisify or Denodeify 把回调转换成Promise.如果你使用Node v8.0或者之上, 你可以使用util.promisify.

他们三个都可以使用。你可以选择任意一个库来使用。不过,每种方法之间都有细微的差别。我建议你查看它的文档,了解方法。

解决方案4: 使用异步函数(async/await)

为了使用异步函数, 你首先需要知道两件事。

  1. 如何把callback转换为Promise(就是我们解决方案三中说的内容)
  2. 怎么使用异步函数(如果你需要帮助,你可以读一下这篇文章)

使用异步函数,您可以编写makeBurger,就像写同步一样!

1
2
3
4
5
6
7
8
9
const makeBurger = async () => {
const beef = await getBeef();
const cookedBeef = await cookBeef(beef);
const buns = await getBuns();
const burger = await putBeefBetweenBuns(cookedBeef, buns);
return burger;
};
// Make and serve burger
makeBurger().then(serve);

我们可以在这里对makeBurger进行一项改进。 你可以同时获得两个getBuns和getBeef的助手。 这意味着您可以使用Promise.all语法, 然后使用await。

1
2
3
4
5
6
7
8
const makeBurger = async () => {
const [beef, buns] = await Promise.all(getBeef, getBuns);
const cookedBeef = await cookBeef(beef);
const burger = await putBeefBetweenBuns(cookedBeef, buns);
return burger;
};
// Make and serve burger
makeBurger().then(serve);

(注意: 你可以在Promise做到相同的效果, 但是它的语法并不是很棒, 不像async/await那么清晰。)

总结

回调地狱并不是像你想象中的那么可怕.这里有四种方式管理回调地狱。

  1. 写注释
  2. 切分函数为更小的函数
  3. 使用Promise
  4. 使用async/await

Understanding Prototypes in JavaScript.md

Posted on 2020-04-12 | In translation

Understanding Prototypes in JavaScript

Prototype是一个怪异并且我们极力避免的一个话题,但是对于顶级的js开发者来说尤为重要.虽然类更容易理解,并且在学习面向对象编程时通常是许多人的起点,但原型是构建JavaScript的基础.

它看起来像类, 闻起来也像, 但是它不是类。因此,忘记你知道的关于class和面向对象语言的概念, 如果像真正的理解prototype的概念。

不, 我是个骗子。来, 让我们快速的回顾一下传统的类以及面向对象编程是如何工作的。

Classes-the master blueprint

我的想法是,所有东西都是某种对象,所有你需要做的就是为那个对象创建“蓝图”,并在每次需要它的唯一实例时实例化新对象。

类往往是举例。然而, 一个类是仅仅是一个对象创建的模式。其他语言也存在原型,但是在js中使用的原型和java等语言的原型不同。这种语言只是在名称上类似, 但是本质上是完全不同的语言。

类(不是特定于语言的)通常具有构造函数,其中的方法被调用以执行某种操作。它们是以这种方式构造的,因为它为对象提供了清晰的界限和环形围栏 - 这个想法是所有对象都是’事物’而所有’事物’都具有属性和功能。

然而,现实并不总是那么明确,定义对象往往很困难。你能共享一个方法, 创建一个超类或者子类但这是另一个不同的故事了。有时, 但你创建一个class, 它能复制和别的class类似的特性, 但并不是相同的class.

Prototype-Do you have a pen?

别误会我的意思, class是一套很好的编程范式。然而js建立于Prototype之上。

一个prototype像一个结构的class, 它被加总在一起通过引用的方式。如果一个class像一个蓝图, 那么prototype就像是在问你-do you have a pen? 如果答案是不。那么你会去问你朋友这个问题。如果他的回答也是不, 那么你的朋友回去问你朋友的朋友。这个过程会持续到没朋友的那个人。

当你在js创造了一个函数, js引擎会自动创建一个空的容器并且命名为prototype, 为了挂载你的方法。

因此, 如果你写了一个函数funciton x() {}, 并且把它打印出来你可能会看到下面的内容:

这意味着, 你能增加一些东西到prototype, 通过一些像这样的方式:

1
2
3
4
function x () {};
x.prototype = {
sayHello: 'Hello'
};

这些将会展示在prototype下并且让我们调用,当我们通过这个函数实例化一个对象的时候。

原型模式的工作原理是函数/方法只编写一次,并且原型对象可以通过原型链引用和使用。

有些困惑是吗?来看个图表。

连接函数时,原型中设置的方法从左到右可用。x和y中的任何内容都可用于z,但不是相反。希望之前的那笔比喻现在更有意义了。

New Initialization Pattern

这有四种方式初始化并且创建一个prototype链,但是在这篇文章里, 我做一个例子就是新初始化模式。我发现这个模式更简单对于演示原型继承以及原型链,特别是那些仍在努力吸收和理解这个想法的人。

让我们开始,从一个空的car函数。你可以在里面有东西但是为了这个演示的目的,我们将把它留空。

1
2
3
function Car() {

}

我们现在往Car的prototype里增加以下属性.

1
2
3
4
Car.prototype = {
washCar: 'wash',
doors: true
}

现在,当我们用console.dir打印出Car的时候, 我们可以看到这两个属性出现在prototype里。

让我们创建另一个函数以便可以链接他们并且获得继承的效果。

1
2
3
function Honda () {

}

我们有Honda函数.现在我们去创建一个引用并且链接到Car, 通过实例化这个Car函数(使用new关键字)

1
let HondaProto = new Car;

你也可以设置你自己的prototype方法在HondaProto上, 像这样

1
2
3
HondaProto.limit = function() {
console.log('limit');
}

为了链接你的Honda函数与我们定制的prototype蓝图, 只需要把Honda.prototype设置为HondaProto

1
Honda.prototype = HondaProto;

OK, 现在, 如果你使用console.dir(Honda), 你将可以看见这个函数Honda()现在已经有了limit函数, 同时,当你访问prototype的时候你可以看到Car的两个属性washCar/door在proto下.

让我们增加更多的函数在这条继承链上, 因此你可以看到这条链在console.log打印的内容里。

1
2
3
4
5
6
7
8
9
10
11
function Electric () {

}

let ElectricProto = new Honda;

ElectricProto.electric = function () {
console.log('electric');
}

Electric.prototype = ElectricProto;

相同的方式实例化Electric对象, 得到的console信息如下。这将增加一个新的proto在原有的proto下方嵌套。

现在, 如果你使用Electric初始化一个新对象并且调用wash方法, 该对象将可以访问所有Car对象的方法和内部设置。子级域链也会继承任何父级链进一步发生变化的变化。

1
2
let LX4Honda = new Electric();
console.log(LX4Honda.washCar);

console.log将打印出 “wash”.

尽管LX4没有叫washCar的方法, 它会沿着原型链往下直到找到这个方法.如果没有匹配, 将返回undefined.

您还可以将一个原型函数附加到另一个链,从而共享该函数。 创建这种关系的方法与上面说明的完全相同。 从逻辑上讲,如果我们继续使用到目前为止的示例,它应该看起来像这样。

Final words

原型以及原型链的继承最有效的地方是,你没必要创建一个全新的桶来容纳里面的所有方法。相反,你只需要创建一个指向这个函数的引用仅当你需要的时候去调用就好。对于类,这些方法坐在桶中等待被调用。

希望上面这个例子对你有些意义, 并且可以帮助你更好的理解原型。这还有一些其他的方式初始化并且创建一个原型继承链, 但是要控制好链的长度,防止他们混淆你。我已经从这里省略了它们,并向您展示了如何使用新的初始化模式创建继承链。但是,你应该去看看Object.create,Object.setPrototypeOf和proto。

这三个列出的方法同样有价值,是创建原型继承链的绝佳选择。

让我们保持联系,加入我每周很棒的网页摘要通讯列表。

谢谢你的阅读

(完)

获取能力的三大要素_百分之二十-知识与问题-系统化训练.md

Posted on 2020-04-12 | In 圈外个人发展课程

如何高效的获取能力

关键词: 20%|知识与问题|系统化训练

20%

小扎说过, 掌握百分之20的技术就能解决百分之80的工程问题。

28法则果然在哪里都受欢迎。
核心观点提到, 获取一种能力最快的方法是掌握其中核心的20%,而最有效的方式就是找对师傅.这件事对我来说很重要,所以至今我都在遵循这个原则,特别是找师傅.

  1. 在大学的时候,我大学室友和我脑子一热, 大家一起买了吉他,随书附赠的还有30天吉他弹唱精通,于是我们开始练习。我总是发现哪里不对,因为吉他的教材会从乐理开始跟你讲, 各种手法和理论知识,说一定要记住,很重要, 于是我们练不下去了。放弃。吉他吃灰。
    工作后,突然觉得不对劲,大家都可以学会的东西,凭啥我就不可以, 我又买了一把。然后这次,我直接找到了我的好朋友朋友,他是当年学校的乐队吉他手。我一不懂就问他,他也很乐意教。慢慢的发现, 自己好像上道了。这20%的师傅真的不亏。

  2. 工作后,我对于跟我一起入职的同学们来说, 我技术进步得很快。我明白我做了什么。我们大家一开始进来的时候,都是自己学,没有方向,触摸不到百分之二十。于是我找了技术最强的大佬。我就跟着他,帮他干活,同时也问他问题,后来自然而然的,就掌握要领了。那这百分之二十就是师傅领进门的含义?

知识与问题

我学习一门新的语言或者框架的时候,一开始大家都是看官方文档, 从头看到尾。真的很累,我甚至会觉得痛苦,感觉看
不完。因为技术文档都是对这语言或框架的全部信息,我就算背下来,还是很容易忘记。后来我发现,对于语言或者一门新的
框架,要了解的仅仅是其中我们最常用的语法,然后就开始去遇到问题->查文档->解决问题->遇到问题。一句话,文档是用来查的, 而不是背的。

系统化训练

其实工程人员的工作内容都是高度重复的。俗称企业螺丝钉。但是其实,反过来想,对于一个工程的运作和理解真的很重要, 无论是你的开发能力和架构能力, 以及团队协调甚至是应变能力。都是一个系统中缺一不可的一环。把工程当作一个系统,然后去理解它,那么你获得的就不止是开发能力。其实这也是系统化思维的内容。

偏题了,对于系统化训练,飞行员的检查清单,应该算是系统化的典型?飞行员都会有自己的检查清单,并且在模拟训练的时候,他们会遇到各种各样的问题,第一反应就会开始去查检查清单,然后行动,并且在工作一定的时间后,飞行员还要回去学校重新模拟训练。

如果他们只单独的学习开飞机,那么应该不能算是拥有一个飞行员恩多能力。

如果你需要某种能力, 为什么不去找一个好老师,然后把你的知识和问题结合,形成组块,最后再通过系统化的训练把组块联通起来获得这种能力呢?

PS:尝试根据知识去问问题.

面试手册.md

Posted on 2020-04-12 | In 算法数据结构

// 基于发布订阅模式写一个事件事件发布订阅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Event {
constructor() {
this.taskList = {}
}

on(eventType, fn) {
// 触发时, 明白同一个事件可能有多个任务的可能
// 所以对于task的处理我们都用数组的格式来处理
let fns = ( this.taskList[eventType] = this.taskList[eventType] || [] )
if(!fns.include(fn) {
fns.push(fn)
}
}

emiter(eventType, data) {
let fns = this.taskList[eventType]
if(Array.isArray(fns)) {
fns.map(item => {
item(data)
})
}
return this
}

off(eventType, fn) {
let fns = this.taskList[type]
if(Array.isArray(fns)) {
if(fn) {
let index = fns.indexOf(callback)
if(index !== -1) {
fns.split(index, 1)
}
}else {
fns.length = 0
}
}
return this
}
}

实现一手深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const isType = (value) => {
return Object.prototype.toString.call(value).match(/(?<= )\w+/)[0]
// String Number Null Undefined Object Date RegExp Symbol Boolean Function Array
}
const deepClone = obj => {
let objType = isType(obj)
let result = {}
for(let i in obj) {
if(objType === 'Object' || objType === 'Array') {
result[i] = deepClone(obj[i])
}
result[i] = obj[i]
}
return result
}
a = {
a: 1,
b: {
a: 1
}
}
b = deepClone(a)

千分位

1
2
3
4
5
6
7
8
9
10
11
12
13
function exchange(num) {
num = String(num)
if(num <= 3) {
return num
}

num = num.replace(/\d{1,3}(?=(\d{3})+$)/g, (v) => {
return v + ','
})
return num
}

exchange(1230597)

模拟new关键字

1
2
3
4
5
6
7
8
9
10
function likeNew (fn, ...args) {
let o = Object.create(fn.prototype)
const result = fn.apply(o, args)
return result || o
}

function Person(name) {
this.name = name
}
likeNew(Person, 'weihaidong')

// 闭包setTimeout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1 setTimeout 外闭包
for(var i = 0; i < 4; i++) {
((i) => {
setTimeout(() => {
console.log(i)
}, 1000 * i)
})(i)
}
// 2 setTimeout 内闭包
for(var i = 0; i < 4; i++) {
setTimeout(((i) => {
return () => {
console.log(i)
}
})(i), 1000 * i)
}

isType函数

1
2
3
4
5
const isType = v => {
const reg = /(\.+\s+)/
return Object.prototype.toString.call(v).replace(/(\[\w+\s+)(\w+)(\])/, '$2')
}
isType(1)

古典继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Person(name) {
this.name = name
}
Person.prototype.getName = function() {
return this.name
}

function Women(name, sex) {
// 链接Person属性
Person.call(this, name)
// 初始化自身属性
this.sex = sex
}

// 链接原型链
Women.prototype = Person.prototype
// 修复constructor指向
Women.prototype.constructor = Women

快排

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const quickSort = arr => {
if(arr.length <= 1) return arr
let [min, flag, max] = arr.reduce((sum, cur, index) => {
if(index === 0) {
sum[1].push(cur)
} else {
if(cur >= sum[1]) sum[2].push(cur)
if(cur < sum[1]) sum[0].push(cur)
}
return sum
}, [[], [], []])
console.log(min, flag, max)
min = quickSort(min)
max = quickSort(max)
return [...min, ...flag, ...max]
}

quickSort([2, 1, 3, 5, 6, 1, 6, 7, 8])

二分法查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const binarySearch = (arr, x) => {
console.log(arr)
if(arr.length <= 1) {
return arr[0] === x ? x : false
}
const flag = Math.floor(arr.length / 2)
let result
if(x === arr[flag]) return result = x
if(x > flag) result = binarySearch(arr.slice(flag), x)
if(x < flag) result = binarySearch(arr.slice(0, flag), x)
return result
}
binarySearch([1, 2, 3, 4, 5, 6], 6)
binarySearch([1, 2, 3, 4, 5, 6], 7)

发布订阅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Event {
constructor() {
this.cache = {}
}

addEvent(type, fn) {
if(this.cache[type]) {
this.cache[type].push(fn)
} else {
this.cache[type] = [fn]
}
}

emmitEvent(type, params) {
const taskList = this.cache[type]
if(taskList) {
taskList.map(item => {
item(params)
})
}
}

deleteEvent(type) {
this.cache[type] = []
}
}

const event = new Event()
event.addEvent('fly', (value) => {
console.log('i will fly', value)
})
setTimeout(() => {
event.emmitEvent('fly', 'go')
}, 2000)

简易版Promise.md

Posted on 2020-04-12 | In interview

手写简易版Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// 常量存Promise的状态
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
function MyPromise(fn) {
const that = this
// promise初始状态为pending
that.state = PENDING
// value为resolve时, 传递的参数
that.value = null
// resolve的回调队列
that.resolvedCallbacks = []
// reject回调队列
that.rejectedCallbacks = []

// resole函数实现
function resolve(value) {
// 只有状态为panding才能修改状态
if(that.state === PENDING) {
// 状态改为resolved
that.state = RESOLVED
// 把值传入value
that.value = value
// 执行resolve回调队列
that.resolvedCallbacks.map(cb => cb(that.value))
}
}

function reject(value) {
// 同上
if(that.state === PENDING) {
that.state = REJECTED
that.value = value
that.rejectedCallbacks.map(cb => cb(that.value))
}
}

try{
fn(resolve, reject)
} catch (e) {
reject(e)
}
}

MyPromise.prototype.then = function(onFulfilled, onRejected) {
const that = this
// 判断传入的是否为函数, 如果非函数包装一层往下执行透传
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : r => {
throw r
}

// 如果状态为panding, 那么把then中的参数(resolve, reject)传入队列
if(that.state === PENDING) {
that.resolvedCallbacks.push(onFulfilled)
that.rejectedCallbacks.push(onRejected)
}

// 如果状态为resolved那么执行onFulfilled函数
if(that.state === RESOLVED) {
onFulfilled(that.value)
}

// 如果状态为reject那么执行onrejeted函数
if(that.state === REJECTED) {
onRejected(that.value)
}
}

new MyPromise((res, rej) => {
setTimeout(() => {
res(1)
}, 0)
}).then(value => {
console.log(value)
})

so, 你觉得一个Promise的结构是怎么样?

  1. 我需要一个对象来保存该promise的状态, 执行的回调队列, 每次promise的结果(promise链)
  2. 我需要rejected/resolved函数, 触发对应状态的回调队列
  3. 我需要then函数解决Promise链问题

使用Class机制重构,重构不了, 如果使用了setTimeout在resolve中取不到this, 走不下去头痛

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
class MyPromise {
constructor(fn) {
const that = this
this.state = PENDING
this.value = null
this.resolvedCallbacks = []
this.rejectedCallbacks = []
try{
fn(this.resolve, this.reject)
} catch (e) {
this.reject(e)
}
}
// resole函数实现
resolve(value) {
// 只有状态为panding才能修改状态
if(this.state === PENDING) {
// 状态改为resolved
this.state = RESOLVED
// 把值传入value
this.value = value
// 执行resolve回调队列
this.resolvedCallbacks.map(cb => cb(this.value))
}
}

reject(value) {
// 同上
if(this.state === PENDING) {
this.state = REJECTED
this.value = value
this.rejectedCallbacks.map(cb => cb(this.value))
}
}

then(onFulfilled, onRejected) {
// 判断传入的是否为函数, 如果非函数包装一层往下执行透传
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : r => {
throw r
}

// 如果状态为panding, 那么把then中的参数(resolve, reject)传入队列
if(this.state === PENDING) {
this.resolvedCallbacks.push(onFulfilled)
this.rejectedCallbacks.push(onRejected)
}

// 如果状态为resolved那么执行onFulfilled函数
if(this.state === RESOLVED) {
onFulfilled(this.value)
}

// 如果状态为reject那么执行onrejeted函数
if(this.state === REJECTED) {
onRejected(this.value)
}
}
}

new MyPromise((res, rej) => {
setTimeout(() => {
res(1)
}, 0)
}).then(value => {
console.log(value)
})

vue-cli.md

Posted on 2020-04-12 | In 构建工具

控制环境变量

  1. 新建.env文件
  2. 在代码中使用process.env.NODE_ENV[变量名]访问
  3. .env权重比: .env.[mode].local > .env.[mode] > .env.local > .env
<1…4567>

Hawei

Fullstack Engineer

61 posts
16 categories
3 tags
RSS
© 2022 Hawei
Powered by Hexo
|
Theme — NexT.Muse v5.1.4