词法作用域

作用域模式有两种,一种是词法作用域,另一种是动态作用域,JavaScript采用的是词法作用域。

大部分的编译器会在编译阶段把程序进行词法化,也就是会对源代码中的字符进行解析,并且赋予词语语义。简单来讲,词法作用域就是词法阶段的作用域,词法作用域是由你在写代码时讲变量和块写在哪里决定的,当词法分析器在处理代码时会保持作用域不变。

考虑下面的代码:

1
2
3
4
5
6
7
8
9
10
11
var name = 'limoer';
function showNameAPI(name){
var city = 'Chongqing';
function showCity(){
var cid = 'CN';
console.log(city + cid);
}
showCity();
console.log(name);
}
showNameAPI(name)

这个代码一共包含三个逐级嵌套的作用域,全局作用域中声明了变量name,全局函数showNameAPI,函数作用域中showNameAPI所创建的作用域,包含标识符city以及showCity,最后是showCity创建的作用域,包含了标识符cid。

作用域查找会在找到第一个匹配的标识符时停止。这里的查找是由内而外的,并且在多级嵌套的作用域内可以定义同名的标识符,但是会产生覆盖。因为作用域查找的规则就是找到第一个匹配的标识符后停止

无论函数在哪里被调用,也无论其是怎么被调用的,其词法作用域只与其被声明的位置有关。

欺骗词法

上面说到词法作用域是在是完全在书写代码是就已经决定,但是也可以通过下面的两种方式在运行时来改变词法作用域。

当然,不出意外的,这两种方式会是不那么讨人喜欢的eval()和with。

我们首先来回顾一下eval(),这个函数接收一个字符串作为参数,这个字符串好像是运行时写在这里的代码一样。这明显是一种词法欺骗,其假装是在书写期间就在那里,而在运行时修改词法作用域。但是引擎对此并不知情,所以其依旧照常按照词法作用域进行查找。
看一个例子:

1
2
3
4
5
6
var name = 'limoer';
function showName(str){
eval(str);
console.log(name);
}
showName("var name = 'lindo'") // lindo

eval(‘name=”lindo”‘)会被引擎误认为在书写时就在那里,由于执行了上面的语句,此时name的值已经被修改了,并且产生了覆盖,遮蔽了外部同名的变量name。

在默认的情况下,如果eval中所执行代码中存在一个或者多个申明,其就会对eval()所处的作用域进行修改。无论何情况,eval(..) 都可以在运行期修改书写期的词法作用域。

再来谈with关键字,我们都知道with关键字用于重复引用一个对象的多个属性的快捷方式,而不需要重复引用对象本身。
看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = {
name: 'limoer',
age: 20,
city: 'Chongqing'
}
console.log(obj.name)
console.log(obj.age)
console.log(obj.city)
// 重复
with(obj){
console.log(name);
console.log(age);
console.log(city);
}

上面的这段代码突出了with关键字优点,它可以简化我们的代码,但是我们这里谈的是with关键字的词法欺骗,看下面一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function info(obj){
with(obj){
name = 'lindo'
}
}
var p1 = {
name: 'limoer'
}
var p2 = {
city: 'Jinan'
}
info(p1)
console.log(p1.name) // 'lindo'
info(p2)
console.log(p2.name) // undefined
console.log(name) //lindo!

上面的这个例子很好的展示了with关键字的词法欺骗,这里创建了两个对象p1和p2,并通过info函数执行with(obj){…},这里进行了简单的LHS查找,并将新值赋给name属性。但是请注意,这里p2对象并不存在name属性,也不会创建name属性,所以p2.name为undefined;这里很好理解,但是为什么神奇的是竟然多出了一个全局变量name呢!?

这里执行with(obj){…}的时候,执行的LHS查找,所以当查找不成功时自动隐式创建一个全局变量,如果这样考虑,那么出乎意料的name属性就不难理解了。

总结

JavaScript拥有的是词法作用域,所谓的词法作用域就是在进行词法分析时的作用域,也就是说,JS的作用域在代码一旦书写完成就能确定(靠书写位置来确定)。词法作用域的理解很简单,但是我们还是需要注意使用eval()和with语句带来的词法欺骗的原因。也许有人会说,在运行时修改词法作用域有利于实现复杂的功能,又利于扩展,何乐而不为呢?可我们在前面提到,在进行编译的时候,JS引擎会对代码进行优化,而这个优化则是根据代码的词法作用域,预先确定变量和函数的位置,才能在执行过程中快速找到标识符。而eval()和with的出现则有可能打破这样的格局,因为引擎在词法分析阶段并不能知道传入的代码到底是什么,会对词法作用域造成怎样的影响。所以,一切优化都是徒劳的,因为在运行时谁都不能确定此时此法作用域到底是怎么样的,所以JS引擎并不会进行优化,导致代码运行缓慢,性能并不好。所以,尽量不要使用它们。

分享到 评论

理解作用域

JS作用域的问题是老生常谈的问题了,我们都知道JS是不存在块级作用域的(ES6以前,try…catch是一个例外),可是仍然实际编程过程中由于对作用域的理解不够深刻,踩坑无数,苦不堪言,恰逢寒假,又到了充(wan)电(shuai)的时间,所以把学习的重点放在认识JS语言本身上面。参考了资料《你不知道的JavaScript》。

实例为先

首先,我们以var a = 1;这一个语句为例,看看这个过程JS到底干了什么。

毫无疑问JS是一门编译型的语言,但它的编译不是提前编译的,编译过程一般发生在语句执行前几个微秒。并且其和其它典型的编译语言一样,编译的步骤是相似的。首先编译器会把语句进行分词,分成单个的代码块,这些代码块被称为词法单元;接下来编译器会把词法单元流转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树,这棵树被称为”抽象语法树AST”;最后一步,代码生成,该过程会把AST转换成可执行代码(机器指令)。

当然,JS的编译过程不会如此简单,其也会包含性能优化等。但这些所有的工作都在执行前几个微秒的时间内完成,并且立即执行它。

在正式的开始了解作用域之前,我们首先认识一下代码在执行时所需要的工具。

  1. 引擎:负责代码编译到执行过程
  2. 编译器:负责词法分析和可执行代码的生成等
  3. 作用域:负责收集并维护由所有声明的标识符(变量)组成的一列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限

现在开始对var a = 1的执行进行分解:

首先编译器会把这段程序分成词法单元,并且构建AST,当遇到var a的时候,首选编译器会询问作用域,是否已经已经有一个同名的变量存在于这个作用域集合中,如果存在则忽略该语句,否则在该作用域申明一个变量,命名为a;接下来,编译器会为引擎生成可执行代码,用于处理a=1这个赋值操作,引擎会询问当前的作用域是否存在一个名字为a的变量,如果存在,则使用这个变量,否则继续操作。最终,如果找到变量a,则给其赋值为1,否则将抛出错误。

总结:在对变量赋值时会存在两个步骤:首先编译器会在作用域中生成一个变量;然后引擎在运行时查找该变量,如果存在就赋值,否则抛出错误。

作用域

1.嵌套作用域:作用域嵌套这个概念是很好理解的,当一个块嵌套在另一个块或函数中的时候,就会发生作用域的嵌套。当在当前作用域无法找到某个变量的时候,引擎就会在外层的嵌套作用域中寻找,知道抵达到最外层作用域或者找到了该变量为止。

考虑以下代码:

1
2
3
4
5
var a = 1;
function add(b){
console.log(a + b)
}
add(2) // 3

对变量a的查找是无法在add函数内部完成的,所以引擎会向上层作用域中查找,并且在外层作用域中找到。

LHS && RHS

引擎查找变量时常用的两种查找方式,这里需要注意的,如果执行右侧查找,无法找到该变量则会抛出引用错误。而进行左侧查找的时候,如果不成功,则会自动的隐式创建一个全局变量(都是在非严格模式下)

小结

简单来说,作用域就是一系列的规则,这套规则用于管理引擎如何在当前作用域以及嵌套的子域中根据标识(zhi)符名称进行变量的查找。

下一篇,将深入的学习词法作用域

分享到 评论

我们的世界

最近看了一部叫做《我们的世界》的韩国剧情片,讲的是这样一个故事:

主人公李善被人孤立,没人愿意和她组队玩集体游戏,她总是最后一个被无奈选中,也不出意料的第一个被淘汰,然后孤苦伶仃的站在一旁,不知道是不是要离开。她在学期末结识了转校生韩智雅,不知道这是不是她结交的第一个朋友,但的确度过了一个快乐的假期。智雅父母离异,却在李善家中看到了温诺的家,于是心生情愫,逐渐远离李善,结交了新的朋友。从好朋友到最熟悉的陌生人,李善就这样在新学期开始,再次被孤立。而韩智雅为了不被大家孤立,不但选择装作不认识李善,反而和班上同学一起排挤她,这让李善儿感到很苦恼。父母的不知情以及后面的一系列的事,让她们两个的关系越来越糟糕。韩智雅成为班上的学霸,而李善也成为了以宝拉为首的小团体利用攻击智雅的工具。最终两人走上了互相伤害的道路,智雅说善儿的父亲是酒鬼,而李善儿反击,把自己知道的智雅撒谎的真相公之于众。最后的最后,一直被孤立的李善还是继续被孤立,而韩智雅也成为那个被孤立的人。故事的最后,班上又开始玩丢球游戏,这次大家不想和两个人玩游戏,李善率先被淘汰,站在一边,韩智雅被诬蔑,李善主动站出来为她说话,避免了僵持不下的尴尬和无助。最后两人站在场边,不时偷望对方。我不禁有这般假设,假如没有选择离开,如今又会怎样?可惜小孩子的思想太过复杂,捉摸不透。电影戛然而止,关于她们两个最终和好的画面留给观众去想。

好了,故事讲完了。李善儿和韩智雅最终还是被孤立,成为了校园冷暴力的受害者。回到现实,我也像她们俩一样,成了受害者,并且顺利的长大了。

故事是这样的

过了这么多年,对小学和初中那段时光的记忆很微弱了。直到前些日子,小学群里面,有人发了一张毕业照照片,照片中的我站在人群的中央,探出一个脑袋,不注意真还发现不了这个瘦小的孩子。而到了快过年的时间,这个群就特别活跃,我却发现,我根本插不上一句话,好不容易找到一个机会发了一条消息,却被无视了,我开始回想,那段小学的日子里,我也经常这样被忽视,也许到现在,他们根本不记得了我了。

我开始回想上小学的那段时光。低年级的时候,我是班上再平常不过的,个子矮小,学习成绩一般,零花钱少,还特别闷。在我的印象中,那个时候我经常中午要去外婆家吃饭,来回的路上会有很多的孩子,但是没人愿意和我一起走,如果我走过去主动和他们说话他们就跑,或者打我。直到后来,我来回的路上终于有了一个伴,那是外婆村子里读小学的一个半傻子(我也不知道该怎么形容),我们一起在小河沟捉鱼,玩弹珠,可也因为这样,其他孩子更不愿与我玩。那时候的我,却没有如电影里两个女孩子那样琢磨不透的小心思,只好选择沉默。

好吧,最终我还是有了第二个(第一个是外婆村子里的傻子)好朋友,在我三年级的时候,我和安静的同桌成为了好朋友,我们很多时候会在放学时候一起回家(貌似还不顺路),开始的时候,我们都不怎么说话,可我发现一个人的字可以写得那么工整,一个人的画可以画得那么漂亮,我开始主动找她说话,后来我们两个人之间似乎有说不完的话,每到下课,我们才不会去和其他同学一样,逛零食店,打乒乓球,玩跳绳。可这段时间没过多久,我的第二个朋友一年后转校离开了。我又开始了被孤立的时光,知道逐渐的适应这样的生活。

这种情况直到小学六年级才得到转机,我的学习成绩也越来越好,我的一个亲戚成了我的班主任,他也很关照我。我虽然依然很闷,但是还是想逃离被孤立。为了融入坏孩子团体,我也开始变‘坏’,我开始在学校小卖店赊账,大部分都是请同学吃了零食;我开始和他们放学不回家跑到网吧去玩,这也是我最开始接触到网络的时候。可即便是这样,我仍然无法摆脱不了被孤立,我能够打很好的乒乓球但是没人愿意加我一个,我也能玩弹珠,但是又有人和我玩?

后来,大家都升入了初中,整个初中我都是在极力的融入这个环境,总在坏孩子和好学生的角色切换,这样的生活我过了三年。初中毕业后,我只身一人去了一个陌生的学校读书,也就和当初的坏孩子军团失去了联系,也许他们也不记得了,那个曾经总是在帮他们跑腿和揽责(背锅?)的我,这个坏孩子。

我的那段故事讲到这个就要画上一个句号了,因为我在高中结识了最要好的几个朋友,我再也是那个被孤立的对象。虽然在不熟识我的人眼里,我仍然很闷,就像刺猬一样,让人不得接近,可现实中的我,不是这样😂

那么,我到底想说什么

我相信在小时候的学生时代,被迫承受孤独的还有很多人。他们是多么渴望有个人成为他们的朋友,玩伴。可奈何无论怎么努力,却还是一再的被孤立。大概是因为差异,微小的差异让我们成为了受伤害的对象。也许是因为我矮小,抑或是我长得丑,家庭条件不好,甚至因为我成绩好…这些都成为了我们被排挤的理由。我们在任何时间段都会受到这样的伤害,成人学会了忍耐,而对于小孩子,他们很多就像李善和韩智雅一样,最终互相伤害。当我们还小时,因该是我们率真的时候,我们的小心思却伤害了我们。当然,这一切的一切,学校和家长并不知情,这样的伤害也许就会持续下去,伴我们成长。

小尾巴

我是先看完简介的时候再看电影的,整个过程我心情很失落,我看到了我的小时候,我并没有被无尽的孤立的所吞噬,我也没有反抗去伤害他人,反而我适应了这样的生活,逐渐的我习惯了过一个人的生活。直到现在,当独处的时候,我会回想,如果如曾经厌倦了无尽的孤立选择伤害反抗,抑或是我本身就是孤立别人的’坏孩子’,如今我又怎样。奈何时间没有倒带,人生不会从头再来。

分享到 评论

React全家桶网络学习资源汇总

前言

不知不觉,学习React时间已经超过三个月了。虽然这几个月断断续续,也算把我所了解的React全家桶学习了一遍,虽然我现在还是什么都不会😂
好了,接下来,我会把我学习过程中用到的一些网络资源都整理在这篇文章里面,分享给大家。

学习react之前

学习react之前,你最好有js(ES6)的基础,下面是JS学习资料搜集汇总。
两本来自于阮一峰大神的开源书籍:
JavaScript标准参考教程这里
ES6入门指南强烈推荐,看过实体书,ES6入门很实用
来自廖雪峰的官方网站的JS教程特别适合快速入门和新手回顾

JavaScript Promise迷你书全面了解Promise的书籍
深入理解JavaScript系列汤姆大叔翻译,比较全面,值得推荐

react学习

需要明确的一点的是,学习react最好的方式是阅读官方文档,这里列出一些资源,可作为学习的参考和补充:
react入门实例教程没错,还是来自于阮一峰大神
Learn React & Webpack by building the Hacker News front page强烈推荐,手把手教你构建一个前端页面
react-组件生命周期详解当我很混淆的时候,就是看这篇博文弄懂的
React入门:关于JSX语法虽然JSX很好理解,但是还是推荐一篇吧,比较详细了
React入门来自则修网的视频教程,一共有两门教程,这一门非常适合入门
react文档中文翻译版本,推荐结合英文文档使用

webpack

webpack作为一个打包工具,如今已经十分出色。
一小时包教会 —— webpack 入门指南不敢保证一小时能不能教会,但是写得还是很详细,值得一看
webpack中文文档(类似于官方文档,比较详细的介绍了webpack的方方面面)(http://webpackdoc.com/)

flux & redux

Flux 架构入门教程阮一峰大神目前专注于react啊,什么都有他
Redux 入门教程还是阮一峰的
Redux Tutorial 中文翻译适合没有耐性看完官方文档的同学
Redux中文文档英文翻译版,适合英语基础较薄弱的同学观看,貌似还可以下载下来
redux其他学习资源我fork的

mocha & chai

测试框架Mocha 实例教程不用我说来自哪里了吧
官方文档最好的学习教程
chai强大的断言库,学习途径目前只有中文文档

react单元测试 & enzyme

React 测试入门教程点这里
enzyme 是用于react单元测试的一个库,类jquery的风格,比官方那一套写起来顺手多了,推荐.

react-router & 其他

React Router 是完整的 React 路由解决方案
React-Router中文文档看这个就够了

immutable.js是一个数据不可变的一个库,据说结合React可以让性能提升十倍,学习的时候有了解过.
Immutable 详解及 React 中实践
官方文档

lodash 其实不属于react全家桶成员(虽然我觉得inmutable也不是),但是作为一个高性能和模块化的JS工具库,值得大家去了解和使用
中文文档,未翻译完全版本

写在后面

上面的资料和文档也许可以很容易就能通过baidu或者其他什么的得到,我算是列举了我学习时候参考的资料吧,不一定适合大家,也许有更适合入门的资料只是我没找到而已。上面列举的也并不是react全家桶的所有技术,包括css-module,覆盖率测试这些我也是刚刚才了解,并没有深入的使用,所以我这里就不列出了。在我看来,react发展到现在,已经不能说其只是一个前端框架的view层了,它是一个技术栈,一种解决方案,并且只有使用其全家桶才能发挥出其威力,作为一个前端初学者而言,这个过程并不会太痛苦,因为我们并没有对传统的web开发思维根深蒂固,所以如果你想学习react,不妨放下心中的顾虑,现在就开始。

学习react不难,但是真的想写好react太难了,angular也是,vue也是,所以不要轻易说你学会了它们。我们仅仅是轮子的使用者而已,可我真想哪天我也能成为造轮子的人,加油!

分享到 评论

使用webpack+npm进行React开发

写在前面

这一期的知识我早就想写了,因为在学习React之初,心里面就有很多疑问,当然其中一个就是:我能不能不把所有的Component写在一个文件中,当然后面没有关注这样的问题,直到前些日子学习Flux才重视起来,恰逢周六,在这里写一篇,关于如何使用webpack+npm进行React开发。

webpack–脚手架工具而已

在这里我并不打算详细介绍webpack如何使用,webpack只是一个前端脚手架工具而已,在进行React开发的时候,我们只是使用babel转换ES6的代码而已,但是webpack也是一个好用而简单的打包工具,想了解更多关于webpack的知识,点击这里
好吧,正式开始吧!
首先我们需要安装node 和 npm, 因为webpack也是依赖node的,安装好node和npm后我们就可以开始安装webpack了,首先安装webpack:

1
npm install -g webpack

当然这里你也可以不全局安装,使用npm init初始化一个package.json文件,使用npm install --save webpack安装。

这里我将会跳过使用命令进行打包,直接进到使用配置管理文件就好了,如果想更深层次的了解webpack,点击上面的介绍咯(真的很详细)。

首先介绍一下这个配置文件,默认命名为webpack.config.js,其是一个node的文件,就像如下的这个样子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	
module.exports = {
entry: './base.jsx',
output: {
filename: './output.js'
},
module: {
loaders:[
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel',
query: {
presets: ['es2015','react']
}
}
]
}
}

好吧,介绍一下上面的这个文件到底描述了些什么,首先我们输出了一个配置对象,entry定义了要引入的文件,当然如果需要引入多个文件的话可以使用一个数组。output顾名思义定义要输出的文件名,这里可以把它理解为打包好存放的文件。
最后,下面的这个module属性用于定义加载器,比如我们需要使用babel来吧jsx转为普通的js代码,test属性表示给所有匹配的jsx执行解析,
exclude表示将会不解析node模块和通过bower安装的模块。loader表示使用解析工具,query则表示的扩展参数,这里表示应用es6和react的解析规则。
好了,对示例文件的简单介绍就告一段路,当然还有一个属性plugins,用于使用插件,有关插件的使用请移至官方文档。

搭建react的开发环境

细心的童鞋可以发现了,上面我演示的配置文件就是一个用于开发react的基本配置文件。好吧,直接开始,首先安装react,

1
2
npm install --save react
npm install --save react-dom

接着安装babel用于解析jsx和es6,

1
npm install --save babel-loader

最后安装一些解析规则

1
2
3
npm install babel-plugin-transform-es2015-arrow-functions --save-dev
npm install babel-preset-es2015 --save-dev
npm install babel-preset-react

到这里,我们就安装完了开发react所需要的模块了,好吧,直接进入最后一步,在这里我将写一个和小很小的例子

一个栗子

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

import React from 'react';
import {render} from 'react-dom';
class InputComp extends React.Component{
constructor(props){
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
value: ''
}
}
handleChange(e){
var newvalue = e.target.value.trim();
this.setState({value: newvalue})
}
render() {
return (
<div>
<input ref="name" type="text" defaultValue={this.props.tag} onChange={this.handleChange}/>
<p>{this.state.value}</p>
</div>
)
}
}
InputComp.propTypes = {
tag: React.PropTypes.string.isRequired
}
InputComp.defaultProps = {
tag: '输入点什么东西吧!'
}
const tag = '请输入';
render(<InputComp />,document.getElementById('app'));

上面的小例子来源于react首页上面的一个例子,这里使用react的写法,对es6不是很了解的童鞋可以点这里,关于适用ES5开发react和使用es6开发有什么区别,可以看我的上一篇博客,这里就不做过多的介绍,
接下来,使用webpack进行打包就好了,使用上面举例的config文件,运行命令webpack就可以了,然后将输出文件引入到html文件中就可以看到效果了,当然如果你不想每一次修改都运行一遍命令的话那就使用监听更新模式,运行

1
webpack --progress --watch

当你修改完文件后,其会监听到文件的改变而做出增量的修改打包(当然第一次还是得手动打包)

尾巴

写到这里,这篇文章就算完了,当你一步步完成上面的步骤,你就搭建了一个十分简陋的React开发环境,这里所说的的是简陋,我并没有加载对css文件的打包,这里大家可以根据自己的需求添加不同的loader来完成。作为一个React的初学者,感觉React这一个体系是十分庞大的,庞大到学习React一个多月仍然觉得力不从心,作为一个大三孩纸,每天被上不完的专业课实验课所压着,抽出来学习的时间并不多,再有自身比较懒惰所以…
好吧,不多说了,好好努力吧,毕竟路都是自己选的。

分享到 评论

使用ES6语法写React

写在前面

最近在学习Redux,不可避免使用npm + webpack的方式来写React Component,由于不是很熟悉ES6踩了很多坑,在这里作为笔记写下来,供以后回顾。

例子

惯例,先写一个例子吧

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
	import React, { Component } from 'react';
import { PropTypes } from 'prop-types';

class InputComp extends Component{
constructor(props){
super(props);
this.state = {
name: ''
}
}
handleChange = (e) =>{
var newvalue = e.target.value.trim();
this.setState({[e.target.name]: newvalue})
}
render() {
const { value } = this.state;
return (
<div>
<input name="name" type="text" value={value} defaultValue={this.props.tag} onChange={this.handleChange}/>
<p>{value}</p>
</div>
)
}
}
InputComp.propTypes = {
tag: PropTypes.string.isRequired
}
InputComp.defaultProps = {
tag: '输入点什么东西吧!'
}

上面是使用ES6语法写的一个简单的Component,有过ES6基础的同学对class, ‘extends’关键字等不会陌生,简单来说,这算是js的语法糖了吧。
组件是一个自定义的js对象,在es5中使用React.createClass();在es6中必须继承React.component。

上面的小例子没什么特别的,我们只需要注意两点就好了:
对于props,我们不能使用getDefaultProps()来添加默认的props,在ES6中有一个属性defalutProps,同理属性校验器propTypes,我们也必须写在外面,内容都和以前一样。也可以把这两个属性都写在’类’里面,使用static关键字申明.
对于state,我们不能使用getInitialState()来设置state的初始值,转而在构造器中使用state属性设置值即可。
对于事件而言,this指向的是当前创建的Component对象,所以需要手动绑定。
还有一些其他的差别,比如es6支持解构等
好吧,简单的就写在这里了

好忙好忙,语言都没办法组织了!
加油!

分享到 评论

JS定时器知多少

在JavaScript中,定时器是一个经常被误用且不被大家所熟识的特性。但在构建应用时其却非常有用。
定时器提供了一种让一段代码在一定的时间之后运行的能力。由于JS的单线程特性,其同一时间只能执行一处代码,而定时器跳出了这一性质,以其特有的方式来执行代码。

在浏览器环境当中,window对象下有两组方法,setTimeOut()/clearTimeOut()以及setInterval()/clearInterval()分别用于设置定时器让其在一段时间后执行/让其停止和每隔一段时间就会执行一次/清除该定时器。
比如下面的代码:

1
2
3
4
5
6
setTimeOut(function(){
// do somethign
}, 1000);
setInterval(function(){
// do this code every 1s
}, 1000);

上面的代码分别创建了一个一次执行定时器和循环执行的定时器,每隔一秒钟,function就会被执行一遍

咋一看,setInterval就像周期性执行setTimeout一样,但是他们有很多不同之处。
举个栗子:

1
2
3
4
5
6
setTimeOut(function repect(){
setTimeOut(repect, 100);
}, 100);
setInterval(function(){
// do some thing
}, 100)

上面两段代码的功能几乎是一样的,但是实际上却不是。setTimeOut()代码中执行前一个回调结束后100毫秒甚至更多,才会执行下一个回调
而setInterval()不一样,每隔100毫秒就会尝试执行,不会受到前面回调的影响。那为什么执行一个setTimeOut()执行回调的时间会更长呢?因为js单线程的特性,当计时时间到,回调函数会被放入执行队列排队,并且执行回调是需要时间的,所以执行时间只会大于设置的时间。setInterval()也一样,但有一点,由于其不关注前一个回调执行的情况,如果一直被延迟,那么setInterval()在可用后会无延迟执行。

好吧,写个栗子证明一下上面的结论:

1
2
3
4
5

console.time('测试setTimeOut执行的时间')
setTimeOut(function(){
console.timeEnd('测试setTimeOut执行的时间');
},1000);

执行结果为:
测试setTimeOut执行的时间: 1004.673ms

关于setInterval()的演示和清除定时器的演示这里就不做了。

分享到 评论

浅谈JS闭包

变量的作用域

了解JS的人都知道,在ES6之前JavaScript中只有函数作用域和全局作用域,而没有块级作用域(try…catch是一个例外)。该怎么理解这句话呢?我们先来看一个例子:

1
2
3
4
for(var i=0; i<5; i++){
console.log(`i=${i}`)
}
console.log(i) // 4

当运行完一个for循环后,i=4。由于JS中不存在块级作用域,所以这里在for循环中申明的变量i是一个全局变量,因此可以在外部访问到。
现在我们来看下一个例子:

1
2
3
4
5
6
7
8
9
function init(){
var name = 'limoer';
function sayHello(){
console.log(`hello ${name}`)
}
sayHello();
}
init(); // hello limoer
console.log(name); // undefined

这里我们定义了一个函数,函数中申明了一个局部变量name,并且在函数内部定义了一个内部函数sayHello,这个函数只能在函数init内使用,然而sayHello并没有自己的局部变量,但是其可以访问到函数外部的变量,即其父级函数的name变量

通过上面的两个例子可以清楚的知道,变量的作用域完全是由它在源代码中的位置决定的,并且嵌套的函数也可以访问其外层作用域中的变量。

闭包

闭包和变量的作用域息息相关。现在我们来修改上面的这个例子

1
2
3
4
5
6
7
8
9
function init(){
var name = 'limoer';
function sayHello(){
console.log(`hello ${name}`)
}
return sayHello
}
var sayHelloFunc = init();
sayHello(); // hello limoer

注意修改的地方,我们这次是直接返回这个内部函数,然后在外部执行这个函数。
但是,通常来说,当函数一旦运行完成,其局部变量就不可用了,在这里是当执行了var sayHelloFunc = init();后name应该不可用了。但是实际运行情况是成功访问到了name这个属性。

原因是因为这里sayHelloFunc已经成为了一个闭包。它由两部分组成,返回的函数本身以及创建该函数的环境。
而所谓的环境是由闭包在创建时其作用域内的变量组成的。对于上面的这个例子,这里的变量就是指的name

再看一个闭包的例子

1
2
3
4
5
6
7
8
9
function addSome(num){
return function(y){
console.log(num + y)
}
}
var add10 = addSome(10);
var add1 = addSome(1);
add10(1); // 11
add1(10); // 11

对于上面的这个例子,addSome()做为一个函数工厂产生了两个闭包,它们共享了函数的定义,但是却又保存了不同的环境。

闭包的应用

通过上面的描述,知道闭包其实就是将函数和其作用环境相互关联起来,达到保存变量的目的。

把上面的例子稍微改一下,我们可以把它用到实践中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<button id="toBlue">切换背景为蓝色</button>
<button id="toYello">切换背景为黄色</button>
<button id="toGreen">切换背景成绿色</button>
<script type="text/javascript">
function changeBgColorTo(type){
return function(){
document.body.style.backgroundColor = type;
}
};
var toBlue = changeBgColorTo('blue');
var toYellow = changeBgColorTo('yellow');
var toGreen = changeBgColorTo('green');
document.getElementsById('toBlue').addEventListener('click', toBlue);
document.getElementsById('toYellow').addEventListener('click', toYello);
document.getElementsById('toGreen').addEventListener('click', toGreen);
</script>
</body>
</html>

上面的例子展示了如何使用闭包来定义公共函数,来减少代码的冗余。

一个常见的错误,使用闭包来解决

直接贴代码吧:
html:

1
2
3
4
<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>

js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}

function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];

for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
}
setupHelp();

上面的代码是我们实际开发过程中非常常见的错误。我们使用循环来给每一个输入框绑定一个事件,从而实现当聚焦到不同的输入框是产生不同的输出。
但是,上面的代码显然不能完成这样的工作,因为当循环完成后,此时item已经指向了helpText的最后一项,而给onfocus绑定的是一个匿名函数,当聚焦到某一个输入框时,执行showHelp(item.help)而item早已是helpText中的最后一项了,所以造成了错误.

知道错误后,我们就知道改怎样修改了。我们需要保存运行时的环境,返回一个闭包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];

for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = (function(help) {
return function(){
showHelp(help)
}
})(item.help);
}
}

好了,简单的对于闭包的介绍就到这里了!
想更系统的学习JS点击这里

分享到 评论

踩过React表单的坑后有感

如题所示,最近捣鼓React表单踩坑了,才捣鼓没多少时日,心就变浮躁了,开始看不下去文档,也不谷歌百度直接开码,然后就是各种报错,越报错心越急越得不到解决心情越差,好不容易静下心来搜集资料准备搞定表单,这里先记下来,以备不时只需!

踩过的坑…

直接开写吧:

input标签忘记闭合导致报错

1
2
3
4
5
6
var SimpleComp = React.createClass({
render(){
return <input type="text" placeholder="随便输入点什么吧!">
}
});
ReactDOM.render(<SimpleComp />, document.body)

运行上面的这段代码,发现并页面渲染异常,打开浏览器console一看,下面的出现了错误,并且是下面这样的:

报错图片

清清楚楚明明白白,标签并没有闭合,并且直指9行行最后渲染出了问题,然后我就一直找一直找,没错误啊,可始终还是报错
最后经过各种尝试才知道,input标签要强制闭合的,不然就会转换错误
知道真相的我眼泪掉下来,原来是这么回事啊,平时html写得飞起,细节这些什么的,都没注意哎!
再有,控制台上打印的异常和错误可千万别轻信,以前已经在这个被坑过了

受控与不受控

有过React基础的童鞋都知道,React内部通过props和state来传递属性和状态,其中属性经由组件外属性传入,作用于组件后无法改变
state 产生于组件内部, 通过setState()来改变状态,每一次改变状态,组件将会被重绘
对于表单来说,props和state用得都很频繁,所以在这里提及一下

先来看非受控组件吧,顾名思义就是组件的状态不受控制,从代码上体现出来的就是,一个input不含有value属性

1
2
3
4
5
6
var NotControlComp = React.createClass({
render() {
return <input type="text" defaultValue={this.props.placeholder}/>
}
});
ReactDOM.render(<NotControlComp placeholder='随便输入点什么吧!' />, document.body)

上面的一段代码定义了一个非受控的input,接受用户的输入而改变状态
注意:这里使用到的defaultValue属性作用类似于placeholder属性

好吧,事实上我们使用非受控组件的情况比较多,我们会监听input的onChange时间,通过state来更新状态
下面是一个简单的获取用户输入的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var ExampleComp = React.createClass({
getDefaultProps(){
return{placeholder: '输入吧'}
},
getInitialState(){
return{
val: ''
}
},
handleChange(e){
this.setState({val: e.target.value})
},
render(){
return(
<div>
<input type="text" defaultValue={this.props.val} onChange={this.handleChange}/>
<p>你输入了:{this.state.val}</p>
</div>
)
}
});
ReactDOM.render(<ExampleComp placeholder='随便输入点什么吧!' />, document.body)

那么受控组件就很显而易见了,受控的input定义了一个value属性,并且value部位null

1
2
3
4
5
6
var ControlComp = React.createClass({
render(){
return <input type="text" value="不要尝试修改这个值啦!" />
}
});
ReactDOM.render(<ControlComp />, document.getElementById('app'))

好了,最简单的受控组件就写好了,当你尝试去修改表单中的值的时候,发现根本无法修改
这点明显和我们平时写html不一样,同样的input标签在html中就可以修改但是到了react中就没办法修改了,想过原因吗?
官方文档中给出了答案: 不比HTML,React组件必须在任何时间点呈现视图的状态而不仅仅是在初始化的时候, 好好理解!

其他

  1. 不要通过添加子节点的方式给<textarea>添加内容,应该使用defaultValue或者value属性,避免产生歧义,因为JSX本身就是javascript

  2. 你可以通过selected属性来选中一个下拉栏, 但是为了组件的可操作性,请使用value 或者 defaultValue来代替就像这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var SelectComp = React.createClass({
    render(){
    return(
    <select value='B'>
    <option value='A'>A</option>
    <option value='B'>A</option>
    <option value='C'>A</option>
    </select>
    )
    }
    });
    ReactDOM.render(<SelectComp />, document.body)

    想要非受控组件的话,就使用defaultValue属性好了
    当然了,可以通过传入一个数组来达到多选的目的, 前提是给select添加multiple={true}就可以了

看到这里,也许有人心里还有疑问,受控的组件既然不能修改那到底该怎么用?
对于这个问题,请看下面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var ControlComp = React.createClass({
getInitialState(){
return{value: 0}
},
handleClick(e){

this.setState({value: this.state.value + 1})
},
render(){
return (
<div>
<input type="text" value={this.state.value} />
<button onClick={this.handleClick}>+</button>
</div>
)
}
});
ReactDOM.render(<ControlComp />, document.body)

好了,我们添加了一个button来控制受控组件的值,当然还有很多方式,比如我们可以给受控组件添加一个onChange事件监听

好了,React表单的基本知识就说到这里,感谢那些曾经让我烦躁无比的坑!

分享到 评论

在React中使用mixin

mixin到底为何方神圣?

mixin被理解为‘混入’的意思,
我们知道,如果你经常使用某一段相同的代码的时候,你会把它进行抽象,封装成类或者function,
‘混入’也一样,它可以解决代码段重复的问题。

jade中的Mixins

jade(pug)是一个高性能的模版引擎,它使用javascript实现,并提供给Node使用.
我在使用Jade模版引擎接触到了Mixin, 下面给出一个例子理解下

1
2
3
4
5
6
7
8
9
10
mixin box(styles)
.conponent
-each style in styles
.squire
-var x = styles.indexOf(style) + 1;
a(href='/users/' + x + '/list') #{style}
.container
mixin(['lin', 'limoer'])
.links
mixin(['parents', 'classmates', 'others'])

你可以无视jade的语法,如果你想学习jade,点这里

首先我使用mixin 关键字申明了一个带参数mixin结构,然后依据传入的参数生成了很多url
这个mixin 结构可以在这个模版文件中使用,通过不同的参数来生成url
从上面可以看出,mixin的确可以解决代码重复的问题

React中的Mixin

首先来写一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var TimerComp = React.createClass({
getInitialState: function() {
return {secondsElapsed: 0}
},
tick: function() {
this.stateState({secondsElapsed: this.state.secondsElapsed + 1})
},
componentDidMount: function() {
this.interval = setInterval(this.tick, 1000)
},
componentWillUnmount: function() {
clearInterval(this.interval)
},
render: function(){
return (
<div>
<p>Seconds Elapse {this.state.secondsElapsed}</p>
</div>
)
}
});

简单的说一下,这里定义了一个定时器组件,会随着时间的增加来自动计时
但是一个应用需要多个计时器呢?我们第一时间想到了组件的嵌套,但是组件的嵌套不但会增加组件的复杂程度
而且,想要修改计时器也是比较困难的
好了,解决代码复用的问题,我们可以使用Mixin, 让其混入进其他组件就好了

码起,还是计时器的例子!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var MixinTimerComp = React.createClass({
mixins: [MixinInterval(1000)],
getInitialState: function() {
return {secondsElapsed: 0}
},
onTick: function(){
this.setState({secondsElapsed: this.state.secondsElapsed + 1})
},
render: function(){
return (
<div>
<p>Seconds Elapse {this.state.secondsElapsed}</p>
</div>
)
}
});

这里我们使用了mixins属性,把自定义的MixinInterval对象混入了Timer组件

来看看MixinInterval的实现:

1
2
3
4
5
6
7
8
9
10
var MixinInterval: function(interval) {
return {
componentDidMount() {
this.__interval = setInterval(this.onTick, interval)
},
componentWillUnmount() {
clearInterval(this.__interval)
}
}
};

就这样实现了一个最简单的问题,和最前面的例子做对比,我们发现,只是把组件中一些代码独立出来成为一个mixin对象
但这样做无疑是解决了代码重用的问题,当我们想定义另一个计时器时候,我们只需混入这个mixin代码
而不必关心混入的计时处理对象是怎么实现的

mixin 实现起来非常简单,React也支持多个mixin的混入(在mixins赋值为一个mixin对象数组)
但有一个问题需要注意,在mixin中尝试覆盖state中定义的键的话,React会抛出错误

1
2
3
4
5
6
7
8
9
10
11
React.createClass({
mixins: [{
getInitialState(){
return {cover: 1}
}
}],
getInitialState(){
return {cover: 2}
}
})
// 错误,尝试覆盖cover属性
分享到 评论