JavaScript笔记--语法
声明
本系列的笔记来自于: 阮一峰老师的JavaScript教程
基本语法
变量
变量的声明与赋值:
var a = 1; a = 'hello';
如果不写 var, 程序也有效, 但这种做法会在不知不觉中创建全局变量.
注释
单行注释: / 多行注释: /* ... *
区块
对于 var 命令来说, JavaScript的区块不构成单独的作用域. 如:
{
var a = 1;
}
console.log(a); // 1
if语句
与C++/Java相同.
switch语句
与C++/Java相同.
三目运算符
JavaScript中存在三目运算符.
循环
for, while, do...while 使用方法与C++/Java一样.
JavaScript有关键字 break 和 continue.
数据类型
JavaScript的数据类型有6种.
- 数值(number): 整数和小数
- 字符串(string): 文本
- 布尔值(boolean)
- undefined: 表示未定义
- null: 空值
- 对象(object): 各种值组成的集合
typeof运算符
JavaScript有三种方法可以确定一个值是什么类型: typedef运算符, instanceof运算符, Object.prototype.toString方法. 这边先介绍typeof运算符.
typeof 123; // "number"
typeof '123'; // "string"
typeof false // "boolean"
function f() {}
typeof f // "function"
应用:
if (typeof v === "undefined") { ... }
布尔值
以下六个值在转换为布尔值时, 被转为false:
- undefined
- null
- false
- 0
- NaN
- ""或''
数值
整数和浮点数
在JavaScript内部, 所有数字都是以64位浮点数形式储存, 即使整数也是如此. 因此, 1和1.0是相同的:
1 === 1.0 // true
由于浮点数不是精确的值, 所以涉及小数的比较和运算, 需要特别小心:
0.1 + 0.2 === 0.3 // false
数值精度
首先介绍 IEEE 754标准, 在64位的情况下:
- 第1位: 符号位
- 第2~12位(共11位): 指数部分
- 第13~64位(共52位): 小数部分(即有效数字)
符号位决定一个数的正负, 指数部分决定数值大小, 小数部分决定数值精度.
指数部分共11个二进制位, 大小范围在0~2047之间. IEEE 754 规定, 如果指数部分的值在区间(0, 2047), 则小数部分的第1位默认是1, 且不保存在64位浮点数中. 所以, JavaScript提供的有效数字最长为53个二进制位. 即:
$$(-1)^{符号位} \times 1.xx...xx \times 2^{指数部分}$$
精度最多只能到53位二进制位, 这意味着, 绝对值小于等于2的53次方的整数, 都可以精确表示. 大于2的53次方的数值, 无法保持精度.
数值范围
指数部分为11个二进制位, 可以表示的最大值是2047, 还要分一半表示负数, 因此, JavaScript能够表示的数值范围为 \(2^{1024}\) 到 \(2^{-1023}\) 之间(开区间), 超出这个范围的数无法表示.
数值表示法
十进制, 十六进制(0xFF), 科学计数法(123e3)
与数值相关的全局方法
- parseInt(): 将字符串转成整数
- parseFloat(): 将字符串转成浮点数
- isNaN(): 判断是否为NaN
- isFinite(): 判断是否为正常数值
字符串
字符串就是0个或多个排在一起的字符, 放在单引号或双引号之中.
单引号字符串内部, 可使用双引号; 双引号字符串内部, 可使用单引号.
字符串拼接使用+.
字符串与数组
字符串可以看成是数组, 因此可以使用数组的方括号运算符, 用来返回某个位置的字符, 索引从0开始. 无法改变字符串之中的单个字符. length 属性返回字符串长度.
var s = 'hello'; s[0] // "h" s[1] = 'a'; s // "hello" s.length // 5
对象
简单说, 对象就是一组"键值对"的集合, 是一种无序的复合数据集合.
var obj = {
foo: 'Hello',
bar: 'World'
};
对象的每一个键名又称为"属性", 它的"键值"可以是任何数据类型. 如果一个属性的值为函数, 通常把这个属性称为"方法", 它可以像函数那样调用.
var obj = {
p: function (x) {
return 2 * x;
}
};
obj.p(1) // 2
如果属性的值是另一个对象, 就形成了链式引用:
var o1 = {};
var o2 = {bar: 'hello'};
o1.foo = o2;
o1.foo.bar // "hello"
该例子还说明, 属性可以动态创建, 不必在对象声明时就指定.
对象的引用
如果把一个变量赋值为一个对象, 则它们都是这个对象的引用, 指向同一个内存地址, 修改其中一个变量, 会影响到其他所有变量. 如果把该变量又赋值为另一个值, 不会对原有对象造成影响.
属性的操作
读取属性
两种方法, 点运算符, 或方括号运算符.
var obj = {
p: 'Hello World'
};
obj.p
obj['p']
注意: 属性名必须放在引号中间.
属性的赋值
var obj = {};
obj.foo = 'Hello';
obj['bar'] = 'World';
查看所有属性
Object.keys(obj);
delete命令
delete obj.p
in运算符
用于检查对象是否包含某个属性.
for ... in 循环
用来遍历一个对象的全部属性. 但有些属性是不可遍历的, 如toString在对象创建之初就存在, 但是不可遍历.
数组
数组是按次序排列的一组值, 下标从0开始. 数组的赋值方法:
// 法1 var arr = ['a', 'b', 'c']; // 法2 var arr = []; arr[0] = 'a';
任何类型的数据都可以放入数组:
var arr = [
{a: 1},
[1, 2, 3],
function () { return true; }
];
数组的本质
数组本质上是一种特殊的对象. typeof 运算符会返回数组的类型是 object. 数组的特殊性在于, 它的键名(属性)是按次序排列的一组整数(0, 1, 2, ...). 如:
var arr = ['a', 'b', 'c']; Object.keys(arr) // ["0", "1", "2"]
Object.keys() 返回数组的所有键名.
JavaScript规定, 对象的键名一律为字符串, 所以, 数组的键名其实也是字符串, 之所以可以用数值读取, 是因为非字符串的键名会被转为字符串.
对象读取成员有两种方法: 点结构(object.key)和方括号结构(object[key]). 但是, 对于数值的键名, 不能使用点结构:
var arr = [1, 2, 3]; arr.0 // SyntaxError
length属性
数组的 length 属性, 返回数组的成员数量.
数组的 length 属性是动态可变的, 如果你想清空一个数组, 就把 length 属性设为0.
有一点需要注意, 数组的 length 属性是键名最大的数字加1, 这就需要数组的键名是整数. 如果我们把数组的键名改成字符串或小数, 则 length 属性保持不变:
var arr = []; arr['p'] = 'abc'; a.length // 0 a[2.1] = 'abc'; a.length // 0
使用 delete 命令删除一个数组成员, 会形成空位, 不影响 length 的值:
var a = [1, 2, 3]; delete a[1]; a[1] // undefined a.length // 3
遍历数组
遍历数组的键名, 使用: in
遍历数组的值, 使用: for ... in(不推荐), while, for, forEach
函数
函数声明
声明函数有三种方法.
function命令
function print (s) {
console.log(s);
}
函数表达式
var print = function (s) {
console.log(s);
};
print(1)
注意, 这种写法在函数表达式后面有分号结尾.
也可以在采用函数表达式声明函数时, function 命令后面带上函数名. 但是这个函数名只在函数内部有效, 在函数外部无效. 建议加上函数名, 因为这样可以在程序出错时, 通过函数调用栈定位.
var print = function x () {
console.log(typeof x);
};
print()
Function构造函数
var add = new Function (
'x',
'y',
'return x + y'
);
// 等同于
function add (x, y) {
return x + y;
}
不推荐这种方法.
函数的重复声明
后面的声明会覆盖前面的声明.
函数作为参数
function add(x, y) {
return x + y;
}
function a(op) {
return op;
}
a(add)(1, 1) // 2
函数的属性和方法
- name: 返回函数的名字 (f1.name)
- length: 返回参数个数 (f1.length)
- toString(): 返回函数内部代码 (f1.toString())
参数
如果在定义时提供了两个参数, 而调用时使用了更多或更少参数, 都不会报错.
参数的传递方式
如果参数是原始数据类型(数值, 字符串, 布尔值), 则采用值传递, 这意味着, 在函数体内修改参数值, 不会影响到函数外部.
var p = 2;
function f(p) {
p = 3;
}
f(p);
p // 2
如果参数是复合类型的值(数组, 对象, 其他函数), 则采用地址传递, 也就是说, 传入函数的原始值的地址, 因此, 在函数内部修改参数, 会影响到原始值.
var obj = { p:1 };
function f(o) { o.p = 2; }
f(obj);
obj.p // 2
注意, 如果函数内部修改的, 是不参数对象的某个属性, 而是替换掉整个参数, 则不会影响到原始值.
var obj = [1, 2, 3];
function f(o) { o = [2, 3, 4]; }
f(obj);
obj // [1, 2, 3]
解析: 形式参数 o 的值一开始是参数 obj 的地址, 重新对 o 赋值, 会导致 o 指向另一个地址, 保存在原地址上的值当然不受影响.
arguments对象(可变参数个数)
arguments 对象包含了函数运行时的所有参数. 使用方法: arguments[index].
function f() {
return arguments.length;
}
f(1, 2, 3) // 3
f(1) // 1
闭包
闭包的由来: 由于变量存在作用域, 所以函数内部可以直接读取全局变量, 而函数外部无法读取函数内部声明的变量. 要想在函数外部获得函数内部的局部变量, 正常情况下是办不到的, 但可以变通方法.
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
在上面的代码中, 函数 f1 的返回值就是函数 f2, 由于 f2 可以读取 f1 的内部变量, 所以就可以在外部获得 f1 的内部变量了.
闭包就是函数 f2, 即能够读取其他函数内部变量的函数. 在本质上, 闭包是把函数内部和外部连接起来的一座桥梁.
闭包的最大用处有两个, 一是可读取函数内部的变量, 二是让这些变量始终保持在内存中, 即闭包可以使它诞生环境一直存在. 如:
function createIncrementor(start) {
return function() {
return start++;
};
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7
通过闭包, start 的状态被保留了, 每一次调用都是在上一次调用的基础上进行计算.
因为 inc 始终在内存中, 而 inc 的存在依赖于 createIncrementor, 因此也始终在内存中, 不会在调用结束后, 被垃圾回收机制回收.
数据类型转换
强制转换
- Number(): 强制转换为数值, 比
parseInt()严格 - String(): 将任意类型的值转化成字符串
- Boolean(): 将任意类型的值转化为布尔值
错误处理机制
Error实例对象
JavaScript解析或运行时, 一旦发生错误, 引擎就会抛出一个错误对象. JavaScript原生提供Error构造函数, 所有抛出的错误都是这个构造函数的实例.
var err = new Error('出错了');
err.message // "出错了"
抛出Error实例对象后, 整个程序就中断在发生错误的地方, 不再往下执行.
原生错误类型
- SyntaxError: 解析代码时发生的语法错误
- ReferenceError: 引用一个不存在的变量时发生的错误
- RangeError: 一个值超出有效范围时发生的错误
- TypeError: 变量或参数不是预期类型时发生的错误
- URIError: URI相关函数的参数不正确时抛出的错误
- EvalError: eval函数没有被正确执行时抛出的错误
自定义错误
function UserError(message) {
this.message = message || '默认信息';
this.name = 'UserError';
}
UserError.prototype = new Error(); // 继承自Error对象
UserError.prototype.constructor = UserError;
// 用法
new UserError('这是自定义的错误');
throw语句
throw 的作用是手动中断程序执行, 抛出一个错误.
if(x < 0) {
throw new Error('x 必须为正数');
}
对于JavaScript引擎来说, 遇到 throw 语句, 程序就中止了.
try ... catch 结构
try ... catch 结构允许对错误进行处理, 选择是否往下执行.
try {
throw "出错了";
} catch(e) {
console.log(111);
}
console.log(222);
try 代码块抛出的错误, 如果被 catch 代码块捕获后, 程序就会继续向下执行.
可以捕获不同类型的错误:
try {
foo.bar();
} catch(e) {
if(e instanceof EvalError) {
console.log(e.name + ":" + e.message);
} else if(e instanceof RangeError) {
console.log(e.name + ":" + e.message);
}
// ...
}
finally 代码块
try ... catch 结构允许在最后添加一个 finally 代码块, 表示不管是否出现错误, 都必须在最后运行的语句, 即使在 try 代码块中有 return 语句, 也还会去执行 finally.
Generated by Emacs 25.x(Org mode 8.x)
Copyright © 2014 - Pinvon - Powered by EGO