JavaScript
1.快速入门
JavaScript代码可以直接嵌在网页的任何地方,不过通常我们都把JavaScript代码放到<head>中:
<head>
<script>
alert('Hello, world');
</script>
</head>
第二种方法是把JavaScript代码放到一个单独的.js文件,然后在HTML中通过<script src="..."></script>引入这个文件:
<head>
<script src="/static/js/abc.js"></script>
</head>
1.1.基本语法
每个语句以;结束
语句块用{...}
var 变量 var x = 1;
// 注释
/*...*/ 大块注释
严格区分大小写
null 空
undefined 未定义
1.2.数据类型和变量
Number JavaScript不区分整数和浮点数,
NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity
字符串 单引号'或双引号"括起来的任意文本
布尔值 只有true、false两种值
&& 与
|| 或
! 非
比较
>
<
== 自动转换数据类型再比较
=== 不会自动转换数据类型,如果数据类型不一致,返回false
不要使用==比较,始终坚持使用===比较。
NaN === NaN; // false
NaN这个特殊的Number与所有其他值都不相等,包括它自己:
isNaN(NaN); // true
1 / 3 === (1 - 2 / 3); // false
浮点数在运算过程中会产生误差,因为计算机无法精确表示无限循环小数。
要比较两个浮点数是否相等,只能计算它们之差的绝对值,看是否小于某个阈值:
Math.abs(1 / 3 - (1 - 2 / 3)) < 0.0000001; // true
数组
包括任意数据类型。
var arr=[1, 2, 3.14, 'Hello', null, true];
另一种创建数组的方法
var arr=new Array(1, 2, 3); // 创建了数组[1, 2, 3]
对象
由键-值组成的无序集合
var person = {
name: 'Bob',
age: 20,
tags: ['js', 'web', 'mobile'],
city: 'Beijing',
hasCar: true,
zipcode: null
};
获取一个对象的属性,我们用对象变量.属性名的方式:
person.name; // 'Bob'
person.zipcode; // null
变量
申明一个变量用var语句
var a; // 申明了变量a,此时a的值为undefined
var $b = 1; // 申明了变量$b,同时给$b赋值,此时$b的值为1
可以对同一个变量反复赋值,可以不同类型
strict模式
如果一个变量没有通过var申明就被使用,那么该变量就自动被申明为全局变量:
在strict模式下运行的JavaScript代码,强制通过var申明变量,未使用var申明变量就使用的,将导致运行错误。
启用strict模式的方法是在JavaScript代码的第一行写上:
'use strict';
1.3.字符串
就是用''或""括起来的字符表示
如果字符串内部既包含'又包含"怎么办?可以用转义字符\来标识
'I\'m \"OK\"!';
转义字符\可以转义很多字符
\n表示换行,
\t表示制表符,
\\表示的字符就是\。
\x##形式的十六进制表示 '\x41'; // 完全等同于 'A'
\u####表示一个Unicode字符: '\u4e2d\u6587'; // 完全等同于 '中文'
` ... ` 多行字符串的表示方法
`这是一个
多行
字符串`;
常用操作
var s = 'Hello, world!';
长度
s.length; // 13
获取指定位置字符
s[1]; //e
字符串是不可变的原有字符串的内容,而是返回一个新字符串:
调用下列方法不会改变
把一个字符串全部变为大写:
s.toUpperCase();
把一个字符串全部变为小写:
s.toLowerCase()
会搜索指定字符串出现的位置:
s.indexOf('world'); // 返回7
s.indexOf('World'); // 没有找到指定的子串,返回-1
返回指定索引区间的子串:
s.substring(0, 5); // 从索引0开始到5(不包括5),返回'Hello'
s.substring(7); // 从索引7开始到结束,返回'world'
1.4.数组
可以包含任意数据类型,并通过索引来访问每个元素。
常用操作
var arr = [1, 2, 3.14, 'Hello', null, true];
长度
arr.length; // 6
直接给Array的length赋一个新的值会导致Array大小的变化:
var arr = [1, 2, 3];
arr.length; // 3
arr.length = 6;
arr; // arr变为[1, 2, 3, undefined, undefined, undefined]
arr.length = 2;
arr; // arr变为[1, 2]
可以通过索引修改值
如果通过索引赋值时,索引超过了范围,同样会引起Array大小的变化:
var arr = [1, 2, 3];
arr[5] = 'x';
arr; // arr变为[1, 2, 3, undefined, undefined, 'x']
搜索一个指定的元素的位置:
arr.indexOf(2); // 元素10的索引为1
截取Array的部分元素,然后返回一个新的Array:
arr.slice(0, 3); // 从索引0开始,到索引3结束,但不包括索引3: [1, 2, 3.14,]
末尾添加若干元素
arr.push('A', 'B'); // 返回Array新的长度: 8
最后一个元素删除掉:
arr.pop(); // pop()返回'B'
头部添加若干元素
arr.unshift('A', 'B'); // 返回Array新的长度: 8
第一个元素删掉:
arr.shift(); // 'A'
排序
arr.sort();
反转
arr.reverse();
从指定的索引开始删除若干元素,然后再从该位置添加若干元素:
arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 [3.14, 'Hello', null]
// 只删除,不添加:
arr.splice(2, 2); // [3.14, 'Hello']
// 只添加,不删除:
arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素
当前的Array和另一个Array连接起来,并返回一个新的Array:
var added = arr.concat([1, 2, 3]);
用指定的字符串连接起来
arr.join('-');
多维数组
数组的某个元素又是一个Array,则可以形成多维数组
var arr = [[1, 2, 3], [400, 500, 600], '-'];
1.4.对象
概念
一种无序的集合数据类型,它由若干键值对组成。
访问不存在的属性不报错,而是返回undefined:
如果属性名包含特殊字符,就必须用''括起来:
var xiaohong = {
name: '小红',
'middle-school': 'No.1 Middle School'
};
访问这个属性也无法使用.操作符,必须用['xxx']来访问:
基本操作
var xiaoming = {
name: '小明',
birth: 1990,
school: 'No.1 Middle School',
height: 1.70,
weight: 65,
score: null
};
获取属性
xiaoming.name; // '小明'
xiaohong['name']; // '小明'
删除
delete xiaoming.birth; // 删除age属性
删除一个不存在的school属性也不会报错
检测xiaoming是否拥有某一属性
'name' in xiaoming; // true
'toString' in xiaoming; // true
如果in判断一个属性存在,这个属性不一定是xiaoming的,它可能是xiaoming继承得到的
要判断一个属性是否是xiaoming自身拥有的,而不是继承得到的
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false
1.5.条件判断
if () { ... } else if {...}else { ... }
循环
for(;;)
var x = 0;
var i;
for (i=1; i<=10000; i++) {
x = x + i;
}
for ... in
把一个对象的所有属性依次循环出来:
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key in o) {
alert(key); // 'name', 'age', 'city'
}
for ... in循环可以直接循环出Array的索引:
while
do { ... } while()
1.6.Map和Set
概念
为了解决对象中,键必须是字符串,引入 Map
Map
Map是一组键值对的结构,具有极快的查找速度。
基本操作
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
var m = new Map(); // 空Map
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
Set
一组key的集合,但不存储value。
没有重复的key,重复元素在Set中自动被过滤:
基本操作
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
s.delete(3); // 删除元素delete(key)
1.7.iterable
概念
遍历Map和Set就无法使用下标,引入了新的iterable类型
Array、Map和Set都属于iterable类型。
通过新的for ... of循环来遍历。
iterable内置的forEach方法,它接收一个函数,每次迭代就自动回调该函数。
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
alert(x);
}
for (var x of s) { // 遍历Set
alert(x);
}
for (var x of m) { // 遍历Map
alert(x[0] + '=' + x[1]);
}
forEach方法
Array
var a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
alert(element);
});
Set
Set与Array类似,但Set没有索引,因此回调函数的前两个参数都是元素本身:
var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set) {
alert(element);
});
Map
Map的回调函数参数依次为value、key和map本身
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
alert(value);
});
可以忽略某些不需要的参数
var a = ['A', 'B', 'C'];
a.forEach(function (element) {
alert(element);
});
2.函数
2.1.函数定义和调用
定义函数
function abs(x) {} //第一种
var abs = function (x) {}; //第二种,末尾加一个;
调用函数
abs(10); // 返回10
//传入的参数比定义的参数多也没有问题
abs(10, 'blablabla'); // 返回10
//传入的参数比定义的少也没有问题:
abs(); // 返回NaN
//此时abs(x)函数的参数x将收到undefined,计算结果为NaN。
//要避免收到undefined,可以对参数进行检查:
function abs(x) {
if (typeof x !== 'number') {
throw 'Not a number';
}
}
arguments
只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array:
function foo(x) {
alert(x); // 10
for (var i=0; i<arguments.length; i++) {
alert(arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);
即使函数不定义任何参数,还是可以拿到参数的值:
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
最常用于判断传入参数的个数。
// foo(a[, b], c)
// 接收2~3个参数,b是可选参数,如果只传2个参数,b默认为null:
function foo(a, b, c) {
if (arguments.length === 2) {
// 实际拿到的参数是a和b,c为undefined
c = b; // 把b赋给c
b = null; // b变为默认值
}
// ...
}
rest参数
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 ]
2.2.变量作用域
概念
函数体内部申明,作用域为整个函数体
查找变量时从自身函数定义开始,从“内”向“外”查找
变量提升
JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部:
function foo() {
var x = 'Hello, ' + y;
alert(x);
var y = 'Bob';
}
相当于:
function foo() {
var y; // 提升变量y的申明
var x = 'Hello, ' + y;
alert(x);
y = 'Bob';
}
全局作用域
不在任何函数内定义的变量就具有全局作用域
JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性:
JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性:
'use strict';
var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'
名字空间
不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
局部作用域
在for中新建的 var变量的作用域依旧是外部函数 || 全局
用let替代var可以申明一个块级作用域的变量:
function foo() {
var sum = 0;
for (let i=0; i<100; i++) {
sum += i;
}
i += 1; // SyntaxError
}
常量
const来定义常量
const与let都具有块级作用域:
const PI = 3.14;
2.3.方法
概念
在一个对象中绑定函数,称为这个对象的方法。
this
this是一个特殊变量,它始终指向当前对象
用一个that变量首先捕获this:
age: function () {
var that = this; // 在方法内部一开始就捕获this
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - that.birth; // 用that而不是this
}
}
apply
控制this的指向
接收两个参数,第一个参数就是需要绑定的this变量,第二个参数是Array,表示函数本身的参数
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
call()
与apply()类似
区别
apply()把参数打包成Array再传入;
call()把参数按顺序传入。
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5
装饰器
利用apply(),我们还可以动态改变函数的行为。
var count = 0;
var oldParseInt = parseInt; // 保存原函数
window.parseInt = function () {
count += 1;
return oldParseInt.apply(null, arguments); // 调用原函数
};
2.4.高阶函数
概念
JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
ex:
function add(x, y, f) {
return f(x) + f(y);
}
map/reduce
map
将数组的每个元素为参数执行参数中的函数
参数:函数
map()方法定义在JavaScript的Array中
ex:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
reduce
reduce()把结果继续和序列的下一个元素做累积计算,其效果就是:
[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
ex:
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
return x + y;
}); // 25
filter
把Array的某些元素过滤掉,然后返回剩下的元素。
filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
ex:
删掉偶数,只保留奇数
var arr = [1, 2, 4, 5, 6, 9, 10, 15];
var r = arr.filter(function (x) {
return x % 2 !== 0;
});
r; // [1, 5, 9, 15]
sort
Array的sort()方法默认把所有元素先转换为String再排序
'10'会排在'2'的前面
sort()方法也是一个高阶函数,可以接收一个比较函数来实现自定义的排序。
对于两个元素x和y,如果认为x < y,则返回-1,如果认为x == y,则返回0,如果认为x > y,则返回1
sort()方法会直接对Array进行修改
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {
return -1;
}
if (x > y) {
return 1;
}
return 0;
}); // [1, 2, 10, 20]
2.5.闭包
概念
函数作为返回值
如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数!
ex:
//数组求和
function lazy_sum(arr) {
var sum = function () {
return arr.reduce(function (x, y) {
return x + y;
});
}
return sum;
}
var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()
f(); // 15
//次幂
function make_pow(n) {
return function (x) {
return Math.pow(x, n);
}
}
// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);
pow2(5); // 25
pow3(7); // 343
2.6.箭头函数
概念
x => x * x
等价于
function (x) {
return x * x;
}
如果要返回一个对象
x => ({ foo: x })
this
箭头函数完全修复了this的指向,this总是指向词法作用域,也就是外层调用者obj
2.7.generator
生成器
一个generator看上去像一个函数,但可以返回多次
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}