MockingBird

记录 Web 前端成长旅程


  • 首页

  • 分类

  • 归档

  • 标签

javascript 定时器工作原理

发表于 2015-09-18   |   分类于 javascript   |  

说到 javascript 中的定时器,我们肯定会想到 setTimeout() 和 setInterval() 这两个函数。本文将从 事件循环(Event Loop) 的角度来分析两者的工作原理和区别。

setTimeout()

MDN对 setTimeout 的定义为:

在指定的延迟时间之后调用一个函数或执行一个代码片段。

语法

setTimeout 的语法非常简单,第一个参数为回调函数,第二个参数为延时的时间。函数返回一个数值类型的ID唯一标示符,此ID可以用作 clearTimeout 的参数来取消定时器:

1
var timeoutID = window.setTimeout(code, delay);

IE0+ 还支持回调参数的传入:

1
var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);

setInterval()

MDN 对 setInterval 的定义为:

周期性地调用一个函数(function)或者执行一段代码。

由于 setInterval 和 setTimeout 的用法一样,所以这里不再列出。

对第二个参数(delay)的说明

由于javascript 的事件循环机制,导致第二个参数并不代表延迟delay毫秒之后立即执行回调函数,而是尝试将回调函数加入到事件队列。实际上,setTimeout 和 setInterval 在这一点上处理又存在区别:

  • setTimeout:延时delay毫秒之后,啥也不管,直接将回调函数加入事件队列。
  • setInterval: 延时delay毫秒之后,先看看事件队列中是否存在还没有执行的回调函数(setInterval的回调函数),如果存在,就不要再往事件队列里加入回调函数了。

所以,当我们的代码中存在耗时的任务时,定时器并不会表现的如我们所想的那样。

通过一个例子来理解

下面的代码,本来希望能够在 100ms 和 200ms 的时候(也就是刚好等待 100ms)调用回调函数:

1
2
3
4
5
6
7
8
9
10
11
12
var timerStart1 = now();
setTimeout(function () {
console.log('第一个setTimeout回调执行等待时间:', now() - timerStart1);

var timerStart2 = now();
setTimeout(function () {
console.log('第二个setTimeout回调执行等待时间:', now() - timerStart2);
}, 100);
}, 100);
// 输出:
// 第一个setTimeout回调执行等待时间: 106
// 第二个setTimeout回调执行等待时间: 107

这样的结果看上去正是我们所想的那样,但是一旦我们在代码中加入了耗时的任务时候,结果就不像我们所期望的那样了:

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
var timerStart1 = now();
setTimeout(function () {
console.log('第一个setTimeout回调执行等待时间:', now() - timerStart1);

var timerStart2 = now();
setTimeout(function () {
console.log('第二个setTimeout回调执行等待时间:', now() - timerStart2);
}, 100);

heavyTask(); // 耗时任务
}, 100);

var loopStart = now();
heavyTask(); // 耗时任务
console.log('heavyTask耗费时间:', now() - loopStart);

function heavyTask() {
var s = now();
while(now() - s < 1000) {
}
}

function now () {
return new Date();
}
// 输出:
// heavyTask耗费时间: 1015
// 第一个setTimeout回调执行等待时间: 1018
// 第二个setTimeout回调执行等待时间: 1000

两个 setTimeout 的等待事件由于耗时任务的存在不再是 100ms 了!我们来描述一下事情的经过:

  1. 首先,第一个耗时任务(heavyTask())开始执行,它需要大约 1000ms 才能执行完毕。
  2. 从耗时任务开始执行,过了 100ms, 第一个 setTimeout 的回调函数期望执行,于是被加入到事件队列,但是此时前面的耗时任务还没执行完,所以它只能在队列中等待,直到耗时任务执行完毕它才开始执行,所以结果中我们开的看到的是: 第一个setTimeout回调执行等待时间: 1018。
  3. 第一个 setTimeout 回调一执行,又开启了第二个 setTimeout, 这个定时器也是期望延时 100ms 之后能够执行它的回调函数。 但是,在第一个 setTimeout 又存在一个耗时任务,所有它的剧情跟第一个定时器一样,也等待了 1000ms 才开始执行。

可以用下面的图来概括:
setTimeout

再来看 setInterval 的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var intervalStart = now();
setInterval(function () {
console.log('interval距定义定时器的时间:', now() - loopStart);
}, 100);

var loopStart = now();
heavyTask();
console.log('heavyTask耗费时间:', now() - loopStart);

function heavyTask() {
var s = now();
while(now() - s < 1000) {
}
}

function now () {
return new Date();
}
// 输出:
// heavyTask耗费时间: 1013
// interval距定义定时器的时间: 1016
// interval距定义定时器的时间: 1123
// interval距定义定时器的时间: 1224

上面这段代码,我们期望每隔 100ms 就打出一条日志。相对于 setTimeout 的区别, setInterval 在准备把回调函数加入到事件队列的时候,会判断队列中是否还有未执行的回调,如果有的话,它就不会再往队列中添加回调函数。 不然,会出现多个回调同时执行的情况。

可以用下面的图来概括:
setInterval

总结

上面对javascript定时器执行原理进行了简要的分析,希望能够帮助我们更深入的理解javascript。文中有描述不当的地方可以在评论中指出。

文章地址:http://blog.mcbird.cn/2015/09/18/javascript-timers/

[译]在 React.js 中使用 ES6+

发表于 2015-09-11   |   分类于 javascript   |  

原文地址: http://babeljs.io/blog/2015/06/07/react-on-es6-plus/

babel.js

在今年对 Instagram Web 进行全新的设计的时候,我喜欢在写 React 组件的时候,用上一些 ES6+ 的新特性。请允许我列举这些能够改变你写 React 应用方式的新特性。比起以往,这些特性能够使你撸起码来更加容易、有趣!

类(Class)

使用 ES6+ 来编写 React 组件最明显的变化就是我们定义组件(类)的语法的方式。我们可以用定义一个继承了 React.Component 的ES6 类来代替原本使用 React.createClass 的来创建类的方式:

1
2
3
4
5
class Photo extends React.Component {
render() {
return <img alt={this.props.caption} src={this.props.src} />;
}
}

我们可以发现这种写法使得定义组件的方式变得更加简洁:

1
2
3
4
5
6
7
8
9
10
// The ES5 way
var Photo = React.createClass({
handleDoubleTap: function(e) { … },
render: function() { … },
});
// The ES6+ way
class Photo extends React.Component {
handleDoubleTap(e) { … }
render() { … }
}

这样我们可以少写一对圆括号、一个分号、每个方法的冒号和 function 关键字。

所有生命周期方法都可以采用这种方式来定义。 但是 componentWillMount 还可以用 constructor 来代替:

1
2
3
4
5
6
7
8
9
10
11
// The ES5 way
var EmbedModal = React.createClass({
componentWillMount: function() { … },
});
// The ES6+ way
class EmbedModal extends React.Component {
constructor(props) {
super(props);
// Operations usually carried out in componentWillMount go here
}
}

属性初始化(property initializers)

在 ES6+ 类中,属性类型 prop type 和默认属性 default prop 可以通过类中的 static 来声明。同时,组件的初始状态( initial state )可以通过 ES7 的属性初始化(property initializers)来完成:

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
// The ES5 way
var Video = React.createClass({
getDefaultProps: function() {
return {
autoPlay: false,
maxLoops: 10,
};
},
getInitialState: function() {
return {
loopsRemaining: this.props.maxLoops,
};
},
propTypes: {
autoPlay: React.PropTypes.bool.isRequired,
maxLoops: React.PropTypes.number.isRequired,
posterFrameSrc: React.PropTypes.string.isRequired,
videoSrc: React.PropTypes.string.isRequired,
},
});
// The ES6+ way
class Video extends React.Component {
static defaultProps = {
autoPlay: false,
maxLoops: 10,
}
static propTypes = {
autoPlay: React.PropTypes.bool.isRequired,
maxLoops: React.PropTypes.number.isRequired,
posterFrameSrc: React.PropTypes.string.isRequired,
videoSrc: React.PropTypes.string.isRequired,
}
state = {
loopsRemaining: this.props.maxLoops,
}
}

ES7 中在构造函数( constructor )下的属性初始化操作中的 this 指向的是类的实例,所以初始状态( initial state )可以通过 this.prop (即传入的参数)来设定。

箭头函数(Arrow function)

React.createClass 方法在你的组件上做了一些额外的绑定工作,以确保在组件实实例的方法内部, this 指向的是组件实例自身。

1
2
3
4
5
6
7
// Autobinding, brought to you by React.createClass
var PostInfo = React.createClass({
handleOptionsButtonClick: function(e) {
// Here, 'this' refers to the component instance.
this.setState({showOptionsModal: true});
},
});

由于我们使用 ES6+ 的语法定义类的时候没有采用 React.createClass 的方式,所以,这样看来我们不得不手动来绑定这些方法中 this 的指向:

1
2
3
4
5
6
7
8
9
10
11
12
// Manually bind, wherever you need to
class PostInfo extends React.Component {
constructor(props) {
super(props);
// Manually bind this method to the component instance...
this.handleOptionsButtonClick = this.handleOptionsButtonClick.bind(this);
}
handleOptionsButtonClick(e) {
// ...to ensure that 'this' refers to the component instance here.
this.setState({showOptionsModal: true});
}
}

幸运的是,通过 ES6+ 的箭头函数( Arrow functions )和属性初始化( property initializers )这两个特性使得把函数的 this 指向绑定为组件的实例变得非常的简单:

1
2
3
4
5
class PostInfo extends React.Component {
handleOptionsButtonClick = (e) => {
this.setState({showOptionsModal: true});
}
}

函数体内的 this 对象,绑定定义时所在的对象,而不是使用时所在的对象。而恰好属性初始化( property initializers )刚好在这个作用域内。

动态属性名 & 字符串模板

在 ES6+ 中对 对象字面量的扩展 使得我们可以在对象字面量中使用表达式来对属性命名。如果是在 ES5 中,我们也许只能这样做:

1
2
3
4
5
6
7
var Form = React.createClass({
onChange: function(inputName, e) {
var stateToSet = {};
stateToSet[inputName + 'Value'] = e.target.value;
this.setState(stateToSet);
},
});

但是,在 ES6+ 中,我们不仅可以在对象字面量属性的定义中使用表达式,还有使用使用 字符串模板 :

1
2
3
4
5
6
7
class Form extends React.Component {
onChange(inputName, e) {
this.setState({
[`${inputName}Value`]: e.target.value,
});
}
}

析构 & 扩展运算符

我们在编写组件的过程中,经常遇到要从父组件要把自己的很多属性多传给子组件的情况。有了 ES6+ 的 _析构_ 和 扩展运算符 特性,这变得非常的方便:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AutoloadingPostsGrid extends React.Component {
render() {
var {
className,
...others, // contains all properties of this.props except for className
} = this.props;
return (
<div className={className}>
<PostsGrid {...others} />
<button onClick={this.handleLoadMoreClick}>Load more</button>
</div>
);
}
}

我们可以把 扩展运算符 属性和普通的属性结合起来使用,这样使得我们可以利用优先级来使用属性的默认值和属性的覆盖。下面这个元素会获得一个 override 的类( class ),及时 this.props 中有传递 className 属性。

1
2
3
<div {...this.props} className="override">
…
</div>

下面这种写法,可以给元素设定默认的 className:

1
2
3
<div className="base" {...this.props}>
…
</div>

最后

我希望你能够享受 ES6+ 的这些特性给你在编写 React.js 中带来的好处。感谢我的同事他们为这篇文章作出的贡献,还有,特别的感谢 Babel 团队,使得我们可以随意的使用这些特性。

文章地址:http://blog.mcbird.cn/2015/09/11/react-on-es6-plus/

推荐阅读

  • 『译』React Mixin 的使用
  • ES6模拟布朗运动

gulp 入门

发表于 2015-08-28   |   分类于 tools   |  

gulp logo

Gulp 是什么?

Gulp 是 Grunt 的替代者。作为构建工具,Gulp 更具有语法简洁易懂的优势,所以学习成本更低。
利用 Gulp, 我们不仅可以对HTML、CSS(LESS|SASS)、JS(CoffeeScript)进行编译;还可以监听文件的变化,同时借助 livereloader 实时地刷新页面;甚至还可以对图片进行压缩优化。下面记录一下我的 Gulp 学习之旅 。

安装 Gulp

Gulp 需要 nodejs.org 运行环境,首先请确保你的机器上安装了 node.js。

  1. 安装全局 Gulp

    1
    npm i --global gulp
  2. 安装本地(作为开发依赖项)Gulp

    1
    npm i --save-dev gulp
  3. 创建 gulpfile.js 文件

    1
    2
    3
    4
    5
    6
    var gulp = require('gulp');

    gulp.task('default', function() {
    // 将你的默认的任务代码放在这
    console.log('hello world!');
    });
  4. 运行 Gulp
    在终端运行:

    1
    gulp

gulp 后不接参数,那么默认会执行 default 任务,所以上面的代码会在终端中输出 hello world!。

使用插件压缩脚本

gulp 依靠各种插件(plugin)实现常用功能。比方说我们要对所有的 *.js 文件进行压缩(借助 gulp-uglify 插件)

1
2
3
4
5
6
7
8
var gulp = require('gulp'),
uglify = require('gulp-uglify');

gulp.task('default', function() {
gulp.src('js/*.js')
.pipe(uglify())
.pipe(gulp.dest('build/js'));
});

重新在终端运行 gulp, Gulp 会对 js/ 目录下的所有 js文件进行压缩,然后输出到 build/js 目录。

给任务(Task)命名

上面我们定义了一个对 js 进行压缩的任务,并把它放在了默认的(default)任务下,其实我们最好把处理脚本的相关操作抽出来,当独作为一个任务(例如 scripts )更为合理:

1
2
3
4
5
gulp.task('scripts', function () {
gulp.src('js/*.js')
.pipe(uglify())
.pipe(gulp.dest('build/js'));
});

我们可以在终端运行指定的任务项,只需在 gulp 后面接上任务名参数:

1
gulp scripts

监听文件的改变

我们可以借助 gulp-watch 插件 来监听文件的改变:

1
2
3
4
5
6
7
var gulp = require('gulp'),
uglify = require('gulp-uglify'),
watch = require('gulp-watch');

gulp.task('watch', function() {
gulp.watch('js/*.js', ['scripts']);
});

这里我们定义了一个 watch 任务,监听 js/*.js 文件的改变, watch() 的第二个参数是文件发生改变之后的回调,这里表示,文件一旦发生改变,就会执行前面我们定义的 scripts 任务,重新对 js 文件进行压缩处理。

用Gulp来编译LESS

Gulp 不仅可以对 js 进行压缩处理,还可以编译 less/scss 文件,这一功能也是我们经常在项目中用到的。这里需要借助 gulp-less 插件:

1
2
3
4
5
6
7
8
9
10
var gulp = require('gulp'),
uglify = require('gulp-uglify'),
watch = require('gulp-watch'),
less = require('gulp-less');

gulp.task('styles', function () {
gulp.src('less/**/*.less')
.pipe(less())
.pipe(gulp.dest('css/'));
});

使用Plumber来让Gulp保持运行

如果我们在编写代码的过程中,不小心代码写错了,存在语法错误,那么编译不会被通过,gulp 会在终端抛出异常,同时终止。这不是我们想要的,我们希望存在语法错误是,Gulp 能够给我们提示,并继续保持运行,而不是中断,这个时候就要借助 gulp-plumber 来帮我们做这个事:

1
2
3
4
5
6
gulp.task('styles', function () {
gulp.src('less/**/*.less')
.pipe(plumber())
.pipe(less())
.pipe(gulp.dest('css/'))
});

注意: 我们要保证 plumber() 要放在其他操作前面,这样才能捕捉到错误异常。

实时更新(LiveReload)页面

我们期望代码更新后不仅能够重新编译代码,而且希望浏览器帮我们自己刷新网页,这个时候,我们就需要用到 gulp-livereload 的帮助了,同时我们需要安装 livereload 的Chrome插件。

1
2
3
4
5
6
7
gulp.task('styles', function () {
gulp.src('less/**/*.less')
.pipe(plumber())
.pipe(less())
.pipe(gulp.dest('css/'))
.pipe(livereload());
});

注意: 与 plumber() 相反,我们要把 livereload() 放在操作的最后。

简单的进行图像压缩

我们可以借助 gulp-imagemin 对图片进行优化:

1
2
3
4
5
gulp.task('images', function () {
return gulp.src('src/images/*')
.pipe(imagemin())
.pipe(gulp.dest('dist/images'));
});

自动补全浏览器前缀

我们还可以用 gulp-autoprefixer 对 CSS3 中的一些属性进行前缀的自动补全(例如 transition, transform 等 ):

1
2
3
4
5
6
7
8
gulp.task('styles', function () {
gulp.src('less/**/*.less')
.pipe(plumber())
.pipe(less())
.pipe(prefixer('last 2 versions'))
.pipe(gulp.dest('css/'))
.pipe(livereload());
});

prefixer() 的参数可参考 这里

最终的 gulpfile.js 配置文件

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
var gulp = require('gulp'),
uglify = require('gulp-uglify'),
watch = require('gulp-watch'),
less = require('gulp-less'),
plumber = require('gulp-plumber'),
livereload = require('gulp-livereload'),
prefixer = require('gulp-autoprefixer');

// Scripts Task
gulp.task('scripts', function () {
gulp.src('js/*.js')
.pipe(plumber())
.pipe(uglify())
.pipe(gulp.dest('build/js'))
.pipe(livereload());
});

// Styles Task
gulp.task('styles', function () {
gulp.src('less/**/*.less')
.pipe(plumber())
.pipe(less())
.pipe(prefixer('last 2 versions'))
.pipe(gulp.dest('css/'))
.pipe(livereload());
});

// HTML Task
gulp.task('html', function() {
gulp.src('*.html')
.pipe(livereload());
})

// Image Task
gulp.task('images', function () {
return gulp.src('src/images/*')
.pipe(imagemin())
.pipe(gulp.dest('dist/images'));
});

// Watch Task
gulp.task('watch', function() {
livereload.listen();
gulp.watch('js/*.js', ['scripts']);
gulp.watch('less/**/*.less', ['styles']);
gulp.watch('*.html', ['html']);
});

gulp.task('default', ['scripts', 'styles', 'watch']);

总结

上面列出了一些日常开发中比较常用的几种功能,其他更多的功能可参考官方 plugin 库,根据个人需求对其功能继续扩展。

[译]如何成为一个优秀的前端工程师

发表于 2015-08-15   |   分类于 think   |  

原文地址:http://philipwalton.com/articles/how-to-become-a-great-front-end-engineer/
来自Google的前端工程师-Philip Walton 分享了自己关于如何成为优秀的工程师的一些观点。个人感觉很有价值,所以翻译成中文,方便大家阅读。 人水平有限,如翻译不妥之处请在评论中指出。

最近,我收到了读者的邮件,引发了我的一些思考。这是他在邮件中问我的问题:

Hi Philip, is it okay to ask how you become a great front-end engineer?
Any advice?

我不得不承认,我感到非常的惊讶,居然会被问到这样的问题,因为我从来没有想过自己是一个优秀的前端工程师。实际上,我在这个行业工作的前几年里,我真的不觉得我能够胜任我的工作。我接受了这些工作只是因为我以前没有意识到我知道得东西太少了,我能够得到这些工作只是因为以前面试我的人不知道问我什么问题。

话虽这么说,我最后还是把我自己的角色做得非常好,而且成为了团队里有价值的一员。当我最后离职的时候(下一个职位我还是无法胜任)我通常也会应试那些将要应聘我的职位的人。现在回想那些我面试过的应聘者,让我明白,看待知识的重要性。尽管我一开始在这个领域很薄弱。我现在的自己可能也不会雇佣以前的那个自己,尽管我知道随着工作经验的积累,成功也是可能的。

在Web领域,我工作的越久,越让我意识到不错的人和真正优秀的人的区别在于不是他们知道什么,而是他们如何思考。 很明显,知识是很重要的–特别是在某些情况下–但是,在一个快速变化的领域并非如此。你获取知识的方式比你知道那些知识更为重要。而且也许最重要的是:你如何利用你的知识去解决日常生活中的问题。

你可以找到大量谈论知识点、框架和工具的文章,这是知识都是获得一份工作所需要知道的。我想说一些不一样的。在这篇文章中,我会说一说前端工程师应有的心态,希望能够回答最开始的问题:怎样才能做到优秀?

不要只是解决问题,找到问题的根源所在

很多的人只是不断地写一些 CSS 和 Javascript 补丁直到他们发现这些东西可以正常的工作了,然后他们就不管了。我在代码审查的过程中看到了很多这样的做法。

我会经常问别人:“为什么你要在这里加一个 float: left ?” 或者 “这个 overflow:hidden 真的需要吗?”,然后他们会回答:“我不知道,但是如果我删掉他们,就出问题了”。

Javascript 也一样。我看到一些人用 setTimeout 来防止执行顺序上的问题,或者是一些人滥用 stopPropagation() 而没有考虑到它也许会影响页面中的其他事件处理函数。

我遇到过很多相同或类似的问题,如果你从来不花时间去了解问题的根源所在,你会发现你会一遍一遍的遭遇同样的困境。

花时间去深入的研究你的解决方案为什么可行看起来需要耗费很多的精力,但是我发誓它会在将来给你节约很多时间。对你现在工作的系统有一个全面的理解可以减少你将来的猜测和检查工作。

学会预测浏览器领域将来的变化

前端跟后端主要的不同是:后端的运行环境在你的控制之下。但是对于前端而言,相比于后端,它完全不在你的控制范围。你的用户所使用的平台或者设备随时都有可能改变,你的代码需要能够优雅地处理这种情况。

我记得早在2011年的时候,我在一个非常著名的 javascript 框架看到下面这样一段源码(大致如此):

1
var isIE6 = !isIE7 && !isIE8 && !isIE9;

我知道在现实生活中,feature detaction 并不能100%的有效,有些时候我们不得不采用这种不好的方式或者浏览器白名单的方式,但是任何时候你这样做的时候,你应该预测未来可能会发生什么,即使发生,你的代码也不应该出现bug。

对于我们大多数人而言,我们在现在的工作岗位上写的代码会比我们的任期更长。我8年前写的一些代码现在还会经常用到,这想起来既让人满足又让让感到可怕。

阅读官方文档

浏览器 bug 总是存在的,但是,当两个浏览器执行相同的代码表现却不一致时,人们经常假设,而不是亲自去检查,只是把他们认为的表现好的叫做“正常”浏览器,表现异常的叫“不正常”浏览器。但并非总是这样,当你做出了错误的假设时,任何你选择的解决方法在未来也许会失效。

一个现实的例子就是 flex items 的默认最小尺寸。根据官方文档,flex items 的 min-width 和 min-height 是 auto (而不是 0),这意味着,它们的尺寸不会比它们的内容还要小。 但是在过去的8个月里,只有 Firefox 是唯一一个正确的实现这一标准的浏览器。

如果你遇到过这类跨浏览器的兼容性问题,而且你注意到你的页面在 Chrome,IE,Opera,和 Safari 上表现一致,唯独 Firefox 上表现不同时,你肯定会猜测这是 Firefox 自己的问题。

当两个或多个浏览器在渲染相同的代码表现不一致时, 你应该花一些时间去研究到底哪个浏览器才是正确的,然后用正确的方式写下你的代码。你的作品才会是面向未来的。

另外,优秀的前端工程师经常都是站在变化的最前列的,他们会在这些技术成为主流之前就采用这些技术,甚至为这些技术作出贡献。如果你凭借你自己的实力去查找官方文档,而且能够想象一个技术在你能够在浏览器中用它的之前将会是如何工作的,你将成为能够谈论这个官方标准会对开发造成什么影响的人。

阅读其他人的代码

阅读其他人的代码,无疑是成为一个更好的开发者的最好方式。

自己解决问题是学习的最好方式,但是如果这些问题都是你以前解决过的,你很快就会进入平稳期(很难有上升的空间)。阅读其他人的代码可以为你打开处理问题的新的思路。而且阅读和理解别人写的代码的能力也是在跟团队合作或者参与开源项目时至关重要的能力。

实际上,我认为在面试一个应聘者是只让他们写代码–新的代码,是最大的错误。我应聘的时候,从来没有被叫过去阅读一些已经存在的代码,在这些代码中找出问题,然后解决它。这是非常不好的,因为作为一个工程师,很多时候我们是在别人的代码上添加和改变一些代码。很少从头写一些新的。

跟比你聪明的人一起工作

在我印象你,比起后端开发者,更多的前端开发者希望成为一个自由职业者(全栈)。也许是前端工程师更趋向于自学,而后端工程师更趋向于学术。

自学并为你自己工作的问题是无法从比你聪明的人身上得到好处。没有人来跟你讨论观点或者帮你审查代码。

我强烈的建议,至少在你职业生涯初期,在一个团队中工作,特别是跟一群比你更聪明更有经验的人工作。

如果你已经结束你的职业生涯,现在只是为你自己工作,那么参与到开源中来。 贡献开源项目会给你很多与团队合作的机会。

重复造轮子

在商业上,重复造轮子是不好的,但是对于学习来说并非如此。你也许尝试从 npm 上获取预输入控件或者事件委托库,但是想象一下如果你自己尝试创造这些东西的话会学到更多。

我确定一些正在阅读这篇文章的人对此感到强烈反对。不要误会我的意思。我不是说你永远也不应该使用第三方库。使用优秀的库是非常明智的事情。

但是,在这篇文章中,我要说的是如何从一个不错的工程师成为一个优秀的工程师。大部分我认为的这个领域中优秀的工程师都是这些优秀的第三方库的维护者。

你也许从来没有构建够自己的 javascript 库,但是你依然能够在你的职业生涯中获得成功,但是,可能你从来没有理解到解决问题的核心。

在这个行业中,人们经常问起的一个问题是:我接下来应该做什么? 如果你问了这个问题,为什么不去尝试重新创造一个你喜欢的 javascript 库或者 CSS 框架,而不是尝试一些新的工具或者写一个新的 app。 这样做的好处是,及时你遇到了困难,你也可以从目前已有的库中的源码找到答案。

把你学到的东西写下来

最后, 你应该把你学到的东西写下来。有太多的理由这样做了,但是,也许最重要的原因是这样可以强迫你更好地理解你所学的东西。如果你无法解释其原理,这是一个很好的机会说明你并没有完全搞懂它。很多时候你没有意识到你不懂,直到你爸它写下来。

在我的经验中,书写、做一个演讲、以及写一些 demos 是强迫我自己完全弄懂一个东西的最好方式,从里到外。及时没有一个人会看你写的东西,但是做这件事的过程更有价值。

文章地址:http://blog.mcbird.cn/2015/08/15/How-to-Become-a-Great-Front-End-Engineer/

推荐阅读

  • 『译』React Mixin 的使用
  • 磨刀不误砍柴工之区块链领域专业术语中英文翻译与发音

『译』React Mixin 的使用

发表于 2015-07-24   |   分类于 javascript   |  

我使用 React.js 构建大型项目已经有一段时间了,我遇到过很多在不同的组件中都要用到相同功能的情况。因此,我花了一个小时左右的时间去了解mixin的用法,然后分享我所学习到的一些东西。

为什么使用 Mixin ?

React回避子类组件,但是我们知道,到处重复地编写同样的代码是不好的。所以为了将同样的功能添加到多个组件当中,你需要将这些通用的功能包装成一个mixin,然后导入到你的模块中。 可以说,相比继承而已,React更喜欢这种组合的方式。嗯,我也喜欢这样。

写一个简单的 Mixin

现在假设我们在写一个app,我们知道在某些情况下我们需要在好几个组件当中设置默认的name属性。
现在,我们不再是像以前一样在每个组件中写多个同样的getDefaultProps方法,我们可以像下面一样定义一个mixin:

1
2
3
4
5
var DefaultNameMixin = {
getDefaultProps: function () {
return {name: "Skippy"};
}
};

它没什么特殊的,就是一个简单的对象而已。

加入到 React 组件中

为了使用mixin,我们只需要简单得在组件中加入mixins属性,然后把刚才我们写的mixin包裹成一个数组,将它作为该属性的值即可:

1
2
3
4
5
6
7
8
var ComponentOne = React.createClass({
mixins: [DefaultNameMixin],
render: function() {
return <h2>Hello {this.props.name}</h2>;
}
});

React.renderComponent(<ComponentOne />, document.body);

JSFiddle 示例:一个简单的 mixin 例子

重复使用

就像你想象的那样,我们可以在任何其他组件中包含我们的mixin:

1
2
3
4
5
6
7
8
9
10
11
var ComponentTwo = React.createClass({
mixins: [DefaultNameMixin],
render: function () {
return (
<div>
<h4>{this.props.name}</h4>
<p>Favorite food: {this.props.food}</p>
</div>
);
}
});

JSFiddle 示例:在多个组件中使用同一个 mixin

生命周期方法会被重复调用!

如何你的mixin当中包含生命周期方法,不要焦急,你仍然可以在你的组件中使用这些方法,而且它们都会被调用:

JSFiddle 示例:展示了两个 default props 都会被设置

两个getDefaultProps方法都将被调用,所以我们可以得到默认为Skippy的name属性和默认为Pancakes的food属性。任何一个生命周期方法或属性都会被顺利地重复调用,但是下面的情况除外:

  • render:包含多个render方法是不行的。React 会跑出异常:
1
2
3
Uncaught Error: Invariant Violation: ReactCompositeComponentInterface: 
You are attempting to define `render` on your component more than once.
This conflict may be due to a mixin.
  • displayName:你多次的对它进行设置是没有问题的,但是,最终的结果只以最后一次设置为准。

需要指出的是,mixin是可以包含在其他的mixin中的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var UselessMixin = {
componentDidMount: function () {
console.log("asdas");
}
};

var LolMixin = {
mixins: [UselessMixin]
};

var PantsOpinion = React.createClass({
mixins: [LolMixin],
render: function () {
return (<p>I dislike pants</p>);
}
});

React.renderComponent(<PantsOpinion />, document.body);

程序会在控制台打印出asdas。

包含多个 Mixins

我们的mixins要包裹在数组当中,提醒了我们可以在组件中包含多个mixins:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var DefaultNameMixin = {
getDefaultProps: function () {
return {name: "Lizie"};
}
};

var DefaultFoodMixin = {
getDefaultProps: function () {
return {food: "Pancakes"};
}
};

var ComponentTwo = React.createClass({
mixins: [DefaultNameMixin, DefaultFoodMixin],
render: function () {
return (
<div>
<h4>{this.props.name}</h4>
<p>Favorite food: {this.props.food}</p>
</div>
);
}
});

注意事项

这里有几件事需要引起我们的注意,当我们使用mixins的时候。 幸运地是,这些看起来并不是什么大问题,下面是我们在实践当中发现的一些问题:

设置相同的 Prop 和 State

如果你尝试在不同的地方定义相同的属性时会出现下面的异常:

1
2
Uncaught Error: Invariant Violation: mergeObjectsWithNoDuplicateKeys(): 
Tried to merge two objects with the same key: name

设置相同的方法

在不同的mixin中定义相同的方法,或者mixin和组件中包含了相同的方法时,会抛出异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var LogOnMountMixin = {
componentDidMount: function () {
console.log("mixin mount method");
this.logBlah()
},
// add a logBlah method here...
logBlah: function () {
console.log("blah");
}
};

var MoreLogOnMountMixin = {
componentDidMount: function () {
console.log("another mixin mount method");
},
// ... and again here.
logBlah: function () {
console.log("something other than blah");
}
};

异常信息同多次定义rander方法时抛出的异常一样:

1
2
3
Uncaught Error: Invariant Violation: ReactCompositeComponentInterface: 
You are attempting to define `logBlah` on your component more than once.
This conflict may be due to a mixin.

多个生命周期方法的调用顺序

如果我们的组件和mixin中都包含了相同的生命周期方法的话会怎样呢?

我们的mixin方法首先会被调用,然后再是组件的中方法被调用。

那当我们的组件中包含多个mixin,而这些mixin中又包含相同的生命周期方法时,调用顺序又是如何?

它们会根据mixins中的顺序从左到右的进行调用。

实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var LogOnMountMixin = {
componentDidMount: function () {
console.log("mixin mount method");
}
};

var MoreLogOnMountMixin = {
componentDidMount: function () {
console.log("another mixin mount method");
}
};
var ComponentOne = React.createClass({
mixins: [MoreLogOnMountMixin, LogOnMountMixin],
componentDidMount: function () {
console.log("component one mount method");
},
...

var ComponentTwo = React.createClass({
mixins: [LogOnMountMixin, MoreLogOnMountMixin],
componentDidMount: function () {
console.log("component two mount method");
},
...

控制台将输出:

1
2
3
4
5
6
7
another mixin mount method
mixin mount method
component one mount method

mixin mount method
another mixin mount method
component two mount method

总结

Mixin 使你React程序变得更为可重用,It’s a Good Thing.
我希望这篇文章能够对你有所帮助,如果有任何反馈,很高兴你能够在twitter上@veddermatic

原文链接:http://simblestudios.com/blog/development/react-mixins-by-example.html
翻译水平有限,文中带有个人理解,如有不恰当的地方,请在评论中指出,非常感谢!

推荐阅读

  • [译]在 React.js 中使用 ES6+
  • [译]如何成为一个优秀的前端工程师
  • 磨刀不误砍柴工之区块链领域专业术语中英文翻译与发音
1234…6
McBird

McBird

分享自己的学习经历,实用技巧

27 日志
13 分类
25 标签
GitHub 微博 知乎
© 2015 - 2021 McBird
由 Hexo 强力驱动
主题 - NexT.Muse