本文最后更新于 2024-12-08,文章内容可能已经过时。

JavaScript

js 是运行在客户端(浏览器)编程语言 ,可以在浏览器实现交互特效网页特效表单验证等!

JavaScript特性

解释性语言

解释性语言就是读一行解释一行, 一般在开发JavaScript 代码时候,计算机是无法直接认识的,需要经过编译或者解释后才能让计算机正确执行代码! 计算机只认识0101011001 这样的二进制数字!

  1. 解释性语言: 会将编写好的代码一行一行解释翻译成浏览器所认识的代码!

  2. 编译性语言: 会将编写好的代码完整过一遍后生成翻译文件进行整体翻译!

单线程

单线程 就是不能在同一个时间段内干多件事儿,只能同一时间内做一件事儿!

多线程 就是可以在同一事件内处理很多事务!

JS三大部分

ECMAScriptDOM 以及BOM

ECMAScript: 是一种规范,用于制定js语法规则JavaScript是在ECMA规范上的一种实现!

DOM:文档对象模型,是w3c标准,提供了关于操作html元素dom的API方法!

BOM:浏览器对象模型 ,是每个浏览器内部的一个实现 ,提供对浏览器操作的API方法!

ECMAScript

ECMAScriptJavaScript一套标准规范, ECMA会制定出方案结果,JS对方案进行的一个功能实现!

例如: 循环 判断 变量声明 函数声明等基本语法!

字面量

字面量表示声明值的一部分,根据值的类型,有不同类型的字面量 !

var age = 15;15数字字面量!

var name = "张三";张三字符串字面量!

变量

变量(容器): 它可以将不同数据类型的值存放到一个变量(容器)当中!

变量就是在程序中用来表示存储数据的!

声明变量

声明变量的方式有三种,分别为 var letconst !

var name = "张三"
let name = "张三"
const name = "张三"

这里以var关键字举例说明:

var 是一种声明变量的关键字定义某个变量时,必须加入关键字进行声明,此处用来表示定义存储变量!

var 变量声明关键字!

name 变量名!

= "张三"; 对变量名进行赋值操作,并存储数据!

三者声明变量区别

var 是属于早期的一种声明变量的一种方式,其中存在众多问题,例如变量提升变量污染重复定义等 !

变量提升: js引擎会在解析js代码时,将变量声明提取到作用域的最顶端,且初始值为undefind!

变量污染: var 没有块级作用域,在块级作用域中定义的变量,依旧可以在上级或全局作用域中访问!

重复声明: var同一个作用域内可以重复声明一个变量,会导致后面的变量值覆盖已有的变量值!

  1. 变量提升

    变量提升,就是js引擎解析代码时,会将声明的所有变量提升在作用域顶部,这就意味着在变量赋值之前依旧可以访问变量,只不过该变量的值为undefind!

     console.log(name); // undefind
     var name = "张三";
     console.log(name); // 张三
    
  2. 变量污染

    变量污染, 就是var是属于函数作用域,不属于块级作用域块级作用域包括(if for switch),这意味着,在块级内部定义的变量,在块级之外依旧可以访问!

    function demo(){
      if(true){
        // 块级内部
        var name = "张三";
      }
      // 块级外部
      console.log(name); // 张三
    }
    
  3. 重复定义声明

    重复定义声明, 在代码中可以在同一个作用域下重复定义一个已存在声明变量的值!

    var age = 19;
    var age = 22;
    console.log(age); // 22
    

letconst

这两种声明方式,是es6提出的标准 ,它们的出现就是为了解决var 声明变量时所遗漏的一些问题 !

  1. let:

    • 不能在同一个作用域内重复定义变量!
    • 无法在变量赋值前进行变量访问,不存在变量提升!
    • 属于块级作用域,在块级之外无法访问,且不会对环境变量造成污染!
  2. const:

    let 关键字基本一致,则区别是,const是用来定义常量!

    • 常量在声明变量时,必须有初始值!
    • 一旦被赋值,则无法在改变值!
变量的本质

定义变量的本质,就是在内存中开辟一个空间,将变量值存储内存空间中!

变量名则为内存空间的一个命名方式,通过命名可以获取对应空间中的存放数据!

命名规范
  1. 不能以关键字进行命名,例如let const if for 等语法关键字!

  2. 只能用下划线 字母 数字 $部分组成不能以数字作为开头!

  3. 首字母不能大写,驼峰命名,例如 noProblem !

数据类型

js数据类型弱类型弱类型就是根据赋值后判定该值类型为某一个具体类型!

基本数据类型

基本类型: (字符串布尔值null, undefind, symbolbigInt)

引用数据类型

引用数据类型 : 也是复杂类型( Array_数组 Object_对象 Function_函数 )

引用数据类型:在内存中会开辟一个新的空间用来存放这个地址,并将这个地址指向声明变量的变量名!

区别
  1. 基本数据类型:
    • 存放在栈内存中,在内存中存放的是值,可以直接对值进行操作,和对比!
  2. 引用数据类型:
    • 存放在堆内存中,在内存中会存放引用类型的地址,并将地址指向声明变量的变量名!
运算符

运算符包含( 算数运算符比较运算符 以及赋值运算符逻辑运算符)

算数运算符:+ - * / %(取模),算数优先级,先乘除后加减,若有括号先算括号里的表达式! %(取模) 就是两者相除剩余的余数!

比较运算符: 分为弱类型判断(== !=) 和 强类型判断(=== !==) 以及大小数值比较 (<= >=)!

赋值运算符: 分为正常赋值( a = b ) 和 逻辑赋值( ??=, ||=, &&=, )

弱类型和强类型比较

弱类型:(== !=) 使用 弱类型判断 时,会做类型隐士转换,比如(“1” == 1)返回为 true,原因是,会将字符串1转换为 number类型后在进行判断!

强类型:(=== !==) 使用 强类型判断 时,类型不会做转换,("1"=== 1) 返回为 false,两者类型不一致,则结果不同!

逻辑赋值
  1. 逻辑与赋值(Logical AND Assignment) - &&=
    • 语法x &&= y
    • 解释:如果x(truthy),则将y赋值给x
  2. 逻辑或赋值(Logical OR Assignment) - ||=
    • 语法x ||= y
    • 解释:如果x(falsy),则将y赋值给x
  3. 逻辑空赋值(Logical Nullish Assignment) - ??=
    • 语法x ??= y
    • 解释:如果xnullundefined,则将y赋值给x
类型转换

类型转换,通常就是将一种基本类型,转换成另一个基本类型,常用在字符串转数字的场景!

"123" + 123 // 123123 不会进行算数运算,直接当作两个字符串进行拼接

转换 字符串 为数字

+"123" + 1 // 124 在字符串前方加入 + 号会做隐式转换为数字

作为"-"号运算符时,例如:

"12" - 1 // 11 会默认将 字符串12 转换为数字12 在做运算
条件判断

条件判断:就是根据表达式返回(true/false)分别进入不同分支代码块进行运行结果!

if语句

语法

if( true ){
 // 条件为 true 时
} else {
 // 条件为 false 时
}
if(languageScore > mathScore){
  // 语文 大于 数学时 进入该分支逻辑
  console.log("语文成绩大于数学成绩!")
} else if(languageScore < mathScore){
  // 语文 小于 数学时 进入该分支
  console.log("语文成绩小于数学成绩!")
}
switch语句
var languageScore = 50;
switch (languageScore) {
  case 10:
    console.log("匹配到 10!");
    break;
  case 20:
    console.log("匹配到 20!");
    break;
  case 50:
    console.log("匹配到 50!");
    break;
  default:
    console.log("默认")
}
循环语句
let __arr_ = [1, 43, 5, 65, 6, 3]

for (let i = 0; i < __arr_.length; i++) {
  console.log("__arr_[i]" + __arr_[i]);
}
冒泡排序
let arr = [1, 43, 5, 65, 6, 3]
// arr.length 长度为6  arr.length - 1 长度为5
// for循环 index 下标从0开始算.  [1 的 index为0 到最后3 的index为5]
for (let i = 0; i < arr.length - 1; i++) {
  // arr.length - i - 1
  // [6-0-1 = 5]
  // [6-1-1 = 4]
  // [6-2-1 = 3]
  for (let j = 0; j < arr.length - i - 1; j++) {
    // arr[j] = 1;
    // arr[j + 1] = 43;
    // 判断前一个是否大于下一个数值,满足条件进行数据交换!
    if (arr[j] > arr[j + 1]) {
      let temp = arr[j];
      arr[j] = arr[j + 1];
      arr[j + 1] = temp;
    }
  }
}

console.log("arr", arr)
函数

函数的作用就是将一些共用的业务逻辑进行抽离, 在不同的业务场景下进行函数调用处理!

封装冒泡函数案例

let __arr_ = [1, 43, 5, 65, 6, 3]

function sortArr(arr) {
  let sortArr = [...arr];
  for (let i = 0; i < sortArr.length - 1; i++) {
    for (let j = 0; j < sortArr.length - i - 1; j++) {
      if (sortArr[j] > sortArr[j + 1]) {
        let temp = sortArr[j];
        sortArr[j] = sortArr[j + 1];
        sortArr[j + 1] = temp;
      }
    }
  }
  return sortArr;
}

let __result = sortArr(__arr_);

console.log("sort before", __arr_, "sort after", __result)
// 排序前[1,43,5,65,6,3] 排序后 [1,3,5,6,43,65]

函数体内中包含,参数对象(arguments)动态绑定对象(this), 返回值(return), 作用域,以及原型链等相关知识!

函数声明方式

命名函数

function funName(){
  
}

let funName = function () {
 
}

匿名函数

匿名函数,就是没有命名的函数,常见的应用场景有,立即执行函数回调函数闭包事件绑定函数

  1. 立即执行函数:

    (function(){
      console.log("函数体~")
    })()
    
  2. 回调函数:

    [1,4,undefind,5,6,null].filter(function(item){
      return !!item;
    })
    
  3. 闭包:

    function closure(){
      let name = "张三";
      return function(){
        console.log("name", name)
        return name;
      }
    }
    
  4. 事件绑定:

    el.onclick = function(){
     console.log("我被点击了~")
    }
    

立即执行函数

立即执行函数,就是创建一个独立作用域的可立即执行函数体!

(function(a,b){
   console.log(a + b)
})(1,2);

注意: 立即执行函数,代码结束位置需要加入";"号,确保上一个代码段执行完毕!

参数传递

参数分为: 形参实惨!

形参: 是在声明函数的时候,定义的参数叫形参!

实参: 在函数调用时,传递的参数可称为实参数,实际传递的参数!

let funName = function (num1, num2) {
  console.log("funName", num1, num2)
}

funName(12, 15);

在函数内部,有一个arguments参数对象,改对象包含一组参数列表!

arguments有自己的length,但是不是真正的数组,无法使用数组的方法处理参数列表对象!

let funName = function (num1, num2) {
  console.log("funName arguments", arguments, arguments.length)
  // log funName arguments {"0":12,"1":15} 2 
}

funName(12, 15);
返回值

返回值,就是函数在执行完毕时,向外部返回的一个结果!

使用return关键字可以返回一个结果!

let funName = function (num1, num2) {
  console.log("funName arguments", arguments, arguments.length)
  return num1 + num2;
}

let sumResult = funName(12, 15);
console.log("sumResult", sumResult)
作用域

作用域就是一个变量可访问范围, 通常在创建一个函数时,内部会有自己的作用域,除了自身作用域以外,还会包含父级作用域!

  1. 局部作用域:
    • 函数内部,或者块级儿内部定义的变量,且该变量仅能在局部作用域内生效!
  2. 全局作用域:
    • 全局代码中定义的变量,可供 全局任何地方访问的变量就是属于全局作用域!
let anya = "全局作用域定义的变量";
function funName(){
  // 局部作用域 父级
  let name = "张三";
  let age = 18;
  console.log(`funName ${name} ${age}`)
  return function (){
    // 局部作用域 子级 解析函数时会创建父级作用域 并指向父级作用域
    // 如果内部作用域没有定义该变量,且被引用时,则会查找父级作用域
    console.log(`return fun ${name} ${age}`)
  }
}

作用域链

js引擎解析函数时,会创建内部作用域,也会有一个父级作用域parentScope!

当在内部作用域内访问一个变量时,且该变量未在当前作用域中定义,则会向父级作用域中查找,直到全局作用域获取到为止

若都没有,则报错is not defind!

this动态绑定

this函数体内特有的动态绑定对象,根据函数调用情况,返回不同this对象!

this四种绑定方式分别为: 默认绑定 隐式绑定 显示绑定 new绑定!

  1. 默认绑定:
    • 全局中定义函数时,并在全局中调用该函数时,this指向 window 全局对象!
  2. 隐式绑定:
    • 函数作为某个对象中的方法时,通过对象的方式来调用该函数,这时,this指向该对象!
  3. 显示绑定:
    • js中有三种方法可以修改函数的this指向,分别是apply call bind,该方法是函数中特有的,用来修改函数中的this指向!
  4. new绑定:
    • new创建构造函数的关键字,创建构造函数时会创建一个空对象,并将函数的this指向这个空对象!
字符串

字符串就是被双引号引住的值,例如 "张三", "描述"!

let str = "Hello, World!";
常用的方法
  1. 字符串的长度:

    • length: "hello".length
  2. 查找字符串:

    • charAt(index): 指定索引位置,返回索引位置所对应的字符!

    • indexOf(substr, start): 检索字符串,根据指定字符,返回该字符的索引位置!

      console.log(str.indexOf("World")); // 输出: 7
      
    • lastIndexOf(substr, start): 返回子字符串最后一次出现的位置

      console.log(str.lastIndexOf("o")); // 输出: 8
      
    • includes(searchString, position): 判断字符串是否包含指定的子字符串,返回布尔值

      console.log(str.includes("World")); // 输出: true
      
    • startsWith(searchString, position): 判断字符串是否以指定的子字符串开始

      console.log(str.startsWith("Hello")); // 输出: true
      
    • endsWith(searchString, length): 判断字符串是否以指定的子字符串结束

      console.log(str.endsWith("!")); // 输出: true
      
  3. 字符串提取:

    • slice(start, end): 根据开始索引和结束索引,对字符串进行截取,不包含最后索引值!。

      "hello,world".slice(2,4); // ll
      
    • substring(start, end): 与 slice 相似,不支持负数!

    • substr(start, length): 从索引开始位置截取,到指定长度!

      // 从开始索引第7个位置,截取5个字符串长度!
      let str = "Hello, World!";
      console.log(str.substr(7, 5)); // 输出: "World"
      
  4. 字符串替换:

    • replace(searchValue, replaceValue): 替换字符串中匹配的值只替换第一个匹配项)。

      let str = "Hello, World!";
      console.log(str.replace("World", "JavaScript")); // 输出: "Hello, JavaScript!"
      
    • replaceAll(searchValue, replaceValue): 替换字符串中所有匹配的值

      let str = "Hello, World! World!";
      console.log(str.replaceAll("World", "JavaScript")); // 输出: "Hello, JavaScript! JavaScript!"
      
  5. 大小写转换:

    • toUpperCase(): 将字符串转换为大写
    • toLowerCase(): 将字符串转换为小写
  6. 去空白字符:

    • trim(): 去除字符串两端的空白字符。
    • trimStart(): 去除字符串开头的空白字符。
    • trimEnd(): 去除字符串结尾的空白字符。
  7. 分割字符串

    • split: 根据指定字符,将字符串进行分割,返回一个分割后的数组列表!

      let str = "Hello, World!";
      console.log(str.split(",")); // 输出: ["Hello", " World!"]
      
  8. 连接字符串:

    • concat(str1, str2, ...): 连接两个或多个字符串
  9. 重复字符串

    • repeat(count): 重复字符串指定的次数。
数组

数组是js中数据结构存储之一,它可以存储任意类型的值,包括基本类型, 引用类型, Set, Map,等数据结构!

let testSet = new Set([1, '2', '5', 7])
let testFun = function () { }
let testArr = [testSet, testFun, 12, "张三", true, { name: "张三"} ]

console.log("testArr", testArr)
操作数组方法
  1. unshift: 在数组的第一项添加元素!
  2. shift: 删除数组的第一项,并返回删除的元素!
  3. push: 在数组的最后一项添加元素!
  4. pop: 删除数组最后一项,并返回删除的元素!
  5. indexOf:检索数组中的某一项,若找到则返回该元素在数组中的索引位置,否则返回-1!
  6. slice: 截取根据起始索引结束索引位置,对数组进行截取,不包含结束索引位置!
  7. splice: 添加删除替换数组中的元素,会改变原始数组!
    • 语法: array.splice(start, deleteCount, item1, item2, ..., itemX)
    • start: 必需。指定开始修改数组的索引位置。如果为负数,则从数组末尾开始计算
    • deleteCount: 可选。指定要删除的元素数量。如果省略或大于等于start数组末尾的元素数量,则删除从 start 到数组末尾的所有元素
    • item1, item2, ..., itemX: 可选。要添加到数组中的新元素,从 start 位置开始插入。
  8. includes: 判断数组中是否包含该元素,返回Boolean!
  9. concat: 将两个数组合并,返回一个新的数组长度!
  10. join: 将数组通过某个字符分割成字符串!
数组遍历方法
let arr = [1,43,45,56,66,3]
let obj = { name: "张三", age: 18 }
  1. for循环:

    for(let i = 0; i < arr.length; i++){
      console.log("遍历的每一项 item", arr[i])
    }
    
  2. forEach:

    forEach 无法通过 breakreturn 提前终止循环

    arr.forEach(function(item){
      console.log("遍历的每一项 item", item)
    })
    
  3. for...in:

    主要用于迭代对象的可枚举属性!

    for(let key in obj) {
      console.log("遍历的每一项 对象的属性", key)
    }
    
  4. for...of:

    循环用于遍历可迭代对象(如数组、字符串、Map、Set 等)的元素。

    for(let item of arr){
     console.log("遍历的每一项 item", item)
    }
    
ES6新增的迭代方法
  1. map:

    对数组元素进行逻辑处理后,返回一个新的数组,不会影响原来的数组结构!

    let newArr = arr.map(function(item, index, sourceArr){
      console.log(item, index, sourceArr)
      return item * 2;
    })
    console.log("newArr", newArr)
    
  2. filter:

    对数组元素进行过滤, 返回一个过滤后新的数组结构! 会过滤掉 undefindnull 的数据!

    // 返回一个 boolean,过滤数组中大于 10 的元素!
    let filterResult = arr.filter((item, index, sourceArr) => {
      return item > 10;
    })
    console.log("filterResult", filterResult)
    
  3. some:

    判断该数组中,是否有某些元素满足条件,满足则返回 true, 否则返回 false;

    let result = arr.some((item, index, sourceArr) => {
      return item === 3;
    })
    console.log("result", result) // true
    
  4. every:

    判断数组中所有元素满足该条件时,返回 true, 否则返回 false;

    let arr = [2,2,5,2,3,5];
    let arr2 = [3,3,3,3,3];
    arr.every(item => item === 2) // false
    arr2.every(item => item === 3); // true
    
  5. find:

    返回满足条件的那一条数据!

    let result = arr.find((item, index, sourceArr) => {
      return item === 3;
    })
    console.log("result", result) // 3
    
  6. findIndex:

    返回满足条件的那一条数据的 index 索引值!

    let result = arr.find((item, index, sourceArr) => {
      return item === 3;
    })
    console.log("result", result) // index 5
    
对象

js对象也是一种数据存储结构,它是以键值对形式存储数据,并通过键来获取值 !

let obj = { name: "张三", age: 19 }
对象的操作
  1. 根据key来获取value:

    console.log("name", obj["name"], obj.name);
    
  2. 设置对象属性值:

    obj["name"] = "李四";
    obj.name = "李四"
    
遍历对象属性
for(let key in obj){
  console.log("obj[key]", obj[key])
}
内置对象

内置对象就是js中预制的对象,像 Date日期 Math数学 RegExp正则对象等 !

全局对象
  1. Object: 是所有对象的基类,包含对象的一些操作方法,例如对象属性是否可枚举,可写,以及一些属性判断等方法!
  2. Function: 是所有函数的基类,提供了一些方法,如 call(), apply(), bind() 等。
  3. Array: 用于创建数组对象,包含一些数组的方法push(), pop(), slice(), splice()!
  4. String: 用于创建字符串对象,包含一些字符串相关的方法,如 slice(), replace(), toUpperCase() 等!
  5. Number: 用于创建数字对象,包含一些数字处理相关的方法,如 toFixed() toPrecision()!
  6. Boolean: 用于创建布尔对象,提供布尔值相关的方法。
  7. Date: 用于处理日期和时间,提供日期和时间相关的方法,如 getFullYear(), getMonth(), getDate() 等!
  8. RegExp: 用于创建正则表达式对象,提供正则表达式相关的方法,如 test(), exec() 等!
错误对象
  1. Error: 所有错误对象的基类

    try {
      throw new Error("An error occurred");
    } catch (e) {
      console.log(e.message); // 输出: "An error occurred"
    }
    
  2. TypeError: 表示类型错误

    try {
      null.f();
    } catch (e) {
      console.log(e instanceof TypeError); // 输出: true
    }
    
  3. ReferenceError: 表示引用错误

    try {
      console.log(x);
    } catch (e) {
      console.log(e instanceof ReferenceError); // 输出: true
    }
    
数学对象

Math: 提供数学常数和函数,如 Math.PI, Math.sin(), Math.cos(), Math.random() 等。

console.log(Math.PI); // 输出: 3.141592653589793
console.log(Math.random()); // 输出: 0 到 1 之间的随机数
  1. Math.abs(): 绝对值,所有负数转换为正数!

  2. Math.max() Math.min(): 返回参数中 最大值(max)最小值(min)!

    Math.max(1,5,2) // 5
    Math.min(1,5,2) // 1
    // 如果没有参数 则是 +infinity 和 -infinity
    
  3. Math.floor() Math.ceil(): 向下取整(floor) 和 向上取整(ceil)!

    // 向下取整
    Math.floor(3.2) // 3
    Math.floor(-3.2) // -4
    // 向上取整
    Math.ceil(3.2) // 4
    Math.ceil(-3.2) // -3
    
  4. Math.random(): 随机数 默认 >= 0 小于1

    // 10 ~ 20 随机数
    function random( min, max ){
      /* 
        先算乘除 在算加减
        Math.random() 默认为 0 ~ 1
        max - min = 10
        Math.random() * 10 + min 10
       */
      return Math.floor(Math.random() * ( max - min ) + min);
    }
    
JSON对象

JSON: 提供将对象转换为 JSON 字符串将 JSON 字符串解析为对象的方法,如 JSON.stringify(), JSON.parse()!

let obj = { name: "Alice", age: 30 };
let json = JSON.stringify(obj);
console.log(json); // 输出: '{"name":"Alice","age":30}'
let parsed = JSON.parse(json);
console.log(parsed); // 输出: { name: "Alice", age: 30 }

DOM

DOM(文档对象模型) document 主要用来操作 html dom元素!

例如: 对 元素的创建 删除 以及 元素属性的添加删除等一系列有关操作!

DOM 不属于javascript中的范畴,是独立的,是w3c标准提供的对html元素一系列操作的接口(api)!

文档中对应的属性
  1. document: 获取整个html文档!
  2. 获取文档中的父节点或子节点
    • parentNode: 获取元素的父节点!
    • childNodes: 获取子节点!
    • sibling: 获取同级节点!
获取元素节点属性

通过 nodeType 属性可以获取元素节点所对应的属性常量值:

属性 数值 描述
文档节点(document) 9 Node.DOCUMENT_NODE
元素节点(element) 1 Node.ELEMENT_NODE
属性节点(attribute) 2 Node.ATTRIBUTE_NODE
文本节点(text) 3 Node.TEXT_NODE
文档片段(DocumentFragment) 11 Node.DOCUMENT_FRAGMENT_NODE
document.nodeType === Node.DOCUMENT_NODE // 9 true
获取元素的方法

<div class = "box"></div>
<div class = "container"></div>
<div id = "app"></div>
根据元素标签获取

指定元素标签返回一组符合元素标签的元素节点集合[HTMLCollection[div]]!

可以实时获取元素的变化,若没有符合的元素,则返回一个空的集合!

document.getElementsByTagName("div") // HTMLCollection[div]

获取元素集合中某个元素

document.getElementsByTagName("div")[0] // 获取集合中第一个元素节点
根据className获取
document.getElementsByClassName("div.box") // HTMLCollection[div.box]
根据 id 获取
document.getElementById("#app") // 返回一个唯一符合的元素节点
使用query选择器

接受一个css选择器,返回第一个符合的元素节点!

document.querySelector(".box") // className
document.querySelector("#box") // id
document.querySelector("div") // tagName
使用queryAll选择器

querySelector类似,返回的是一个NodeList元素节点集合!

document.querySelectorAll(".box")
创建元素与属性
创建和删除元素

使用createElement方法来创建一个元素标签!

let divEl = document.createElement('div');
document.removeChild(divEl); // 删除指定元素
创建元素属性

使用createdAttribute 方法来创建一个元素属性,并返回一个元素节点!

使用setAttributeNode给元素设置属性!

let idAttr = document.createAttribute('id');
divEl.setAttributeNode(idAttr);

使用setAttributegetAttribute设置元素的属性获取元素的属性!

el.setAttribute('style', 'display: flex; flex-wrap: wrap; gap: 10px;');
console.log(el.getAttribute('style')); // display: flex; flex-wrap: wrap; gap: 10px;

使用removeAttribute可以删除指定的属性!

el.removeAttribute('style');
追加子元素

使用appendChild父元素节点内部元素之后添加子元素节点!

let pEl = document.createElement("p");
pEl.innerText = "hello world~";
divEl.appendChild(pEl);

使用insertBefore父元素节点内部元素之前添加子元素节点!

Element元素属性

元素属性就是,可通过Element对象直接获取或设置对应的属性内容!

属性 描述 获取方式
id 获取元素属性id el.id
className 获取元素属性的class el.className
classList 获取元素属性的class集合 el.classList
innerText 设置获取元素节点的内容content el.innerText获取 / el.innerText = '新的内容'
innerHtml 设置获取元素节点中的html元素内容 el.innerHtml获取 / el.innerHtml = '<p>新的内容</p>'
console.log("获取id", div.id); // div-id
console.log("获取class", div.className); // div-class
console.log("获取classList", div.classList); // DOMTokenList(1) ["div-class", value: "div-class"]
添加或删除class属性

add属性可以添加一个class属性,remove可以删除一个class属性!

 el.classList.add("new_class_name")
 el.classList.remove("new_class_name")

contains 属性可以判断该元素中的class属性列表中是否包含,指定class选择器!

el.classList.contains('new_class_name');

toggle 如果class列表中不存在,则添加,否则删除!

el.classList.toggle('new_class_name');
innerHTML属性

innerHTML 可以在页面中添加元素标签以及内容!

el.innerHTML = `<div style="width: 50px; height: 50px; background-color: red;"></div>`

循环追加多个标签时,存在性能问题,导致页面渲染缓慢!

拼接字符串会引起渲染性能问题!

let startTime = Date.now();
let div = document.querySelector('.test');
for(let i = 0; i < 1000; i++){
  div.innerHTML += `<div style="width: 50px; height: 50px; background-color: red; margin-bottom: 10px;"></div>`
}
let endTime = Date.now();
console.log(`生成1000个div耗时${endTime - startTime}ms`); // 生成1000个div耗时372ms左右

处理性能问题,可以将添加的元素放在数组当中,通过join的方法,将数组转换为字符串,最后赋值与innerHTML元素!

let startTime = Date.now();
let arr = [];
let div = document.querySelector('.test');
for(let i = 0; i < 1000; i++){
  arr.push(`<div style="width: 50px; height: 50px; background-color: red; margin-bottom: 10px;"></div>`);
}
div.innerHTML = arr.join('');
let endTime = Date.now();
console.log(`生成1000个div耗时${endTime - startTime}ms`); // 生成1000个div耗时1ms

相对于 createElement方法创建元素innerHTML采用数组方式生成元素时,其效率更高!

获取元素的位置

包含元素的宽高网页可见宽高 以及body的宽高,用来配合元素在页面中的位置计算!

元素偏移量(offset)
offset系列

offset(偏移量):

  1. 获取元素的自身的宽高

  2. 还有获取当前元素的距离定位的父元素距离位置!

常用的属性

属性 描述
el.offsetParent 获取带有定位(绝对定位 和 相对定位)的父元素,如果没有定位,则获取body!
el.offsetLeft 获取元素距离带有定位的父元素的左侧距离
el.offsetTop 获取元素距离带有定位的父元素的顶部距离
el.offsetHeight 获取元素自身的高度内边距 边框 以及内容区的高度!
el.offsetWidth 获取元素自身的宽度内边距 边框 以及内容区的宽度!

offsetParent: 带有定位的父元素,没有则获取 body!

parentNode: 不管父元素有没有定位,都是最近一层上级元素!

offsetstyle 的区别

offset:只读属性,无法进行赋值,只能用来获取元素的相关属性!

style: 行内样式,只能获取和修改元素的行内样式!

获取在盒子内部的xy坐标

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>获取鼠标在盒子内部的坐标</title>
  <style>
    .box{
      width: 200px;
      height: 200px;
      background-color: #ccc;
    }
  </style>
</head>
<body>
  <div class="box"></div>
  <script>
    const box = document.querySelector('.box');
    box.addEventListener('click', function(event){
      /* const x = event.clientX - box.offsetLeft;
      const y = event.clientY - box.offsetTop; */
      const x = event.pageX - box.offsetLeft; 
      const y = event.pageY - box.offsetTop;
      console.log(`鼠标在盒子内部的坐标为:(${x}, ${y})`);
    })
  </script>
</body>
</html>

拖动元素案例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>拖拽盒子</title>
    <style>
      :root, body {
        width: 100%;
        height: 100%;
      }
      :root {
        margin: 0;
        padding: 0;
      }
      .box {
        width: 500px;
        height: 300px;
        background-color: #fcf;
        border-radius: 15px;
        box-shadow: 0 0 10px #999;
        position: fixed;
        top: 50%;
        left: 50%;
        /* margin: auto; */
        transform: translate(-50%, -50%);
        cursor: move;
      }
    </style>
  </head>
  <body>
    <div class="box"></div>
    <script>
      const box = document.querySelector('.box');
      // 当鼠标按下时
      box.addEventListener('mousedown', function(event) {
        const innerX = event.pageX - box.offsetLeft;
        const innerY = event.pageY - box.offsetTop;
        // 当鼠标在 文档页面 上移动时
        document.addEventListener('mousemove', move);
        // 当鼠标在 文档页面 上抬起时
        document.addEventListener('mouseup', up);
        function move(event) {
          console.log(box.offsetLeft)
          box.style.left = event.pageX - innerX + 'px';
          box.style.top = event.pageY - innerY + 'px';
        }
        function up() {
          document.removeEventListener('mousemove', move);
          document.removeEventListener('mouseup', up);
        }
      });
    </script>
  </body>
</html>

innerXinnerY鼠标box元素内部触发事件时到左侧的一个距离!

move触发移动事件时,需要重新计算出,box元素的距离body topleft 值!

计算规则: (pageX, pageY or clientX ,clientY )获取鼠标距离左侧顶部的坐标值(x,y)

减去鼠标距离box元素内部坐标值(x,y)!

内部鼠标距离元素 left 和 top 的距离,通过 pageX - offsetLeft 和 pageY - offsetTop!

client系列

获取元素的边框大小,以及元素的大小!

属性 描述
el.clientLeft 返回左边框的大小!
el.clientTop 返回上边框的大小!
el.clientWidth 返回元素的宽度,包含 padding内容的宽度,不包含border!
el.clientHeight 返回元素的高度,包含 padding内容的高度,不包含border!
scroll系列

可以获取元素滚动时的距离!

属性 描述
e.scrollTop 返回上层被滚动隐藏元素的距离!
e.scrollLeft 返回左侧被滚动隐藏元素的距离!
e.scrollWidth 返回自身实际的宽度,不包含border!
e.scrollHeight 返回自身实际的高度,不包含border!

在窗口下滚动被卷去的元素距离

window.pageXOffset: 获取在窗口内滚动被卷去的左侧距离!

window.pageYOffset: 获取在窗口内滚动被卷去的顶部距离!

兼容问题 : >= ie9 否则使用 document.body.scrollTop

如果是在元素内部滚动,请使用el.scrollTopel.scrollLeft!

滚动案例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>滚动案例</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      div:not(.box, .back-top, .top) {
        width: 85%;
        border-radius: 10px;
        margin: 15px auto;
        text-align: center;
        font-weight: bold;
        font-size: 24px;
        color: white;
      }
      div.box {
        position: relative;
      }
      div.header {
        height: 100px;
        line-height: 100px;
        background-color: cadetblue;
      }
      div.banner {
        height: 400px;
        line-height: 300px;
        background-color: darkcyan;
      }
      div.main {
        height: 1200px;
        line-height: 800px;
        background-color: darkslategray;
      }
      .back-top {
        width: 50px;
        height: 100px;
        border: 1px solid red;
        position: absolute;
        top: 300px;
        right: 4.6%;
        background-color: tomato;
        border-radius: 5px;
        font-size: 14px;
        font-weight: bold;
        color: #fff;
        line-height: 100px;
        text-align: center;
      }
      .back-top .top {
        cursor: pointer;
        opacity: 0;
        transition: all 0.3s ease;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="header">header</div>
      <div class="banner">banner</div>
      <div class="main">main</div>
      <div class="back-top">
        <div class="top">Top</div>
      </div>
    </div>

    <script>
      /* 
        1. 判断 header 是否在可视范围内
        2. 设置 backTop 为 fixed 重新计算 top 值
      */
      const header = document.querySelector(".header");
      const banner = document.querySelector(".banner");
      const main = document.querySelector(".main");
      const backTop = document.querySelector(".back-top");

      const headerTop = header.offsetTop;
      const bannerTop = banner.offsetTop;
      const mainTop = main.offsetTop;
      const backTopOffset = backTop.offsetTop - bannerTop;
      document.addEventListener("scroll", function (e) {
        if (window.pageYOffset >= bannerTop) {
          console.log("header 隐藏");
          backTop.style.position = "fixed";
          backTop.style.top = backTopOffset + "px";
        } else {
          console.log("header 显示");
          backTop.style.position = "absolute";
          backTop.style.top = 300 + "px";
        }
        // 滚动到主内容区域
        if (window.pageYOffset >= mainTop) {
          console.log("main 显示");
          backTop.children[0].style.opacity = 1;
        } else {
          console.log("main 隐藏");
          backTop.children[0].style.opacity = 0;
        }
      });
      backTop.addEventListener("click", function (e) {
        window.scrollTo({
          top: 0,
          left: 0,
          behavior: "smooth",
        });
      })
    </script>
  </body>
</html>
总结

offsetclient 以及 scroll 都可以获取元素本身的宽高,除了offset获取宽高包含boder以外,其它两个都不包含border!

设置css样式
  1. 使用setAttribute来设置样式:

    el.setAttribute("style", "width: 120px; height:120px; background: red;")
    
  2. 使用style属性来设置:

    el.style.width = "120px";
    el.style.color = "red";
    
  3. 使用cssText属性来设置:

    el.style.cssText = "width: 120px;",
    + "height: 120px;",
    + "background: red;",
    
事件操作

事件就是一种在网页元素中的操作行为,例如鼠标事件滚动事件, 键盘事件等!

鼠标事件: 常见的有点击事件滚轮事件,以及鼠标移入移出的事件!

键盘事件: 就是键盘的按键触发事件的一种行为操作!

let btn = document.querySelector(".btn");
btn.onclick = function( e ){
  console.log("触发点击事件!", e)
}

以上就是一个按钮点击事件的案例!

注册事件

注册事件,就是给元素添加事件的方式,一种是传统方式注册,一种是对事件的一个监听!

传统方式注册

  1. 在元素上通过事件绑定属性来注册一个事件:

     <button onclick = "alert("hello")"></button>
    
  2. 在js代码中通过元素对象,获取事件属性并赋值与函数来注册事件:

    el.onclick = function(){
    
    }
    

传统事件注册,只能给元素绑定一个事件,若出现多次绑定的情况,则后面的绑定事件会覆盖之前的绑定!

btn.onclick = function( e ){
  console.log("触发点击事件!", e)
}
btn.onclick = function( e ){
  console.log("我会覆盖之前的事件绑定", e)
}

事件监听器

事件监听器就是通过addEventListener该方法来注册一个事件!

addEventListener: 该方法存在兼容性问题,IE9之前不支持,添加事件需使用attachEvent(非标准,生产环境不推荐使用);

参数:

  • 事件监听的类型(type): click mouseover mouseout等...,这里不需要写 on !

  • 事件监听的回调函数(callback): 当触发事件时,该回调函数被执行!

el.addEventListener('click', function(e){
   console.log(e.target);
})
el.addEventListener('click', function(e){
   console.log(e.target);
})

给某个元素添加事件监听!

这里同一个元素可以添加多个事件监听,且依次执行!

attachEvent事件注册

事件类型,需要补充on语法!

el.attachEvent('onclick', function(e){
   console.log(e.target);
})
删除事件

删除事件 使用该方法 removeEventListener,可以对事件进行解绑和删除!

  1. 传统事件绑定删除方式:

    el.onclick = null;
    
  2. 使用监听器绑定事件后进行解绑

    // 绑定监听的函数
    let bindFunction = functon(){
    
    }
    
    el.addEventListener("click", bindFunction);
    // 解除绑定函数
    el.removeEventListener("click", bindFunction);
    
  3. 使用attchEvent方式绑定的事件进行解绑:

     let bindFunction = functon(){
     
     }
     
     el.attachEvent("onclick", bindFunction);
     // 解除绑定函数
     el.detach("onclick", bindFunction);
    
DOM事件流

事件流就是一个传播过程,当给元素绑定事件时会有以下流程:

捕获阶段: 由根元素子元素的一个过程,直到发现被绑定事件的子元素!

冒泡阶段: 由子元素根元素的一个过程,直到根元素为止!

事件捕获

addEventListerer 第三个参数为Boolean,为true时,则为捕获阶段,为false时或默认时,则为冒泡阶段!

捕获阶段: 事件的执行顺序,会从根元素开始, 从 ,进行事件触发执行!

<div class="parent">
    <div class="sub">sub</div>
</div>
let parent = document.querySelector('.parent');
let sub = document.querySelector('.sub');
parent.addEventListener('click', function(e){
  console.log("parent click");
}, true);
sub.addEventListener('click', function(e){
  console.log("sub click");
},true);
// log parent click
// log sub click

冒泡阶段

冒泡阶段: 事件的执行顺序,会从子元素开始,从,进行事件触发执行!

设置冒泡行为,第三个参数为false时,或者默认不传时,则为冒泡!

let parent = document.querySelector('.parent');
let sub = document.querySelector('.sub');
parent.addEventListener('click', function(e){
  console.log("parent click");
}, false);
sub.addEventListener('click', function(e){
  console.log("sub click");
},false);
// log sub click
// log parent click
事件对象

事件对象(event),就是每一个事件类型触发后产生的一个对象 !

鼠标事件触发后,事件对象会包含该鼠标对象的信息!

键盘事件触发后,事件对象会包含改键盘对象的信息!

el.onclick = function ( event ) {
  console.log("包含了click点击事件的信息", event);
}

event 是一个对象,包含了,当前触发事件的一系列信息!

注意: eventie678里存在兼容问题!

可以通过window.event来获取!

el.onclick = function ( event ) {
  console.log("包含了click点击事件的信息", event || window.event);
}

this 绑定对象 与 el.target对象的区别

this绑定对象,是当前绑定事件元素对象!

el.target 是获取返回触发事件的元素对象信息!

<ul>
  <li>sub</li>
  <li>sub</li>
  <li>sub</li>
  <li>sub</li>
  <li>sub</li>
</ul> 
<script>
    let ul = document.querySelector('ul');
    // this = ul 当前绑定元素
    // el.target = 触发事件的元素 比如我点击了 li 则 li为触发元素的对象!
    ul.addEventListener('click', function(e){
      console.log("ul click", e.target, this);
    })
</script>

el.target 是有兼容问题,在ie678下可以通过 el.srcElement来获取

阻止默认事件

阻止默认事件(preventDefault方法),默认事件就是某个元素在触发事件时,一些默认行为操作!

表单提交 button type="submit",提交后,会默认提交以及刷新浏览器的行为!

<form action="http://www.baidu.com">
  <button type="submit">提交</button>
</form>
<script>
    let btn = document.querySelector('button');
    btn.addEventListener('click', function(e){
      e.preventDefault(); // 阻止 默认提交行为操作 以及浏览器刷新操作!
      console.log('提交'); 
    })
</script>

preventDefault 兼容问题:

ie678请使用: e.returnValue! 这是一个属性不是一个方法!

btn.onclick = function(e){
  // e.preventDefault(); // 兼容问题
  // e.returnValue;
  return false;
}
// 三种方式都可以阻止默认事件
阻止冒泡事件

冒泡事件,从子元素开始,向上层逐个事件触发,为了保证事件触发的单一行,需要进行冒泡行为阻止!

stopPropagation 该方法,可以帮我们阻止事件传播过程!

el.onclick = function(e){
  e.stopPropagation(); // 兼容问题
  e.cancelBubble = true; // 兼容 ie678
}
事件委托

事件委托,就是不在子元素上进行一个一个事件绑定,通过事件传播的行为,将事件绑定到父元素上,来操作子元素!

<ul>
  <li>1. 事件委托</li>
  <li>2. 事件委托</li>
  <li>3. 事件委托</li>
  <li>4. 事件委托</li>
  <li>5. 事件委托</li>
</ul>
<script>
    let ul = document.querySelector('ul');
    ul.addEventListener('click', function(e){
      console.log(e.target.innerText);
    })
</script>

事件对象中常见的属性和方法

鼠标事件

事件类型有很多种,鼠标移入元素 移出元素 点击元素 以及 触发焦点 和 失去焦点事件等类型!

事件类型 事件描述
onclick 鼠标点击左键事件
onmouseenter 鼠标移入自身元素时触发事件,如果自身元素下有子元素,则移入子元素时,不触发,因为没有冒泡!
onmouseleave 鼠标移出事件,与enter相似
onmouseover 鼠标经过该元素时频繁触发事件,如果自身元素下有子元素,则鼠标移入时依旧触发事件!
onmouseout 鼠标移出事件,与over相似
onfocus 鼠标触发焦点事件
onblur 鼠标失去焦点事件
onmousemove 鼠标移动事件
onmouseup 鼠标按键抬起触发事件
onmousedown 鼠标按键按下触发事件
contentmenu 鼠标右键按下触发事件
selectstart 鼠标开始选择时触发事件
ondbclick 鼠标双击时触发事件操作

禁止鼠标右键菜单

document.addEventListener("contextmenu", function(e){
  e.preventDefault();
  console.log("contextmenu", e.target);
})

禁止文字选择

document.addEventListener("selectstart", function(e){
  e.preventDefault();
  console.log("contextmenu", e.target);
})

鼠标事件对象

鼠标事件对象 event , 当鼠标事件触发时,产生的MouseEvent事件对象,包含关于鼠标相关属性信息!

比如,鼠标的坐标位置等相关信息!

属性 描述
e.clientX 当前鼠标指针相对于 body 可视窗口 x 坐标!
e.clientY 当前鼠标指针相对于 body 可视窗口 y 坐标!
e.pageX 当前鼠标指针相对于 document(文档 包含滚动区域x 坐标!
e.pageY 当前鼠标指针相对于 document(文档) 包含滚动区域y 坐标!
e.screenX 当前元素相对于 电脑屏幕 x 坐标!
e.screenY 当前元素相对于 电脑屏幕 y 坐标!
e.offsetX 当前鼠标指针相对于当前元素left距离!
e.offsetY 当前鼠标指针相对于当前元素top距离 !

鼠标事件跟随案例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>鼠标跟随案例</title>
  <style>
    body{
      height: 3000px;
    }
    .cursor-box{
      width: 30px;
      height: 30px;
      background-color: #f00;
      position: relative;
      cursor: pointer;
      box-shadow: 0 0 10px #000;
      border-radius: 20px;
    }
  </style>
</head>
<body>
  <div class="cursor-box"></div>
  <script>
    const cursorBox = document.querySelector('.cursor-box');
    document.addEventListener('mousemove', (event) => {
      cursorBox.style.left = event.pageX + 'px';
      cursorBox.style.top = event.pageY + 'px';
    });
  </script>
</body>
</html>

案例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    *{
      margin: 0;
      padding: 0;
      /* 设置边框盒 外边距, 内容等的宽高自动计算,加上内边距和边框*/
      box-sizing: border-box;
    }
    .container{
      width: 100%;
      height: 100vh;
      border: 1px solid #000;
      display: flex;
      flex-wrap: wrap;
      gap: 10px;
      justify-content: center;
      align-items: center;
    }
    .container div{
      width: 100px;
      height: 100px;
      background-color: sandybrown;
      text-align: center;
      line-height: 100px;
      font-size: 14px;
      font-weight: bold;
      border-radius: 25px;
      transition: all .5s linear;
    }
    .container div:hover{
      transform: scale(1.1);
      cursor: pointer;
    }
    .container div:nth-child(2n){
      background-color: skyblue;
    }
  </style>
</head>
<body>
  <div class="container">
    <div>click</div>
    <div>mouseover</div>
    <div>mouseout</div>
    <div>mousedown</div>
    <div>mouseup</div>
    <div>mousemove</div>
  </div> 
  <div class="test"></div>
  <script>
    let events = ['click', 'mouseover', 'mouseout', 'mousedown', 'mouseup', 'mousemove'];
    let container = document.querySelector('.container');
    let els = document.querySelectorAll('.container div');
    for(let i = 0; i < events.length; i++){
      let event = `on${events[i]}`;
      els[i][event] = function(){
        console.log(event);
      }
    }
  </script>
</body>
</html>
键盘事件
事件 描述
onkeyup 按键抬起时触发事件
onkeydown 按键按下时触发事件 不区分大小写!
onkeypress 按键按下时触发事件 不识别 ctrl shift 箭头,区分大小写!

键盘事件对象

event键盘事件对象,就是当触发键盘行为操作时,会生成一个键盘对象(keybordEvent)

根据keyCode来判断对应的按键值

keyCode 返回一个 ASCLL码值 ,每一个按键,且对应一个相应的值!

表单事件
事件 描述
submit 表单元素提交时,触发该事件!
reset 表单元素被重置时,触发该事件!
change 表单元素发生改变的时候触发该事件!
input 表单元素输入内容时触发该事件!
窗口事件
事件 描述
load 页面以及所有资源加载完毕时触发该事件!
unload 用户离开页面时触发该事件!
scroll 页面滚动时触发该事件!
resize 窗口或者元素大小发生改变时触发该事件!
触摸事件
事件 描述
touchstart 用户开始触摸屏幕时触发。
touchmove 用户触摸屏幕并移动时触发。
touchend 用户停止触摸屏幕时触发
touchcancel 触摸被中断时触发
拖拽事件

拖拽元素时,需要给拖拽元素增加拖拽属性,才可以对元素进行拖拽行为!

draggable="true"

<div id="draggableid" class="drag-box" draggable="true">拖拽元素</div>
<!-- 放置目标区域 -->
<div class="placement-box">请将红色的框拖拽到此处</p>
事件 描述
dragstart 当用户开始拖动元素时触发。此事件通常用于初始化拖拽数据,如设置 dataTransfer 对象的数据类型和数据值
drag 当元素被拖动时持续触发。此事件可以用于实时更新拖拽过程中的状态,例如显示拖拽进度
dragenter 被拖动的元素进入目标元素时触发。此事件用于确定目标元素是否可以接受拖拽的数据
dragover 被拖动的元素在目标元素上移动时持续触发。此事件用于更新拖拽过程中的视觉效果,例如改变目标元素的样式
dragleave 被拖动的元素离开目标元素时触发。此事件用于重置目标元素的样式
drop 用户释放鼠标按钮并完成拖放操作时触发。此事件用于处理拖放的数据,例如将拖动的元素插入到目标元素
dragend 拖拽操作结束时触发,无论是否成功放下。此事件用于清理拖拽过程中的状态,例如重置拖拽元素的样式

拖拽案例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>拖拽事件</title>
    <style>
      div{
        padding: 15px;
      }
      .drag-box {
        width: 100px;
        height: 100px;
        background-color: #f00;
        color: #fff;
        cursor: move;
        border-radius: 10px;
      }
      .placement-box {
        margin-top: 15px;
        height: 300px;
        border-radius: 10px;
        background-color: #4fc3f7;
      }
    </style>
  </head>
  <body>
    <div id="draggableid" class="drag-box" draggable="true">拖拽元素</div>
    <!-- 放置目标区域 -->
    <div class="placement-box">请将红色的框拖拽到此处</p>
    <script>
      const draggable = document.querySelector(".drag-box");
      const placement = document.querySelector(".placement-box");
      // 开始拖拽
      draggable.addEventListener("dragstart", function (event) {
        console.log("开始拖拽",event);
        event.dataTransfer.setData("text/plain", event.target.id);
        event.dataTransfer.effectAllowed = "move";
      });

      // 拖拽过程中持续触发
      draggable.addEventListener("drag", function (event) {
        // console.log("正在拖动...");
      });

      // 进入目标元素时触发
      placement.addEventListener("dragenter", function (event) {
        event.preventDefault();
        console.log("进入目标元素",event);
        event.target.classList.add("dragover");
      });

      // 在目标元素上移动时持续触发
      placement.addEventListener("dragover", function (event) {
        event.preventDefault();
        // console.log("在目标元素上移动",event);
      });

      // 离开目标元素时触发
      draggable.addEventListener("dragleave", function (event) {
        event.target.classList.remove("dragover");
        // console.log("离开目标元素");
      });

      // 放下元素时触发
      placement.addEventListener("drop", function (event) {
        event.preventDefault();
        console.log("放下元素",this, event.target);
        const id = event.dataTransfer.getData("text/plain");
        const draggableElement = document.getElementById(id);
        event.target.appendChild(draggableElement);
        event.target.classList.remove("dragover");
      });

      // 拖拽结束时触发
      draggable.addEventListener("dragend", function (event) {
        console.log("拖拽结束");
      });
    </script>
  </body>
</html>
  1. 事件顺序: 拖拽事件有特定的触发顺序,通常是 dragstart -> drag -> dragenter -> dragover -> drop -> dragend
  2. 阻止默认行为: 在 dragoverdrop 事件中,需要调用 event.preventDefault()阻止默认的拖拽行为(如打开文件)。
  3. 数据传递: 在 dragstart 事件中,可以使用 dataTransfer.setData 方法传递数据,在 drop 事件中可以使用 dataTransfer.getData 方法获取数据
  4. 浏览器兼容性: 虽然大多数现代浏览器

BOM

BOM(浏览器对象模型) 主要是浏览器内一些相关的api操作!

例如: 对 浏览器窗口对一些操作,如果页面跳转历史记录(history), location, navgitor,本地存储(sessionStorage localStorage cookie)等!

BOM缺乏标准规范JS的标准规范是由ECMA实现的,DOM 的标准规范是由W3C实现的,而BOM最早是由Netscape浏览器标准的一部分!

BOM浏览器厂商自己定义的标准,兼容性比较差!

window全局对象

浏览器中,window是一个顶层对象也是全局对象,可以通过该对象来访问一些基本的webapi!

所有定义在全局的变量和函数,都会被绑定在顶层对象window中!

window.alert(123) // alert(123) window可以省略!

注意: window对象中有一个特殊的属性window.name ,该name属性,是在一开始初始化时,就已经存在的属性,且默认值为" "!

window常见的事件
页面加载事件
  1. onload: 当页面所有资源以及dom元素加载完毕时调用该回调函数!

    window.onload = function(){
      console.log("页面所有资源以及dom元素以加载完毕!")
    }
    window.addListenerEvent("load", function(){
     console.log("页面所有资源以及dom元素以加载完毕!")
    })
    
  2. DOMContentLoaded: 当所有dom元素加载完毕时调用该回调函数,不包含图片 flash 以及css样式表资源!

    • ie9+ 支持!
    • 好处是,不用等待其他外部引用资源加载完毕时,处理逻辑操作,当外部资源体积大且加载缓慢,则只需要处理dom时可以使用该事件!
    document.addListenerEvent("DOMContentLoaded", function(){
      console.log("所有的 dom 元素加载完毕时 调用回调函数, 不包含外部资源!")
    })
    
  3. pageshow: 在页面显示时加载事件,无论页面是否缓存!

    • a 连接跳转 返回当前页时触发事件
    • 刷新页面或者强制刷新触发事件
    • pageshow 会在 load 事件后触发
    • e.persisted 可以判断该是否在缓存中的页面中触发!
    • 火狐浏览器,在跳转页面并通过浏览记录返回时,会对原有的页面进行缓存,缓存后的页面不会触发onload事件!
    window.addListenerEvent("pageshow", function(e){
      console.log("在 onload 事件后触发,无论页面是否有缓存!")
    })
    
窗口以及元素大小监听事件

resize事件,窗口或者元素大小发生变化时,触发该事件!

window.onresize = function(e){
   console.log("窗口大小发生改变!")
}
定时器

定时器就是根据指定时间执行一段逻辑!

setTimeOut

根据指定时间,在时间结束后执行回调函数!

单位为毫秒!

第一个参数,回调函数,第二个参数,指定的时间单位(毫秒)!

setTimeout(function(){
  console.log("3秒钟会执行此回调函数!")
},3000)
setInterval

根据指定时间循环间隔执行回调函数!

单位为毫秒!

第一个参数,回调函数,第二个参数,指定的时间单位(毫秒)!

setInterval(function(){
  console.log("每此间隔3秒钟执行回调函数!")
},3000)
清除定时器

清除定时器,在一定时机内,将定时器进行清除结束回调函数的执行行为!

clearInterval清除定时器(Interval)的一个函数,且参数为定时器所返回的id值!

clearTimeout清除定时器(Timeout)的一个函数,且参数为定时器所返回的id值!

let startTime = Date.now();
setTimeout(()=>{
  let endTime = Date.now();
  console.log(`执行时间:${endTime - startTime}ms`);
  // 5s中清除定时器setInterval
  clearInterval(intervalId);
},5000)

let intervalId = setInterval(() => {
  console.log("每次间隔2s");
}, 2000);
倒计时案例
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>定时器</title>
    <style>
      .timer-box {
        height: 90vh;
        display: flex;
        justify-content: center;
        align-items: center;
        gap: 10px;
      }
      .timer-box div {
        width: 50px;
        height: 50px;
        background-color: rgba(0, 0, 0, 0.6);
        text-align: center;
        line-height: 50px;
        font-weight: bold;
        color: #fff;
      }
    </style>
  </head>
  <body>
    <center><h2>倒计时</h2></center>
    <div class="timer-box">
      <div class="hour">1</div>
      <div class="minute">2</div>
      <div class="second">3</div>
    </div>
    <script>
      const hour = document.querySelector(".hour");
      const minute = document.querySelector(".minute");
      const second = document.querySelector(".second");
      const futureTime = new Date("2024-11-27 15:00:00").getTime();
      
      // 防止一开始页面加载时,时间显示为空白
      countdown();

      let timerId = setInterval(countdown, 1000); // 每秒执行一次倒计时

      // 时间单位换算
      // 1秒 = 1000毫秒
      // 1分钟 = 60秒 * 1000毫秒
      // 1小时 = 60分钟 * 60秒 * 1000毫秒
      // 1天 = 24小时 * 60分钟 * 60秒 * 1000毫秒
      // 1周 = 7天 * 24小时 * 60分钟 * 60秒 * 1000毫秒
      // 1月 = 30天 * 7天 * 24小时 * 60分钟 * 60秒 * 1000毫秒
      // 1年 = 365天 * 30天 * 7天 * 24小时 * 60分钟 * 60秒 * 1000毫秒
      function countdown() {
        const nowTime = new Date().getTime();
        const gapTime = (futureTime - nowTime) / 1000; // 未来时间 - 现在时间 = 剩余时间
        // 3600 = 1000 * 60秒 * 60分 1小时
        const hourGap = Math.floor(gapTime / 3600); // 剩余小时
        const minuteGap = Math.floor((gapTime - hourGap * 3600) / 60); // 剩余分钟
        const secondGap = Math.floor(gapTime - hourGap * 3600 - minuteGap * 60); // 剩余秒
        hour.textContent = hourGap < 10 ? "0" + hourGap : hourGap;
        minute.textContent = minuteGap < 10 ? "0" + minuteGap : minuteGap;
        second.textContent = secondGap < 10 ? "0" + secondGap : secondGap;
        if (gapTime <= 0) {
          clearInterval(timerId);
          hour.textContent = "00";
          minute.textContent = "00";
          second.textContent = "00";
        }
      }
    </script>
  </body>
</html>
时间单位换算

1秒 = 1000毫秒

1分钟 = 60秒 * 1000毫秒

1小时 = 60分钟 * 60秒 * 1000毫秒

1天 = 24小时 * 60分钟 * 60秒 * 1000毫秒

1周 = 7天 * 24小时 * 60分钟 * 60秒 * 1000毫秒

1月 = 30天 * 7天 * 24小时 * 60分钟 * 60秒 * 1000毫秒

1年 = 365天 * 30天 * 7天 * 24小时 * 60分钟 * 60秒 * 1000毫秒

function countdown() {
    const nowTime = new Date().getTime();
    const gapTime = futureTime - nowTime; // 未来时间 - 现在时间 = 剩余时间
    // 3600 = 1000 * 60秒 * 60分 1小时
    const yearGap = Math.floor(gapTime / (1000 * 60 * 60 * 24 * 365));
    const monthGap = Math.floor(gapTime / (1000 * 60 * 60 * 24 * 30));
    const weekGap = Math.floor(gapTime / (1000 * 60 * 60 * 24 * 7));
    const dayGap = Math.floor(gapTime / (1000 * 60 * 60 * 24));
    const hourGap = Math.floor((gapTime / (1000 * 60 * 60)) % 24); // 剩余小时
    const minuteGap = Math.floor((gapTime / 1000 / 60) % 60); // 剩余分钟
    const secondGap = Math.floor((gapTime / 1000) % 60); // 剩余秒
    day.textContent = dayGap;
    hour.textContent = hourGap < 10 ? "0" + hourGap : hourGap;
    minute.textContent = minuteGap < 10 ? "0" + minuteGap : minuteGap;
    second.textContent = secondGap < 10 ? "0" + secondGap : secondGap;
    if (gapTime <= 0) {
      clearInterval(timerId);
      day.textContent = "00";
      hour.textContent = "00";
      minute.textContent = "00";
      second.textContent = "00";
    }
  }

小时: parseInt(时间戳 / 60 / 60 % 24)

分钟: parseInt(时间戳 / 60 % 60)

秒: parseInt(时间戳 % 60)

js执行机制

js 语言单线程语言,在代码执行只能同时处理一件事情!

js 代码中,如果遇到代码运行比较耗时的逻辑,按照单线程的管理,只有等待耗时任务完成时,才能执行下一段逻辑!

因此,为此解决问题,则执行代码逻辑分为,同步代码执行,和异步代码执行!

同步任务(代码)

同步任务: 代码依次执行,只有等待上一步代码执行完毕后才会执行下一步代码,且不能同时执行其他任务代码!

异步任务(代码)

异步任务: 代码依次执行,但是执行代码时,遇到异步任务代码,会将异步任务回调函数存放到任务队列当中,不影响其它代码执行,当需要异步任务代码执行时,会从队列中按次序获取相应的回调函数并执行!

定时器(setTimeout setInterval)事件(click mousemove...) 以及网络请求(ajax)等都属于异步任务!

执行栈: 此处,会执行同步任务代码!

console.log(1);

setTimeout(fun, 100);

console.log(2);

任务队列: 此处,会存放异步任务回调函数!

  1. fun 回调函数 | console.log(3)

执行顺序,首先执行执行栈中的同步任务,当遇到异步任务时,会将回调函数放入任务队列当中,然后继续执行同步任务,至到同步任务执行完毕后,才会从任务队列中执行异步任务回调函数!

事件循环

执行代码时,遇到异步代码,会将异步回调函数存放到任务队列当中,但是在存放过程中,有一个异步任务队列处理程序,主要来判断,该异步任务回调函数是否需要存放到任务队列当中!

执行栈 -> 异步任务处理 -> 任务队列(执行回调函数后调用) -> 执行栈

如上图,点击事件,经过异步任务队列处理程序不会立即存放到任务队列当中,只有点击的时候,会将异步回调函数添加到任务队列中并执行异步回调函数,执行完毕后,并清空任务队列中的回调函数,待下次新的异步回调函数进入!

执行栈中的同步代码执行完毕时,执行栈反复查看异步任务队列中,是否有可执行的异步回调函数来执行!

location

location 主要用来处理和解析网页域名路径(URL)!

域名(URL)组成

http://127.0.0.1:5500/dom.html?id=1234591283

协议:// ip 地址 : port(端口)?query(查询参数)

常见的属性
属性 描述
location.href 获取设置整个URL路径
location.host 返回主机的域名
location.part 返回端口号,未写返回空的字符串
location.pathname 返回路径
location.search 返回参数
location.hash 返回 hash 路径 # 号后面的锚点
常见的方法
方法 描述
location.assign(url) href 一样,可以跳转页面,会有浏览历史记录!
location.replace(url) 替换当前页面,不会被浏览历史记录!
location.reload(true) 重新加载页面,相当于 f5 刷新,如果参数true, 则为强制刷新 ctrl+f5

navigator包含了浏览器的一些基本信息,常用的属性有userAgent, 主要用来获取当前平台的信息,可以判断是PC 还是 移动端H5

history

history 对象,主要用来和浏览器历史记录进行交互,可以实现浏览记录前进后退跳转的基本功能 !

  • back(): 后退功能!

  • forword(): 前进功能!

  • go(-1): 跳转功能,参数如果是1这是前进功能,如果是-1这是后退功能!

本地存储

本地存储是一种存储数据的一种方式,平时我们的数据刷新浏览器或者关闭浏览器以及标签页时,数据都会消失,通过

本地存储的方式,将数据存储到浏览器本地内存中,这样就不会因刷新或者关闭的情况下丢失数据了!

本地存储,有三种方式,sessionStorage(会话存储) localStorage(本地存储) cookie !

特性 sessionStorage localStorage cooKie
存储容量 5M 5M 4kb
声明周期 会话窗口关闭(浏览器或标签页) 持久存储,至到手动清除 设置过期时间(持久存储),会话结束(cookie)
与服务器交互 不会自动发送到服务器 不会自动发送到服务器 每次http请求都会自动发送到服务器
用途 主要用于在单个会话中存储临时数据 主要用于在客户端存储大量数据,如用户设置、缓存数据等 主要用于存储用户状态信息,如登录状态、用户偏好设置
安全性 不支持 HttpOnly,安全性较低 不支持 HttpOnly,安全性较低 可以设置 HttpOnlySecure 属性,增加安全性
API 提供简单的 setItemgetItem 方法 提供简单的 setItem 和 getItem 方法 需要使用 document.cookie 进行操作
sessonStorage

会话存储,存储大小5M,不支持 httpOnly ,浏览器窗口关闭标签页关闭会话结束!

api 参数 描述
sessionStorage.setItem() setItem(key,value) 设置存储数据,key为键value为值!
sessionStorage.getItem() getItem(key) 根据key获取存储数据,key为键!
sessionStorage.removeItem() removeItem(key) 根据key删除存储数据,key为键!
sessionStorage.clear() clear() 清空存储数据!
localStorage

本地存储,存储大小5M(因每个浏览器不一致,大小或更多,且通常最大为5M,最小为4M),不支持 httpOnly手动清除!

api 参数 描述
localStorage.setItem() setItem(key,value) 设置存储数据,key为键value为值!
localStorage.getItem() getItem(key) 根据key获取存储数据,key为键!
localStorage.removeItem() removeItem(key) 根据key删除存储数据,key为键!
localStorage.clear() clear() 清空存储数据!

Cookie 是一种小型文本文件,由服务器发送到用户的浏览器,并在用户下次访问同一网站时由浏览器自动发送回服务器Cookie 主要用于存储用户状态信息,如登录状态、用户偏好设置等。

存储方式

每个 Cookie 都有名称、值、路径、域、过期时间等属性。

cookie封装

设置 Cookie

function setCookie(name, value, days) {
    let expires = "";
    if (days) {
        const date = new Date();
        date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
        expires = "; expires=" + date.toUTCString();
    }
    document.cookie = name + "=" + (value || "")  + expires + "; path=/";
}

setCookie('username', 'JohnDoe', 7); // 设置一个名为 'username' 的 Cookie,过期时间为 7 天

获取cookie

function getCookie(name) {
    const nameEQ = name + "=";
    const ca = document.cookie.split(';');
    for(let i=0;i < ca.length;i++) {
        let c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
    }
    return null;
}

const username = getCookie('username');
console.log(username); // 输出: JohnDoe

删除cookie

function deleteCookie(name) {   
    document.cookie = name + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}

deleteCookie('username'); // 删除名为 'username' 的 Cookie