HTML
CSS
JS
JS-下
jQuery
Node.js + Gulp
JavaScript 教程
HTML 是网页的结构,CSS 是网页的外观,而 JavaScript 是页面的行为。
历史
变量
变量是对“值”的具名引用。变量就是为“值”起名,然后引用这个名字,就等同于引用这个值。变量的名字就是变量名。
1
let a = 1;
变量提升
JavaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。
1
2
console.log(a);
let a = 1;
等于
1
2
3
let a;
console.log(a);
a = 1;
最后的结果是显示 undefined,表示变量 a 已声明,但还未赋值。
作用域
全局,函数和块(ES6). 在ES6中引入了块作用域的概念,并且只有let和const 是块作用域,var 仍然是函数作用域
Hoisting and TDZ
作用域链
数据类型
JavaScript 数据类型有 2 大分类:一是“基本数据类型”,二是“特殊数据类型”。
其中,基本数据类型包括以下 3 种:
(1)数字型(Number 型):如整型 84,浮点型 3.14;
(2)字符串型(String 型):如”绿叶学习网”;
(3)布尔型(Boolean 型):true 或 fasle;
特殊数据类型有 3 种:
(1)空值(null 型);
(2)未定义值(undefined 型);
(3)转义字符;
值类型与引用类型
基本数据类型属于值类型,值存储在 Stack 里。 Object 等属于引用类型,存储在 Heap 里
当我们申请一个值类型时,我们实际上是在堆栈中申请了一块内存,分配如下:
1
let age = 30;
当再声明一个变量等于 age 时
1
2
let age = 30;
let oldAge = age;
在内存中,实际发生的事情是两个变量都指向了同一个内存地址:
第三步,当我们修改 age 的值时
1
2
3
let age = 30;
let oldAge = age;
let age = 31;
在内存中,已分配的内存中的值不可再修改(the value at a certain memory address is immutable),实际发生的事情是变量 age 重新申请了一块新的内存,去存储新的值 31.
而对于引用类型,当初始化一个引用类型时,变量的值存储在 Heap 里,堆栈的 value 只记录了 heap 的地址。
1
2
3
const me = {
age: 30
}
当定义另一个变量 friend 时, friend 和 me 都指向了同一个内存地址.
1
2
3
4
const me = {
age: 30
}
const friend = me;
如果此时修改 friend 的 age, 我们实际上只是修改了 Heap 中存储的 value,Stack 里的地址没有发生任何变化。
1
2
3
4
5
const me = {
age: 30
}
const friend = me;
friend.age = 27;
这也解释了一个引用类型声明为 const,我们仍然可以修改它的值
1
2
3
4
5
const testObj = {
age: 30
}
testObj.age = 27; // allow
testObj = {} // not allow, due testObj is const
Number
JavaScript 内部,所有数字都是以 64 位浮点数形式储存,即使整数也是如此。这就是说,JavaScript 语言的底层根本没有整数,所有数字都是小数(64 位浮点数)。
1
1 === 1.0 // true
由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。
1
2
3
0.1 + 0.2 === 0.3 // false
0.3 / 0.1 // 2.9999999999999996
(0.3 - 0.2) === (0.2 - 0.1) // false
将string转为Number
1
2
3
4
5
6
Number('23');
Number.parseInt('30px') // 30
Number.parseInt('23.15') // 23
Number.isFinit(20) // Checking if value is number
Number.isNaN(20) // Check if value is NaN
对于较长的数字,可以加下划线进行分隔
1
2
const diameter = 287_460_000_000
console.log(diameter); // 287460000000
BigInt
1
2
console.log(2**53 -1 );
console.log(Number.MAX_SAFE_INTEGER) //Number 有最大限制9007199254740991, 超过这个最大值,JavaScript无法保证准确
BigInt没有最大值的限制
1
console.log(typeof 20n) // bigint
Boolean
5 种 falsy value:
1
0, '', undefined, null, NaN
typeof 运算符
typeof 运算符用于返回它的操作数当前所容纳的数据的类型,这对于判断一个变量是否已被定义特别有用。
例如:typeof(1)返回值是 number,typeof(“javascript”)返回值是 string。
在实际编程中,通常用 tppeof 判断变量是否定义。
1
2
3
4
5
6
7
8
9
10
// 错误的写法
if (v) {
// ...
}
// ReferenceError: v is not defined
// 正确的写法
if (typeof v === "undefined") {
// ...
}
NaN
NaN 是 JavaScript 的特殊值,表示“非数字”(Not a Number),主要出现在将字符串解析成数字出错的场合。
isNaN 方法可以用来判断一个值是否为 NaN。
1
2
isNaN(NaN) // true
isNaN(123) // false
对象(object)
对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合。
1
2
3
4
5
6
7
const jonas = {
firtName: 'Jonas',
lastName: 'Schemdtman',
age: 2023 - 1991,
job: 'teacher',
firends: ['Michael', 'Peter', 'Steven']
};
对象的访问可以使用键值也可以用方括号
1
2
3
4
5
jonas.firstName
jonas['lastName']
const nameKey = 'Name';
jonas['first' + nameKey] // 可以拼接
检查键值是否存在:
1
2
3
if(!jonas['location']) {
console.log('does not exist');
}
对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。
1
2
3
4
5
6
let obj = {
p: function (x) {
return 2 * x;
}
};
obj.p(1) // 2
属性可以动态创建,不必在对象声明时就指定。
1
2
3
let obj = {};
obj.foo = 123;
obj.foo // 123
查看一个对象本身的所有属性,可以使用 Object.keys 方法。
1
2
3
4
5
6
let obj = {
key1: 1,
key2: 2
};
Object.keys(obj);
// ['key1', 'key2']
使用for...of
遍历 Object 的 keys
1
2
3
4
5
6
7
let obj = {
key1: 1,
key2: 2
};
for(const key of Object.keys(obj)) {
console.log(key);
}
Object.values 可以返回所有的 value 值
Object.entries:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const openingHours = {
thu: {
open: 12,
close: 22,
},
fir: {
open: 11,
close: 23,
},
sat: {
open: 0, // Open 24 hours
close: 24,
},
};
const entries = Object.entries(openingHours);
for (const [key, { open, close }] of entries) {
console.log(`On ${key} we open at ${open} and close at ${close}`);
}
数组
创建数组
在 JavaScript 中,创建数组共有 3 种方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let myArr = new Array();
let myArr = new Array(3);
myArr[0]="HTML";
myArr[1]="CSS";
myArr[2]="JavaScript";
let myArr = new Array(1,2,3,4); // bad
let arr = ['a', 'b', 'c']; // good
const x = new Array(7);
x.fill(23, 3, 5);
const y = Array.from({lenght: 3}, () => 1); //[1,1,1]
本质上,数组属于一种特殊的对象。typeof 运算符会返回数组的类型是 object。
清空数组的一个有效方法,就是将 length 属性设为 0。
1
2
let arr = [ 'a', 'b', 'c' ];
arr.length = 0;
遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const arr3 = [1,2,3,4,5,6]
for(const item of arr3){
console.log(item);
}
for(const [index, item] of arr3.entries()){
console.log(`index: ${index}, item: ${item}`);
}
arr3.forEach(function(item) {
console.log(item);
})
arr3.forEach( function(item, index, array) {
console.log(`index: ${index}, item: ${item}`);
})
注意:
forEach 无法添加break和Continue
常用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
at(index) // 获取数组指定位置的元素
concat() //多个数组合并
fill(data, startIndex, endIndex) // 填充数组
find() //返回第一个符合的元素
findIndex() //返回第一个符合的元素的index
filter() //过滤数组成员,将满足条件的成员组成一个新数组返回。
flat() //按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回
flatMap() //首先使用映射函数映射每个元素,然后将结果压缩成一个新数组
includes('subElement') //是否包含指定元素
indexOf('subElement') //返回指定元素的位置
join('seperator') //将数组元素连接成字符串
map() // 将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回
shift() //移除第一个元素
slice(startIndex, endIndex) //获取数组中的某段数组元素
splice(startIndex, endIndex) // 与slice相似,但会影响原数组
pop() //删除数组最后一个元素
push() //在数组末尾添加元素
reduce(function, initialValue) //对数组遍历,返回一个单个返回值
reverse() //数组元素反转,影响原数组
sort() //数组元素正向排序
toString() //将数组转换为字符串
unshift('newItem') //添加新的元素到数组开头
example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const arr1 = [1,2,3];
const arr2 = [4,5,6];
console.log(arr1.at(0)) //1
// getting last array element
console.log(arr1[arr1.length - 1]); // 3
console.log(arr1.slice(-1)[0]); // 3
console.log(arr1.at(-1)); // 3
const sum = arr1.concat(arr2);
console.log(sum); // [1,2,3,4,5,6]
console.log([...arr1, ...arr2]); // [1,2,3,4,5,6]
console.log(sum.join('-')); // 1-2-3-4-5-6
slice() 剪切数组,并返回copy数组,不会改变原数组
1
2
3
let arr = ['a', 'b', 'c', 'd', 'e'];
console.log(arr.slice(2)) // ["c", "d", "e"]
console.log(arr.slice(-1)) // ["e"] 返回最后一个元素
slice()方法的一个重要应用,是将类似数组的对象转为真正的数组。
1
2
Array.prototype.slice.call(document.querySelectorAll("div"));
Array.prototype.slice.call(arguments);
map()
map 方法将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回。
1
2
3
4
5
const numbers = [1, 2, 3];
const newArr = numbers.map(function (n) {
return n + 1;
}); // [2, 3, 4]
const newArr2 = numbers.map(num => num + 1); // [2,3,4]
filter()
filter 方法用于过滤数组成员,满足条件的成员组成一个新数组返回。
1
2
3
4
[1, 2, 3, 4, 5].filter(function (elem) {
return (elem > 3);
})
// [4, 5]
flat(), flatMap()
1
2
3
4
5
6
var arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]
var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(Infinity);
flatMap() 展平的深度值为 1 ,而flat() 可以指定多级。
some(),every()
some 方法是只要一个成员的返回值是 true,则整个 some 方法的返回值就是 true,否则返回 false。
1
2
3
4
5
let arr = [1, 2, 3, 4, 5];
arr.some(function (elem, index, arr) {
return elem >= 3;
});
// true
every 方法是所有成员的返回值都是 true,整个 every 方法才返回 true,否则返回 false。
unshift()
将一个或多个元素添加到数组的开头,并返回数组的新长度
1
2
3
4
const friends = [ 'Michale', 'Steven', 'Peter'];
const newLength = friend.unshift('John');
console.log(friends); // ['John', 'Michale', 'Steven', 'Peter']
console.log(newLength); // 4
reduce()
Array.reduce()方法是对数组的遍历,返回一个单个返回值
1
2
3
4
5
6
const array1 = [1, 2, 3, 4];
const initialValue = 0;
const sumWithInitial = array1.reduce((accumulator, currentValue) => accumulator + currentValue, initialValue);
console.log(sumWithInitial); // 10
const max = array1.reduce((acc, mov) => acc > mov ? acc : mov, array1[0]); // 4
Set
Set 与数组相似,但元素是唯一的
1
2
3
4
5
6
7
8
9
10
const orderArr = ['Pasta', 'Pizza', 'Pizza', 'Risotto', 'Pasta', 'Pizza'];
const orderSet = new Set(orderArr);
console.log(orderSet); // {"Pasta", "Pizza", "Risotto"}
console.log(orderSet.size); // 3
console.log(orderSet.has('Pizza')); // true
orderSet.add('Garlic Bread');
orderSet.delete('Risotto');
orderSet.clear();
与数组不同,Set 没有索引,所以无法通过 index 来访问元素. orderSet[0] 是错误的
Maps
Maps 用来存储 key,value pair
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const rest = new Map();
rest.set('name', 'Classico Italiano');
rest.set('open', 11);
rest.set('close', 23);
rest.set(true, 'We are open');
rest.set(false, 'We are closed');
console.log(rest.get(true)); // We are open
const currentTime = 12;
console.log(
rest.get(currentTime > rest.get('open') && currentTime < rest.get('close'))
); // We are open
console.log(rest.has('categories')); // false
rest.delete('name');
rest.forEach(function(value, key, map) {
console.log(`${key}: ${value}`);
})
字符串对象
-
字符串模板拼接
1 2 3 4 5 6
const firstName = 'Jonas'; const birthYear = 1991; const year = 2037; const job = 'teacher' const jonas = `I'm ${firstName}, a ${year - bithYear} year old ${job}!`; console.log(jonas);
-
length 属性
我们可以通过 length 属性来获取字符串的长度。1
字符串名.length
-
match()
使用 match()方法可以从字符串内索引指定的值,或者找到一个或多个正则表达式的匹配。1 2
stringObject.match(字符串) //匹配字符串; stringObject.match(正则表达式) //匹配正则表达式
-
indexOf()
使用 indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。1
stringObject.indexOf(字符串)
-
replace()
replace()方法常常用于在字符串中用一些字符替换另一些字符,或者替换一个与正则表达式匹配的子串。1 2
stringObject.replace(原字符,替换字符) stringObject.replace(正则表达式,替换字符) //匹配正则表达式
-
charAt()
使用 charAt()方法来获取字符串中的某一个字符1
stringObject.charAt(n)
-
连接字符串
使用 concat()方法来连接 2 个或多个字符串。1
字符串1.concat(字符串2,字符串3,…,字符串n);
-
split()
使用 split()方法把一个字符串分割成字符串数组。1
字符串.split(分割符)
-
substring()
使用 substring()方法来提取字符串中的某一部分字符串。1
字符串.substring(开始位置,结束位置)
-
slice(startIndex, length)
剪切字符串1
str.slice(-1) // get last character
- .toLowerCase(), .toUpperCase()
- includes() 是否含有指定字符串,返回 boolean
- startsWith(str), endsWith(str)
-
padEnd(), padStart() 填充字符串
1 2 3 4 5 6 7 8 9 10
const maskCreditCard = function (number) { const str = String(number); const last = str.slice(-4); return last.padStart(str.length, '*'); }; console.log(maskCreditCard(123456789)); const month = 8; console.log(`${month}`.padStart(2, 0)); // 08
- repeat(count)
日期对象
创建日期对象必须使用“new 语句”。
1
2
let 日期对象名 = new Date();
let 日期对象名 = new Date(日期字符串);
常用方法:
1
2
3
4
5
6
getFullYear() //返回一个表示年份的4位数字
getMonth() //返回值是0(一月)到11(十二月)之间的一个整数
getDate() //返回值是1~31之间的一个整数
getHours() //返回值是0~23之间的一个整数,来表示小时数
getMinutes() //返回值是0~59之间的一个整数,来表示分钟数
getSeconds() //返回值是0~59之间的一个整数,来表示秒数
将日期时间转换为字符串:
1
2
3
4
toString() //将日期时间转换为普通字符串
toUTCString() //将日期时间转换为世界时间(UTC)格式的字符串
toLocaleString() //将日期时间转换为本地时间格式的字符串
toJSON() //方法返回一个符合 JSON 格式的 ISO 日期字符串
console 对象
console.warn()
,console.error()
warn 方法和 error 方法也是在控制台输出信息,它们与 log 方法的不同之处在于,warn 方法输出信息时,在最前面加一个黄色三角,表示警告;error 方法输出信息时,在最前面加一个红色的叉,表示出错。-
console.table()
对于某些复合类型的数据,console.table 方法可以将其转为表格显示。1 2 3 4 5 6
let languages = [ { name: "JavaScript", fileExtension: ".js" }, { name: "TypeScript", fileExtension: ".ts" }, { name: "CoffeeScript", fileExtension: ".coffee" } ]; console.table(languages);
-
console.count()
count 方法用于计数,输出它被调用了多少次。 -
console.time(),console.timeEnd()
这两个方法用于计时,可以算出一个操作所花费的准确时间。1 2 3 4 5 6 7
console.time('Array initialize'); let array= new Array(1000000); for (let i = array.length - 1; i >= 0; i--) { array[i] = new Object(); }; console.timeEnd('Array initialize'); // Array initialize: 1914.481ms
- console.trace()
console.trace 方法显示当前执行的代码在堆栈中的调用路径。
RegExp 对象
新建正则表达式有两种方法。一种是使用字面量,以斜杠表示开始和结束。
1
let regex = /xyz/;
另一种是使用 RegExp 构造函数。
1
let regex = new RegExp('xyz');
第一种方法在引擎编译代码时,就会新建正则表达式,第二种方法在运行时新建正则表达式,所以前者的效率较高。而且,前者比较便利和直观
常用方法:
-
test()
正则实例对象的 test 方法返回一个布尔值,表示当前模式是否能匹配参数字符串。1
/cat/.test('cats and dogs') // true
-
exec()
用来返回匹配结果。如果发现匹配,就返回一个数组,成员是匹配成功的子字符串,否则返回 null。1 2 3
let s = '_x_x'; let r1 = /x/; r1.exec(s) // ["x"]
-
String.match()
字符串实例对象的 match 方法对字符串进行正则匹配,返回匹配结果。1 2 3
let s = '_x_x'; let r1 = /x/; s.match(r1) // ["x"]
匹配规则
https://wangdoc.com/javascript/stdlib/regexp.html
Math 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
max(x,y) //返回x和y中的最大值
min(x,y) //返回x和y中的最小值
pow(x,y) //返回x的y次幂
abs(x) //返回数的绝对值
random() //返回0~1之间的随机数
trunc() //截取整数部分,舍弃小数部分
round(x) //把数四舍五入为最接近的整数
ceil(x) //对一个数进行上舍入
floor(x) //对一个数进行下舍入
toFixed(2) //将数字转换为字符串,并将字符串四舍五入为指定的小数位数。
生成一个 1-20 的随机数
1
Math.trunc(Math.random() * 20) + 1;
JSON 对象
JSON 格式(JavaScript Object Notation 的缩写)是一种用于数据交换的文本格式,2001 年由 Douglas Crockford 提出,目的是取代繁琐笨重的 XML 格式。
字符串必须使用双引号表示,不能使用单引号。
对象的键名必须放在双引号里面。
数组或对象最后一个成员的后面,不能加逗号。
以下都是合法的 JSON。
1
2
3
4
5
["one", "two", "three"]
{ "one": 1, "two": 2, "three": 3 }
{ name: "张三", "age": 32 }
{"names": ["张三", "李四"] }
[ { "name": "张三"}, {"name": "李四"} ]
JSON.stringify()
将一个值转为 JSON 字符串。该字符串符合 JSON 格式,并且可以被 JSON.parse 方法还原。
1
2
JSON.stringify([1, "false", false]) // '[1,"false",false]'
JSON.stringify({ name: "张三" }) // '{"name":"张三"}'
JSON.parse()
JSON.parse 方法用于将 JSON 字符串转换成对应的值。
1
2
let o = JSON.parse('{"name": "张三"}');
o.name // 张三
类型转换
-
字符串型转换为数值型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
const inputYear = '1991' Number(inputYear); Number('test string') // NAN String(23); parseInt() //将字符串型转换为整型 parseFloat() //将字符串型转换为浮点型 console.log('23' - '10' - 3) //输出 number 10 console.log('23' + '10' +3) //输出 string 23103 let n = '1'+1 n = n - 1; console.log(n) //输出数字10
-
数值型转换为字符串型
1
.toString()
解构
在 ES5 中:如果计划从数组中提取特定元素,就需使用元素的索引,并将其保存到变量之中
1
2
3
4
const arr = [2, 3, 4];
const a = arr[0];
const b = arr[1];
const c = arr[2]
如果想对元素进行交换,需要引入一个临时变量
1
2
3
4
const temp = arr[0];
arr[0] = arr[1];
arr[1] = temp;
console.log(arr) // [3,2,4]
在 ES6 中引入了解构的功能,以简化获取数组中数据的过程:
1
2
3
4
5
6
7
8
9
const arr = [2,3,4];
const [x, y, z] = = arr;
console.log(x, y, z);
const [x1, y1] = arr;
console.log(x1, y1); //只取前两个元素
const[x2,,z2] = arr;
console.log(x2, z2) // 用逗号跳过元素
利用解构进行数组元素交换:
1
2
3
4
5
const arr1 = [2, 3, 4];
let [first, second] = arr1;
[first, second] = [second, first];
console.log(first, second); // 3,2
console.log(arr1) // still return 2,3,4
利用解构,让函数返回多个值
1
2
3
4
5
6
const switchElement = function (a, b) {
return [b, a];
};
const [item1, item2] = switchElement(1, 2);
console.log(item1, item2);
利用解构设置默认值,通常用在 API 函数调用设置默认值:
1
2
const [p = 1, q = 1, r = 1] = [2, 3];
console.log(p, q, r); // 2, 3, 1
在 Object 对象上使用解构:
1
2
3
4
5
6
7
const restaurant = {
name: 'Classico Italiano',
location: 'Via Angelo Tavanti 23, Firenze, Italy',
categories: ['Italian', 'Pizzeria', 'Vegetarian', 'Organic'],
};
const { name, categories } = restaurant;
console.log(name, categories);
在 Object 上进行解构需要用大括号,并且变量名必须与 Object 中的属性名一致。
也可以进行重命名。
1
2
const { name: restaurantName, categories: tags } = restaurant;
console.log(restaurantName, tags);
设置默认值:
1
2
const { name: restaurantName = '', categories: tags = [], menu=[] } = restaurant;
console.log(restaurantName, tags, menu);
嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const restaurant = {
openingHours: {
thu: {
open: 12,
close: 22,
},
fri: {
open: 11,
close: 23,
},
sat: {
open: 0, // Open 24 hours
close: 24,
},
},
};
const { openingHours } = restaurant;
const { fri: { open, close } } = openingHours;
console.log(open, close);
运算符
相等运算符
JavaScript 提供两种相等运算符:==和===。
简单说,它们的区别是相等运算符(==)比较两个值是否相等,严格相等运算符(===)比较它们是否为“同一个值”。如果两个值不是同一类型,严格相等运算符(===)直接返回 false,而相等运算符(==)会将它们转换成同一个类型,再用严格相等运算符进行比较。
两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个地址。
相等运算符会自动转换变量类型,造成很多意想不到的情况。建议不要使用相等运算符(==),只使用严格相等运算符(===)。
1
2
3
18 === 18 // true
'18' == 18 // true
'18' === 18 // false
Spread Operator…
1
2
3
4
const arr=[7,8,9]
const newArr = [1,2, ...arr];
console.log(newArr) // [1,2,7,8,9]
console.log(...newArr) // 1,2,7,8,9
合并两个数组
1
2
3
const arr1 = [1,2];
const arr2 = [3,4];
const arr3 = [...arr1, ...arr2];
也可以用在字符串上:
1
2
3
const str = 'Jonas';
const letters = [...str, ' ', 'S.'];
console.log(letters) //["J","o", "n", "a", "s", " ", "S."]
Rest Pattern
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const arr = [1,2, ...[3,4]];
console.log(arr) // [1,2,3,4]
const [a,b,...others] = arr;
console.log(a, b, others) // 1,2, [3,4]
const { Sat, ...workDays } = restaurant.openingHours;
console.log(workDays);
const add = function (...param) {
let sum = 0;
for (let index = 0; index < param.length; index++) {
sum += param[index];
}
return sum;
};
console.log(add(1, 2, 3, 4, 5, 6));
Optional Chaining ?.
1
2
3
4
console.log(restaurant.order?.(0,1) ?? 'Method does not exist'); //如果order function存在,则调用,否则返回Method does not exist
const users = [{ name: 'Jonas'}];
console.log(users[0]?.name ?? 'User array empty');
函数
在 JavaScript 中,使用函数前,必须用 function 关键字来定义函数。
JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。
由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民。
可以将函数作为变量传递给另一个变量
1
2
3
4
5
6
7
8
9
const jonas = {
year: 1991,
calcAge: function() {
return 2023 - this.year;
}
}
const f = jonas.calcAge;
f(); // 会报错, 因为单独使用function,函数没有owner,导致this是undefined
将函数传递给其他高阶函数
1
2
3
4
5
6
7
8
9
10
11
12
const upperFirstWord = function (str) {
const [first, ...others] = str.split(' ');
return [first.toUpperCase(), ...others].join(' ');
};
const transform = function (str, fn) {
console.log(`Original string: ${str}`);
console.log(`Transformed string: ${fn(str)}`);
console.log(`Transformed by: ${fn.name}`);
};
transform('JavaScript is the best!', upperFirstWord);
函数表达式:
1
2
3
4
const calAge = function(birthYear) {
return 2023 - birthYear;
}
console.log(calAge(1991));
箭头函数:
1
2
3
4
5
6
7
8
9
const calAge2 = birthYear => 2023 - birthYear;
console.log(calAge2(1991));
const yearsUntilRetirement = birthYear => {
const age = 2023 - birthYear;
const retirement = 65 - age;
return retirement;
}
console.log(yearsUntilRetirement(1991));
注意: 箭头函数没有自己的 this 对象,而是使用 parent 的 this:
1
2
3
4
5
const jonas = {
firstName: 'Jonas',
greet: () => console.log(`Hey ${this.firstName}`),
}
jonas.greet(); // console result: Hey undefined
箭头函数中 this 和 var 使用会产生奇怪效果:
1
2
3
4
5
6
var firstName = 'Matilda';
const jonas = {
firstName: 'Jonas',
greet: () => console.log(`Hey ${this.firstName}`),
}
jonas.greet(); // console result: Hey Matilda
对比以下两段代码:
1
2
3
4
5
6
7
8
9
10
11
const jonas = {
firstName: 'Jonas',
year: 1995,
calcAge: function() {
const isMillenial = function() {
console.log(this.year >=1991 & this.year <= 1996)
}
isMillenial();
}
}
jonas.calcAge(); // 会报错, this is undefined
使用箭头函数
1
2
3
4
5
6
7
8
9
10
11
const jonas = {
firstName: 'Jonas',
year: 1995,
calcAge: function() {
const isMillenial = () => {
console.log(this.year >=1991 & this.year <= 1996)
}
isMillenial();
}
}
jonas.calcAge(); // 箭头函数继承了parent的this,能正常输出
默认参数
1
2
3
4
5
6
7
8
9
10
11
const bookings = [];
const createBooking = function (flightNum, numPassengers = 1, price = 199) {
const booking = {
flightNum,
numPassengers,
price,
};
bookings.push(booking);
};
createBooking('LH123');
createBooking('LH123', undefined, 1000);
参数传递方式
函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。
1
2
3
4
5
let p = 2;
function f(p) {
p = 3;
}
f(p); p // 2
如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
1
2
3
4
5
6
let obj = { p: 1 };
function f(o) {
o.p = 2;
}
f(obj);
obj.p // 2
arguments 对象
arguments 对象包含了函数运行时的所有参数,这个对象只有在函数体内部,才可以使用。
1
2
3
4
5
6
7
8
9
let f = function (one) {
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
}
f(1, 2, 3)
// 1
// 2
// 3
虽然 arguments 很像数组,但它是一个对象。数组专有的方法(比如 slice 和 forEach),不能在 arguments 对象上直接使用。
闭包
闭包是“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境,比如 f2 记住了它诞生的环境 f1,所以从 f2 可以得到 f1 的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。
1
2
3
4
5
6
7
8
9
10
function createIncrementor() {
let num = 0;
return function () {
return num++;
};
}
let inc = createIncrementor();
inc() // 0
inc() // 1
inc() // 2
闭包的另一个用处,是封装对象的私有属性和私有方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Person(name) {
let _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}
return {
name: name,
getAge: getAge,
setAge: setAge
};
}
let p1 = Person('张三');
p1.setAge(25);
p1.getAge() // 25
call, bind
call() 方法是 JavaScript 中的函数方法,用于调用一个函数,并将指定的对象作为函数的上下文(this 值)。除了可以指定上下文外,我们还可以将参数作为一个列表传递给该方法。
1
function.call(thisArg, arg1, arg2, ...)
示例:
1
2
3
4
5
6
7
8
9
function greet(message) {
console.log(message + ', ' + this.name + '!');
}
const person = {
name: 'Alice'
};
greet.call(person, 'Hello');
在上述示例中,我们定义了一个 greet 函数,它接受一个消息作为参数,并将该消息与 this.name 的值一起输出到控制台。然后,我们使用 call() 方法将 person 对象作为上下文来调用 greet 函数,并传递了消息参数。
bind() 方法也是 JavaScript 中的函数方法,用于创建一个新的绑定函数。绑定函数是原始函数的一个副本,其中的 this 值被永久地绑定到指定的对象。与 call() 方法不同,bind() 方法不会立即执行函数,而是返回一个绑定了指定上下文的新函数。
1
2
3
4
5
6
7
8
9
10
function greet(message) {
console.log(message + ', ' + this.name + '!');
}
const person = {
name: 'Alice'
};
const greetPerson = greet.bind(person, 'Hello');
greetPerson();
在上述示例中,我们定义了一个 greet 函数,与之前的示例相同。然后,我们使用 bind() 方法创建了一个绑定函数 greetPerson,将 person 对象作为上下文,并传递了消息参数。最后,我们调用 greetPerson() 函数,它会在 person 对象的上下文中输出相应的消息。
另外一个例子,每个国家可以指定自己的税率
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const addTax = (rate, value) => value + value * rate;
console.log(addTax(0.1, 200));
const addJapan = addTax.bind(null, 0.23);
console.log(addJapan(200));
// .bind 相当于以下的复杂函数
const addTaxRate = function (rate) {
return function (value) {
return value + value * rate;
};
};
const addJapn2 = addTaxRate(0.23);
console.log(addJapn2(200));
两者区别:
- 执行时机:
call() 方法立即调用原始函数,并传递参数列表。
bind() 方法返回一个新的绑定函数,不会立即调用原始函数,需要手动调用返回的绑定函数。 - 返回值:
call() 方法执行后,会返回原始函数的结果。
bind() 方法返回一个新的函数,该函数与原始函数具有相同的函数体,但上下文已被永久绑定。 - 函数绑定:
call() 方法在调用时临时绑定函数的上下文(this 值)到指定的对象,只在函数调用期间有效。
bind() 方法创建一个新的绑定函数,将函数的上下文永久绑定到指定的对象。 - 参数传递:
call() 方法在调用时可以接受一个参数列表,并将这些参数作为参数传递给函数。
bind() 方法可以预先传递参数给绑定函数,并在调用绑定函数时作为参数传递。
适用场景:
当我们知道参数的具体数量时,可以使用 call()。
当我们想预设函数的部分参数,并创建一个新的函数时,可以使用 bind() 方法。
当我们需要立即调用函数,并指定执行上下文时,可以使用 call()方法。
面向对象
JavaScript 语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)。
构造函数就是一个普通的函数,但是有自己的特征和用法。
1
2
3
4
5
let Vehicle = function () {
this.price = 1000;
};
let v = new Vehicle();
v.price // 1000
原型对象 rototype
原型链
异步操作
异步操作的几种模式:
-
回调函数
1 2 3 4 5 6 7 8
function f1(callback) { // ... callback(); } function f2() { // ... } f1(f2);
回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(coupling),使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。
-
事件监听
另一种思路是采用事件驱动模式。异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。1 2 3 4 5 6 7
f1.on('done', f2); function f1() { setTimeout(function () { // ... f1.trigger('done'); }, 1000); }
这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以“去耦合”(decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。
-
发布/订阅
下面采用的是 Ben Alman 的 Tiny Pub/Sub,这是 jQuery 的一个插件。1 2 3 4 5 6 7 8 9 10 11 12
// f2向信号中心jQuery订阅done信号 jQuery.subscribe('done', f2); function f1() { setTimeout(function () { // ... jQuery.publish('done'); }, 1000); } // f2完成执行后,可以取消订阅(unsubscribe)。 jQuery.unsubscribe('done', f2);
定时器
-
setTimeout()
setTimeout 函数用来指定某个函数或某段代码,在多少毫秒之后执行。1
let timerId = setTimeout(func|code, delay);
-
setInterval()
setInterval 函数的用法与 setTimeout 完全一致,区别仅仅在于 setInterval 指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行。 -
clearTimeout(),clearInterval()
1 2 3 4
let id1 = setTimeout(f, 1000); let id2 = setInterval(f, 1000); clearTimeout(id1); clearInterval(id2);
Promise
Promise 对象是 JavaScript 的异步操作解决方案,为异步操作提供统一接口。它起到代理作用(proxy),充当异步操作与回调函数之间的中介,使得异步操作具备同步操作的接口。
1
2
3
4
5
function f1(resolve, reject) {
// 异步代码...
}
let p1 = new Promise(f1);
p1.then(f2);
Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态。
1
2
3
异步操作未完成(pending)
异步操作成功(fulfilled)
异步操作失败(rejected)
Promise 实例的 then 方法,用来添加回调函数。
then 方法可以接受两个回调函数,第一个是异步操作成功时(变为 fulfilled 状态)的回调函数,第二个是异步操作失败(变为 rejected)时的回调函数(该参数可以省略)。一旦状态改变,就调用相应的回调函数。
微任务
Promise 的回调函数属于异步任务,会在同步任务之后执行。
但是,Promise 的回调函数不是正常的异步任务,而是微任务(microtask)。它们的区别在于,正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。这意味着,微任务的执行时间一定早于正常任务。
1
2
3
4
5
6
7
8
9
10
11
12
setTimeout(function() {
console.log(1);
}, 0);
new Promise(function (resolve, reject) {
resolve(2);
}).then(console.log);
console.log(3);
// 3
// 2
// 1
窗口对象
在 JavaScript 中,一个浏览器窗口就是一个 window 对象。window 对象主要用来控制由窗口弹出的对话框、打开窗口或关闭窗口、控制窗口的大小和位置等等。
打开窗口
1
window.open(URL, 窗口名称, 参数);
URL:指的是打开窗口的地址,窗口名称:指的是 window 对象的名称,可以是 a 标签或 form 标签中 target 属性值。如果指定的名称是一个已经存在的窗口名称,则返回对该窗口的引用,而不会再新打开一个窗口。
参数:对打开的窗口进行属性设置。
1
2
3
4
5
6
// 打开一个指定位置的窗口:
window.open("www.google.com","","top=200,left=200");
// 打开一个指定大小的窗口:
window.open("www.google.com","","width=200,height=200");
// 打开一个固定大小的窗口:
window.open("www.google.com","","width=200,height=200,resizable");
其他常用方法:
1
2
3
4
5
open()、close() //打开窗口、关闭窗口
resizeBy()、resizeTo() //改变窗口大小
moveBy()、moveTo() //移动窗口
setTimeout(code, time)、clearTimeout() //设置或取消“一次性”执行的定时器
setInterval()、clearInterval() //设置或取消“重复性”执行的定时器
窗口历史
history 对象属性:
1
2
3
4
current //当前窗口的URL
next //历史列表中的下一个URL
previous //历史列表中的前一个URL
length //历史列表的长度,用于判断列表中的入口数目
history 对象方法:
1
2
3
go() //进入指定的网页
back() //返回上一页
forward() //进入下一页
常见的“上一页”与“下一页”实现代码如下:
1
2
<a href="javascript:window.history.forward();">下一页</a>
<a href="javascript:window.history.back();">上一页</a>
对话框
在 JavaScript 中,对话框共有 3 种,这 3 种对话框分别使用以下 3 种方法定义:
1
2
3
alert(message); // 弹出一个提示框
confirm(message); // 用于确认信息,它只有一个参数,返回值为true或false
prompt(message); // 用于输入并返回用户输入的字符串
文档对象
document 对象是 window 对象中的子对象。
-
document 对象属性
1 2 3 4 5 6 7 8 9 10 11
title 文档标题,即title标签内容 URL 文档地址 fileCreateDate 文档创建日期 fileModifiedDate 文档修改时间(精确到天) lastModified 文档修改时间(精确到秒) fileSize 文档大小 fgColor 定义文档的前景色 bgColor 定义文档的背景色 linkColor 定义“未访问”的超链接颜色 alinkColor 定义“被激活”的超链接颜色 vlinkColor 定义“访问过”的超链接颜色
-
document 对象方法
1 2 3 4
document.write() 输入文本到当前打开的文档 document.writeIn() 输入文本到当前打开的文档,并添加换行符“\n” document.getElementById() 获取某个id值的元素 document.getElementsByName() 获取某个name值的元素,用于表单元素
DOM 对象
DOM,全称“Document Object Model(文档对象模型)”,它是由 W3C 组织定义的一个标准。
在前端开发时,我们往往需要在页面某个地方添加一个元素或者删除元素,这种添加元素、删除元素的操作就是通过 DOM 来实现的。说白了,DOM 就是一个接口,我们可以通过 DOM 来操作页面中各种元素,例如添加元素、删除元素、替换元素等。
-
DOM 节点属性
1 2 3 4 5 6 7
parentNode 获取当前节点的父节点 childNodes 获取当前节点的子节点集合 firstChild 获取当前节点的第一个子节点 lastChild 获取当前节点的最后一个子节点 previousSibling 获取当前节点的前一个兄弟节点 nextSibling 获取当前节点的后一个兄弟节点 attributes 元素的属性列表
-
DOM 节点操作
1 2 3 4 5
let e = document.createElement("元素名"); obj.appendChild(new) obj.insertBefore(new,ref) obj.removeChild(oldChild); obj.style.属性名; // 操作CSS样式
操作实例:
1 2 3 4 5 6
let ccontent = document.createElement("div"); ccontent.className = "box"; ccontent.style.position = "absolute"; ccontent.style.top = "10px"; ccontent.style.left = "10px"; cparent.appendChild(ccontent);
-
选择节点
1
document.querySelector('.message').textContent
-
添加事件
1 2 3
document.querySelector('.btnCheck').addEventListener('click', function () { console.log(document.querySelector('.guess').value); });
监听键盘 keydown 事件:
1 2 3 4 5
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') { console.log('ESC is pressed!'); } });
-
修改HTML,
innerHTML
获取全部html内容,insertAdjacentHTML
插入页面元素1 2 3 4 5 6 7 8
const containerMovements = document.querySelector('.movements'); containerMovements.innerHTML = ''; const html = ` <div class="movements__row"> ... </div> `; containerMovements.insertAdjacentHTML("afterbegin", html);
事件
在 JavaScript 中,事件往往是页面的一些动作引起的,例如当用户按下鼠标或者提交表单,甚至在页面移动鼠标时,事件都会出现。
在 JavaScript 中,调用事件的方式共有 2 种:
-
在 script 标签中调用;
1 2 3 4 5 6 7 8 9 10
let e = document.getElementById("btn"); e.onclick = function () { alert("button clicked"); } document.getElementById("btn1").addEventListener("mouseover", showType); function showType(event){ alert(event.type); alert(event.target); }
-
在元素中调用;
1 2 3
<body> <input type="button" onclick="alert('button clicked')" value="按钮"/> <body>
鼠标事件
1
2
3
4
5
6
7
onclick 鼠标单击事件
ondbclick 鼠标双击事件
onmouseover 鼠标移入事件
onmouseout 鼠标移出事件
onmousemove 鼠标移动事件
onmousedown 鼠标按下事件
onmouseup 鼠标松开事件
键盘事件
1
2
3
onkeydown 按下键事件(包括数字键、功能键)
onkeypress 按下键事件(只包含数字键)
onkeyup 放开键事件(包括数字键、功能键)
页面相关事件
1
2
3
onload 页面加载事件
onresize 页面大小事件
onerror 页面或图片加载出错事件