跳至主要內容

JavaScript

大约 58 分钟约 17518 字

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变为falsefalse变为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位浮点数。

数组

  1. 创建数组 如我们像创建一个具有'张三' '李四' '王五'三个元素的数组
//第一种方式
let arr1 = new Array();
arr1[0] = '张三';
arr1[1] = '李四';
arr1[2] = '王五';

//第二种方式
let arr2 = new Array('张三', '李四', '王五');

//第三种方式
let arr3 = ['张三', '李四', '王五'];
  1. 访问数组
//通过下标访问数组
for (item in arr1) {
    document.write(item + ',');
};
  1. 数组属性

length表示长度、prototype是所有JS对象的共有属性,通过原型可以给数组对象添加属性和方法,在创建方法后所有数组都可以使用此方法。

document.write('arr2的长度:' + arr2.length);
  1. 数组方法
  • 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关键字定义的,具有名称,能调用的函数之外,还有其他类型的特殊类型的函数。

  1. 匿名函数

匿名函数就是不具有名称的函数,它一般作为作为回调函数使用。

let data = 1234;

function execute(data, func) {
    func(data);
}

execute(data, function () {
    console.log(data);
})
  1. 箭头函数

箭头函数和匿名函数功能相同,定义是(params) => {function_body}

  1. 自执行函数

若页面的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节省空间。

创建对象的常用方法

  1. 工厂模式——不推荐

使用工厂模式需要注意以下几点:方法中定义对象,并定义对象的各种属性,虽然属性可以是方法,但是建议将属性为方法的属性定义到方法之外,这样可以避免重复创建该方法。

引用该对象时不要使用new创建对象,在方法的最后返回该对象,这样直接调用这个方法就可以接收到方式返回的对象。

function Parent1() {
    let Child = new Object();
    Child.name = '张三';
    Child.age = 3;
    return Child;
};
let obj1 = Parent1();
  1. 自定义方法构造方式——不推荐

此方法同工厂模式一样,若属性为方法,建议将属性为方法的属性定义到方法之外。

function Parent2(name, age) {
    this.name = name;
    this.age = age;
};
let obj2 = new Parent2(name = "张三", age = 18);
  1. 原型模式——不推荐
let age = function(){
    return 18;
};
function Parent3(){
    Parent3.prototype.name = '张三';
    Parent3.prototype.age = age();
};
let obj3 = new Parent3();
  1. 原型模式和构造方法模式——推荐

这种模式是将不是方法的属性定义在构造方法内,把是方法的属性放在方法外面,用原型定义。

function Parent4(){
    this.name = '张三';
    this.age = 18;
};
Parent4.prototype.lev = function(){
    return this.name;
}

let obj4 =new Parent4();
document.write(obj4.lev())  //输出张三
  1. 动态原型模式
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: '男'
};
  1. for in语句 它用来遍历对象的每一个属性
for (letiable in obj) {
    document.write('obj的' + letiable + '属性值为' + obj[letiable] + '<br\>');
}
  1. 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))

其他知识

  1. 运算符

其他运算符和其他编程语言的运算符相类似,这里不再赘述,演示一下其他运算符。

void运算符:用于计算括号内的值,并返回undefined,语法是void()。

let let1 = void (1 + 1);
document.write('let1 = ' + let1 + '<br\>');
  1. 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对象包含有关浏览器的有关信息,属性如下
  • 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对象

  1. 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获取各子窗口的最顶层对象
  1. 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);
}
  1. 间接引用节点
  • element.parentNode属性:引用父节点
  • element.childNodes属性:返回所有子节点的数组
  • element.nextSibling属性和element.nextPreviousSibling属性:对下一个兄弟节点和对上一个兄弟节点的引用
  1. 获取节点信息
  • nodeName属性:获得节点名称
  • nodeType属性:获得节点类型
  • nodeValue属性:获得节点的值
  • hasChildNodes():判断是否有子节点
  • tagName属性:获得标记名称
  1. 处理节点信息
  • 除了通过“元素节点.属性名称”的方式访问外,还可以通过setAttribute()和getAttribute()方法设置和获得节点属性
  • elementNode.setAttribute(attributeName,value):设置元素节点的属性
  • elementNode.getAttribute(attributeName):获取属性值
  1. 处理文本节点
  • innerHTML属性:设置或返回节点开始或结束标签之间的HTML
  • innerText属性:设置或返回节点开始和结束标签之间的文本,不包括HTML标签
  1. 改变文档层次结构
  • document.createElement()方法:创建元素节点
  • document.createTextNode()方法:创建文本节点
  • appendChild(childElement)方法:添加子节点
  • insertBefore(newNode,refNode):插入子节点,第一个参数代表插入的节点,第二个参数代表将节点插入它之前
  • replaceChild(newNode,oldNode):取代子节点,oldNode必须是parentNode的子节点
  • cloneNode(includeChildren):复制节点。includeChildren代表是否复制子节点
  • removeChild(childNode):删除子节点

DOM元素节点

在DOM模型中有三个节点,它们分别是元素节点、文本节点和属性节点。

  1. 元素节点:即标记节点 如li ol等
function getNodeProperty1() {
    let node = document.getElementById('myList');
    alert('ELEMENT_NODE:' + node.nodeType);  //1,ELEMENT_NODE
    alert('元素标记名:' + node.nodeName);  //元素标记名
    alert('节点值:' + node.nodeValue); //null
}
  1. 文本节点 即文字部分
function getNodeProperty2() {
    let node = document.getElementsByTagName('li')[0].firstChild;
    alert('TEXT_NODE:' + node.nodeType);  //3,TEXT_NODE
    alert('节点名称:' + node.nodeName);  //#text
    alert('文本内容:' + node.nodeValue); //文本内容
}
  1. 属性节点 即标签的属性
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); //属性值
}

文档对象

文档的属性和方法有很多,下面给出一些具体实例。

  1. title存储的是文档的标题,可以通过重新赋值实现改变文档标题。
function changeTitle() {
    document.title = '改变了文档标题';
}
  1. 获取文档信息
function getMsg() {
    alert(
        '当前文档的域名是:' + document.domain + '\n' +
        '当前文档的状态:' + document.readyState + '\n' +
        '当前文档有关的所有cookie:' + document.cookie + '\n' +
        '当前文档的URL:' + document.URL
    );
}
  1. 颜色属性
  • Document对象提供了alinkColor、bgColor、fgColor等几个元素属性
  • alinkColor可以设置活动链接的颜色
  • bgColor可以设置背景颜色
  • fgColor可以设置前景颜色
  • linkColor可以设置未访问链接颜色
  • vlinkColor可以设置已访问链接的颜色
  1. 输出数据
  • 使用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一些属性做特别介绍

  1. links属性 用于返回当前文档的所有超链接数组,语法如下:
document.links[1].innerHTML;
document.write('文档的第一个超链接文本:' + document.links[1].innerHTML + '<br\>');
  1. lastModified属性 用于返回文档最后修改的日期和时间,语法如下:
document.lastModified;
document.write('文档最后被编辑的时间:' + document.lastModified + '<br\>');
  1. 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代码并换行

下面详细介绍这些方法:

  1. createElement() 它可以动态添加一个HTML标记
function addInput() {
    let input = document.createElement('input');
    input.type = 'text';
    input.name = 'txt';
    input.value = '动态添加的文本框';
    document.body.appendChild(input);
}
  1. getElementById() 使用此方法可以获取指定元素并修改其内容
function modify() {
    document.getElementById('input').value = '修改后的文本';
}
  1. 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页面连接到一起,使用户可以和用户进行交互、以响应用户的操作,如浏览器载入文档或用户动作如敲击键盘、

  • 滚动鼠标等触发,而事件处理程序则说明一个对象如何响应事件。

  • 事件的调用方式:

  1. 在script标签中调用(在下方代码有演示)

  2. 在元素中调用

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); //移除事件

现将常用事件写在下面。

  1. 鼠标键盘事件:
  • onkeydown:某个键盘的键被按下时触发此案件
  • onkeypress:某个键盘的按键被按下或按住时触发此案件
  • onkeyup:某个键盘的按键被松开时触发事件
  • onclick:鼠标单击某个对象时触发此事件
  • ondblclick:鼠标双击某个对象时触发此事件
  • onmousedown:某个鼠标按键被按下时触发此事件
  • onmousemove:鼠标被移动时触发此事件
  • onmouseout:鼠标从某元素移开时触发此事件
  • onmouseover:鼠标移动到某元素上方时触发此事件
  • onmouseup:某个鼠标按键被松开时触发此事件
  • onmouseleave:当鼠标指针移出元素时触发此事件
  • onmouseenter:当鼠标指针移动到某元素上方时触发此事件
  • oncontextmenu:在用户单击鼠标右键打开上下文菜单时触发此事件
  1. 页面相关事件
  • onload:某个页面或图像被完成加载时触发此事件
  • onabort:图像加载被中断时触发此事件
  • onerror:当加载文档或图像发生错误时触发此事件
  • onresize:当浏览器的窗口大小发生改变时触发此事件
  • onbeforeunload:当前页面的内容将要被改变时触发此事件
  • onunload:当前页面将被改变时触发此事件
  • Onhashchange:该事件在当前URL的锚部分发生修改时被触发
  • Onpageshow:该事件在用户访问该页面时被触发
  • Onpagehide:该事件在用户离开当前网页跳转到另外一个网页时触发
  • Onscroll:当文档被滚动时触发此事件
  1. 表单相关事件
  • onreset:当重置按钮被单击时触发此事件
  • onblur:当元素失去焦点时触发此事件
  • onchange:当元素失去焦点并且元素的内容发生改变时触发此事件
  • onsubmit:当提交按钮被单击时触发此事件
  • onfocus:当元素获得焦点时触发此事件
  • onfocusin:元素即将获得焦点时触发此事件
  • onfocusout:元素即将失去焦点时触发此事件
  • oninput:元素获取用户输入时触发此事件
  • onsearch:用户向搜索域输入文本时触发此事件(<input='research'>)
  • onselect:用户选取文本时触发此事件
  1. 拖动相关事件
  • 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():初始化键盘事件对象的值

文本相关事件

  1. oncopy:在用户复制文本时被触发

  2. onpaste:在用户粘贴文本时被触发

  3. oncut:在用户剪切文本时被触发

  4. 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>

键盘相关事件

  1. onkeydown事件:键盘被按下时触发
  2. onkeypress事件:键盘被按下时被触发,此事件只有字符键被按下时触发,单独按下功能键、shift、alt、ctrl等按键时不触发
  3. 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>

鼠标相关事件

  1. 鼠标相关事件

单击事件是鼠标点击时触发的事件,用onclick实现,这里不再演示

  1. 鼠标按下与松开事件

鼠标按下事件为onmousedown事件,用户在按下鼠标时触发

  1. 鼠标移入移出事件

鼠标移入事件由onmouseover控制,鼠标移除事件由onmouseout控制

  1. 鼠标移动事件

鼠标移动事件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>

表单相关事件

  1. 获得焦点和失去焦点事件

onfocus事件在某个元素获取焦点时被触发

onblur事件在某个元素失去焦点时被触发

  1. onchange事件在对象内容被改变且失去焦点时被触发,一般用在下拉列表中

  2. 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>

页面相关事件

  1. onload:在页面加载完成后被触发
  2. onresize:在浏览器大小被改变后被触发
  3. 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");
}
上次编辑于:
贡献者: QI