Fork me on GitHub

JavaScript笔记

文章概述

本文主要介绍使用Javascript的笔记,涵盖ES6语法(ECMAScript标准委员会会在每年的6月份正式发布一次规范的修订)。

参考资料

ES的官方链接

文档资料

1
2
3
【阮一峰ES6全面介绍GitHub地址】
> 源地址:https://github.com/ruanyf/es6tutorial
> fork地址:https://github.com/cnlius/es6tutorial

基础语法

js代码

内嵌js

将js代码,在head中用包裹;

1
2
3
4
5
<head>
<script>
alert('Hello, world');
</script>
</head>
外联js

将js写到单独的.js文件中,在head内用引入;

1
2
3
<head>
<script src="/static/js/abc.js"></script>
</head>

js调试

chrome

开发者调试界面:

1
在chrome浏览器中打开开发者工具、或者ctrl+shift+i;
控制台
  1. 执行js代码
1
在开发者界面的console选项中,可以直接写js代码,按回车键执行;
  1. 在console界面打印变量值
1
2
1> 在console中,通过console.log(x),按回车,可以直接打印变量值;
2> 在js代码中执行console.log('hello'),来打印一些需要的值;

js基本语句

语句结构
1
每个语句以;结束,语句块用{...}
strict模式

启用strict模式的方法是在JavaScript代码的第一行写上:

1
'use strict';
注释
1
2
// 单行注释
/*多行注释*/
语法规则
  • JavaScript 对大小写是敏感的。
  • 忽略多余的空格。
  • 文本字符串中使用反斜杠(\)对代码行进行换行;
允许语句尾部逗号(ES8)

函数参数列表与调用中的尾部逗号,该特性允许我们在定义或者调用函数时添加尾部逗号而不报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let foo = function (
a,
b,
c,
) {
console.log('a:', a)
console.log('b:', b)
console.log('c:', c)
}
foo(1, 3, 4, )

//输出结果为:
a: 1
b: 3
c: 4

条件判断

  1. 条件判断语句:if(){}else{}
1
其中else语句是可选的。如果语句块只包含一条语句,那么可以省略{};
  1. 多行条件判断: if(){}else if(){}…else{}
  2. switch条件判断:
1
2
3
4
5
6
7
8
9
10
switch(n){
case 1:
执行代码块 1
break;
case 2:
执行代码块 2
break;
default:
n 与 case 1 和 case 2 不同时执行的代码
}

循环语句

for循环
  1. for循环最常用的地方是利用索引来遍历数组:
1
2
3
4
5
6
var arr = ['Apple', 'Google', 'Microsoft'];
var i, x;
for (i=0; i<arr.length; i++) {
x = arr[i];
console.log(x);
}
  1. for循环的3个条件都是可以省略的,如果没有退出循环的判断条件,就必须使用break语句退出循环,否则就是死循环:
1
2
3
4
5
6
7
var x = 0;
for (;;) { // 将无限循环下去
if (x > 100) {
break; // 通过if判断来退出循环
}
x ++;
}
for…in
遍历对象
  • for循环的一个变体是for(…in…)循环,它可以把一个对象的所有属性依次循环出来;
  • for(…in…)遍历的实际上是对象的属性名称,一个Array数组实际上也是一个对象,它的每个元素的索引被视为一个属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};

// 遍历所有属性
for (var key in o) {
console.log(key); // 'name', 'age', 'city'
}

// 过滤继承来的属性
for (var key in o) {
if (o.hasOwnProperty(key)) {
console.log(key); // 'name', 'age', 'city'
}
}
遍历数组
  • Array也是对象,而它的每个元素的索引被视为对象的属性,因此,for … in循环可以直接循环出Array的索引;
  • for…in循环对Array的循环得到的是String而不是Number。
1
2
3
4
5
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循环只有一个判断条件,条件满足,就不断循环,条件不满足时则退出循环。

1
2
3
4
5
6
7
var x = 0;
var n = 99;
while (n > 0) {
x = x + n;
n = n - 2;
}
x; // 2500
do…while
  • do { … } while()循环,它和while循环的唯一区别在于,不是在每次循环开始的时候判断条件,而是在每次循环完成的时候判断条件:
  • do { … } while()循环要小心,循环体会至少执行1次,而for和while循环则可能一次都不执行。
1
2
3
4
5
var n = 0;
do {
n = n + 1;
} while (n < 100);
n; // 100

数据类型

undefined

  • undefined表示值未定义,即变量不含有值;
  • undefined常用在判断函数参数是否传递;
  • 没有toString()方法;
1
2
let x;
typeof x; //undefined

null

  • null表示“空”,不是0,也不是‘’,null表示一个空的值。
  • 判断是否为null,请使用:myVar === null;
  • null没有toString()方法;

数字

Number类型

概述
  • JavaScript不区分整数和浮点数,统一用Number表示;
  • 常见的合法的Number类型:
1
2
3
4
5
6
7
123; // 整数123
0.456; // 浮点数0.456
1.2345e3; // 科学计数法表示1.2345x1000,等同于1234.5
-99; // 负数
NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity
0xff00 // 计算机由于使用二进制,所以,有时候用十六进制表示整数比较方便.
常用语法
常见属性
  • constructor 返回对创建此对象的 Number 函数的引用。
  • MAX_VALUE 可表示的最大的数。
  • MIN_VALUE 可表示的最小的数。
  • NaN 非数字值。
  • NEGATIVE_INFINITY 负无穷大,溢出时返回该值。
  • POSITIVE_INFINITY 正无穷大,溢出时返回该值。
  • prototype 使您有能力向对象添加属性和方法。
类型转换
  • otherType->number: 用parseInt(x)或parseFloat(x)来转换任意类型到number;
  • number->string: number值调用toString(),需要特殊处理:
1
2
123..toString(); // '123', 注意是两个点!
(123).toString(); // '123'
常用方法
  • toString 把数字转换为字符串,使用指定的基数。
  • toLocaleString 把数字转换为字符串,使用本地数字格式顺序。
  • toFixed 把数字转换为字符串,结果的小数点后有指定位数的数字。
  • toExponential 把对象的值转换为指数计数法。
  • toPrecision 把数字格式化为指定的长度。
  • valueOf 返回一个 Number 对象的基本数字值。
数学运算

number可以直接做四则运算,规则和数学一致:

1
2
3
4
5
6
1 + 2; // 3
(1 + 2) * 5 / 2; // 7.5
2 / 0; // Infinity
0 / 0; // NaN
10 % 3; // 1 求余运算
10.5 % 3; // 1.5 求余运算
求幂运算符(ES7)

ES7使用更加简洁的**作为幂运算符:

1
3**2; //求3的2次方,等同于Math.pow(3, 2);

布尔值

概述:一个布尔值只有true、false两种值。

1
JavaScript把null、undefined、0、NaN和空字符串''视为false,其他值一概视为true。
布尔值运算
1
2
3
4
- &&运算是与运算,只有所有都为true,&&运算结果才是true;
- ||运算是或运算,只要其中有一个为true,||运算结果就是true;
- !运算是非运算,它是一个单目运算符,把true变成false,false变成true;
- 比较运算符:>、>=、==、===(推荐使用)、!==;

注意:

1
2
3
4
1> 相等运算符==和===
第一种是==比较,它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果;
第二种是===比较,它不会自动转换数据类型,如果数据类型不一致,返回false,如果一致,再比较。
2> NaN与所有其他值都不相等,包括它自己.唯一能判断NaN的方法是通过isNaN(num)函数;

字符串

概念:字符串是以单引号’或双引号”括起来的任意文本。

常见字符串
转义字符

转义字符\可以转义字符

  • \n 表示换行
  • \t 表示制表符
  • \x## 表示ASCII字符的十六进制形式
1
'\x41'; // 完全等同于 'A'
  • \u####表示一个Unicode字符
1
'\u4e2d\u6587'; // 完全等同于 '中文'
多行字符串
1
最新的ES6标准新增了一种多行字符串的表示方法,用反引号 ` ... ` 表示:

例如:

1
2
3
`这是一个
多行
字符串`;

字符串模板

ES6新增了一种模板字符串,在字符串中使用变量名,会自动替换成字符串中的变量所代表的的值;

例如:

1
2
3
var name = '小明';
var age = 20;
var message = `你好, ${name}, 你今年${age}岁了!`;
常用操作
访问字符串
1
2
3
4
5
6
7
8
9
var s = 'Hello, world!';
// 字符串长度(双字节的汉字会占两位,可以通过for(..of..)方式计算)
s.length; // 13
// 根据索引访问字符串
s[0]; // 'H'
s[6]; // ' '
s[7]; // 'w'
s[12]; // '!'
s[13]; // undefined 超出范围的索引不会报错,但一律返回undefined
大小写转换
  • toUpperCase:返回一个新字符串,等于原来字符串全部变为大写;
1
2
3
var s = 'Hello';
var upper = s.toUpperCase();
upper; // 返回'HELLO'
  • toLowerCase:返回一个新字符串,等于原来字符串全部变为小写;
1
2
3
var s = 'Hello';
var lower = s.toLowerCase(); // 返回'hello'并赋值给变量lower
lower; // 'hello'
索引

indexOf()会搜索指定字符串出现的位置:

1
2
3
var s = 'hello, world';
s.indexOf('world'); // 返回7
s.indexOf('World'); // 没有找到指定的子串,返回-1
真实长度

如果字符串中包含汉字,有些汉字长度是2,此时获取字符串长度用for..of:

1
2
3
4
let len=0;
for(let v of c){
len++;
}
截取

substring()返回指定索引区间的子串:

1
2
3
var s = 'hello, world'
s.substring(0, 5); // 从索引0开始到5(不包括5),返回'hello'
s.substring(7); // 从索引7开始到结束,返回'world'
拷贝

repeat()

  • 参数是大于等于-1的负数或者Infinity,会报错。
  • repeat方法返回一个新字符串,表示将原字符串重复n次。
1
2
3
4
5
6
7
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
'na'.repeat(-0.9) // ""
'na'.repeat(NaN) // ""
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"
字符串补全长度(ES8)
  1. padStart()用于头部补全,padEnd()用于尾部补全;

方法概述:
padStart和padEnd一共接受两个参数:

  • 第一个参数用来指定字符串的最小长度;
  • 第二个参数是用来补全的字符串,如果省略,默认使用空格补全长度;
1
2
3
4
5
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
  1. 如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。
1
2
'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'
  1. 如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。
1
2
'abc'.padStart(10, '0123456789')
// '0123456abc'
  1. padStart用途:
  • padStart的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。
1
2
3
'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"
  • 另一个用途是提示字符串格式。
1
2
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
raw转义和拼接

模板斜杠转义-多字符串拼接

【转义】

对模板字符串中的斜杠进行再次转义;

1
2
3
4
5
String.raw`Hi\n${2+3}!`;
// 返回 "Hi\\n5!"

String.raw`Hi\u000A!`;
// 返回 "Hi\\u000A!"

【拼接】

作为正常的函数使用:String.raw({raw: ‘’},1,2)。
这时,它的第一个参数,应该是一个具有raw属性的对象,且raw属性的值应该是一个数组。

1
2
3
4
5
String.raw({ raw: 'test' }, 0, 1, 2);
// 't0e1s2t'

// 等同于
String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2);
特点
  • 字符串是不可变的,如果对字符串的某个索引赋值,不会改变字符串;
    例如:
1
2
3
var s = 'Test';
s[0] = 'X';
alert(s); // s仍然为'Test'
常用属性
  • constructor:对创建该对象的函数的引用;
  • length:字符串的长度
  • prototype:允许您向对象添加属性和方法
常用方法
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
方法	描述
anchor() 创建 HTML 锚。
big() 用大号字体显示字符串。
blink() 显示闪动字符串。
bold() 使用粗体显示字符串。
charAt() 返回在指定位置的字符。
charCodeAt() 返回在指定的位置的字符的 Unicode 编码。
concat() 连接字符串。
fixed() 以打字机文本显示字符串。
fontcolor() 使用指定的颜色来显示字符串。
fontsize() 使用指定的尺寸来显示字符串。
fromCharCode() 从字符编码创建一个字符串。
indexOf() 检索字符串。
italics() 使用斜体显示字符串。
lastIndexOf() 从后向前搜索字符串。
link() 将字符串显示为链接。
localeCompare() 用本地特定的顺序来比较两个字符串。
match() 找到一个或多个正则表达式的匹配。
replace() 替换与正则表达式匹配的子串。
search() 检索与正则表达式相匹配的值。
slice() 提取字符串的片断,并在新的字符串中返回被提取的部分。
small() 使用小字号来显示字符串。
split() 把字符串分割为字符串数组。
strike() 使用删除线来显示字符串。
sub() 把字符串显示为下标。
substr() 从起始索引号提取字符串中指定数目的字符。
substring() 提取字符串中两个指定的索引号之间的字符。
sup() 把字符串显示为上标。
toLocaleLowerCase() 把字符串转换为小写。
toLocaleUpperCase() 把字符串转换为大写。
toLowerCase() 把字符串转换为小写。
toUpperCase() 把字符串转换为大写。
toSource() 代表对象的源代码。
toString() 返回字符串。
valueOf() 返回某个字符串对象的原始值。

数组

Array提供了一种顺序存储一组元素的功能,并可以按索引来读写。

概述
  • JavaScript的Array可以包含任意数据类型,并通过索引来访问每个元素。
  • 数组是一组按顺序排列的集合,集合的每个值称为元素。
  • JavaScript的数组可以包括任意数据类型。
  • 多维数组:如果数组的某个元素又是一个Array,则可以形成多维数组;
创建数组
1
2
3
4
5
6
// 通过[]创建(推荐)
[1, 2, 3.14, 'Hello', null, true];
// 通过Array创建
new Array(1, 2, 3); // 创建了数组[1, 2, 3]
// 多维数组
var arr = [[1, 2, 3], [400, 500, 600], '-'];
操作数组
索引
  • 通过索引来访问数组元素;
  • 如果通过索引赋值时,索引超过了范围,会引起Array大小的变化;
  • Array也可以通过indexOf()来搜索一个指定的元素的位置;
  • 判断Array要使用Array.isArray(arr);
1
2
3
4
5
6
7
8
9
10
11
12
13
// 通过索引来访问
var arr = [1, 2, 3.14, 'Hello', null, true];
arr[0]; // 返回索引为0的元素,即1
arr[5]; // 返回索引为5的元素,即true
arr[6]; // 索引超出了范围,返回undefined

// 索引赋值改变数组大小
var arr = [1, 2, 3];
arr[5] = 'x';
arr; // arr变为[1, 2, 3, undefined, undefined, 'x']

// 获取元素所在数组的索引
arr.indexOf('hello');
长度
  • 直接通过arr.length获取数组的长度;
  • 直接给Array的length赋一个新的值会导致Array大小的变化;
1
2
3
4
5
6
7
8
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]
截取

通过slice()截取Array的部分元素,然后返回一个新的Array;

1
2
3
4
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']
arr.slice(); // 不给slice()传递任何参数,它就会从头到尾截取所有元素,相当于copy。
增删元素
  1. push()向Array的末尾添加若干元素,pop()则把Array的最后一个元素删除掉;
  2. 如果要往Array的头部添加若干元素,使用unshift()方法,shift()方法则把Array的第一个元素删掉;
  3. splice()方法是修改Array的“万能方法”,它可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素;

注意:

  • 空数组继续pop或shift不会报错,而是返回undefined;
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
//【操作尾部】
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; // []

//【操作头部】
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; // []

// 【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']
排序
  • sort()排序,直接调用时,按照默认升序排序;
  • Array的sort()方法默认把所有元素先转换为String再排序;
1
2
3
var arr = ['B', 'C', 'A'];
arr.sort();
arr; // ['A', 'B', 'C']
反转

reverse()把整个Array的元素给掉个个,也就是反转;

1
2
3
var arr = ['one', 'two', 'three'];
arr.reverse();
arr; // ['three', 'two', 'one']
连接字符串
  • concat()方法把当前的Array和另一个Array连接起来,并返回一个新的Array;
  • 实际上,concat()方法可以接收任意个元素和Array,并且自动把Array拆开,然后全部添加到新的Array里;
1
2
3
4
5
6
7
var arr = ['A', 'B', 'C'];
var added = arr.concat([1, 2, 3]);
added; // ['A', 'B', 'C', 1, 2, 3]
arr; // ['A', 'B', 'C']

var arr = ['A', 'B', 'C'];
arr.concat(1, 2, [3, 4]); // ['A', 'B', 'C', 1, 2, 3, 4]
连接子元素

join()方法是一个非常实用的方法,它把当前Array的每个元素都用指定的字符串连接起来,然后返回新的连接后的字符串;

1
2
var arr = ['A', 'B', 'C', 1, 2, 3];
arr.join('-'); // 'A-B-C-1-2-3'
常用属性
  • constructor 返回对创建此对象的数组函数的引用。
  • length 设置或返回数组中元素的数目。
  • prototype 使您有能力向对象添加属性和方法。
常用方法
concat()

连接两个或更多的数组,并返回结果。

join()

把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。

pop()

删除并返回数组的最后一个元素

push()

向数组的末尾添加一个或更多元素,并返回新的长度。

reverse()

颠倒数组中元素的顺序。

shift()

删除并返回数组的第一个元素

slice()

从某个已有的数组返回选定的元素

sort()

对数组的元素进行排序

splice()

删除元素,并向数组添加新元素。

toSource()

返回该对象的源代码。

toString()

把数组转换为字符串,并返回结果。

toLocaleString()

把数组转换为本地数组,并返回结果。

unshift()

向数组的开头添加一个或更多元素,并返回新的长度。

valueOf()

返回数组对象的原始值

索引

indexOf(element):
返回的是某个元素在数组中的下标值,如果值大于-1,说明元素在数组里。

1
2
【注意】
> indexOf(NaN)=-1,即使NaN存在于数组中;
包含(ES7)

ES7新增原型方法:查找一个值在不在数组里,若在,则返回true,反之返回false。

1
Array.prototype.includes();

  1. 特点:
1
2
> 在判断 +0 与 -0 时,被认为是相同的。
> includes()只能判断简单类型的数据,对于复杂类型的数据,比如对象类型的数组,二维数组,这些,是无法判断的。
  1. 语法:
1
2
3
4
Array.prototype.includes(searchElement,fromIndex):
【参数说明】
> searchElement: 要搜索的值;
> fromIndex: 搜索的开始索引,默认索引值为0;
  1. 示例:
1
2
3
4
5
let array = ['a', 'b', 'c',+0];
console.log(array.includes('b')); //true
console.log(array.includes('d')); //false
console.log(array.includes('c',2)); //false
console.log(array.includes(-0)); //true
  1. 对比indexOf():

使用场景:由于它对NaN的处理方式与indexOf不同,假如你只想知道某个值是否在数组中而并不关心它的索引位置,建议使用includes()。如果你想获取一个值在数组中的位置,那么你只能使用indexOf方法。

对象

JavaScript的对象是一组由键-值组成的无序集合.

概念与访问
  • JavaScript的对象是一组由键-值组成的无序集合.
  • JavaScript用一个{…}表示一个对象,键值对以xxx: xxx形式申明,用,隔开。注意,最后一个键值对不需要在末尾加”,”。
  • JavaScript对象的键都是字符串类型,值可以是任意数据类型。
  • 获取对象的属性,用对象变量.属性名的方式;
  • 如果属性名(键)包含特殊字符,就必须用’’括起来,并且,访问这个属性也无法使用.操作符,必须用[‘xxx’]来访问;
  • es6中:
1
2
3
1. 变量名可以直接作为对象成员;
2. 对象中的方法可以简写成函数的定义;
3. 表达式可以作为属性名和方法名;
示例:
  • es5写法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建对象
var person = {
name: 'Bob',
age: 20,
tags: ['js', 'web', 'mobile'],
city: 'Beijing',
hasCar: true,
zipcode: null,
'middle-school': 'No.1 Middle School'
};
// 获取对象属性
person.name; // 'Bob'
person.zipcode; // null
person['name'];
person['middle-school']; // No.1 Middle School
  • es6写法:
1
2
3
4
5
6
7
8
9
10
let name='lily'
var person = {
name, //外部变量
getName(){ //方法简写
return this.name;
},
['get'+'Age'](){ //表达式
return this.age;
}
};
对象的方法
增删属性

由于JavaScript的对象是动态类型,你可以自由地给一个对象添加或删除属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var 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属性也不会报错
包含
  • 如果我们要检测对象是否拥有某一属性,可以用in操作符,此属性可能是继承来的;
  • 判断一个属性是否是xiaoming自身拥有的,而不是继承得到的,可以用hasOwnProperty()方法;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var xiaoming = {
name: '小明',
birth: 1990,
school: 'No.1 Middle School',
height: 1.70,
weight: 65,
score: null
};
'name' in xiaoming; // true
'grade' in xiaoming; // false
// 继承(object)来的属性
'toString' in xiaoming; // true

// 判断是否是自身的属性
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false

Map

ES6引入Map是一组键值对的结构,具有极快的查找速度。

使用方法

初始化Map需要一个二维数组,或者直接初始化一个空Map。

1
2
var m = new Map(); // 空Map
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
常用操作
  • 一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值覆盖:
1
2
3
4
5
6
7
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'
m.get('Adam'); // undefined
遍历
  1. for…of
  2. forEach
  3. 解构赋值

Set

Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。

简介
  • 要创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set;
  • 重复元素在Set中自动被过滤;
1
2
3
4
5
6
// 创建
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"}
常用操作
1
2
3
4
5
6
7
8
9
10
11
12
13
// 增加
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}

s.has(3); // false

iterable

概述
  • 遍历Array可以采用下标循环,遍历Map和Set就无法使用下标。
  • 为了统一集合类型,ES6标准引入了新的iterable类型,Array、Map和Set都属于iterable类型。
  • 具有iterable类型的集合可以通过新的for…of循环来遍历。
  • for…of循环是ES6引入的新的语法,只循环集合内的元素本身值;
遍历元素
for…of
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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]);
}

// 仅遍历集合自身元素值
var a = ['A', 'B', 'C'];
a.name = 'Hello'; // a的对象属性
for (var x of a) {
console.log(x); // 'A', 'B', 'C'
}
forEach

更好的遍历集合的方式是直接使用iterable内置的forEach方法,它接收一个函数,每次迭代就自动回调该函数;

forEach接收的函数不能break;

  1. 遍历Array:
1
2
3
4
5
6
7
var a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
console.log(element + ', index = ' + index);
});
  1. 遍历Set:
1
Set与Array类似,但Set没有索引,因此回调函数的前两个参数都是元素本身:
1
2
3
4
var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, sameElement, set) {
console.log(element);
});
  1. 遍历Map:
1
Map的回调函数参数依次为value、key和map本身
1
2
3
4
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
console.log(value);
});
  1. 可以忽略无用参数
1
如果对某些参数不感兴趣,由于JavaScript的函数调用不要求参数必须一致,因此可以忽略它们。例如,只需要获得Array的element:
1
2
3
4
var a = ['A', 'B', 'C'];
a.forEach(function (element) {
console.log(element);
});

变量

var变量

变量的定义:
  • 变量在JavaScript中就是用一个变量名表示;
  • 申明一个变量用var语句。
  • 变量名是大小写英文、数字、$和_的组合,且不能用数字开头。
  • 变量名也不能是JavaScript的关键字,如if、while等。
  • 变量不仅可以是数字,还可以是任意数据类型。

例如:

1
2
3
4
5
var a; // 申明了变量a,此时a的值为undefined
var $b = 1; // 申明了变量$b,同时给$b赋值,此时$b的值为1
var s_007 = '007'; // s_007是一个字符串
var Answer = true; // Answer是一个布尔值true
var t = null; // t的值是null

全局变量

不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性:

1
2
3
var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'
变量注意事项
  • 如果一个变量没有通过var申明就被使用,那么该变量就自动被申明为全局变量;
  • 使用var申明的变量则不是全局变量,它的范围被限制在该变量被申明的函数体内,同名变量在不同的函数体内互不冲突。
  • 在strict模式下运行的JavaScript代码,强制通过var申明变量;
  • var存在变量提升问题:在函数中,JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部,在使用到该变量,但是该变量没有定义时,返回的是undefined,不报错;
  • 判断某个全局变量是否存在用typeof window.myVar === ‘undefined’;
  • 函数内部判断某个变量是否存在用typeof myVar === ‘undefined’。
1
'use strict';

ES6变量

let

ES6 推荐使用let命令来声明变量;

  • let声明的变量,在let命令所在的块级作用域内有效,var在整个函数全局有效;
  • let声明的变量,在同一块级作用域中,不可以在声明之前使用。
  • let声明的变量,在同一块级作用域中不能重复声明;

let和var区别示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// a[i]中的i是函数全局的,所有数组a的成员里面的i,指向的都是同一个i;
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10

// let 声明的变量仅在块级作用域内有效
let b = [];
for (let i = 0; i < 10; i++) {
b[i] = function () {
console.log(i);
};
}
b[6](); // 10

常量

由于var和let申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”:

1
var PI = 3.14;
const

ES6 新增了const命令,用来声明不变的常量;

  • const声明一个只读的常量,一旦声明,就必须立即初始化,不能留到以后赋值。常量的值不能改变。
  • const的作用域与let命令相同:只在声明所在的块级作用域内有效。
  • const命令声明的常量也是不提升到全局,同样存在暂时性死区,只能在声明的块级作用域内使用。
  • const声明的常量,也与let一样不可重复声明。
  • const声明的变量值不变,对于简单的数据类型(数值、字符串、布尔值),变量指向的内存地址不变;对于复合类型的数据(数组和对象),变量指向的内存地址保存的指针不变,指针指向的是数据结构,数据结构的变化不受约束。

注意:

  1. const数组对象的数据结构可以改动;
1
2
3
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;

解构赋值

概念
  • 解构赋值,可以同时对一组变量进行赋值。
  • 左边模式=右边模式,以模式匹配的写法,即等号两边的模式相同,左边模式的变量会被赋值为右边模式对应的值。

解构要点:

1
2
3
4
5
6

- 解构不成功,变量的值就等于undefined。
- 如果等号左边的变量数量多于右边模式数组元素的数量,左边对应不到值的变量结构不成功值就是undefined。
- 只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
- 解构赋值允许指定默认值,只有数组成员严格等于undefined,默认值才会生效(注意:null不严格等于undefined)。
- 解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。

不完全解构

即等号左边的模式只匹配一部分的等号右边的数组,可以结构成功。

1
2
3
4
5
6
7
8
let [x, y] = [1, 2, 3];
x // 1
y // 2
// -----------------------------
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
解构目标类型
数组
  1. 简单数组
1
let [a, b, c] = [1, 2, 3]; // 结果:a=1 b=2 c=3
  1. 间隔性赋值(忽略前两个元素,只对第三个元素赋值)
1
let [ , , third] = ["foo", "bar", "baz"]; // 结果:third="baz"
  1. 嵌套数组
1
2
3
4
5
6
7
8
9
10
11
let [foo, [[bar], baz]] = [1, [[2], 3]]; // 结果:foo=1 bar=2 baz=3


let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
  1. 数组解构赋值设置默认值
1
2
3
4
5
6
7
8
9
10
let [foo = true] = []; // foo=true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
let [x = 1] = [null]; // x=null

// 表达式作为数组解构赋值默认值
function f() {
console.log('aaa');
}
let [x = f()] = [1];
  1. 数组按索引解构赋值给对象
1
2
3
4
5
let arr = [1, 2, 3];
// 方括号这种写法,属于“属性名表达式
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3
字符串
1
2
3
4
5
6
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
Set
1
2
let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a"
对象
  • 被解构的对象的属性可以没有次序,变量必须与属性同名,才能取到正确的值。
  1. 简单对象
1
2
3
4
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
baz // undefined
  1. 对象属性别名赋值
1
2
// 对象解构赋值的实质是下面这种形式的简写({属性名:属性别名}={...})
let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };

别名解构赋值

1
2
3
4
5
6
7
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

  1. 嵌套结构的对象
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
// 简单嵌套
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
// p是模式,不是变量
let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

// 复杂嵌套对象的解构赋值
const node = {
loc: {
start: {
line: 1,
column: 5
}
}
};

let { loc, loc: { start }, loc: { start: { line }} } = node;
line // 1
loc // Object {start: Object}
start // Object {line: 1, column: 5}
/*
上面代码有三次解构赋值,分别是对loc、start、line三个属性的解构赋值。
注意,最后一次对line属性的解构赋值之中,只有line是变量,loc和start都是模式,不是变量。
*/
  1. 属性与变量冲突

有些时候,如果变量已经被声明了,再次赋值的时候,正确的写法也会报语法错误,这是因为JavaScript引擎把{开头的语句当作了块处理,于是赋值不再合法。

解决方法是用小括号括起来:

1
2
var x, y;
({x, y} = { name: '小明', x: 100, y: 200});
  1. 指定默认值

同数组解构赋值指定默认值约束类似;

1
2
3
4
5
6
7
8
9
10
11
var {x = 3} = {};
x // 3
var {x, y = 5} = {x: 1};
x // 1
y // 5
var {x: y = 3} = {};
y // 3
var {x: y = 3} = {x: 5};
y // 5
var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"
  1. 长度

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

1
2
let {length : len} = 'hello';
len // 5

数值和布尔值

数值和布尔值的解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

1
2
3
4
5
6
// 数值和布尔值的包装对象都有toString属性,因此变量s值等于对应类型的toString;
let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true
函数
  1. 对函数的参数解构赋值
1
2
3
4
5
6
7
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
// -------------------
[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]
  1. 参数默认值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 为函数move的参数变量指定默认值
function move({x = 0, y = 0} = {}) {
return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
// ---------------------------------
// 为函数move的参数指定默认值,而不是为变量x和y指定默认值
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
Iterator

具有 Iterator 接口的数据类型都可以被结构赋值;

Generator函数

Generator函数,原生具有 Iterator 接口。解构赋值会依次从这个接口获取值。

1
2
3
4
5
6
7
8
9
10
11
function* fibs() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}

let [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5
用途
交换变量的值
1
2
3
4
let x = 1;
let y = 2;

[x, y] = [y, x];
从函数返回多个值

函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
函数参数的定义
1
2
3
4
5
6
7
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
提取JSON数据

解构赋值对提取 JSON 对象中的数据,尤其有用。

1
2
3
4
5
6
7
8
9
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};

let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
函数参数的默认值

指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || ‘default foo’;这样的语句。

1
2
3
4
5
6
7
8
9
10
11
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
}) {
// ... do stuff
};
遍历Map结构

任何部署了 Iterator 接口的对象,都可以用for…of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。

1
2
3
4
5
6
7
8
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world

如果只想获取键名,或者只想获取键值,可以写成下面这样。

1
2
3
4
5
6
7
8
// 获取键名
for (let [key] of map) {
// ...
}
// 获取键值
for (let [,value] of map) {
// ...
}
取出模板库成员

加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。

1
const { SourceMapConsumer, SourceNode } = require("source-map");

函数

函数语法

普通函数
概述
  • function指出这是一个函数定义;
  • abs是函数的名称;
  • (x)括号内列出函数的参数,多个参数以,分隔;
  • { … }之间的代码是函数体,可以包含若干语句,甚至可以没有任何语句。
  • return时,函数就执行完毕,并将结果返回;
  • 如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined。
  • abs()函数实际上是一个函数对象,而函数名abs可以视为指向该函数的变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}

// 调用函数
abs(10); // 返回10
// 调用函数,多传参数不会报错;
abs(10, 'blablabla'); // 返回10
// 调用函数少传参数,返回NaN
abs(); // abs(x)函数的参数x将收到undefined,计算结果为NaN。
匿名函数
1
2
3
4
5
6
7
8
9
10
11
12
// 在这种方式下,function (x) { ... }是一个匿名函数,它没有函数名。
// 但是,这个匿名函数赋值给了变量abs,所以,通过变量abs就可以调用该函数。
var abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}; // 函数体末尾加一个;

// 调用
abs(-19); // 19
自执行函数

我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,关键是这种机制不会污染全局对象。

自执行函数,即定义和调用合为一体。

写法
1
2
3
4
5
6
7
8
9
10
11
12
(function () { /* code */ } ()); // 推荐使用这个
(function () { /* code */ })(); // 但是这个也是可以用的

//如果你不在意返回值,或者不怕难以阅读,甚至可以在function前面加一元操作符号。
!function () { /* code */ } ();
~function () { /* code */ } ();
-function () { /* code */ } ();
+function () { /* code */ } ();

//可以使用new关键字,但不确定它的效率
new function () { /* code */ }
new function () { /* code */ } () // 如果需要传递参数,只需要加上括弧()
示例
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
function(x){
alert(x);
}(5);//报错,function name expected

var aa = function(x){
alert(x);
}(1);//1

true && function(x){
alert(x);
}(2);//2

0, function(x){
alert(x);
}(3);//3

!function(x){
alert(x);
}(4);//4

~function(x){
alert(x);
}(5);//5

-function(x){
alert(x);
}(6);//6

+function(x){
alert(x);
}(7);//7

new function (){
alert(8);//8
}

new function (x){
alert(x);
}(9);//9
函数参数检查

避免参数undefined,可以对参数进行检查:

1
2
3
4
5
6
7
8
9
10
function abs(x) {
if (typeof x !== 'number') {
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
全参关键字
  • JavaScript函数默认隐藏的关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。
  • arguments类似Array但它不是一个Array;
1
2
3
4
5
6
7
8
// 遍历参数
function foo(x) {
console.log('x = ' + x); // 10
for (var i=0; i<arguments.length; i++) {
console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);
  • 利用arguments,你可以获得调用者传入的参数个数和所有参数,包括传入的不需要的参数。
1
2
3
4
5
6
7
8
9
10
11
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
  • arguments[i]类似Array通过索引获取具体参数值;
多余参数

ES6标准引入了rest参数(…rest),将多余的参数放到Array里;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 []
函数返回多个值

return多行

1
2
3
4
5
function foo() {
return { // 这里不会自动加分号,因为{表示语句尚未结束
name: 'foo'
};
}

作用域

全局作用域
  • JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误;
  • 以变量方式var foo = function () {}定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到window对象;
1
2
3
4
5
6
function foo() {
alert('foo');
}

foo(); // 直接调用foo()
window.foo(); // 通过window.foo()调用
名字空间

不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中,即把自己的代码全部放入唯一的名字空间xxx中(许多著名的JavaScript库都是这么干的:jQuery,YUI,underscore等等)。

示例:

1
2
3
4
5
6
7
8
9
10
11
// 唯一的全局变量MYAPP:
var MYAPP = {};

// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// 其他函数:
MYAPP.foo = function () {
return 'foo';
};

局部作用域

let替代var即可

方法

概念
  • 给对象的某个属性绑定函数,称为这个对象的方法;
  • 对象属性绑定外部函数,无参数时,直接用函数名赋值;
1
2
3
4
5
6
7
8
9
10
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是一个特殊变量,它始终指向当前对象;
  • this没有确切的对象时,指向最上层的window;
  • 方法的内部函数中的this,默认指向window,可以在方法顶层用var that = this;来指向当前对象;
  • 对象.方法():对象调用方法时,this指向此对象;
1
2
3
4
5
6
7
8
9
10
11
12
13
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var that = this; // 在方法内部一开始就捕获this
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - that.birth; // 用that而不是this
}
return getAgeFromBirth();
}
};
xiaoming.age(); // 25
对象外部函数使用
  • 在对象外部的函数使用this时,如果函数不依赖于对象,此时this指向window;
  • 函数本身的apply方法绑定对象到函数:它接收两个参数: 第一个参数就是需要绑定的this变量,第二个参数是Array。即apply()把参数打包成Array再传入函数中;
1
2
3
4
5
6
7
8
9
10
11
12
13
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, 参数为空
  • 函数本身的call()方法绑定对象到函数:它接收n个参数: 第一个参数就是需要绑定的this变量,之后的参数按顺序传入函数。
1
2
3
// 对普通函数调用,我们通常把this绑定为null。
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5

高阶函数

定义

一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

简单的高阶函数
1
2
3
function add(x, y, f) {
return f(x) + f(y);
}
map

map()方法定义在JavaScript的Array中,调用Array的map()方法,自己的函数,得到了一个新的Array作为结果;

1
2
3
4
5
6
7
8
9
10
// 把Array的所有数字平方:
function pow(x) {
return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]

// 把Array的所有数字转为字符串:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
reduce

Array的reduce()把一个函数作用在这个Array的[x1, x2, x3…]元素成员上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算;

reduce效果公式
1
[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
语法

reduce有两个重载方法,第一个参数都是接受一个函数(数组成员操作逻辑),第二个参数是一个初始值;

1
2
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;

示例
1
2
3
4
5
6
7
8
9
10
11
// 对一个Array求和
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
return x + y;
}); // 25

// 把[1, 3, 5, 7, 9]变换成整数13579
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
return x * 10 + y;
}); // 13579
filter

“筛选”函数。

概念
  • 用于把Array的某些元素过滤掉,然后返回剩下的元素;
  • 和map()类似,Array的filter()也接收一个函数。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
  • filter()接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示Array的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身:
1
2
3
4
5
6
7
var arr = ['A', 'B', 'C'];
var r = arr.filter(function (element, index, self) {
console.log(element); // 依次打印'A', 'B', 'C'
console.log(index); // 依次打印0, 1, 2
console.log(self); // self就是变量arr
return true;
});
示例
  1. Array中,删掉偶数,只保留奇数:
1
2
3
4
5
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]
  1. Array中的空字符串删掉
1
2
3
4
5
var arr = ['A', '', 'B', null, undefined, 'C', '  '];
var r = arr.filter(function (s) {
return s && s.trim(); // 注意:IE9以下的版本没有trim()方法
});
r; // ['A', 'B', 'C']
  1. 巧妙地去除Array的重复元素
1
2
3
4
5
// 去除重复元素依靠的是indexOf总是返回第一个元素的位置,后续的重复元素位置与indexOf返回的位置不相等,因此被filter滤掉了
var arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];
var r = arr.filter(function (element, index, self) {
return self.indexOf(element) === index;
});
sort
  • Array的sort()方法默认把所有元素先转换为String再排序,默认升序;
  • 默认情况下,对字符串排序,是按照ASCII的大小比较的;
  • sort()方法也作为高阶函数,可以接收一个比较函数来实现自定义的排序。
  • 对于两个元素x和y的比较,如果认为x < y,则返回-1,如果认为x == y,则返回0,如果认为x > y,则返回1;

注意:

1
sort()方法会直接对Array进行修改,它返回的结果仍是当前Array;

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
// 升序排序
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {
return -1;
}
if (x > y) {
return 1;
}
return 0;
});
console.log(arr); // [1, 2, 10, 20]

// 倒序排序
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {
return 1;
}
if (x > y) {
return -1;
}
return 0;
}); // [20, 10, 2, 1]

// 忽略大小写排序
var arr = ['Google', 'apple', 'Microsoft'];
arr.sort(function (s1, s2) {
x1 = s1.toUpperCase();
x2 = s2.toUpperCase();
if (x1 < x2) {
return -1;
}
if (x1 > x2) {
return 1;
}
return 0;
}); // ['apple', 'Google', 'Microsoft']

闭包

概念
  • 闭包指:一个函数返回了一个函数后,其内部的局部变量还被新函数引用,返回的函数f并没有立刻执行,而是直到调用了f()才执行;
  • 闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。

示例:创建一个闭包函数;

1
2
3
4
5
6
7
8
9
10
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
  • 闭包每次每次调用都会返回一个新的函数,即使参数相同,结果函数也不等;
1
2
3
var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1 === f2; // false
  • 返回函数不要引用任何循环变量,或者后续会发生变化的变量。

示例:创建一个匿名函数并立刻执行:

1
2
3
(function (x) {
return x * x;
})(3);

实例
  1. 利用闭包求次方数;
1
2
3
4
5
6
7
8
9
10
11
12
function make_pow(n) {
return function (x) {
return Math.pow(x, n);
}
}

// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);

console.log(pow2(5)); // 25
console.log(pow3(7)); // 343

箭头函数

概述

ES6标准新增了一种新的函数:Arrow Function(箭头函数)。

特点
  • 箭头函数相当于匿名函数,并且简化了函数定义。
  • this指向:箭头函数没有独立作用域,所以箭头函数中的this指向的是当前箭头函数父对象的作用域;
  • 箭头函数不能用作构造函数;
  • 箭头函数没有prototype属性;
  • 箭头函数的语法格式:
1
2
3
1> 只包含一个表达式,{...}和return都省略掉了。
2> 包含多条语句,不能省略{ ... }和return;
3> 参数只有一个可以省略括号(),参数大于一个时不能省略括号;
示例
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

// 单语句箭头函数
x => x * x

// 相当于
function (x) {
return x * x;
}

// 两个参数
(x, y) => x * x + y * y

// 无参数:
() => 3.14

// 多语句箭头函数
x => {
if (x > 0) {
return x * x;
}
else {
return - x * x;
}
}

// 可变参数:
(x, y, ...rest) => {
var i, sum = x + y;
for (i=0; i<rest.length; i++) {
sum += rest[i];
}
return sum;
}

// 返回一个对象(注意:对象和函数体的{...}有语法冲突,需要用()括起来)
x => ({ foo: x })
this
  • 箭头函数完全修复了this的指向,this总是指向词法作用域,也就是外层调用者obj;
  • 箭头函数完全替代了var that = this; 获取外部调用obj的形式;
1
2
3
4
5
6
7
8
9
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 25
  • 由于this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略;
1
2
3
4
5
6
7
8
9
var obj = {
birth: 1990,
getAge: function (year) {
var b = this.birth; // 1990
var fn = (y) => y - this.birth; // this.birth仍是1990
return fn.call({birth:2000}, year);
}
};
obj.getAge(2015); // 25

内置对象

在JavaScript的世界里,一切都是对象。

Object

常用方法

获取属性名
  • 作用:keys方法返回对象的所有属性名,返回一个数组;
  • 语法:
1
Object.keys(obj); //obj是目标对象;
获取属性值

[Object.values()]

  • 返回自己的键值对中属性的值,返回的数组顺序,也跟Object.entries()保持一致。
1
2
Object.values({ one: 1, two: 2 })            //[1, 2]
Object.values({ 3: 'a', 4: 'b', 1: 'c' }) //['c', 'a', 'b']
对象转二维数组

[Object.entries()]

  • 该方法会将某个对象的可枚举属性与值按照二维数组的方式返回。若目标对象是数组时,则会将数组的下标作为键值返回。
1
2
Object.entries({ one: 1, two: 2 })    //[['one', 1], ['two', 2]]
Object.entries([1, 2]) //[['0', 1], ['1', 2]]

注意:键值对中,如果键的值是Symbol,编译时将会被忽略。

  • Object.entries()返回的数组的顺序与for-in循环保持一致,即如果对象的key值是数字,则返回值会对key值进行排序,返回的是排序后的结果。例如:
1
Object.entries({ 3: 'a', 4: 'b', 1: 'c' })    //[['1', 'c'], ['3', 'a'], ['4', 'b']]
使用场景
  • 对象属性的遍历:
1
2
3
4
5
6
7
8
let obj = { one: 1, two: 2 };
for (let [k,v] of Object.entries(obj)) {
console.log(`${JSON.stringify(k)}: ${JSON.stringify(v)}`);
}

//输出结果如下:
'one': 1
'two': 2
浅拷贝
  • 作用:Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
  • 语法:
1
2
3
4
Object.assign(target, ...sources)
【参数】
- target:目标对象(如果参数不是对象,则会先转成对象,然后返回)。
- sources:源对象。
  • 要点:
1
2
3
4
1. 如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
2. 由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。
3. Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。
4. Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
  • 示例:
1
2
3
4
5
var target = { a: 1 };  
var source1 = { b: 2 };
var source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
属性和属性描述

[Object.getOwnPropertyDescriptors()]

  • 该方法会返回目标对象中所有属性的属性描述符,该属性必须是对象自己定义的,不能是从原型链继承来的。
语法
1
2
3
4
Object.getOwnPropertyDescriptors(obj,'propName')
【参数】
> obj: 对象本身;
> propName: 用引号包裹,obj的属性key;
使用实例
  1. 单个参数:
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
let obj = {
id: 1,
name: 'test',
get gender() {
console.log('gender')
},
set grade(g) {
console.log(g)
}
}
Object.getOwnPropertyDescriptors(obj)

//输出结果为:
{
gender: {
configurable: true,
enumerable: true,
get: f gender(),
set: undefined
},
grade: {
configurable: true,
enumerable: true,
get: undefined,
set: f grade(g)
},
id: {
configurable: true,
enumerable: true,
value: 1,
writable: true
},
name: {
configurable: true,
enumerable: true,
value: 'test',
writable: true
}
}
  1. 两个参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let obj = {
id: 1,
name: 'test',
get gender() {
console.log('gender')
},
set grade(g) {
console.log(g)
}
}
Object.getOwnPropertyDescriptors(obj, 'id')

//输出结果为:
{
id: {
configurable: true,
enumerable: true,
value: 1,
writable: true
}
}
使用场景-拷贝

Object.getOwnPropertyDescriptors()主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let obj = {
id: 1,
name: 'test',
get gender() {
console.log('gender')
}
}
Object.assign(obj)

//输出结果为:
{
gender: undefined
id: 1,
name: 'test'
}

Object.getOwnPropertyDescriptors方法配合Object.defineProperties方法,就可以实现正确拷贝。

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
let obj = {
id: 1,
name: 'test',
get gender() {
console.log('gender')
}
}
let obj1 = {}
Object.defineProperties(obj1, Object.getOwnPropertyDescriptors(obj))
Object.getOwnPropertyDescriptors(obj1)

//输出结果为:
{
gender: {
configurable: true,
enumerable: true,
get: f gender(),
set: undefined
},
id: {
configurable: true,
enumerable: true,
value: 1,
writable: true
},
name: {
configurable: true,
enumerable: true,
value: 'test',
writable: true
}
}
冻结对象
  • freeze 用来冻结对象属性,此模式下对象添加/更改属性会报错或不起作用;
1
let foo=Object.freeze({});
  • 彻底冻结:遍历对象成员,冻结对象本身和对象的属性:
1
2
3
4
5
6
7
8
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};

对象类型

基础对象类型
  • 用typeof操作符获取对象的类型,它总是返回一个字符串;
  • 用typeof将无法区分出null、Array和通常意义上的object{}。
1
2
3
4
5
6
7
8
9
typeof 123; // 'number'
typeof NaN; // 'number'
typeof 'str'; // 'string'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof Math.abs; // 'function'
typeof null; // 'object'
typeof []; // 'object'
typeof {}; // 'object'
简单类型转换
toString()
  • ECMAScript 定义所有对象都有 toString() 方法,无论它是伪对象,还是真对象。因为 String 类型属于伪对象.
  • null和 undefined不可以;

    1
    2
    3
    4
    5
    arrayObject.toString()
    booleanObject.toString()
    dateObject.toString()
    NumberObject.toString()
    stringObject.toString()
  • Boolean 类型的 toString() 方法只是输出 “true” 或 “false”;

  • Number 类型的 toString() 方法比较特殊,它有两种模式,即默认模式和基模式。
1
2
3
4
5
6
7
8
9
10
// 1. 默认模式,toString()方法只是用相应的字符串输出数字值(无论是整数、浮点数还是科学计数法)的十进制;
var iNum1 = 10;
var iNum2 = 10.0;
alert(iNum1.toString()); //输出 "10"
alert(iNum2.toString()); //输出 "10"
// 2. 按参数类型转换,参数是数值表示几进制;
var iNum = 10;
alert(iNum.toString(2)); //输出 "1010"
alert(iNum.toString(8)); //输出 "12"
alert(iNum.toString(16)); //输出 "A"
转换成数字

parseInt() 和 parseFloat()

1
2
3
4
5
6
7
// 基本转换
var iNum1 = parseInt("56.9"); //返回 56
var fNum3 = parseFloat("11.2"); //返回 11.2
// 按配置的进制转
var iNum2 = parseInt("10", 8); //返回 8

var fNum1 = parseFloat("red"); //返回 NaN
强制类型转换

ECMAScript 中可用的 3 种强制类型转换如下:

  1. Boolean(value) - 把给定的值转换成 Boolean 型;
1
2
3
4
5
6
var b1 = Boolean("");		//false - 空字符串
var b2 = Boolean("hello"); //true - 非空字符串
var b1 = Boolean(50); //true - 非零数字
var b1 = Boolean(null); //false - null
var b1 = Boolean(0); //false - 零
var b1 = Boolean(new object()); //true - 对象
  1. Number(value) - 把给定的值转换成数字(可以是整数或浮点数);
1
2
3
4
5
6
7
8
9
10
用法	结果
Number(false) 0
Number(true) 1
Number(undefined) NaN
Number(null) 0
Number("1.2") 1.2
Number("12") 12
Number("1.2.3") NaN
Number(new object()) NaN
Number(50) 50
  1. String(value) - 把给定的值转换成字符串;

最后一种强制类型转换方法 String() 是最简单的,因为它可把任何值转换成字符串。

1
2
3
var s1 = String(null);	//"null"
var oNull = null;
var s2 = oNull.toString(); //会引发错误

包装对象
概念
  • 形如string对应String,用new关键字来创建string的包装对象;
  • 一般不推荐使用包装对象;
常见的包装对象
1
2
3
var n = new Number(123); // 123,生成了新的包装类型
var b = new Boolean(true); // true,生成了新的包装类型
var s = new String('str'); // 'str',生成了新的包装类型
要点
  • 包装对象后,数据类型已经变为object;
  • 包装对象和原始值用===比较会返回false;
1
2
3
4
5
6
7
8
typeof new Number(123); // 'object'
new Number(123) === 123; // false

typeof new Boolean(true); // 'object'
new Boolean(true) === true; // false

typeof new String('str'); // 'object'
new String('str') === 'str'; // false

Math对象

Math 对象用于执行数学任务。

Math 对象并不像 Date 和 String 那样是对象的类,因此没有构造函数 Math(),像 Math.sin() 这样的函数只是函数,不是某个对象的方法。您无需创建它,通过把 Math 作为对象使用就可以调用其所有属性和方法。

Math对象属性
  • E 返回算术常量 e,即自然对数的底数(约等于2.718)。
  • LN2 返回 2 的自然对数(约等于0.693)。
  • LN10 返回 10 的自然对数(约等于2.302)。
  • LOG2E 返回以 2 为底的 e 的对数(约等于 1.414)。
  • LOG10E 返回以 10 为底的 e 的对数(约等于0.434)。
  • PI 返回圆周率(约等于3.14159)。
  • SQRT1_2 返回返回 2 的平方根的倒数(约等于 0.707)。
  • SQRT2 返回 2 的平方根(约等于 1.414)。
Math对象方法
  • pow(a,b): 返回a的b次方;
  • abs(x) 返回数的绝对值。
  • acos(x) 返回数的反余弦值。
  • asin(x) 返回数的反正弦值。
  • atan(x) 以介于 -PI/2 与 PI/2 弧度之间的数值来返回 x 的反正切值。
  • atan2(y,x) 返回从 x 轴到点 (x,y) 的角度(介于 -PI/2 与 PI/2 弧度之间)。
  • ceil(x) 对数进行上舍入。
  • cos(x) 返回数的余弦。
  • exp(x) 返回 e 的指数。
  • floor(x) 对数进行下舍入。
  • log(x) 返回数的自然对数(底为e)。
  • max(x,y) 返回 x 和 y 中的最高值。
  • min(x,y) 返回 x 和 y 中的最低值。
  • pow(x,y) 返回 x 的 y 次幂。
  • random() 返回 0 ~ 1 之间的随机数。
  • round(x) 把数四舍五入为最接近的整数。
  • sin(x) 返回数的正弦。
  • sqrt(x) 返回数的平方根。
  • tan(x) 返回角的正切。
  • toSource() 返回该对象的源代码。
  • valueOf() 返回 Math 对象的原始值。

日期和时间

Date: 在JavaScript中,Date对象用来表示日期和时间。

Date对象
  • JavaScript的月份范围用整数表示是0~11月;
创建Date对象
  1. 创建默认Date对象:
1
2
// 注释:Date 对象会自动把当前日期和时间保存为其初始值。
var myDate=new Date()
  1. 根据指定的时间创建:
1
2
var d = new Date(2015, 5, 19, 20, 15, 30, 123);
d; // Fri Jun 19 2015 20:15:30 GMT+0800 (CST)
  1. 解析ISO 8601格式的时间为时间戳:
  • 使用Date.parse()时传入的字符串使用实际月份01~12,转换为Date对象后getMonth()获取的月份值为0 ~ 11。
1
2
var d = Date.parse('2015-06-24T19:49:22.875+08:00');
d; // 1435146562875
  1. 根据指定时间戳创建:
1
2
3
var d = new Date(1435146562875);
d; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
d.getMonth(); // 5
时区

Date对象表示的时间总是按浏览器所在时区显示的,不过我们既可以显示本地时间,也可以显示调整后的UTC时间:

1
2
3
var d = new Date(1435146562875);
d.toLocaleString(); // '2015/6/24 下午7:49:22',本地时间(北京时区+8:00),显示的字符串与操作系统设定的格式有关
d.toUTCString(); // 'Wed, 24 Jun 2015 11:49:22 GMT',UTC时间,与本地时间相差8小时
当前时间

注意:当前时间是浏览器从本机操作系统获取的时间,所以不一定准确,因为用户可以把当前时间设定为任何值。

1
2
3
4
5
6
7
8
9
10
11
12
var now = new Date();
now; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
now.getFullYear(); // 2015, 年份
now.getMonth(); // 5, 月份,注意月份范围是0~11,5表示六月
now.getDate(); // 24, 表示24号
now.getDay(); // 3, 表示星期三
now.getHours(); // 19, 24小时制
now.getMinutes(); // 49, 分钟
now.getSeconds(); // 22, 秒
now.getMilliseconds(); // 875, 毫秒数
now.getTime(); // 1435146562875, 以number形式表示的时间戳
Date.now; // 时间戳,老版本IE没有now()方法
Date对象属性
  • constructor 返回对创建此对象的 Date 函数的引用。
  • prototype 使您有能力向对象添加属性和方法。
Date对象方法
  • Date() 返回当日的日期和时间。
  • getDate() 从 Date 对象返回一个月中的某一天 (1 ~ 31)。
  • getDay() 从 Date 对象返回一周中的某一天 (0 ~ 6)。
  • getMonth() 从 Date 对象返回月份 (0 ~ 11)。
  • getFullYear() 从 Date 对象以四位数字返回年份。
  • getYear() 请使用 getFullYear() 方法代替。
  • getHours() 返回 Date 对象的小时 (0 ~ 23)。
  • getMinutes() 返回 Date 对象的分钟 (0 ~ 59)。
  • getSeconds() 返回 Date 对象的秒数 (0 ~ 59)。
  • getMilliseconds() 返回 Date 对象的毫秒(0 ~ 999)。
  • getTime() 返回 1970 年 1 月 1 日至今的毫秒数。
  • getTimezoneOffset() 返回本地时间与格林威治标准时间 (GMT) 的分钟差。
  • getUTCDate() 根据世界时从 Date 对象返回月中的一天 (1 ~ 31)。
  • getUTCDay() 根据世界时从 Date 对象返回周中的一天 (0 ~ 6)。
  • getUTCMonth() 根据世界时从 Date 对象返回月份 (0 ~ 11)。
  • getUTCFullYear() 根据世界时从 Date 对象返回四位数的年份。
  • getUTCHours() 根据世界时返回 Date 对象的小时 (0 ~ 23)。
  • getUTCMinutes() 根据世界时返回 Date 对象的分钟 (0 ~ 59)。
  • getUTCSeconds() 根据世界时返回 Date 对象的秒钟 (0 ~ 59)。
  • getUTCMilliseconds() 根据世界时返回 Date 对象的毫秒(0 ~ 999)。
  • parse() 返回1970年1月1日午夜到指定日期(字符串)的毫秒数。
  • setDate() 设置 Date 对象中月的某一天 (1 ~ 31)。
  • setMonth() 设置 Date 对象中月份 (0 ~ 11)。
  • setFullYear() 设置 Date 对象中的年份(四位数字)。
  • setYear() 请使用 setFullYear() 方法代替。
  • setHours() 设置 Date 对象中的小时 (0 ~ 23)。
  • setMinutes() 设置 Date 对象中的分钟 (0 ~ 59)。
  • setSeconds() 设置 Date 对象中的秒钟 (0 ~ 59)。
  • setMilliseconds() 设置 Date 对象中的毫秒 (0 ~ 999)。
  • setTime() 以毫秒设置 Date 对象。
  • setUTCDate() 根据世界时设置 Date 对象中月份的一天 (1 ~ 31)。
  • setUTCMonth() 根据世界时设置 Date 对象中的月份 (0 ~ 11)。
  • setUTCFullYear() 根据世界时设置 Date 对象中的年份(四位数字)。
  • setUTCHours() 根据世界时设置 Date 对象中的小时 (0 ~ 23)。
  • setUTCMinutes() 根据世界时设置 Date 对象中的分钟 (0 ~ 59)。
  • setUTCSeconds() 根据世界时设置 Date 对象中的秒钟 (0 ~ 59)。
  • setUTCMilliseconds() 根据世界时设置 Date 对象中的毫秒 (0 ~ 999)。
  • toSource() 返回该对象的源代码。
  • toString() 把 Date 对象转换为字符串。
  • toTimeString() 把 Date 对象的时间部分转换为字符串。
  • toDateString() 把 Date 对象的日期部分转换为字符串。
  • toGMTString() 请使用 toUTCString() 方法代替。
  • toUTCString() 根据世界时,把 Date 对象转换为字符串。
  • toLocaleString() 根据本地时间格式,把 Date 对象转换为字符串。
  • toLocaleTimeString() 根据本地时间格式,把 Date 对象的时间部分转换为字符串。
  • toLocaleDateString() 根据本地时间格式,把 Date 对象的日期部分转换为字符串。
  • UTC() 根据世界时返回 1970 年 1 月 1 日 到指定日期的毫秒数。
  • valueOf() 返回 Date 对象的原始值。

正则表达式

创建正则表达式

JavaScript有两种方式创建一个正则表达式:

  1. 第一种方式是直接通过/正则表达式/写出来;
  2. 第二种方式是通过new RegExp(‘正则表达式’)创建一个RegExp对象;
1
2
3
4
5
var re1 = /ABC\-001/;
var re2 = new RegExp('ABC\\-001');

re1; // /ABC\-001/
re2; // /ABC\-001/
操作正则
正则匹配

test()方法:用于测试给定的字符串是否符合条件。

1
2
3
4
var re = /^\d{3}\-\d{3,8}$/;
re.test('010-12345'); // true
re.test('010-1234x'); // false
re.test('010 12345'); // false
切分字符串
1
2
3
4
// 按空格分割字符串
'a b c'.split(/\s+/); // ['a', 'b', 'c']
// 按空格、都好、分号,分割字符串
'a,b;; c d'.split(/[\s\,\;]+/); // ['a', 'b', 'c', 'd']
分组匹配

除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)。

  • 用exec()方法提取出子串来;
  • exec()方法在匹配成功后,会返回一个Array,第一个元素是正则表达式匹配到的整个字符串,后面的字符串表示匹配成功的子串。
  • exec()方法在匹配失败时返回null。
1
2
3
4
// ^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:
var re = /^(\d{3})-(\d{3,8})$/;
re.exec('010-12345'); // ['010-12345', '010', '12345']
re.exec('010 12345'); // null
贪婪匹配
  • 需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。
  • 加个?就可以让正则采用非贪婪匹配(也就是尽可能少匹配);
1
2
var re = /^(\d+?)(0*)$/;
re.exec('102300'); // ['102300', '1023', '00']
全局搜索匹配

全局匹配类似搜索,因此不能使用/^…$/,那样只会最多匹配一次。

JavaScript的正则表达式还有几个特殊的匹配标志;

1
2
3
1> 指定i标志,表示忽略大小写;
2> 指定m标志,表示执行多行匹配。
3> 指定g标志,表示全局匹配。

  1. 表示全局匹配标志g:
1
2
3
var r1 = /test/g;
// 等价于:
var r2 = new RegExp('test', 'g');

全局匹配流程:

全局匹配可以多次执行exec()方法来搜索一个匹配的字符串。当我们指定g标志后,每次运行exec(),正则表达式本身会更新lastIndex属性,表示上次匹配到的最后索引;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var s = 'JavaScript, VBScript, JScript and ECMAScript';
var re=/[a-zA-Z]+Script/g;

// 使用全局匹配:
re.exec(s); // ['JavaScript']
re.lastIndex; // 10

re.exec(s); // ['VBScript']
re.lastIndex; // 20

re.exec(s); // ['JScript']
re.lastIndex; // 29

re.exec(s); // ['ECMAScript']
re.lastIndex; // 44

re.exec(s); // null,直到结束仍没有匹配到

Json

JSON字符串规定必须用双引号””,Object的键也必须用双引号””;

数据类型:
  • number:和JavaScript的number完全一致;
  • boolean:就是JavaScript的true或false;
  • string:就是JavaScript的string;
  • null:就是JavaScript的null;
  • array:就是JavaScript的Array表示方式——[];
  • object:就是JavaScript的{ … }表示方式。
序列化
1
对象转Json字符串;

Json序列化方法:JSON.stringify(aimObj,replacer,space);

  1. aimObj:被序列化对象;
  2. replacer:控制如何筛选对象的键值;
  3. space: json格式化缩进占位符;
示例
  1. 简单序列化
1
2
3
4
5
6
7
8
9
10
11
12
var xiaoming = {
name: '小明',
age: 14,
gender: true,
height: 1.65,
grade: null,
'middle-school': '\"W3C\" Middle School',
skills: ['JavaScript', 'Java', 'Python', 'Lisp']
};
// 对象转json字符串
var s = JSON.stringify(xiaoming);
console.log(s);
  1. 按缩进输出
1
JSON.stringify(xiaoming, null, '  ');
  1. 控制如何筛选对象的键值
  • 仅输出指定的属性,可以传入Array:
1
2
3
4
5
6
7
8
9
10
11
JSON.stringify(xiaoming, ['name', 'skills'], '  ');
结果如下:
{
"name": "小明",
"skills": [
"JavaScript",
"Java",
"Python",
"Lisp"
]
}
  • 传入处理函数(参数是:key,value),这样对象的每个键值对都会被函数先处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function convert(key, value) {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value;
}

JSON.stringify(xiaoming, convert, ' ');

结果如下:
{
"name": "小明",
"age": 14,
"gender": true,
"height": 1.65,
"grade": null,
"middle-school": "\"W3C\" MIDDLE SCHOOL",
"skills": [
"JAVASCRIPT",
"JAVA",
"PYTHON",
"LISP"
]
}
  • 精确控制序列化,可以给xiaoming定义一个toJSON()的方法,调用序列化方法,会直接返回toJson的结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var xiaoming = {
name: '小明',
age: 14,
gender: true,
height: 1.65,
grade: null,
'middle-school': '\"W3C\" Middle School',
skills: ['JavaScript', 'Java', 'Python', 'Lisp'],
toJSON: function () {
return { // 只输出name和age,并且改变了key:
'Name': this.name,
'Age': this.age
};
}
};

JSON.stringify(xiaoming); // '{"Name":"小明","Age":14}'
反序列化
1
把JSON格式的字符串,转化为JavaScript对象;

反序列化方法:JSON.parse(jsonString, function)

  1. jsonString: json字符串;
  2. reviver: 处理反序列化结果;
示例
  1. 简单反序列化;
1
2
3
4
JSON.parse('[1,2,3,true]'); // [1, 2, 3, true]
JSON.parse('{"name":"小明","age":14}'); // Object {name: '小明', age: 14}
JSON.parse('true'); // true
JSON.parse('123.45'); // 123.45
  1. 反序列化前处理逻辑;
1
2
3
4
5
6
7
var obj = JSON.parse('{"name":"小明","age":14}', function (key, value) {
if (key === 'name') {
return value + '同学';
}
return value;
});
console.log(JSON.stringify(obj)); // {name: '小明同学', age: 14}

面向对象编程

对象原型链

原型对象
  • JavaScript对每个创建的对象X都会设置一个原型,指向它的原型对象X.prototype。
  • prototype允许您向对象添加属性和方法;
查找对象属性的原型链
概述

用Obj.xxx访问一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到Object.prototype对象,最后,如果还没有找到,就只能返回undefined。这个过程形成对象的原型链。

实例

访问某对象属性,执行的原型链具体如下:

1
2
Obj ----> Obj.prototype ----> Object.prototype ----> null
// obj.prototype: 对象的原型,包含该对象的所有属性;

创建对象

创建方式
  • 直接用{…}创建一个对象;
  • 构造函数创建对象;

构造函数

创建对象
  1. 创建构造函数并生成对象:
1
2
除了直接用{...}创建一个对象外,JavaScript还可以用一种构造函数的方法来创建对象。
关键字new来调用这个函数,并返回一个对象;
1
2
3
4
5
6
7
8
9
10
11
function Student(name) {
this.name = name;

this.hello = function () {
alert('Hello, ' + this.name + '!');
}
}

var xiaoming = new Student('小明');
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!
  1. 用new Student()创建的对象还从原型上获得了一个constructor属性,它指向函数Student本身:
1
2
3
4
5
6
xiaoming.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student; // true

Object.getPrototypeOf(xiaoming) === Student.prototype; // true

xiaoming instanceof Student; // true
  1. prototype设置共享原型对象的属性:
1
2
3
4
5
6
7
function Student(name) {
this.name = name;
}

Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
};
创建对象注意
1
2
如果不写new,这就是一个普通函数,它返回undefined。
如果写了new,它就变成了一个构造函数,它绑定的this指向新创建的对象,并默认返回this,也就是说,不需要在最后写return this;。
设置属性默认值
1
2
3
4
function Student(props) {
this.name = props.name || '匿名'; // 默认值为'匿名'
this.grade = props.grade || 1; // 默认值为1
}
对象实例类型判断
1
xiaoming instanceof Student; // true
封装创建构造函数对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Student(props) {
this.name = props.name || '匿名'; // 默认值为'匿名'
this.grade = props.grade || 1; // 默认值为1
}

Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
};

function createStudent(props) {
return new Student(props || {})
}

// 使用
var xiaoming = createStudent({
name: '小明'
});

xiaoming.grade; // 1

原型继承

原型继承:把一个对象的原型,指向另一个对象;

原型继承的原型链为:

1
new ChildObject() ----> ChildObject.prototype ----> ParentObject.prototype ----> Object.prototype ----> null

创建方式
__proto__

概述:

1
2
1> 通过obj1.__proto__=obj2,将obj的原型指向obj2,这样obj1就拥有了obj2的属性,包括属性值;
2> 不要直接用obj.__proto__去改变一个对象的原型,并且,低版本的IE也无法使用__proto__;

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var Student = {
name: 'Robot',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
};

var xiaoming = {
name: '小明'
};

// 让xiaoming继承Student,即把xiaoming的原型指向了对象Student;
xiaoming.__proto__ = Student;

// 测试
xiaoming.name; // '小明'
xiaoming.run(); // 小明 is running
Object.create()
  • Object.create()方法可以传入一个原型对象,并创建一个基于该原型的新对象;
  • Object.create()创建的新对象什么属性都没有,因此,我们可以编写一个函数来创建xiaoming;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 原型对象:
var Student = {
name: 'Robot',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
};

function createStudent(name) {
// 基于Student原型创建一个新对象:
var s = Object.create(Student);
// 初始化新对象:
s.name = name;
return s;
}

var xiaoming = createStudent('小明');
xiaoming.run(); // 小明 is running...
xiaoming.__proto__ === Student; // true
中间对象
  1. 借助一个中间对象来实现正确的原型链,这个中间对象的原型要指向Student.prototype;
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
// PrimaryStudent构造函数:
function PrimaryStudent(props) {
// 调用Student构造函数,绑定this变量:
Student.call(this, props);
this.grade = props.grade || 1;
}

// 空函数F:
function F() {
}

// 把F的原型指向Student.prototype:
F.prototype = Student.prototype;

// 把PrimaryStudent的原型指向一个新的F对象,F对象的原型正好指向Student.prototype:
PrimaryStudent.prototype = new F();

// 把PrimaryStudent原型的构造函数修复为PrimaryStudent:
PrimaryStudent.prototype.constructor = PrimaryStudent;

// 继续在PrimaryStudent原型(就是new F()对象)上定义方法:
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};

// 创建xiaoming:
var xiaoming = new PrimaryStudent({
name: '小明',
grade: 2
});
xiaoming.name; // '小明'
xiaoming.grade; // 2

// 验证原型:
xiaoming.__proto__ === PrimaryStudent.prototype; // true
xiaoming.__proto__.__proto__ === Student.prototype; // true

// 验证继承关系:
xiaoming instanceof PrimaryStudent; // true
xiaoming instanceof Student; // true
  1. 继承简化

继承这个动作用一个inherits()函数封装起来,还可以隐藏F的定义,并简化代码:

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
// 封装可复用的继承函数
function inherits(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}

// 父对象
function Student(props) {
this.name = props.name || 'Unnamed';
}

Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}

// 子对象
function PrimaryStudent(props) {
// 调用Student构造函数,绑定this变量:
Student.call(this, props);
this.grade = props.grade || 1;
}

// 实现原型继承链:
inherits(PrimaryStudent, Student);

// 绑定其他方法到PrimaryStudent原型:
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};

关键字class从ES6开始正式被引入到JavaScript中。class的目的就是让定义类更简单。

定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义类
class Student {
constructor(name) {
this.name = name;
}

hello() {
alert('Hello, ' + this.name + '!');
}
}

// 创建对象
var xiaoming = new Student('小明');
xiaoming.hello();
继承
  1. extends则表示原型链对象来继承自Student。
  2. 子类的构造函数参数任意,但是需要通过super(params)来调用父类的构造函数,否则父类的name属性无法正常初始化。
  3. 子类继承父类,就拥有了父类的属性和方法;
1
2
3
4
5
6
7
8
9
10
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 记得用super调用父类的构造方法!
this.grade = grade;
}

myGrade() {
alert('I am at grade ' + this.grade);
}
}

模块

浏览器目前还不支持ES6模块,为了现在就能使用,可以将转为ES5的写法。

js模块化,将每个js文件看做一个模块,可以将js文件中的成员导出,或者导入别的模块的成员;

导入导出

  • 使用export关键字导出js模块中的成员,利用import导入js模块成员;
  • 导入时:可用as定义别名,导入的类要放在最前面,模块成员放在{}内;
  • 同一模块内既可以导入也可以发生导出;
示例

【a.js文件】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//直接导出单个
export let str = 'string';
let obj = {
name: 'jason'
};
let fn = () => {
console.log('es6 fn')
};

//导出多个
export {
obj,
fn
};

//默认导出,导入时可以使用任意名称
export default class Student {
constructor(){
this.age=12;
}
getAge() {
return this.age;
}
};

【b.js文件】

1
2
3
4
5
6
7
8
import Student, {str as type, obj, fn} from "./module";

console.log('str:'+type);
console.log('obj:'+obj.name);
console.log('fn:'+fn);
fn();
let student=new Student();
console.log('Student:'+student.getAge());

浏览器

主流浏览器

  • IE 6~11:国内用得最多的IE浏览器,历来对W3C标准支持差。从IE10开始支持ES6标准;
  • Chrome:Google出品的基于Webkit内核浏览器,内置了非常强悍的JavaScript引擎——V8。由于Chrome一经安装就时刻保持自升级,所以不用管它的版本,最新版早就支持ES6了;
  • Safari:Apple的Mac系统自带的基于Webkit内核的浏览器,从OS X 10.7 Lion自带的6.1版本开始支持ES6,目前最新的OS X 10.11 El Capitan自带的Safari版本是9.x,早已支持ES6;
  • Firefox:Mozilla自己研制的Gecko内核和JavaScript引擎OdinMonkey。早期的Firefox按版本发布,后来终于聪明地学习Chrome的做法进行自升级,时刻保持最新;
  • 移动设备上目前iOS和Android两大阵营分别主要使用Apple的Safari和Google的Chrome,由于两者都是Webkit核心,结果HTML5首先在手机上全面普及(桌面绝对是Microsoft拖了后腿),对JavaScript的标准支持也很好,最新版本均支持ES6。
浏览器差异
  • 不同的浏览器对JavaScript支持的差异主要是,有些API的接口不一样,比如AJAX,File接口。
  • 对于ES6标准,不同的浏览器对各个特性支持也不一样。

浏览器对象

window

  • JavaScript可以获取浏览器提供的很多对象,并进行操作。
  • window对象不但充当全局作用域,而且表示浏览器窗口.
浏览器尺寸属性图示

image

净宽高
1
2
window对象有innerWidth和innerHeight属性,可以获取浏览器窗口的内部宽度和高度。
内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高。
全宽高

outerWidth和outerHeight属性,可以获取浏览器窗口的整个宽高。

浏览器的信息

navigator对象表示浏览器的信息,最常用的属性包括:

  • navigator.appName:浏览器名称;
  • navigator.appVersion:浏览器版本;
  • navigator.language:浏览器设置的语言;
  • navigator.platform:操作系统类型;
  • navigator.userAgent:浏览器设定的User-Agent(用户代理)字符串。
根据浏览器获取信息

navigator的信息可以很容易地被用户修改,所以JavaScript读取的值不一定是正确的。

  • 错误的方式:

很多初学者为了针对不同浏览器编写不同的代码,喜欢用if判断浏览器版本,例如:

1
2
3
4
5
6
var width;
if (getIEVersion(navigator.userAgent) < 9) {
width = document.body.clientWidth;
} else {
width = window.innerWidth;
}
  • 正确的方式

确的方法是充分利用JavaScript对不存在属性返回undefined的特性,直接用短路运算符||计算:

1
var width = window.innerWidth || document.body.clientWidth;

屏幕信息

window.screen 对象包含有关用户屏幕的信息。

screen对象表示屏幕的信息,常用的属性有:

  • screen.width:屏幕宽度,以像素为单位;
  • screen.height:屏幕高度,以像素为单位;
  • screen.colorDepth:返回颜色位数,如8、16、24。
  • screen.availHeight: 可用高度;
URL信息

location

URL信息

location对象获取当前页面的URL信息。

一个完整的URL:

1
http://www.example.com:8080/path/index.html?a=1&b=2#TOP
1
2
3
4
5
6
7
location.href; // 完整地址
location.protocol; // 'http'
location.host; // 'www.example.com'
location.port; // '8080'
location.pathname; // '/path/index.html'
location.search; // '?a=1&b=2'
location.hash; // 'TOP'
加载URL
  • 要加载一个新页面,可以调用location.assign(URL)。
  • 如果要重新加载当前页面,调用location.reload()方法非常方便。
1
2
3
4
5
if (confirm('重新加载当前页' + location.href + '?')) {
location.reload();
} else {
location.assign('/'); // 设置一个新的URL地址
}
history

history对象保存了浏览器的历史记录;

history的方法
  • history.back():用户点击了浏览器的“后退”按钮。
  • history.forwar():用户点击了浏览器的“前进”按钮。
window相关方法
open

window.open() 方法用于打开一个新的浏览器窗口或查找一个已命名的窗口。

1
语法: window.open(URL,name,features,replace)
1
2
3
4
5
6
7
URL	一个可选的字符串,声明了要在新窗口中显示的文档的 URL。如果省略了这个参数,或者它的值是空字符串,那么新窗口就不会显示任何文档。
name 一个可选的字符串,该字符串是一个由逗号分隔的特征列表,其中包括数字、字母和下划线,该字符声明了新窗口的名称。这个名称可以用作标记 <a> 和 <form> 的属性 target 的值。如果该参数指定了一个已经存在的窗口,那么 open() 方法就不再创建一个新窗口,而只是返回对指定窗口的引用。在这种情况下,features 将被忽略。
features 一个可选的字符串,声明了新窗口要显示的标准浏览器的特征。如果省略该参数,新窗口将具有所有标准特征。在窗口特征这个表格中,我们对该字符串的格式进行了详细的说明。
replace
一个可选的布尔值。规定了装载到窗口的 URL 是在窗口的浏览历史中创建一个新条目,还是替换浏览历史中的当前条目。支持下面的值:
true - URL 替换浏览历史中的当前条目。
false - URL 在浏览历史中创建新的条目。
概念
  • cookie是以分号分割的多个k-v字段组成的字符串;
  • cookie存储是用户端保存请求信息的机制;
  • 存储在本地的加密文件里,用户不能直接访问,需要用浏览器访问;
  • cookie的存储有路径和域名的限制,一个请求只能操作自己有权限的cookie;
  • document.cookie.toString获取的是所有cookies的key+value拼接字符串;
cookie信息
  • name: cookie的名称;
  • domain: cookie生效的域名,这个域名有作用域的限制,如:一个二级域名可以访问一级域名的cookie,但是不能操作其他二级域名的cookie,也不能操作所属的三级域名的cookie;
  • path: cookie的生效路径,比如同一个域名下不同路径的cookie也是无法操作的;
  • expires:cookie的过期时间,超过这个时间cookie就会被删除,如果不设置此属性,cookie只会存在于一次会话中,那么浏览器关闭时cookie就会被删除;
  • HttpOnly: 表示cookie是由服务端设置的,不允许在用户端进行更改,这种类型的cookie,js无法进行操作。
设置cookie
1
2
3
4
5
6
7
// 设置cookie
function setCookie(keyName, value) {
var days = 30;
var exp = new Date();
exp.setTime(exp.getTime() + days);
document.cookie = name + "=" + escape(value) + ";expires=" + exp.getDate();
}
获取cookis
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function getXCookie(key) {
alert(document.cookie.toString());
if (document.cookie.length > 0) {
let key_start = document.cookie.indexOf(key + "=");
if (key_start !== -1) {
key_start = key_start + key.length + 1;
let key_end = document.cookie.indexOf(";", key_start);
if (key_end === -1) {
key_end = document.cookie.length;
}
alert(document.cookie.substring(key_start, key_end))
}
} else {
alert("no cookie");
}
}

session

  • session是服务端保存请求信息的机制;

    session是用在服务端记录请求者身份的,服务端接到http请求后,服务端生成一个sessionId,这个sessionId种到用户的cookie里,这样同一用户再发一次这个请求,服务端根据cookie就知道是哪个用户发来的。

  • sessionId并不一定都是用cookie实现的,也可以放在请求参数里,或者在http请求头里开辟一个token字段。

  • 会话由浏览器控制,如果没有设置过期时间,会话结束,session就失效;

消息框

可以在 JavaScript 中创建三种消息框:警告框、确认框、提示框。

警告框
  • 警告框经常用于确保用户可以得到某些信息。
  • 当警告框出现后,用户需要点击确定按钮才能继续进行操作。
1
alert("文本");
确认框
  • 确认框用于使用户可以验证或者接受某些信息。
  • 当确认框出现后,用户需要点击确定或者取消按钮才能继续进行操作。
  • 如果用户点击确认,那么返回值为 true。如果用户点击取消,那么返回值为 false。
1
2
3
4
5
6
7
8
confirm("文本")

var result = confirm("确定要这么做吗?");
if(result){//true
alert('点了确定');
} else { //false
alert('点了取消');
}
提示框
  • 提示框经常用于提示用户在进入页面前输入某个值。
  • 当提示框出现后,用户需要输入某个值,然后点击确认或取消按钮才能继续操纵。
  • 如果用户点击确认,那么返回值为输入的值。如果用户点击取消,那么返回值为 null。
1
prompt("文本","默认值")

HTTP

路由

路由的种类
  1. 页面路由:
  2. hash路由:
  3. H5路由:
页面路由

直接在当前页面进行跳转,js代码如下:

1
2
3
4
//当前页面跳转到指定页面:
window.location.href='https://www.baidu.com/';
//回退功能,回退到上一页:
history.back();

hash路由

通过window.location方法直接指定一个hash值,页面的地址会追加这个哈希值作为新的请求地址,通过window.onhashchange方法监听页面的hash变化,实现代码如下:

1
2
3
4
5
6
7
8
//为当前页面地址追加指定hash值
window.location="#test";
//获取页面地址的hash值
window.location.hash
//监听页面hash的变化
window.onhashchange=()=>{
console.log("current hash: "+window.location.hash);
}
H5路由
  • 添加并定位到一个新地址:

H5提供的路由,通过history.pushState推入一个状态,当前地址会追加一个hash值或者一个路径地址:

1
2
3
4
//推入一个hash:
history.pushState("name","title","#hash1");
//推入一个路径:
history.pushState("name","title","/path");
  • 替换当前页面地址追加的部分的hash或者地址名:
1
2
3
4
5
6
7
8
history.replaceState("name","title","xxxx");
//监听页面通过回退和前进页面历史改变页面时,回调:
window.onpopstate=()=>{
console.log("href="+window.location.href+"\n");
console.log("pathname="+window.location.pathname+"\n");
console.log("hash="+window.location.hash+"\n");
console.log("search="+window.location.search+"\n");
};

DOM

document

当前页面

概述
  • document对象表示当前页面。
  • 由于HTML在浏览器中以DOM形式表示为树形结构,document对象就是整个DOM树的根节点。
  • 查找DOM树的某个节点,需要从document对象开始查找。最常用的查找是根据ID和Tag Name。
页面属性
标题

标题:document.title;

1
2
3
// document的title属性是从HTML文档中的<title>xxx</title>读取的,但是可以动态改变:
document.title; // 读取标题
document.title = '努力学习JavaScript!'; // 改变标题
cookies
1
2
js可以获取网页的cookie信息;
防止外部第三方js读取cookies,服务器端设置Cookie时,应该始终坚持使用httpOnly来设置;
1
document.cookie; // 获取页面设置的cookie信息;
body属性
1
2
3
4
5
6
7
8
网页可见区域宽: document.body.clientWidth
网页可见区域高: document.body.clientHeight
网页可见区域宽: document.body.offsetWidth (包括边线的宽)
网页可见区域高: document.body.offsetHeight (包括边线的高)
网页正文全文宽: document.body.scrollWidth
网页正文全文高: document.body.scrollHeight
网页被卷去的高: document.body.scrollTop
网页被卷去的左: document.body.scrollLeft
dom节点元素

dom详细内容见操作dom

  1. document.getElementById(“x_id”):按ID获得一个DOM节点;
  2. document.getElementsByTagName(“x_tag”):按html标签Tag名称获得一组DOM节点;
  3. 获取节点的内容innerHTML:document.getElementById(‘x_id’).innerHTML ;

示例:

1
2
3
4
5
6
7
8
<dl id="drink-menu" style="border:solid 1px #ccc;padding:6px;">
<dt>摩卡</dt>
<dd>热摩卡咖啡</dd>
<dt>酸奶</dt>
<dd>北京老酸奶</dd>
<dt>果汁</dt>
<dd>鲜榨苹果汁</dd>
</dl>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 根据id获取dom节点元素
var menu = document.getElementById('drink-menu');
// 根据标签名获取dom节点元素
var drinks = document.getElementsByTagName('dt');
var i, s, menu, drinks;

menu = document.getElementById('drink-menu');
menu.tagName; // 'DL'

drinks = document.getElementsByTagName('dt');
s = '提供的饮料有:';
for (i=0; i<drinks.length; i++) {
s = s + drinks[i].innerHTML + ',';
}
console.log(s);

DOM事件处理

dom对象提供如下两个方法处理监听事件:

  1. addEventListener() 方法用于向指定元素添加事件句柄。
  2. removeEventListener()方法来移除addEventListener()方法添加的事件句柄。
1
2
3
4
5
// 添加 <div> 事件句柄 
document.getElementById("myDIV").addEventListener("mousemove", myFunction);

// 移除 <div> 事件句柄
document.getElementById("myDIV").removeEventListener("mousemove", myFunction);
event对象

event时dom自带的事件对象

  • event.preventDefault():该方法将通知 Web 浏览器不要执行与事件关联的默认动作(如果存在这样的动作)。
  • event.stopPropagation():该方法不能阻止同一个 Document 节点上的其他事件句柄被调用,但是它可以阻止把事件分派到其他节点。

操作DOM

创建节点
1
2
3
4
5
6
7
8
9
// 1. 创建新节点;
let d = document.createElement('style');
// 2. 设置节点属性;
d.setAttribute('type', 'text/css');
d.setAttribute("id", "container");
// 也可以直接属性赋值:d.id="container";
// 获取目标节点,追加元素
let list = document.getElementById('list');
list.appendChild(d);
插入DOM

如果DOM节点是空的,可以直接使用innerHTML “插入”新的DOM节点。

appendChild
  • 把一个子节点添加到父节点的最后一个子节点,首先会从原先的位置删除,再插入到新的位置。
  • 从零创建一个新的节点,然后插入到指定位置;

【实例1】增加节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- HTML结构 -->
<p id="js">JavaScript</p>
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
</div>

<script>
// 把原有节点添加到的最后一项
let js = document.getElementById('js'),
let list = document.getElementById('list');
list.appendChild(js);

// 创建新节点,并添加
let list = document.getElementById('list'),
let haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.appendChild(haskell);
</script>

【实例2】向head添加css样式:

1
2
3
4
var d = document.createElement('style');
d.setAttribute('type', 'text/css');
d.innerHTML = 'p { color: red }';
document.getElementsByTagName('head')[0].appendChild(d);
insertBefore
1
2
// 把子节点插入到指定的位置referenceElement元素之前
parentElement.insertBefore(newElement, referenceElement);
删除DOM
1
removeChild

删除Dom的步骤:

1
要删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild把自己删掉;
1
2
3
4
5
6
7
// 拿到待删除节点:
var self = document.getElementById('to-be-removed');
// 拿到父节点:
var parent = self.parentElement;
// 删除:
var removed = parent.removeChild(self);
removed === self; // true
1
【注意】删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置。
更新DOM
innerHTML
  • 不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树;
  • 用innerHTML时要注意,是否需要写入HTML。如果写入的字符串是通过网络拿到了,要注意对字符编码来避免XSS攻击。
1
2
3
4
5
var p = document.getElementById('container');
// 修改内容
p.innerHTML="hello world";
// 修改内容并添加子元素
p.innerHTML="hello <span style='color:red'>RED</span>";
write
  • document.write() 仅仅向文档输出写内容。
  • 如果在文档已完成加载后执行 document.write,整个 HTML 页面将被覆盖;
1
document.write("糟糕!文档消失了。");
innerText
  • 向标签元素中添加纯文本内容,即使是html代码也会被原样输出;
  • innerText不返回隐藏元素的文本
1
2
3
var p = document.getElementById('container');
p.innerText="hello <span style='color:red'>RED</span>";
// hello <span style='color:red'>RED</span>
textContent
  • 向标签元素中添加纯文本内容,即使是html代码也会被原样输出;
  • innerText会输出所有文本,包括隐藏的;
  • 注意IE<9不支持textContent。
1
2
var p = document.getElementById('container');
p.textContent="<p>hello</p><p>hello</p><p>hello</p>";
查询DOM
操作方法
  • document.getElementById():根据元素id获取唯一的DOM节点;
1
2
3
4
5
6
7
// 1. 获取ID为'test'的节点:
var test = document.getElementById('test');
// 2. 获取节点test下的所有直属子节点:
var cs = test.children;
// 3. 获取节点test下第一个、最后一个子节点:
var first = test.firstElementChild;
var last = test.lastElementChild;
  • document.getElementsByTagName():根据元素名称获取一组DOM节点;
  • document.getElementsByClassName():根据css选择器类名获取一组DOM节点;
1
2
3
4
5
【查找某节点下的一组节点】
// 先定位ID为'test-table'的节点,再返回其内部所有tr节点:
var trs = document.getElementById('test-table').getElementsByTagName('tr');
// 先定位ID为'test-div'的节点,再返回其内部所有class包含red的节点:
var reds = document.getElementById('test-div').getElementsByClassName('red');
  • querySelector()和querySelectorAll()(更加方便)
1
2
1> querySelector() 根据选择器名称获取节点元素;
2> querySelectorAll() 根据选择器名称获取所有符合条件的一组节点;
1
2
3
4
5
// 通过querySelector获取ID为q1的节点:
var q1 = document.querySelector('#q1');

// 通过querySelectorAll获取q1节点内的符合条件的所有节点:
var ps = q1.querySelectorAll('div.highlighted > p');
【注意】
  1. 低版本的IE<8不支持querySelector和querySelectorAll。IE8仅有限支持。
  2. 严格地讲,我们这里的DOM节点是指Element,但是DOM节点实际上是Node。(详见:w3cschool文档节点类型)
遍历节点
1
2
3
4
5
6
var
i, c,
list = document.getElementById('list');
for (i = 0; i < list.children.length; i++) {
c = list.children[i]; // 拿到第i个子节点
}
dom宽高
1
2
3
4
元素的实际高度:document.getElementById("div").offsetHeight
元素的实际宽度:document.getElementById("div").offsetWidth
元素的实际距离左边界的距离:document.getElementById("div").offsetLeft
元素的实际距离上边界的距离:document.getElementById("div").offsetTop
Dom相关方法
属性
1
2
3
4
// 获取属性值
domX.getAttribute("data-animal-type");
// 设置属性值
domX.setAttribute('type', 'text/css');

操作表单

  • 用JavaScript操作表单和操作DOM是类似的,因为表单本身也是DOM树。
  • 表单的输入框、下拉框等可以接收用户输入,JavaScript来操作表单,可以获得用户输入的内容,或者对一个输入框设置新的内容。
表单输入控件
1
2
3
4
5
6
文本框,对应的<input type="text">,用于输入文本;
口令框,对应的<input type="password">,用于输入口令;
单选框,对应的<input type="radio">,用于选择一项;
复选框,对应的<input type="checkbox">,用于选择多项;
下拉框,对应的<select>,用于选择一项;
隐藏文本(多用于密码),对应的<input type="hidden">,用户不可见,但表单提交时会把隐藏文本发送到服务器。
控件值
  1. text、password、hidden、select控件
  • 获得了一个input节点的引用,就可以直接调用value获得对应的用户输入值;
1
2
3
4
5
6
// <input type="text" id="email">
var input = document.getElementById('email');
// 获取值
input.value;
// 设置值
input.value = 'test@example.com';
  1. 单选框和复选框
1
对于单选框和复选框,value属性返回的永远是HTML预设的值,而我们需要获得的实际是用户是否“勾上了”选项,所以应该用checked判断
1
2
3
4
5
6
7
8
9
10
11
12
// <label><input type="radio" name="weekday" id="monday" value="1"> Monday</label>
// <label><input type="radio" name="weekday" id="tuesday" value="2"> Tuesday</label>
var mon = document.getElementById('monday');
var tue = document.getElementById('tuesday');
mon.value; // '1'
tue.value; // '2'

// 获取选择的状态
mon.checked; // true或者false
tue.checked; // true或者false
// 设置选择的状态
input.checked=true;
  1. HTML5控件
1
2
3
不支持HTML5的浏览器无法识别新的控件,会把它们当做type="text"来显示。
支持HTML5的浏览器将获得格式化的字符串。
例如,type="date"类型的input的value将保证是一个有效的YYYY-MM-DD格式的日期,或者空字符串。

HTML5控件date、datetime、datetime-local、color等,它们都使用input标签,通过value获取值;

提交表单
  • 一旦用户点击“Submit”按钮,表单开始提交,浏览器就会刷新页面,然后在新页面里告诉你操作是成功了还是失败了。
  • 如果不幸由于网络太慢或者其他原因,就会得到一个404页面。
响应onsubmit
  • 浏览器默认点击submit时提交表单,或者用户在最后一个输入框按回车键。
  • return true来告诉浏览器继续提交,如果return false,浏览器将不会继续提交form,这种情况通常对应用户输入有误,提示用户错误信息后终止提交form。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- HTML -->
<form id="test-form" onsubmit="return checkForm()">
<input type="text" name="test">
<button type="submit">Submit</button>
</form>

<form id="test-form" onsubmit="checkForm()">
<input type="text" name="test">
<button type="submit">Submit</button>
</form>

<script>
function checkForm() {
var form = document.getElementById('test-form');
// 可以在此修改form的input...
// 继续下一步:
return true;
}
</script>
点击事件提交
  • 缺点是扰乱了浏览器对form的正常提交;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- HTML -->
<form id="test-form">
<input type="text" name="test">
<button type="button" onclick="doSubmitForm()">Submit</button>
</form>

<script>
function doSubmitForm() {
var form = document.getElementById('test-form');
// 可以在此修改form的input...
// 提交form:
form.submit();
}
</script>
实例
  1. 密码加密
1
2
1. 提交表单,对密码加密,利用隐藏元素提交表单。
2. md5: https://github.com/blueimp/JavaScript-MD5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- HTML -->
<form id="login-form" method="post" onsubmit="return checkForm()">
<input type="text" id="username" name="username">
<input type="password" id="input-password">
<input type="hidden" id="md5-password" name="password">
<button type="submit">Submit</button>
</form>

<script>
function checkForm() {
var input_pwd = document.getElementById('input-password');
var md5_pwd = document.getElementById('md5-password');
// 把用户输入的明文变为MD5:
md5_pwd.value = toMD5(input_pwd.value);
// 继续下一步:
return true;
}
</script>

操作文件

普通文件
1
2
// 上传文件的唯一控件
<input type="file">;
特点
  1. input.value赋值是没有任何效果的;
  2. JavaScript也无法获得该文件的真实路径;
  3. 当表单包含file时,表单的enctype必须指定为multipart/form-data,method必须指定为post,浏览器才能正确编码并以multipart/form-data格式发送表单的数据。
文件信息
  1. 表单文件改变监听:
1
2
3
4
5
var fileInput = document.getElementById('test-file-upload');
// 监听file的改变获取file的值
fileInput.addEventListener('change', function () {
var filePath = fileInput.value;
});
  1. 提交表单前校验文件:
1
2
3
4
5
6
var f = document.getElementById('test-file-upload');
var filename = f.value; // 'C:\fakepath\test.png'
if (!filename || !(filename.endsWith('.jpg') || filename.endsWith('.png') || filename.endsWith('.gif'))) {
alert('Can only upload image file.');
return false;
}
HTML5 File
  • HTML5新增的File API允许JavaScript读取文件内容,获得更多的文件信息。
  • File API提供了File和FileReader两个主要对象,可以获得文件信息并读取文件。
File
  • 文件名:file.name;
  • 文件大小:file.size;
  • 文件最终修改时间:file.lastModifiedDate;
FileReader

FileReader用于异步读取文件:

1
2
3
4
5
6
7
var reader = new FileReader();
reader.onload = function(e) {
var data = e.target.result; // 'data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...'
preview.style.backgroundImage = 'url(' + data + ')';
};
// 以DataURL的形式读取文件:
reader.readAsDataURL(file);

【注意】

1
2
1> 以DataURL的形式读取到的文件是一个字符串,类似于data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...,常用于设置图像。
2> 如果需要服务器端处理,把字符串base64,后面的字符发送给服务器并用Base64解码就可以得到原始文件的二进制内容。

实例
  1. 选择图片并回显;
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
<div id="show_image" style="width: 100px; height: 100px;border: 1px solid black;background-size: 100%;"></div>
<input type="file" id="avatar" name="image" onchange="changeImage()">
<div id="info"></div>

<script type="text/javascript">
function changeImage() {
console.log("----------------------------");
let fileInput = document.getElementById('avatar');
let preview = document.getElementById('show_image');
let info = document.getElementById('info');

// 清除背景图片:
preview.style.backgroundImage = "";

// 检查文件是否选择:
if (!fileInput.value) {
info.innerHTML = '没有选择文件';
return;
}
// 获取File引用:
let file = fileInput.files[0];

// 文件名
console.log("文件名:"+file.name);
// 文件大小
console.log("文件大小:"+file.size);
// 文件最后修改的时间
console.log("文件最终修改时间:"+file.lastModifiedDate);

if (file.type !== 'image/jpeg' && file.type !== 'image/png' && file.type !== 'image/gif') {
alert('不是有效的图片文件!');
return;
}

// 读取文件:
let reader = new FileReader();
reader.onload = e=>{
let data = e.target.result; // 'data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...'
// 填充文件到div
preview.style.backgroundImage = 'url(' + data + ')';
};
// 以DataURL的形式读取文件:
reader.readAsDataURL(file);
}
</script>

Ajax

  • Ajax(Asynchronous JavaScript and XML)意思就是用JavaScript执行异步网络请求;
  • AJAX = 异步 JavaScript 和 XML;
  • AJAX请求是异步执行的,也就是说,要通过回调函数获得响应。

XMLHttpRequest

  • XMLHttpRequest用于发送请求;
  • 所有现代浏览器均支持 XMLHttpRequest 对象(IE5 和 IE6 使用 ActiveXObject)。
  • 所有现代浏览器(IE7+、Firefox、Chrome、Safari 以及 Opera)均内建 XMLHttpRequest 对象。

ajax使用

  1. 创建XMLHttpRequest请求对象:
1
2
3
4
5
6
var request;
if (window.XMLHttpRequest) { // code for IE7+, Firefox, Chrome, Opera, Safari
request = new XMLHttpRequest();
} else { // code for IE6, IE5
request = new ActiveXObject('Microsoft.XMLHTTP');
}
  1. 设置请求返回处理:
  • 当请求被发送到服务器时,我们需要执行一些基于响应的任务。
  • 每当 readyState 改变时,就会触发 onreadystatechange 事件。
  • readyState 属性存有 XMLHttpRequest 的状态信息;
1
2
3
4
5
6
7
readyState	
存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。
0: 请求未初始化
1: 服务器连接已建立
2: 请求已接收
3: 请求处理中
4: 请求已完成,且响应已就绪
  • http状态码
1
2
3
status	
200: "OK"
404: 未找到页面

配置实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function success(text) {
console.log("success="+text);
}

function fail(code) {
console.log("error code is "+code);
}

request.onreadystatechange = function () {
if (request.readyState === 4) { // 成功完成
// 判断响应结果:
if (request.status === 200) {
// request.responseXML; // XML格式的数据
// 成功,通过responseText拿到响应的文本:
return success(request.responseText);
} else {
// 失败,根据响应码判断失败原因:
return fail(request.status);
}
} else {
// HTTP请求还在继续...
console.log("链接异常...")
}
}

  1. open方法发起请求

open()规定请求的类型、URL 以及是否异步处理请求。

1
2
3
4
5
6
【语法格式】
request.open(method,url,async);
【参数说明】
- method:请求的类型;GET 或 POST
- url:文件在服务器上的位置
- asynctrue(异步)或 false(同步)

【GET请求】

1
2
3
4
// 设置请求
request.open("GET","url?param1=1&params2=2",true);
// 发送请求
request.send();

【POST请求】

1
2
3
4
// 设置请求
request.open("POST","url",true);
// 发送请求带参数
request.send(params);

  1. 设置请求头信息
1
2
// 请求头信息设置
request.setRequestHeader("Content-type","application/x-www-form-urlencoded");

跨域

a.com访问b.com就发生了跨域,
跨域的解决方案见:细说ajax跨域

Promise

概念

  • Promise主要用于在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了;
  • 支持链式调用;
语法格式
1
2
3
4
//写法1:
new Promise((resolve,reject)=>{}).then(result=>{},error=>{});
//写法2:
new Promise((resolve,reject)=>{}).then(result=>{}).catch(error=>{});

【参数说明】

  • Promise对象接受一个匿名函数作为参数,匿名函数有两个参数(resolve,reject),可以看成两个方法;
  • 写法1中:
1
2
- 调用resolve方法,会回调then方法的第一个匿名函数;
- 调用reject方法会回调then方法的第二个匿名函数;
  • 写法2中:
1
2
- 调用resolve方法,会回调then方法中的匿名函数;
- 调用reject方法会回调catch方法中的匿名函数;

异步任务

单异步任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function test(resolve, reject) {
var timeOut = Math.random() * 2;
setTimeout(function () {
if (timeOut < 1) {
resolve('200 OK');
} else {
reject('timeout is' + timeOut);
}
}, timeOut * 1000);
}

function testMethod() {
new Promise(test)
.then(function (result) {
console.log('成功:' + result);
})
.catch(function (reason) {
console.log('失败:' + reason);
});
}
串异步任务

串行执行若干异步任务

问题描述:若干个异步任务,需要先做任务1,如果成功后再做任务2,任何任务失败则不再继续并执行错误处理函数?

1
2
3
4
5
6
7
let job1=new Promise(task1Fn());
let job2=new Promise(task2n());
let job3=new Promise(task3Fn());
// 写法1:
job1.then(()=>{return job2;}).then(()=>{});
// 写法2:
job1.then(job2).then(job3).catch(handleError);
并行异步任务
Promise.all

描述:并行执行两个任务p1,p2,全部结束后打印结果?

1
2
3
4
5
6
7
8
9
10
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 获得一个Array: ['P1', 'P2']
});
Promise.race

描述:并行执行执行多个异步任务,任意任务执行结束,丢弃其他任务?

1
2
3
4
5
6
7
8
9
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P1'
});

执行ajax

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
// ajax函数将返回Promise对象:
function ajax(method, url, data) {
var request = new XMLHttpRequest();
return new Promise(function (resolve, reject) {
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === 200) {
resolve(request.responseText);
} else {
reject(request.status);
}
}
};
request.open(method, url);
request.send(data);
});
}

// 入口
function mainFn(){
var p = ajax('GET', '/api/categories');
p.then(function (text) { // 如果AJAX成功,获得响应内容
log.innerText = text;
}).catch(function (status) { // 如果AJAX失败,获得响应代码
log.innerText = 'ERROR: ' + status;
});
}

fetch

fetch用于访问和操纵HTTP请求;

对比ajax

  • 除非网络故障,否则fetch()返回的Promise 不会被标记为reject,只有resolve状态,但是可以通过resolve的ok属性来判断请求结果是否被拒(404 or 500);
  • 默认情况下,fetch不会从服务端发送或接收任何 cookies, 要发送cookies,必须设置credentials选项。

fetch语法

【语法格式】

1
Promise<Response> fetch(input[, init]);

【参数解释】

  • input:(必须参数)url或request对象;
  • init: (可选参数)配置请求信息的对象;
1
2
3
4
5
6
7
8
9
10
11
配置参数如下:
- method: 请求使用的方法,如 GET、POST。
- headers: 请求的头信息,形式为 Headers 的对象或包含 ByteString 值的对象字面量。
- body: 请求的 body 信息:可能是一个 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息。
- mode: 请求的模式,如 cors、 no-cors 或者 same-origin。
- credentials: 请求的凭据credentials,如omit(不在请求中包含凭据)、same-origin(在请求URL与调用脚本位于同一起源处时发送凭据)或者 include(发送包含凭据的请求)。为了在当前域名内自动发送cookie, 必须提供这个选项, 从Chrome50开始,这个属性也可以接受FederatedCredential 实例或是一个PasswordCredential实例。
- cache: 请求的 cache 模式: default 、 no-store 、 reload 、 no-cache 、 force-cache 或者 only-if-cached 。
- redirect: 可用的 redirect 模式: follow (自动重定向), error (如果产生重定向将自动终止并且抛出一个错误), 或者 manual (手动处理重定向). 在Chrome中,Chrome 47之前的默认值是 follow,从 Chrome 47开始是 manual。
- referrer: 一个 USVString 可以是 no-referrer、client或一个 URL。默认是 client。
- referrerPolicy: Specifies the value of the referer HTTP header. May be one of no-referrer、 no-referrer-when-downgrade、 origin、 origin-when-cross-origin、 unsafe-url 。
- integrity: 包括请求的 subresource integrity 值 ( 例如: sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=)。
Request对象
1
2
3
4
5
6
7
8
9
10
11
12
13
var myHeaders = new Headers();
var myInit = { method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default' };
var myRequest = new Request('flowers.jpg', myInit);
//请求
fetch(myRequest).then(function(response) {
return response.blob();
}).then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
});
Response对象

Response实例是在fetch()处理完promises之后返回的.

创建Response

Response() 构造方法接受两个可选参数—response的数据体和一个初始化对象(与Request()所接受的init参数类似.)

1
2
3
4
5
6
7
var myBody = new Blob();

addEventListener('fetch', function(event) {
event.respondWith(new Response(myBody, {
headers: { "Content-Type" : "text/plain" }
});
});
response常见属性
  • Response.status — 整数(默认值为200) 为response的状态码.
  • Response.statusText — 字符串(默认值为”OK”),该值与HTTP状态码消息对应.
  • Response.ok — 如上所示, 该属性是来检查response的状态是否在200-299(包括200,299)这个范围内.该属性返回一个Boolean值.
Headers

headers配置也创建Headers对象来设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
var myHeaders = new Headers({
"Content-Length": content.length.toString(),
"X-Custom-Header": "ProcessThisImmediately",
});
//添加配置
myHeaders.append("Content-Type", "text/plain");
//更新写入配置(有一些属性不可以写入,如Origin)
myHeaders.set("Content-Type", "application/json");
//获取配置
console.log(myHeaders.get("Content-Length")); // 11
console.log(myHeaders.getAll("X-Custom-Header")); // ["ProcessThisImmediately", "AnotherValue"]
//删除配置
myHeaders.delete("X-Custom-Header");

Body

body用于指定提交请求的参数;

body参数类型可以是以下任意类型的实例:

  • ArrayBuffer
  • ArrayBufferView (Uint8Array and friends)
  • Blob/File
  • string
  • URLSearchParams(用于form-data格式提交请求参数)
  • FormData(用于图文格式提交请求参数)

Body类定义了以下方法 (这些方法都被 Request 和Response所实现)以获取body内容. 这些方法都会返回一个被解析后的promise对象和数据:

  • arrayBuffer()
  • blob(): Response流
  • json():Body mixin的json()方法使用一个Response 流,并将其读取完成。它返回一个 promise ,解析结果是将文本体解析为JSON。
  • text()
  • formData():将Response对象中的所承载的数据流读取并封装成为一个对象。

请求示例

Get请求
1
2
3
4
5
6
7
8
//get请求
fetch("http://localhost:8080/test-api/get")
.then(function (response) {
return response.json(); //返回一个Promise对象
})
.then(function (myJson) { //需要二次处理才会返回处理结果
console.log(myJson);
});
Get请求-带参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function testGetByParams() {
let url = "http://localhost:8080/test-api/get";
let params={tag:'tag'};
if (params) {
let paramsArray = [];
//拼接参数
Object.keys(params).forEach(key => paramsArray.push(key + '=' + params[key]));
if (url.search(/\?/) === -1) {
url += '?' + paramsArray.join('&')
} else {
url += '&' + paramsArray.join('&')
}
}

fetch(url, {
method: 'GET'
}).then(function (response) {
return response.json(); //返回一个Promise对象
}).then(function (myJson) { //需要二次处理才会返回处理结果
console.log(myJson);
});
}
post请求-body形式

JSON.stringify()来组织参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function testPostBody() {
fetch("http://localhost:8080/test-api/postBody", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({tag: 'this'}),
credentials: 'same-origin', //请求可以带cookie
}).then(response => {
if (response.ok) {
return response.json();
}
throw new Error('Network response was not ok.');
}).catch(error => {
console.error('Error:', error)
}).then(response => {
console.log('Success:', response)
});
}

post请求-FormData形式

URLSearchParams来组织参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function testPostFormData() {
let params = new URLSearchParams();
params.append('tag', 'tag');

fetch("http://localhost:8080/test-api/postFormData", {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params,
credentials: 'same-origin', //请求可以带cookie
}).then(response => {
if (response.ok) {
return response.json();
}
throw new Error('Network response was not ok.');
}).catch(error => {
console.error('Error:', error)
}).then(response => {
console.log('Success:', response)
});
}

post请求-form-data提交文件文字

表单的形式提交文件和文字,参数是FormData对象,不用指定Content-Type:

1
2
3
4
5
6
7
8
9
10
11
12
13
let formData = new FormData();
let fileField = document.querySelector("input[type='file']");

formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);

fetch('https://example.com/profile/avatar', {
method: 'POST',
body: formData
})
.then(response => response.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));

Generator

Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。

概述

  • generator和函数不同的是,generator由function定义(注意多出的号),并且,除了return语句,还可以用yield返回多次。
  • 调用一个generator和调用函数不一样,调用生成器g(),仅仅是创建了一个generator对象,还没有去执行它,调用generator对象的next()方法才会执行。
  • 整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield语句注明。
基本示例
1
2
3
4
5
6
7
8
9
10
11
12
function handleFn() {
return 1;
}

let gen = function* () {
yield handleFn();
};

let g = gen();
let result = g.next();

console.log(result); //{ value: 1, done: false }

使用方式

执行generator对象有两个方法:

  1. 一是不断地调用generator对象的next()方法;
  2. 直接用for … of循环迭代generator对象,这种方式不需要我们自己判断done;
实例
  1. 打印一个菲波那切数列;
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
function* fib(max) {
var
t,
a = 0,
b = 1,
n = 0;
while (n < max) {
yield a;
[a, b] = [b, a + b];
n ++;
}
return;
}

// 1. next()方法输出结果
var f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}

// 2. for ... of循环迭代generator对象输出结果
for (var x of fib(5)) {
console.log(x); // 依次输出0, 1, 1, 2, 3, ...
}

执行异步任务

Generator 函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* gen() {
let url = "http://localhost:8080/test-api/testGet";
yield fetch(url);
}

function testGenerator() {
let g = gen();
let result = g.next();
result.value.then(function (data) {
return data.json();
}).then(function (data) {
console.log(data);
});
}

【上述代码执行流程】
首先执行 Generator 函数,获取遍历器对象,然后使用next方法(第二行),执行异步任务的第一阶段。由于Fetch模块返回的是一个 Promise 对象,因此要用then方法调用下一个next方法。

异步

ES6中用Generator将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。

es8引入async/await异步函数,即在async标识的函数体内部,使用await调用异步方法,就会立刻执行,并将结果返回;

语法

四种写法
1
2
3
4
1> 函数声明: async function foo() {}
2> 函数表达式: const foo = async function() {}
3> 对象的方式: let obj = { async foo() {} }
4> 箭头函数: const foo = async () => {}
错误处理

使用try..catch来处理错误:

1
2
3
4
5
6
7
async function asyncFunc() {
try {
await fetch(asyncTask());
}catch (e) {
console.error(e);
}
}

fetch

async/wait与fetch结合,可以像写同步代码一样写异步;

处理单个异步任务
1
2
3
4
5
6
async function asyncFunc() {
let url = "http://localhost:8080/test-api/testGet";
let response = await fetch(url);
let data = await response.json(); //注意此处返回Promise对象
console.log(data);
}
顺序处理多个异步任务
1
2
3
4
5
async function asyncFunc() {
let url = "http://localhost:8080/test-api/testGet";
let response1 = await fetch(url);
let response2 = await fetch(url);
}
并行处理多个异步任务
1
2
3
4
5
6
7
8
async function asyncFunc() {
let url = "http://localhost:8080/test-api/testGet";
const [res1, res2] = await Promise.all([
await fetch(url),
await fetch(url)
]);
console.log(res1, res2);
}

错误处理

捕获异常

1
2
3
4
5
6
7
try {
...
} catch (e) {
...
} finally {
...
}

错误类型

  • JavaScript有一个标准的Error对象表示错误,还有从Error派生的TypeError、ReferenceError等错误对象。
  • 我们在处理错误时,可以通过catch(e)捕获的变量e访问错误对象。

抛出错误

  • 程序也可以主动抛出一个错误,让执行流程直接跳转到catch块。
  • 抛出错误使用throw语句。
1
throw new Error('输入错误');

参考

计时操作

  • setTimeout()未来的某时执行代码
  • clearTimeout()取消setTimeout()
1
2
3
4
5
6
7
8
9
function callback() {
console.log('Done');
}

function testMethod() {
console.log('before setTimeout()');
setTimeout(callback, 1000); // 1秒钟后调用callback函数
console.log('after setTimeout()');
}

全局函数

可在全局直接调用;

  • decodeURI() 解码某个编码的 URI。
  • decodeURIComponent() 解码一个编码的 URI 组件。
  • encodeURI() 把字符串编码为 URI。
  • encodeURIComponent() 把字符串编码为 URI 组件。
  • escape() 对字符串进行编码。
  • eval() 计算 JavaScript 字符串,并把它作为脚本代码来执行。
  • getClass() 返回一个 JavaObject 的 JavaClass。
  • isFinite() 检查某个值是否为有穷大的数。
  • isNaN() 检查某个值是否是数字。
  • Number() 把对象的值转换为数字。
  • parseFloat() 解析一个字符串并返回一个浮点数。
  • parseInt() 解析一个字符串并返回一个整数。
  • String() 把对象的值转换为字符串。
  • unescape() 对由 escape() 编码的字符串进行解码。

常见事件

事件通常与函数配合使用,这样就可以通过发生的事件来驱动函数执行。

下面是一个属性列表,这些属性可插入 HTML 标签来定义事件动作:

  • onabort 图像加载被中断
  • onblur 元素失去焦点
  • onchange 用户改变域的内容
  • onclick 鼠标点击某个对象
  • ondblclick 鼠标双击某个对象
  • onerror 当加载文档或图像时发生某个错误
  • onfocus 元素获得焦点
  • onkeydown 某个键盘的键被按下
  • onkeypress 某个键盘的键被按下或按住
  • onkeyup 某个键盘的键被松开
  • onload 某个页面或图像被完成加载
  • onmousedown 某个鼠标按键被按下
  • onmousemove 鼠标被移动
  • onmouseout 鼠标从某元素移开
  • onmouseover 鼠标被移到某元素之上
  • onmouseup 某个鼠标按键被松开
  • onreset 重置按钮被点击
  • onresize 窗口或框架被调整尺寸
  • onselect 文本被选定
  • onsubmit 提交按钮被点击
  • onunload 用户退出页面
坚持原创技术分享,您的支持将鼓励我继续创作!