JavaScript教程
- JavaScript是世界上最流行的脚本语言,JavaScript是一种运行在浏览器中的解释型的编程语言。
- 在Web世界里,只有JavaScript能跨平台、跨浏览器驱动网页,与用户交互。
- 新兴的Node.js把JavaScript引入到了服务器端,JavaScript已经变成了全能型选手。
- JavaScript确实很容易上手,但其精髓却不为大多数开发人员所熟知。编写高质量的JavaScript代码更是难上加难。
JavaScript简介
- 由于网景公司希望能在静态HTML页面上添加一些动态效果,于是叫Brendan Eich这哥们在两周之内设计出了JavaScript语言。你没看错,这哥们只用了10天时间。
- 为什么起名叫JavaScript?原因是当时Java语言非常红火,所以网景公司希望借Java的名气来推广,但事实上JavaScript除了语法上有点像Java,其他部分基本上没啥关系。
快速入门
- // 以双斜杠开头直到行末的是注释,注释是给人看的,会被浏览器忽略
- /* 在这中间的也是注释,将被浏览器忽略 */
- 如果你对自己还有更高的要求,可以研究开发者工具的“源码(Sources)”,掌握断点、单步执行等高级调试技巧。
基本语法
- JavaScript的语法和Java语言类似,每个语句以
;
结束,语句块用{...}
,可以嵌套。
- JavaScript每个语句的结尾最好加
;
确保运行结果与期望一致。 - 花括号{…}内语句最好缩进,可以帮助整理代码。
- JavaScript每个语句的结尾最好加
数据类型和变量
计算机能处理的有数值、文本、图形、音频、视频、网页等各种各样的数据,不同的数据,需要定义不同的数据类型。
在JavaScript中定义了以下几种数据类型:
1. Number: JavaScript不区分整数和浮点数,统一用Number表示
2. 字符串: 字符串是以单引号’或双引号”括起来的任意文本
3. 布尔值:布尔值只有true
、false
两种值.与、或、非 分别用 &&,||,!
表示
4. 数组: 数组是一组按顺序排列的集合,集合的每个值称为元素。JavaScript的数组可以包括任意数据类型。[1, 2, 3.14, 'Hello', null, true];
,new Array(1, 2, 3);
5. 对象:JavaScript的对象是一组由键-值组成的无序集合。对象的键都是字符串类型,值可以是任意数据类型。每个键又称为对象的属性。获取一个对象的属性,我们用对象变量.属性名
的方式
- 十六进制前缀:
0x
。例如:0xff00
,0xa5b4c3d2
- JavaScript允许对任意数据类型做比较
- 相等运算符:
==
比较会转换数据类型后比较,===
比较不会转自动换数据类型,直接比较 NaN
这个特殊的Number
与所有其他值都不相等,包括它自己- 唯一能判断
NaN
的方法是通过isNaN()
函数 - 浮点数的相等比较: 因为计算机无法精确表示无限循环小数。要比较两个浮点数是否相等,只能计算它们之差的绝对值,看是否小于某个阈值
Math.abs(1 / 3 - (1 - 2 / 3)) < 0.0000001; // true
- 数组的元素可以通过索引来访问。索引的起始值为
0
- 申明一个变量用
var
语句,变量名也可以用中文,但是,请不要给自己找麻烦。 - 变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。Java是静态语言
- 变量没有通过
var
申明就被使用,该变量就自动被申明为全局变量,使用var
申明的变量则不是全局变量,它的范围被限制在该变量被申明的函数体内,为了修补JavaScript这一严重设计缺陷,ECMA在后续规范中推出了strict模式.启用strict模式的方法是在JavaScript代码的第一行写上:'use strict';
字符串
- 转义字符:
'I\'m \"OK\"!';
表示I'm "OK"!
- 十六进制
\x41
可以表示ASCII
字符"A"
,个Unicode字符可用\u####
表示 - 多行字符串:用反引号表示,键盘的ESC下方。类似
python
中'''...'''
- 模板字符串:把多个字符串连接起来,可以用
+
号连接。ES6
用${var}
自动替换字符串中的变量,此时字符串引号改为反引号。类似python3
中'{}'.fromat(var)
字符串操作:
s.length,s[0]
- 字符串是不可变的,字符串的某个索引赋值无意义
- JavaScript字符串常用方法,调用方法返回新字符串:
s='Hello';s.toUpperCase();s.toLowerCase();s.indexOf('w');s.substring(0,5);s.substring(7);
数组
JavaScript数组常用方法:
1. Array
的长度:arr.length
2. arr.indexOf(var);
3. arr.slice(0,3);arr.slice(3);arr.slice();
4. arr.push('A', 'B');arr.pop();
5. arr.unshift('A', 'B');arr.shift();
6. arr.sort();
7. arr.reverse();
8. arr.concat([1, 2, 3]);
,类似python
的lst.extend
9. arr.join('-');
10. splice()
方法是修改Array的“万能方法”,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素:
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']
Array
的索引可赋值- 直接给
Array
的length
赋一个新的值会导致Array
大小的变化,不建议直接修改Array的大小 slice()
就是对应String
的substring()
版本- sort()直接修改当前Array的元素位置
- concat()方法返回新的Array
- 如果Array的元素不是字符串,将自动转换为字符串后再
join
。
对象
JavaScript的对象是一种无序的集合数据类型,它由若干键值对组成。用{…}表示对象,键值对以xxx: xxx
形式申明,用,
隔开
JavaScript数组常用方法:
1. 新增age属性:xiaoming.age = 18;
2. 删除age属性: delete xiaoming.age;
3. 判断属性存在:'age' in xiaoming;
4. 判断属性是否自身拥有:xiaoming.hasOwnProperty('name');
- 属性名包含特殊字符,就必须用”括起来,访问该属性必须用[‘xxx’]来访问
- 访问不存在的属性不报错,而是返回undefined
- JavaScript的对象是动态类型,你可以自由地给一个对象添加或删除属性
- 如果
in
判断一个属性存在,这个属性不一定是xiaoming
的,它可能是xiaoming
继承得到的
条件判断
JavaScript使用if () { ... } else { ... }
来进行条件判断
- 语句块只包含一条语句,那么可以省略
{}
,建议永远都要写上{}
- JavaScript把
null、undefined、0、NaN
和空字符串”视为false
,其他值一概视为true
,因此上述代码条件判断的结果是true
。
循环
for
循环
var x = 0;
var i;
for (i=1; i<=10000; i++) { // 分号间隔
x = x + i;
}
x; // 50005000
i=1; i<=10000; i++
分别为初始条件、判断条件、递增条件for
循环最常用的地方是利用索引来遍历数组for
循环的3个条件都是可以省略的,如果没有退出循环的判断条件,就必须使用break
语句退出循环,否则就是死循环for
循环的一个变体是for (var key in o) in
循环,它可以把一个对象的所有属性依次循环出来- 由于Array也是对象,而它的每个元素的索引被视为对象的属性,因此,for … in循环可以直接循环出Array的索引
var a = ['A', 'B', 'C'];
for (var i in a) {
console.log(i); // '0', '1', '2'
console.log(a[i]); // 'A', 'B', 'C'
}
while
循环
while
循环只有一个判断条件,条件满足,就不断循环,条件不满足时则退出循环。
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
循环则可能一次都不执行。
Map和Set 数据类型
Map和Set是ES6标准新增的数据类型
Map
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]); // 二维数组初始化 Map
m.get('Michael'); // 95
var m = new Map(); // 初始化一个空 Map
m.set('Adam', 67); // 添加新的 key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在 key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除 key 'Adam'
- Map是一组键值对的结构,具有极快的查找速度。
- 多次对一个key放入value,后面的值会把前面的值冲掉
Set 集合
var s = new Set(); // 初始化一个空 Set
var s2 = new Set([1, 2, 3]); // 数组初始化 Set
s.add(4); // 添加元素
s.delete(3); // 删除元素
- Set和Map类似,也是一组不重复
key
的集合,且不存储value
iterable
for ... of
循环
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]);
}
for ... of
循环和for ... in
循环有何区别?
答:for ... in
遍历的实际上是对象的属性名称,会把新增属性包括在内。for ... of
循环,它只循环集合本身的元素
iterable内置的forEach方法
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, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
console.log(element + ', index = ' + index);
});
for ... of
循环是ES6
引入的新的语法。具有iterable
类型的集合可以通过新的for ... of
循环来遍历- 一个Array数组实际上也是一个对象,它的每个元素的索引被视为一个属性
- iterable内置的forEach方法接收一个函数,每次迭代就自动回调该函数
- Set与Array类似,但Set没有索引,因此回调函数的前两个参数都是元素本身
- Map的回调函数参数依次为value、key和map本身
- 由于JavaScript的函数调用不要求参数必须一致,可以忽略
函数
函数定义和调用
function myabs(x) { //第一种定义函数的方式
// var abs = function (x) { //第二种定义函数的方式,匿名函数,赋值给了变量myabs
if (typeof x !== 'number') { // 参数检查
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
- 函数执行到return时结束,并将结果返回
- 没有return语句,函数执行完毕后也会返回结果,只是结果为undefined,类似
python
返回None
- 第二种方式按照完整语法需要在函数体末尾加一个;,表示赋值语句结束。
- JavaScript允许传入任意个参数而不影响调用
- JavaScript关键字arguments,只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array
- 利用arguments,即使函数不定义任何参数,还是可以拿到参数的值
rest参数
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);
- rest参数只能写在最后,前面用…标识
变量作用域与解构赋值
- var申明的变量实际上是有作用域的
- 函数体内部申明变量的作用域为整个函数体,在函数体外不可引用
- JavaScript的函数可以嵌套,内部函数可以访问外部函数定义的变量,反过来则不行
- 内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量
- 在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个var申明函数内部用到的所有变量
- 不在任何函数内定义的变量就具有全局作用域(只有一个)。实际上,JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性
- 全局变量会绑定到window上,为避免造成命名冲突,并且很难被发现。减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
- JavaScript的变量作用域实际上是函数内部,for循环等语句块中定义的循环变量在函数内部均有效。为了解决块级作用域,ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量
- var和let申明的是变量
- ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域
解构赋值
从ES6开始,JavaScript引入了解构赋值,可以同时对一组变量进行赋值。
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
// x, y, z分别被赋值为数组对应元素:
console.log('x = ' + x + ', y = ' + y + ', z = ' + z);
// x = hello, y = JavaScript, z = ES6
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};
var {name, age, passport} = person;
// name, age, passport分别被赋值为对应属性:
console.log('name = ' + name + ', age = ' + age + ', passport = ' + passport);
var {name, single=true} = person;
- 使用解构赋值可以减少代码量
- 对数组元素进行解构赋值时,多个变量要用[…]括起来
- 对一个对象进行解构赋值时,同样可以直接对嵌套的对象属性进行赋值,只要保证对应的层次是一致的
- 解构赋值如果对应的属性不存在,变量将被赋值为
undefined
- 解构赋值还可以使用默认值,这样就避免了不存在的属性返回undefined的问题
- 交换两个变量
[x, y] = [y, x]
- 如果一个函数接收一个对象作为参数,那么,可以使用解构直接把对象的属性绑定到变量中。例如,下面的函数可以快速创建一个Date对象
function buildDate({year, month, day, hour=0, minute=0, second=0}) {
return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
}
// 只需要year、month和day这三个属性
buildDate({ year: 2017, month: 1, day: 1 });
// Sun Jan 01 2017 00:00:00 GMT+0800 (CST)
方法
绑定到对象上的函数称为方法
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是一个特殊变量,它始终指向当前对象,也就是xiaoming这个变量
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25, 正常结果
getAge(); // NaN
- 以对象的方法形式调用,比如xiaoming.age(),该函数的this指向被调用的对象,也就是xiaoming,这是符合我们预期的。
- 如果单独调用函数,比如getAge(),此时,该函数的this指向全局对象,也就是window
// 用apply修复getAge()调用
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
apply()类似的方法是call(),唯一区别是:apply()把参数打包成Array再传入;call()把参数按顺序传入。
高阶函数
一个函数就可以接收另一个函数作为参数,称之为高阶函数
map/reduce
- map()方法定义在JavaScript的Array中,我们调用Array的map()方法,传入我们自己的函数,就得到了一个新的Array作为结果
- map()传入的参数是pow,即函数对象本身,把运算规则抽象