JavaScript的语法和Java语言类似,每个语句以;
结束,语句块用{...}
。但是,JavaScript并不强制要求在每个语句的结尾加;
,浏览器中负责执行JavaScript代码的引擎会自动在每个语句的结尾补上;
但是让JavaScript引擎自动加分号在某些情况下会改变程序的语义,导致运行结果与期望不一致。因此,强烈所有语句都以分号结尾
单行注释://
// 这是一行注释
alert('hello'); // 这也是注释
// 以//开头直到行末的字符被视为行注释,注释是给开发人员看到,JavaScript引擎会自动忽略。
多行注释:/**/
/* 从这里开始是块注释
仍然是注释
仍然是注释
注释结束 */
// 用/*...*/把多行字符包裹起来,把一大“块”视为一个注释
JavaScript严格区分大小写,如果弄错了大小写,程序将报错或者运行不正常。
变量一个存放任意数据类型的数据的内存空间;JavaScrip的变量名称是大小写英文、数字、$
和_
的组合;且不能用数字开头,也不能是JavaScript的关键字。
变量名也可以用中文,但不推荐。
通过=
给变量赋值
声明变量时不需要指定数据类型
声明变量时需指定其作用域
关键字 | 作用范围 |
---|---|
var | 方法作用域,在方法内部的定义变量,方法内任何地方都可以访问。 |
let | 块作用域,即其在整个大括号 {} 之内可见 |
不写关键字,就是全局作用域 | |
const | 和 let 的作用域是一致的,不同的是 const不可变变量。 |
function hehe() {
console.log("start。。。")
for (let i=0; i<2; i++) {
let m = 10;
var n = 20;
console.log("m = " + m, "n = " + n);
}
console.log("m = " + m, "n = " + n); //这里无法使用变量m,但可以使用变量n。
}
数字
JavaScript不区分整数和浮点数,统一用Number表示。
字符串
字符串是以单引号’或双引号"括起来的任意文本,比如'abc'
,"xyz"
等等。
布尔值
布尔值和布尔代数的表示完全一致,一个布尔值只有true
、false
两种值,要么是true
,要么是false
,可以直接用true
、false
表示布尔值,也可以通过布尔运算计算出来。
数组
数组是一组按顺序排列的集合,集合的每个值称为元素。JavaScript的数组可以包括任意数据类型。
对象
JavaScript的对象是一组由键-值组成的无序集合。
JavaScript的字符串就是用''
或""
括起来的字符表示。
由于多行字符串用\n
写起来比较费事,所以最新的ES6标准新增了一种多行字符串的表示方法,用反引号(`)。
要把多个字符串连接起来,可以用+
号连接
var name = '小明';
var age = 20;
var message = '你好, ' + name + ', 你今年' + age + '岁了!';
ES6新增了一种模板字符串
var name = '小明';
var age = 20;
var message = `你好, ${name}, 你今年${age}岁了!`;
字符串长度
var s = 'Hello, world!';
s.length; // 13
获取某个位置字符
var s = 'Hello, world!';
s[1]; // 'e'
s[12]; // '!'
s[13]; // undefined 超出范围的索引不会报错,但一律返回undefined
字符串是不可变的,如果对字符串的某个索引赋值,不会有任何错误,但也没有任何效果。**注意:**下标是从0开始的。
把一个字符串全部变为大写 - toUpperCase()
var s = 'Hello';
s.toUpperCase(); // 返回'HELLO'
把一个字符串全部变为小写 - toLowerCase()
var s = 'Hello';
s.toLowerCase(); // 返回'hello'
搜索指定字符串出现的位置 - indexOf()
var s = 'hello, world';
s.indexOf('world'); // 返回7,返回的是位置不是下标。
s.indexOf('World'); // 没有找到指定的子串,返回-1
返回指定索引区间的子串 - substring()
var s = 'hello, world'
s.substring(0, 5); // 从索引0开始到5(不包括5),返回'hello'
s.substring(7); // 从索引7开始到结束,返回'world'
字符串转数字 - parseInt()和parseFloat()
parseFloat('3.14') // 3.14
parseInt('3.14') // 3
JavaScript的Array
可以包含任意数据类型,并通过索引来访问每个元素
获取数组长度
var arr = [1, 2, 3.14, 'Hello', null, true];
arr.length; // 6
修改数组长度
var arr = [1, 2, 3];
arr.length; // 3
arr.length = 6;
arr; // arr变为[1, 2, 3, undefined, undefined, undefined]
修改数组中的元素
var arr = ['A', 'B', 'C'];
arr[1] = 99; // 通过索引把对应的元素修改为新的值
arr; // arr现在变为['A', 99, 'C']
索引赋值时,索引超过了范围,数组大小将会变化
var arr = [1, 2, 3];
arr[5] = 'x';
arr; // arr变为[1, 2, 3, undefined, undefined, 'x']
**注意:**大多数其他编程语言不允许直接改变数组的大小,越界访问索引会报错。然而,JavaScript的Array
却不会有任何错误。在编写代码时,不建议直接修改Array
的大小,访问索引时要确保索引不会越界。
indexOf - 搜索指定元素返回下标
var arr = [10, 20, '30', 'xyz'];
arr.indexOf(10); // 元素10的索引为0
arr.indexOf(30); // 元素30没有找到,返回-1
arr.indexOf('30'); // 元素'30'的索引为2
slice - 根据给定的下标截取部分元素返回一个新数组
var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
arr.slice(0, 3); // 从索引0开始,到索引3结束,但不包括索引3: ['A', 'B', 'C']
arr.slice(3); // 从索引3开始到结束: ['D', 'E', 'F', 'G']
push和pop
push()
向Array
的末尾添加若干元素,pop()
则把Array
的最后一个元素删除掉
var arr = [1, 2];
arr.push('A', 'B'); // 返回Array新的长度: 4
arr; // [1, 2, 'A', 'B']
arr.pop(); // pop()返回'B'
arr; // [1, 2, 'A']
arr.pop(); arr.pop(); arr.pop(); // 连续pop 3次
arr; // []
arr.pop(); // 空数组继续pop不会报错,而是返回undefined
arr; // []
unshift和shift
如果要往Array
的头部添加若干元素,使用unshift()
方法,shift()
方法则把Array
的第一个元素删掉
var arr = [1, 2];
arr.unshift('A', 'B'); // 返回Array新的长度: 4
arr; // ['A', 'B', 1, 2]
arr.shift(); // 'A'
arr; // ['B', 1, 2]
arr.shift(); arr.shift(); arr.shift(); // 连续shift 3次
arr; // []
arr.shift(); // 空数组继续shift不会报错,而是返回undefined
arr; // []
sort
sort()
可以对当前Array
进行排序,它会直接修改当前Array
的元素位置,直接调用时,按照默认顺序排序;还可以指定顺序排序
reverse - 把整个数组元素翻转过来
splice - 修改数组
var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
// 从索引2开始删除3个元素,然后再添加两个元素:
arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL', 'Excite']
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
// 只删除,不添加:
arr.splice(2, 2); // ['Google', 'Facebook']
arr; // ['Microsoft', 'Apple', 'Oracle']
// 只添加,不删除:
arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
concat - 连接两个数组,返回新的数组
var arr = ['A', 'B', 'C'];
var added = arr.concat([1, 2, 3]);
added; // ['A', 'B', 'C', 1, 2, 3]
arr; // ['A', 'B', 'C']
join - 把数组元素都用指定的字符串连接起来
var arr = ['A', 'B', 'C', 1, 2, 3];
arr.join('-'); // 'A-B-C-1-2-3'
多维数组
如果数组的某个元素又是一个Array
,则可以形成多维数组
var arr = [[1, 2, 3], [400, 500, 600], '-'];
JavaScript的对象是一种无序的集合数据类型,它由若干键值对组成。
JavaScript用一个{...}
表示一个对象,键值对以xxx: xxx
形式申明,用,
隔开。注意,最后一个键值对不需要在末尾加,
var xiaoming = {
name: '小明',
birth: 1990,
'school': 'No.1 Middle School',
height: 1.70,
weight: 65,
score: null
};
访问属性
xiaoming.school // 'No.1 Middle School' 推荐使用
xiaoming["school"] // 'No.1 Middle School'
设置新值
xiaoming.name = '小红'
xiaoming["name"] = '小华'
map是一组键值对的结构,具有极快的查找速度。
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
m.get('Michael'); // 95
m.set('Adam', 67); // 添加新的key-value
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined
注意,一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉。
要创建一个Set
,需要提供一个Array
作为输入,或者直接创建一个空Set
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
没有重复元素
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
添加元素
s.add(4);
s; // Set {1, 2, 3, 4}
s.add(4);
s; // 仍然是 Set {1, 2, 3, 4}
删除元素
var s = new Set([1, 2, 3]);
s; // Set {1, 2, 3}
s.delete(3);
s; // Set {1, 2}
Map
和Set
是ES6标准新增的数据类型,请根据浏览器的支持情况决定是否要使用。
遍历Array
可以采用下标循环,遍历Map
和Set
就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable
类型,Array
、Map
和Set
都属于iterable
类型。
具有iterable
类型的集合可以通过新的for ... of
循环来遍历。for ... of
循环是ES6引入的新的语法。
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍历Array
console.log(x);
}
for (var x of s) { // 遍历Set
console.log(x);
}
for (var x of m) { // 遍历Map
console.log(x[0] + '=' + x[1]);
}
然而,更好的方式是直接使用iterable
内置的forEach
方法,它接收一个函数,每次迭代就自动回调该函数。
forEach()
方法是ES5.1标准引入的,你需要测试浏览器是否支持。
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
a.forEach(function (element) {
console.log(element); // 遍历Array
});
s.forEach(function (element, sameElement, set) {
console.log(element); // 遍历Set
});
m.forEach(function (value, key, map) {
console.log(value); // 遍历Map
});
var age = 3;
if (age >= 18) {
alert('adult');
} else {
if (age >= 6) {
alert('teenager');
} else {
alert('kid');
}
}
重点: 若不用{}
,此条件判断最后一个关键字后,只有一条语句是该条件判断的,因此推荐使用{}
var name = 'tom';
switch (name) {
case 'tom':
age = 18;
break;
case 'John':
age = 35;
break;
default:
age = 100;
}
for
a = [11,22,33,44];
for (i=0; i<a.length; i++) {
console.log(a[i]);
}
for
循环的3个条件都是可以省略的,如果没有退出循环的判断条件,就必须使用break
语句退出循环,否则就是死循环:
var x = 0;
for (;;) { // 将无限循环下去
if (x > 100) {
break; // 通过if判断来退出循环
}
x ++;
}
for…in
a = [11,22,33,44];
for(var item in a) {
console.log(item);
}
while - 每次循环前都先判断条件,True继续,False退出
var x = 0;
var n = 99;
while (n > 0) {
x = x + n;
n = n - 2;
}
x; // 2500
do…while - 先执行一次之后,再判断
var n = 0;
do {
n = n + 1;
} while (n < 100);
n; // 100
用do { ... } while()
循环要小心,循环体会至少执行1次,而for
和while
循环则可能一次都不执行。
在JavaScript中,定义函数的方式如下:
function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
上述abs()
函数的定义如下:
function
指出这是一个函数定义;abs
是函数的名称;(x)
括号内列出函数的参数,多个参数以,
分隔;{ ... }
之间的代码是函数体,可以包含若干语句,甚至可以没有任何语句。请注意,函数体内部的语句在执行时,一旦执行到return
时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。
如果没有return
语句,函数执行完毕后也会返回结果,只是结果为undefined
。
由于JavaScript的函数也是一个对象,上述定义的abs()
函数实际上是一个函数对象,而函数名abs
可以视为指向该函数的变量。
因此,第二种定义函数的方式如下:
var abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
};
在这种方式下,function (x) { ... }
是一个匿名函数,它没有函数名。但是,这个匿名函数赋值给了变量abs
,所以,通过变量abs
就可以调用该函数。
上述两种定义完全等价,注意第二种方式按照完整语法需要在函数体末尾加一个;
,表示赋值语句结束。
调用函数时,按顺序传入参数即可:
abs(10); // 返回10
abs(-9); // 返回9
由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数:
abs(10, 'blablabla'); // 返回10
abs(-9, 'haha', 'hehe', null); // 返回9
传入的参数比定义的少也没有问题:
abs(); // 返回NaN;此时abs(x)函数的参数x将收到undefined,计算结果为NaN。
arguments
利用arguments
,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值。
function abs() {
if (arguments.length === 0) {
return 0;
}
var x = arguments[0];
return x >= 0 ? x : -x;
}
abs(); // 0
abs(10); // 10
abs(-9); // 9
reset
ES6标准引入了rest参数。rest参数只能写在最后,前面用...
标识,从运行结果可知,传入的参数先绑定a
、b
,多余的参数以数组形式交给变量rest
,所以,不再需要arguments
我们就获取了全部参数。
function foo(a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
foo(1, 2, 3, 4, 5);
// 结果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]
foo(1);
// 结果:
// a = 1
// b = undefined
// Array []
在JavaScript中,用var
申明的变量实际上是有作用域的。如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量:
function foo() {
var x = 1;
x = x + 1;
}
x = x + 2; // ReferenceError! 无法在函数体外引用变量x
由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行:
function foo() {
var x = 1;
function bar() {
var y = x + 1; // bar可以访问foo的变量x!
}
var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
}
JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。
JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部:
'use strict';
function foo() {
var x = 'Hello, ' + y;
console.log(x);
var y = 'Bob';
}
foo();
虽然是strict模式,但语句var x = 'Hello, ' + y;
并不报错,原因是变量y
在稍后申明了。但是console.log
显示Hello, undefined
,说明变量y
的值为undefined
。这正是因为JavaScript引擎自动提升了变量y
的声明,但不会提升变量y
的赋值。
由于JavaScript的这一怪异的“特性”,我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。
不在任何函数内定义的变量就具有全局作用域。
JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError
错误。
全局变量会绑定到window
上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
把自己的代码全部放入唯一的名字空间MYAPP
中,会大大减少全局变量冲突的可能。
由于JavaScript的变量作用域实际上是函数内部,我们在for
循环等语句块中是无法定义具有局部作用域的变量的:
function foo() {
for (var i=0; i<100; i++) {
//
}
i += 100; // 仍然可以引用变量i
}
为了解决块级作用域,ES6引入了新的关键字let
,用let
替代var
可以申明一个块级作用域的变量:
function foo() {
var sum = 0;
for (let i=0; i<100; i++) {
sum += i;
}
// SyntaxError:
i += 1;
}
由于var
和let
申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”。ES6标准引入了新的关键字const
来定义常量,const
与let
都具有块级作用域:
const PI = 3.14;
PI = 3; // 某些浏览器不报错,但是无效果!
PI; // 3.14
从ES6开始,JavaScript引入了解构赋值,可以同时对一组变量进行赋值。
// 如果浏览器支持解构赋值就不会报错:
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
// x, y, z分别被赋值为数组对应元素:
console.log('x = ' + x + ', y = ' + y + ', z = ' + z);
注意,对数组元素进行解构赋值时,多个变量要用[...]
括起来。如果数组本身还有嵌套,也可以通过下面的形式进行解构赋值,注意嵌套层次和位置要保持一致:
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
x; // 'hello'
y; // 'JavaScript'
z; // 'ES6'
解构赋值还可以忽略某些元素:
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素
z; // 'ES6'
如果需要从一个对象中取出若干属性,也可以使用解构赋值,便于快速获取对象的指定属性:
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};
var {name, age, passport} = person;
console.log('name = ' + name + ', age = ' + age + ', passport = ' + passport);
// name = 小明, age = 20, passport = G-12345678
在一个对象中绑定函数,称为这个对象的方法。
在JavaScript中,对象的定义是这样的:
var xiaoming = {
name: '小明',
birth: 1990
};
但是,如果我们给xiaoming
绑定一个函数,就可以做更多的事情。比如,写个age()
方法,返回xiaoming
的年龄:
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 今年调用是25,明年调用就变成26了
绑定到对象上的函数称为方法,和普通函数也没啥区别,但是它在内部使用了一个this
关键字。this
是一个特殊变量,它始终指向当前对象,也就是xiaoming
这个变量。
JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。