JavaScript
JavaScript基础
JavaScript书写位置
JavaScript需要包含在HTML的script标签中,也可以通过script标签的src属性引入一个单独的js文件。
JavaScript的结束符
JavaScript的结束符为英文分号,若JavaScript的每一行只有一个语句,那么这个分号可以省略,但最好还是添加分号,因为某些情况下会报错(比如自执行函数)。
注释
JavaScript的注释和其他语言类似,分为块注释和行注释。块注释使用//
,行注释用/* */
。
// 行注释
/* 块注释 */
输出
JavaScript的输出方式有以下几种:
- console.log,用于在控制台打印,不直接在页面中出现,需要在浏览器中打开开发者模式并转到控制台才能看到。
- document.write,直接在页面中打印,一般适合初学者输出表达式的值使用,不推荐用于开发环境中。
- alert,用于在浏览器中打开弹窗提示,用于提示用户信息。
JavaScript字面量
字面量是常量的一种,它的值就是字面意思的值。最常用的就是整数、浮点数、布尔值、字符串。
1 // 整数
1.2345 //浮点数
true // 布尔值
'Hello World' // 字符串
声明变量和赋值
JavaScript使用let声明局部变量,使用const声明常量。let已经不再使用,推荐使用前两种关键字。
let a = 1; // 声明变量,可更改
a = 2; // 正确
const b = 'constant'; // 声明常量,不能更改
b = 'others'; // 错误
运算符
JavaScript的运算符和其他语言很相似,可以进行加减乘除、字符串相加、使用括号扩大表达式优先级、比较大小与判断是否等于。
算术运算符
- 加法运算符(
+
):用来求两个数值的和,或者连接两个字符串。 - 减法运算符(
-
):用来计算两个数值之间的差值。 - 乘法运算符(
*
):用来计算两个数值的乘积。 - 除法运算符(
/
):用来计算两个数值相除的结果。 - 取余运算符(
%
):用来计算两个数值相除的余数。
比较运算符
- 等于(
==
):检查两个值是否相等,会进行类型转换。 - 全等于(
===
):检查两个值是否相等,不进行类型转换,类型也必须相同。 - 不等于(
!=
):检查两个值是否不相等,会进行类型转换。 - 不全等于(
!==
):检查两个值是否不相等,不进行类型转换,类型也必须不同。 - 大于(
>
):检查左侧值是否大于右侧值。 - 大于等于(
>=
):检查左侧值是否大于或等于右侧值。 - 小于(
<
):检查左侧值是否小于右侧值。 - 小于等于(
<=
):检查左侧值是否小于或等于右侧值。
逻辑运算符
- 逻辑与(
&&
):仅当两边的表达式都为真时,结果为真。 - 逻辑或(
||
):两边的表达式有一个为真时,结果为真。 - 逻辑非(
!
):将布尔值反转,true
变为false
,false
变为true
。
赋值运算符
- 基本赋值(
=
):将右侧的值赋给左侧的变量。 - 加法赋值(
+=
):将右侧值加上左侧变量的值,然后将结果赋给左侧的变量。 - 减法赋值(
-=
):将左侧变量的值减去右侧的值,然后将结果赋给左侧的变量。 - 乘法赋值(
*=
):将右侧值与左侧变量的值相乘,然后将结果赋给左侧的变量。 - 除法赋值(
/=
):将左侧变量的值除以右侧的值,然后将结果赋给左侧的变量。
其他运算符
- 条件(三元)运算符(
? :
):表达式条件 ? 表达式1 : 表达式2
,如果条件为真,返回表达式1的结果,否则返回表表达式2的结果。 - 递增(
++
):将数值增加1。 - 递减(
--
):将数值减少1。 - 展开运算符(
...
):用于数组或对象中,将数组元素或对象属性展开。 - 类型运算符(
typeof
):返回变量或表达式的类型。
这里特别说一下展开运算符。
展开运算符(...
)是ES6中引入的一项功能,可以在数组或对象字面量中使用,以表达式的形式将数组元素或对象属性"展开"。这个运算符非常灵活,可以用在多种场合,比如函数调用参数列表、数组字面量、对象字面量等。以下是一些常见的使用场景和示例:
数组中使用展开运算符
合并数组
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let mergedArr = [...arr1, ...arr2]; // 结果:[1, 2, 3, 4, 5, 6]
复制数组
let arr = [1, 2, 3];
let arrCopy = [...arr]; // 结果:[1, 2, 3]
将字符串转换为字符数组
let str = "hello";
let chars = [...str]; // 结果:['h', 'e', 'l', 'l', 'o']
在函数调用时展开数组元素
function sum(x, y, z) {
return x + y + z;
}
let numbers = [1, 2, 3];
console.log(sum(...numbers)); // 结果:6
对象中使用使用展开运算符
对象合并
let obj1 = { foo: 'bar', x: 42 };
let obj2 = { foo: 'baz', y: 13 };
let mergedObj = { ...obj1, ...obj2 };
// 结果:{ foo: "baz", x: 42, y: 13 }
// 注意:相同的属性名,后面的属性会覆盖前面的。
复制对象
let obj = { foo: 'bar', x: 42 };
let objCopy = { ...obj }; // 结果:{ foo: "bar", x: 42 }
添加或修改对象属性
let obj = { foo: 'bar', x: 42 };
let newObj = { ...obj, foo: 'baz', y: 13 }; // 结果:{ foo: "baz", x: 42, y: 13 }
展开运算符提供了一种简洁的方式来扩展和构建数组或对象,使得代码更加清晰和易于理解。在处理复杂的数据结构时,这个运算符显得尤为有用。
基本类型
JavaScript是弱类型语言。
JS数据类型共有九个:Null、Number、Boolean、String、Object、Reference、List、Completion、Undefined。
Null表示空值,用于定义空的或不存在的引用,但是Null不等同于0或空字符串。Undefined表示未定义变量。
let let1;
document.write('let1的类型:' + let1 + '<br\>'); //undefined
let let2=null;
document.write('let2的类型:' + let2 + '<br\>'); //null
布尔型和其他类型可以相互转换,非空或非0数字就是True。
字符串可以使用引号引起来定义,也可以使用newString('value')定义。
let let3='hello'+'world';
字符串可以进行相加运算,结果等于两个字符串首尾连接。
document.write('let3的值:' + let3 + ', let3的长度:' + let3.length + '<br\>');
使用字符串对象的length()方法可以获取字符串的长度。
使用以下四个方法可以转换字符串的大小写:s.toLocaleLowerCase()、s.toLowerCase()、toLocaleUpperCase()、toUpperCase()。
JS的数值类型Number,包含整数和小数,内部使用64位浮点数。
数组
- 创建数组 如我们像创建一个具有'张三' '李四' '王五'三个元素的数组
//第一种方式
let arr1 = new Array();
arr1[0] = '张三';
arr1[1] = '李四';
arr1[2] = '王五';
//第二种方式
let arr2 = new Array('张三', '李四', '王五');
//第三种方式
let arr3 = ['张三', '李四', '王五'];
- 访问数组
//通过下标访问数组
for (item in arr1) {
document.write(item + ',');
};
- 数组属性
length表示长度、prototype是所有JS对象的共有属性,通过原型可以给数组对象添加属性和方法,在创建方法后所有数组都可以使用此方法。
document.write('arr2的长度:' + arr2.length);
- 数组方法
concat():连接两个或更多的数组
every() 检验数组的每个元素是否都符合条件
filter() 检测数值元素,并返回符合符合条件而定元素组成的数组
find() 返回符合传入测试(方法)的数组元素
findIndex() 返回符合传入测试(方法)的数组元素索引
forEach() 数组每一个元素都执行一次回调方法
indexOf() 搜索元素,并返回它所在的位置
join() 将数组的所有元素放入一个字符串中
lastIndexOf() 返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后往前搜索
map() 通过指定方法处理数组的每个元素,返回处理后的数组
pop() 删除数组的最后一个元素并返回处理后的数组
push() 在数组后面添加新的一个或多个元素并返回新的长度
reduce() 将数组计算为一个值(从左到右)
reduceRight() 将数组计算为一个值(从右到左)
reverse() 反转数组的元素顺序
slice() 选取数组的一部分并返回新的数组
some() 检测数组元素中是否有元素符合指定条件
sort() 排序
splice() 从数组中添加或删除元素
toString() 把数组转化为字符串,并返回
valueOf() 返回数组对象的原始值
流程控制 - if
JavaScript使用if、else两个关键字进行流程控制,与其他语言类似。
let flag = true;
let flag2 = false;
if (flag) {
console.log(1);
} else if (flag) {
console.log(2);
} else {
console.log(3);
}
if内也能嵌套其他if,和其他语言类似。
JavaScript也支持三元运算符flag?a:b
,在计算单个表达式的值时最常用,使用if则会显得很冗余。
let flag = true;
console.log(flag ? 'TRUE' : 'FALSE');
流程控制 - switch
若条件选择过多,使用多个else if会让代码非常难看,这时就可以使用switch关键字达到多次判断的效果,使用方法和其他语言类似。
let el = 'el2';
switch el:
case 'el1':
console.log('el1');
case 'el2':
console.log('el2');
case 'el3':
console.log('el3');
流程控制 - for和while
JavaScript的for关键字和while关键字和其他语言类似,都是进行循环控制。也可以使用continue进行下一次循环,或使用break跳出此循环。
let arr = ['el1', 'el2', 'el3'];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
let i = 0;
while (i < arr.length) {
console.log(arr[i]);
i++;
}
let arr = ['el1', 'el2', 'break', 'el3'];
for (let i = 0; i < arr.length; i++) {
if (arr[i] == 'break'){
break;
} else {
console.log(arr[i]);
}
}
JavaScript支持快速遍历列表或对象,若遍历对象则相当于遍历Object.keys()。结构是for (let el {in|of} arr)
,其中for、in、of是关键字,el是每次遍历后得到的元素,arr是列表或对象,in和of可以二选一。
let arr = ['el1', 'el2', 'el3'];
for (let el of arr) {
console.log(el);
}
方法/函数
基础
JS定义方法的语法如下:
function func1(a, b) { //声明式方法定义
return a * b;
}
document.write('a * b = ' + func1(3, 4) + '<br\>');
let func2 = function (a, b) { //方法表达式定义
return a * b;
}
document.write('a * b = ' + func2(3, 4) + '<br\>');
let func3 = new Function('a', 'b', 'return a*b;');
document.write('a * b = ' + func3(3, 4) + '<br\>');
运行结果:
a * b = 12
a * b = 12
a * b = 12
方法的调用:
和其他编程语言类似,当方法在外部单独定义而没有用其他形式(如作为成员方法),直接调用即可。
function func4(a) {
return 'a = ' + a;
}
document.write(func4(3) + '<br\>'); //作为普通方法调用
let myObj = {
name: '张三',
age: 18,
getAge: function () {
return this.name + '是' + this.age + '岁';
}
};
document.write(myObj.getAge() + '<br\>'); //作为对象方法调用
运行结果:
a = 3
张三是18岁
若使用new关键字则是创建了一个对象:
function Person() {
this.name = '王五';
this.age = 20;
};
let person = new Person();
document.write(person.name + '的年龄是' + person.age + '<br\>'); //王五的年龄是20
在JS中,方法也是对象,他有自己的属性和方法:
//call() 和 apply() 是预定义的方法方法,可用于调用方法,两个方法的第一个参数必须是方法本身
function myFunc(a, b) {
return a + b;
}
result = myFunc.call(null, 3, 4);
document.write(result + '<br\>'); //7
result = myFunc.apply(null, [3, 4]); //7
document.write(result + '<br\>');
函数可以通过属性访问不定长参数。在函数内部作用域有一个伪数组arguments,通过访问它可以获取到函数被调用时传入的所有参数。
function func() {
console.log('函数的参数');
for (let i of arguments) {
console.log(arguments[i]);
}
}
func(1, 2, 3, 4, 5, 6)
函数参数可以带一个...,代表它接受不定长度的参数,它在函数内部是一个数组,它一般放在函数参数列表的最后。
function func(a, b, ...c) {
console.log('函数的剩余参数');
for (let i of c) {
console.log(c[i]);
}
}
func(1, 2, 3, 4, 5, 6)
箭头函数
箭头函数基本定义:(params) => { function_body }。
const func = () => {console.log(1)}
func();
若箭头函数只有一个形参,可以省略小括号。
const func = x => { console.log(x); }
func(1);
若箭头函数的函数体只有一个语句,可省略大括号。
const func = () => console.log(1);
func();
只有一个return语句,则直接简化为return的具体内容。
const func = x => x + x;
console.log(func(1));
普通函数的this指向的是调用者对象,箭头函数没有this语法,只会向上级传递。
内置方法
eval()将读取字符串并把它作为JS语句运行。
eval('document.write("我是eval()函数执行语句")' + '<br\>');
isFinite()用于检验参数是否为无穷大。若参数是正负无穷大或非数字则返回false。
document.write('isFinite(NaN):' + isFinite(NaN) + '<br\>');
isNaN()检验参数是否为非数字。
document.write('isNaN("a"):' + isNaN(123) + '<br\>');
parseInt(value,radix)将字符串读取为对应的整数。radix是基数,可选。
document.write('parseInt("123"):' + parseInt('123') + '<br\>');
parseFloat(value) 将字符串解析为浮点数。
document.write('parseFloat("123。456"):' + parseInt('123.456') + '<br\>');
escape(string) 将字符串转换为编码。
document.write('escape("Hello"):' + escape('Hello') + '<br\>');
unescape(string) 解码字符串。
特殊类型的函数
JavaScript除了使用function关键字定义的,具有名称,能调用的函数之外,还有其他类型的特殊类型的函数。
- 匿名函数
匿名函数就是不具有名称的函数,它一般作为作为回调函数使用。
let data = 1234;
function execute(data, func) {
func(data);
}
execute(data, function () {
console.log(data);
})
- 箭头函数
箭头函数和匿名函数功能相同,定义是(params) => {function_body}
- 自执行函数
若页面的JS文件过多,有些JS文件中会重复定义一些变量,这些变量直接会引起混淆,因此就引入了一个自执行函数的概念。由于函数作用域的缘故,自执行函数内变量的作用域仅限于该函数,因此它们直接不会互相干扰。
(function (a, b) {
console.log(a, b);
})(123, 'str');
面向对象
使用内置对象创建对象:
let str = new String('初始化String'); //创建字符串
let str1 = '初始化String';
let func = new Function(x, alert(x));
let o = new Object();
创建对象的第二种方法:直接定义:
let person = {
name: '张三',
age: 18
};
document.getElementsByClassName('person').innerHTML = '现在' + person.name + person.age + '岁了' + '<br\>';
高级对象创建方法:使用this关键字。
function person() { //构造方法
this.name = '李四';
this.age = 19;
}
let person2 = person(); //创建对象的方法
使用prototype构造:
let person3 = {};
person3.prototype.name = '王五';
person3.prototype.age = 20; //通过原型prototype更改对象的属性
this和prototype定义的不同是属性的占用空间不同,使用this关键字是在内存中开辟内存空间,使用原型是改变父级数据,因此原型比this节省空间。
创建对象的常用方法
- 工厂模式——不推荐
使用工厂模式需要注意以下几点:方法中定义对象,并定义对象的各种属性,虽然属性可以是方法,但是建议将属性为方法的属性定义到方法之外,这样可以避免重复创建该方法。
引用该对象时不要使用new创建对象,在方法的最后返回该对象,这样直接调用这个方法就可以接收到方式返回的对象。
function Parent1() {
let Child = new Object();
Child.name = '张三';
Child.age = 3;
return Child;
};
let obj1 = Parent1();
- 自定义方法构造方式——不推荐
此方法同工厂模式一样,若属性为方法,建议将属性为方法的属性定义到方法之外。
function Parent2(name, age) {
this.name = name;
this.age = age;
};
let obj2 = new Parent2(name = "张三", age = 18);
- 原型模式——不推荐
let age = function(){
return 18;
};
function Parent3(){
Parent3.prototype.name = '张三';
Parent3.prototype.age = age();
};
let obj3 = new Parent3();
- 原型模式和构造方法模式——推荐
这种模式是将不是方法的属性定义在构造方法内,把是方法的属性放在方法外面,用原型定义。
function Parent4(){
this.name = '张三';
this.age = 18;
};
Parent4.prototype.lev = function(){
return this.name;
}
let obj4 =new Parent4();
document.write(obj4.lev()) //输出张三
- 动态原型模式
function Parent(){
this.name = '张三';
this.age = 18;
if (typeof Parent._lev == undefined) {
Parent.prototype.lev = function(){ //在该模式中,属性为方法的属性在方法定义,但是需要保证创建该对象的实例时,属性的方法不会重复创建
return this.name;
}
Parent._lev = true;
}
}
有关原型的知识请向下查阅。
对象访问语句
首先先定义如下变量:
let obj = {
name: '张三',
age: 18,
sex: '男'
};
- for in语句 它用来遍历对象的每一个属性
for (letiable in obj) {
document.write('obj的' + letiable + '属性值为' + obj[letiable] + '<br\>');
}
- with语句
语法是 with (object) {statements} 其中obj代表对象名 使用with语句的作用是不需要重复指定对象名称,如下面的代码:
//不使用with
document.write('不使用with:obj的name属性=' + obj.name + ' obj的age属性' + obj.age);
//使用with
with (obj) {
document.write('使用with:obj的name属性=' + name + ' obj的age属性' + age);
}
对象序列化
对象序列化的意义是将对象的属性和值转换为字符串,序列化是对象转换为JSON,使用JSON.stringify(),反序列化是将JSON转换为对象,使用JSON.parse()。
对象序列化使用的方法是String.stringify(value[,replacer[,space]])。[]表示可选参数,
value是有效的JSON字符串。
replacer:用于转换结果的对象和数组。若replace为方法,则JSON.stringify()将调用此方法,并传入每个成员的键和值。使用返回值而不是原始值。若此方法返回undefined则排除成员。跟对象的键是一个空字符串:''。若replacer是一个数组,则仅转换数组中具有键值的成员。成员的转换顺序与每个键在数组的顺序一样。当value参数也为数组时,将忽略replacer数组。
space:文本添加缩进、空格和换行符,若replacer是一个数字,则返回值文本在每个级别缩进指定数目的空格,若space大于10,则文本缩进指定数目的空格,若space大于10,则文本缩进10个空格。space可以使用非数字,如\t。
返回值:返回包含JSON文本的字符串
let obj = {
name: '张三',
age: 18
};
//实例1:只有一个参数情况
document.write('只有一个参数情况' + '<br\>');
document.write(JSON.stringify(obj) + '<br\>');
//实例2,有多个参数
document.write('有多个参数的情况' + '<br\>');
document.write(JSON.stringify(obj, null, 4) + '<br\>'); //4相当于一个制表符(暂时无效果)
//对象反序列化:是通过JSON.parse()实现的
//JSON.parse(text, [receiver]) 其中text是一个有效的JSON字符串,receiver是结果转换方法,将为此对象的每个成员调用此方法
let text = '{"name":"李四","age":19}';
document.write('解析JSON:' + '<br\>');
let result = JSON.parse(text);
for (letiable in result) {
document.write('结果的' + letiable + '属性 = ' + result[letiable] + '<br\>');
}
运行结果:
只有一个参数情况
{"name":"张三","age":18}
有多个参数的情况
{ "name": "张三", "age": 18 }
解析JSON:
结果的name属性 = 李四
结果的age属性 = 19
创建对象的过程
这里的创建对象是指使用构造函数和new实例化新对象的过程。
function Obj(val=null) {
this.val = val
this.print = function () {
console.log(this.val)
}
}
let obj = new Obj('新对象');
obj.print()
- 实例化执行过程:创建新对象、构造函数this指向新对象、执行构造函数代码、修改this,指向新的属性、返回新对象。
对象的拷贝
对象的拷贝不能只用一个简单的赋值语句实现。
const obj1 = {key: "val1"};
const obj2 = obj1;
obj2.key = "val2"; //在这里obj1的key也会变 因为它们两个指向的内存空间是一致的
若对象里的属性类型是简单数据类型,则可以使用Object.assgin方法实现浅拷贝,但不能实现深层拷贝。
const obj1 = {key: "val1"};
const obj2 = {};
Object.assgin(obj2, obj1)
obj2.key = "val2"; //在这里obj1的key也会变 因为它们两个指向的内存空间是一致的
const obj1 = {obj: {key: "val"}};
const obj2 = {};
Object.assign(obj2, obj1);
obj2.obj.key = "val2"; //在这一步obj1.obj.key也会更改 因为只拷贝了浅层的属性,obj仍然指向同一个对象空间
深拷贝有三个方法:递归、lodash/cloneDeep、JSON.stringify。
递归实现深拷贝的实现方法是遍历对象的键,若键是普通值则直接将值赋值给新对象的属性即可,否则递归深拷贝。
let oldObj = {
number: 6,
boolean: true,
str: "你好",
obj: {
key1: "value1",
key2: "value2"
},
arr: [1, 2, 3, 4]
}
let newObj = copyDeep(oldObj);
function copyDeep(obj) {
// 检查obj是否为null,因为typeof null也会返回'object'
if (obj === null) return null;
// 处理数组
if (Array.isArray(obj)) {
return obj.map(item => copyDeep(item));
}
// 处理对象
if (typeof obj === 'object') {
let newObj = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) { // 确保key是obj自身的属性
newObj[key] = copyDeep(obj[key]);
}
}
return newObj;
}
// 处理基本类型和函数
return obj;
}
第二种方法使用了一个第三方库loadsh,这里不演示。
第三种方法最为简便,一行代码就能搞定。
let newObj = JSON.parse(JSON.stringify(oldObj))
其他知识
- 运算符
其他运算符和其他编程语言的运算符相类似,这里不再赘述,演示一下其他运算符。
void运算符:用于计算括号内的值,并返回undefined,语法是void()。
let let1 = void (1 + 1);
document.write('let1 = ' + let1 + '<br\>');
- typeof运算符:计算表达式的结果类型,值只能是number string boolean object function undefined中的一个。
let let2 = '123';
let let3 = null;
let let4 = 1.2;
document.write('let2:' + typeof let4 + ' let3:' + typeof let3 + 'let4:' + typeof let4 + '<br\>');
一些对象的常用方法
forEach方法
forEach语法用于循环调用数组的每个元素,相当于增强的for循环。它接受一个函数作为参数,这个函数需要有两个参数,第一个代表每次遍历时遍历到的数组元素,第二个代表元素的索引。
const arr = [1, 2, 3, 4];
arr.forEach((el, index) => {
console.log(`${el}的索引为${index}`)
})
Object.keys()方法和Object.values()方法
Object.keys(obj)方法可以获取到obj对象的所有自定义属性值,返回一个数组。
obj = {
key1: "value1",
key2: "value2",
key3: "value3"
};
console.log(Object.keys(obj)) //[ 'key1', 'key2', 'key3' ]
Object.values(obj)方法可以获取到对象的所有属性值。
obj = {
key1: "value1",
key2: "value2",
key3: "value3"
};
console.log(Object.values(obj)) //[ 'value1', 'value2', 'value3' ]
Object.assign(obj)
Object.assign(obj)返回obj对象的深层拷贝,与obj对象不一致。
obj = {
key1: "value1",
key2: "value2",
key3: "value3"
};
const obj2 = {};
Object.assign(obj2, Object.assign(obj))
console.log(obj2)
数组reduce方法
数组的reduce方法返回一个字面值,它接受一个函数类型的参数,作用是按照参数的方法对数组元素进行累次运算,返回运算的结果。参数函数接收两个参数,第一个参数为数组的上一个值,第二个参数为当前值。
let arr = [1, 2, 3, 4];
let result = arr.reduce(function (prev, curr) {
return prev + curr
})
console.log(result) //10
reduce还接受第二个参数,代表运算的初始值。
let arr = [1, 2, 3, 4];
let result = arr.reduce(function (prev, curr) {
return prev + curr
}, 10)
console.log(result) //20
若没有起始值,则上一次值为数组第一个元素的值,每一次循环,将返回值作为下一此循环的上一次值,若有起始值,则上一次值为起始值。
数组map方法
数组实例的map方法用于将数组的各个元素进行处理,返回处理后的数组。
let arr1 = [1, 2, 3, 4];
let arr2 = arr1.map(function (el) {
return el > 2 ? el + 3 : el;
})
console.log(arr2) // [1, 2, 6, 7]
map函数的第一个参数为一个函数,这个函数可以有三个参数。第一个参数为当前元素,这个必须包含,第二个参数为该元素的索引,可省略,第三个参数为数组本身,其他和这个方法有同样参数的方法,参数意义和此方法的参数意义基本相同,下面不再介绍。
数组filter方法
数组实例的filter方法用于过滤数组中不符合条件的数组,返回符合条件的数组。
let arr1 = [1, 2, 3, 4];
let arr2 = arr1.filter(function (el) {
return el > 2;
})
console.log(arr2) // [3, 4]
数组find方法
数组实例的find方法用于找到第一个符合条件的元素,若没有则返回undefined。
let arr1 = [1, 2, -3, 4];
let arr2 = arr1.find(function (el) {
return el < 0;
})
console.log(arr2) //-3
以上三个方法可以用于查找特定对象。
let arr = [
{
name: "张三",
age: 18
},
{
name: "李四",
age: 19
},
{
name: "王五",
age: 20
}
]
el = arr.find(function (el) {
return el.name === "王五";
})
console.log(el) //第三个对象
函数call方法
函数call的第一个参数为thisArg,用于指定函数执行时this的值,常用于改变函数中this的值。调用call方法后相当于直接调用函数,只是改变了函数中this的指向。它的返回值就是函数的返回值。
function func() {
console.log(`this的指向:${this}`);
console.log(`参数列表:${arguments}`);
}
func(1, true, "参数之一");
func.call({key: "val"}, 1, true, "参数之一");
函数apply方法
它和call方法的唯一差别就是它的第二个参数接收一个列表,表示参数列表,其他和call方法基本相似。
function func() {
console.log(`this的指向:${this}`);
console.log(`参数列表:${arguments}`);
}
func(1, true, "参数之一");
func.apply({key: "val"}, [1, true, "参数之一"]);
函数bind方法
bind方法不会调用函数,但是会改变函数的指向。它的调用方式和call相同,但返回值为改变了this指向的原函数拷贝,是一个新函数。
const btn = document.querySelector("button");
btn.addEventListener("click", function () {
this.disabled = true;
setTimeout(function () {
// 原来该函数的this指向window,我们要将它改为btn
this.disabled = false;
}.bind(this), 2000)
})
闭包
首先演示一下使用闭包做一个累加函数,这个函数的功能就是每调用一次就返回该函数被调用的次数。
function addFunc() {
let value = 0;
return function () {
return value++;
}
}
let add = addFunc();
document.write('调用addFunc()次数:' + add() + '<br\>');
document.write('调用addFunc()次数:' + add() + '<br\>');
document.write('调用addFunc()次数:' + add() + '<br\>');
运行结果:
调用addFunc()次数:0
调用addFunc()次数:1
调用addFunc()次数:2
在上例中,value是定义在函数内部的局部变量,在函数执行完后会被释放,但是该函数的内部还有一个函数,并且它将这个变量作为返回值使用,因此这个变量会保留在内存中。这样addFunc就是一个闭包。
接下来介绍闭包与类。JavaScript中有一个概念叫原型,可以将它理解为内部类和子类的结合体 JavaScript支持继承 就是通过原型实现的。若把一个对象赋值给另一个对象的原型,那么后者的父类就是前者。
function Father() {
this.value1 = '父类';
}
let father = new Father();
document.write('父对象的值:' + father.value1 + '<br\>');
function Son() {
this.value2 = '子类';
}
Son.prototype = new Father();
let son = new Son();
document.write('子对象的值:' + son.value2 + '<br\>');
document.write('子对象继承自父对象的值:' + son.value1 + '<br\>');
运行结果:
父对象的值:父类
子对象的值:子类
子对象继承自父对象的值:父类
这些原型逐层引用构成原型链,在读取一个变量的属性时,若此对象没有,就逐层向上层的原型中寻找。
特殊对象
Location对象
Location对象是window对象的属性 Location对象的属性如下
hash 返回一个URL的锚部分 host 返回一个URL的主机名和端口 hostname 返回URL的主机名
href 返回完整的URL pathname 返回URL的路径名 port 返回一个URL服务器使用的端口号
protocol 返回一个URL协议 search 返回一个URL的查询部分
Location对象的常用方法
assign() 载入一个新的文档
reload() 重新载入当前文档
replace() 用新的文档替换现有文档
Location对象使用最多的就是replace() 替换文档的的方法。
function f() { window.location.replace('https:- www.baidu.com'); }
Navigator对象
- Navigator对象包含有关浏览器的有关信息,属性如下
- appCodeName 返回浏览器的代码名
- appName 返回浏览器的名称
- appVersion 返回浏览器的平台和版本
- cookieEnabled 指明浏览器是否允许使用cookie
- platform 返回运行浏览器的操作系统平台
- userAgent 返回由客户机发送服务器的user-agent头部的值
- Navigator对象的方法如下
- javaEnabled() 指定是否在浏览器中启用Java
- traitEnabled() 规定浏览器是否启用数据污点
document.write('浏览器代号' + window.navigator.appCodeName + '<br\>');
document.write('浏览器名称' + window.navigator.appName + '<br\>');
document.write('浏览器版本' + window.navigator.appVersion + '<br\>');
Window对象
window对象表示浏览器中打开的窗体,若文档中包含框架(frame标签或iframe标签),浏览器会为HTML文档创建一个window对象,并为每个文档创建一个额外的window对象
- window对象属性
- closed 返回窗口是否已关闭
- defaultStatus 设置或返回窗口状态栏的默认文本
- document 对Document对象的只读引用
- frames 返回窗口中所有命名的框架 该集合是Window对象的数组,每个Window对象在窗口中都有一个框架
- history 对History对象的只读引用
- innerHeight 返回窗口的文档显示区的高度
- innerWeight 返回窗口的文档显示区的宽度
- length 设置或返回窗口的框架数量
- location 用于设置窗口或框架的Location对象
- name 用于返回窗口的名称
- navigator 对Navigator的只读引用
- opener 返回对创建此窗口的引用
- outerHeight 返回窗口的外部高度,包含工具条和滚动条
- outerWeight 返回窗口的外部宽度,包含工具条和滚动条
- pageXOffset 设置或返回当前页面相对于窗口显示区左上角的X位置
- pageYOffset 设置或返回当前页面相对于窗口显示区左上角的Y位置
- parent 返回父窗口
- screen 对Screen对象的只读引用
- screenLeft 返回相对于屏幕窗口的X坐标
- screenTop 返回相对于屏幕窗口的Y坐标
- screenX 返回相对于屏幕窗口的X坐标
- screenY 返回相对于屏幕窗口的Y坐标
- self 返回对当前窗口的引用,等价于window属性
- status 设置窗口状态栏的文本
- top 返回最顶层的父窗口
通过window的frameElement属性可以获取当前窗口所在的框架对象。
let documentObj = window.frameElement;
- parent属性返回当前窗口的父窗口 top获取各子窗口的最顶层对象
- window对象的其他方法
- alert() 显示有一段消息和一个确认按钮的警告框
- blur() 把键盘焦点从顶层窗口移开
- clearInterval() 取消由setInterval()设置的timeout
- clearTimeout() 取消由setTimeout()设置的timeout
- close() 关闭浏览器窗口
- confirm() 显示带有一段消息以及确认按钮和取消按钮的对话框
- createPopup() 创建一个Pop-up窗口
- focus() 把键盘焦点给予一个窗口
- moveBy() 可相对窗口的当前坐标把它移动指定的像素
- moveTo() 把窗口的左上角移动到某一个坐标
- open() 打开一个浏览器窗口或查找一个已经命名的窗口
- print() 打印当前窗口的内容
- prompt() 显示可提示用户输入的对话框
- resizeBy() 按照指定的像素调整窗口的大小
- resizeTo() 把窗口的大小调整到指定的宽度和高度
- scrollBy() 按照指定的像素值滚动内容
- scrollTo() 把内容滚动到指定的坐标
- setInterval() 按照指定的周期调用函数或计算表达式
- setTimeout() 在指定的毫秒数后调用函数或表达式
对话框
- alert() 生成一个带有指定信息的警告对话框
- confirm(text) 生成一个通知对话框,该对话框内有一段文字,并且有确认和取消按钮 当点击确认按钮时此按钮返回true
- prompt(text,defaultText) 生成一个具有提示信息和输入框的对话框 defaultText是在输入框内的默认文本 若点击取消 该方法返回null 否则返回输入框内的字符串
打开新窗口
- window.open(URL,name,specs,replace) 用于打开一个新的浏览器窗口或查找一个已命名的窗口
- URL:可选,打开指定的页面的URL。若没指定则打开空白窗口
- name:可选,指定target属性或窗口的名称,可选的值如下
- _blank 加载到一个新的窗口,默认值 _parent 加载到父框架 _self 替换当前页面 _top 替换任何可加载的框架集 name 窗口名称
- specs:可选,一个逗号分隔的项目列表,支持的值如下
- height=pixels 窗口的高度 width=pixels 窗口的宽度 left=pixels 该窗口的左侧位置 menubar=yes|no|1|0 是否显示菜单栏
- location=yes|no|1|0 是否显示该地址字段 resizable=yes|no|1|0 是否可调整大小
- scrollbars=yes|no|1|0 是否显示滚动条 status=yes|no|1|0 是否添加一个状态栏
- titlebar=yes|no|1|0 是否显示标题栏 toolbar=yes|no|1|0 是否显示浏览器工具栏
- replace:装载到窗口的URL是在浏览历史中创造一个新条目,还是替换浏览历史中的当前条目
DOM
DOM (Document Object Model) 译为文档对象模型,是 HTML 和 XML 文档的编程接口。
HTML DOM 定义了访问和操作 HTML 文档的标准方法。
DOM 以树结构表达 HTML 文档。
通过可编程的文档模型,JS有足够的能力创建动态的HTML,可以改变文档中所有的HTML元素,CSS样式。
下面列举一些基本的DOM方法
- 直接引用节点 document.getElementById('id1'); - 此方法在文档中通过id查找元素,返回值只有一个
- document.getElementsByTagName('id1'); - 此方法通过标记名称寻找元素,返回数组
function start1(){
//获取所有的body元素列表(此处只有一个)
let documentElements = document.getElementsByTagName('body');
//body元素是这个列表的第一个元素
let body = documentElements.item(0);
//获取body的子元素中所有的p元素
let pElements = body.getElementsByTagName('p');
//获得第二个p元素
let p = pElements.item(1);
}
- 间接引用节点
- element.parentNode属性:引用父节点
- element.childNodes属性:返回所有子节点的数组
- element.nextSibling属性和element.nextPreviousSibling属性:对下一个兄弟节点和对上一个兄弟节点的引用
- 获取节点信息
- nodeName属性:获得节点名称
- nodeType属性:获得节点类型
- nodeValue属性:获得节点的值
- hasChildNodes():判断是否有子节点
- tagName属性:获得标记名称
- 处理节点信息
- 除了通过“元素节点.属性名称”的方式访问外,还可以通过setAttribute()和getAttribute()方法设置和获得节点属性
- elementNode.setAttribute(attributeName,value):设置元素节点的属性
- elementNode.getAttribute(attributeName):获取属性值
- 处理文本节点
- innerHTML属性:设置或返回节点开始或结束标签之间的HTML
- innerText属性:设置或返回节点开始和结束标签之间的文本,不包括HTML标签
- 改变文档层次结构
- document.createElement()方法:创建元素节点
- document.createTextNode()方法:创建文本节点
- appendChild(childElement)方法:添加子节点
- insertBefore(newNode,refNode):插入子节点,第一个参数代表插入的节点,第二个参数代表将节点插入它之前
- replaceChild(newNode,oldNode):取代子节点,oldNode必须是parentNode的子节点
- cloneNode(includeChildren):复制节点。includeChildren代表是否复制子节点
- removeChild(childNode):删除子节点
DOM元素节点
在DOM模型中有三个节点,它们分别是元素节点、文本节点和属性节点。
- 元素节点:即标记节点 如li ol等
function getNodeProperty1() {
let node = document.getElementById('myList');
alert('ELEMENT_NODE:' + node.nodeType); //1,ELEMENT_NODE
alert('元素标记名:' + node.nodeName); //元素标记名
alert('节点值:' + node.nodeValue); //null
}
- 文本节点 即文字部分
function getNodeProperty2() {
let node = document.getElementsByTagName('li')[0].firstChild;
alert('TEXT_NODE:' + node.nodeType); //3,TEXT_NODE
alert('节点名称:' + node.nodeName); //#text
alert('文本内容:' + node.nodeValue); //文本内容
}
- 属性节点 即标签的属性
function getNodeProperty3() {
let node = document.getElementsByTagName('li')[0].getAttributeNode('name');
//getAttributeNode(nodeName)获取属性节点
alert('ATTRIBUTE_NODE:' + node.nodeType); //2,ATTRIBUTE_NODE
alert('属性名:' + node.nodeName); //属性名
alert('属性值:' + node.nodeValue); //属性值
}
文档对象
文档的属性和方法有很多,下面给出一些具体实例。
- title存储的是文档的标题,可以通过重新赋值实现改变文档标题。
function changeTitle() {
document.title = '改变了文档标题';
}
- 获取文档信息
function getMsg() {
alert(
'当前文档的域名是:' + document.domain + '\n' +
'当前文档的状态:' + document.readyState + '\n' +
'当前文档有关的所有cookie:' + document.cookie + '\n' +
'当前文档的URL:' + document.URL
);
}
- 颜色属性
- Document对象提供了alinkColor、bgColor、fgColor等几个元素属性
- alinkColor可以设置活动链接的颜色
- bgColor可以设置背景颜色
- fgColor可以设置前景颜色
- linkColor可以设置未访问链接颜色
- vlinkColor可以设置已访问链接的颜色
- 输出数据
- 使用document.write()输出数据,使用document.writeln()输出数据并换行
- 使用document.open()和document.close()可以在打开的新窗口中输出数据,其中document.open()用于打开文档输出流,用于接收来自document.write()和document.writeln()方法的输出,并使用document.close()关闭输出流。
function createDoc() {
let w = window.open();
w.document.open();
w.document.write('在新窗口中输出数据');
w.document.close();
}
DOM对象属性
document对象提供了可以操作网页文本的属性和方法,前面的document.write()方法就是document对象提供的。
document对象的常用方法和属性如下:
属性:
- document.alinkColor 链接文字的颜色,对应
<body>
标签的alink属性 - document.vlinkColor 表示已访问的链接的颜色,对应于
<body>
标签的vlink属性 - document.linkColor 未被访问的链接文字的颜色,对应于
<body>
标签的link属性 - document.bgColor 文档的背景色,对应于
<body>
标签的bgcolor属性 - document.fgcolor 文档的文本颜色(不包含超链接的颜色),对应于
<body>
标记的text属性 - document.fileSize 当前文件的大小
- document.fileModifiedDate 文档最后修改的日期
- document.fileCreatedDate 文档的创建日期
- document.activeElement 返回当前的焦点元素
- document.adoptNode(node) 从另外一个文档返回adapted节点到当前文档
- document.anchors 返回对当前文档中所有anchor对象的引用
- document.applets 返回当前文档中所有applet对象的引用
- document.baseURI 返回文档的绝对基础URI
- document.body 返回文档的body元素、
- document.cookie 设置或返回当前文档的所有有关cookie
- document.doctype 返回与文档有关的文档声明
- document.documentElement 返回文档的根节点
- document.documentMode 返回用于通过浏览器渲染文档的模式
- document.documentURI 设置或返回文档的位置
- document.domain 返回当前文档的域名
- document.domConfig 返回normalizeDocument()被调用时使用的配置
- document.embeds 返回文档中所有嵌入的内容的集合
- document.forms 返回文档中所有form对象的引用
- document.images 返回对文档中所有image对象的引用
- document.implementation 返回处理该文档的DOMImplementation的对象
- document.inputEncoding 返回用于浏览器的编码形式
- document.lastModified 返回文档最后修改的日期和时间
- document.links 返回对文档中所有area和link的引用
- document.readyState 返回文档状态(载入中...)
- document.referer 返沪载入当前文档的URL
- document.scripts 返回界面中所有脚本的集合
- document.strictErrorChecking 设置或返回是否进行强制错误检查
- document.title 返回当前文档的标题
- document.URL 返回文档完整的URL
下面对JS的document一些属性做特别介绍
- links属性 用于返回当前文档的所有超链接数组,语法如下:
document.links[1].innerHTML;
document.write('文档的第一个超链接文本:' + document.links[1].innerHTML + '<br\>');
- lastModified属性 用于返回文档最后修改的日期和时间,语法如下:
document.lastModified;
document.write('文档最后被编辑的时间:' + document.lastModified + '<br\>');
- forms属性 该属性返回所有的表单对象 语法:
document.forms;
document.write('文档中表单的数量' + document.forms.length + '<br\>')
DOM方法
document.addEventListener() 向文档添加句柄
document.close() 关闭用document.open()打开的输出流,并显示选定的数据
document.open() 打开一个流,以收集来自任何document.write()或document.writeln()方法
document.createAttribute() 创建一个属性节点
document.createComment() 创建注释节点
document.createDocumentFragment() 创建空的DocumentFragment节点,并返回此对象
document.createTextNode() 创建文本节点
document.createElement() 创建元素节点
document.getElementsByClassName() 返回文档中所有指定类名的元素集合,作为NodeList对象
document.getElementById() 返回对拥有指定id的第一个对象的引用
document.getElementsByName() 返回带有指定名称的对象集合
document.getElementsByTagName() 返回带有指定标签名的对象集合
document.importNode() 把一个节点从另一个文档复制到该文档以应用
document.normalize() 删除空文本节点,并连接相邻节点
document.normalizeDocument() 删除空文本节点,并连接相邻节点的文档
document.querySelector() 返回文档中匹配指定的CSS选择器的第一元素
document.querySelectorAll() 返回文档中匹配的CSS选择器的所有元素节点列表
document.removeEventListener() 移除由document.addEventListener()方法添加的
document.renameNode() 重命名元素或属性节点
document.write() 向文档写HTML表达式或JS代码
document.writeln() 向文档写HTML表达式或JS代码并换行
下面详细介绍这些方法:
- createElement() 它可以动态添加一个HTML标记
function addInput() {
let input = document.createElement('input');
input.type = 'text';
input.name = 'txt';
input.value = '动态添加的文本框';
document.body.appendChild(input);
}
- getElementById() 使用此方法可以获取指定元素并修改其内容
function modify() {
document.getElementById('input').value = '修改后的文本';
}
- addEventListener() 向文档中添加句柄 如我们为页面点击添加一个弹出对话框的功能
document.addEventListener('click', function () {
alert('点击了文档')
});
使用DOM改变元素CSS样式
使用DOM可以改变CSS样式,语法是document.getElementById(id).style.property = newStyle
。
function changeCSS() {
document.getElementById('myButton').style.fontFamily = '楷体';
}
“三位一体”的页面: //网页的内容可以分为三层:结构层、表现层和行为层。
- 结构层:由HTML和XML等标记语言负责创建,元素(标签)对页面各个部分的含义做出描述,例如
<ul>
表示创建无序列表 - 表现层:由CSS创建,即如何显示这些内容,如显示蓝色、宋体字体显示文字。
- 行为层:负责内容应该如何对这些事件做出反应,由JS和DOM完成。
使用className属性可以修改节点的CSS样式。
function changeClassName() {
document.getElementsByClassName('myStyle1')[0].className = 'myStyle2';
}
通过className新增CSS样式,语法是node.className += newStyle;
。
function addCSS() {
document.getElementById('myButton2').className += ' myStyle3';
}
还有一种常常改变元素样式的方法,就是快速修改元素的class列表。JavaScript为DOM对象提供了一个ClassList属性,它是一个列表,可以通过对这个列表进行操作来改变元素的class属性的值,进而改变元素的属性。
const object = document.querySelector("#object");
object.classList.add("class1"); //添加一个类名
object.classList.add("class2", "class3"); //添加多个类名
object.classList.remove("class2"); //移除某个类名
object.classList.toggle("class1", flag); //若flag为真则添加/保留class1,否则删除
事件
事件可以用于处理表单验证、用户输入、用户行为、浏览器动作。
它和Web页面连接到一起,使用户可以和用户进行交互、以响应用户的操作,如浏览器载入文档或用户动作如敲击键盘、
滚动鼠标等触发,而事件处理程序则说明一个对象如何响应事件。
事件的调用方式:
在script标签中调用(在下方代码有演示)
在元素中调用
document.getElementById('myButton1').onclick = function () {
alert('通过script标签调用事件')
}
function event1() {
alert('通过元素本身调用事件');
}
为元素的onclick属性赋值以添加事件的方式已被弃用,可以使用addEventListener函数来添加事件。它的第一个参数是事件类型,比如click为点击事件,keyup为键盘松开的事件,第二个参数为一个函数,它是回调函数,在事件被触发后执行。
const el = document.querySelector("#object");
el.addEventListener("click", function() {
console.log(el + '的click事件被触发')
})
若使用onclick属性添加事件,然后想要移除事件,可以将onclick设为null。
若使用第二种方式添加实现,则需要调用removeEventListener函数移除事件,该方法接收一个参数,就是对应的回调函数本身。所以若有移除事件的需求,则不能像上方示例中将回调函数表达为匿名函数。
const el = document.querySelector("#object");
const func = function() {
console.log(el + '的click事件被触发')
}
el.addEventListener('click', func); //添加事件
el.removeEventListener('click', func); //移除事件
现将常用事件写在下面。
- 鼠标键盘事件:
- onkeydown:某个键盘的键被按下时触发此案件
- onkeypress:某个键盘的按键被按下或按住时触发此案件
- onkeyup:某个键盘的按键被松开时触发事件
- onclick:鼠标单击某个对象时触发此事件
- ondblclick:鼠标双击某个对象时触发此事件
- onmousedown:某个鼠标按键被按下时触发此事件
- onmousemove:鼠标被移动时触发此事件
- onmouseout:鼠标从某元素移开时触发此事件
- onmouseover:鼠标移动到某元素上方时触发此事件
- onmouseup:某个鼠标按键被松开时触发此事件
- onmouseleave:当鼠标指针移出元素时触发此事件
- onmouseenter:当鼠标指针移动到某元素上方时触发此事件
- oncontextmenu:在用户单击鼠标右键打开上下文菜单时触发此事件
- 页面相关事件
- onload:某个页面或图像被完成加载时触发此事件
- onabort:图像加载被中断时触发此事件
- onerror:当加载文档或图像发生错误时触发此事件
- onresize:当浏览器的窗口大小发生改变时触发此事件
- onbeforeunload:当前页面的内容将要被改变时触发此事件
- onunload:当前页面将被改变时触发此事件
- Onhashchange:该事件在当前URL的锚部分发生修改时被触发
- Onpageshow:该事件在用户访问该页面时被触发
- Onpagehide:该事件在用户离开当前网页跳转到另外一个网页时触发
- Onscroll:当文档被滚动时触发此事件
- 表单相关事件
- onreset:当重置按钮被单击时触发此事件
- onblur:当元素失去焦点时触发此事件
- onchange:当元素失去焦点并且元素的内容发生改变时触发此事件
- onsubmit:当提交按钮被单击时触发此事件
- onfocus:当元素获得焦点时触发此事件
- onfocusin:元素即将获得焦点时触发此事件
- onfocusout:元素即将失去焦点时触发此事件
- oninput:元素获取用户输入时触发此事件
- onsearch:用户向搜索域输入文本时触发此事件(<input='research'>)
- onselect:用户选取文本时触发此事件
- 拖动相关事件
- ondrag:元素正在被拖动时触发此事件
- ondragend:元素被拖动完成后触发此事件
- ondragenter:被拖动的元素到达放置目标后触发此事件
- ondragleave:被拖动的元素离开放置目标后触发此事件
- ondragover:被拖动的元素在放置目标上时触发此事件
- ondrop:该事件在拖动元素放置在目标区域时触发 5. 编辑相关事件
- onselect:当文本内容被选择时触发此事件
- onselectstart:当文本内容的选择即将发生后触发此事件
- oncopy:当页面的被选择内容被复制时触发此事件
- oncut:当页面的被选择内容被剪切时触发此事件
- onpaste:当页面被被粘贴时触发此事件
- onafterprint:该事件在页面已经开始打印、或打印窗口被关闭时触发
- onbeforeprint:该事件在页面即将被打印时触发
事件对象属性
下面列举了Event对象的属性:
- type:返回当前Event对象表示的事件名称
- altLeft:该属性设置或获取左Alt的状态,返回值为true时表示关闭,ctrlLeft和shiftLeft属性类似
- srcElement:该属性设置或获取触发事件的对象
- button:该属性设置或获取触发事件时按下的鼠标按键
- clientX:该属性获取鼠标在浏览器窗口中的X坐标,只读
- clientY:该属性获取鼠标在浏览器窗口中的Y坐标,只读
- offsetX:发生事件的地点在事件源元素的坐标系统中的X坐标
- offsetY:发生事件的地点在事件源元素的坐标系统中的Y坐标
- altKey:返回当事件被触发时Alt键是否被按下
- ctrlKey:返回当事件被触发时ctrl键是否被按下
- shiftKey:返回当事件被触发时shift键是否被按下
- cancelBubble:返回是否不接受上层元素的控制
- Bubble:指示事件是否是气泡事件
- currentTarget:返回其事件监听器触发该事件的元素
- eventPause:返回事件传播的当前阶段
- target:返回触发此事件的元素(事件的目标节点)
- timestamp:返回事件生成的日期和事件
- Location:返回按键在设备上的位置
- charCode:返回onkeypress事件触发键值的字母代码
- key:在按下按键时返回按键的标识符
- keyCode:返回onkeypress事件触发的键值的字母代码,或者是onkeydown或onkeyup的代码
- Which:同上
- metaKey:返回当事件被触发时“meta”键是否被按下
- relatedTarget:返回与该事件的目标节点相关的节点
事件对象Event对象的方法:主要用于创建新的事件对象、初始化新创建对象属性等,主要方法如下:
- createEvent():创建新的事件对象
- initEvent():初始化新创建的Event对象的属性
- preventEvent()通知浏览器不要执行与事件关联的默认动作
- stopPropagation():不再派发事件
- addEventListener():允许在目标事件中注册监听事件
- dispatchEvent():允许发送事件到监听器上
- removeEventListener():运行一次注册在事件目标上的监听事件
- handleEvent():把任意对象注册为事件处理程序
- initMouseEvent():初始化鼠标事件对象的值
- initKeyboardEvent():初始化键盘事件对象的值
文本相关事件
oncopy:在用户复制文本时被触发
onpaste:在用户粘贴文本时被触发
oncut:在用户剪切文本时被触发
onselect:在用户选择文本时被触发
代码:
<!DOCTYPE html>
<html lang="en"
xmlns:input="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>
文本编辑事件</title>
<script>
//1. oncopy:在用户复制文本时被触发
function Copy() {
alert('复制了文本');
}
//2. onpaste:在用户粘贴文本时被触发
function Paste() {
alert('粘贴了文本');
}
//3. oncut:在用户剪切文本时被触发
function Cut() {
alert('剪切了文本');
}
//4. onselect:在用户选择文本时被触发
function Select(){
alert('选择了文本');
}
</script>
</head>
<body>
<table>
<tr>
<td>
<p oncopy="Copy()">
复制此段文本时触发oncopy事件</p>
</td>
<td><input type="text"
onpaste="Paste()">
</td>
</tr>
<tr>
<td><input type="text"
oncut='Cut()'
value="剪切此段文本时触发此事件">
</td>
<td>
<p onselect="Select()">在选择此段文本时触发此事件</p>
</td>
</tr>
</table>
</body>
</html>
键盘相关事件
- onkeydown事件:键盘被按下时触发
- onkeypress事件:键盘被按下时被触发,此事件只有字符键被按下时触发,单独按下功能键、shift、alt、ctrl等按键时不触发
- onkeyup事件:键盘按键被松开时触发此事件
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>
键盘相关事件</title>
<script>
//1. onkeydown事件:键盘被按下时触发
function keyDown() {
alert('你按下了一个键盘按键');
}
//2. onkeypress事件:键盘被按下时被触发,此事件只有字符键被按下时触发,单独按下功能键、shift、alt、ctrl等按键时不触发
function keyPress() {
alert('你按下了一个键盘按键(在按下非字符键时无效)');
}
//3. onkeyup事件:键盘按键被松开时触发此事件
function keyUp() {
alert('键盘的某个按键被松开');
}
</script>
</head>
<body>
<input type="text"
title="在这里按下键盘按键时弹出对话框"
onkeydown="keyDown()">
<input type="text"
title="在这里按下键盘按键时弹出对话框(在按下非字符键时无效)"
onkeydown="keyPress()">
<input type="text"
title="键盘的某个按键被松开"
onkeydown="keyUp()">
</body>
</html>
鼠标相关事件
- 鼠标相关事件
单击事件是鼠标点击时触发的事件,用onclick实现,这里不再演示
- 鼠标按下与松开事件
鼠标按下事件为onmousedown事件,用户在按下鼠标时触发
- 鼠标移入移出事件
鼠标移入事件由onmouseover控制,鼠标移除事件由onmouseout控制
- 鼠标移动事件
鼠标移动事件onmousemove控制
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>
鼠标相关事件</title>
<script>
//本文件介绍有关事件的使用
//1. 鼠标相关事件
//单击事件是鼠标点击时触发的使劲按,用onclick实现,这里不再演示
//2. 鼠标按下与松开事件
//鼠标按下事件为onmousedown事件,用户在按下鼠标时触发
function clickEvent1() {
document.getElementById('button').style.backgroundColor = 'black';
document.getElementById('button').style.color = 'white';
}
function clickEvent2() {
document.getElementById('button').style.backgroundColor = 'white';
document.getElementById('button').style.color = 'black';
}
//3. 鼠标移入移出事件
//鼠标移入事件由onmouseover控制,鼠标移除事件由onmouseout控制
function moveEvent1() {
document.getElementById('div').style.backgroundColor = 'black';
document.getElementById('div').style.color = 'white';
document.getElementById('div').innerHTML = '现在鼠标在此div内';
}
function moveEvent2() {
document.getElementById('div').style.backgroundColor = 'white';
document.getElementById('div').style.color = 'black';
document.getElementById('div').innerHTML = '现在鼠标在此div外';
}
//4. 鼠标移动事件
// 鼠标移动事件onmousemove控制
</script>
</head>
<body>
<button id='button'
onmousedown="clickEvent1()"
onmouseup="clickEvent2()">
按下和松开按钮,此按钮会变换两次颜色
</button>
<br>
<br>
<div style="background-color: red; width: 200px; height: 200px"
id="div"
onmouseover="moveEvent1()"
onmouseout="moveEvent2()">
将鼠标移到此div之上然后再移出,此div会变换两次颜色
</div>
</body>
</html>
表单相关事件
- 获得焦点和失去焦点事件
onfocus事件在某个元素获取焦点时被触发
onblur事件在某个元素失去焦点时被触发
onchange事件在对象内容被改变且失去焦点时被触发,一般用在下拉列表中
onsubmit事件在表单提交时被触发,该事件可以验证表单输入项的正确性;onreset事件在表单被重置时触发,一般用于清空文本框
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>
表单相关事件</title>
<script>
//1. 获得焦点和失去焦点事件
// onfocus事件在某个元素获取焦点时被触发
// onblur事件在某个元素失去焦点时被触发
function onFocus() {
document.getElementById('text').value = '文本框获得焦点'
}
function onBlur() {
document.getElementById('text').value = '文本框失去焦点'
}
//2. onchange事件在对象内容被改变且失去焦点时被触发,一般用在下拉列表中
function onChange() {
let obj = document.getElementById('combobox')
document.getElementById('text').value = '下拉菜单的值为' + obj.options[obj.selectedIndex].innerHTML;
}
//3. onsubmit事件在表单提交时被触发,该事件可以验证表单输入项的正确性;onreset事件在表单被重置时触发,一般用于清空文本框
function submit() {
alert('提交了表单');
}
function reset() {
alert('重置了表单');
}
</script>
</head>
<body>
<form action="#">
<input type="text"
onfocus="onFocus()"
onblur="onBlur()"
id="text">
<select id="combobox"
onchange="onChange()">
<option value="1">
选项一
</option>
<option value="2">
选项二
</option>
<option value="3">
选项三
</option>
</select></form>
<hr>
<form action="#"
onsubmit="submit()"
onreset="reset()">
<table>
<tr>
<td>用户名</td>
<td><input
type="text"
name="用户名"
id="user_name">
</td>
</tr>
<tr>
<td>密码</td>
<td><input
type="password"
name="密码"
id="password">
</td>
</tr>
<tr>
<td>
<input type="submit"
value="提交">
</td>
<td><input
type="reset"
value="重置">
</td>
</tr>
</table>
</form>
</body>
</html>
页面相关事件
- onload:在页面加载完成后被触发
- onresize:在浏览器大小被改变后被触发
- onbeforeunload:在浏览器被关闭前被触发
拖动相关事件
- ondragstart事件在用户开始拖动元素时被触发 ondrag事件在元素正在被拖动时被触发 ondragend事件在元素被拖动完后被触发
- ondragenter事件在元素进入某范围内时被触发 ondragover事件在元素在某一范围内拖动时被触发 ondragleave事件在元素离开某一范围后被触发
多媒体相关事件
- onabort事件在视频或音频被终止加载时被触发
- oncanplay事件在用户可以开始播放音乐或视频时被触发、
- oncanplaythrough事件在音乐或视频可以正常播放且无须缓冲或停顿时被触发
- ondurationchange事件的时长发生变化时被出阿飞
- onemptied事件在期播放列表为空时被触发
- onended事件音频或视频结束播放时被触发
- onerror事件在视频或音频加载期间发生错误时被触发
- onloadeddata事件在浏览器加载音频或视频时被触发
- onloadedmetadata事件在浏览器在加载指定音频或视频的元数据时被触发
- onloadstart事件在浏览器开始寻找指定音频或视频时被触发
- onpause事件在音频或视频被暂停时被触发
- onplay事件在视频或音频开始播放时被触发
- onplaying事件在视频或音频暂停或者在缓冲后后准备重新开始播放时被触发
- onprogress事件在浏览器下载指定音频或视频时被触发
- onratechange事件在视频或视频的播放速度被改变时被触发
- onseeked事件在用户重新定位视频或音频的播放位置后被触发
- onseeking事件在用户重新开始定位视频或音频时被触发
- onstalled事件在浏览器获取媒体数据、但媒体数据不可用时被触发
- onsuspend事件在浏览器读取媒体数据终止时被触发
- ontimeupdate事件在当前的播放位置发生改变时被触发
- onvolumechange事件在媒体音量被改变时被触发
- onwaiting事件在视频由于要播放下一帧而需要缓冲时被触发
事件委托
举个例子,有一些li元素被包裹在ul元素内,需求是在点击哪个li时就让这个li元素的字体颜色变为红色。若li子元素很多,则我们不可能单独为每一个li添加事件监听,若使用foreach遍历这些元素,则达不到“点击哪个li时就让这个li元素的字体颜色变为红色”这个要求,因为这时候遍历得到的li元素都是等价的。所以我们可以采用以下方法解决这个问题:
- 为ul元素添加事件监听,在点击li时同样会触发ul的事件监听;
- 查找是哪个子元素触发了ul的事件监听,并改变对应元素的样式。
事件对象有一些属性,和target有关,它们就是获取哪个子元素触发了父元素的事件监听。
const el = document.querySelector("ul");
el.addEventListener('click', function (e) {
console.log(e.target.style.color = 'red')
})
e.target就代表触发父元素事件监听的子元素本身,因此可以直接对其操作。
事件循环
JavaScript的一大特点就是单线程,同一时间只能做一件事。单线程就意味着,前一个任务结束才能进行后一个任务,若前面的渲染时长太大,后面的渲染就要等待很久才能进行,造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
为了解决这个问题,H5提出了Web Worker标准,允许JavaScript创建多个线程,于是Javascript出现了同步和异步。
在JavaScript中,事件监听、setTimeout等定时器函数是实现异步的途径,它们是通过执行回调函数实现异步的。JavaScript是顺序执行的脚本语言,在遇到同步执行的语句时会将其加入执行栈并执行,直到它执行完毕再执行下一个语句。若遇到异步执行的语句则将其加入任务队列,并读取下一个语句。当其他同步语句执行完毕后,JavaScript回到任务队列寻找是否有可执行的任务,若有则执行此任务然后再同步执行,否则继续同步执行,以此往复循环。
这里有一个经典问题:下列语句的打印顺序是什么。
console.log(1);
setTimeout(function () {
console.log(4);
}, 3000);
console.log(3);
window.addEventListener("click", function () {
console.log(2)
});
答案是1324或1342,是哪个取决于三秒内有没有点击。
JavaScript主线程不断地执行任务和取任务,就称这个过程为事件循环。
本地存储
为了让页面刷新或销毁后也能保存数据以便于下次访问页面时使用,HTML5让数据能够存放在用户浏览器中。它设置、读取方便,刷新页面也不会丢失数据,容量很大。
本地存储分为两类,一个是localStorage,另一个为sessionStorage。前者会被永久保存到计算机中,直到用户主动删除。后者则在浏览器被关闭后销毁。这两者都是内置对象,可以直接调用。
本地存储以键值对的形式存在,且值只能存储字符串。因此若它存储的是数字或对象,则需要做一些处理。
以localStorage为例,生产环境中也常常使用此对象。
// 设置普通对象
localStorage.setItem('key', 'value1'); //设置值
let value = localStorage.getItem('key'); //获取值
console.log(value);
localStorage.removeItem('key'); //删除值
// 设置对象,需要将对象和字符串相互转换
let obj = {'item1': 'value1', 'item2': 'value2'};
localStorage.setItem('obj', JSON.stringify(obj)); //设置对象类型
obj = JSON.parse(localStorage.getItem('obj')) //将字符串类型转换为对象
垃圾回收机制
JavaScript垃圾回收机制和其他语言比较相似,这里列举一些可能不知道的。
全局变量一般不会回收,局部变量会在作用域外被回收。若内存没有及时回收出去而被其他程序利用的则为内存泄漏。
浏览器中,垃圾回收机制主要由引用计数法和标记清除法实现的。前者指若没有对象指向它的引用则将其回收,但它有个致命的问题:若循环引用,即两个对象互相引用,尽管他们不再使用,垃圾回收器也不会回收,因此会造成内存泄漏。
function func() {
let o1 = {};
let o2 = {};
o1.val = o2;
o2.val = o1;
return "引用计数无法回收"
}
func()
现代浏览器大多使用引用计数法了,都使用标记清除法,或基于标记清除法改进的算法。它寻找从根部无法到达的对象,并将其标记为无法使用,然后将其回收。
变量提升
使用var声明的变量若在之前被使用,则会正常输出结果,因为JS隐式地将声明的语句放到作用域最前面了,但是没有提升赋值。
console.log(num); //输出“undefined”
var num = 0;
函数也具有函数提升的特性。
func(); //输出111
function func() {
console.log("111");
}
需要注意变量提升只会提升声明,不会提升赋值,下面的函数运行会报错。所以函数赋值必须在先。
func(); //相当于let func; func为undefined,不可调用
let func = function () {
console.log(111);
}
数组解构和对象解构
声明变量时,可以使用中括号包裹多个变量,像使用数组那样,典型的可用于交换变量。
const arr = [1, 2, 3];
const [a, b, c] = arr;
let d = 4;
let e = 5;
[d, e] = [e, d];
若变量多于数组元素个数,多出的变量值为undefined。
const [a, b, c, d] = [1, 2, 3];
console.log(d); //undefined
可为元素附默认值,在变量多出数组元素个数的时候,元素的值将为默认值。
const [a=0, b=0, c=0, d=0] = [1, 2, 3];
console.log(d); //0
使用省略号可以给变量比数组元素个数多出的部分设为真数组。
const [a, b, ...c] = [1, 2, 3, 4];
console.log(c); //[3, 4]
不想赋值给某个变量的,直接省略即可。
const [a, b, , d] = [1, 2, 3, 4];
多维数组也支持解构。
const [a, b, [c, d]] = [1, 2, [3, 4]];
JavaScript支持对象解构,在解构时要让变量名等于对象的属性名。
const {name, age} = {name: '张三', age: 18}
console.log(name, age)
可以用冒号加别名的方法,将对象解构后赋值给不同名的变量。
const {name: username, age} = {name: "张三", age: 18}
console.log(username, age)
数组解构和对象解构可以混用,但要保证数组的顺序一致和对象的名称一致。
const [{name: name1, age: age1}, {name: name2, age: age2}] = [{name: "张三", age: 18}, {name: "李四", age: 19}]
console.log(name1, age1, name2, age2)
原型
基础
原型是所有对象共有的一个属性,它们都指向唯一的原型对象。类实例化为对象时,每个对象之间都是相互独立的,但它们的原型对象都是同一个,因此原型对象可用于定义公有方法,多个对象使用的方法在内存中都是同一个方法。
function Obj() {
this.func = () => {}
}
let obj1 = new Obj();
let obj2 = new Obj();
console.log(obj1.func === obj2.func) //false
function Obj2() {}
Obj2.prototype.func = () => {}
let obj3 = new Obj2();
let obj4 = new Obj2();
console.log(obj3.func === obj4.func) //true
构造函数和原型函数的this都指向实例化的对象。
通过原型,可以实现为对象/类拓展功能,如给数组扩展一个累加功能。
Array.prototype.sum = function () {
return this.reduce((prev, curr) => {
return prev + curr
})
}
arr = [1, 2, 3, 4];
console.log(arr.sum()) //10
constructor属性
每个原型对象都有个constructor属性,它指向该原型对象的构造函数。
对象原型__proto__
每个对象都有一个__proto__
指向构造函数的prototype原型对象。之所以对象可以使用构造函数Prototype原型对象的属性和方法,就是因为此属性存在。
__proto__
是JS非标准属性,它和[[prototype]]意义相同,用于指明当前实例对象指向哪个原型对象prototype。__proto__
属性里也有一个constructor属性,指向创建该实例的构造函数。
原型继承
继承是面向对象的一个基本特点,JavaScript的继承就是通过原型实现的。
若一个子类的prototype属性指向父类,则子类就继承自父类。
若我们使用function和this创建对象,则类的继承是通过将子类函数的prototype属性指向父类的实例实现的。
function Father() {
this.key1 = "父类"
}
function Son() {
this.key2 = "子类"
}
Son.prototype = new Father();
let obj1 = new Father();
console.log(obj1.key1)
let obj2 = new Son();
console.log(obj2.key1)
console.log(obj2.key2)
实现原理就是多个子类都同时使用了一个父类,若对父类的行为做更改,所有继承自该父类的子类也会被修改。
原型链和instanseof运算符
下面的图和例子描述了JavaScript中如何通过原型实现继承的。
function Fn () {}
为了方便区分__proto__
属性和prototype对象,我们习惯称前者为隐式原型,称后者为显式原型。而隐式原型还等同于[[prototype]]。
JavaScript规定任何对象都有自己的原型对象,函数也是对象,函数实际上是由构造函数Function实例化而来的。且任何对象都可以充当其他对象的原型对象,原型对象也有自己的原型对象,因此原型链就形成了。
在上方的例子中,定义Fn函数的过程就相当于const Fn = new Function()
。在这里,Fn是一个对象,Function是一个构造函数。__proto__
是对象具有的属性,在本例中,Fn、Function.prototype、Object.prototype都有这个属性。prototype是一个类型为对象的属性,在本例中,Function、Object两个构造函数拥有此对象属性。此外,Fn是Function构造函数的实例。
理清以上关系后,我们就可以梳理这些对象、构造函数中原型是如何串联起来的。Fn对象有一个__proto__
属性,它指向的是Function构造函数的prototype对象,即Fn.__proto__ === Function.prototype
。
console.log(Fn.__proto__ === Function.prototype) // true
Function.prototype又是一个对象,它也有__proto__
属性,它指向的是Object的prototype对象,所以Function继承自Object。
console.log(Function.prototype.__proto__ === Object.prototype) // true
由于Object为最顶层父类,它不继承自任何类,因此Object.prototype.__proto__
也就不指向任何原型对象了,为null。
console.log(Object.prototype.__proto__) // null
我们在继承时使用了如下的语法。
function MyClass() {}
MyClass.prototype = new Object();
console.log(MyClass.prototype.__proto__ === Object.prototype) // true
可以看到在指定原型对象实现继承时,原型链就已经形成了。而JavaScript在寻找对象的一个属性或方法时,若构造函数中没有,则沿着原型链向上寻找,继承的功能就实现了。
异常处理
JavaScript使用throw关键字抛出异常。
function add(a, b) {
if (typeof a === "undefined" || typeof b === "undefined") throw new Error("a和b参数必须都包含");
return a + b;
}
JavaScript使用try运行可能抛出异常的代码,使用catch捕捉异常后运行错误处理,使用finally进行最终的处理。和Java一致,若try语句内出现了异常,会停止执行下方的代码,直接跳转至catch或finally。catch和finally的执行顺序是先catch后finally,这两个必须同时包含一个。若try语句内没有异常,则不会执行catch内的代码,而会执行finally内的代码。
try {
add();
} catch (err) {
console.log(err.message)
} finally {
console.log("finally");
}