Javascript笔记
Javascript笔记
Javascript简介
Javascript是世界上最流行的脚本语言,因为你在电脑、手机、平板上浏览的所有的网页,以及无数基于HTML5的手机App,交互逻辑都是由JavaScript驱动的。
简单地说,JavaScript是一种运行在浏览器中的解释型的编程语言。
JavaScript快速入门
有一位先哲说的好啊,学习一门语言先看文档,看文档前先看快速入门。
基本语法
1. 语法规则
首先来介绍一下Javascript的语法规则。
- Js语法和Java类似,每个句子以
;
结尾,语句块用{···code···}
。(但是,JavaScript并不强制要求在每个语句的结尾加;
,浏览器中负责执行JavaScript代码的引擎会自动在每个语句的结尾补上;
) - Js语法的注释为
//
,/* … */
2. 基本数据类型
Number
JavaScript不区分整数和浮点数,统一用Number表示,以下都是合法的Number类型。
1
2
3
4
5
6100; //整形
2.71828; //浮点型
-100; //负数
1.23e3; //科学计数法
NaN; //Not a Number。无法计算时使用NaN表示。
Infinity; //无限大。当数值超过了Js的Number所能表达的最大值时,则表示为Infinity四则运算和Python相似。即(
+
,-
,**
,/
,%
)但是在这里提一下整除。1
2
3
4parseInt(a / b); //丢弃小数部分,保留整数部分
Math.ceil(a / b); //向上整除 4/3=2
Math.floor(a / b); //向下整除 4/3=1
Math.round(a / b); //四舍五入字符串
JavaScript的字符串就是用
''
或""
括起来的字符表示。但是这里有许多的特例。如果
'
本身也是一个字符,那就可以用""
括起来。如果字符串内部既包含'
又包含"
,可以用转义字符\
来标识。exp:
'I\'m \"OK\"!';
然后需要了解转义字符
\n
,\t
,\\
。ASCII字符可以以
\x##
形式的十六进制表示,例如:1
'\x41'; // 完全等同于 'A'
还可以用
\u####
表示一个Unicode字符:1
'\u4e2d\u6587'; // 完全等同于 '中文'
由于多行字符串用
\n
写起来比较费事,所以ES6标准新增了一种多行字符串的表示方法,用反引号`...`
表示。下面介绍一下字符串的操作
字符串支持拼接。
string_A + string_B
字符串支持模板字符串。(ES6支持)
1
2
3var name = '小明';
var age = 20;
var message = `你好, ${name}, 你今年${age}岁了!`; //使用$引用变量(需要使用反引号进行引用)1
2
3
4
5
6
7
8
9
10
11var s = "Hello World!";
s.length; //返回字符串长度
s[0]; //H
s[11]; //!
s[12]; //undefined
s.toUpperCase(); //转大写
s.toLowCase(); //转小写
s.indexOf("world"); //6
s.indexOf("World"); //-1
s.substring(0, 5); //从索引0~5(不包含5)hello
s.substring(7); //索引7到最后 world!布尔值
布尔值的解释比较简单,和一般编程语言都是类似的。这里说一下几个符号
1
2
3&& // and
|| // or
! // not接下来就是比较重要的比较运算符了。
当我们对数据类型进行比较的时候(任意数据类型),我们会得到一个布尔值。例如
1
2
3
4
5
62>5; //false
2<5; //true
1==1; //true
// 特别的
false == 0; //true
false === 0; //false不难看出,出现了特例1
==
和===
。==
比较时,它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果===
比较时,它不会自动转换数据类型,如果数据类型不一致,返回false
,如果一致,再比较。因此,绝大部分时间请使用
===
进行比较特例2:
NaN
1
NaN === NaN; //false
因此,唯一可以判断
NaN
的方法是通过isNaN()
函数。特例3:浮点数比较
请看代码
1
1/3 === (1 - 2/3); //false
这个特性和C语言一样。因为计算机无法精确表示无限循环小数。
要比较两个浮点数是否相等,只能计算它们之差的绝对值,看是否小于某个阈值:
1
Math.abs(1/3 - (1 - 2/3)) < 0.00000001; //true
特例4:null和undefined
null
很好理解。和其他编程语言类似,null
就是一个空值。就是Java
里的null
,python
里的None
。undefined
也很好理解,就是未定义的意思。但是使用起来有些讲究。因为它表示的是值未定义。一般情况下,仅仅在判断函数参数是否传递的情况下有用。其余时刻都用null
。数组
数组是一组按顺序排列的集合,集合的每个值称为元素。JavaScript的数组可以包括任意数据类型。例如:
1
[1, 2, 3.14, 'Hello', null, true];
介绍两种创建数组的方法。
方法一:
new Array();
方法二:
var arr = [];
当然对于我来说,我更喜欢第二种方式。因为可读性更高,别人一看到
[..., ...]
就知道是数组。基本用法:
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
62var arr = [1, 2, 3]; //定义数组arr
arr.length; //arr的长度
arr.indexOf(1); // 0
arr.indexOf(4); // -1
arr.slice(0, 2); // [1, 2]
arr.slice(2); // [3]
// slice可以不带参数,见特例3
arr.push(1); // arr -> [1, 2, 3, 1]
arr.push('1'); // arr -> [1, 2, 3, '1']
arr.push(['1']); // arr -> [1, 2, 3, ['1']]
arr.pop(); // arr -> [1, 2]
// push/pop向数组尾部添加/删除一个元素
arr.unshift(0); // arr -> [0, 1, 2, 3]
arr.shift(); // arr -> [2, 3]
// unshift/shift向数组头部添加/删除一个元素
arr.sort(); // arr会排序。(Array的排列顺序是根据Ascii码大小进行的排序)
arr.reverse(); // arr -> [3, 2, 1]
arr.splice(0, 2); // arr -> [3], return [1, 2]
arr.splice(0, 2, "1", "2"); // arr -> ['1', '2', 3], return [1, 2]
// splice()方法是修改Array的“金钥匙”。其中(起始位置, 个数, [替换元素])
// splice()的返回值是删掉的元素, 改变的是arr。
var new_arr;
new_arr = arr.concat([4, 5, 6]); // new_arr = [1,2,3,4,5,6]
new_arr = arr.concaat(4, 5, 6); // new_arr = [1,2,3,4,5,6]
new_arr = arr.concat(4, [5, 6]); // new_arr = [1,2,3,4,5,6]
// 需要注意的是,arr本身的值没有变,还是[1,2,3]
// 由例子来看,concat()方法本身可以自动拆分
arr.join('-'); // 1-2-3
// 如果arr的元素不是字符串形式,则自动转换为字符串形式。
多维数组形同Java,不再赘述。
//特例1
arr.length = 6; // arr -> [1,2,3,undefined,undefined,undefined]
arr.length = 2; // arr -> [1,2]
//特例2
arr[4] = 5; // arr -> [1, 2, 3, undefined, 5]
//特例3
var copy = arr.slice(); // copy = [1, 2, 3]
copy === arr; // false
// 由此我们可以复制一个arr。
//特例4
var arr = [];
arr.pop(); // 空数组继续pop不会报错,而是返回undefined
arr; // []
//特例5
var arr = [];
arr.shift(); // 空数组继续shift不会报错,而是返回undefined
arr; // []对象
JavaScript的对象是一组由键-值组成的无序集合,例如:
1
2
3
4
5
6
7
8var person = {
name: 'Bob',
age: 20,
tags: ['js', 'web', 'mobile'],
city: 'Beijing',
hasCar: true,
zipcode: null
};JavaScript对象的键都是字符串类型,值可以是任意数据类型。上述
person
对象一共定义了6个键值对,其中每个键又称为对象的属性,例如,person
的name
属性为'Bob'
,zipcode
属性为null
。要获取一个对象的属性,我们用
对象变量.属性名
的方式:1
2person.name; // 'Bob'
person.zipcode; // null访问属性是通过
.
操作符完成的,但这要求属性名必须是一个有效的变量名。如果属性名包含特殊字符,就必须用''
括起来:1
2
3
4var xiaohong = {
name: '小红',
'middle-school': 'No.1 Middle School'
};xiaohong
的属性名middle-school
不是一个有效的变量,就需要用''
括起来。访问这个属性也无法使用.
操作符,必须用['xxx']
来访问:1
2
3xiaohong['middle-school']; // 'No.1 Middle School'
xiaohong['name']; // '小红'
xiaohong.name; // '小红'实际上JavaScript对象的所有属性都是字符串,不过属性对应的值可以是任意数据类型。
如果访问一个不存在的属性会返回什么呢?JavaScript规定,访问不存在的属性不报错,而是返回
undefined
1
2
3
4var xiaoming = {
name:'xiaoming';
}
xiaoming.age; // undefined由于JavaScript的对象是动态类型,你可以自由地给一个对象添加或删除属性:
1
2
3
4
5
6
7
8
9
10
11var xiaoming = {
name: '小明'
};
xiaoming.age; // undefined
xiaoming.age = 18; // 新增一个age属性
xiaoming.age; // 18
delete xiaoming.age; // 删除age属性
xiaoming.age; // undefined
delete xiaoming['name']; // 删除name属性
xiaoming.name; // undefined
delete xiaoming.school; // 删除一个不存在的school属性也不会报错如果我们要检测
xiaoming
是否拥有某一属性,可以用in
操作符:1
2
3
4
5
6
7
8
9
10var xiaoming = {
name: '小明',
birth: 1990,
school: 'No.1 Middle School',
height: 1.70,
weight: 65,
score: null
};
'name' in xiaoming; // true
'grade' in xiaoming; // false不过要小心,如果
in
判断一个属性存在,这个属性不一定是xiaoming
的,它可能是xiaoming
继承得到的:1
'toString' in xiaoming; // true
因为
toString
定义在object
对象中,而所有对象最终都会在原型链上指向object
,所以xiaoming
也拥有toString
属性。要判断一个属性是否是
xiaoming
自身拥有的,而不是继承得到的,可以用hasOwnProperty()
方法:1
2
3
4
5var xiaoming = {
name: '小明'
};
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false
3. 变量
定义规则:变量名是大小写英文、数字、$
和_
的组合,且不能用数字开头。变量名也不能是JavaScript的关键字,如if
、while
等。
申明一个变量用var
语句,比如:
1 | var a; // 申明了变量a,此时a的值为undefined |
剩下的赋值问题都是基础知识,不赘述了。
4. strict模式
这里只是提一嘴。(并不是重点)
JavaScript在设计之初,为了方便初学者学习,并不强制要求用var
申明变量。这个设计错误带来了严重的后果:如果一个变量没有通过var
申明就被使用,那么该变量就自动被申明为全局变量:
1 | i = 10; // i现在是全局变量 |
在同一个页面的不同的JavaScript文件中,如果都不用var
申明,恰好都使用了变量i
,将造成变量i
互相影响,产生难以调试的错误结果。
使用var
申明的变量则不是全局变量,它的范围被限制在该变量被申明的函数体内,同名变量在不同的函数体内互不冲突。
为了修补JavaScript这一严重设计缺陷,ECMA在后续规范中推出了strict模式,在strict模式下运行的JavaScript代码,强制通过var
申明变量,未使用var
申明变量就使用的,将导致运行错误。
启用strict模式的方法是在JavaScript代码的第一行写上:
1 | ; |
5. 条件判断
条件判断部分,Javascript使用的结构和C和Java一样。
1 | if (xxxx){ |
嵌套和其他语言一样,不再赘述。
JavaScript把null
、undefined
、0
、NaN
和空字符串''
视为false
,其他值一概视为true
,因此上述代码条件判断的结果是true
。
6. 循环
Javascript有三种循环方式for
,while
,do ... while
。
和C/C++很类似,但也有特殊点。
for循环
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
35var x = 0;
vaar i;
for(i = 0; i < 10; i++){
x = x + i;
}
// for ... in ...用法
// 1.
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key in o) {
console.log(key); // 'name', 'age', 'city'
}
// 2. 过滤掉对象的继承属性
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key in o) {
if (o.hasOwnProperty(key)) {
console.log(key); // 'name', 'age', 'city'
}
}
// 3. Array对象 -> 类似python
var a = ['A', 'B', 'C'];
for (var i in a) {
console.log(i); // '0', '1', '2'
console.log(a[i]); // 'A', 'B', 'C'
}
2. while循环
while循环不需要这么麻烦。只需要括号里的条件为`false`,则退出循环。
1
2
3
while(true){
break;
}
3. do...while循环
1
2
3
4
5
var n = 0;
do {
n = n + 1;
} while (n < 100);
// n -> 100
需要注意的是,do ... while至少执行一次。
7. Map & Set (ES6)
1. Map
Map
是一组键值对的结构,具有极快的查找速度。
访问Map
的方法也很简单,通过访问键得到值。
初始化Map
需要一个二维数组,或者直接初始化一个空Map
。Map
具有以下方法:
1 | var m = new Map(); // 空Map |
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
1 | var m = new Map(); |
相比起之前说到的对象,对象最大的劣势在于,键值对的键只能使用字符串,而Map的键不受影响。
2. Set
Set
和Map
类似,也是一组key的集合,但不存储value。由于key不能重复。所以,在Set
中,没有重复的key。
要创建一个Set
,需要提供一个Array
作为输入,或者直接创建一个空Set
:
1 | var s1 = new Set(); // 空Set |
重复元素在Set
中自动被过滤:
1 | var s = new Set([1, 2, 3, 3, '3']); |
通过add(key)
方法可以添加元素到Set
中,可以重复添加,但不会有效果:
1 | s.add(4); |
通过delete(key)
方法可以删除元素:
1 | var s = new Set([1, 2, 3]); |
8. Iterable(ES6)
遍历Array
可以采用下标循环,遍历Map
和Set
就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable
类型,Array
、Map
和Set
都属于iterable
类型。
具有iterable
类型的集合可以通过新的for ... of
循环来遍历。
使用for ... of
循环遍历集合,用法如下:
1 | var a = ['A', 'B', 'C']; |
此时我们抛出一个疑问,这么看来,for…in 和 for…of没有什么区别啊。
这个问题需要举个例子来说明
1 | var a = ['A', 'B', 'C']; |
然后我们要提一下iterable
内置的forEach
方法,它接收一个函数,每次迭代就自动回调该函数。
1 | var a = ['A', 'B', 'C']; |
如果对某些参数不感兴趣,由于JavaScript的函数调用不要求参数必须一致,因此可以忽略它们。例如,只需要获得Array
的element
:
1 | var a = ['A', 'B', 'C']; |
函数
1. 函数
函数定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
// 第一种。如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined。
var abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
};
// 第二种。由于JavaScript的函数也是一个对象,上述定义的abs()函数实际上是一个函数对象,而函数名abs可以视为指向该函数的变量。
// 在这种方式下,function (x) { ... }是一个匿名函数,它没有函数名。但是,这个匿名函数赋值给了变量abs,所以,通过变量abs就可以调用该函数。上述两种定义完全等价,注意第二种方式按照完整语法需要在函数体末尾加一个;,表示赋值语句结束。
2. 函数调用
调用函数时,按顺序传入参数即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
abs(10); // return 10
abs(-9); // return -9
// 调用函数时,按照顺序传入参数即可
abs(10, 'blablabla'); // 返回10
abs(-9, 'haha', 'hehe', null); // 返回9
//由于 JavaScript 允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数
abs(); // NaN
// 传入的参数比定义的少也没有问题。此时abs(x)函数的参数x收到的是 undefined , 计算结果是 NaN 。
function abs(x) {
if (typeof x !== 'number') {
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
// 如果想避免接收到 undefined ,我们可以对参数进行过滤,抛出异常。
3. argument