process 对象与 Buffer 类

在上一篇博客中,我们一起对于 node 的模块机制有了一点点的了解,当时我们就知道了在 node 中的全局对象是 global 而不是浏览器中的 window 它们有一部分属性方法是相同的,
比如:clearInterval / clearTimeout / setInterval / setTimeout / console。他们的用法和在浏览器中的用法是一样的,接下来我们就来看看那些在 node 中所特有全局属性和方法。

我们先来稍微跑个题,看看与上一个篇博客的中介绍的 模块系统相关 的两个属性,还是可以模块的路径有关:

  • __filename : 返回 当前模块文件解析后的绝对路径
  • __dirname : 返回 当前模块文件所在目录 解析后的绝对路径

注意:它们可以直接使用,但是实际上并非全局的,而是在模块作用域下的

直接在文件内输入,运行即可输出:

1
2
console.log(__filename);
console.log(__dirname);

process 对象

process 对象的属性和方法

process对象是一个全局对象,可以在任何地方都能访问到他,通过这个对象提供的属性和方法,使我们可以对当前运行的程序的进程进行访问和控制

  • process.argv :一个包含命令行参数的数组。第一个元素会是 ‘node’, 第二个元素将是 .Js 文件的名称。接下来的元素依次是命令行传入的参数。
  • process.execPath : 开启当前进程的绝对路径
  • process.env : 返回用户环境信息的对象
  • process.version : 返回node版本信息
  • process.versions : 返回node以及node依赖包版本信息
  • process.pid : 当前进程的pid
  • process.title : 当前进程的显示名称(Getter/Setter)
  • process.arch : 返回当前CPU处理器架构 arm/ia32/x64
  • process.platform : 返回当前操作系统平台
  • process.cwd() : 返回当前进程的工作目录
  • process.chdir(directory) : 改变进程的当前进程的工作目录,若操作失败则抛出异常。
  • process.memoryUsage() : 返回node进程的内存使用情况,单位是byte
  • process.exit(code) : 退出
  • process.kill(pid) : 向进程发送信息
  • 标准输入/输出流(IO):stdin 和stdout提供了操作输入数据和输出数据的方法,我们也通常称为IO操作

这里主要是对于标准输入/输出流(IO)的理解,放上一个中文维基百科的解释:标准输入/输出流,在这里关于 stdout就简单举例 console.log实现:

1
2
3
console.log = function(d) {
process.stdout.write(d + '\n');
};

再看一个stdin的简单示例:

1
2
3
4
5
6
7
process.stdout.write('请输入内容:');
//默认情况下,输入流是关闭的,要监听处理输入流数据,首先要开启输入流
process.stdin.resume();
//用于监听用户的输入数据12
process.stdin.on('data', function(chunk) {
console.log('用户输入了:' + chunk);
});

shell 执行该文件后,输入内容并回车,会把你输入的内容打印出来

Buffer 类

什么 buffer 类?

Buffer 类是一个全局变量类型,用于操作二进制数据流的类。我们在操作文件或者网络数据的时候,其实操作的就是二进制数据流,Buffer 类就是 Node 为了我们更好的操 作二进制数据而创建的类

  • new Buffer(size): Number 类型 配一个新的 buffer 大小是 size 的8位字节.
  • new Buffer(array): Array 类型分配一个新的 buffer 使用一个8位字节 array 数组.
  • new Buffer(str, [encoding]):分配一个新的 buffer ,其中包含着给定的 str 字符串. encoding 编码方式默认是:'utf8'.
    • str : String类型 - 需要存入buffer的string字符串.
    • encoding : String类型 - 使用什么编码方式,参数可选.

Buffer 方法学习

  1. buf.length:这个buffer的bytes大小。注意这未必是这buffer里面内容的大小。length 的依据是buffer对象所分配的内存数值,它不会随着这个buffer对象内容的改变而改变。
  2. buf.write(string, [offset], [length], [encoding]) : 根据参数 offset 偏移量和指定的encoding编码方式,length 长度是将要写入的字符串的bytes大小,将参数 string 数据写入buffer。
    • string : String类型 - 将要被写入 buffer 的数据
    • offset : Number类型, 可选参数, 默认: 0
    • length : Number类型, 可选参数, 默认: buffer.length - offset
    • encoding : String类型, 可选参数, 默认: ‘utf8’
1
2
3
4
5
6
7
var str = 'buffer';
console.log(new Buffer(str));
var buf = new Buffer(6);
buf.write(str,1,3);
console.log(buf);
// <Buffer 62 75 66 66 65 72>
// <Buffer 62 62 75 66 01 00>

这里我们指定了 offset 和 length ,分别为1和3,所以可以看到两次在输出时,62,75,66,这部分是相同的,并且第二次输出它的位置偏移了1个位置。

  1. buf.toString([encoding], [start], [end]):根据 encoding参数(默认是’utf8’)返回一个解码的 string 类型。还会根据传入的参数 start (默认是0)和 end (默认是 buffer.length)作为取值范围。
    • encoding: String类型, 可选参数, 默认: ‘utf8’
    • start: Number类型, 可选参数, 默认: 0
    • end: Number类型, 可选参数, 默认: buffer.length
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var str1 = 'buffer';
var bf1 = new Buffer(str1);
console.log(bf1.toString());
console.log(bf1.toString('utf8',1,4));

var str2 = '二进制';
var bf2 = new Buffer(str2);
console.log(bf2);
console.log(bf2.toString('utf8',1));

// buffer
// uff
// <Buffer e4 ba 8c e8 bf 9b e5 88 b6>
// ��进制

在这里通过例子和输出,就可以发现 toString() 方法看到在截取时 取左不取右,所以第一例子输出了 uff
第二例子则可以说明,中文使用3个字节来存储,所以在偏移量为一时读取到的 ba 8c被输出为乱码,当偏移量为3时,正常。

  1. buf.toJSON():返回一个 JSON表示的Buffer实例。JSON.stringify 将会默认调用来字符串序列化这个Buffer实例。如:{ type: 'Buffer', data: [ 98, 117, 102, 102, 101, 114 ] }
  2. buf.slice([start], [end]):返回一个新的buffer,这个 buffer 将会和老的 buffer 引用相同的内存地址,只是偏移和裁剪了索引,方法类似于数组。 负的索引是从 buffer 尾部开始计算的。
  3. buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd]):进行 buffer 的拷贝,源和目标可以是重叠的。 targetStart 目标开始偏移 和sourceStart源开始偏移 默认都是 0. sourceEnd 源结束位置偏移默认是源的长度 buffer.length.
    如果传递的值是undefined/NaN 或者是 out of bounds 超越边界的,就将设置为他们的默认值。(译者:这个默认值下面有的例子有说明)
    • targetBuffer: Buffer 类型对象 - 将要进行拷贝的Buffer
    • targetStart: Number类型, 可选参数, 默认: 0
    • sourceStart: Number类型, 可选参数, 默认: 0
    • sourceEnd: Number类型, 可选参数, 默认: buffer.length

类/静态方法

  1. Buffer.isEncoding(encoding):用来测试给定的编码字符串,如果给定的编码 encoding 是有效的,返回 true,否则返回 false :Buffer.isEncoding('utf8')
  2. Buffer.isBuffer(obj):测试这个 obj 是否是一个 Buffer.
  3. Buffer.byteLength(string, [encoding]):将会返回这个字符串真实 byte 长度。 encoding 编码默认是: ‘utf8’. 这个和 String.prototype.length 是不一样的,因为那个方法返回这个字符串中有几个字符的数量。
  4. Buffer.concat(list, [totalLength]): 返回一个保存着将传入 buffer 数组中所有 buffer 对象拼接在一起的 buffer 对象。其实就是将数组中所有的 buffer 实例通过复制拼接在一起
    • list : {Array}数组类型,Buffer 数组,用于被连接。
    • totalLength : {Number}类型 上述 Buffer 数组的所有Buffer的总大小。(数组里 Buffer 实例的大小总和)

这里我们再回头来看看,在上半部分中的介绍process 对象,说的标准输入输出流的时候,我们有这样一个例子:

1
2
3
4
5
6
7
process.stdout.write('请输入内容:');
//默认情况下,输入流是关闭的,要监听处理输入流数据,首先要开启输入流
process.stdin.resume();
//用于监听用户的输入数据12
process.stdin.on('data', function(chunk) {
console.log('用户输入了:' + chunk);
});

我们运行这一段代码,在命令行中输入:process,可以看到有下面这样的结果:

1
2
请输入内容:process
用户输入了:process

在这里看来,我们接收到的用户输入 chunk ,就是一个字符串,实际上它是一个buffer对象,我们重写 porcess.stdin 方法,

1
2
3
process.stdin.on('data', function(chunk) {
console.log(chunk);
});

输入同样的内容打印出来的是 <Buffer 70 72 6f 63 65 73 73 0a> ,可以知道在标准输入输出流中,实际上也是进行二进制的数据传输。
在第一个示例中 console.log('用户输入了:' + chunk); 因为 chunk与字符串相链接,所以 它实际上自动调用了 toString() 方法。

这是对于 process 对象与 Buffer 类的简单学习,理解较为粗浅,如有错误之处请指出,谢谢!

本文地址://guowenfh.github.io/2016/10/15/node-global-object/

Node.js 模块系统

什么是模块?

  • 在 node 中,文件和模块是一一对应的,也就是一个文件就是一个模块
  • 每个模块都有自己的作用域
  • 我们使用 var 来申明的一个变量,他并不是全局的,而是属于当前模块下

模块的加载与使用

模块的引入

在 node 中,我们使用 require('模块') 的形式将一个模块引入到我们的当前的文件中来。关于 require 路径的写法也有如下几种:

  • 模块路径可以是一个以 / 开头,表示一个 绝对路径
  • 模块路径以 ./ 开头,表示当前目录出发的 相对路径
  • 模块路径如果没有以 / 或者 ./ 开头,那么这个模块要么是 核心模块 要么是通过 npm 安装在 node_modules 文件夹下的。

看到上面的不同路径写法, 我们就想起来在 web 中引入当前文件夹中的文件时是不需要加上./的, 但在 node 中使用 .// 开头的路径和不使用时,有着很大的差别。

模块的加载机制

  1. 首先按照加载的模块的文件名称进行查找
  2. 如果没有找到,则会在模块文件名称后加上 .js 的后缀,进行查找
  3. 如果还没有找到,则会在文件名称后加上 .json 的后缀,进行查找
  4. 如果还没有,则会在文件名称后加上 .node 的后缀,进行查找
  5. 最终未找到,抛出异常

模块数据的使用

与浏览器中不同 , 在 node 中,每一个模块都有自己的作用域,在模块中使用 var 申明的变量的作用域范围是在该模块内,而不是 node 全局的。
但是你忘记了使用 var 进行变量声明,恭喜你这时和浏览器中忘记使用的效果是一致的。该变量也将挂在全局对象上。
只不过在 node中,全局对象是global,而不是浏览器中的window

下面看一个简单的例子:

我们从两个文件 a.jsb.js 就可以简单的看到 node 的声明变量的作用域。

a.js:

1
2
var a = '这是a声明的变量';
console.log('这是a.js模块');

b.js:

1
2
3
4
require('./2.js');
console.log('bbbb');
console.log(a);
console.log(global.a);

node b.js 输出结果:

1
2
3
这是2.js模块
bbbb
console.log(a); //ReferenceError: a is not defined

至于去掉 var 之后会是怎么样,大家自己试试吧 ^ ^

暴露模块API

module、module.exports、exports

如同上面代码中所展示的,在 nodejs 中不能直接去拿到另一个模块中的变量函数等等。要让模块暴露一个API成为 require调用的返回值,我们就需要通过 module.exports 或者 exports 对外提供模块内部变量的访问。

默认情况下,每个模块都会暴露处一个空对象,如果你想要在该对象上添加属性,那么简单的使用 exports 即可。

看这个例子:

module_a.js:

1
2
3
4
5
6
console.log(exports === module.exports)
exports.name = 'module_a.js'
exports.data = 'this is module_a.js data'
exports.getDate = function() {
return new Date();
}

module_b.js:

1
2
3
4
var a = require('./module_a.js')
console.log(a.name)
console.log(a.data)
console.log(a.getDate())

node module_b.js输出:

1
2
3
4
true
module_a.js
this is module_a.js data
Thu Sep 22 2016 22:23:59 GMT+0800 (CST)

可以看到这里实际上在模块内部 exports 就是对于module.exports的引用,并且在使用require引入模块之后,a 是一个对象。可见外部模块通过 require(模块) 方法加载模块,该函数返回的就是被加载模块的 module.exports 对象

如果这样在 module.exports 对象上一个一个添加属性还满足不了你的需求,你还可以将 module.exports 彻底重写。我们将上面的两个文件改写:

module_a.js:

1
2
3
module.exports = function(text) {
console.log('printf: ' + text);
}

module_b.js:

1
2
var a = require('./module_a.js')
a('ok');

node module_b.js输出:printf: ok

这样有一个需要注意的地方是:直接覆盖 exports 或者 module.exports让模块导出一个值,这样做会破坏 exportsmodule.exports 的引用关系。

这是对于 node 的模块系统的学习,理解有限,如果有错误之处,请指出,谢谢!

本文地址://guowenfh.github.io/2016/10/15/node-module-system/

eslint 使用实践

本文地址://guowenfh.github.io/2016/08/07/eslint-config-npmbace/

在上一篇博客详解 ESLint 规则,规范你的代码中,经过对于 ESLint 规则的学习,当时就决定一定要将 ESLint 使用起来,进行语法校验,来减少我们在程序中可能出现的错误。
但在当时,我们仅仅是把 eslint 的规则过了一遍,并没有把 eslint 的使用过程描述出来,而且要真正的在实际中使用,以及想要在团队中推广开来,需要有一个渐渐的过程,所以反响平平,这篇博客十一个补充,我把一些常见的错误和最佳实践集合到了一起(最最基础的)。为了方便大家使用,还将配置作为一个 npm包 来方便大家使用~顺便也学了一下怎怎么发布

发布一个 eslint 规则的 npm 包

1.确保本地已经安装node和npm

2.申请npm帐号,直接上官网注册一下就好,在输入下面的命令添加把帐号加到本地之前之前,确保你npm的源地址是官方的地址(我把源切成了淘宝结果在发布包的时候发布不了)

1
2
3
4
5
npm adduser

Username: your name
Password: your password
Email: yourmail@gmail.com

3.建立一个存放规则的文件夹,例如我使用的: eslint-config-3introspection(npm包的名称只能小写)
按照正常的流程npm init初始化项目,我们把规则存在在一个 eslintrc.js的文件夹中, 因为我们现在的规则实在是太简单,而且也没考虑要真的做为一个开源的项目来维护,我们在index.js只需要写下一句话module.exports = require('./eslintrc');,我们的package.json除了name,之外也没做什么修改。。。

关于package.json的编写,以及一个最基本的npm包的模样长什么样,我这里不说了,网上很多,也比较简单

准备工作都做好了就是发布了,很简单的一个命令:出现错误请自行网上查找把。。。

1
npm publish

下面简单的说一下使用:

第一步:npm 全局安装 eslinteslint-config-3introspection

如果你还没全局安装eslint的话,先执行下面的命令全局安装eslint, 如果已经安装了请看下一条

1
sudo npm install eslint -g

在全局安装定制的 eslint 包

1
sudo npm install eslint-config-3introspection -g

成功效果如下:

依赖信息的错误不用去管,我们只是需要其中的配置文件而已。

(windows的用户有可能出现无法引入eslint无法引用到全局模块的问题,请将 eslint-config-3introspection,安装在你的工作区目录,以便不同项目都可以引用到)

第二部:在项目的 根目录下 新建一个 .eslintrc 文件

在文件内部添加下面这样的字段

1
2
3
{
"extends": "3introspection"
}

这样就将我们定制的规则导入到项目中了。

第三步:检查编辑器是否已经安装 eslint 插件

下面是sublimeText的:

打开command+shift+p,打开命令窗口,输入 pcl ,回车

pcl

再输入 sublimeLinter,查看是否如下,不对的话一一安装这两个插件:

sublimeLinter

若上面两步成功,重启编辑器,再打开文件,应该就能看到 eslint 规则已经生效了,如下:

错误

这样就可以愉快的使用eslint进行语法校验拉😂😂😂

失败的情况: 打开command+shift+p,打开命令窗口,输入 sublimeLinter 找到这一项,回车后,找到 eslint 开启选项,重启编辑器再查看是不是已经好了。
sublimeLinter

如果规则不不起效果又不知道是什么地方出了问题,参考附录1

附录

附录1:vscode
安装

.eslintrc将{"extends": "3introspection"},引入,重启,不起效果的话,vscode会有很明显的报错信息

还是查找路径的问题,最简单的解决办法,在项目执行一次npm i eslint-config-3introspection,把配置按照到node_modul

其他编辑器都类似

附录2:说明

  1. 在项目目录中 .eslintrc 中的配置,会覆盖引入的配置,有兴趣的可以进行更详细的定制。例如加入以下字段,将项目的全局变量都列在这边。
1
2
3
4
5
// 项目中 定义的全局变量
"globals": {
"Tatami": true,
"$": true
},
  1. 在你的 JavaScript 文件中,用注释指定全局变量,格式如下:
1
/* global var1, var2 */
  1. 可以在你的文件中使用以下格式的块注释来临时禁止规则出现警告:(禁用指定规则,空格后接规则名称,多个名称时逗号隔开)
1
2
3
4
5
6
/* eslint-disable */

alert('foo');// eslint-disable-line 当前行上禁用所有规则:

/* eslint-enable */

  1. 快速查看当前模块所有错误,效果如下:
1
eslint 模块名/文件名
  1. .eslintignore,如同.gitignore一样忽略对于某些文件,文件夹的检查

附录3:eslinrc.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
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
module.exports = {
// 环境定义了预定义的全局变量。更多在官网查看
'env': {
'browser': true,
'node': true,
'commonjs': true,
'amd': true,
},
// JavaScript 语言选项
'parserOptions': {
// ECMAScript 版本
'ecmaVersion': 6,
'sourceType': 'script', // module

},
/**
* "off" 或 0 - 关闭规则
* "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出),
* "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
*/
'rules': {
// //////////////
// 可能的错误 //
// //////////////
'no-console': 0,
// 禁止条件表达式中出现赋值操作符
'no-cond-assign': 2,
// 禁止在条件中使用常量表达式
'no-constant-condition': 2,
// 禁止 function 定义中出现重名参数
'no-dupe-args': 2,
// 禁止对象字面量中出现重复的 key
'no-dupe-keys': 2,
// 禁止重复的 case 标签
'no-duplicate-case': 2,
// 禁止空语句块
'no-empty': 1,
// 禁止在正则表达式中使用空字符集 (/^abc[]/)
'no-empty-character-class': 2,
// 禁止对 catch 子句的参数重新赋值
'no-ex-assign': 2,
// 禁止不必要的布尔转换
'no-extra-boolean-cast': 1,
// 禁止对 function 声明重新赋值
'no-func-assign': 2,
// 禁止在嵌套的块中出现 function 或 var 声明
'no-inner-declarations': [2, 'functions'],
// 禁止 RegExp 构造函数中无效的正则表达式字符串
'no-invalid-regexp': 2,
// 禁止在 in 表达式中出现否定的左操作数
'no-negated-in-lhs': 2,
// !!!!!!!!!!!禁止出现令人困惑的多行表达式
'no-unexpected-multiline': 1,
// 禁止在return、throw、continue 和 break语句之后出现不可达代码
'no-unreachable': 1,
// 要求使用 isNaN() 检查 NaN
'use-isnan': 2,
// 强制使用有效的 JSDoc 注释
'valid-jsdoc': [1, { 'requireReturn': false }],
// 强制 typeof 表达式与有效的字符串进行比较
// typeof foo === "undefimed" 错误
'valid-typeof': 2,

// ////////////
// 最佳实践 //
// ////////////

// 强制把变量的使用限制在其定义的作用域范围内
'block-scoped-var': 1,
// 强制所有控制语句使用一致的括号风格
'curly': [2, 'all'],
// switch 语句强制 default 分支,也可添加 // no default 注释取消此次警告
'default-case': 1,
// 使用 === 替代 == allow-null允许null和undefined==
'eqeqeq': [1, 'allow-null'],
// 不允许在 case 子句中使用词法声明
'no-case-declarations': 2,
// 禁止除法操作符显式的出现在正则表达式开始的位置
'no-div-regex': 2,
// 禁止在没有类型检查操作符的情况下与 null 进行比较
'no-eq-null': 1,
// 禁止扩展原生类型
'no-extend-native': 2,
// 禁止不必要的 .bind() 调用
'no-extra-bind': 1,
// 禁止数字字面量中使用前导和末尾小数点
'no-floating-decimal': 2,
// 禁用不必要的嵌套块
'no-lone-blocks': 1,
// 禁止在循环中出现 function 声明和表达式
'no-loop-func': 1,
// 禁止对原生对象赋值
'no-native-reassign': 2,
// 禁止在非赋值或条件语句中使用 new 操作符
'no-new': 1,
// 不允许对 function 的参数进行重新赋值
'no-param-reassign': [1, { 'props': true }],
// 禁止使用 var 多次声明同一变量
'no-redeclare': 2,
// 禁止自我赋值
'no-self-assign': 2,
// 禁止自身比较
'no-self-compare': 2,
// 禁用一成不变的循环条件
'no-unmodified-loop-condition': 2,
// 禁止不必要的 .call() 和 .apply()
'no-useless-call': 2,
// 禁止不必要的字符串字面量或模板字面量的连接
'no-useless-concat': 2,
// 强制在parseInt()使用基数参数
'radix': 1,
// 要求所有的 var 声明出现在它们所在的作用域顶部
'vars-on-top': 1,
// 要求 IIFE 使用括号括起来
'wrap-iife': [2, 'any'],

// ////////////
// 变量声明 //
// ////////////

// 禁止删除变量
'no-delete-var': 2,
// 禁止 var 声明 与外层作用域的变量同名
'no-shadow': 1,
// 禁止覆盖受限制的标识符
'no-shadow-restricted-names': 2,
// 禁用未声明的变量,除非它们在 /*global */ 注释中被提到
'no-undef': 2,
// 禁止将变量初始化为 undefined
'no-undef-init': 1,
// 禁止将 undefined 作为标识符
'no-undefined': 1,
// 禁止出现未使用过的变量
'no-unused-vars': [1, { 'vars': 'all', 'args': 'none' }],
// 不允许在变量定义之前使用它们
'no-use-before-define': 2,
},
};

详解 ESLint 规则,规范你的代码

因为前几天发现 CSDN上有直接把我文章 复制过去然后标原创的情况,以后会统一在博客头部加入原文链接~

本文个人博客地址

在很久之前就想通过工具来规范自己的代码风格,减少程序出错的概率,如果看过我的一个前端程序猿的Sublime Text3的自我修养,这篇博客的朋友,肯定知道在当时我使用SublimeLinter-jshint插件来规范风格,但是实际上一直懒癌发作也没去看它的文档,使用着它默认的规则。不过现在是时候切换到 ESLint 了!

作为一个有理想有抱负的前端工程师,只是使用默认规则,而不是看完文档了然于心,显然是不行滴 ^_^.. 团队协作时,若是团队的代码风格统一,能够大大减少沟通成本。(其实面试时和老大聊到代码规范,当时就说到用 JSHint ,或者 ESLint 等工具来统一的。。。这也算是我来填一个坑吧~)

好了,前情摘要就到这,我们开始吧!

什么是 ESLint ?

ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性和避免错误。在许多方面,它和 JSLint、JSHint 相似,除了少数的例外:

  • ESLint 使用 Espree 解析 JavaScript。
  • ESLint 使用 AST 去分析代码中的模式
  • ESLint 是完全插件化的。每一个规则都是一个插件并且你可以在运行时添加更多的规则。

以上来自官网。不想再说下去,反正就是一个代码风格检测工具就对了

如何使用

  1. 安装
    1
    npm install -g eslint
  2. 如果你第一次使用 ESLint,你必须使用 –init 命令新建一个配置文件:
    1
    eslint --init
  3. 使用 ESLint 检测任何 JavaScript 文件:
    1
    eslint test.js test2.js
  4. ESLint 中一些规则运行命令它可以帮你自动修复
    1
    eslint test.js --fix

为了可以更直观的反馈,可能更多的会直接安装编辑器插件来进行错误提示,以Sublime 为例:

package control 中 ,先安装在 SublimeLinter ,再安装 SublimeLinter-contrib-eslint 在项目目录下新建 .eslintrc 文件,自定义规则。
重新载入文件应该就生效了(不生效的话 Ctrl+Shift+P 调用命令面板 找到sublimelinter: toggle linter 设置生效就好了),其他的编辑器异曲同工,就不再说了。

关于在如何在构建工具中使用,在这里不做说明(官网有)

规则定义

ESLint 支持几种格式的配置文件,如果同一个目录下有多个配置文件,ESLint 只会使用一个。优先级顺序如下:

  1. JavaScript - 使用 .eslintrc.js 然后输出一个配置对象。
  2. YAML - 使用 .eslintrc.yaml 或 .eslintrc.yml 去定义配置的结构。
  3. JSON -使用 .eslintrc.json 去定义配置的结构,ESLint 的 JSON 文件允许 JavaScript 风格的注释。
  4. Deprecated -使用 .eslintrc,可以使 JSON 也可以是 YAML。
  5. package.json - 在 package.json 里创建一个 eslintConfig属性,在那里定义你的配置。

下面就是规则啦,本人使用了.eslintrc格式,说明也在里面:

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
{
// 环境定义了预定义的全局变量。
"env": {
//环境定义了预定义的全局变量。更多在官网查看
"browser": true,
"node": true,
"commonjs": true,
"amd": true,
"es6":true,
"mocha":true
},
// JavaScript 语言选项
"parserOptions": {
// ECMAScript 版本
"ecmaVersion": 6,
"sourceType": "script",//module
// 想使用的额外的语言特性:
"ecmaFeatures": {
// 允许在全局作用域下使用 return 语句
"globalReturn":true,
// impliedStric
"impliedStrict":true,
// 启用 JSX
"jsx":true
}
},
/**
* "off" 或 0 - 关闭规则
* "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出),
* "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
*/
"rules": {

////////////////
// 可能的错误 //
////////////////

// 禁止条件表达式中出现赋值操作符
"no-cond-assign": 2,
// 禁用 console
"no-console": 0,
// 禁止在条件中使用常量表达式
// if (false) {
// doSomethingUnfinished();
// } //cuowu
"no-constant-condition": 2,
// 禁止在正则表达式中使用控制字符 :new RegExp("\x1f")
"no-control-regex": 2,
// 数组和对象键值对最后一个逗号, never参数:不能带末尾的逗号, always参数:必须带末尾的逗号,
// always-multiline:多行模式必须带逗号,单行模式不能带逗号
"comma-dangle": [1, "always-multiline"],
// 禁用 debugger
"no-debugger": 2,
// 禁止 function 定义中出现重名参数
"no-dupe-args": 2,
// 禁止对象字面量中出现重复的 key
"no-dupe-keys": 2,
// 禁止重复的 case 标签
"no-duplicate-case": 2,
// 禁止空语句块
"no-empty": 2,
// 禁止在正则表达式中使用空字符集 (/^abc[]/)
"no-empty-character-class": 2,
// 禁止对 catch 子句的参数重新赋值
"no-ex-assign": 2,
// 禁止不必要的布尔转换
"no-extra-boolean-cast": 2,
// 禁止不必要的括号 //(a * b) + c;//报错
"no-extra-parens": 0,
// 禁止不必要的分号
"no-extra-semi": 2,
// 禁止对 function 声明重新赋值
"no-func-assign": 2,
// 禁止在嵌套的块中出现 function 或 var 声明
"no-inner-declarations": [2, "functions"],
// 禁止 RegExp 构造函数中无效的正则表达式字符串
"no-invalid-regexp": 2,
// 禁止在字符串和注释之外不规则的空白
"no-irregular-whitespace": 2,
// 禁止在 in 表达式中出现否定的左操作数
"no-negated-in-lhs": 2,
// 禁止把全局对象 (Math 和 JSON) 作为函数调用 错误:var math = Math();
"no-obj-calls": 2,
// 禁止直接使用 Object.prototypes 的内置属性
"no-prototype-builtins":0,
// 禁止正则表达式字面量中出现多个空格
"no-regex-spaces": 2,
// 禁用稀疏数组
"no-sparse-arrays": 2,
// 禁止出现令人困惑的多行表达式
"no-unexpected-multiline": 2,
// 禁止在return、throw、continue 和 break语句之后出现不可达代码
/*
function foo() {
return true;
console.log("done");
}//错误
*/
"no-unreachable": 2,
// 要求使用 isNaN() 检查 NaN
"use-isnan": 2,
// 强制使用有效的 JSDoc 注释
"valid-jsdoc": 1,
// 强制 typeof 表达式与有效的字符串进行比较
// typeof foo === "undefimed" 错误
"valid-typeof": 2,


//////////////
// 最佳实践 //
//////////////

// 定义对象的set存取器属性时,强制定义get
"accessor-pairs": 2,
// 强制数组方法的回调函数中有 return 语句
"array-callback-return":0,
// 强制把变量的使用限制在其定义的作用域范围内
"block-scoped-var": 0,
// 限制圈复杂度,也就是类似if else能连续接多少个
"complexity": [2, 9],
// 要求 return 语句要么总是指定返回的值,要么不指定
"consistent-return": 0,
// 强制所有控制语句使用一致的括号风格
"curly": [2, "all"],
// switch 语句强制 default 分支,也可添加 // no default 注释取消此次警告
"default-case": 2,
// 强制object.key 中 . 的位置,参数:
// property,'.'号应与属性在同一行
// object, '.' 号应与对象名在同一行
"dot-location": [2, "property"],
// 强制使用.号取属性
// 参数: allowKeywords:true 使用保留字做属性名时,只能使用.方式取属性
// false 使用保留字做属性名时, 只能使用[]方式取属性 e.g [2, {"allowKeywords": false}]
// allowPattern: 当属性名匹配提供的正则表达式时,允许使用[]方式取值,否则只能用.号取值 e.g [2, {"allowPattern": "^[a-z]+(_[a-z]+)+$"}]
"dot-notation": [2, { "allowKeywords": false }],
// 使用 === 替代 == allow-null允许null和undefined==
"eqeqeq": [2, "allow-null"],
// 要求 for-in 循环中有一个 if 语句
"guard-for-in": 2,
// 禁用 alert、confirm 和 prompt
"no-alert": 0,
// 禁用 arguments.caller 或 arguments.callee
"no-caller": 2,
// 不允许在 case 子句中使用词法声明
"no-case-declarations":2,
// 禁止除法操作符显式的出现在正则表达式开始的位置
"no-div-regex": 2,
// 禁止 if 语句中有 return 之后有 else
"no-else-return": 0,
// 禁止出现空函数.如果一个函数包含了一条注释,它将不会被认为有问题。
"no-empty-function":2,
// 禁止使用空解构模式no-empty-pattern
"no-empty-pattern":2,
// 禁止在没有类型检查操作符的情况下与 null 进行比较
"no-eq-null": 1,
// 禁用 eval()
"no-eval": 2,
// 禁止扩展原生类型
"no-extend-native": 2,
// 禁止不必要的 .bind() 调用
"no-extra-bind": 2,
// 禁用不必要的标签
"no-extra-label:":0,
// 禁止 case 语句落空
"no-fallthrough": 2,
// 禁止数字字面量中使用前导和末尾小数点
"no-floating-decimal": 2,
// 禁止使用短符号进行类型转换(!!fOO)
"no-implicit-coercion":0,
// 禁止在全局范围内使用 var 和命名的 function 声明
"no-implicit-globals":1,
// 禁止使用类似 eval() 的方法
"no-implied-eval": 2,
// 禁止 this 关键字出现在类和类对象之外
"no-invalid-this":0,
// 禁用 __iterator__ 属性
"no-iterator": 2,
// 禁用标签语句
"no-labels": 2,
// 禁用不必要的嵌套块
"no-lone-blocks": 2,
// 禁止在循环中出现 function 声明和表达式
"no-loop-func":1,
// 禁用魔术数字(3.14什么的用常量代替)
"no-magic-numbers":[1,{ "ignore": [0,-1,1] }],
// 禁止使用多个空格
"no-multi-spaces": 2,
// 禁止使用多行字符串,在 JavaScript 中,可以在新行之前使用斜线创建多行字符串
"no-multi-str": 2,
// 禁止对原生对象赋值
"no-native-reassign": 2,
// 禁止在非赋值或条件语句中使用 new 操作符
"no-new": 2,
// 禁止对 Function 对象使用 new 操作符
"no-new-func": 0,
// 禁止对 String,Number 和 Boolean 使用 new 操作符
"no-new-wrappers": 2,
// 禁用八进制字面量
"no-octal": 2,
// 禁止在字符串中使用八进制转义序列
"no-octal-escape": 2,
// 不允许对 function 的参数进行重新赋值
"no-param-reassign": 0,
// 禁用 __proto__ 属性
"no-proto": 2,
// 禁止使用 var 多次声明同一变量
"no-redeclare": 2,
// 禁用指定的通过 require 加载的模块
"no-return-assign": 0,
// 禁止使用 javascript: url
"no-script-url": 0,
// 禁止自我赋值
"no-self-assign":2,
// 禁止自身比较
"no-self-compare": 2,
// 禁用逗号操作符
"no-sequences": 2,
// 禁止抛出非异常字面量
"no-throw-literal": 2,
// 禁用一成不变的循环条件
"no-unmodified-loop-condition":2,
// 禁止出现未使用过的表达式
"no-unused-expressions": 0,
// 禁用未使用过的标签
"no-unused-labels":2,
// 禁止不必要的 .call() 和 .apply()
"no-useless-call":2,
// 禁止不必要的字符串字面量或模板字面量的连接
"no-useless-concat":2,
// 禁用不必要的转义字符
"no-useless-escape":0,
// 禁用 void 操作符
"no-void": 0,
// 禁止在注释中使用特定的警告术语
"no-warning-comments": 0,
// 禁用 with 语句
"no-with": 2,
// 强制在parseInt()使用基数参数
"radix": 2,
// 要求所有的 var 声明出现在它们所在的作用域顶部
"vars-on-top": 0,
// 要求 IIFE 使用括号括起来
"wrap-iife": [2, "any"],
// 要求或禁止 “Yoda” 条件
"yoda": [2, "never"],
// 要求或禁止使用严格模式指令
"strict": 0,


//////////////
// 变量声明 //
//////////////

// 要求或禁止 var 声明中的初始化(初值)
"init-declarations":0,
// 不允许 catch 子句的参数与外层作用域中的变量同名
"no-catch-shadow": 0,
// 禁止删除变量
"no-delete-var": 2,
// 不允许标签与变量同名
"no-label-var": 2,
// 禁用特定的全局变量
"no-restricted-globals":0,
// 禁止 var 声明 与外层作用域的变量同名
"no-shadow": 0,
// 禁止覆盖受限制的标识符
"no-shadow-restricted-names": 2,
// 禁用未声明的变量,除非它们在 /*global */ 注释中被提到
"no-undef": 2,
// 禁止将变量初始化为 undefined
"no-undef-init": 2,
// 禁止将 undefined 作为标识符
"no-undefined": 0,
// 禁止出现未使用过的变量
"no-unused-vars": [2, { "vars": "all", "args": "none" }],
// 不允许在变量定义之前使用它们
"no-use-before-define": 0,

//////////////////////////
// Node.js and CommonJS //
//////////////////////////

// require return statements after callbacks
"callback-return":0,
// 要求 require() 出现在顶层模块作用域中
"global-require": 1,
// 要求回调函数中有容错处理
"handle-callback-err": [2, "^(err|error)$"],
// 禁止混合常规 var 声明和 require 调用
"no-mixed-requires": 0,
// 禁止调用 require 时使用 new 操作符
"no-new-require": 2,
// 禁止对 __dirname 和 __filename进行字符串连接
"no-path-concat": 0,
// 禁用 process.env
"no-process-env": 0,
// 禁用 process.exit()
"no-process-exit": 0,
// 禁用同步方法
"no-sync": 0,

//////////////
// 风格指南 //
//////////////

// 指定数组的元素之间要以空格隔开(, 后面), never参数:[ 之前和 ] 之后不能带空格,always参数:[ 之前和 ] 之后必须带空格
"array-bracket-spacing": [2, "never"],
// 禁止或强制在单行代码块中使用空格(禁用)
"block-spacing":[1,"never"],
//强制使用一致的缩进 第二个参数为 "tab" 时,会使用tab,
// if while function 后面的{必须与if在同一行,java风格。
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
// 双峰驼命名格式
"camelcase": 2,
// 控制逗号前后的空格
"comma-spacing": [2, { "before": false, "after": true }],
// 控制逗号在行尾出现还是在行首出现 (默认行尾)
// http://eslint.org/docs/rules/comma-style
"comma-style": [2, "last"],
//"SwitchCase" (默认:0) 强制 switch 语句中的 case 子句的缩进水平
// 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always
"computed-property-spacing": [2, "never"],
// 用于指统一在回调函数中指向this的变量名,箭头函数中的this已经可以指向外层调用者,应该没卵用了
// e.g [0,"that"] 指定只能 var that = this. that不能指向其他任何值,this也不能赋值给that以外的其他值
"consistent-this": [1,"that"],
// 强制使用命名的 function 表达式
"func-names": 0,
// 文件末尾强制换行
"eol-last": 2,
"indent": [2, 4, { "SwitchCase": 1 }],
// 强制在对象字面量的属性中键和值之间使用一致的间距
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
// 强制使用一致的换行风格
"linebreak-style": [1,"unix"],
// 要求在注释周围有空行 ( 要求在块级注释之前有一空行)
"lines-around-comment": [1,{"beforeBlockComment":true}],
// 强制一致地使用函数声明或函数表达式,方法定义风格,参数:
// declaration: 强制使用方法声明的方式,function f(){} e.g [2, "declaration"]
// expression:强制使用方法表达式的方式,var f = function() {} e.g [2, "expression"]
// allowArrowFunctions: declaration风格中允许箭头函数。 e.g [2, "declaration", { "allowArrowFunctions": true }]
"func-style": 0,
// 强制回调函数最大嵌套深度 5层
"max-nested-callbacks": [1,5],
// 禁止使用指定的标识符
"id-blacklist":0,
// 强制标识符的最新和最大长度
"id-length":0,
// 要求标识符匹配一个指定的正则表达式
"id-match":0,
// 强制在 JSX 属性中一致地使用双引号或单引号
"jsx-quotes":0,
// 强制在关键字前后使用一致的空格 (前后腰需要)
"keyword-spacing":2,
// 强制一行的最大长度
"max-len":[1,200],
// 强制最大行数
"max-lines":0,
// 强制 function 定义中最多允许的参数数量
"max-params":[1,7],
// 强制 function 块最多允许的的语句数量
"max-statements":[1,200],
// 强制每一行中所允许的最大语句数量
"max-statements-per-line":0,
// 要求构造函数首字母大写 (要求调用 new 操作符时有首字母大小的函数,允许调用首字母大写的函数时没有 new 操作符。)
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
// 要求调用无参构造函数时有圆括号
"new-parens": 2,
// 要求或禁止 var 声明语句后有一行空行
"newline-after-var": 0,
// 禁止使用 Array 构造函数
"no-array-constructor": 2,
// 禁用按位运算符
"no-bitwise":0,
// 要求 return 语句之前有一空行
"newline-before-return":0,
// 要求方法链中每个调用都有一个换行符
"newline-per-chained-call":1,
// 禁用 continue 语句
"no-continue": 0,
// 禁止在代码行后使用内联注释
"no-inline-comments": 0,
// 禁止 if 作为唯一的语句出现在 else 语句中
"no-lonely-if": 0,
// 禁止混合使用不同的操作符
"no-mixed-operators":0,
// 不允许空格和 tab 混合缩进
"no-mixed-spaces-and-tabs": 2,
// 不允许多个空行
"no-multiple-empty-lines": [2, { "max": 2 }],
// 不允许否定的表达式
"no-negated-condition":0,
// 不允许使用嵌套的三元表达式
"no-nested-ternary": 0,
// 禁止使用 Object 的构造函数
"no-new-object": 2,
// 禁止使用一元操作符 ++ 和 --
"no-plusplus":0,
// 禁止使用特定的语法
"no-restricted-syntax":0,
// 禁止 function 标识符和括号之间出现空格
"no-spaced-func": 2,
// 不允许使用三元操作符
"no-ternary": 0,
// 禁用行尾空格
"no-trailing-spaces": 2,
// 禁止标识符中有悬空下划线_bar
"no-underscore-dangle": 0,
// 禁止可以在有更简单的可替代的表达式时使用三元操作符
"no-unneeded-ternary": 2,
// 禁止属性前有空白
"no-whitespace-before-property":0,
// 强制花括号内换行符的一致性
"object-curly-newline":0,
// 强制在花括号中使用一致的空格
"object-curly-spacing": 0,
// 强制将对象的属性放在不同的行上
"object-property-newline":0,
// 强制函数中的变量要么一起声明要么分开声明
"one-var": [2, { "initialized": "never" }],
// 要求或禁止在 var 声明周围换行
"one-var-declaration-per-line":0,
// 要求或禁止在可能的情况下要求使用简化的赋值操作符
"operator-assignment": 0,
// 强制操作符使用一致的换行符
"operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }],
// 要求或禁止块内填充
"padded-blocks": 0,
// 要求对象字面量属性名称用引号括起来
"quote-props": 0,
// 强制使用一致的反勾号、双引号或单引号
"quotes": [2, "single", "avoid-escape"],
// 要求使用 JSDoc 注释
"require-jsdoc":1,
// 要求或禁止使用分号而不是 ASI(这个才是控制行尾部分号的,)
"semi": [2, "always"],
// 强制分号之前和之后使用一致的空格
"semi-spacing": 0,
// 要求同一个声明块中的变量按顺序排列
"sort-vars": 0,
// 强制在块之前使用一致的空格
"space-before-blocks": [2, "always"],
// 强制在 function的左括号之前使用一致的空格
"space-before-function-paren": [2, "always"],
// 强制在圆括号内使用一致的空格
"space-in-parens": [2, "never"],
// 要求操作符周围有空格
"space-infix-ops": 2,
// 强制在一元操作符前后使用一致的空格
"space-unary-ops": [2, { "words": true, "nonwords": false }],
// 强制在注释中 // 或 /* 使用一致的空格
"spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!"] }],
// 要求或禁止 Unicode BOM
"unicode-bom": 0,
// 要求正则表达式被括号括起来
"wrap-regex": 0,

//////////////
// ES6.相关 //
//////////////

// 要求箭头函数体使用大括号
"arrow-body-style": 2,
// 要求箭头函数的参数使用圆括号
"arrow-parens": 2,
"arrow-spacing":[2,{ "before": true, "after": true }],
// 强制在子类构造函数中用super()调用父类构造函数,TypeScrip的编译器也会提示
"constructor-super": 0,
// 强制 generator 函数中 * 号周围使用一致的空格
"generator-star-spacing": [2, { "before": true, "after": true }],
// 禁止修改类声明的变量
"no-class-assign":2,
// 不允许箭头功能,在那里他们可以混淆的比较
"no-confusing-arrow":0,
// 禁止修改 const 声明的变量
"no-const-assign":2,
// 禁止类成员中出现重复的名称
"no-dupe-class-members":2,
// 不允许复制模块的进口
"no-duplicate-imports":0,
// 禁止 Symbol 的构造函数
"no-new-symbol":2,
// 允许指定模块加载时的进口
"no-restricted-imports":0,
// 禁止在构造函数中,在调用 super() 之前使用 this 或 super
"no-this-before-super": 2,
// 禁止不必要的计算性能键对象的文字
"no-useless-computed-key":0,
// 要求使用 let 或 const 而不是 var
"no-var": 0,
// 要求或禁止对象字面量中方法和属性使用简写语法
"object-shorthand": 0,
// 要求使用箭头函数作为回调
"prefer-arrow-callback":0,
// 要求使用 const 声明那些声明后不再被修改的变量
"prefer-const": 0,
// 要求在合适的地方使用 Reflect 方法
"prefer-reflect":0,
// 要求使用扩展运算符而非 .apply()
"prefer-spread":0,
// 要求使用模板字面量而非字符串连接
"prefer-template":0,
// Suggest using the rest parameters instead of arguments
"prefer-rest-params":0,
// 要求generator 函数内有 yield
"require-yield":0,
// enforce spacing between rest and spread operators and their expressions
"rest-spread-spacing":0,
// 强制模块内的 import 排序
"sort-imports":0,
// 要求或禁止模板字符串中的嵌入表达式周围空格的使用
"template-curly-spacing":1,
// 强制在 yield* 表达式中 * 周围使用空格
"yield-star-spacing":2
}
}

更多内容,以及每一项的配置详情可以在 官网查看

Javascript中this与闭包学习笔记

理解 Javascript中的this

基于不同的调用方式this的指向也会有所不同,调用方式大致有如下几种:

调用方式 表达式
构造函数调用 new Foo();
对象方法调用 o.method();
函数直接调用 foo();
call/apply/bind func.call(o);

现在就来看看这些不同的调用模式,this的指向会有怎么样的区别:

构造函数调用模式

1
2
3
4
5
6
7
8
9
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
console.info(this.name);
};
}
var allen = new Person("allen",12);
console.info(allen);//{name: "allen", age: 12};...

通过这样的代码可以很清楚的的看出,构造函数 Person 内部的this指向被创建的调用对象 allen

对象方法调用

通过上面的代码很明显我们创建了一个 allen 对象,其中有一个 sayName 方法, 直接打印 this.name ,现在我们就来看一下它会输出什么。

1
allen.sayName();//allen

很明显,这里函数中的this指向allen对象本身。

函数直接调用

先来看一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function add(a, b) {
return a + b;
}
var myNumber = {
value: 1,
double: function() {
function handle() {
this.value = add(this.value, this.value);
}
handle();
}
};
console.info(myNumber.value);//1
myNumber.double();
console.info(myNumber.value);//1

解析: 首先我们定义了一个全局函数add用于加法运算,接着我们定义了一个对象,有一属性value为1,还有一个方法的目的是让value值乘以二。我们在函数内嵌套定义了一个函数handle,调用add方法并且执行。但是在调用函数值执行之后并没有达到我们想要的效果。这是为什么呢?
如何你打开chrome调试工具并打下断点会发现在handle函数内部的this会指向window!
由此可以发现,在函数内部创建的函数,在这个函数调用时,函数内部的this会指向window而不是外部的函数

下面就就可以看一下常见的两个方案:

1
2
3
4
5
6
7
8
9
10
11
12
// 取消 handle函数的定义,直接在对象的方法中使用this
double2: function() {
this.value = add(this.value, this.value);
},
// 使用变量保存外部函数的this。
double3: function() {
var that = this;
function handle() {
that.value = add(that.value, that.value);
}
handle();
}

使用 call/applybind 手动改变 this

先来看下面这样一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Point(x, y){
this.x = x;
this.y = y;
}
Point.prototype.move = function(stepX, stepY){
this.x += stepX;
this.y += stepY;
};
var p = new Point(0, 0);
console.log(p);//{x: 0, y: 0}
p.move(2,2);
console.log(p);//{x: 2, y: 2}
var circle = {x:1,y:1,r:1};
p.move.apply(circle, [2,1]);
console.info(circle);//{x: 3, y: 2, r: 1}

我们使用Point构造函数可以创建出一个点,在其原型上有一个move方法可以使这个点坐标移动。
之后我们又创建circle对象,有x/y/r属性(把它想象成一个圆),之后我们的需求是:将这个圆的圆心移动,我们就使用了apply来借用move方法,最终将圆移动了位置,最终效果如下图:
apply使用圆示意图

  • function.prototype.apply/call

在上面我们可以看到能实现圆心移动的关键方法就是apply,大致解析如下,p.move是一个函数它的作用就是将一个点移动,然后我们通过apply方法把它借用给circle这个对象。将circle对象上的x/y属性进行变更,分别加2和1,实现了圆心的移动。很明显在这里 apply方法描述的就是一个借用的功能.

为什么会把apply/call放在一起说呢,因为他们的功能并没有实质性的区别。只是在传入参数的时候,apply需要将参数以数组的形式进行传递,而call是将需要传入的参数一个一个跟在借用的对象后。下面一个小例子足以说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
function sum(a, b) {
return a + b;
}
function call1(num1, num2) {
return sum.call(this, num1, num2);
}
function apply1(num1, num2) {
// return sum.apply(this,[num1,num2])
return sum.apply(this, arguments);//利用函数的arguments对象

}
console.info(call1(10, 20));//30
console.info(call1(5, 10));//15

可以看到我们在后两个函数中,可以直接使用sum方法。

  • function.prototype.bind

这里来看看ES5引入的bind,又有什么不同,还是和上面类似的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Point(x, y){
this.x = x;
this.y = y;
}
Point.prototype.move = function(stepX, stepY){
this.x += stepX;
this.y += stepY;
};
var p = new Point(0, 0);
var circle = {x:1,y:1,r:1};
var circleMove = p.move.bind(circle,2,2);
circleMove();
console.info(circle);//{x: 3, y: 3, r: 1}
circleMove(3,4);
console.info(circle);//{x: 5, y: 5, r: 1}

这里我使用了和 call 类似的调用方法,但是显然 bind 和 call 不一样,使用 bind 时,它会将我们绑定 this 后的函数引用返回,然后手动执行。可以看到的是,因为在这里我们绑定的对象的后面传入了x/y两个值,所以执行后坐标立即变化,并且在后来手动设置偏移量时也不再起到效果。
这样的相比于apply立即执行的好处时,我们可以使用定时器,例如:setTimeout(circleMove,1000),延迟一秒后移动。

当然,每次只能移动固定的值也不是一件很好的事情,所以我们在使用 bind 的时候常常不会设置其默认参数, var circleMove2 = p.move.bind(circle,);,之后在执行函数时,再将参数传入circleMove(3,4);,这样就可以实现每次自定义偏移量了

这又引出了call/applybind的作用的另外一种说法: 扩充作用域

1
2
3
4
5
6
7
8
9
10
11
12
var color = 'red';
var obj = {color:'blue'};
var obj1 = {color:'black'};
var obj2 = {color:'yellow'};

function showColor(){
console.info(this.color);
}
showColor();//red
showColor.call(obj);//blue
showColor.apply(obj1);//black
showColor.bind(obj2)();//yellow

可以看到这里都实现了一样的效果。值得说的是使用callaplly()来扩充作用域的最大好处就是对象不需要与方法有任何耦合关系。

闭包

简单定义

先来看这样的一段代码,在chrome中找到Scope列表,可以看到,在作用域链上我们已经创建了一个闭包作用域!

1
2
3
4
5
6
7
8
(function() {
var a = 0;
function b() {
a = 1;
debugger;
}
b();
})();

闭包一个最简单的定义就是:闭包就是说在函数内部定义了一个函数,然后这个函数调用到了父函数内的相关临时变量,这些相关的临时变量就会存入闭包作用域里面.这就是闭包最基础的定义

保存变量

下面就来看一下闭包的一个基本特性保存变量

1
2
3
4
5
6
7
8
9
function add(){
var i = 0;
return function(){
console.info(i++);
};
}
var f = add();
f();//1
f();//2

我们定义了一个 add 方法,执行完毕后会返回一个函数,接着我们就把这个函数赋值给了变量f,由于 add 函数也是返回一个函数,在我们每一次执行f()的时候,它引用了add内的变量i,并且保存在自己的闭包作用域内,所以一直输出执行的话,也会累加输出。

小tips

需要我们记住的是 每次函数调用的时候创建一个新的闭包

1
2
3
var fun = add();
fun();//1
fun();//2

我们再来通过简单的例子看看另一个注意的地方:

1
2
3
4
5
6
7
8
9
10
function test(){
var a = 0;
var ff = function(){
console.info(a);
};
a = 1214;
return ff;
}
var b = test();
b();//1214

执行的结果是1214,从这里我们可以看到 闭包中局部变量是引用而非拷贝,其实这样的改变发散开来我们就可以知道,即使在这里变量 a 未在函数 ff 之前定义,而是var a = 1214;我们同样会得到同样的结果

点击li显示对应编号案例解析

其实上面这些我是很晕的,来看一个我们实际在前端编程过程中经常遇到的问题。
我们有一个列表,分别为1/2/3,我们的需求是在点击不同的数字时,也能把它对应的编号弹出来。然后我们洋洋洒洒写下了这样的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<ul id="#list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
(function() {
var oLi = document.getElementById("#list").getElementsByTagName("li");
for (var i = 0; i < oLi.length; i++) {
oLi[i].onclick = function() {
alert(i);
};
}
})();
</script>

一运行,发现懵了。怎么弹出来的都是3?不对啊,我不是用循环将值都传进去了吗?

如果你确实理解了上面的 闭包中局部变量是引用而非拷贝这一节中的两个案例的话,那么就应该能了解一些。

解析:在这里我们为每一个li的onclick事件 绑定了一个匿名函数,这个匿名函数就形成了一个闭包。这些匿名函数并不立即执行,而是在点击对应的li的时候才回去执行它。
而在这时就和上面的a = 1214;这个例子一样,此时的循环早已结束,i 就等于oLi.length,在我们点击不同的li时,闭包中引用的其实都是引用的同一个变量i自然弹出来的都是3,(这里不理解引用的都是用一个i的话,可以将alert(i);替换成alert(i++);,再到浏览器上去进行测试)

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(function() {
var oLi = document.getElementById("#list").getElementsByTagName("li");
for (var i = 0; i < oLi.length; i++) {
oLi[i].onclick = (function(j) {
return function(){
alert(j);
};
})(i);
}
})();
/*
(function() {
var oLi = document.getElementById("#list").getElementsByTagName("li");
for (var i = 0; i < oLi.length; i++) {
(function(j){
oLi[i].onclick= function(){
alert(j);
};
})(i);
}
})();
*/

可以看到这里给出了两个简单的写法,但实际上除了写法不同之外、闭包包含范围、内容也不太一样(有兴趣的可以打开chrome调试工具看看),但是达到的效果是一样的。这样我们就为每个lionclick事件的匿名函数,都保存下了自己闭包变量。就可以实现在点击每个li的时候弹出对应的标号了。(还可以将alert(j);替换成alert(j++);欣赏一下点击不同li时的累加效果)

当然如果你只是想要记住一些标号这么简单的事情,其实还可以将变量保留于元素节点上,也能达到一样的效果,如下:

1
2
3
4
5
6
7
8
9
(function() {
var oLi = document.getElementById("#list").getElementsByTagName("li");
for (var i = 0; i < oLi.length; i++) {
oLi[i].flag = i;
oLi[i].onclick = function() {
alert(this.flag);
};
}
})();

如果有错误之处,请指正。谢谢!

JavaScript函数学习笔记

函数

函数式一块javascript代码,定义一次,可以被多次调用与执行,JS中的函数也是对象,所以JS函数可以像其他对象那样操作,和传递,所以也称函数对象
函数的参数列表相当于函数的人口,return相当于函数的出口,函数本身就是一种数据类型,函数是可以嵌套定义的。

函数基础

定义函数的方式

有三种方式可以定义函数。并且有着如下区别:

定义方式: function 语句(函数声明) Function 构造函数 函数表达式
解析时机: 优先解析(声明前置) 顺序解析 顺序解析(声明提前,赋值不提前)
允许匿名: 有名 匿名 匿名
形式: 句子 表达式 表达式
性质: 静态 动态 静态
作用域: 具有函数的作用域 顶级函数(顶级作用域) 具有函数作用域

主要有下面两个需要注意的地方:

  • 理解Function 构造函数的顶级作用域,直接看代码就好:很直观就可以表示出来
1
2
3
4
5
6
7
var a = 1;
function test() {
var a = 2;
var func = new Function("console.info(a)");
func();
}
test();//1;
  • 函数表达式的多种创建方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 最简单的形式,将函数赋值给一个变量
var add = function(a,b){
//do ..
}
// 立即执行匿名函数表达式IIFE
(function() {
//do ..
})();
// 将函数当成返回值
return function(){
//do ..
}
//命名式函数表达式NFE
var add = function foo(a,b){
// do..
// 只在foo函数内部可以使用foo这个名称(用途如:递归调用)(在新版浏览器下可用)
// 外部访问不到foo这个函数名
}

函数的参数

在JS中函数的参数分为形式参数和实际参数两个概念

1
2
3
4
5
6
7
8
function test(a,b,c,d) {
console.log(test.length)//4
return a+b+c;
}
console.info(test(10,20,30))//60
console.log(test.length)//4
console.log(arguments.length);//3,表示实际接受的参数个数
console.log(arguments[0]);//10,传进来的第一个参数===a

函数内部 ,JS使用了一个特别的变量arguments类数组对象(以后再来说这个问题),用来接受传入函数的实际参数列表。

1
2
3
4
5
function arg(a,b,c){
arguments[0] = 1;
console.info(a,b,c)
}
arg(143,456,6)//1 456 6

普通模式下可以直接对于arguments对象进行更改,上面的代码就是一个很直观的例子,我们直接更改了第一个参数的值,但建议不要去试图更改arguments对象的属性,不符合规范。
注:严格模式下arguments对象是实参的一个副本,所以上面的改动不会生效(自行尝试)

构造函数

什么是构造函数?

其实构造函数只是普通函数的一个变种,它可以当成普通的函数方式调用,也能通过new关键字来调用。 在Javascript中通过 new 关键字方式调用的函数都被认为是构造函数。
在构造函数内部( 也就是被调用的函数内) this 指向新创建的对象Object。 这个新创建的对象的 prototype 被指向到构造函数的 prototype
如果被调用的函数没有显式的 return 表达式,则隐式的会返回 this 对象(也就是新创建的对象)。

这就来看看实际中不同情况下的区别:

  1. 使用new关键字吗,不指定return语句
1
2
3
4
5
6
7
8
9
function Person() {
this.father = 'allen';
}
Person.prototype.getFatherName = function(){
console.info(this.father);
};
var hong = new Person();
console.info( new Person());//Person {father: "allen"}
hong.getFatherName();//allen
  1. 使用new关键字,并且指定return语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person() {
this.father = 'allen';
return "1";
}
Person.prototype.getFatherName = function(){
console.info(this.father);
};
var hong = new Person();
console.info( hong);//Person {father: "allen"}
hong.getFatherName();//allen

//把return的值换成一个对象:
//return "1";----------->return {a:1};或者return new String("12");
var hong = new Person();
console.info( hong);//Object {a: 1}
hong.getFatherName();//[脚本错误]
  1. 不使用new关键字,当成普通函数直接调用
1
2
3
4
5
6
7
8
9
10
11
12
13
function Person() {
this.father = 'allen';
return function(){
return {}
};
}
Person.prototype.getFatherName = function(){
console.info(this.father);
};
var bai = Person();
console.info(bai);//undefined
console.info(father);//allen
bai.getFatherName();//[脚本错误]

通过这三段不同情况的示例代码,应该可以发现一些区别:

  1. 使用new关键字吗,不指定return语句时,将隐式的会返回 this 对象(返也就是新创建的对象)。
  2. 使用new关键字,并且指定return语句时,需分为两种情况:
    • 返回值为标准类型,显式的 return 表达式将不会影响返回结果
    • 返回值为对象,将直接返回你显式设置的对象
  3. 不使用new关键字,当成普通函数直接调用时,this指向全局对象 window,所以内部this指定的属性与方法,全部都暴露到全局,导致全局变量污染。prototype上的方法不起效果(当然你要是这样的形式指定了return返回的内容,它自然会原样返回啦!)

工厂模型

为了不使用new关键字,构造函数必须显式的返回一个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function createPerson(name, age, sex) {
var obj = {};
obj.name = name;
obj.age = age;
var private = 2;//私有外部不可访问
obj.sayName = function(){
console.info(this.name)
}
obj.getPrivate = function() {
return private;
}
return obj;
}
var p1 = createPerson("小红",21,"女")
var p2 = createPerson("小国",22,"男")
console.info(p1.name);//小红
console.info(p2.age);//22
p2.sayName();//小国

这就是一个简单的工厂模型,使用或者不使用new关键字没有功能性的区别。这样的方式比起 new的调用方式不容易出错,并且可以充分利用 私有变量带来的便利, 但是也有下面这样的问题

  1. 占用更多的内存,因为新创建的对象不能共享原型上的方法。
  2. 为了实现继承,工厂方法需要从另外一个对象拷贝所有属性,或者把一个对象作为新创建对象的原型。

这一篇博客最主要是对于函数学习的笔记,不过用自己的话来描述总描述的还不够清晰。
有错误之处请指出!

使用搜索:谷歌必应百度