前端三层:
| | 语言 | 功能 |
|-------|-------|-------|
| 结构层 | HTML | 搭建结构、放置部件、描述语义 |
| 样式层 | CSS | 美化页面、实现布局 |
| 行为层 | JavaScript | 实现交互效果、数据收发、表单验证等 |
ECMAScript是JavaScript的标准:
- 1997年,欧洲计算机制造商协会(ECMA)设置了JavaScript的标准,命名为 ECMAScript
REPL(Read、Eval、Print、Loop)环境:
- 浏览器控制台是一个 REPL 环境,可以使用它临时测试表达式的值
- read:读
- eval:执行
- print:打印
- loop:循环
标识符的命名规则:
- 只能由字母、数字、下划线、$符组成,但不能以数字开头
- 不能是关键字或保留字
- 变量名大小写敏感,a和A是两个不同的变量
// 合法命名举例
str aBc_123 $2 $0o_o0$ $
// 非法命名举例
2a aBc#123 ¥2 true
优秀的变量命名方法:
- 驼峰命名法:mathScore 不建议的命名:mathscore
- c风格:math_test_score
定义变量:
- 要想使用变量,第一步就是声明它,并给它赋值
- 使用逗号同时声明和初始化多个变量
// 使用 var 关键字定义变量
// = 表示赋值,将右边的数值赋值给左边的变量
var a = 5;
// 使用逗号同时声明和初始化多个变量
var b = 2, c = 3, d = 4;
使用变量:
- 当变量被赋值后,就可以使用它
// 变量使用时不能加引号,加引号就变成字符串了
console.log(a);
改变变量的值:
- 变量的值可以被改变,当改变变量值时不再需要写关键字 var 了
var a = 10;
a = 12; // 更改变量a的值为12
变量的默认值:
- 一个变量只定义,但没有赋初值,默认值是 undefined
- 一个变量只有被 var 定义,并赋初值之后,才能算正式初始化完成
变量定义的常见错误:
- 不用 var 定义,而直接将值赋予它,虽然不引发报错,但 会产生作用域问题
- 使用一个既没有被 var 定义过,也没有赋值过的字符,就会产生引用错误
console.log(a); // 先使用变量,由于不提升值,所有输出 undefined
var a = 12; // 后定义变量
JS 中两大类数据类型:
- 基本数据类型:Number、String、Boolean、Undefined、Null
- 复杂数据类型:Object、Array、Function、RegExp、Date、Map、Set、Symbol等等
typeof 运算符:
- 使用 typeof 运算符可以检测 值或变量 的类型
var a = 12;
console.log(typeof a); // number
5种基本数据类型的 typeof 检测结果:
| 类型名 | typeof 检测结果 |
|--------|--------|
| 数字类型 | number |
| 字符串类型 | string |
| 布尔类型 | boolean |
| undefined类型 | undefined |
| null类型 | object |
科学计数法:
- 较大或较小数(绝对值较小)可以写成科学计数法形式
3e8; // 300000000
3e-4; // 0.0003
-3e4; // -30000
.3e5; // 30000
不同进制的数字:
- 二进制数值以0b开头,例:0b11
- 八进制数值以0开头,例:017
- 十六进制数值以0x开头,例:0xf
一个特殊的数字型值 NaN:
- NaN是英语“not a number”的意思,即“不是一个数”,但它 是一个数字类型的值
typeof NaN; // number
console.log(NaN == NaN); // false
typeof 11; //number
typeof '11'; // string
'hello' + ' ' + 'world'; //hello world
var a = 10;
var str = '我有' + a + '元钱';
var str = '';
'JS'.length // 2
''.length // 0
字符串的常用方法:
- “方法”就是能够打点调用的函数,字符串有丰富的方法
方法 | 功能 |
---|---|
charAt() | 得到指定位置字符 |
substring() | 提取字符串 |
substr() | 提取字符串 |
slice() | 提取字符串 |
toUpperCase() | 将字符串变为大写 |
toLowerCase() | 将字符串变为小写 |
indexOf() | 检索字符串 |
- charAt() 方法可以得到指定位置的字符,当指定的位置超出字符串长度,则返回空字符串 |
'我喜欢JS'.charAt(0) // '我'
'我喜欢JS'.charAt(10) // ''
'我喜欢JS'.substring(1,3) // '喜欢'
'我喜欢JS'.charAt(2) // '欢JS'
'我喜欢JS'.substring(3,1) // '喜欢'
'我喜欢JavaScript'.substr(2,3) // '欢Ja'
'我喜欢JavaScript'.substr(3) // ' JavaScript'
'我喜欢JavaScript'.substr(-5,4) // 'crip'
'我喜欢JavaScript'.slice(2,3) // '欢Ja'
'我喜欢JavaScript'.slice(-6,-1) // ' Scrip'
'我喜欢JavaScript'.slice(5,4) // ''
'i like js'.toUpperCase() // 'I LIKE JS'
'HELLO'.toLowerCase() // 'hello'
'abcdefg'.indexOf('b') // 1
'abcdefg'.indexOf('ef') // 4
'abcdefg'.indexOf('h') // -1
3 < 5 // true
5 < 3 // false
typeof undefined; // undefined
console.log(a); // undefined
console.log(typeof a); // undefined
var a = 12;
typeof null; // object
其他值 -> 数字(Number):
- 使用 Number() 函数,可以将其它值转换为数字
- 纯数字字符串能转变为数字,不是纯数字的字符串中就会转为 NaN
- 空字符串会转为 0
- 布尔值转变为数字:true变为1;false变为0
- undefined 和 null 变为数字:undefined 变为 NaN;null 变为 0
Number('123'); // 123
Number('123.4'); // 123.4
Number('12月'); // NaN
Number('2e3'); // 2000
Number(''); // 0
Number(true); // 1
Number(false); // 0
Number(undefined); // NaN
Number(null); // 0
使用 parseInt() 函数:
- parseInt() 函数的功能是将字符串转为整数
- parseInt() 会自动截掉第一个非数字字符之后的所有字符
- parseInt() 可以将所有文字截掉
- 如果字符串不是以数字开头,则转为 NaN
- parseInt()函数不会四舍五入
parseInt('3.14'); // 3
parseInt('3.14圆周率'); // 3
parseInt('圆周率是3.14'); // NaN
parseInt('3.89'); // 3
使用 parseFloat() 函数:
- parseFloat() 函数的功能是将字符串转为浮点数
parseFloat('3.14'); // 3.14
parseFloat('3.14圆周率'); // 3.14
parseFloat('圆周率是3.14'); // NaN
parseFloat('3.89'); // 3.89
使用 String() 函数:
- 使用 String() 函数可以将其它值转变为字符串
- 数字->字符串:变为“长得相同”的字符串;科学计数法和非十进制数字会转为 十进制的值
- 布尔->字符串:变为“长的相同”的字符串
- undefined 和 null -> 字符串:变为“长的相同”的字符串
String(123); // '123'
String(3.14); // '3.14'
String(2e3); // '2000'
String(NaN); // 'NaN'
String(Infinity); // 'Infinity'
String(0xf); // '15'
String(true); // 'true'
String(false); // 'false'
String(undefined); // 'undefined'
String(null); // 'null'
使用 toString() 方法:
- 使用 toString() 方法将其它值转为字符串
- 几乎所有的值都有 toString() 方法,功能是将值转为字符串
- 数字需要加圆括号才能调用 toString() 方法
(3).toString(); // "3"
使用 Boolean() 函数:
- 使用 Boolean() 函数可以将其它值转为布尔值
- 数字->布尔值:0和NaN转为false;其他数字都转为 true
- 字符串->布尔值:空字符串变为false;其它都转为true
- undefined 和 null->布尔值:都转为false
Boolean(13); // true
Boolean(0); // false
Boolean(NaN); // false
Boolean(Infinity); // true
Boolean(-Infinity); // true
Boolean(''); // false
Boolean('adc'); // true
Boolean('false'); // true
Boolean(undefined); // false
Boolean(null); // false
运算符名称 | 运算符 |
---|---|
加 | + |
减 | - |
乘 | * |
除 | / |
取余 | % |
- 加号有作“加法”和“连字符”两种作用 | |
- 如果加号两边的操作数都是数字,则为“加法”,否则为“连字符” | |
- 取余运算也叫作“求模运算”,用百分号 % 表示 | |
- a % b 表示求 a 除以 b 的余数 |
隐式类型转换:
- 如果 参与数学运算的某操作数不是数字型,那么JavaScript会自动将此操作数转换为数字型
- 隐式转换的本质是内部调用 Number() 函数
3 * '4' // 12
true + true // 2
false + 1 // 1
3 * '2天' // NaN
有关IEEE754:(toFixed()方法)
- 在JavaScript中,有些小数的数学运算不是很精准
0.1 + 0.2 // 0.30000000000000004
Number((0.1 + 0.2).toFixed(2)); // 0.3
幂和开根号:
- JavaScript中没有提供幂计算、开根号的运算符,需要使用Math对象的相关方法进行计算
Math.pow(2, 3) // 8
Math.pow(3, 2) // 9
Math.sqrt(81) // 9
Math.sqrt(-81) // NaN 负数不能开根号
向上取整和向下取整:
- Math.ceil() 向上取整;Math.floor() 向下取整
Math.ceil(2.4) // 3
Math.floor(2.4) // 2
Math.ceil(-2.4) // -2
Math.floor(-2.4) // -3
Math.ceil(2) // 2
Math.floor(2) // 2
运算符名称 | 运算符 |
---|---|
大于 | > |
小于 | < |
大于或等于 | >= |
小于或等于 | <= |
等于 | == |
不等于 | != |
全等于 | === |
不全等于 | !== |
- 大于 >、小于 < 两个符号和数学相同 | |
- “大于等于”运算符是 >=,“小于等于”运算符是 <= | |
- 如果想比较两个值是否相等,此时应该使用 == 运算符 | |
- 两个等号 == 运算符 不比较值的类型,它会进行隐式转换后比较值是否相等 | |
- 三个等号 === 运算符,不仅比较值是否相等,也比较类型是否相同 |
5 == '5' // true
5 === '5' // false
1 == true // true
1 === true // false
0 == false // true
0 === false // false
0 == undefined // false
0 === undefined // false
undefined == null // true
undefined === null // false (typeof) undefined object
5 != 6 // true
5 !== 6 // true
5 != '5' // false
5 !== '5' // true
NaN不自等:
- NaN 作为一个特殊的数字类型值,它在用 == 比较的时候也有特殊结果
NaN == NaN // false
NaN === NaN // false
isNaN(NaN) // true
isNaN(3) // false
isNaN(undefined) // true
isNaN('3年') // true
isNaN(null) // false
运算符名称 | 运算符 |
---|---|
与 | && |
或 | || |
非 | ! |
与运算:
- && 表示“且”,称为“与运算”,口诀:全真才真
true && true // true
false && true // false
false && false // false
true && false // false
或运算:
- || 表示“或者”,称为“或运算”,口诀:有真即真
true || true // true
false || true // true
false || false // false
true || false // true
非运算:
- ! 表示“非”,也可以成为“置反运算”
- ! 是一个“单目运算符”,只需要一个操作数
- 置反运算的结果一定是布尔值
!true // false
!false // true
!0 // true
!undefined // true
!'' // true
!'hello' // false
!!true // true
!!0 // false
!!'' // false
!!'hello' // true
短路计算:
- a && b 运算中:a 真,表达式值为 b;a 假,表达式值为 a
3 && 6 // 6
undefined && 12 // undefined
null && 2 // null
'' && 13 // ''
NaN && undefined // undefined
逻辑运算的优先顺序:
- 逻辑运算的优先级:非 -> 与 -> 或
!true || true // true
3 && 4 || 5 && 6 // 4
运算符名称 | 运算符 |
---|---|
赋值 | = |
快捷赋值 | +=、-=、*=、/=、%= |
自增运算 | ++ |
自减运算 | – |
赋值运算符:
- JS中,= 表示赋值,== 是判断是否相等(不比较类型),=== 是判断是否全等(比较类型)
- 赋值运算符会将赋值符号右边的数值,赋予赋值符号左边的变量
- 赋值运算也会产生值,符号后面的值将作为“赋值运算的值”
var a;
console.log(a = 3); // 3
var a,b,c;
a = b = c = 12;
console.log(a); // 12
console.log(b); // 12
console.log(c); // 12
++a 和 a++ 的区别:
- ++a 先加再用;a++ 先用再加
- 同理,–a 先减再用;a– 先用再减
var a = 3;
var b = a++;
console.log(b); // 3
a = 3;
var c = ++a;
console.log(c); // 4
a = 3;
var m = --a;
console.log(m); // 2
a = 3;
var n = a--;
console.log(n); // 3
5 < 3 + 3 // true
3 > 2 && 8 > 3 + 4 // true
3 > 2 && 8 > 3 + 5 // false
!3 < 5 - 3 // true
!3 < 5 - 5 // false
变量的范围表示:
- 验证变量 a 是否介于 3 到 12 之间
a >= 3 && a <= 12
闰年判断:
- 公历闰年的简单计算方法,满足以下两个条件之一
- 能被 4 整除且不能被100整除
- 能被100整除也能被400整除
// 弹出输入框
var year = Number(prompt('请输入年份'));
// 根据两个条件判断(满足以下两个条件之一)
// 能被4整除且不能被100整除
// 能被100整出也能被400整除
alert((year / 4 == 0 && year % 100 != 0)||(year % 100 == 0 && year % 400 == 0));
if(条件1) {
// 符合条件1执行
// 语句块1
}else if(条件2) {
// 符合条件2执行
// 语句块2
}
……
else {
// 以上条件都不符合
// 语句块
}
算法题:判断水仙花数
- 水仙花数是这样的三位数:它的每个数位的立方和等于它的本身
- 如何拆位:
1)数学方法:百位是原数字除以100取整;十位是原数字除以10取整,再与10求模;个位是原数字与10求模
2)字符串方法:直接将原数字变为字符串,然后使用 charAt() 方法得到每个数位的值
var m = Number(prompt('请输入一个三位数'));
// 对用户输入的数值,进行合法性教验
if(!NaN(m) && m >= 100 && m <= 999) {
var m_str = m.toString();
// 百分位
// Math.floor() 向下取整;Math.ceil() 向上取整
// var a = Math.floor(m / 100);
var a = Number(m_str.charAt(0));
// 十分位
// var b = Math.floor(m / 10) % 10;
var b = Number(m_str.charAt(1));
// 个位
// var c = m % 10;
var c = Number(m_str.charAt(2));
// 根据水仙花条件判断
if(Math.pow(a,3) + Math.pow(b,3) + Math.pow(c,3) == m) {
alert(m + '是水仙花数!');
} else {
alert(m + '不是水仙花数');
}
}else {
alert('您的输入数字不合法!');
}
定义数组:
- 定义数组只需要使用方括号 [] 即可
var arr = ['a','b','c','d'];
var arr = new Array('a','b','c','d');
var arr = new Array(4);
访问数组项:
- 数组每一项都有下标,下标从 0 开始
- 为了防止误会,一般称呼某项为“下标为x的项”,而不称呼为“第x项”
- 可以使用 在方括号中书写下标的形式,访问数组的任一项
var arr = ['a','b','c','d'];
console.log(arr[0]); // a
console.log(arr[1]); // b
console.log(arr[2]); // c
console.log(arr[3]); // d
下标越界:
- JavaScript中规定,访问数组中不存在的项会返回 undefined,不会报错
var arr = ['a','b','c','d'];
console.log(arr[4]); // undefined
console.log(arr[-1]); // undefined
console.log(arr[100]); // undefined
数组的长度:
- 数组的 length 属性 表示它的长度
- 数组最后一项的下标是数组的长度减 1
var arr = ['a','b','c','d'];
console.log(arr.length); // 4
更改数组项:
- 数组并不是只读的,我们可以通过数组下标找到对应的项,然后更改值
var arr = [1,2,3,4];
arr[0]++;
arr[3] = 5;
console.log(arr); // [2, 2, 3, 5]
var arr = [1,2,3,4];
arr[6] = 5;
console.log(arr); // [1, 2, 3, 4, undefined, undefined, 5]
数组遍历:
- 数组的最大优点就是 方便遍历
var arr = [1,2,3,4];
for(var i = 0; i < arr.length; i++) {
console.log(arr[i]); // [1, 2, 3, 4]
}
数组的头尾操作方法:
方法 | 功能 |
---|---|
push() | 在尾部插入新项 |
pop() | 在尾部删除 |
unshift() | 在头部插入新项 |
shift() | 在头部删除 |
push() 方法:
- push() 方法用来 在数组末尾推入新项,参数就是要推入的项
- 如果要推入多项,可以用逗号隔开,推入的项直接放到数组尾部,相当于在原数组后补充一个数组
- 调用 push() 方法后,数组会立即改变,不需要赋值
var arr = [1,2,3];
arr.push(11);
arr.push(21,22,23);
console.log(arr); // [1, 2, 3, 11, 21, 22, 23]
pop() 方法:
- 与 push() 方法相反,pop() 方法用来 删除数组中的最后一项
- pop() 方法不仅会删除数组末项,而且还会返回被删除的项
- 如果在 pop() 方法的圆括号内添加参数,参数没有任何意义,也不会报错
var arr = [11, 22, 33, 44, 55];
var item = arr.pop();
console.log(item); // 55
console.log(arr); // [11, 22, 33, 44]
unshift() 方法:
- unshift() 方法用来 在数组头部插入新项,参数就是要插入的项
- 如果要推入多项,可以用逗号隔开,推入的项直接放到数组头部,相当于在原数组前补充一个数组
- 调用 unshift() 方法后,数组会立即改变,不需要赋值
var arr = [1,2,3];
arr.unshift(11);
arr.unshift(21,22,23);
console.log(arr); // [21, 22, 23, 11, 1, 2, 3]
shift() 方法:
- 与 unshift() 方法相反,shift() 方法用来 删除数组中下标为 0 的项
- shift() 方法不仅会删除数组末项,而且还会返回被删除的项
- 如果在 shift() 方法的圆括号内添加参数,参数没有任何意义,也不会报错
var arr = [11, 22, 33, 44, 55];
var item = arr.shift();
console.log(item); // 11
console.log(arr); // [22, 33, 44, 55]
splice() 方法:
- splice(start, length, [params …]) 方法用于 替换数组中的指定项
- start 表示起始项,从下标为 start 的项开始替换
- length 表示替换多少项
- [params …] 表示替换成的内容
- splice() 方法会以数组形式返回被删除的项
var arr = ['a','b','c','d','e','f'];
// 1.表示从下标为3项开始,将后面的 2 项替换为 1,2,3
arr.splice(3,2,1,2,3);
console.log(arr); // ['a','b','c', 1, 2, 3,'f']
// 2.表示在数组下标为 3 的位置插入 1,2,3
arr.splice(3,0,1,2,3);
console.log(arr); // ['a','b','c', 1, 2, 3, ‘d', 'e', 'f']
// 3.表示从数组下标为 3 的位置开始删除 2 项
arr.splice(3,2);
console.log(arr); // ['a','b','c', 'f']
// 4.表示从数组下标为 3 的位置开始删除后面所有项
var items = arr.splice(3);
console.log(arr); // ['a','b','c']
console.log(items); // ['d','e','f']
slice() 方法:
- slice() 方法用于得到 子数组,类似于字符串的 slice() 方法
- slice(a, b) 截取的子数组 从下标为 a 的项开始,到下标为 b 的项结束(不包括下标为 b 的项)
- slice() 方法不会更改原数组
- slice() 如果不提供第二个参数,则表示从指定项开始,提取所有后续项作为子数组
- slice() 方法的 参数允许为负数,表示数组的倒数第几项
var arr = ['a','b','c','d','e','f'];
var child_arr1 = arr.slice(3,5);
var child_arr2 = arr.slice(3);
var child_arr3 = arr.slice(3,-2);
var child_arr4 = arr.slice(-4, -2);
console.log(child_arr1); // ['d','e']
console.log(child_arr2); // ['d','e','f']
console.log(child_arr3); // ['d']
console.log(child_arr4); // ['c', 'd']
console.log(arr); // ['a','b','c','d','e','f']
join() 和 split() 方法:
- 数组的 join() 方法可以 使数组转为字符串;字符串的 split() 方法可以使字符串转为数组
- join() 的参数表示 以什么字符作为连接符,如果 留空则默认以逗号分隔,如同 toString() 方法
- split() 的参数表示以什么字符拆分字符串,一般不能留空
var arr = ['a','b','c','d'];
// 不写参数,默认使用“,”分隔
var arr_str = arr.join();
console.log(arr_str); // a,b,c,d
// 使用“-”作为分隔符
var arr_str1 = arr.join('-');
console.log(arr_str1); // a-b-c-d
// 使用空字符串作为分隔符
var arr_str2 = arr.join('');
console.log(arr_str2); // abcd
// 不写参数,会将整个字符串当做一个数组项
var str_arr = arr_str.split();
console.log(str_arr); // ["a,b,c,d"]
// 使用空字符串,会连带将分隔符一起作为数组项
var str_arr = arr_str.split('');
console.log(str_arr); // ["a", ",", "b", ",", "c", ",", "d"]
// 使用分隔符分隔,会将每一个字符作为一项
var str_arr = arr_str.split(',');
console.log(str_arr); // ["a", "b", "c", "d"]
字符串和数组更多相关特性:
- 字符串也可以使用方括号内写下标的形式访问某个字符,等价于 charAt() 方法
- 这样的话,就不常使用 charAt() 了
- 字符串的一些算法问题有时候会转换为数组解决
var str = "hello world!";
console.log(str[0]); // "h"
console.log(str[2]); // "l"
console.log(str[5]); // " "
console.log(str[11]); // "!"
console.log(charAt(0)); // "h"
console.log(charAt(2)); // "l"
console.log(charAt(5)); // " "
console.log(charAt(11)); // "!"
concat() 方法:
- concat() 方法可以 合并连结多个数组
- concat() 方法并不会改变原数组
var arr1 = [1,2,3];
var arr2 = [4,5,6];
var arr3 = [7,8,9,10,11];
var arr = arr1.concat(arr2, arr3);
console.log(arr); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
console.log(arr1); // [1, 2, 3]
reverse() 方法:
- reverse() 方法用来将一个数组中的全部项顺序置反
var arr = ['A','B','C','D','E','F','G'];
arr.reverse();
console.log(arr); // ["G", "F", "E", "D", "C", "B", "A"]
// 将一个字符串置反
var str = "abcdefg";
console.log(str.split('').reverse().join('')); // "gfedcba"
// 逐步拆解如下:
var str_arr = str.split('');
console.log(arr); // ["a", "b", "c", "d", "e", "f", "g"]
arr.reverse();
var arr_str = arr.join('');
console.log(arr_str); // "gfedcba"
indexOf 和 includes() 方法:
- indexOf() 方法的功能是 搜索数组中的元素,并返回它所在的位置,如果元素不存在,则返回 -1
var arr = ['a','b','c','d','e','c'];
// 搜索数组中的元素,并返回它所在的位置(下标数)
var index1 = arr.indexOf('a');
console.log(index1); // 0
// 当数组中多次出现某个元素,则返回第一次出现时所在的下标数
var index2 = arr.indexOf('c');
console.log(index2); // 2
// 当搜索的元素不存在,则返回 -1
var index3 = arr.indexOf('g');
console.log(index3); // -1
var arr = ['a','b','c','d','e','c'];
// 数组是否包含一个指定的值,返回布尔值
var flag1 = arr.includes('a');
console.log(flag1); // true
var flag2 = arr.includes('g');
console.log(flag2); // false
var arr = ['a','b','c',11,22];
var flag1 = arr.includes('a');
console.log(flag1); // true
var flag2 = arr.includes(22);
console.log(flag2); // true
var flag3 = arr.includes('11');
console.log(flag3); // false
var flag4 = arr.includes(b);
console.log(flag4); // 报错
var index1 = arr.indexOf('b');
console.log(index1); // 1
var index2 = arr.indexOf(22);
console.log(index2); // 4
var index3 = arr.indexOf('11');
console.log(index3); // -1
数组去重:
var arr = [1,1,3,4,2,3,5,5,7,6,7,3,2,9];
var result = [];
for(var i = 0; i < arr.length; i++) {
if(!result.includes(arr[i])) {
result.push(arr[i]);
}
}
console.log(result); // [1, 3, 4, 2, 5, 7, 6, 9]
随机样本:
- 题目:请随机从原数组中取3项
- 思路:准备一个空结果数组,遍历原数组,随机选择一项推入结果数组,并将这项删除
var arr = [1,3,2,5,9,3,7,6,4,8,10];
var result = [];
// 3 表示执行三次
for(var i = 0; i < 3; i++) {
// [a,b]区间的随机整数是:parseInt(Math.random() * (b - a + 1)) + a;
var randomIndex = parseInt(Math.random() * arr.length);
// 将随机索引下标项推入结果数组
result.push(arr[randomIndex]);
// 原数组删除这项
// splice(a,b) 表示从 a 位置开始删除 b 项
arr.splice(randomIndex,1);
}
console.log(result);
console.log(arr);
var arr = [1,3,2,5,9,7,6,4,8,10];
for(var i = 0; i < arr.length - 1; i++) {
for(var j = 0; j < arr.length - i - 1; j++) {
if(arr[j] > arr[j + 1]) {
var temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
console.log(arr);
var matrix = [
[11,22,33],
[1,2,3],
[4,5,6,7],
[45,54,66]
];
console.log('数组长度为:' + matrix.length); // 4
// 遍历二维数组中的每一项
for(var i = 0; i < matrix.length; i++) {
for(var j = 0; j < matrix[i].length; j++) {
console.log(matrix[i][j]);
}
}
函数的定义:
// function 表示定义函数
// fun 是函数名,函数名必须符合 JS 标识符命名规则
// () 里面是行参列表,即使没有行参,也必须书写圆括号
// {} 大括号中就是函数体语句
function fun() {
// 函数体语句
}
函数表达式:
- 相当于定义一个变量指向匿名函数
// function 是匿名函数
var fun = function() {
// 函数体语句
}
函数的调用:
- 执行函数体中的所有语句,就称为“调用函数”
- 调用函数非常简单,只需要在函数名字后书写圆括号对即可
fun(); // 调用函数
function fun() {
console.log('A')
console.log('B')
console.log('C')
}
console.log('1')
console.log('2')
console.log('3')
fun();
console.log('4')
console.log('5')
console.log('6')
// 在浏览器控制台输出
1 2 3 A B C 4 5 6
fun();
function fun() {
console.log('函数体被执行');
}
fun(); // 引发错误
var fun = function() {
console.log('函数体被执行');
}
函数的优先提升:
- 函数优先提升
- 然后才是变量声明的提升,无法覆盖提升的函数
fun(); // 打印 B
var fun = function() {
console.log(' A');
}
function fun() {
console.log('B');
}
fun(); // 打印 A
函数的参数:
- 参数是函数内的一些待定值,在函数调用时,必须传入这些参数的具体值
- 函数的参数可多可少,函数可以没有参数,也可以有多个参数,多个参数之间要用逗号隔开
// 这里的 a 和 b 就是函数的行参
function sum(a, b) {
var add = a + b;
console.log("两个参数之和为:" + add);
}
// 这里的 3 和 5 就是调用函数传入的实参
sum(3,5);
行参和实参个数不同的情况:
- 当行参数大于实参数,则没有收到实参的行参的值就会为undefined;un defined 进行任何运算结果都是 NaN
function fun(a, b, c) {
var sum = a + b + c;
console.log(sum);
}
fun(1,2);
// 输出结果为
NaN
function fun(a, b) {
var sum = a + b;
console.log(sum);
}
fun(2,3,5); // 控制台报错
arguments:
- 函数内 arguments 表示它接收到的实参列表,它是一个类数组对象
- 不管用户传入多少个实际参数,永远能够得到传入的值
- 类数组对象:所有属性均为从0开始的自然数序列,并且有length属性,和数组类似可以用方括号“[]”书写下标访问对象的某个属性值,但是不能调用数组的方法
function fun() {
var sum = 0;
for(var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
console.log(sum);
}
fun(12,23,32,55);
fun(6,7);
fun(3,-2,-5,6);
函数的返回值:
- 函数体内可以使用 return 关键字 表示 “函数的返回值”
- 函数的返回值可以被变量接收
- 调用一个有返回值的函数,可以被当做一个普通值,从而可以出现在任何可以书写值的地方
function sum(a,b) {
return a + b;
}
var result = sum(1,2) * sum(5,7);
function fun() {
console.log('A');
return 'B';
console.log('C'); // 不会执行
}
console.log(1)
var ch = fun();
console.log(ch);
console.log(2)
// 输出结果
1 A B 2
// 求一个数的阶乘
// 思路:n! 就是 n*(n-1)!
function factorial(n) {
// 递归的出口
if(n == 1) return 1;
return n * factorial(n - 1);
}
var result = factorial(10);
console.log(result);
// 计算一个数字的阶乘
function factorial(n) {
var result = 1;
for(var i = 1; i <= n; i++) {
result *= i;
}
return result;
}
// 利用穷举法寻找100到999的喇叭花数
for(var i = 100; i <= 999; i++) {
// 把数字变为字符串
var i_str = i.toString();
var a = Number(i_str[0]); // 百分位
var b = Number(i_str[1]); // 十分位
var c = Number(i_str[2]); // 个位
if(i == factorial(a) + factorial(b) + factorial(c)) {
console.log(i);
}
}
var arr = [12,3,1,4,6,2];
// 调用 sort() 方法排序
arr.sort(function(a,b) {
if(a > b) {
return 1;
} else {
return -1;
}
});
console.log(arr);
// 也可以简写为
var arr = [12,3,1,4,6,2];
// 调用 sort() 方法排序
arr.sort(function(a,b) {
return a - b; // 升序
// return b - a; // 降序
});
console.log(arr);
function fib(n) {
// 数列的下标为0的项和下标为1的项的值都是1
if(n == 0 || n == 1) return 1;
// 除了前两项,后面的都是 等于前两项的和
return fib(n - 1) + fib(n - 2);
}
举例 | 当 var a = b 变量传值时 | 当用 == 比较时 | |
---|---|---|---|
基本类型 | 数字、字符串、布尔型、undefined | 内存中产生新的副本 | 比较值是否相等 |
引用类型 | 对象、数组 | 内存中不产生新的副本,而是让新的变量指向同一个对象 | 比较内存地址是否相同,即比较是否是同一个对象 |
浅克隆:
- 使用 var arr1 = arr2 这样的语句 不能 实现数组的克隆
- 浅克隆:准备一个空的数组,然后使用 for 循环遍历原数组,将遍历到的项都推入空的数组
- 浅克隆只克隆数组的一层,如果数组是多维数组,则克隆的项会“藕断丝连”,即除一维外的其它维数组将会以引用的方式克隆到新数组中,与原数组“藕断丝连”
var arr = [11,33,22,55,44];
// 空的数组
var result = [];
// 遍历原数组,将遍历到的项都推入到空数组中
for(var i = 0; i < arr.length; i++) {
result.push(arr[i]);
}
console.log(result); // 11,33,22,55,44
console.log(result == arr); // false
实现深克隆:
- 使用递归思想,如果遍历到的项是基本类型值,则直接推入到结果数组中;如果遍历到的项又是数组,则重复执行浅克隆的操作。
// 原数组
var arr = [33,44,22,55,[12,2,31,6],10,[11,32,[9,4,5,7,2]]];
function deepClone(arr) {
// 结果数组
var result = [];
// 遍历数组的每一项
for(var i = 0; i < arr.length; i++) {
// 如果遍历到的项是数组
if(Array.isArray(arr[i])) {
// 递归
result.push(deepClone(arr[i]));
} else {
// 如果不是数组项,而是基本类型值,就直接推入到结果数组中
// 相当于递归的出口
result.push(arr[i]);
}
}
// 返回结果数组
return result;
}
var temp = deepClone(arr);
function fun() {
// 变量 a 是在 fun函数中被定义的,所以变量a只在fun函数内部有定义,
// fun函数就是a的作用域,变量a被称为局部变量
var a = 10;
}
fun();
console.log(a); // 此时会报错:a is not defined
var a = 10;
function fun() {
a++;
console.log(a); // 输出 11
}
fun();
console.log(a); // 输出 11
var a = 10;
function fun() {
var a = 5;
a++;
console.log(a); // 输出 6
}
fun();
console.log(a); // 输出 10
var a = 10;
function fun() {
a++;
var a = 5;
console.log(a); // 输出 5
}
fun();
console.log(a); // 输出 10,局部变量不影响全局变量
function fun() {
function inner() { // 该函数是局部函数
console.log("局部函数");
}
inner(); // 调用内部函数
}
fun(); // 调用外部函数
var a = 10;
var b = 20;
function fun() {
var c = 30;
function inner() { // 该函数是局部函数
var a = 40;
var d = 50;
console.log(a, b, c, d); // 使用变量时,JS 会从当前层开始,逐层向上寻找定义
}
inner();
}
fun(); // 输出 a=40 b=20 c=30 d=50
function fun() {
a = 5;
console.log(a); // 输出 5
}
fun();
console.log(a); // 输出 5
function fun() {
// 定义一个局部变量
var name = "ABC";
// 返回一个局部函数
return function() {
console.log(name);
}; // 别忘了“;”,return 返回的结果要有“;”结尾
// 或者写为下面这种形式
// function innerFun() {
// console.log(name);
// }
// return innerFun;
}
// 调用外部函数,就能得到内部函数,用变量 inn 来接收
var inn = fun();
// 执行 inn 函数,就相当于在 fun 函数的外部,执行了内部函数
inn(); // 内部函数被移动到了外部执行
闭包用途1–记忆性:
- 当闭包产生时,函数所处环境的状态会始终保持在内存中,不会在外层函数调用后被自动清除。这就是闭包的记忆性。
- 记忆性在下面的函数中表现为传入checkTemp函数的值时,他能一直记住
// 闭包的记忆性举例
// 创建体温检测函数 checkTemp(n),可以检查体温n是否正常,函数会返回布尔值
// 但是,不同的小区有不同的体温检测标准,比如A小区温度合格线时37.3,而B小区体温合格线是37.1
function checkTemp(standardTemp) {
return function(n) {
if(n <= standardTemp) {
alert("体温正常")
} else {
alert("体温偏高")
}
};
}
var checkTemp_A = checkTemp(37.3);
var checkTemp_B = checkTemp(37.1);
// 输入值测试
checkTemp_A(37.0); // 输出:体温正常
checkTemp_A(37.4); // 输出:体温偏高
checkTemp_B(37.2); // 输出:体温偏高
checkTemp_B(37.0); // 输出:体温正常
闭包用途1–模拟私有变量:
- 在Java、C++等语言中有私有属性的概念,但是在JavaScript中只能用闭包来模拟
- 模拟私有变量就是在函数内部定义函数去使用局部变量,然后在外部调用内部函数得到局部变量的值,此时定义的局部变量对于外部来说就是私有的
// 题目:请定义一个变量a,要求是能保证这个a 只能被进行指定操作,而不能进行其它操作
// 封装一个函数,这个函数的功能就是私有化变量
function fun {
// 定义一个局部变量
var a = 0;
return {
getA:function() {
return a;
},
add:function() {
return a++;
}
};
}
var obj = fun();
// 想在fun函数外面使用变量a,唯一的方法就是调用getA() 方法
console.log(obj.getA())
使用闭包的注意点:
- 不能滥用闭包,否则会造成网页的性能问题,严重时可能造成内存泄漏。所谓内存泄漏是指程序中已动态分配的内存由于某中原因未释放或无法释放。
// 包裹函数的圆括号的功能:将函数变为表达式
// 后面的圆括号的功能:运行函数
(function() {
// 任意语句
})();
// 错误示例
function() {
//语句
}();
// 正确示例
(function() {
// 语句
})();
IIFE 的作用1——为变量赋值:
- 为变量赋值:当给变量赋值需要一些较为复杂的计算时(如if语句),使用IIFE显得语法更紧凑
var age = 23;
var sex = "男";
// 定义变量,变量的值由上面的变量决定
var title = (function() {
if(age < 18) {
return ""小朋友;
} else {
if(sex == "男") {
return '先生';
} else {
return '女士';
}
}
})();
console.log(title);
IIFE 的作用2——将全局变量变为局部变量:
- IIFE 可以在一些场合(如for循环中)将全局变量变为局部变量,语法显得紧凑
var arr = [];
for(var i = 0; i < 5; i++) {
(function(i) {
arr.push(function() {
alert(i);
});
})(i);
}
arr[2](); // 弹出 2
nodeType常用属性值:
- 节点的nodeType属性可以显示这个节点具体的类型
nodeType值 | 节点类型 |
---|---|
1 | 元素节点,例如 p标签 和 div标签 |
3 | 文字节点 |
8 | 注释节点 |
9 | document 节点 |
10 | DTD节点 |
方法 | 功能 | 兼容性 |
---|---|---|
document.getElementById() | 通过 id 得到 元素 | IE6 |
document.getElementsByTagName() | 通过 标签名 得到 元素数组 | IE6 |
document.getElementsByClassName() | 通过 类名 得到 元素数组 | IE9 |
document.querySelector() | 通过 选择器 得到 元素 | IE8部分兼容,IE9完全兼容 |
document.querySelectorAll() | 通过 选择器 得到 元素数组 | IE8部分兼容,IE9完全兼容 |
getElementById():
- document.getElementById() 功能是 通过 id 得到元素节点
- 参数就是元素节点的 id,注意参数不要写 # 号,错误示例: (‘#box’)
- 如果页面上有相同 id 的元素,则只能得到第一个
- 不管元素藏的位置有多深,都能通过 id 把它找到
<div id = "box1">我是第一个</div>
<p id = "box2">我的第二个</p>
// JS 代码:
var box1 = document.getElementById('box1');
var box2 = document.getElementById('box2');
getElementsByTagName():
- document.getElementsByTagName() 方法的功能是 通过标签名得到节点数组
- 数组方便遍历,从而可以批量操控元素节点
- 即使页面上只有一个指定标签名的节点,也将得到长度为1的数组
- 任何一个节点元素也可以调用 getElementsByTagName() 方法,从而得到其内部的某种类的元素节点
<div id = "box">
<p>一段文字</p>
<p>一段文字</p>
</div>
<div id = "box1">
<p>一段文字</p>
<p>一段文字</p>
</div>
// JS 代码:
// var ps = document.ElementsByTagName('p'); //获得所有
var box = document.getElementById('box');
var ps_box = box.getElementsByTagName('p'); // 获得box盒子下的p
getElementsByClassName():
- document.getElementsByClassName() 方法的功能是 通过类名得到节点数组
- 注意不要写“.”号,错误示例:(‘.box’)
- getElemensByClassName() 方法从IE9开始兼容
- 某个节点元素也可以调用 getElementsByClassName() 方法,从而得到其内部的某类名的元素节点
<div class = "box">我是一个盒子</div>
<div>我是一个盒子</div>
<div class = "box1">我是一个盒子</div>
<div class = "box">我是一个盒子</div>
// JS 代码:
var box = document.getElementsByClassName('box');
querySelector():
- querySelector() 方法的功能是 通过选择器得到元素
- 这里的是选择器,所以必须加CSS对应的符号:id选择器是“#”,类选择器是“.”(下面示例中两个选择器间的空格表示是使用:后代选择器,CSS相关的内容参考:https://blog.csdn.net/weixin_49809163/article/details/115440235)
- querySelector() 方法从IE8开始兼容,但从IE9开始支持CSS3的选择器,如:nth-child()、:[src^=’cat’]等CSS选择器形式都支持
<div id = "box">
<p>我是段落</p>
<p class="spec">我是段落</p>
<p>我是段落</p>
</div>
// JS 代码:
var the_p = document.querySelector('#box .spec');
querySelectorAll():
- querySelectorAll() 方法的功能是 通过选择器得到元素数组
- 即使页面上只有一个符合选择器的节点,也将得到长度为1的数组
<div id = "box">
<p>我是段落</p>
<p class="spec">我是段落</p>
<p>我是段落</p>
</div>
// JS 代码:
var ps = document.querySelectorAll('#box p');
关系 | 考虑所有节点 | 只考虑元素节点 |
---|---|---|
子节点 | childNodes | children |
父节点 | parentNodes | parentNodes |
第一个子节点 | firstChild | firstElementChild |
最后一个子节点 | lastChild | lastElementChild |
前一个兄弟节点 | previousSibling | previousElementSibling |
后一个兄弟节点 | nextSibling | nextElementSibling |
封装节点关系函数:
- 书写IE6也能兼容的“寻找所有元素子节点”函数
- 书写IE6也能兼容的“寻找前一个元素兄弟节点”函数
- 如何编写函数,获得某元素的所有的兄弟节点?
// 封装一个函数,这个函数可以返回元素的所有子元素节点(兼容到IE6),类似children的功能
function getChildren(node) {
// 结果数组
var children = [];
// 遍历node这个节点的所有子节点,判断每一个子节点的nodeType属性是不是1
// 如果是1(nodeType值为 1 表示的是元素节点),就推入结果数组
for(var i = 0; i < node.childNodes.length; i++) {
if(node.childNodes[i].nodeType == 1) {
children.push(node.childNodes[i]);
}
}
return children;
}
// 封装一个函数,这个函数可以返回元素的前一个兄弟节点(兼容到IE6),类似previousElementSibling的功能
function getElementPrevSibling(node) {
var o = node;
// 使用while语句
while(o.previousSibling != null) {
if(o.previousSibling.nodeType == 1) {
// 结束循环
return o.previousSibling;
}
// 让 o 成为它的前一个节点
o = o.previousSibling;
}
// 没有前一个兄弟节点时返回 null
return null;
}
// 封装第三个函数,这个函数可以返回元素的所有元素兄弟节点
function getAllElementSibling(node) {
// 前面的元素兄弟节点
var prevs = [];
// 后面的元素兄弟节点
var nexts = [];
var o = node;
// 遍历node前面的节点
while(o.previousSibling != null) {
if(o.previousSibling.nodeType == 1) {
prevs.unshift(o.previousSibling);
}
o = o.previousSibling;
}
o = node;
// 遍历node后面的节点
while(o.nextsSibling != null) {
if(o.nextsSibling.nodeType == 1) {
nexts.push(o.nextsSibling);
}
o = o.nextsSibling;
}
// 将两个数组进行合并,然后返回
return prevs.concat(nexts);
}
改变元素节点内容:
- 改变元素节点的内容可以使用两个相关属性:innerHTML 和 innerText
- innerHTML 属性能以 HTML语法 设置节点中的内容
- innerText 属性只能以 纯文本的形式 设置节点中的内容
改变元素节点的CSS样式:
- 标准W3C属性,如src、href等,只需要通过获得的元素节点直接打点进行更改即可
- CSS 属性要写成“驼峰”形式
- CSS属性值要设置成完整形式
- 书写CSS时要注意单位
- 改变元素节点的CSS样式需要使用下面示例语句:
var oBox = document.getElementsByClassName('box');
oBox.style.backgroungColor = 'blue';
oBox.style.backgroungImage = 'url(images/1.jpg)';
var oBox = document.getElementsByClassName('box');
oBox.setAttribute('data-n', 10);
var n = oBox.getAttribute('data-n');
节点的创建:
- document.createElement() 方法用于创建一个指定 tag name 的HTML元素。
// 示例:
var cDiv = document.createElement('div');
父节点.appendChild(孤儿节点);
- 小练习:创建九九乘法表
```c
// html 代码
<table id = "mytable"></table>
// CSS代码:
td {
width: 120px;
height: 20px;
border: 1px solid #000;
}
// JS 代码:
var mytable = document.getElementById('mytable');
for(var i = 1; i <= 9; i++) {
// 创建了新的tr标签
var ctr = document.createElement('tr');
for(var j = 1; j <= i; j++) {
// 创建新的td标签
var ctd = document.createElement('td');
// 设置td内部的文字
ctd.innerText = j + 'x' + i + '=' + (i * j);
// 让tr追加td标签
ctr.appendChild(ctd);
}
// 让mytable追加tr标签
mytable.appendChild(ctr);
}
移动节点:
- 如果将已经挂载到DOM树上的节点成为 appendChild() 或 insertBefore() 的参数,这个节点将会移动
新父节点.appendChild(已经有的父节点);
新父节点.insertBefore(已经有的父节点, 标杆子节点);
删除节点:
- removeChild() 方法从DOM中删除一个子节点
父节点.removeChild(要删除的子节点);
克隆节点:
- cloneNode() 方法可以克隆节点,克隆出的节点是孤儿节点
var 克隆得到的孤儿节点 = 老节点.cloneNode();
var 克隆得到的孤儿节点 = 老节点.cloneNode(true); // 连带后代
var 克隆得到的孤儿节点 = 老节点.cloneNode(false); // 只有自身
oBox.onclick = function() {
// 点击盒子时,执行这里的语句
};
常见的鼠标事件监听:
| 事件名 | 事件描述 |
|-------|-------|
| onclick | 当鼠标 点击 某个对象时触发事件 |
| ondblclick | 当鼠标 双击 某个对象时触发事件 |
| onmousedown | 当鼠标上某个按键在某个对象上 按下 时触发事件 |
| onmouseup | 当鼠标上某个按键在某个对象上 松开 时触发事件 |
| onmousemove | 当鼠标上某个按键在某个对象上 移动 时触发事件 |
| onmouseenter | 当鼠标 进入 某个对象时触发事件(相似事件 onmouseover 冒泡)|
| onmouseleave | 当鼠标 离开 某个对象时触发事件(相似事件 onmouseout 冒泡)|
| onmousewheel | 当鼠标 滚轮滚动 时触发事件(它的事件对象e提供deltaY属性表示滚轮滚动方向,向下为正值,向上为负值) |
常见的键盘事件监听:
| 事件名 | 事件描述 |
|-------|-------|
| onkeypress | 当某个键盘的按键被 按下(系统按钮如箭头键和F1-F12功能键无法得到识别)时触发事件 |
| onkeydown | 当某个键盘的键被 按下(系统按钮可以识别,并且会优先于 onkeypress 发生)时触发事件 |
| onkeyup | 当某个键盘的键被 松开 时触发事件 |
常见的表单事件监听:
| 事件名 | 事件描述 |
|-------|-------|
| onchange | 当 用户改变域的内容 时触发事件 |
| oninput | 当 用户在域内输入内容 时触发事件(回退不触发) |
| onfocus | 当 某元素获得焦点(比如tab键或鼠标点击)时触发事件 |
| onblur | 当 某元素失去焦点 时触发事件 |
| onsubmit | 当 表单被提交时 触发事件 |
| onreset | 当 表单被重置时 触发事件|
常见的页面事件监听:
| 事件名 | 事件描述 |
|-------|-------|
| onload | 当 页面或图片被完成加载 时触发事件 |
| onunload | 当 用户退出页面 时触发事件 |
addEventListener() 方法:
- DOM0 级事件监听:只能监听冒泡阶段
oBox.onclick = function() {
// 触发事件后的执行语句
};
oBox.addEventListener('click', function() {
// 触发事件后的执行语句
}, true);
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
// JS 代码:
var oBox1 = document.getElementById('box1');
var oBox2 = document.getElementById('box2');
var oBox3 = document.getElementById('box3');
oBox1.addEventListener('click', function() {
console.log('我是box1的捕获阶段');
}, true);
oBox2.addEventListener('click', function() {
console.log('我是box2的捕获阶段');
}, true);
oBox3.addEventListener('click', function() {
console.log('我是box3的捕获阶段');
}, true);
oBox1.addEventListener('click', function() {
console.log('我是box1的冒泡阶段');
}, false);
oBox2.addEventListener('click', function() {
console.log('我是box2的冒泡阶段');
}, false);
oBox3.addEventListener('click', function() {
console.log('我是box3的冒泡阶段');
}, false);
注意事项:
- 最内部元素不再区分捕获和冒泡阶段,会先执行写在前面的监听,然后执行写在后面的监听
- 如果给元素设置相同的两个或多个同名事件,则 DOM0 级写法 后写的会覆盖先写的;而 DOM2 级会按照顺序执行
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
// JS 代码:
var oBox1 = document.getElementById('box1');
var oBox2 = document.getElementById('box2');
var oBox3 = document.getElementById('box3');
oBox1.onclick = function() {
console.log('A');
};
oBox1.onclick = function() {
console.log('B');
};
oBox1.addEventListener('click', function() {
console.log('C');
}, false);
oBox1.addEventListener('click', function() {
console.log('D');
}, false);
// 输出结果
B C D
// 对象 e 就是这次事件的“事件对象”
oBox.onclick(e) {
// 事件被触发时执行的语句
}
在鼠标事件当中,描述鼠标位置的一些属性:
| 属性 | 属性描述 |
|-------|-------|
| offsetX | 鼠标指针相对于 事件源元素 的水平坐标 |
| offsetY | 鼠标指针相对于 事件源元素 的垂直坐标 |
| clientX | 鼠标指针相对于 浏览器 的水平坐标 |
| clientY | 鼠标指针相对于 浏览器 的垂直坐标 |
| pageX | 鼠标指针相对于 整张网页 的水平坐标 |
| pageY | 鼠标指针相对于 整张网页 的垂直坐标 |
<div id = "box"></div>
// JS 代码:
var oBox = document.getElementById('box');
oBox.onmousemove = function(e) {
console.log('offsetX/Y: ' + e.offsetX + ' , ' + e.offsetY);
console.log('clientX/Y: ' + e.clientX + ' , ' + e.clientY);
console.log('pageX/Y: ' + e.pageX + ' , ' + e.pageY);
};
按键的 e.charCode 和 e.keyCode 属性:
- e.charCode 属性通常用于 onkeypress 事件中,表示用户输入的字符的“字符码”
- e.keyCode 属性通常用于 onkeydown 事件和 onkeyup 事件中,表示用户按下的按键的“键码”
charCode 字符码:
| 字符 | 字符码 |
|-------|-------|
| 数字 0~9 | 48 ~ 57 |
| 大写字母 A~Z | 65 ~ 90 |
| 小写字母 a~z | 97 ~ 122 |
keyCode 键码:
| 按键 | 键码 |
|-------|-------|
| 数字 0~9 | 48 ~ 57(与charCode字符码完全相同) |
| 字母不分大小写 a~z | 65 ~ 90 (与charCode字符码的大写字母A~Z相同,而keyCode不分大小写,一律为65~90) |
| 四个方向键:左⬅️、上⬆️、右➡️、下⬇️ | 37、38、39、40 |
| 回车键 | 13 |
| 空格键 | 32 |
// html
<div id="box"></div>
// CSS
#box {
position: absolute;
top: 200px;
left: 200px;
width: 100px;
height: 100px;
background-color: blue;
}
// JS 代码:
var oBox = document.getElementById('box');
// 定义全局变量 t、l分别表示盒子的 top属性和left属性值
var t = 200;
var l = 200;
// 监听document对象的键盘按下事件监听,表示用户在整个网页上按下按键
document.onkeydown = function(e) {
// 条件是以后按下的方向键左37、上38、右39、下40
switch(e.keyCode) {
case 37:
l -= 10;
break;
case 38:
t -= 10;
break;
case 39:
l += 10;
break;
case 40:
t += 10;
break;
}
// 更改样式
oBox.style.top = t + 'px';
oBox.style.left = l + 'px';
};
e.preventDefault() 方法:
- e.preventDefault() 方法用来 阻止事件产生的“默认动作”
- 一些特殊的业务需求需要阻止事件的“默认动作”
小案例1: 制作一个文本框,只能让用户在其中输入小写字母和数字,其它字符输入没有效果
<div>
<p>只能输入小写字母和数字:</p>
<input type="text" id="field">
</div>
//JS 代码
var oField = document.getElementById('field');
oField.onkeypress = function(e) {
// 根据用户输入字符的字符码(e.charCode)决定是否显示
// 数字0-9字符码为:48-57
// 小写字母a-z字符码为:97-122
if(!(e.charCode >= 48 && e.charCode <= 57 || e.charCode >= 97 && e.cahrCode <= 122)) {
// 阻止浏览器的默认行为:输入一个字符显示一个字符的行为
e.preventDefault();
}
};
小案例2: 制作鼠标滚轮事件:当鼠标在盒子中向下滚动时,数字加1,反之,数字减1
- 鼠标滚轮事件是 onmousewheel,它的事件对象 e 提供 deltaY 属性表示鼠标滚动方向,向下滚动时返回正值,向上滚动时返回负值
<div id="box"></div>
<h1 id="info">0</h1>
//JS 代码
var oBox = document.getElementById('box');
var oInfo = document.getElementById('info');
// 定义全局变量
var a = 0;
// 给盒子添加鼠标滚轮滚动事件监听
oField.onmousewheel = function(e) {
// 阻止浏览器的默认行为:当用户在盒子里面滚动鼠标滚轮的时候,此时不会引起页面滚动条的滚动
e.preventDefault();
if(e.deltaY > 0) {
a++;
} else {
a--;
}
oInfo.innerText = a;
};
e.stopPropagation() 方法:
- e.stopPropagation() 方法用来 阻止事件继续传播
- 在一些场合,非常有必要切断事件继续传播,否则会造成页面特效显示出bug
小案例:制作一个弹出层:点击按钮显示弹出层,点击网页任意地方,弹出层关闭
<button id="btn">点我弹出弹出层</button>
<div class="model" id="model"></div>
// JS代码
var oBtn = document.getElementById('btn');
var oModel = document.getElementById('model');
// 点击按钮的时候,弹出弹出层
oBtn.onclick = function(e) {
// 阻止事件传播到document 身上
e.stopPropagation();
oModel.style.display = "block";
};
// 点击页面任何地方,关闭弹出层
document.onclick = function() {
oModel.style.display = 'none';
};
// 点击弹出层内部的时候,不能关闭弹出层,所以应该阻止事件继续传播
oModel.onclick = function(e) {
// 阻止事件继续传播到 document 身上
e.stopPropagation();
};
问题1(批量添加): 页面上有一个无序列表 < ul >,它内部有多个< li > 元素,请批量给它们添加点击事件监听,实现效果:点击哪个 < li > 元素,哪个 < li > 元素就变红
<ul id="list">
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
</ul>
// JS 代码
var oList = document.getElementById('list');
var lis = oList.getElementsByTagName('li');
// 书写循环语句,批量给元素添加监听
for(var i = 0; i < lis.length; i++) {
lis[i].onclick = function() {
this.style.color = 'red';
};
}
批量事件监听的性能问题:
- 每一个事件监听注册都会消耗一定的系统内存,而批量添加事件会导致监听数量太多,内存消耗非常大
- 实际上,每一个批量标签(如: li 标签)的事件处理函数都是不同的函数,存放这些函数本身也会占用内存
问题2(动态绑定): 页面上有一个无序列表 < ul >,它内部没有< li > 元素,请制作一个按钮,点击这个按钮就能增加一个< li >元素,并为每个增加的< li >元素添加点击事件,实现效果是点击哪个< li > 元素,哪个< li > 元素就变红
<button id="btn">点我添加新的li列表项</button>
<ul id="list"></ul>
// JS 代码
var oBtn = document.getElementById('btn');
var oList = document.getElementById('list');
// 按钮点击事件
oBtn.onclick = function() {
// 创建一个新的 li 列表项,孤儿节点
var oLi = document.createElement('li');
oLi.innerHTML = '我是列表项';
// 上树
oList.appendChild(oLi);
// 给新创建的这个 li 节点添加 onclick 事件监听
oLi.onclick = function() {
this.style.color = 'red';
};
};
动态绑定事件的性能问题:
- 新增元素必须分别添加事件监听,不能自动获得事件监听
- 大量事件监听、大量事件处理函数都会 产生大量内存消耗
事件委托:
- 事件委托:就是 利用事件冒泡机制,将后代元素事件委托给祖先元素
- e.target 和 e.currentTarget 属性: 事件委托通常需要结合使用 e.target 属性
- 当有大量类似元素需要批量添加事件监听时,使用时间委托可以减少内存开销
- 当有动态元素节点上树时,使用事件委托可以让新上树的元素具有事件监听
属性 | 属性描述 |
---|---|
target | 触发此事件的最早元素,用户真正触发的那个元素,即“事件源元素” |
currentTarget | 事件处理程序附加到的元素 |
使用事件委托时需要注意的事项:
- onmouseenter 和 onmouseover 都表示“鼠标进入”,它们的区别是:onmouseenter 不冒泡,onmouseover 冒泡
- 使用事件委托时要注意:不能委托不冒泡的事件给祖先元素
- 最内层元素不能再有额外的内层元素了(若最内层还存在内内层,当触发内内层时,内内层执行事件语句,而最内层不执行;而触发最内层时,最内层及最内层里面的内内层都执行事件语句)
定时器:
- setInterval() 函数可以 重复调用一个函数,在每次调用之间具有固定的时间间隔
- 第一个参数是函数
- 第二个参数是间隔时间,单位为:毫秒(ms),1秒 = 1000毫秒
setInterval(function() {
// 这个函数将自动被以固定间隔时间调用
},5000);
setInterval(function(a, b) {
// 函数行参中 a对应33,b对应55
},3000, 33, 55);
function fun() {
// 函数体语句
}
// 具名函数当做第一个参数,注意这里没有圆括号
setInterval(fun, 2000);
清除定时器:
- clearInterval() 函数可以清除一个定时器
// 设置定时器,并且用变量接收这个定时器
var timer = setInterval(function() {
// 函数体语句
}, 3000);
// 点击按钮时,清除定时器
oBox.onclick = function() {
// 清除定时器时,要传入定时器变量
clearInterval(timer);
};
<h1 id="info"></h1>
<button id="btn1">开始</button>
<button id="btn1">暂停</button>
// JS 代码:
var oInfo = document.getElementById('info');
var oBtn1 = document.getElementById('btn1');
var oBtn2 = document.getElementById('btn2');
// 定义全局变量
var timer;
var a = 0;
// 点击开始,计时器开始运行
oBox1.onclick = function() {
// 为了防止定时器叠加,应该在设置定时器之前先清除定时器
clearInterval(timer);
// 设置定时器
timer = setInterval(function() {
// 函数体语句
oInfo.innerText = ++a;
}, 1000);
};
// 点击按钮时,清除定时器
oBox2.onclick = function() {
// 清除定时器时,要传入定时器变量
clearInterval(timer);
};
延时器:
- setTimeout() 函数可以设置一个延时器,当指定时间到达之后,会执行函数一次,之后不再重复执行
setTimeout(function() {
// 这个函数会在延迟2秒后执行一次
}, 2000);
<button id="btn1">2秒后弹出你好</button>
<button id="btn1">取消弹出</button>
// JS 代码:
var oBtn1 = document.getElementById('btn1');
var oBtn2 = document.getElementById('btn2');
// 定义全局变量
var timer;
// 点击开始,计时器开始运行
oBox1.onclick = function() {
// 设置定时器
timer = setTimeout(function() {
// 函数体语句
alert('你好');
}, 2000);
};
// 点击按钮时,清除定时器
oBox2.onclick = function() {
// 清除定时器时,要传入定时器变量
clearTimeout(timer);
};
初步认识一步语句:
- setInterval() 和 setTimeout() 是两个异步语句
- 异步(asynchronous):不会阻塞CPU继续执行其它语句,当异步完成时,会执行“回调函数(callback)”
- 异步语句不会阻塞程序的正常执行
setTimeout(function() {
console.log('A');
}, 2000);
console.log('B');
// 输出结果
B A
使用定时器实现动画:
- 使用定时器可以实现动画,利用的是“视觉暂留”原理
<button id="btn">移动</button>
<div id="box"></div>
// CSS
#box {
width: 100px;
height: 100px;
background-color: orange;
position: absolute;
top: 100px;
left: 100px;
}
// JS 代码
var oBtn = document.getElementById('btn');
var oBox = document.getElementById('box');
// 标识量 0:表示在左边,1:表示在右边
var pos = 0;
// 事件监听
oBtn.onclick = function() {
// 加上过度
oBox.style.transition = 'all 2s linear 0s';
if(pos == 0) {
// 瞬间移动,但由于有过渡,所以是动画
oBox.style.left = '1100px';
pos = 1;
} else {
oBox.style.left = '100px';
pos = 0;
}
};
var lock = true;
function 需要节流的函数() {
// 如果锁是关闭状态,则不执行
if(!lock) return;
// 函数核心语句
// 关锁
lock = false;
// 指定毫秒数后将锁打开
setTimeout(function() {
lock = true;
}, 2000);
};
<button id="btn">移动</button>
<div id="box"></div>
// CSS
#box {
width: 100px;
height: 100px;
background-color: orange;
position: absolute;
top: 100px;
left: 100px;
}
// JS 代码
var oBtn = document.getElementById('btn');
var oBox = document.getElementById('box');
// 标识量 0:表示在左边,1:表示在右边
var pos = 0;
// 函数节流锁
var lock = true;
// 事件监听
oBtn.onclick = function() {
// 检查锁是否关闭
if(!lock) return;
// 加上过度
oBox.style.transition = 'all 2s linear 0s';
if(pos == 0) {
// 瞬间移动,但由于有过渡,所以是动画
oBox.style.left = '1100px';
pos = 1;
} else {
oBox.style.left = '100px';
pos = 0;
}
// 关锁
lock = false;
// 设置指定毫秒数打开,一般设置为动画过渡时间
setTimeout(function() {
lock = true;
}, 2000);
};
<div id="box" class="box">
<ul id="list">
<li><img src="/images/number/0.png" alt=""></li>
<li><img src="/images/number/1.png" alt=""></li>
<li><img src="/images/number/2.png" alt=""></li>
<li><img src="/images/number/3.png" alt=""></li>
<li><img src="/images/number/4.png" alt=""></li>
<li><img src="/images/number/5.png" alt=""></li>
</ul>
</div>
// CSS
* {
margin: 0;
padding: 0;
}
.box {
width: 1000px;
height: 130px;
border: 1px solid #000;
margin: 50px auto;
overflow: hidden;
}
.box ul {
list-style: none;
width: 5000px;
position: relative;
}
.box ul li {
float: left;
margin-right: 10px;
}
// JS 代码
var oBox = document.getElementById('box');
var oList = document.getElementById('list');
// 复制一遍所有的 li
oList.innerHTML += oList.innerHTML;
// 全局变量,表示当前 list 的 left 值
var left = 0;
var timer;
move();
function move() {
// 防止动画累积
clearInterval(timer);
// 定时器
timer = setInterval(function() {
left -= 6;
if(left <= -1260) {
left = 0;
}
oList.style.left = left + 'px';
},20);
};
// 添加鼠标进入ul元素,停止定时器
oBox.onmouseenter = function() {
clearInterval(timer);
};
// 鼠标离开继续执行定时器
oBox.onmouseleave = function() {
move();
};
<div class="carousel">
<ul id="list">
<li><img src="/images/0.png" alt=""></li>
<li><img src="/images/1.png" alt=""></li>
<li><img src="/images/2.png" alt=""></li>
<li><img src="/images/3.png" alt=""></li>
<li><img src="/images/4.png" alt=""></li>
</ul>
</div>
<a href="javascript:;" class="leftbtn" id="leftbtn"></a>
<a href="javascript:;" class="rightbtn" id="rightbtn"></a>
// CSS
* {
margin: 0;
padding: 0;
}
.carousel {
width: 650px;
height: 360px;
border: 1px solid #000;
margin: 50px auto;
position: relative;
overflow: hidden;
}
.carousel ul {
list-style: none;
width: 6000px;
position: relative;
left: 0px;
transition: left .5s ease 0s;
}
.carousel ul li {
float: left;
}
.carousel .leftbtn {
position: absolute;
left: 20px;
top: 50%;
margin-top: -25px;
width: 50px;
height: 50px;
background-color: rgb(28,180,226);
border-radius: 50%;
}
.carousel .rightbtn {
position: absolute;
right: 20px;
top: 50%;
margin-top: -25px;
width: 50px;
height: 50px;
background-color: rgb(28,180,226);
border-radius: 50%;
}
// JS 代码
// 得到按钮和ul,ul整体进行运动
var rightbtn = document.getElementById('rightbtn');
var leftbtn = document.getElementById('leftbtn');
var list = document.getElementById('list');
// 克隆第一张图片
var cloneli = list.firstElementChild.cloneNode(true);
// 将克隆的图片追加到最后
list.appendChild(cloneli);
// 定义全局变量,当前ul显示到第几张了,从0开始数
var idx = 0;
// 函数节流锁
var lock = true;
// 右边按钮监听
rightbtn.onclick = function() {
// 判断锁的状态
if(!lock) return;
// 关锁
lock = false;
// 因为最后一张图片会把过渡去掉,所以需要在这给list加过渡
list.style.transition = 'left .5s ease 0s';
idx ++;
if(idx > 4) {
// 设置一个延时器,延时器的功能是将ul瞬间拉回0的位置
setTimeout(function() {
// 取消过渡,因为要瞬间移动到第一张图片
list.style.transition = 'none';
list.style.left = 0;
idx = 0;
},500);
}
list.style.left = -idx * 650 + 'px';
//函数节流
setTimeout(function() {
lock = true;
},500);
};
// 左边按钮监听
leftbtn.onclick = function() {
// 判断锁的状态
if(!lock) return;
// 关锁
lock = false;
// 判断是不是第0张,如果是,就要瞬间用假的替换真的
if(idx == 0) {
// 取消过渡
list.style.transition = 'none';
// 直接瞬间移动到最后的假图片上
list.style.left = -5 * 650 + 'px';
// 设置一个延时器,这个延时器的延时时间可以是0毫秒
// 虽然是0毫秒,但是可以让我们过渡先是瞬间取消过渡,然后再加上
setTimeout(function() {
// 加过渡
list.style.transition = 'left .5s ease 0s';
// idx改为真正的最后一张图片的编号
idx = 4;
list.style.left = -idx * 650 + 'px';
},0);
} else {
idx --;
list.style.left = -idx * 650 + 'px';
}
// 函数节流
setTimeout(function() {
lock = true;
},500);
};
<div class="carousel">
<ul id="list">
<li><img src="/images/0.png" alt=""></li>
<li><img src="/images/1.png" alt=""></li>
<li><img src="/images/2.png" alt=""></li>
<li><img src="/images/3.png" alt=""></li>
<li><img src="/images/4.png" alt=""></li>
</ul>
</div>
<a href="javascript:;" class="leftbtn" id="leftbtn"></a>
<a href="javascript:;" class="rightbtn" id="rightbtn"></a>
// CSS
* {
margin: 0;
padding: 0;
}
.carousel {
width: 650px;
height: 360px;
border: 1px solid #000;
margin: 50px auto;
position: relative;
}
.carousel ul {
list-style: none;
}
.carousel ul li {
position: absolute;
top: 0;
left: 0;
/* 后面图片透明度为0 */
opacity: 0;
transition: opacity 1s ease 0s;
}
.carousel ul li:first-child {
/* 第一张图片透明度为1 */
opacity: 1;
}
.carousel .leftbtn {
position: absolute;
left: 20px;
top: 50%;
margin-top: -25px;
width: 50px;
height: 50px;
background-color: rgb(28,180,226);
border-radius: 50%;
}
.carousel .rightbtn {
position: absolute;
right: 20px;
top: 50%;
margin-top: -25px;
width: 50px;
height: 50px;
background-color: rgb(28,180,226);
border-radius: 50%;
}
// JS 代码
// 得到按钮和ul,ul整体进行运动
var rightbtn = document.getElementById('rightbtn');
var leftbtn = document.getElementById('leftbtn');
var list = document.getElementById('list');
var lis = document.getElementsByTagName('li');
// 当前显示的是第几张图
var idx = 0;
// 节流
var lock = true;
// 右边按钮监听
rightbtn.onclick = function() {
// 判断锁的状态
if(!lock) return;
// 关锁
lock = false;
// 还没有改 idx,此时的idx这个图片就是老图,老图淡出
lis[idx].style.opacity = 0;
idx ++;
if(idx > 4) idx = 0;
// 改了 idx,此时的idx这个图片就是新图,新图淡入
lis[idx].style.opacity = 1;
//函数节流
setTimeout(function() {
lock = true;
},1000);
};
// 左边按钮监听
leftbtn.onclick = function() {
// 判断锁的状态
if(!lock) return;
// 关锁
lock = false;
// 还没有改 idx,此时的idx这个图片就是老图,老图淡出
lis[idx].style.opacity = 0;
idx --;
if(idx < 0) idx = 4;
// 改了 idx,此时的idx这个图片就是新图,新图淡入
lis[idx].style.opacity = 1;
//函数节流
setTimeout(function() {
lock = true;
},1000);
};
var a = 10;
console.log(window.a == a); // 输出 true
console.log(window.alert == alert); // true
console.log(window.setInterval == setInterval); // true
窗口尺寸相关的参数:
- 获得不包含滚动条的窗口宽度,要用 document.documentElement.clientWidth
- 获得不包含滚动条的窗口高度,要用 document.documentElement.clientHeight
属性 | 意义 |
---|---|
innerHeight | 浏览器窗口的内容区域的高度,包含水平滚动条(如果有的话) |
innerWidth | 浏览器窗口的内容区域的宽度,包含垂直滚动条(如果有的话) |
outerHeight | 浏览器窗口的外部高度 |
outerWidth | 浏览器窗口的外部宽度 |
resize 事件:
- 在窗口大小改变之后,就会触发 resize 事件,可以使用 window.onresize 或者 window.addEventListener(‘resize’) 来绑定事件处理函数
已卷动高度:
- window.scrollY 属性表示在垂直方向已滚动的像素值
- document.documentElement.scrollTop 属性也表示窗口卷动高度
// 为了更好的支持浏览器兼容性,使用下面这种方式
var scrollTop = window.scrollY || document.documentElement.scrollTop;
scroll 事件:
- 在窗口被卷动之后,就会触发 scroll 事件,可以利用 window.onscroll 或者 window.addEventListener(‘scroll’) 来绑定事件处理函数
属性 | 意义 |
---|---|
appName | 浏览器官方名称 |
appVersion | 浏览器版本 |
userAgent | 浏览器的用户代理(含有内核信息和封装壳信息) |
platform | 用户操作系统 |
history.back(); // 等同于点击浏览器的回退按钮
history.go(-1); // 等同于 history.back();
// 这是第一个页面
<h1>我是temp网页</h1>
<a href="test.html">点我进入第二个页面</a>
// 我是第二个页面
<h1>我是test.html网页</h1>
<button id="btn">回退</button>
// JS代码
var btn = document.getElementById('btn');
btn.onclick = function() {
// history.back();
history.go(-1);
}
window.location = 'http://www.baidu.com';
window.location.href = 'http://www.baidu.com';
重新加载当前页面:
- 可以调用 location 的 reload 方法以 重新加载当前页面,参数 true 表示强制从服务器加载
window.location.reload(true);
GET 请求的查询参数:
- window.location.search 属性即为当前浏览器的 GET 请求的查询参数
比如网址:https://www.baidu.com/?a=1&b=2
console.log(window.location.search); //得到 ?a=1&b=2
body {
height: 5000px;
}
.backtotop {
width: 60px;
height: 60px;
background-color: rgba(233,233,233,.6);
position: fixed;
bottom: 100px;
right: 100px;
/* 小手状 */
cursor: pointer;
}
<div class="backtotop" id="backtotopbtn">返回顶部</div>
var backtotopbtn = document.getElementById('backtotopbtn');
// 设置全局变量
var timer;
backtotopbtn.onclick = function() {
// 设表先关
clearInterval(timer);
// 设置定时器
timer = setInterval(function() {
// 不断让 scrollTop减少
document.documentElement.scrollTop -= 200;
// 停止定时器
if(document.documentElement.scrollTop <= 0) {
clearInterval(timer);
}
},20);
};
* {
margin: 0;
padding: 0;
}
#content-part {
width: 1000px;
margin:0px auto;
margin-bottom: 30px;
background-color: #ccc;
font-size: 50px;
}
.floornav {
position: fixed;
right: 40px;
top: 50%;
margin-top: -100px;
width: 120px;
height: 200px;
background-color: orange;
}
.floornav ul {
list-style: none;
}
.floornav ul li {
width: 120px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 26px;
/* 小手指针 */
cursor: pointer;
}
.floornav ul li.current {
background: purple;
color: white;
}
// html
<div class="floornav">
<ul id="list">
<li data-n="科技" class="current">科技</li>
<li data-n="体育">体育</li>
<li data-n="新闻">新闻</li>
<li data-n="娱乐">娱乐</li>
<li data-n="视频">视频</li>
</ul>
</div>
<section class="content-part" style="height:674px;" data-n="科技">科技栏目</section>
<section class="content-part" style="height:654px;" data-n="体育">体育栏目</section>
<section class="content-part" style="height:622px;" data-n="新闻">新闻栏目</section>
<section class="content-part" style="height:432px;" data-n="娱乐">娱乐栏目</section>
<section class="content-part" style="height:526px;" data-n="视频">视频栏目</section>
// JS 代码
var list = document.getElementById('list');
var contentParts = document.querySelectorAll('.content-part');
var lis = document.querySelectAll('#list li');
// 使用事件委托给 li添加监听
list.onclick = function(e) {
if(e.target.targetName.toLowerCase() == 'li') {
// getAttribute表示得到标签上的某个属性值
var n = e.target.getAttribute('data-n');
// 可以使用属性选择器(就是方括号选择器)来寻找带有相同data-n的content-part
var contentPart = document.querySelector('.content-part[data-n=' + n + ']');
// 让页面的卷动自动成为这个盒子的offsetTop值
docuemnt.documentElement.scrollTop = contentPart.offsetTop;
}
};
// 在页面加载好后,将所有的content-part盒子的offsetTop值推入数组
var offsetTopArr = [];
// 遍历所有的 contentPart,将他们的净位置推入数组
for(var i = 0; i < contentParts.length; i++) {
offsetTopArr.push(contentParts[i].offsetTop);
}
// 为了最后一项可以方便比较,我们可以推入一个无穷大
offsetTopArr.push(Infinity);
// 当前所在楼层
var nowfloor = -1;
// 窗口的卷动
window.onscroll = function() {
var scrollTop = document.documentElement.scrollTop;
// 遍历offsetTopArr数组,看看当前的scrollTop 值在哪两个楼层之间
for(var i = 0; i < offsetTopArr.length; i++) {
if(scrollTop > offsetTopArr[i] && scrollTop < offsetTopArr[i + 1]) {
break;
}
}
// 退出循环的时候,i是几就表示当前楼层是几
// 如果当前所在楼层不是i,表示换楼了
if(nowfloor != i) {
// 让全局变量改变为这个楼层号
nowfloor = i;
// 设置下标为i的项有cur
for(var j= 0; j < lis.length; j++) {
if(j == i) {
lis[j].className = 'current';
} else {
lis[j].className = '';
}
}
}
};
var zhangsan = {
name: '张三',
age: 12,
sex: '男',
hobbies: ['玩游戏','编程','踢足球']
};
var zhangsan = {
name: '张三',
// 属性名中有短横,不符合JS标识符命名规范,属性名必须加引号包裹
'favorite-book': '三体'
};
属性的访问:
- 可以用“点语法”访问对象中指定的值,例:zhangsan.name;
- 如果属性名不符合JS标识符命名规范,则必须用方括号的写法来访问,例:zhangsan[‘favorite-book’];
- 如果属性名以变量形式存储,则必须使用方括号形式访问
var obj = {
a: 1,
b: 3
};
// 属性名用变量存储
var key = 'b';
console.log(obj.key); // undefined
console.log(obj[key]); // 3
属性的更改:
- 直接使用赋值运算符重新对某属性赋值即可更改属性
var obj = {
a: 10
};
obj.a = 30;
console.log(obj.a); // 30
属性的新增(创建):
- 如果对象本身没有某个属性值,则用点语法赋值时,这个属性会被创建出来
var obj = {
a: 10
};
obj.b = 30;
属性的删除:
- 如果要删除某个对象的属性,需要使用 delete 操作符
var obj = {
a: 10,
b: 20
};
delete obj.b;
var xiaoming = {
name: '小明',
age: 18,
sex: '男',
sayHello: function() {
console.log('小明的sayHello方法!')
}
};
// k:循环变量,它会依次成为对象的每一个键
// obj:要遍历的对象
for(var k in obj) {
console.log('属性' + k + '的值是:' + obj[k]);
}
举例 | 当 var a = b 变量传值时 | 当用 == 比较时 | |
---|---|---|---|
基本类型 | 数字、字符串、布尔型、undefined、null | 内存中产生新的副本 | 比较值是否相等 |
引用类型 | 对象、数组等(还包括函数、正则表达式) | 内存中不产生新的副本,而是让新的变量指向同一个对象 | 比较内存地址是否相同,即比较是否是同一个对象 |
对象是引用类型值,这意味着:
- 不能用 var obj1 = obj2; 这样的语法克隆一个对象
- 使用 == 或 === 进行对象的比较时,比较的是它们是否为内存中的同一个对象,而不是比较值是否相同
对象浅克隆:
- 只克隆对象的“表层”,如果对象的某些属性又是引用类型值,则不进一步克隆它们,只是传递它们的引用
var obj1 ={
a: 1,
b: 2,
c: [11,22,33]
};
var obj2 = {};
// 本质上 obj1 和 obj2 的 c属性还是同一块内存中的数组,并没有实现克隆
for(var k in obj1) {
// 每遍历一个 k 属性,就给 obj2 页添加一个同名的 k属性
// 且每个 k属性对应的值也和 obj1的相同
obj2[k] = obj1[k];
}
console.log(obj1.c == obj2.c); // true
对象的深克隆:
- 克隆对象的“全貌”,不论对象的属性值是否是引用类型值,都能实现全部克隆
- 和数组的深克隆类似,对象的深克隆需要使用递归
- 数组的 typeof 也是 object,所以先检测数组
var obj1 ={
a: 1,
b: 2,
c: [11,22,33,{
m: 22,
n:78,
p:[65,23,21]
}]
};
function deepClone(obj) {
var result;
// 要判断 obj 是对象还是数组
// 数组的 typeof 也是 object,所以先检测数组
if(Array.isArray(obj)) {
// 数组
for(var i = 0; i < obj.length; i++) {
result.push(deepClone(obj[i]));
}
} else if(typeof obj == 'object') {
// 对象
for(var k in obj) {
result[k] = deepClone(obj[k]);
}
} else {
// 基本类型值
result = obj;
}
return result;
};
var obj2 = deepClone(obj1);
obj1.c.push(99);
console.log(obj1);
console.log(obj2);
var obj = {
a: 1,
b: 2,
fun: function() {
console.log(this.a + this.b);
}
};
obj.fun(); // 输出结果是 3
var fn = obj.fun; // 将函数提出来单独存为变量
fn(); // 输出结果是 NaN
对象.方法();
例题1:
- 这题的 this 指代的是 obj 这个对象
function fun() {
console.log(this.a + this.b);
};
var obj = {
a: 3,
b: 5,
fn: fun
};
obj.fn(); // 输出 8
例题2:
- 这题的 this 指代的是 obj2 这个对象
var obj1 = {
a: 1,
b: 3,
fn: function() {
console.log(this.a + this.b);
}
};
var obj2 = {
a: 2,
b: 5,
fun: obj1.fn();
};
obj2.fun(); // 输出 7
例题3:
- 这题的 this 指代的是 outer 这个对象
function outer() {
var a = 2;
var b = 3;
return {
a: 11,
b: 22,
fun: function() {
console.log(this.a + this.b);
}
};
};
outer.fun(); // 输出 33
例题4:
- 这题的 this 指代的是 c[0] 这个对象
function fun() {
console.log(this.a + this.b);
};
var obj = {
a: 1,
b: 2,
c: [{
a: 3,
b: 4,
c: fun
}]
};
var a = 5;
obj.c[0].c(); // 输出 7
函数()
例题1:
var obj = {
a: 1,
b: 2,
fun: function() {
console.log(this.a + this.b);
}
};
var a = 5;
var b = 6;
var fn = obj.fun;
fun(); // 输出 11
例题2:
function fun() {
return this.a + this.b;
}
var a = 5;
var b = 6;
var obj = {
a: 3,
b: fun(), // 此处调用时的 this 是 window
fun: fun // 此处调用时的 this 是 obj
};
var result = obj.fun(); // 此处调用时的 this 是 obj
console.log(result); // 输出 14
数组[下标]()
例题1:
var arr = ['A','B','C',function(){
console.log(this[1]);
}];
arr[3](); // 输出 B
例题2:
function fun() {
arguments[3]();
}
// 调用函数
fun('A','B','C',function(){
console.log(this[1]);
}); // 输出 B
(function() {
})();
题目1:
var a = 1;
var obj = {
a: 2,
fun: (function {
var a = this.a; // tnis指代window,变量 a 的值为全局变量赋值的 1
return function() {
console.log(a + this.a); // 此处的this指代 obj
}
})()
};
obj.fun(); // 输出 3
setInterval(函数, 时间);
setTimeout(函数, 时间);
题目1:
var obj = {
a: 2,
b: 3,
fun: function {
console.log(this.a + this.b);
}
};
var a = 5;
var b = 6;
setTimeout(obj.fun, 2000); // 输出 11
题目2:
var obj = {
a: 2,
b: 3,
fun: function {
console.log(this.a + this.b);
}
};
var a = 5;
var b = 6;
setTimeout(function() {
obj.fun(); // 此处适用规则1
}, 2000); // 输出 5
DOM元素.onclick = function() {
};
案例1: 请实现效果:点击哪个盒子,哪个盒子就变红,要求使用同一个事件处理函数实现(不能使用事件委托)
<div id="box1"></div>
<div id="box2"></div>
<div id="box3"></div>
// JS
var box1 = document,getElementById('box1');
var box2 = document,getElementById('box2');
var box3 = document,getElementById('box3');
function setColorToRed() {
this.style.backgroundColor = 'red';
}
box1.onclick = setColorToRed;
box2.onclick = setColorToRed;
box3.onclick = setColorToRed;
案例2: 请实现效果:点击哪个盒子,哪个盒子在2000毫秒后变红,要求使用同一个事件处理函数实现(不能使用事件委托)
<div id="box1"></div>
<div id="box2"></div>
<div id="box3"></div>
// JS
var box1 = document,getElementById('box1');
var box2 = document,getElementById('box2');
var box3 = document,getElementById('box3');
function setColorToRed() {
// 备份上下文
var that = this;
setTimeout(function() {
that.style.backgroundColor = 'red';
}, 2000);
}
box1.onclick = setColorToRed;
box2.onclick = setColorToRed;
box3.onclick = setColorToRed;
二者的区别(体现在有参数时):
- call 要用逗号罗列参数
- apply 要把参数写到数组中
// 没有参数
函数.call(指定的上下文);
或者:
函数.apply(指定的上下文);
// 有参数
函数.call(指定的上下文, 参数1, 参数2 ...);
或者:
函数.apply(指定的上下文, [参数1, 参数2 ...]);
示例:
function sum() {
console.log(this.a + this.b + this.c);
};
var obj = {
a: 100,
b: 70,
c: 90,
// sum: sum
};
// 方法1: 可以在obj 中使用 sum: sum 指定上下文
// obj.sum();
// 方法2:可以使用 call 或 apply 的方式指定上下文(将 sum: sum 注释掉)
sum.call(obj);
// 或者 sum.apply(obj);
规则 | 上下文 |
---|---|
对象.函数() | 对象 |
函数() | window |
数组[下标] () | 数组 |
IIFE | window |
定时器 | window |
DOM事件处理函数 | 绑定DOM的元素 |
call 和 apply | 任意指定 |
用 new 调用函数 | 创建出对象 |
new 函数()
JS 规定,使用 new 操作符调用函数会进行“四步走”:
- 函数体内会自动创建一个空白对象
- 函数的上下文(this)会指向这个对象
- 函数体内的语句会执行
- 函数会自动返回上下文对象,即使函数没有 return 语句
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
var xiaoming = new People('小明',12,'男');
var xiaoqiang = new People('小强',12,'男');
var xiaolan = new People('小兰',12,'女');
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
People('小明',12,'男');
People('小强',12,'男');
People('小兰',12,'女');
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.sayHello = function() {
console.log('我是 '+ this.name + ',我今年 '+ this.age + '岁了');
};
}
var xiaoming = new People('小明',12,'男');
var xiaoqiang = new People('小强',14,'男');
var xiaolan = new People('小兰',18,'女');
xiaoming.sayHello();
xiaoqiang.sayHello();
xiaolan.sayHello();
什么是 prototype:
- 任何函数都有 prototype 属性,prototype 是“原型”的意思
- prototype 属性值是个对象,它默认拥有 constructor 属性,该属性指回函数
- 对普通函数来说,prototype 属性没有任何作用,而 构造函数的 prototype属性非常有用
- 构造函数的 prototype 属性是它的实例的原型
- 构造函数的实例可以调用 “双下划线proto双下划线”属性来查看实例的原型
function sum(a,b) {
return a + b;
}
console.log(sum.prototype); //sum的原型是函数,{constructor: function}
console.log(typeof sum.prototype); // object
console.log(sum.prototype.constructor === sum); //true
原型链查找:
- JavaScript规定:实例可以打点访问它的原型的属性和方法,这称为“原型链查找”
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 在构造函数的 prototype 上添加 nationality 属性
People.prototype.nationality = '中国';
var xiaoming = new People('小明',12,'男');
// 实例可以打点访问原型的属性和方法
console.log(xiaoming.nationality); // 输出:中国
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 在构造函数的 prototype 上添加 nationality 属性
People.prototype.nationality = '中国';
var xiaoming = new People('小明',12,'男');
var tom = new People('汤姆',13,'男');
// 实例可以打点访问原型的属性和方法
console.log(xiaoming.nationality); // 输出:中国
// 这里输出中国的原因是tom实例没有nationality属性,只能到原型上查找
console.log(tom.nationality); // 输出:中国
// tom 实例添加nationality属性
tom.nationality = '美国';
console.log(tom.nationality); // 输出:美国
hasOwnProperty方法:
- hasOwnProperty 方法可以 检查对象是否真正“自己拥有”某个属性或者方法(区别于原型上的)
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 在构造函数的 prototype 上添加 nationality 属性
People.prototype.nationality = '中国';
var xiaoming = new People('小明',12,'男');
console.log(xiaoming.hasOwnProperty('name')); // true
console.log(xiaoming.hasOwnProperty('age')); // true
console.log(xiaoming.hasOwnProperty('sex')); // true
console.log(xiaoming.hasOwnProperty('nationality')); // false
in 运算符:
- in 运算符只能 检查某个属性或方法是否可以被对象访问,不能检查是否是自己的属性或方法
// 接上例
console.log('name' in xiaoming); // true
console.log('age' in xiaoming); // true
console.log('sex' in xiaoming); // true
console.log('nationality' in xiaoming); // true
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 在构造函数的 prototype 属性上添加 方法
People.prototype.sayHello = function() {
console.log('我是'+ this.name + ',今年' + this.age + '岁了,我是一个' + this.sex + '生');
}
People.prototype.growup = function() {
this.age ++;
}
var xiaoming = new People('小明',12,'男');
var xiaohong = new People('小红',10,'女');
console.log(xiaoming.sayHello === xiaohong.sayHello); // true,表明两个实例调用的是同一个方法
xiaoming.sayHello(); // 我是小明,今年12岁了,我是一个男生
xiaohong.sayHello(); // 我是小红,今年10岁了,我是一个女生
xiaoming.growup();
xiaoming.sayHello(); // 我是小明,今年13岁了,我是一个男生
xiaohong.sayHello(); // 我是小红,今年10岁了,我是一个女生
JavaScript中如何实现继承:
- 实现继承的关键在于:子类必须拥有父类全部的属性和方法,同时子类还应该能定义自己特有的属性和方法
- 使用JavaScript特有的原型链来实现继承,是普遍的做法
- 在ES6中,还将介绍新的实现继承的方法
- 子类可以重写父类的同名方法
// 父类
function People(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
};
People.prototype.sayHello = function() {
console.log('你好,我是'+ this.name + ',我今年'+ this.age + '岁了');
};
People.prototype.sleep = function() {
console.log(this.name + '正在睡觉,zzzzz');
};
// 子类
function Student(name,age,sex,school,studentNumber) {
this.name = name;
this.age = age;
this.sex = sex;
this.school = school;
this.studentNumber = studentNumber;
};
// 实现继承的关键语句:子类的prototype属性指向父类的实例
// 这句关键语句必须写在子类实例原型之前
Student.prototype = new People();
// 子类的方法
Student.prototype.study = function() {
console.log(this.name + '正在学习');
};
Student.prototype.exam = function() {
console.log(this.name + '在考试');
};
// 实例化
var xiaoming = new Student('小明',10,'男','xxx小学',12306);
xiaoming.study();
xiaoming.sayHello();
xiaoming.sleep(); // 小明正在睡觉,zzzzz
// 重写(override)父类的sleep方法
Student.prototype.sleep = function() {
console.log(this.name + '正在睡觉,zzzzz,请不要打扰!');
};
xiaoming.sleep(); // 小明正在睡觉,zzzzz,请不要打扰!
var a = new Number(123);
var b = new String('Hello');
var c = new Boolean(true);
console.log(a); // 123
console.log(typeof a); // object
console.log(b); // Hello
console.log(typeof b); // object
console.log(c); // true
console.log(typeof c); // object
console.log(5 + a); // 128
var d = 2;
console.log(d.__proto__ == Number.prototype); // true
console.log(Math.max(6,2,3,5)); // 6
console.log(Math.min(6,2,3,5)); // 2
var arr = [3,6,9,2];
var max = Math.max.apply(null,arr);
console.log(max); // 9
parseInt(Math.random()*(b - a + 1)) + a
日期对象的常见方法:
| 方法 | 功能 |
|------|-------|
| getDate() | 得到日期1~31 |
| getDay() | 得到星期0~6 |
| getMonth() | 得到月份0~11 |
| getFullYear() | 得到年份 |
| getHours() | 得到小时数0~23 |
| getMinutes() | 得到分钟数0~59 |
| getSeconds() | 得到秒数0~59 |
时间戳:
- 时间戳表示1970年1月1日零点整距离某时刻的毫秒数
- 通过 getTime() 方法或者 Date.parse() 函数可以将时间对象变为时间戳
- 通过 new Date(时间戳) 的写法,可以将时间戳变为日期对象
- 1970年1月1日之间的时间戳表示为负的
// 日期对象
var date = new Date();
// 显示时间戳。时间戳表示1970年1月1日距离此时的毫秒数
var timestamp1 = date.getTime(); // 精确到毫秒
var timestamp2 = Date.parse(date); //精确到毫秒,也是毫秒数,只不过最后三位一定是000
// 把时间戳变为时间对象,比如:1601536565000
var dd = new Date(1601536565000);
小案例:在页面上实时显示距离2021年高考还有多少天、多少时、多少分、多少秒
<h1>2021年高考倒计时</h1>
<h1 id="info"></h1>
// JS 代码
var info = document.getElementById('info');
setInterval(function() {
// 当前时间
var nowDate = new Date();
// 目标时间,5表示6月
var targetDate = new Date(2021,5,7);
// 毫秒差
var diff = nowDate - targetDate;
// 将毫秒差换算为:天、时、分、秒
// 换算为天:毫秒差除以一天的总毫秒数,就能换算为天
var day = parseIn(diff / (1000 * 60 * 60 * 24));
// 换算为时:毫秒差与一天的毫秒数的余数再除以一小时的毫秒数
var hours = parseIn(diff % (1000 * 60 * 60 * 24) / (1000 * 60 * 60));
// 换算为分:
var minutes = parseIn(diff % (1000 * 60 * 60) / (1000 * 60));
// 换算为秒:
var seconds = parseIn(diff % (1000 * 60 * 60) / (1000 * 60) / 1000);
// 显示内容
info.innerText = day + '天' + hours + '时' + minutes + '分' + seconds + '秒';
},1000);