Skip to content

JavaScript(ES5)

JavaScript基础

1.JS是运行在客户端的一种解释型(非编译型)编程语言。

2.JS作用:

​ 用来完成前后端交互,增加用户体验的一些逻辑实现。

3.一个网站是由三部分组成:

​ 结构(THML/HTML5),表现(CSS/CSS3),行为(JavaScript)

4.前端开发的核心是JavaScript

5.JavaScript的组成:

1)ECMAScript(标准:ECMA-262):基础语言部分(基础,面向对象等)
2)DOM(标准:W3C):节点操作
3)BOM(无标准):浏览器操作

6.JS的特点

1)松散性

​ JS中的变量没有一个明确的类型,也叫弱类型的语言(允许将一块内存看作多种类型)。

2)对象属性

​ 对象的属性也可以映射为任意的数据。

3)继承机制

​ JS是基于原型继承的。

7.使用JavaScript

1)使用script标签(只能被当前html文件使用)
<script type="text/javascript" defer="defer" charset="utf-8">
    alert('这是我的第一个JS程序');//alert表示弹出一个警告框
</script>

​ defer:表示所有DOM元素加载完成后,再执行JS代码(现在开发一般不需要)

​ charset:字符编码(主要解决汉字乱码问题)(现在开发一般不需要)

​ 注意:

​ a.script标签可以写在网页代码的任意地方,因为JS是同步执行(从上往下)的,但为了避免JS阻塞和影响操作DOM,最好写在body后 。(JS报错后面内容将无法显示执行)

​ b.如果要输出script结束标签时必须拆开写

alert('</s'+'ript>');
2)在a标签的href中写JS代码(实际开发中不建议这样使用,因为会影响性能)
<a href="javascript:alert('你好呀 王大头')">点击我</a>
    <a href="javascript:var a=10,b=20;alert('结果为:'+(a+b))">10+20=</a>
    <a href="javascript:close();">&times;</a>
    <a href="javascript:void(1)">test</a> <!--阻止默认行为-->
3)用script标签引入外部js文件(可以被多个html文件共用)
<script src="" data-missing="01_test.js" type="text/javascript"></script>

​ 说明:

​ src:表示引入的外部的JS文件的路径和文件名(只能用于引入JS文件)

​ async:异步加载JS代码(在加载DOM元素(html元素)的同时可以运行JS代码)用于外部加载 的JS代码。

8.标识符

​ 定义规则:

​ 1)第一个字符必须是字母,下划线,美元符号$。

​ 2)其他字符可以是字母,下划线,美元符或数字。

​ 3)不能把关键字,保留字,true,false和null作为标识符。

​ 4)不能使用特殊字符,空格,一般采用小驼峰写法。

​ 关键字:

image-20230817194458856

​ 保留字:

image-20230817194522028

​ 注意:在JS中,任何地方是严格区分大小写的!!

9.注释

​ 注释在浏览器中不会被解析执行,仅起说明作用。

1)单行注释(ctrl+/)

​ //注释内容 一般用于对当前行的代码进行说明

2)多行注释(ctrl+shift+/)

​ /*

​ 注释内容

​ ........

​ */ 也叫做块注释,一般用于对下边的代码进行整体说明,且说明内容可能较多

​ 说明:在项目开发中要习惯写注释,便于后期项目维护。

10.常量

​ 常量也叫直接量或字面量,在程序中给出具体的数据。

​ 常量是不可以改变的。

​ 如:100,true,‘abc’,null,undfeined等

11.变量

​ 变量就是在内存中开辟一段空间用于存放某个数据。

​ 变量必须要有变量名,变量名必须遵循标识符的命名规范。

​ 定义:

​ 1)只定义变量

​ var x;

​ var a,b,c;

​ 2)定义变量且赋值

​ var x1 = true;

​ var y1 = 10,y2 = null;

​ 说明:

​ 1)定义变量时不需要给出数据类型。(松散式语言特点)。

​ 2)变量可以重复定义,后面覆盖前面。

​ 3)不用var定义变量也可以,默认是为window对象添加了属性。

​ name = ‘张三’;//相当于 window.name= '张三';

​ 4)定义的变量如果没有赋值,系统将自动赋默认值为'undefined'

​ 5)一条语句可以以分号结束也可以不加分号;如果是多条语句写在同一行上,必须用分号隔开

​ 6)在书写代码时除了字符串中可以使用中文标点之外,其他任意地方只能用英文标点符号。

12.数据类型

​ 数据类型指的是在内存存储的方式。

​ 分为:

1)基本数据类型

number:数值型

​ 用来表示一个数字,通常可用作进行算术运算

​ 分为整形和浮点型(小数位可以浮动)

​ 100(十进制)

​ 0O123(八进制)

​ 0Xae12(十六进制)

​ isNaN();用来判断是不是 不是一个数。(true:不是一个数,false:是一个数)NaN(Not a Number)

string:字符型

​ 用引号(单双引号都可以,没有区别)引起来的一串字符(可以是数字,字母,标点符号,汉字等)

'abc'  "abc"  "a\''bc" // \转义字符\' \" 在字符串中输出英文的单双引号

boolean:布尔型

​ 表示真(ture)或假(flase)。

null:空

​ 表示一个空对象的地址指针指向的为空。

undefine:未定义

​ 表示定义了一个变量,但如果没有给这个变量赋值,系统将自动赋值为undefined

​ tips: 类型转换

	var score = parseFloat(prompt('请输入成绩'));
		//parseInt():转换为int类型
		//parseFloat():转换为float类型
2)引用数据类型

object:对象型

​ 用来声明或存储一个对象(对象,数组,函数,正则,字符,数值等)

13.运算符

1)算数运算符

​ + - * / %(求余/取摸) ++(自增) --(自减)

​ 注意:

​ a.结果可以为小数

​ b.取模结果符号取决于被除数与被除数符号相同(被除数%除数)

​ c.自增和自减分为前置++/--(先++/--) 和 后置++/--(后++/--)

2)字符串运算符
	+ :用于实现字符串拼接

​ 其他数据类型与字符串进行 + 运算,会自动转化为字符型,再拼接

​ .length:输出字符串长度

3)关系运算符

​ > < >= <= == === != !==

	==,!=:只比较值,不比较数据类型
    ===,!==:即比较值,又比较类型,有一个不相等返回flase

​ 返回的结果只能时true或flase

比较方法:

​ a.数值比较,是比较其大小

​ b.字符比较,是比较ASCII码值的大小(按位比较,从前往后)

​ 0:48,A :65 a:97,回车:13 ESC:27

​ c.汉字比较,是比较其Unicode编码值的大小

​ .charCodeAt(n) 求字符串中下标为n的字符的Unicode编码

console.log("张三".charCodeAt(0));//张的Unicode编码  24352
console.log("abc".charCodeAt(0));//a的Unicode编码 97

​ 比较:部分符号<数字<大写字母<小写字母<汉字

​ true和flase与数字进行比较时会自动转换true 1,flase 0。true和flase与字符串比较全为flase。

4)逻辑运算符

​ !,&&,|| 返回值一般为true或flase,也可能返回其他值。

: 逻辑非(取反)(单目/一元运算符)

​ !true->false

​ 注意:

​ 非空字符串为true

​ 对象或数组不管有没有值,都转换为true

&& :逻辑与(双目/二元运算符)

​ 只要有一个操作数为flase,结果为flase

​ 注意:

​ 如果两个中任意一个操作数非逻辑值(boolean),第一个操作数的结果为true时,返回第二个操作数的值;

​ 第一个操作数结果为false时,返回第一个操作数的值。

||: 逻辑或(双目/二元运算符)

​ 只要有一个操作数为true,结果为true

​ 注意:

​ 如果两个中任意一个操作数非逻辑值(boolean),第一个操作数的结果为true时,返回第一个操作数的值;

​ 第一个操作数结果为false时,返回第二个操作数的值。

短路运算:

​ a.&&运算时,如果第一个操作数为flase时,不需要计算第二个操作数,结果返回flase或其他值。

​ b.||运算时,如果第一个操作数为true时,不需要计算第二个操作数,结果返回true或其他值。

​ 单目运算:只有一个操作数 eg:!true;

​ 双目运算:有两个操作数 eg:true&&false;

5)位运算符(以二进制的方式进行运算)

​ 暂时不讲

6)三目运算符(条件运算符)

​ 语法:

​ 表达式1?表达式2:表达式3

​ 如果表达式1成立,返回表达式2的结果,如果不成立返回表达式3的结果。

​ tips:

​ 三目语句相当于if语句中的双分支结构。如果表达式2或表达式3较为复杂,建议用if语句或switch语句实现

14.流程控制

JS是一门既面向过程,也是面向对象的解释型编程语言。

​ 面向过程:按照代码书写console.log(y);的顺序依次执行(OOP)。

​ JS也是一门结构性语言。

​ JS的结构分为顺序结构,分支(条件/选择)结构和循环结构。

​ 顺序结构:按照代码的书写顺序依次执行,一般包含初始化,赋值,输入/输出等语句。

​ 条件结构:用if或switch语句实现,其中的代码的执行是有条件选择执行的。

​ 循环结构:某部分代码在指定的条件按范围内反复执行,用for/for...in/forEach/while/do....while语句实现。

1)条件结构

if语句

​ a.单分支

​ 语法:

​ if(条件)语句;

​ 或

​ if(条件){

​ 语句组;

​ }

​ 如果条件成立,将执行语句/语句组;条件不成立,执行if的下一条语句。

​ b.双分支

​ 语法:

​ if(条件)语句1;else 语句2;

​ 或

​ if(条件){

​ 语句组1;

​ }

​ else {

​ 语句组2;

​ }

​ 如果条件成立,将执行语句1/语句组1;条件不成立,执行语句2/语句组2。

​ 注意:else表示“否则”的意思,后不能写条件。

​ c.多分支(三分支及以上的)

​ 多分支实际上是单分支和双分支的嵌套

​ 语法:

​ if(条件1){

​ if(条件2){

​ if(条件3){

​ 语句或语句组;

​ }

​ }

​ }

​ 或

​ if(条件1){

​ 语句组1;

​ }

​ else {

​ if(条件2){

​ 语句组1;

​ }

​ }

​ 或

​ if(条件1){

​ 语句组1;

​ }else if(条件2){

​ 语句组2;

​ }else if(条件3){

​ 语句组3;

​ }

​ ......

​ else{

​ 语句组n;

​ }

​ 如果条件1不成立,则判断条件2,条件2不成立,则判断条件3......直到成立,若都不成立执行else内的代码。

​ 可以有多个else if 但只能有一个else

switch语句

​ 语法:

​ switch(表达式){

​ case 数值1:语句(组);[break;]

​ case 数值2:语句(组);[break;]

​ case 数值3:语句(组);[break;]

​ .........

​ default:语句(组)

​ }

​ eg:

 //需求:根据学生的成绩情况给定等级
      var score = parseFloat(prompt("请输入成绩")); //parseInt():将字符型转换为int类型
      //var score = prompt('请输入成绩');
      switch (
        Math.floor(score / 10) //Match.floor向下取整
      ) {
        case 9:
        case 8:
          console.log("优秀");
          break;
        case 7:
          console.log("良好");
          break;
        case 6:
          console.log("及格");
          break;
        case 5:
        case 4:
        case 3:
        case 2:
        case 1:
        case 0:
          console.log("不及格");
          break;
        default:
          console.log("输入成绩有误");
      }

​ 先判断条件,如果表达式的结果为case后面的某个对应的值,将执行后面所对应的语句或语句(组),如果语句后有break,将中止该情况语句,如果没有break,继续执行后面的语句,直到遇到break为止;如果条件都不满足将自动执行default后面的语句。

​ swich与if的区别:

​ switch一般用于能获取结果的简单条件的判断,而if一般用在较为复杂的条件判断。

​ if能实现的条件判断,switch不一定能实现,switch能实现的条件判断,if也一定能。(if用途更为广泛)

​ 如果switch和if都能用的情况下,switch会更简洁一些。

2)循环结构

a.计数循环 (for)

​ 语法:

​ for([变量初始值];[条件];[步长/循环增量]){

​ [循环体;]

​ [continue;] 结束本次循环开始下一次循环

​ [break;] 结束循环

​ }

​ 说明:

​ 先执行变量初始值,再判断条件,如果条件成立,执行循环体,再计算步长,再 判断条件.......直到条件不成立退出循环

​ 该循环的次数是可以计算出来的:

​ 循环次数=[(终值-初值)/步长]+1

b.当型循环(while)

​ 语法:

​ while(条件){

​ [循环体;]

​ [continue;] 结束本次循环

​ [break;] 结束循环

​ }

​ 说明:

​ 当条件成立时,执行循环体,反之结束循环

​ 先判断条件,再执行循环体。

c.直到型循环(do......while)

​ 语法:

​ do{

​ [循环体;]

​ [continue;] 结束本次循环

​ [break;] 结束循环

​ }while(条件);

​ 说明:

​ 先执行循环体,再判断条件,直到条件不成立跳出循环。

​ 无论条件是否成立都会执行一次循环。

​ do....while与while循环的区别:

​ 当条件一次都不成立时,do....while循环至少会执行一次,while循环一次也不会执行

d.数组和对象遍历(后面再讲)

JavaScript高级

1.函数

​ 函数就是将具有一定功能的一段JS代码的封装,可以在程序的多个地方被反复调用。

1)定义函数

​ 格式一:

​ function 函数名([形参列表]){

​ 函数体;

​ [return <表达式>;]

​ }

​ 格式二:

​ var 变量名 = function ([形参列表]){

​ 函数体;

​ [return <表达式>;]

​ }

​ 格式三:

​ ;(function ([形参列表]){

​ 函数体;

​ [return <表达式>;]

​ })([实参列表]);

​ 上面的函数叫立即执行表达式(IIFE),它会自动调用自身,不会被其他地方调用,一般用于JS库或者JS插件的封装或闭包处理。

2)函数调用

​ 函数名([实参列表]);

​ 如果不传参数括号也不能省略。

​ 参数个数匹配:实参个数超过形参个数,将实参个数依次传给形参

​ 参数个数匹配:形参个数超过实参个数,将实参个数依次传给形参如果形参没有与实参匹配的值那么形参的值默认为undefined

​ tips:

​ 函数不会自动执行(IIFE(立即行函数表达式)除外),必须通过调用才能执行。

3)return

​ 函数可以通过return返回结果,如果return没有返回结果,表示结束函数的调用,且返回调用处。

4)arguments对象(重要)

​ 返回实参列表的一个伪数组。

​ 一般用在不确定传过来的实参的个数的情况下。

<script>
      function sum() {
        console.log(arguments);//返回实参列表的一个伪数组。
        /*console.log(arguments.length); //获取实参个数
        console.log(arguments[0]); //获取第一个实参*/
        var result = 0;
        for (var i = 0; i < arguments.length; i++) {
          //console.log(arguments[i]);//通过arguments实现累加和
          result += arguments[i];
        }
        return result;
      }

      console.log(sum(1, 2, 3));
      console.log(sum(1, 2, 3, 4));
</script>

2.对象(object)

​ 对象,其实是一种类型,即引用类型,用于将数据和功能组织在一起。

​ 对象由属性和方法组成,通常用键值对定义。

1)对象定义

a)new构建

​ new Object();

  //1)new构建
      //a)创建一个空的对象
      var obj1 = new Object();
      //对空对象赋值  对象名.属性=值;对象名.方法名=function(){}
      obj1.name = "张三";
      obj1.age = 16;
      //方法
      obj1.fn = function () {
        console.log(11111);
      };
      console.log(obj1);
      console.log(obj1.name);
      obj1.fn();

      //b)创建对象的同时并对该对象初始化处理 属性名:值, 方法名:function(){},
      var obj2 = new Object({
        name: "李四",
        age: 13,
        fn2: function () {
          console.log("我是对象里面的方法2");
        },
      });
      console.log(obj2);

b)字面量定义(常用)

​ var obj = {

​ key:value,//属性

​ fn:function(){//方法

​ ........

​ }

​ }

 //2)字面量定义
      //创建一个空对象
      var obj3 = {}; 
      obj3.name = "王麻子";
      obj3.age = 17;
      console.log(obj3);

      //创建对象的同时并对该对象进行初始化处理
      var obj4 = {
        name: "王五",
        age: 17,
        fn3: function () {
          console.log("我是对象里面的方法3");
        },
        fn4: function () {
          console.log("我是对象里面的方法4");
        },
      };
      console.log(obj4);
      obj4.fn4();
2)对象引用

a)对象名.属性名(常用)

​ 对象名.方法名([实参列表])

b)对象名[' 属性名' ]

​ 对象名[ 方法名 ]

​ 注意:

​ 当属性名为变量时必须用 [ ]

var obj = {};
str = 'name';
obj.str = '张三';//错误
obj[str]='张三'; //相当于 obj[name]=‘张三’;

​ tips:

​ 单引号里面不能嵌套单引号,双引号里面不能嵌套双引号。单引号里面可以嵌套双引号,双引号里面可以嵌套单引号。

3.数组(Array)

​ 数组将一组数据组合到一起,并存入到一个变量中,数组是有序排列的,占用一段连续的内存空间。

​ 一个数组可以存储不同类型的数据。在项目开发中,在一个数组中尽量存放同一类数据

1)定义数组

a.new

​ new Arry([值列表])

 //a.new
      var arr1 = new Array();
      //创建了一个空数组,这里的arr1为数组名,存储的是该数组的首地址。
      arr1.push(1); //数组名.push() 向数组中添加数据
      arr1.push("hello");
      arr1.push(true);
      for (var i = 0; i < 10; i++) {
        arr1.push(i);
      }
      console.log(arr1);
      //创建一个数组,同时对该数组初始化
      var arr2 = new Array(123, false, null, "hello");
      console.log(arr2);

b.字面量创建(常用)

​ var arr = [值列表]

​ 数组名.push() 向数组中添加数据

 //b.字面量创建
      //创建了一个空数组,这里的arr1为数组名,存储的是该数组的首地址。
      var arr3 = [];
      arr3.push(123); //向数组中添加数据
      arr3.push(false);
      arr3.push("aaaa");
      console.log(arr3);

      var arr4 = [1, 2, 3, false , "bbb"];//定义数组时对其进行初始化
      console.log(arr4);
2)获取数组元素值

​ 数组名 [ 下标 ] //下标可以是一个数值型变量,也可以是一个表达式或函数或变量,下标从0开始。

    var arr1 = [123, true, "haha"];//创建一个数组
    console.log(arr1);//获取整个数组的内容
    console.log(arr1[0]);
    console.log(arr1[2]);
    console.log(arr1[true+1]);
3)遍历数组

​ 一维数组用一个循环实现;二维数组必须用双重循环实现。

​ a.for

​ b.for.....in //推荐写法

​ c.forEach()

 //一维数组遍历
 var arr1 = [1, 2, 3, 4, 5];
      //for循环
      for (var i = 0; i < arr1.length; i++) {
          console.log(arr1[i]);
      }
      //for in循环
      for(var i in arr1){
        console.log(arr1[i]);
      }
      //forEach
      arr1.forEach (function(value,index){
        console.log(index ,value)
      })
 //二维数组遍历
      var arr2 = [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9],
      ];
      //for
      for (var i = 0; i < arr2.length; i++) {
        //先遍历行
        console.log(arr2[i]);
        //再遍历列
        for (var j = 0; j < arr2[i].length; j++) {
          console.log(arr2[i][j]);
        }
      }
      //for in
      for(var i in arr2){
        for(var j in arr2[i]){
          console.log(arr2[i][j]);
        }
      }
      //forEach
      arr2.forEach(function(v){
        v.forEach(function(v1){
          console.log(v1)
        })
      })
4)数组的属性和方法

a.length属性:获取数组的长度

b.方法

Ⅰ)push()

​ 向数组中添加数据到数组的最后。(无返回值)

	  arr.push(10);
      arr.push("hello");
      //arr.push([1,2,3]);;

Ⅱ)pop()

​ 删除数组最后一个元素。(无返回值)

      arr.pop(); //每次只能删除最后一个元素
      //arr = []//删除全部元素 将数组置空

Ⅲ)unshift()

​ 向数组头部添加数据(无返回值)

      arr.unshift(0);
      arr.unshift(-1, 0);
      arr.unshift(["a", "b"]);

Ⅳ)shift()

​ 删除数组的第一个元素(无返回值)

      arr.shift();

Ⅴ)concat()

​ 将两个或多个数组组合成一个数组 (返回拼接后的数组)

 	  var arr  = 
 	  var arr2 = ["abc", "123", 0];
      var arr3 = [10, 11, 12];
      var arr4 = arr.concat(arr2, arr3); //可以传多个参数按次序进行连接

Ⅵ)reverse()

​ 对数组进行倒序处理 (无返回值)

	arr.reverse();
    console.log(arr);

Ⅶ)join()

​ 将数组转换为字符串 (有返回值)

      var str = arr.join();
      console.log(typeof arr);//object
      console.log(typeof str);//string
      console.log(str);
      var str = arr.join("@"); //指定字符串拼接的符号
      var str = arr.join("");

​ **Ⅷ)splice()**常用

​ 删除,修改或向数组中添加数据。(无返回值)

javascript
	  var arr1 = [1, 2, 3, 4,5,7];
      console.log(arr1);
      //1.删除元素
      var arr2 = arr1.splice(2, 2);
      //splice(起始位置, 删除个数)从下标为2的位置开始删除,删除2个元素
      console.log(arr2);//返回被删除的元素
      console.log(arr1);//arr1删除后剩余的元素

    //2.修改数据
        var arr2 = arr1.splice(1,2,'aaa','bbb','cccc');
        //splice(起始位置,替换个数,替换内容)
        console.log(arr1);
        console.log(arr2);//被替换的元素

    //3.添加数据
        arr1=[1,2,3,4,5];
        arr1.splice(2,0,99,'11111');
        //splice(起始位置,0,添加数据)
        //通过修改替换的元素的起始位置,来实现在任意地方添加元素
        arr1.splice(arr1.length,0,'haha');
        console.log(arr1);

4.Function类型

​ function是一个用来构建函数的类(构造函数)。

1)函数创建
	//1.普通创建 (常用)
    function fn(n){
        console.log(n);
    }
    fn(10);

    //2.使用变量初始化函数 (常用)
    var fn1 = function(n){
        console.log(n);
    }
    fn1(100);

    //3.new 创建
    var fn2 = new Function();
    fn2 = function(n){
        console.log(n);
    }
    fn2(1000);
2)函数传参 (参数可以是变量,对象,数组,表达式,函数等)

​ 参数为对象:

JavaScript
 var person = function (obj) {
        return obj.name + "," + obj.sex;
      };
      var a = {
        name: "张三",
        sex: "男",
        age: 18,
      };
      console.log(person(a));

​ 设置参数默认值

javascript
      //info给默认值 传递info值将覆盖 未传值是将使用默认值
      function person1(obj, info = "是一位好学生!") {
        return obj.name + "," + obj.sex + "," + info;
      }
      console.log(person1(a));
      console.log(person1(a, "学习很好"));

​ 形参中含有函数

javascript
 function box(sumFunction, num) {//形参中含有函数
        return sumFunction(num);//返回一个函数 sum(10); 
      }
      function sum(num) {
        return num + 10;//20
      }
      var result = box(sum, 10); //传递函数到另一个函数里
      console.log(result);
3)函数内部属性

​ 在函数内部有两个特殊的对象:arguments和this。

callee:

​ arguments是一个类数组对象,包含着传入函数中的所有参数主要用途是保存函数参数。

​ arguments还有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。

也即是可以通过arguments.callee调用函数自身,一般用于函数的递归调用。

​ 函数自己调用自己叫函数的递归调用。

 //需求:求6!=
      function fact(n) {
        if (n == 1) {
          return 1;
        } else {
          //return n * fact(n - 1);
          return n * arguments.callee(n-1);//更优
        }
      }

this指针对象

​ 在全局中this指向的是window(在JS中没有global这个全局对象,而JS的全局对象是window)

​ 在函数中this指向的是这个函数执行所操作的当前对象。

	 var name = "李四";// window.name='李四'
      var age = 12;
      var obj = {//对象
        name: "张三",
        age: 16,
        fn: function () {
          console.log(this.name + this.age);//this指向的是当前执行这个函数的对象
        },
      };
      console.log(this.name + this.age);//this指向的是window
      obj.fn();

JS面向对象

1.内置对象

1)global对象

​ 在JS中没有global对象,web浏览器将global作为window对象的一部分加以实现。

​ 方法:

a.encodeURIComponent():对unicode进行编码处理

b.decodeURIComponent():对unicode进行解码处理

        var str ='我来搞前端,oh,yeah!'
        console.log(str);
        //unicode编码处理 对英文字母和符号不进行编码
        console.log(encodeURIComponent(str));
        //unicode解码
        console.log(decodeURIComponent(encodeURIComponent(str)));

image-20230817194844030

image-20230817194913109

c.eval(''):具有字符串解析器的作用(慎用!因为它的性能较差,且比较危险)

      var x = "Tom";
      var str1 = "他的名字叫:x";
      var str2 = eval("'他的名字叫:'+x");
      //eval('')方法自含有一对引号,它中间包含的是一段JS代码,这里的eval()的作用是将这部分JS代码解释出来
      console.log(str1,'/n',str2);
2)Math对象

​ 该对象主要提供了大量的数学运算的属性和方法。

​ 属性:

​ Math.E:e

​ Math.PI:Π

​ 方法:

​ Math.min():取最小值

​ Math.max():取最大值

​ Math.round():四舍五入函数

​ Math.ceil():向上取整(取大于或等于操作数的最小整数)

​ Math.floor():向下取整(取小于或等于操作数的最小整数)

​ Math.random():产生[0,1)之间的一个任意小数

​ Math.pow(num,power) :返回num的power次幂

​ Math.sqrt(num) :返回num的平方根

​ Math.abs(num) :返回num的绝对值

​ Math.exp(num) :返回Math.E的num次幂

​ Math.log(num):返回num的自然对数

​ Math.acos(x) 返回x的反余弦值

​ Math.asin(x) 返回x的反正弦值

​ Math.atan(x) 返回x的反正切值

​ Math.atan2(y,x) 返回y/x的反正切值

​ Math.cos(x) 返回x的余弦值

​ Math.sin(x) 返回x的正弦值

​ Math.tan(x) 返回x的正切值

 //属性
      console.log(Math.E);
      console.log(Math.PI);
      //方法
      console.log("最小值", Math.min(12, 2, 3, 4));
      console.log("最大值", Math.max(12, 2, 3, 4));//12
      console.log("四舍五入", Math.round(12.134));//12
      console.log("四舍五入", (12.134324).toFixed(2));//'12.13'
      //.toFixed(n) 四舍五入可以保留n位小数,但结果为字符型数据
      console.log("四舍五入", parseFloat((12.134324).toFixed(2)));//12.13
      //用parseFloat将字符型数字转换为浮点型
      console.log("向上取整", Math.ceil(12.134)); //13
      console.log("向上取整", Math.ceil(-12.134)); //-12
      console.log("向下取整", Math.floor(12.134)); //12
      console.log("向下取整", Math.floor(-12.134)); //-13
      console.log("随机函数", Math.random()); //[0-1)
      console.log("随机函数", Math.random(2)); //random中的参数为随机种子
      //需求:输出[30-88]之间的随机数
      console.log(
        "输出[30-88]之间的随机数",
        Math.ceil(Math.random() * 58 + 30)
      );
      console.log("2的3次方为", Math.pow(2, 3));
      console.log("9的平方根为", Math.sqrt(9));
      console.log("30°的正弦为", Math.round(Math.sin(Math.PI / 6) * 10) / 10); //在JS运算中必须将角度转换为弧度再运算
      console.log("35°的余弦为", Math.cos((35 * Math.PI) / 180).toFixed(1));

2.面向对象

​ 创建对象:

1)常规创建

​ a.new

​ b.字面量

2)工厂模式

​ 通过封装函数实现创建一批相似的对象。

​ 缺陷:无法知道创建的对象是哪一个对象的实例。

​ 工厂模式:成批创建相似对象

​ 传统创建相似对象的缺陷:代码重复率太高

 //工厂模式创建对象
      function creatObj(name, age) {
        var obj = new Object();//创建一个新对象
        obj.name = name;
        obj.age = age;
        obj.fn = function () {
          return this.name + this.age;
        };
        return obj;//返回对象
      }

      var obj3 = creatObj("张三", 20);
      var obj4 = creatObj("李四", 23);
      console.log(obj3, obj4);
      console.log(obj3.fn());

3)构造函数(类)

​ 构造函数是用来构建一个类(ES5中没有类的概念,实际上这里的构造函数就是类)。

​ 类是对象的一个抽象符号化表示(把相同或相似的一部分对象抽离出来就形成了一个类)。

​ 对象是类的实例化(具体化)(赋予一定的属性和功能)

​ a.创建构造函数(类)

​ 语法:

​ functon 类名([形参列表]){

​ this.属性名 = 参数;

​ ........

​ this.方法名 = function(){

​ 函数体;

​ }

​ .........

​ }

​ b.通过构造函数实例化对象

​ var 对象名 = new 类名(实参列表);

      //构造函数(类)创建对象
      function Person(name, age = 16) { //age = 16 默认初始值
        //构造函数名首字母名要大写(这里创建的不止是一个函数,实际上是构建了一个类)
        this.name = name; //this代表的不是Person而是实例化后的对象
        this.age = age;
        this.fn = function () {
          return this.name + this.age;
        };
      }
      //实例化对象
      var obj7 = new Person("王麻子", 77);//实列化时this指向该对象obj7
      var obj8 = new Person("武大郎", 66);//实列化时this指向该对象obj8
      console.log(obj7.name);
      console.log(obj7.fn());

      console.log(obj7 instanceof Object); 
      console.log(obj7 instanceof Person);
      console.log(obj8 instanceof Person);//可以判断obj7和obj8属于具体属于那个类

​ 注意:构造函数名每个单词首字母名要大写(大驼峰)。

使用了构造函数的方法,和使用工厂模式的方法他们不同之处如下:

​ ①构造函数方法没有显式的创建对象(new Object());

​ ②直接将属性和方法赋值给this对象;

​ ③没有renturn语句。

构造函数的方法有一些规范:

​ ①函数名和实例化构造名相同且单词首字母大写,(PS:非强制,但这么写有助于区分构造函数和普通函数);

​ ②通过构造函数创建对象,必须使用new运算符。 (var 对象名 = new 构造函数名(实参列表))

构造函数执行的过程:

​ ①当使用了构造函数,并且new 构造函数(),那么就后台执行了new Object();

​ ②将构造函数的作用域给新对象,(即new Object()创建出的对象),而函数体内的this就代表new Object() 出来的对象。

​ ③执行构造函数内的代码;

​ ④返回新对象(后台直接返回)

3.基本包装类型

​ 在基本数据类型中有3个特殊类的存在:String,Number和Boolean。

​ 上面三个基本类型都有自己的包装对象,有相应的属性和方法。调用方法的过程是在后台发生的,所以我们称作为基本包装类型。

​ 通俗地讲就是基本类型的数据都有一个包装他们的类,这些类都有自己的属性和方法,这些基本类型的数据都可以直接去调用这些属性和方法。

1)Boolean类型

​ 没有自己的属性和方法。

2)Number类型

​ a.属性

属性描述
MAX_VALUE表示Number的最大数(最大范围)
MIN_VALUE表示Number的最小值(最小范围)
NaN非数值
NEGATIVE_INFINITY负无穷大,溢出返回该值
POSITIVE_INFINITY无穷大,溢出返回该值
prototype原型,用于增加新属性和方法

​ b.方法

方法描述
toString()将数值转化为字符串,并且可以转换进制
toLocaleString()根据本地数字格式转换为字符串,含有千位分隔符
toFixed(n)将数字保留小数点后指定位数并转化为字符串
toExponential()将数字以指数形式表示,保留小数点后指定位数并转化为字符串
toPrecision()指数形式或点形式表述数,保留小数点后面指定位数并转化为字符串
valeOf()显示原始值
var box = 1000.789;     
alert(box.toString());//转换为字符串,传参可以转换进制     
alert(box.toLocaleString());//本地形式,1,000.789    
alert(box.toFixed(2));//小数点保留,'1000.79'     
alert(box.toExponential());//指数形式,传参会保留小数点'1.000789e+3  e+3表示10的三次方
alert(box.toPrecision(3)); //指数或点形式,传参保留小数点1.00e+3

​ 注:只有几种语言具有地方特色有大小写本地性,一般来说,是否本地化效果都是一致的。

3)String类型

​ a.属性

​ .length //长度

​ b.方法

方法描述
str.charAt(n)返回指定索引位置的字符(从0开始)
str.charCodeAt(n)以Unicode编码形式返回指定索引位置的字符
str.concat(str1...str2)将字符串参数串连到调用该方法的字符串
str.slice(n,m)返回字符串n到m之间位置的字符串(截取从下标为[n,m) )
str.substring(n,m)同上
str.substr(n,m)返回字符串下标从n开始的m个字符 n<0从右向左截取
str.indexOf(str1,[n])从n开始搜索第一个str1,并将搜索的索引值返回,找不到返回-1
str.lastIndexOf(str,n)从n开始搜索最后一个str,并将搜索的索引值返回
//charAt(n)返回指定索引位置的字符
      console.log(str1.charAt(4));
      //charCodeAt(n)返回指定索引位置的字符的UNicode码
      console.log(str1.charCodeAt(3));
      //concat(str1...str2) 将字符串参数串联到调用该方法的字符串
      var str3 = str1.concat(str2);
      console.log(str3);
      //slice(n,m) 返回字符串n到m之间位置的字符串
      console.log(str3.slice(1, 4));
      //substring(n,m) 同上
      console.log(str3.substring(1, 4));
      //substr(n,m) 返回字符串n开始的m个字符
      console.log(str3.substr(1, 4)); //从下标为1开始截取4个字符
      console.log(str3.substr(1, 99999)); //截取个数不够,就截取到最后
      console.log(str3.substr(0)); //如果只带一个参数:从指定下标截取到最后
      console.log(str3.substr(-2)); //如果第一个参数为负数,表示反向截取
      //indexOf(str,n)从n开始搜索第一个str,并将搜索的索引值返回
      console.log(str3.indexOf("l")); //从下标为0的位置开始搜索 返回第一个‘l’位置的下标
      console.log(str3.indexOf("l", 4)); //从下标为4的位置开始搜索 返回第一个‘l’位置的下标
      console.log(str3.indexOf("lfbdan")); //如果str不存在将返回-1
      //lastIndexOf(str,n)从n开始搜索最后一个str,并将搜索的索引值返回
      console.log(str3.lastIndexOf("l")); //从下标为0的位置开始搜索返回最后一个‘l’位置的下标/从后往前搜索找第一次出现
方法描述
str.toLowerCase()将字符串全部转换为小写(验证码处理)
str.toUpperCase()将字符串全部转换为大写(验证码处理)
str.match(pattern)返回pattern中的字串或null
str.replace(pattern,replacement)用replacement替换pattern
str.search(pattern)返回字符串中pattern开始位置
str.split(pattern)返回字符串按指定pattern拆分的数组
String.fromCharCode(ascii)静态方法,输出Ascii码对应值
str.localeCompare(str1)比较两个字符串,并返回相应的值
var str0 = "how ARE you??";
      console.log(str0);
      //str.toLowerCase()将字符串全部转换为小写
      console.log(str0.toLowerCase());
      //str.toUpperCase()将字符串全部转换为大写
      console.log(str0.toUpperCase());
      //str.toLocaleLowerCase()将字符串全部转换为小写,并且本地化
      //str.toLocaleUpperCase()将字符串全部转换为大写,并且本地化
      //match(pattern)返回pattern中的字串或null
      console.log(str0.match("are")); //不匹配返回null
      console.log(str0.match("ARE")); //返回匹配的内容(是一个数组)['ARE', index: 4, input: 'how ARE you??', groups: undefined]
      console.log(str0.match(/ARE/)[0]); //返回匹配的内容 ARE
      console.log(str0.match(/ARE/).index); //返回匹配开始的下标 4
      console.log(str0.match(/y/)); //正则匹配  /y/匹配y
      //replace(pattern,replacement)用replacement替换pattern
      console.log(str0.replace("how", "HOW")); //将字符串中的how 替换为HOW  ['how ARE you??']
      console.log(str0.replace(/\?/, "@")); //尽量使用正则写法 /?有特殊含义用\?转义字符表示 how ARE you@? 只替换了第一个
      console.log(str0.replace(/\?/gi, "!")); //g为全局替换,替换所有的i为忽略所有大小写
      console.log(str0.replace("", "...")); //pattern什么也不写:在最前面添加
      console.log(str0.replace("you", "")); //replacements什么也不写:删除操作
      //search(pattern)返回字符串中pattern开始位置
      console.log(str0.search(/\?/)); //11 表示从字符串下标为11的位置开始匹配
      //split(pattern)返回字符串按指定pattern拆分的数组
      var arr = str0.split();
      console.log(arr); //['how ARE you??']  1个数组1个元素
      var arr1 = str0.split(" "); //以空格作为切割符
      console.log(arr1); // ['how', 'ARE', 'you??'] 1个数组3个元素
      var arr2 = str0.split(""); //把字符串中的每一个字符切割成一个元素数组
      console.log(arr2); //['h', 'o', 'w', ' ', 'A', 'R', 'E', ' ', 'y', 'o', 'u', '?', '?']
      str1 = arr2.join(""); //将数组变为字符串每个元素间用''进行连接,默认为,
      console.log(str1); //how ARE you??
      //String.fromCharCode(ascii)静态方法,输出Ascii码对应值
      console.log(String.fromCharCode(65)); //输出ASCII码值65所对应的字符
      console.log(String.fromCharCode(97)); //输出ASCII码值97所对应的字符
      //localeCompare(str1,str2)比较两个字符串,并返回相应的值
      var s1 = 'aaa',s2='bbb';
      console.log(s1.localeCompare(s2));//-1 不相同的两个字符串比较时,如果第一个字符串的ASCI码小于第二个返回-1
      console.log(s2.localeCompare(s1));//1 不相同的两个字符串比较时,如果第一个字符串的ASCI码大于第二个返回1
      console.log(s2.localeCompare(s2));//0 相同的两个字符串比较时返回0

获取字符串第n个字符并判断其值的三种方法

JavaScript
   console.log(selector[0] === "<");
   console.log(selector.charAt(0) === "<");
   console.log(selector.indexOf("<") === 0);

4.变量,作用域及内存

1)作用域

a.基本数据类型的变量

​ 基本类型的变量的值存储在栈中。通过变量名可以直接获取变量的值。

b.引用类型的变量

​ 引用类型的变量的值存储在堆中,在栈中存储的是引用类型的变量的地址(指针)。

​ 如果要获取引用类型变量的值,需要先从栈中获取地址,再按址查找,从而获取到值。

image-20230817195217350

​ ps:字符串非引用类型。

2)作用域

​ 在ES5中作用域分为全局作用域和局部作用域。

​ 在ES6中作用域分为全局作用域,局部作用域和块级作用域三种。

​ 全局作用域:

​ 定义在函数外部的变量拥有全局作用域。

​ 局部作用域:

​ 定义在函数内部的变量拥有局部作用域。

console.log(x); //var定义的变量存在变量位置提升,该变量默认值为undefinde 可以拿到变量但不能拿到下面所赋的值
      var x = 123; //这里的x是一个全局变量,任意地方都可以访问
      function fn1(n) {
        //在外部定义的函数也为全局变量,函数的形参为局部变量
        var y = "abc"; //这里的y为局部变量只能在该函数内进行访问
        console.log(x, y);
        console.log(1);
      }
      function fn2() {
        console.log(x + 10);
      }
      fn1(1);
      fn2();
      console.log(x);
      //console.log(y);// y is not defined
3)垃圾回收机制

​ JS有自动回收垃圾的功能。

​ 在项目开发过程中,初始化对象时,最好赋初值为null。

5.正则表达式

​ 参考网站:https://www.runoob.com/regexp/regexp-metachar.html

https://www.runoob.com/jsref/jsref-obj-regexp.html

https://www.runoob.com/js/js-regexp.html

1)什么是正则表达式

​ 正则表达式是由一个字符序列形成的搜索模式。

​ 当你在文本中搜索数据时,你可以用搜索模式来描述你要查询的内容。

​ 正则表达式可以是一个简单的字符,或一个更复杂的模式。

​ 正则表达式可用于所有文本搜索和文本替换的操作。

image-20231029162645830image-20231029162752908

2)语法:

​ /正则表达式主体/ 修饰符(可选)

3)正则表达式元字符和特性

字符匹配

  • 普通字符:普通字符按照字面意义进行匹配,例如匹配字母 "a" 将匹配到文本中的 "a" 字符。

  • 元字符:元字符具有特殊的含义,例如 \d 匹配任意数字字符,\w 匹配任意字母数字字符,. 匹配任意字符(除了换行符)等。

    量词

  • *:匹配前面的模式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。

  • +:匹配前面的模式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。

  • ?:匹配前面的模式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 。? 等价于 {0,1}。

  • {n}:匹配前面的模式恰好 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。

  • {n,}:匹配前面的模式至少 n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。

  • {n,m}:匹配前面的模式至少 n 次且不超过 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。

    字符类

  • [ ]:匹配括号内的任意一个字符。例如,[abc] 匹配字符 "a"、"b" 或 "c"。

  • [^ ]:匹配除了括号内的字符以外的任意一个字符。例如,[^abc] 匹配除了字符 "a"、"b" 或 "c" 以外的任意字符。

    边界匹配

  • ^:匹配字符串的开头。

  • $:匹配字符串的结尾。

  • \b:匹配单词边界。

  • \B:匹配非单词边界。

    分组和捕获

  • ( ):用于分组和捕获子表达式。

  • (?: ):用于分组但不捕获子表达式。

    特殊字符

  • \:转义字符,用于匹配特殊字符本身。

  • .:匹配任意字符(除了换行符)。

  • |:用于指定多个模式的选择。

4)修饰符
修饰符描述
i将匹配设置为不区分大小写
g全局匹配
m多行匹配
s特殊字符圆点 . 中包含换行符 \n

eg:

javascript
// 在字符串中查找 "runoob":
var str="Google runoob taobao runoob"; 
console.log(str.match(/runoob/)); //查找第一次匹配项
console.log(str.match(/runoob/g)); //查找所有匹配项
// 查找全部wdt 不区分大小写
var str1 = "WDT, wDt Wdt hhhhh"
console.log(str1.match(/wdt/gi));//查找所有的wdt不区分大小写

var str="google\nrunoob\ntaobao";
var n1=str.match(/google./);   // 没有使用 s,无法匹配\n
var n2=str.match(/runoob./s);  // 使用 s,匹配\n
基本模式匹配
字符作用
^word匹配以word开头的字符串
$word匹配以word结尾的字符串
word只匹配字符串word
\ \匹配\(转义字符)
\ .匹配.
[a-z]匹配所有小写字母
[A-Z]匹配所有大写字母
[0-9]匹配所有数字
[0-9 \ . \ -]匹配所有数字,句号和减号
[\ f \r \ t \ n]匹配所有空白字符
[^a-z]匹配除了小写字母以外的全部字符
[^ \ '' \ ']匹配除了'' ' 外全部字符

eg:

javascript
//匹配一个由一个小写字母和一位数字组成的字符串,比如 "z2"、"t6" 
^[a-z][0-9]$

注意:

​ 在[]内使用 ^表示 非 排除的意思。

4)字符簇

​ 跟在字符或字符簇后面的花括号({})用来确定前面的内容的重复出现的次数。

​ 一个数字 {x} 的意思是前面的字符或字符簇只出现x次 ;一个数字加逗号 {x,} 的意思是前面的内容出现x或更多的次数 ;两个数字用逗号分隔的数字 {x,y} 表示 前面的内容至少出现x次,但不超过y次

字符簇描述
^[a-zA-Z_]$所有字母和下划线
^[[:alpha:]]{3}$所有的3个字母的单词
^a$字母a
^a{4}$aaaa
^a{2,4}$aa,aaa或aaaa
^a{1,3}$a,aa,aaa

JSON和DOM操作(重要)

1.JSON(JavaScript Object Natation:JS对象简谱)

是一种轻量级的数据交换格式。用独立的编程语言的文本来存储和表示数据。

1)优点:

​ 易于阅读和编写,同时也易于浏览器解析和生成,并有效地提升网络传输效率。

2)与XML比较:

​ JSON是一个书写或解析时的一个对象,更容易解析,而XML是由用户自定义标签来存储数据的,对前端来说,不容易书写且解析来比较困难。

3)JSON文件内容:

​ 它可以是一个单值,也可以是一个对象,也可以是一个数组,也可以是对象和数组的结合。

4)JSON写在哪里

​ 可以写在JavaScript代码中也可形成一个独立的.json文本文件中。(.json文件中的所有对象键值对中的key必须用双引号包裹)

​ a.在JS中书写JSON数据

​ 如果是对象,键可以用单或双引号引起来也可以不加引号。

​ 值如果是字符串,必须用单或双引号引起来 ,数值型数据和逻辑值以及null不能加引号。

​ b.独立的 JSON文件

​ Ⅰ)文件的扩展名必须是.json。JSON文件不是JS文件,不能出现任何的JS代码,它只是一个文本文件而已。

​ Ⅱ)数据不能赋给某个变量

​ Ⅲ)键(key)必须用双引号引起来

​ Ⅳ)值如果是字符型数据,必须用双引号引起来,其他类型是的数据不能用引号引起来

​ Ⅴ)在JSON文件中不能添加任何注释

5)数据值可以有以下三种

​ ①简单值:可以在JSON中表示字符串,数值,布尔值和null。但JSON不支持JavaScript中的特殊undefined。

​ ②对象 {}

​ ③数组 []

​ ④数组对象组合

6)在实际开发中的数据处理

​ 在实际项目开发中,如果后台工程师还没有创建好后台数据接口时,前端工程师可以先做数据mock(模拟),写对应的HTML,CSS和JS代码,等后台数据可以调用时,再进行替换即可。

​ 在项目开发中,数据最好分离开来,形成单独的JSON文件。

7)解析JSON数据

​ a)JS中JSON

​ 如果是JSON数据可以直接访问;如果是JSON格式的字符串需要JSON.parse()方法进行转换。

JavaScript
 	JSON.parse();//将JSON格式的字符串转换为JSON
    JSON.stringify();//将JSON转换为JSON格式的字符串
javascript
	  //如果返回的是具有JSON格式的字符串,需要将它解析为JavaScript识别的JSON,用JSON.parse()这个方法实现。
      //JSON格式的字符串规范:其中的键必须用双引号引起来,值如果是字符型数据,也必须用双引号引起来。
      console.log(JSON.parse(students2));//将JSON格式的字符串转换为JSON
      console.log(JSON.stringify(students2));//将JSON转换为JSON格式的字符串

​ 解析JSON数据的目的是为了将动态数据渲染到前端(浏览器)

javascript
 //解析JSON数据的目的是为了将数动态据渲染到前端(浏览器)
      var txt = "";
      for (var i = 0; i < students1.length; i++) {
        txt += "<li>" + students1[i].name + "</li>";
      }//常量用引号引起来,变量不加引号
document.getElementsByClassName('stu-list')[0].innerHTML=txt;

​ b)解析JSON文件

​ JSON文件必须用 Ajax(异步请求)技术去获取。

2.Ajax请求操作步骤

javascript
<script>
      //1.创建请求对象
      var xhr = new XMLHttpRequest();
      //2.建立请求连接
      xhr.open("get", "url", true); //get/post:请求方式  url表示请求路径  true/flase:表示异步操作,flase表同步操作
      //4.前端对请求的结果进行处理
      xhr.onreadystatechange = function () {
        if (xhr.readyState == 4 && xhr.status == 200) {
          //如果请求成功
    	 console.log(JSON.parse(xhr.responseText)); //responseText:获取请求的结果
      	 var data = JSON.parse(xhr.responseText)//对请求到的数据进行操作
     	 };
      }
      //3.向后台发送请求
      xhr.send();
    </script>

​ 注意:

​ 如果发送ajax请求,必须以http(服务器端)的方式启动文件,不能在本地直接打开。

3.DOM节点

​ DOM:(Documnet Object Model:文档对象模型):是HTML和XML文档的编程接口,定义了访问和操作HTML和XML文档的标准方法。(XML用来传输数据而不显示数据)

​ DOM以树型目录结构表达HTML和XML文档的,每一个节点就是一个DOM元素。image-20230817195321145

1)DOM节点层次

​ 节点层次分为父子节点和同胞节点两种。

​ 在节点树中,顶层节点被称为根(root)

​ 每个节点都有父节点,除了根(他没有父节点)

​ 一个节点可以拥有任意数量的子节点

​ 同胞节点是拥有相同父节点的节点,也叫兄弟节点。

2)DOM节点的分类

​ 元素节点:标签

​ 属性节点:标签的属性

​ 文本节点:标签后的换行符

​ 文档节点:document

3)DOM节点的名称(nodeName)
节点类型nodeName
元素节点标签名相同
属性节点属性名相同
文本节点#text
文档节点#document
(document:返回整个文档)
4)DOM节点的值(nodeValue)
节点类型nodeValue
元素节点是undefined或null
文本节点文本内容
属性节点属性值
5)DOM节点的类型(nodeType)
元素类型nodeType
元素1
属性2
文本3
注释8
文档9

4.节点操作(重要,核心)

1)获取节点

​ 通过ID获取节点 [ 返回具体某个节点 ](ID值唯一

document.getElementByID("ID名")

​ 通过标签名获取节点[ 返回节点数组,即使该标签只有一个](访问时要添加数组下标)

document.getElementsByTagName("标签名")

​ 通过标签的name值(表单中)获取节点 [ 返回节点数组]

document.getElementsByName("name名")

​ 通过class值来获取节点 [ 返回节点数组]

document.getElementsByClassName("Class名")

​ 根据选择器返回找到结果集中的第一个

document.querySelect("选择器")

​ 根据选择器返回找到的结果集,是个节点数组

document.querySelectAll("选择器")

​ 获取当前节点的子节点,返回一个数组

DOM.children/DOM.children[0]
2)创建DOM节点

​ Ⅰ)创建元素节点

   document.createElement("标签名");

​ Ⅱ)创建文本节点

	document.createTextNode("文本内容");

​ Ⅲ)创建属性节点

	document.createAttribute("属性名");
	 属性节点名.value = "属性值"; //为属性设置值

​ Ⅳ)关联以上三个节点

	元素节点名.appendChild(文本节点名);//在元素节点上添加文本节点
	元素节点名.setAttributeNode(属性节点名);//在元素节点上添加属性节点
	document.body.appendChild(元素节点名);//将创建的节点添加到文档中
javascript
 <style>
      .wrapper {
        color: aqua;
        font: bold 18px 黑体;
        background-color: #999999;
      }
      .box{
          border: 2px solid rgb(31, 29, 29);
      }
    </style>
    <script>
      //创建元素节点
      var el = document.createElement("p");
      //创建文本节点
      var txt = document.createTextNode("这个节点是动态创建的");
      //创建属性节点
      var attr = document.createAttribute("class"); //添加属性名
      attr.value = "wrapper box"; //为属性设置值 可添加多个属性值
      //关联以上三个节点
      el.appendChild(txt); //在元素节点上添加文本节点
      el.setAttributeNode(attr);//在元素节点上添加属性节点
  document.body.appendChild(el);//将创建的节点添加到文档中
      </scrip>

简洁写法:

javascript
<script>
	var oDiv = document.createElement('div');//创建元素节点
    oDiv.setAttribute('class','wrapper box');//为元素节点添加属性及属性值
    oDiv.innerHTML='创建DOM元素的简洁写法';//为元素节点设置文本内容
    document.body.appendChild(oDiv);//将创建的节点添加到文档中
    </script>

​ tips: 若要创建多个不同节点可通过封装一个函数(通过传入形参来实现)。

3)插入节点

​ Ⅰ)插入内部的尾部

​ 父节点.appendChild(创建的节点)

​ Ⅱ)插入内部的某个节点前面

​ 父节点.insertBefore(创建的节点,已知的的子节点)

javascript
<script>
      var li = document.createElement("li");
      var txt = document.createTextNode("这是新创建的li标签");
      li.appendChild(txt);
      //上面两行可以简写为
      //li.innerHTML = "这是新创建的li标签";
      //Ⅰ)插入内部的尾部
      //父节点.appendChild(创建的节点)
      var list = document.getElementsByClassName("list")[0];
      list.appendChild(li);

      //Ⅱ)插入内部的某个节点前面
      //父节点.insertBefore(创建的节点,已知的的子节点)
        //var oLi = document.getElementsByTagName('li')[2]; //通过标签名直接获取
        var ul = document.getElementsByClassName("list")[0];//通过父级元素间接获取
        var oLi = ul.getElementsByTagName('li')[2];//可以限定到类名为list中的li标签
        ul.insertBefore(li,oLi);
    </script>
4)替换节点

​ 父节点.replaceChild(新节点,老节点)

javascript
 <body>
    <div class="box">box</div>
    <strong>strong</strong>
    <script>
      //需求:将strong标签修改成"<p style='color:pink;border:2px dotted #000;'>这是修改后的节点及内容</P>"
      //1.创建新节点
      var newP = document.createElement("p");
      newP.setAttribute("style", "color:pink;border:2px dotted #000;");
      newP.setAttribute("title", "节点修改");//有多个不同的属性,分开添加
      newP.innerHTML = "这是修改后的节点及内容";
      console.log(newP);

      //2.替换节点
        var oBody = document.body;//找到父节点 对于节点的获取最好缓存到内存变量中
        var old = document.getElementsByTagName('strong')[0];//找到被替换节点 返回的是一个数组必须用下标
        oBody.replaceChild(newP,old);//替换
    </script>
  </body>
5)克隆节点

​ 克隆节点就是将我们需要的节点复制一份,分为深度克隆和浅克隆。

​ 深度克隆:包含子节点一起克隆。

​ 浅克隆:只会找到这个节点克隆,不会克隆子节点(包括文本节点)。

​ 语法:需要被复制的节点.cloneNode(true/false);

​ true:复制当前节点以及所有子节点(深度克隆)

​ flase:仅复制当前节点(浅克隆)

html
 <body>
    <div class="box">
      <strong>strong</strong><!--box下	的子元素/子标签/子节点-->
      <span>span</span>
    </div>
    <script>
      var box = document.getElementsByClassName("box")[0];
      //深度克隆
      var deepClone = box.cloneNode(true);
      document.body.appendChild(deepClone); //添加
      //浅克隆
      var shallowClone = box.cloneNode(false);
      document.body.appendChild(shallowClone);
    </script>
  </body>
6)删除节点

​ Ⅰ)删除当前节点及子节点

​ 节点.remove();

​ Ⅱ)删除子节点

​ 父节点.removeChild(子节点);

html
<body>
    <div class="box">
      <strong>strong</strong>
      <span>span</span>
    </div>
    <ul>
      <li>li1</li>
      <li>li2</li>
      <li>li3</li>
      <li>li4</li>
      <li>li5</li>
    </ul>
    <script>
      var box = document.getElementsByClassName("box")[0];
      //1)删除当前节点及子节点
      box.remove();

      //2)删除子节点
      var span = box.getElementsByTagName("span")[0]; //找box下的span标签
      box.removeChild(span);

      //需求:删除ul中的所有li标签
      var ul = document.getElementsByTagName('ul')[0];
      var lis = ul.getElementsByTagName('li');
      console.log(lis.length);
      for(var i=0; i<lis.length;i++){
        ul.removeChild(lis[i]);
        --i;
        //数组下标动态变化,始终删除下标为0的li标签,直到全部删除完毕
      }
    </script>
  </body>
7)节点属性操作

​ Ⅰ)获取属性值

​ DOM节点.属性名 //不能获取用户自定义的属性。(data-属性名:用户自定义属性)

​ DOM节点.getAtteibute(属性名) //获取所有属性包括用户自定义的属性值。(常用)

​ Ⅱ)设置属性值

​ DOM节点.属性名 = 属性值 //不能设置用户自定义属性的值

​ DOM节点.setAtteibute(属性名,属性值) //设置所有属性值。(常用)

​ Ⅲ)删除属性值

​ DOM节点.属性名=' '; //不能删除用户自定义的属性。

​ DOM节点.removeAttribute(属性名) //删除所有属性。(常用)

8)节点文本操作

​ Ⅰ)获取文本内容

​ 节点.innerHTML //获取节点下的所有内容(包含了标签)

​ (用于获取双标签的文本内容 ge:< p>< /p> 单标签不能使用该方式获取问文本内容,因为单标签无文本内容ge:< img>)

​ 节点.innerText //获取节点下的文本内容,会过滤掉标签

​ 节点.value //获取input输入框等表单控件的内容

​ 节点.getAttribute("value") //value是表单输入框的属性,可以使用getAttribute获得value的值

​ Ⅱ)设置文本内容

​ 节点.innerHTML= "文本内容" //会翻译html标签

​ 节点.innerText = “文本内容” //不会翻译html标签

​ 节点.value = 值

​ 节点.setAttribute("value",值) //因为value是属性,所以也可以用这个方法设置内容。

​ Ⅲ)删除文本内容

​ 节点.innerHTML=""

​ 节点.innerText=""

​ 节点.value=""

​ 节点.removeAttribute(“value“)

html
 <body>
    <button id="btn"><span>这是</span>切换图片</button>
    <img src="" data-missing="bg1.jpg" alt="picture" />
    <form action="">
      <input name="txt" type="text" value="这是表单文本框的内容" /><br />
      <input type="radio" name="sex" value="1" />男
      <input type="radio" name="sex" value="0" />女<br />

      <input type="checkbox" name="hobby" value="唱" />唱
      <input type="checkbox" name="hobby" value="跳" />跳
      <input type="checkbox" name="hobby" value="rap" />rap
      <input type="checkbox" name="hobby" value="篮球" />篮球<br />

      <input type="button" value="提交" id="ok" />
    </form>

    <script>
      //Ⅰ)获取文本内容
      //节点.innerHTML  //获取节点下的所有内容包含了标签(用于获取双标签的文本内容)
      console.log(document.getElementById("btn").innerHTML);
      console.log(document.getElementsByTagName("img")[0].innerHTML); //单标签不能使用该方式获取问文本内容,因为单标签无文本内容
      //节点.innerText  //获取节点下的文本内容,会过滤掉标签
      console.log(document.getElementById("btn").innerText); //文本内容中的标签不会获取到
      //节点.value  //获取input输入框等表单控件的内容
      console.log(document.getElementsByName("txt")[0].value);
      console.log(document.forms[0].txt.value); //多种获取方式
      console.log(document.querySelector("input").value);
      document.getElementsByName("txt")[0].onblur = function () {
        //onblur:事件(失焦点时触发该事件)
        console.log(this.value); //这里的this代表的是当前操作的DOM对象document.getElementsByName("txt")[0]
        if (this.value == "aaa") {
          alert("登录成功");
          location.href = "https://baidu.com"; //location.href可以用于动态跳转;a标签静态跳转
        } else {
          alert("您输入账号不正确");
        }
      };
      //节点.getAttribute("value") //value是表单输入框的属性,可以使用getAttribute获得value的值
      console.log("value" + document.forms[0].txt.getAttribute("value")); //只能获取标签属性的值,不能获取标签的文本内容

      //Ⅱ)设置文本内容
      //节点.innerHTML= "文本内容"  //会翻译html标签
      document.getElementById("btn").innerHTML = "aaa";
      document.getElementById("btn").innerHTML =
        "这是一个<strong>按钮</strong>"; //会自动解析html标签
      //节点.innerText = “文本内容”  //不会翻译html标签
      document.getElementById("btn").innerText =
        "这是一个<strong>按钮</strong>"; //不会自动解析标签
      //节点.value = 值
      document.forms[0].sex.value = 0; //设置某个单选钮被选中
      document.forms[0].hobby.value = "唱"; //不能通过value设置复选框的某项被选中
      document.forms[0].hobby[1].checked = true; //设置复选框的某项被选中要用checked属性实现
      document.forms[0].hobby[2].checked = true;
      document.forms[0].hobby[2].checked = false; //取消被选中

      //单击‘提交’按钮获取所有复选框被选中的内容
      document.getElementById("ok").onclick = function () {
        var chks = document.forms[0].hobby; //获取所有的复选框DOM
        //console.log(chks);
        var arr = [];
        for (var i in chks) {
          if (chks[i].checked == true) {
            //如果被选中
            arr.push(chks[i].value); //将value传入数组里
          }
        }
        console.log(arr);
      };
      //节点.setAttribute("value",值)  //因为value是属性,所以也可以用这个方法设置内容。
      document.forms[0].hobby[0].setAttribute("value", "不唱"); //修改value值
      document.forms[0].hobby[0].setAttribute("checked", true); //设置被选中
      document.forms[0].hobby[0].removeAttribute("checked"); //设置取消被选中

      //Ⅲ)删除文本内容
      //节点.innerHTML=""
      document.getElementById("btn").innerHTML = "";
      //节点.innerText=""
      document.getElementById("btn").innerText = "";
      //节点.value=""
      document.forms[0].hobby[0].value = "";
      //节点.removeAttribute(“value“)
      document.forms[0].hobby[0].removeAttribute("value");
    </script>
  </body>
9)DOM节点样式操作(重要)

​ a)操作样式class

​ Ⅰ)获取class

​ 节点.className获取节点的所有class

​ 节点.getAttribute("class")获取节点的所有class

​ Ⅱ)设置class

​ 节点.className=值(会覆盖原有的值)

​ 节点.setAttribute("class",值)

​ Ⅲ)其他方法

​ 节点.classList.add(value); //为元素添加指定的类,(不会覆盖原有的类名)

​ 节点.classList.contains(value); //判断元素是否含义指定的类,如果存在返回true

​ 节点.classList.remove(value); //删除指定的类

​ 节点.classList.toggle(value); //有就删除,没有就添加指定类

html
 <style>
      .p-style {
        color: aqua;
        background-color: pink;
        font-size: 30px;
        font-weight: bold;
      }
      .demo {
        border: 3px dotted red;
      }
    </style>
  </head>
  <body>
    <p class="test p-style">这是一段测试文本</p>
    <p class="test">这是一段测试文本</p>
  </body>
  <script>
    //Ⅰ)获取class
    //节点.className获取节点的所有class
    console.log(document.getElementsByClassName("test")[0].className);
    //节点.getAttribute("class")获取节点的所有class
    console.log(
      "getAttribute:" +
        document.getElementsByClassName("test")[0].getAttribute("class")
    );

    ///Ⅱ)设置class
    //节点.className=值
    document.getElementsByTagName("p")[1].className = "p-style"; //将类名修改为 p-style
    //节点.setAttribute("class",值)
    document
      .getElementsByTagName("p")[1]
      .setAttribute("class", "demo style p-style");
    //Ⅲ)其他方法
    //节点.classList.add(value); //为元素添加指定的类
    document.getElementsByTagName("p")[1].classList.add("dome1");
    console.log(document.getElementsByTagName("p")[1].className);
    //节点.classList.contains(value);  //判断元素是否含义指定的类,如果存在返回true
    document.getElementsByTagName("p")[1].onclick = function () {
      if (document.getElementsByTagName("p")[1].classList.contains("p-style")) {
        alert("有"); //有就删除
        document.getElementsByTagName("p")[1].classList.remove("p-style");
      } else {
        alert("没有"); //没有就添加
        document.getElementsByTagName("p")[1].classList.add("p-style");
      }
    };
    //节点.classList.remove(value); //删除指定的类
    //节点.classList.toggle(value);  //有就删除,没有就添加指定类
    var dom = document.getElementsByTagName("p")[0];
    dom.onclick = function () {
      //alert();
      dom.classList.toggle("demo"); //切换样式
    };
  </script>

​ b)操作内联样式

​ Ⅰ)获取内联样式

​ 节点.style.样式属性名 //获取某个具体的内联样式

​ 节点.style.cssText //获取某个节点的所有内联样式,返回字符串

JavaScript
document.querySelector("div").style.cssText ="color:red;font-size:30px;border:10px solid pink";

​ Ⅱ)设置内联样式

​ 节点.style.样式属性名 = 属性值 //设置某个具体的内联样式

​ 节点.style.cssText = 属性值或属性值列表 //设置某个节点的所有内联样式

html
<body>
    <p style="color: pink">这是一段测试文本</p>
    <p style="color: aqua; font-size: 28px">这是一段测试文本</p>
    <div>div</div>
  </body>
  <script>
    var pDom = document.getElementsByTagName("p");
    //Ⅰ)获取内联样式
    //节点.style.样式属性名  //获取某个具体的内联样式
    console.log(pDom[0].style.color);
    console.log(pDom[1].style.fontSize);
    //节点.style.cssText  //获取某个节点的所有内联样式,返回字符串
    console.log(pDom[1].style.cssText);
    console.log(pDom[0].style.cssText);

    //将cssText返回的字符串转换为对象
    var str = pDom[1].style.cssText,
      arr = [],
      cssObj = {};
    arr = str.split("; ");
    for (var i in arr) {
      var newArr = [];
      newArr = arr[i].split(": ");
      cssObj[newArr[0]] = newArr[1]; //不能用.语法 因为newArr[0]为变量
    }
    console.log(cssObj);
    console.log(cssObj.color);
    console.log(cssObj["font-size"]);

    //Ⅱ)设置内联样式
    //节点.style.样式属性名 = 属性值   //设置某个具体的内联样式
    pDom[0].style.color = "blue";
    pDom[1].style.fontSize = "10px";
    //节点.style.cssText  = 属性值或属性值列表  //设置某个节点的所有内联样式
    document.querySelector("div").style.cssText =
      "color:red;font-size:30px;border:10px solid pink";
  </script>

4.常用事件

​ onload:(页面加载时自动启动该事件) onclick:(单击) ondblclick:(双击) onmouseover/onmouseenter:(鼠标移入) onmouseout/onmouseleave:(鼠标移出) onmousemove:(移动鼠标) onkeydown:(按下鼠标的键) onkeyup:(松开键) onkeypress:(输入某个字符) onfocus:(获焦) onblur:(失焦) onresize:(改变窗口尺寸) onsubmit(提交事件) onreset(重置事件) onchange(改变内容)onmousedown(按下鼠标键)onmouseout(松下鼠标键)

​ 在console控制台输入:console.dir(document) //查看事件 以on开头

Javascript基础事件与Event对象(原生态)

1. 事件

​ 事件指的是文档或浏览器窗口中发生的一些特定的交互瞬间,也可以理解为作用在对象上的操作(动作)。

事件是可以被JS监听到的动作。一般在开发中是通过触发DOM来完成事件操作。

2.EventTarget接收事件接口(API)

​ EventTarget:是一个由可以接收事件的对象来实现接口,并且为他们创建监听器。

三个接口:

​ ①addEventListener():绑定事件监听函数,实现监听

​ ②removeEventLister():移除事件监听

​ ③dispatchEvent():自动触发用户自定义事件

1)addEventListener():绑定事件监听函数,实现监听

​ 语法:

​ DOM.addEventListener(type,listener[,userCapture])

​ type:事件名(不带on)

​ listener:监听函数(尽量独立创建,不要在addEventListener()内写监听函数,便于事件移除)

​ userCapyture:是否为事件捕获(true/false默认)false:事件冒泡,true:事件捕获。

​ 注意:可以为同一个DOM绑定多个事件,但不能一次添加多个事件。

​ 示例:

javascript
    function myEvent() {
      console.log("我是mouseover事件");
    }
    document
      .querySelector(".box")
      .addEventListener("mouseover", myEvent, false);
2)removeEventLister():移除事件监听

​ 语法:

​ DOM.removeEventListener(type,listener [,userCapture])

​ type:事件名

​ listener:监听函数

​ userCapyture:是否为事件捕获(true/false默认)

​ 注意:第二个参数不能缺省,且要与创建时的addEventListener()内容相同

javascript
    document
    	.querySelector(".box")
    	.removeEventListener("mouseover",myEvent);
    //第二个参数不能缺省,要与addEventListener()内相同
3)dispatchEvent():自动触发用户自定义事件

​ 语法:

​ DOM.dispatchEvent(event)

​ event:用户自定义事件的实例

​ 示例:

javascript
    // Ⅰ)创建事件监听
    document.body.addEventListener("myEvent", function () {
      this.style.background = "aqua";
    });
    // Ⅱ)创建myEvent事件实例
    var event = new Event("myEvent");
    // Ⅲ)触发事件
    document.body.dispatchEvent(event);

3.JS常用事件

1)UI事件

​ onload:页面所有DOM元素加载完成后自动触发(常用)

​ 若要对body内的DOM进行操作时,如果JS代码放在body后则不需要onload,如果JS代码放在body前则需要使用onload

​ 示例:

html
    <script>
    //console.log(document.getElementsByClassName("box")[0]);
    //页面尚未加载完毕,无法获取到DOM节点  若想获得必须放在onload里面
    /* window.onload = function(){
        console.log(document.getElementsByClassName("box")[0]);
    } */
    onload = function(){//如果DOM对象是window,window可以省略
        console.log(document.getElementsByClassName("box")[0]);
    }
    </script>
    <body></body>

​ onunload:当页面关闭或被切换到其他页面时被触发,一般用于垃圾回收处理,清空缓存等操作。

​ onabort:忽略错误事件(不常用)

​ onerror:当页面有错误时将触发该事件

javascript
 window.onerror = function () {
    	alert("页面有错误");
	};

​ onselect:选中表单元素中的文本内容时触发 (在input表单中选中输入的文字后触发)

​ onresize:改变窗口大小时触发(作用于window对象)(常用)

​ 示例:

javascript
 window.onresize = function () {
      console.log(window.innerHeight);//窗口大小变化,输出当前页面高度
    };

​ onscroll:滚动页面滚动条时触发(常用)

​ 示例:

javascript
 window.onscroll = function () {
      console.log(Math.round(window.scrollY));//窗口滚动条滚动时,获取距页面顶部距离
    };
//添加事件监听写法
 window.addEventListener("scroll",function () { 
  console.log(Math.round(window.scrollY));
 })
2)焦点事件(一般作用在表单组件上)

​ onfocus:获得焦点时触发(掌握)

​ onblur:失去焦点时触发(掌握)

​ 示例:

javascript
    var boxes = document.querySelectorAll("input");
    //遍历input
    for (var i in boxes) {
      // 获得焦点 ,添加默认值
      boxes[i].onfocus = function () {
        this.value = "这是默认内容";
      };
      //失焦 ,清空默认值
      boxes[i].onblur = function () {
        this.value = "";
      };
    }

​ onfocusin:获得焦点时触发(在子元素上也会触发,一般与focusout结合使用)

​ onfocusout:失去焦点时触发(在子元素上也会触发,一般与focusin结合使用)

​ tips:

​ 可以直接在DOM节点上绑定事件

html
<html>
    <input onfocus="setValue()" class="price" type="text" /> //setValue()为自定义函数
</html>
<script>
  function setValue() {
   document.querySelector(".price").value = 99.99;
    }
</script>
3)鼠标事件

​ onclick:单击事件(相当于onmousedown和onmouseup)(掌握)

​ ondblclick:双击事件

javascript
 //设置一个标识位,实现双击字体变大,再双击字体变小
    var flag = 0;
    oBox.ondblclick = function () {
      console.log("这是双击事件");
      if (flag === 0) {
        this.style.fontSize = "20px";
        flag = 1;
      } else {
        this.style.fontSize = "14px";
        flag = 0;
      }
    };

​ onmousedown:按下鼠标键(掌握)

​ onmouseup:松开鼠标键(掌握)

​ onmousemove:移动鼠标键(掌握)

​ onmouseover:鼠标移入(经过)(会触发事件冒泡)(掌握)

​ onmouseout:鼠标离开(会触发事件冒泡)(掌握)

​ onmouseenter:鼠标移入(不会触发事件冒泡)(掌握)

​ onmouseleave:鼠标离开(不会触发事件冒泡)(掌握)

​ tips:

​ 事件冒泡:如果多个元素之间是一个包含的关系,并且绑定同一个事件,触发子元素绑定的事件时父元素绑定的事件也会触发,它是由子元素向父元素触发,这种情况称为事件冒泡。即子元素会先执行自己绑定的事件,然后再执行父节点绑定的相同的事件。

​ 阻止事件冒泡:只执行自身绑定的事件,不会继续执行父节点绑定的相同事件。

4)鼠标滚轮事件

​ onmousewheel:鼠标滚轮滚动时触发(火狐不支持,用mousescroll实现)

​ 滚轮向下滚动:e.deltaY为正数,滚轮向上滚动:e.deltaY为负数

javascript
oBxo.onmousewheel = function (e) {
	//滚轮向下滚动:
    e.deltaY为正数 e.deltaX=0,e.wheelDelta为负数,且两个值的绝对值不一定相同
	//滚轮向上滚动:
    e.deltaY为负数,e.wheelDelta为正数,且两个值的绝对值不一定相同
	//FF,IE,Chrome可以识别 event.deltaY
	//Edge识别event.deltaY和event.wheelDelta
	 if (e.deltaY > 0||e.wheelDelta<0) {//兼容处理判断滚轮滚动方向
      }
  }
5)键盘事件(一般用于表单组件)

​ onkeydown:按下键盘键(掌握)

​ 对象属性.keyCode获取键值(包含功能键) 不区分大小写 都按大写

​ onkeyup:松开键盘键(掌握)

​ onkeypress:输入字符(等价于onkeydown+onkeyup)(掌握)

​ 对象属性.keyCode用来获取输入字符的ASCII码值(不包含功能键)区分大小写

​ 注意:

​ onkeydown和onkeyup一般用来获取按下的键的键值,功能键也有键值,不区分字母大小写

​ onkeypress一般用来获取输入字符的ASCII码值,不识别功能键,区分字母大小写

​ 示例:

html
<body>
    <input type="text" />
  </body>
  <script>
    var oInput = document.querySelector("input");
    oInput.onkeydown = function (e) {
      console.log("keydown"+e.keyCode); 
      //keyCode用来获取键值(包含功能键) 不区分大小写 都按大写
      //当按下回车键输出表单内的值
      if (e.keyCode == 13) {
        console.log(this.value);
      }
    };

    oInput.onkeypress = function (e) {
      console.log("keypress"+e.keyCode);    
      //keyCode用来获取输入字符的ASCII码值(不包含功能键)区分大小写
    };

    //失焦验证
    oInput.onblur = function (e) {
      //正则验证
      if (!/^[A-Za-z0-9_\-]{4,6}$/.test(this.value)) {
        //输入不合法时
        // alert("您输入的内容格式不正确");
      }
    };
  </script>

4.事件对象

1)什么是事件对象?

​ 事件发生之后,会产生一个事件对象,作为参数传给监听函数。浏览器的原生提供了一个Event对象,所有的事件都是这个对象的实例。

​ 语法:

​ new Event(type,option)

​ type:事件名(一般是用户自定义的)

​ option:事件对象的配置由两个重要的参数:

​ bubbles:true/flase,是否允许冒泡

​ cancelable:true/flase,表示事件是否可以取消

​ 示例:

javascript
    // 事件对象实例化
    var event1 = new Event("watch", {
      bubbles: true,
      cancelable: false,
    });
    // 事件监听
    document.querySelector(".test").addEventListener("watch",fn)
    function fn(){
        console.log("这是自定义事件的触发");
    }
    //触发事件(用户自定义事件的触发)
    document.querySelector(".test").dispatchEvent(event1)
2)事件对象常用的属性和方法

​ e.target:获取当前事件触发的DOM节点

​ e.type:当前触发的事件名(只读,不可修改)

​ e.eventPhase:返回当前事件所处的阶段(只读,不可修改)

​ 0:事件没有发生

​ 1:处于捕获阶段

​ 2:事件到达了目标点

​ 3:事件处于冒泡阶段

​ e.canselable:表示事件是否可以取消(只读,不可修改)

​ e.preventDefault():阻止默认行为(如a标签的跳转功能)

​ e.stopPropagation():阻止冒泡

​ 示例:

javascript
var oBox = document.querySelector(".box");
    oBox.onclick = function (e) {
      console.log(e);
      console.log(e.target);
      //对获取到的节点进行操作(单击文本后变色)
      e.target.style.color = "aqua";
      console.log("事件名为" + e.type);
      console.log("事件处于第几阶段" + e.eventPhase);
    };
3)事件对象兼容写法(IE9以下的版本不支持)

​ Ⅰ)event对象兼容写法

​ var e = event || window.event;

​ Ⅱ)获取目标对象兼容写法

​ event.target || event.srcElement

html
 <body>
    <div class="box" onclick="getEvent(event)">事件兼容写法</div>
  </body>
  <script>
    function getEvent(e) {
      //event对象兼容写法
      var e = e || window.event;
      //获取目标对象兼容写法                                                             
      console.log(target);
      target = e.target || e.srcElement;
      target.style.color = "aqua";
    }
   </script>

​ Ⅲ)阻止默认行为兼容写法

​ event.preventDefault ? event.preventDefault () : event.returnValue = false

​ 或者:

​ return false;

​ 或:

javascript
	if (e.preventDefault) {
		e.preventDefault();
		} else {
		return false;
	 }
html
  </body>
    <a href="https:baidu.com">百度</a>
  </body>
  <script>
 //阻止默认行为兼容写法
    document.getElementsByTagName("a")[0].onclick = function (e) {
      var e = e || window.event;
      e.preventDefault ? e.preventDefault() : e.returnValue = false;
      //或
      if (e.preventDefault) {
        e.preventDefault();
      } else {
        return false;
      }
      //或
      return false;
    };
  </script>

​ Ⅳ)阻止冒泡的兼容写法

​ event.stopPropagation ? event.stopPropagation () : event.cancelBuble = true

html
  <body>
	<div id="father">
      <div id="son"></div>
    </div>
  </body>
  <script>
    var father = document.getElementById("father");
    var son = document.getElementById("son");
    father.onclick = function () {
      console.log("father");
    };
    son.onclick = function (e) {
      //阻止son的冒泡行为
      var e = e || window.event;
      e.stoppropagation ? e.stoppropagation() : (e.cancelBubble = true);
      console.log("son");
    };
  </script>

5.网页中常用坐标

1)获取屏幕的宽高

​ screen.width //计算机屏幕的宽

​ screen.height //计算机屏幕的高

​ screen.availWidth //计算机屏幕可用宽度,屏幕的像素宽度减去系统部件高度之后的值

​ screen.availHeight //计算机屏幕可用高度,屏幕的像素高度减去系统部件高度之后的值(不包含底部任务栏的高度)

​ tips:

​ screen.width与screen.availWidth相同,screen.height与screen.availHeight相差一个任务栏的高度

2)获得窗口位置及大小

​ window.screenTop //打开的浏览器窗口顶部距计算机屏幕顶部的距离

​ (会根据窗口的位置的不同而改变,可以获取到窗口左上角据屏幕左上角的距离)

​ window.screenLeft //打开的浏览器窗口左侧距离计算机屏幕左侧的距离

window.innerWidth //浏览器窗口中可视区域(viewpoint)的宽度 不同浏览器结果一样(常用)

window.innerHeight //浏览器窗口中可视区域(viewpoint)的高度 该值与浏览器是否显示菜单栏等因素有关(常用)

​ window.outerWidth //浏览器窗口本身的宽度(可视区域宽度+浏览器边框宽度)不同浏览器结果不同

​ window.outerHeight //浏览器窗口本身的高度

​ 注意:

​ chrome在最大化时浏览器窗口没有边框值,非最大化时有8px边框

​ FF(火狐)和IE上下左右有8px的边框宽度

3)元素对象的信息

​ 盒子真实大小: boxWidth = 2margin + 2border + 2padding + width boxHeight = 2margin + 2border + 2padding + height

element.offsetWidth :获取元素宽度,返回元素的宽度包括边框和填充,但不包括外边距(常用)

​ element.offsetWidth = 2border + 2padding + width

element.offsetHeight :获取元素高度,返回元素的高度包括边框和填 充,但不包括外边距(常用) ​ element.offsetHeight = 2border + 2padding + height

element.offsetLeft: 获取元素到屏幕最左侧距离(包括滚动条内的距离),对象相对于版面或由 offsetLeft属性指定的父坐标的计算 左侧位置(常用) ​ element.offsetTop : 获取元素到屏幕最顶部距离(包括滚动条内的距离),对象相对于版面或由 offsetTop属性指定的父坐标的计算 顶端位置(常用)

element.clientHeight:在页面上返回内容的可视高度(不包括边框和滚动条内的区域) ​ clientHeight = 2*padding + height - scrollbarHeight

element.clientWidth :在页面上返回内容的可视宽度(不包括边框和滚动条内的区域)

​ clientWidth = 2*padding + width - scrollbarWidth

element.offsetWidth==element.clientWidth true (在无滚动条时)

获取浏览器可视区宽高(不包括边框,边距或滚动条)var height = document.documentElement.clientHeight;var width = document.documentElement.clientWidth;

获取页面全部区域宽高(包括边框,边距或滚动条)var height = document.documentElement.offsetHeight;var width = document.documentElement.offsetWidth;

element.scrollTop:向下滑动滚动块时元素隐藏内容的高度。不设置时默认为0,其值随着滚动块滚动而变化(常用) ​ element.scrollLeft: 向右滑动滚动块时元素隐藏内容的宽度。不设置时默认为0,其值随着滚动块滚动而变化(常用)

​ document.documentElement.scrollTop,document.documentElement.scrollLeft

获取被滚动条隐藏的高度/宽度

var height = document.documentElement.scrollHeight;var width = document.documentElement.scrollWidth;

​ element.scrollWidth :返回元素的整个宽度(包括带滚动条的隐蔽的地方) ​ scrollWidth = 2*padding + width

​ element.scrollHeight :返回整个元素的高度(包括带滚动条的隐蔽的地方) ​ scrollHeight = 2*padding + width

4)event对象中的坐标信息

​ event.pageX:相对整个页面的坐标,以页面的左上角为坐标原点到鼠标所在点的水平距离(IE8不支持)

​ event.pageY:相对整个页面的坐标,以页面的左上角为坐标原点到鼠标所在点的垂直距离(IE8不支持)与offset效果相同

e.clientX:相对浏览器可视区域的坐标,以浏览器可视区域左上角为坐标原点到鼠标所在点的水平距离(常用)(不包括滚顶条内的距离) ​ e.clientY:相对浏览器可视区域的坐标,以浏览器可视区域左上角为坐标原点到鼠标所在点的垂直距离(常用)(不包括滚顶条内的距离)

​ event.screenX:相对电脑屏幕的坐标,以屏幕左上角为坐标原点到鼠标所在点的水平距离(与浏览器窗口大小无关) ​ event.screenY:相对电脑屏幕的坐标,以屏幕左上角为坐标原点到鼠标所在点的垂直距离(与浏览器窗口大小无关)

​ **event.offsetX:**相对于自身的坐标,以自身的border左上角为坐标原点到鼠标所在点的水平距离(包括滚顶条内的距离) ​ **event.offsetY:**相对于自身的坐标,以自身的border左上角为坐标原点到鼠标所在点的水平距离(包括滚顶条内的距离)

实训一:会伸缩的盒子

​ 需求:创建一个div块,向上滚动鼠标滚轮减少div块的高度,向下滚动鼠标滚轮增加div块的高度

实训二:可以拖拽的窗口

​ 创建一个div块,用鼠标事件实现拖拽功能。需求:

​ 1)当鼠标放在div块上时,按下鼠标左键并移动鼠标,div块随之运动;

​ 2)当松开鼠标左键,div块停止运动

JavaScript优化(重要)

1.事件流

​ 事件流指的是从页面中接收事件的顺序。

​ 分为冒泡流和捕获流。

​ DOM二级事件规定事件流包括三个阶段:

​ ①.事件捕获阶段

​ ②.处于目标阶段

​ ③.事件冒泡阶段

​ eg:触发div上的(text)事件的执行顺序

image-20230816145454922

​ DOM在触发事件后,会经历事件捕获和事件冒泡两个重要阶段。

2.事件冒泡

​ 事件冒泡:如果多个元素之间是一个包含的关系,并且绑定同一个事件,触发子元素绑定事件时父元素绑定的相同事件也会触发,它是由子元素向父元素触发,这种情况称为事件冒泡。即由最里层向最外层触发事件的过程。

​ 示例:

html
  <body>
    <div class="parent">
      <div class="child">please click me!</div>
    </div>
  </body>
  <script>
    // 获取DOM
    var parent = document.getElementsByClassName("parent")[0];
    var child = document.getElementsByClassName("child")[0];

    // child
    child.addEventListener(
      "click",
      function () {
        console.log("child");
      },
      false
    ); //false:事件冒泡(默认) true:事件捕获

    // parent
    parent.addEventListener(
      "click",
      function () {
        console.log("parent");
      },
      false//false:事件冒泡 true:事件捕获
    );

    // body
    document.body.addEventListener(
      "click",
      function () {
        console.log("body");
      },
      false//false:事件冒泡 true:事件捕获
    );

    // document
    document.addEventListener(
      "click",
      function () {
        console.log("document");
      },
      false//false:事件冒泡 true:事件捕获
    );

    // window
    window.addEventListener(
      "click",
      function () {
        console.log("window");
      },
      false//false:事件冒泡 true:事件捕获
    );
  </script>
</html>

​ 如果单击了child,点击事件会按child->parent->body->document->window顺序触发,相当于逐级向上触发,这种情况称为事件冒泡。

image-20230816151902280

3.事件捕获

​ 由最外层向最里层触发事件的过程,叫事件捕获(这个过程与事件冒泡的过程是相反的)

​ 语法:

​ DOM.addEventListener(”事件名“,callback,true)

​ 注意:

​ ①.如果想要把事件冒泡改为事件捕获,要用addEventListener去写事件监听,不要直接用onXXX事件去写

​ ②.将触发的子元素的父元素以上的元素的事件用true实现

​ 如果单击了child,点击事件会按window>document->body->parent->child-顺序进行捕获

image-20230816154421135

4.阻止事件冒泡

​ 阻止事件冒泡兼容写法:

​ event.stopPropagation ? event.stopPropagation() : (event.cancelBubble = true);

​ 不考虑兼容:

​ event.stopPropagation() ;

​ 示例:

javascript
	// child
    child.addEventListener(
      "click",
      function (e) {
        // 兼容event
        var e = e || window.event;
        // 阻止事件冒泡兼容写法
        e.stopPropagation ? e.stopPropagation() : (e.cancelBubble = true);
        console.log("child");
      },
      false
    ); //false:事件冒泡 true:事件捕获

5.事件委托(重要)

​ 将所有子元素的事件写到(委托)父元素上,这样的过程叫事件委托。即将子元素的事件绑定到父元素上,通过事件对象event.target对子元素进行操作。

​ 使用场景:

​ 1)将多个子元素的事件委托给父元素完成(基于性能优化的考虑)

​ 2)在动态加进来的元素上绑定事件

​ tips:

​ 若在子元素内触发了父元素绑定的事件,则该事件也会被执行,但若在事件触发的回调函数中传递event事件对象,当在子元素中触发事件,event事件对象为子元素,当在父元素中触发事件,event事件对象为父元素。因此可以将子元素的事件委托给父元素,当在子元素上触发该事件时可以通过event对象对子元素进行操作。

​ 示例:

html
  <body>
    <ul id="list">
      <li>li1</li>
      <li>li2</li>
      <li>li3</li>
      <li>li4</li>
    </ul>
  </body>
  <script src="" data-missing="eventUtil(事件委托JS文件).js"></script>
  <!-- 引入单独的事件委托JS文件 -->
  <script>
    // 封装一个事件委托的对象  可以将此部分独立为一个JS文件
    var EventUtil = {
      // event对象兼容处理
      getEvent: function (event) {
        return event || window.event;
      },
      // 目标对象兼容处理
      getTarget: function (event) {
        return event.target || event.srcElement;
      },
      // 绑定事件兼容处理
      addHandler: function (element, type, handler) {
        if (element.addEventListener) {
          //如果浏览器识别addEventListener()则不需要做兼容
          element.addEventListener(type, handler, false);
        } else if (element.attachEvent) {
          //兼容IE低版本
          element.attachEvent("on" + type, handler);
        } else {
          element["on" + type] = handler;
          //不可以直接用.语法,type为变量
        }
      },
    };
    var oList = document.getElementById("list");
    // 将事件绑定(委托)给父级list
    EventUtil.addHandler(oList, "click", function (event) {
      //获取事件对象
      event = EventUtil.getEvent(event);
      // 获取目标对象
      var target = EventUtil.getTarget(event);
      console.log(target);
      target.style.color = "aqua";
      target.innerHTML += "<strong>aaa</strong>";
    });
  </script>
</html>

6.懒加载(重要)

1)什么是懒加载

​ 懒加载也就是延迟加载。当访问一个页面的时候,先把img元素或是其他元素的背景图片路径替换成一张大小为1px*1px图片的路径(这样只需请求一次,俗称占位图),只有当图片出现在浏览器的可视区域内时,才设置图片真正的路径,让图片显示出来。这就是图片懒加载。

2)使用场景

​ 当网站的图片很多或图片比较大时,基于网站性能和用户体验考虑,这是需要用到懒加载。

​ 很多页面,内容很丰富,页面很长,图片较多。比如说各种商城页面。这些页面的图片数量多,而且比较大,少说百来k,多则上兆。要是页面载入就一次性加载完毕,估计用户已经失去耐心关闭网页了。

3)原理

​ 初始时,img中的src不赋值(或者赋予一个占位图片),而是将真正的图片地址存在用户自定义属性data-src,当鼠标滚动到可视区时,这时用data-src中的值替换src中的值。

4)优点

​ 页面加载速度快,可以减轻服务器的压力,节约了流量,用户体验好。

5)步骤

​ Ⅰ)获取可视区的高度

​ var height = document.documentElement.clientHeight;

​ Ⅱ)获取滚动条的位置(处理IE低版本兼容)

​ var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;

​ Ⅲ)遍历图片 for (var i = 0; i < imgs.length; i++)

​ Ⅳ)判断图片是否可以显示 ​ 可视区高度+滚动条内的距离>图片到顶端的距离 即图片即将到达要显示区域开始加载图片 ​ if (imgs[i].offsetTop <= height + scrollTop)

​ Ⅴ)判断图片是否为未加载

​ 避免重复加载已加载图片,图片的src的属性为空或没有src属性即还未加载 ​ if (imgs[i].getAttribute("src") == "" || !imgs[i].getAttribute("src"))

​ Ⅶ)修改图片路径 ​ // imgs[i].setAttribute("src", imgs[i].getAttribute("data-src"));或 ​ imgs[i].src = imgs[i].getAttribute("data-src");

html
   <style>
      * {
        margin: 0;
        padding: 0;
      }
      .box {
        width: 800px;
        margin: 0 auto;
      }
      .lazy-load {
        width: 800px;
        height: 300px;
        display: block;
        background-color: #eee;
        /* 取消轮廓线 */
        outline: none;
        border: 0;
      }
    </style>
  </head>
  <body>
    <div class="box">
   <!--给图片设置自定义属性 data-scr="实际要加载图片路径"-->
      <img class="lazy-load" data-src="" data-missing="001.jpg" />
      <img class="lazy-load" data-src="" data-missing="002.jpg" />
      <img class="lazy-load" data-src="" data-missing="003.jpg" />
      <img class="lazy-load" data-src="" data-missing="004.jpg" />
    </div>
  </body>
  <!-- 引入懒加载JS文件 -->
  <script src="" data-missing="lazyLoad(懒加载).js"></script>
  <script>
    //获取DOM
    var imgs = document.getElementsByClassName("lazy-load");
    //在屏幕滚动前先调用一次懒加载把可以加载的图片加载完成
    lazyLoad(imgs);
    // 当屏幕滚动时调用懒加载
    window.onscroll = function () {
      lazyLoad(imgs);
    };
  </script>
</html>

​ 懒加载JS文件,也可写在< script >内

javascript
// n 存储图片加载到的位置,避免每次从第一张图开始遍历
var n = 0;
// 封装一个懒加载函数
function lazyLoad(imgs) {
  // console.log(imgs);
  //Ⅰ)获取可视区的高度
  var height = document.documentElement.clientHeight;
  // var height = window.innerHeight;
  // window.innerHeight包含边框,document.clientHeight 不包含边框
  // Ⅱ)获取滚动条的位置(处理IE低版本兼容)
  var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  // Ⅲ)遍历图片
  for (var i = n; i < imgs.length; i++) {
    // console.log(imgs[i].getAttribute("src"));
  // Ⅳ)判断图片是否可以显示
    //可视区高度+滚动条内的距离>图片到顶端的距离 即图片到达显示区域可以加载图片
    if (imgs[i].offsetTop <= height + scrollTop) {
  // Ⅴ)判断图片是否为未加载
      // 避免重复加载已加载图片,图片的src的属性为空即还未加载
      if (imgs[i].getAttribute("src") == "" || !imgs[i].getAttribute("src")) {
  // Ⅶ)修改图片路径
        // imgs[i].setAttribute("src", imgs[i].getAttribute("data-src"));
        imgs[i].src = imgs[i].getAttribute("data-src");
      }
      n = i + 1;
    }
  }
}

7.预加载(重要)

1)什么是预加载?

​ 提前加载图片,当用户需要查看时可以直接从本地缓存中渲染

2)为什么要使用预加载?

​ 图片预先加载到浏览器中,方便网站上冲浪,它保证了图片快速,无缝地发布,增加用户体验。

3)实现预加载的方法

​ ①.用CSS和Javascript实现预加载

​ 如果用CSS实现预加载,如果中间某一个图太大,可能会导致后面图加载不进来的情况,在前端渲染可能渲染不出来。

​ 示例:

css
<style>
        #preload1{
            background: url(./images/001.jpg) no-repeat -9999px -9999px;
        }
        #preload2{
            background: url(./images/002.jpg) no-repeat -9999px -9999px;
        }
        #preload3{
            background: url(./images/003.jpg) no-repeat -9999px -9999px;
        }
        #preload4{
            background: url(./images/004.jpg) no-repeat -9999px -9999px;
        }
    </style>

​ ②.仅使用Javascript实现预加载(推荐写法)

​ 实例:

html
<script>
    // 预加载图片
    function preloader() {
      if (document.images) {
        // 实例化图片对象
        var img1 = new Image();
        var img2 = new Image();
        var img3 = new Image();
        var img4 = new Image();
        // 为图片对象添加属性
        img1.src = "./images/001.jpg";
        img2.src = "./images/002.jpg";
        img3.src = "./images/003.jpg";
        img4.src = "./images/004.jpg";
        // 或
         var img1 = new Image().src = "./images/001.jpg";
         var img2 = new Image().src = "./images/002.jpg";
         var img3 = new Image().src = "./images/003.jpg";
         var img4 = new Image().src = "./images/004.jpg";
      }
    }
    // 监听页面是否加载完成,只有加载完成后,才执行预加载
    function addLoadEvent(fn) {
      var load = window.onload;
      // 判断DOM是否加载完毕 加载完成后会获得回调函数
      if (typeof window.onload == "function") {
        // 页面加载完成,执行预加载
        window.onload = fn;
      } else {
        window.onload = function () {
          if (load) {
            load();
          }
          fn();
        };
      }
    }
    addLoadEvent(preloader);	
  </script>

​ ③.使用Ajax实现预加载(推荐写法)

javascript
<script>
    // 页面加载完成后再执行
    onload = function () {
      // 使用定时器的目的:避免请求的时间过长
      setTimeout(function () {
        // 实例化请求对象
        var xhr1 = new XMLHttpRequest();
        // 创建请求
        xhr1.open(
          "get", //请求方式
          "https://cdn.bootcdn.net/ajax/libs/jquery/3.3.1/jquery.min.js",//请求对象地址
          true	//是否异步请求
        );
        // 向后台发送请求
        xhr1.send();

        var xhr3 = new XMLHttpRequest();
        xhr3.open(
          "get",
"https://img1.baidu.com/it/u=3750617966,2166234761&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1692378000&t=92ac7061344549bd062c8083af726dbd",
          true
        );
        xhr3.send();
         
        // 调用数据
        xhr1.onreadystatechange = function () {
         //请求成功
        if (xhr1.readyState == 4 && xhr1.status == 200) {
         //responseText:获取请求的结果
          console.log(xhr1.responseText);
        }
      };
      }, 1000);
    };
  </script>

8.懒加载和预加载的对比

1)概念不同

​ 懒加载也叫延迟加载:JS图片延迟加载,延迟加载图片或符合某些条件时才加载某些图片。

​ 预加载:提前加载图片,当用户需要查看时可以直接从本地缓存中渲染。

2)技术区别

​ 两种技术的本质:两者的行为是相反的,一个是提前加载,一个是缓迟甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。

3)懒加载的意义及实现方式

​ 意义:

​ 懒加载的主要目的是作为服务器前端的优化,减少请求或延迟请求次数。

​ 实现方式:

​ ①.第一种是纯粹的延迟加载,使用setTimeOut或setInterval进行加载延迟。

​ ②.第二种是条件加载,符合某些条件,或触发了某些事件才开始进行异步加载。

​ ③.第三种是可视区加载,即仅加载用户可以看到的区域,在这个主要监控滚动条来实现,一般会在距用户看到某图片前一定距离时开始加载,这样能保证用户下拉时正好能看到图片。(推荐写法)

4)预加载的意义及实现方式

​ 意义:

​ 预加载可以说是牺牲服务器前端性能,换取更好的用户体验,这样可以使用户的操作得到最快的反应

​ 实现方式:

​ ①.用CSS和Javascript实现预加载

​ ②.仅使用Javascript实现预加载(推荐写法)

​ ③.使用Ajax实现预加载(推荐写法)

实训一:利用插件实现图片懒加载

案例

1.原生JS返回顶部效果

1)需求分析

​ ①.创建一个有滚动条的界面;

​ ②.当滚动条滚动的距离大于可视区的高度时,显示“回到顶部”,否则隐藏;

​ ③.点击回到顶部时,页面快速滚动到顶部,同时“回到顶部”消失

2)页面布局

​ ①.HTML

html
	<!-- 回到顶部 -->
    <a class="go-top" href="javascript:void(0)"></a>
    <!-- href="javascript:void(0)" 阻止a标签默认行为 -->

​ ②.CSS

​ “回到顶部“要用到固定定位

3)逻辑实现

​ ①.获取可视区的高度

javascript
	var clientHeight = document.documentElement.clientHeight;
    // 或 var clientHeight1 = window.innerHeight;

​ ②.滚动时获取滚动条所在的位置

javascript
	var scrollTop = document.documentElement.scrollTop||document.body.scrollTop;

​ ③.当滚动条滚动的距离大于等于可视区的高度时,显示“回到顶部"火箭1图片,否则隐藏;

javascript
	if(scrollTop >= clientHeight){
            goTop.style.display = "block";
        //不能在此处设置“返回顶部图片”火箭1,防止无法显示火箭2图片
        }else{
            goTop.style.display = "none";
            goTop.style.background = "url(./images/huojian1.jpg) no-repeat";
        //当火箭2返回时,滚动条距离小于屏幕高度时不显示火箭图片,并将火箭2该为火箭1图片,便于下次使用
        //解决了在下次使用时仍为火箭2图片的问题
     }

​ ④.单击”回到顶部“将快速回到顶部

javascript
 timer = setInterval(function () {
          // 单击向上滚动时”回到顶部“切换第二张图片
          goTop.style.background = "url(./images/huojian2.jpg) no-repeat";
          // 每次向上移动后重新获取滚动条距离顶部的位置
          var scrollTop =
            document.documentElement.scrollTop;
          //设置速度  实现动态向上滚动
          var speed = Math.floor(-scrollTop / 100);
          // 每隔1ms 高度减去一个值 直到减完为止  (设置窗口距离顶端的距离)
          document.documentElement.scrollTop =
            scrollTop + speed;
          // 当距离顶部距离为0时移除定时器,避免一直执行
          if (scrollTop == 0) {
            clearInterval(timer);
          }
        }, 1);

​ ⑤.当回到顶部时一定要清除定时器

javascript
	clearInterval(timer);

2.原生JS折叠菜单(手风琴菜单)效果

1)需求分析

​ ①.创建一个垂直的菜单,初始时显示第一个菜单的内容,其他菜单项的内容隐藏(布局+样式);

​ ②.单击某项菜单时,将展开该菜单,其他菜单的内容则全收起来。

2)布局
3)逻辑实现

​ 当点击某个标题时,之前已经展开的详细内容进行隐藏,当前标题下的详细内容进行展示。

javascript
   //直接用for循环嵌套事件不可行。因为for先于回调函数(异步)执行完,如果在回调函数中获取i的值将得到的是for循环完的值
   // 方法一:将for循环中的var改为let,let有闭包的作用(let属于ES6写法)
    for (let i = 0; i < titles.length; i++) {
      // 单击标题
      titles[i].onclick = function () {
        for (let j = 0; j < texts.length; j++) {
          if (j == i) {
            // 将单击的标题下的p标签显示出来
            texts[j].style.display = "block";
          } else {
            // 将非单击标题下的p标签全部收起来
            texts[j].style.display = "none";
          }
        }
      };
    } 
    // 方法二:用JS闭包实现
    for (var i = 0; i < titles.length; i++) {
      (function (i) {
        titles[i].onclick = function () {
          for (var j = 0; j < texts.length; j++) {
            if (j == i) {
              // 将单击的标题下的p标签显示出来 h3与p标签的下标相对应
              texts[j].style.display = "block";
            } else {
              // 将非单击标题下的p标签全部收起来
              texts[j].style.display = "none";
            }
          }
        };
      })(i);
    }

3.选项卡

1)需求分析

​ 功能:在有限的空间呈现更多内容,一般用于分类实现,如商品分类,新闻分类,热销商品展示等。通过单击或鼠标经过选项卡标题时,呈现对应的内容。

​ 技术点:

​ ①布局

​ ②JS实现(保持标题的下标与内容的下标一致,即通过下标进行关联)

2)参考网站

http://sina.com.cn

http://taobao.com

http://jd.com

3)布局

​ 选项卡至少是两项及以上,但并不能太多,一般2-5项即可。

​ 设置 active show 类名用来设置被激活的状态,便于JS通过添加类名来设置激活状态。

4)逻辑实现方法一

​ 技术点:

​ ①通过绑定事件的方法传this(通过this可以获取到当前操作的DOM)

​ 注意:只能传this来获取当前DOM

html
<!-- 选项卡标题 -->
      <ul class="tab-header">
        <li class="active" onclick="tab(this)" onmouseover="tab(this)">
          前端开发
        </li>
        <li onclick="tab(this)" onmouseover="tab(this)">UI/UE设计</li>
        <li onclick="tab(this)" onmouseover="tab(this)">Python开发</li>
     </ul>

​ ②通过循环遍历进行选项卡标题和内容的关联(通过this获取当前触发事件的DOM然后通过for循环进行遍历,配合if语句将被触发的DOM设置激活状态,未被触发的DOM取消其激活状态。)

JavaScript
function tab(tab) {
      // 通过this可以获取当前DOM
      // console.log(tab);
      for (var i = 0; i < tHeadLis.length; i++) {
        if (tHeadLis[i] === tab) {
          // tab.classList.add("active");
          tHeadLis[i].classList.add("active");
          tContentLis[i].classList.add("show");
          console.log(tHeadLis[i]);
          //两者的下标相对应,通过下标相关联
        } else {
          tHeadLis[i].classList.remove("active");
          tContentLis[i].classList.remove("show");
        }
      }
    }

​ ③通过两个get获取不同ul内的li标签

html
 <!-- 选项卡标题 -->
      <ul class="tab-header">
        <li>UI/UE设计</li>
        <li>Python开发</li>
      </ul>
      <!-- 选项卡内容 -->
      <ul class="tab-content">
        <li>UI设置为产品的“脸面”</li>
        <li>Python是一个面向对象的解释型高级编程语言</li>
      </ul>
JavaScript
var tHeadLis = document
      .getElementsByClassName("tab-header")[0]
      .getElementsByTagName("li");
var tContentLis = document
      .getElementsByClassName("tab-content")[0]
      .getElementsByTagName("li");
5)逻辑实现方法二

​ 技术点:

​ ①不需要在DOM上绑定事件,传this,通过遍历DOM,触发事件调用函数传当前DOM。

JavaScript
    // 此做法不需要在li标签内绑定事件
      for (var i = 0, len = tHeadLis.length; i < len; i++) {
        tHeadLis[i].onmouseenter = showTab; 
        //注意:不是函数调用不能加()即showTab()会执行函数
      }

​ ②遍历DOM用this这个DOM进行匹配,匹配成功后在做相关操作。

​ 因为直接在事件的回调函数中无法拿到下标i,因此在事件的回调函数中再次遍历通过判断与事件的实例化对象相等时获取到下标。

JavaScript
    function showTab() {
      for (var i = 0, len = tHeadLis.length; i < len; i++) {
        if (tHeadLis[i] == this) {
          //this为当前触发onmouseenter的实例化对象
          tHeadLis[i].className = "active";
          tContentLis[i].className = "show";
        } else {
          tHeadLis[i].className = "";
          tContentLis[i].className = "";
        }
      }
    }

注意:

​ DOM的获取,及长度length的获取最好存在变量中,避免浏览器多次解析这是基于网站性能的考虑。

4. 城市级联

1)思路

​ ①引入城市相关信息

​ ②选择“省份/自治区”,市会加载对应省份/自治区相关数据,选择"市",县或区的数据也会跟市的数据匹配。

2)使用场景

​ ①城市定位

​ ②购物时接收货物的地址的填写

​ ③注册写入地址时

​ .........

3)参考效果

​ 58同城:https://www.58.com/changecity.html?fullpath=0&utm_source=market&spm=u-2d2yxv86y3v43nkddh1.BDPCPZ_BT&PGTID=0d100000-0092-6cca-e0a1-f2d5b6f4aef8&ClickID=5

4)实现效果

​ 城市联动(级联效果)

5)布局
6)逻辑实现

​ ①引入城市 city.js

html
 <script src="" data-missing="city.js"></script>

​ 注意:如果是JS文件,直接用script标签引入即可;如果是JSON或xml文件,需要用ajax请求过来。

​ ②动态加载省份

JavaScript
for (var i = 0, len = province.length; i < len; i++) {
    var el = document.createElement("option");
    el.innerText = province[i].name;
    el.value = i;
    prov.appendChild(el);
  }

​ ③改变省份时加载对应的城市数据

​ onchange()事件:改变input输入框的内容时会执行,单选框与复选框改变后也会被触发。

JavaScript
  prov.onchange = function () {
    // 先清空当前city中的全部option再进行添加
    city.innerHTML='<option disabled selected>选择市</option>';
    // 遍历添加城市
    for (var i = 0, len = province[this.value].city.length; i < len; i++) {
      var cit = document.createElement("option");
      cit.innerText = province[this.value].city[i].name;
      city.appendChild(cit);
    }
  };

​ 注意:在添加新的数据时要先清空当前city中的option再进行添加(要保留默认选项)。

JavaScript
area.innerHTML = "<option disabled selected>选择区/县</option>";

​ ④根据省份和选择的市动态加载区县

JavaScript
 city.onchange = function () {
    // 先清空
    //或 area.innerHTML = ""; 最好用下面写法 便于onchange事件的触发
    area.innerHTML = "<option disabled selected>选择区/县</option>";
     
    // 获取市对应省份的下标
    var provIndex = prov.value;
    //获取当前选择的市的下标
    var cityIndex = this.value;
    // 或 var cityIndex = city.value
     
    var areas = province[provIndex].city[cityIndex].districtAndCounty;
    // for (var i = 0, len = areas.length; i < len; i++) {
    for (var i in areas) {
      var are = document.createElement("option");
      are.innerText = areas[i];
      are.value = i;
      area.appendChild(are);
    }
  };

​ ⑤获取选择的地址信息

​ 使用事件嵌套事件的写法,将上一个事件获取的数据保存到变量中,减少下一个事件获取上一个事件内数据的代码量。

JavaScript
// 定义变量用于存放地址选择的结果
  var address = {
    province: "",
    city: "",
    area: "",
  };
  // 动态加载省份
  for (var i = 0, len = province.length; i < len; i++) {
    var el = document.createElement("option");
    el.innerText = province[i].name;
    el.value = i;
    // value存储省份的下标
    prov.appendChild(el);
  }

  // 改变省份时加载对应的城市数据
  prov.onchange = function () {
    // 先清空当前city中的全部option再进行添加
    city.innerHTML = "<option disabled selected>选择市</option>";
    // 省改变时区县也要清空
    area.innerHTML = "<option disabled selected>选择区/县</option>";
    // 遍历添加城市
    for (var i = 0, len = province[this.value].city.length; i < len; i++) {
      var cit = document.createElement("option");
      cit.innerText = province[this.value].city[i].name;
      // value存储市的下标
      cit.value = i;
      city.appendChild(cit);
    }
    // 三个onchange事件嵌套  将当前选择的省份暂存 便于后面操作
    var selectProv = province[this.value];
    // 将选择的省份存入address中
    address.province = province[this.value].name;

    // 根据省份和选择的市动态加载区县
    city.onchange = function () {
      // 先清空
      area.innerHTML = "<option disabled selected>选择区/县</option>";
      //获取市对应的全部区县
      var areas = selectProv.city[this.value].districtAndCounty;
      //遍历添加选项
      for (var i in areas) {
        var are = document.createElement("option");
        are.innerText = areas[i];
        are.value = i;
        area.appendChild(are);
      }
      // 三个onchange事件嵌套  将当前选择的市暂存 便于后面操作
      var selectArea = selectProv.city[this.value];
      // 将选择的市存入address中
      address.city = selectArea.name;

      area.onchange = function () {
        // 将选择的区县存入address中
        address.area = areas[this.value];
        console.log(address);
      };
    };
  };

​ ⑥将选择的地址信息写入文本框中

JavaScript
 // 如果选择的省 市 区县 都不为空时,按钮状态可用
        if (
          address.province != "" &&
          address.city != "" &&
          address.area != ""
        ) {
          //btnGetAddr.removeAttribute("disabled");
          btnGetAddr.disable = false;
        }

        // 如果提交按钮可用 就将数据显示在文本框中
        if (btnGetAddr.disable == false) {
          btnGetAddr.onclick = function () {
            var text = address.province + "-";
            text += address.city + "-";
            text += address.area;
            console.log(text);
            // 添加数据
            selAddr.value = text;
          };
        }

组件开发

1.放大镜案例

1)需求分析

​ 底部有缩略图,左边是小图,右边是大图,当鼠标放在缩略图上时,小图会自动切换成和缩略图一样的图,鼠标放在小图上时,右侧的大图会显示与小图对应位置上放大后的局部展示。

2)使用场景

​ 一般用于电商网站商品详情中商品图片细节展示。

3)实现原理

​ 当鼠标在小图片上移动时,通过捕捉在小图片上的位置,定位大图片的相对位置。

4)包含元素

​ 缩略图,小图,等比的大图,放大镜,大图的窗口。

5)技术

​ 布局:定位,蒙版(遮罩层)

​ 逻辑:获取定位,修改样式,获取元素的下标

6)准备图片

​ 在实际开发中,图片数据是从后台前端通过ajax获取的。

7)布局
css
 position: absolute;
  /*相对于父级定位,父级必须设置过定位(相对/绝对),父级若没定位则相对于(0,0)*/
8)逻辑实现

​ ①获取DOM封装

JavaScript
    function $(className) {
        return document.getElementsByClassName(className)[0];
      }

​ ②获取DOM

javascript
	var smallImg = $("small-img").getElementsByTagName("img")[0],
    ball = $("ball").getElementsByTagName("img")[0],
    bigImg = $("big-img").getElementsByTagName("img")[0],
    thums = $("thum").getElementsByTagName("li");

​ ③鼠标移动到缩略图上时更改小图和大图并与缩略图一致

JavaScript
 for (var i in thums) {
        thums[i].onmouseover = show;
        thums[i].onclick = show;
      //方法一:通过对获取到的图片的src进行切割获取到下标
      /* thums[i].onmouseover = function () {
        console.log(this.getElementsByTagName("img")[0].src);
        var index = this.getElementsByTagName("img")[0].src.substr(-5, 1);
        console.log(index);
      }; */
    }
    //方法二: 因为直接在事件的回调函数中无法拿到下标i,
    // 因此在事件的回调函数中再次遍历通过判断与事件的实例化对象相等时获取到下标
    function show() {
      for (var i in thums) {
        if (thums[i] == this) {
          // 更改小图和大图的src
          index = parseInt(i) + 1;
          smallImg.src = "./images/small0" + index + ".jpg";
          // 设置激活状态
          thums[i].className = "active";
        } else {
          thums[i].className = "";
        }
      }
    }

​ ④在小图中移动鼠标获取鼠标当前位置(左上角位置)

JavaScript
  var mouseX = e.clientX - ball.offsetWidth / 2 - 100;
  var mouseY = e.clientY - ball.offsetHeight / 2 - 20;

​ ⑤越界处理

JavaScript
  if (mouseX < 0) {
    mouseX = 0;
  } else if (mouseX >= smallImg.clientWidth - ball.offsetWidth) {
    mouseX = smallImg.clientWidth - ball.offsetWidth;
    // console.log(ball.offsetWidth == ball.clientWidth); //true 无滚动条时
  }
  if (mouseY < 0) {
    mouseY = 0;
  } else if (mouseY >= smallImg.clientHeight - ball.offsetHeight) {
    mouseY = smallImg.clientHeight - ball.offsetHeight;
  }

​ ⑥更改放大镜位置

前提:必须将其设置为绝对定位,让且脱离文档流。相对定位不脱离文档流。

脱离文档流的好处:不占用原本的位置,当其移动到其他位置时,它原本的位置可以让其他内容占用。

而其自身又可以覆盖在其他元素内容上,不占用其位置。

JavaScript
  ball.style.left = mouseX + "px";
  ball.style.top = mouseY + "px"

​ ⑦更改大图的位置(通过定位让大图移动位置)

​ 前提:必须将其设置为绝对定位,让且脱离文档流。相对定位不脱离文档流

JavaScript
  bigImg.style.left = mouseX * -2.28 + "px";
  bigImg.style.top = mouseY * -2.28 + "px";

​ -2.28:负号表示反向移动 ,2.28表示缩放比例

​ 大图的显示随着放大镜位置的改变而改变,但大图是改变的是图片的位置,因此需要反向移动,大图与小图有2.28倍的放缩比例,在通过小图的鼠标位置改变大图时需要*2.28得到大图位置

​ 设置放大镜与大图完整代码

JavaScript
smallImg.onmousemove = function (e) {
  var e = e || window.event;
  //鼠标在小图上移动时显示放大镜和大图
  ball.style.display = "block";
  bigImg.style.display = "block";

  // 获取鼠标的坐标
  var mouseX = e.clientX - ball.offsetWidth / 2 - 100;
  var mouseY = e.clientY - ball.offsetHeight / 2 - 20;
  // 越界处理
  if (mouseX < 0) {
    mouseX = 0;
  } else if (mouseX >= smallImg.clientWidth - ball.offsetWidth) {
    mouseX = smallImg.clientWidth - ball.offsetWidth;
  }
  if (mouseY < 0) {
    mouseY = 0;
  } else if (mouseY >= smallImg.clientHeight - ball.offsetHeight) {
    mouseY = smallImg.clientHeight - ball.offsetHeight;
  }

  //更改放大镜的位置
  ball.style.left = mouseX + "px";
  ball.style.top = mouseY + "px";

  // 更改大图的位置
  bigImg.style.left = mouseX * -2.28 + "px";
  bigImg.style.top = mouseY * -2.28 + "px";

  //鼠标离开时放大镜和大图隐藏
  smallImg.onmouseleave = function () {
    ball.style.display = "none";
    bigImg.style.display = "none";
  };
};

2.轮播图案例

​ 轮播图参考网站:https://www.jq22.com ->媒体导航

1)什么叫轮播?

​ 设置一组图片让其在规定的时间内轮流播放。

2)使用场景

​ 一般用于热销商品,新闻,产品公司形象宣传等。

3)为什么使用轮播?

​ 在有限的空间呈现更多内容。

4)使用轮播的注意事项

​ ①轮播一般放在显著位置。

​ ②轮播的图不宜太多,因为用户很少有耐心将其看完。

​ ③轮播图建议2-5张轮播即可。

​ ④轮播图单图播放时间一般在3-5秒之间(通过计时器实现)。

​ ⑤轮播图都是有跳转的(图要包裹在a标签中)

5)基本轮播的组成

​ 一般由三部分组成:

​ ①轮播组图

​ ②控制器

​ ③分页器

6)实现原理

​ 将一些图片在一行中平铺,然后计算偏移量再利用计时器实现定时播放。

轮播实现

1)简单轮播

​ 只有轮播图没有控制器和分页器。

实现逻辑:

​ 通过定时更改图片地址来实现。

html
<body>
    <div id="banner">
      <a href="1.html"><img src="" data-missing="1.jpg" alt="banner" /></a>
    </div>
  </body>
  <script>
    // mock(模拟)数据
    var imgs = [
      { src: "1.jpg", url: "1.html" },
      { src: "2.jpg", url: "2.html" },
      { src: "3.jpg", url: "3.html" },
      { src: "4.jpg", url: "4.html" },
    ];
    var idx = 0; //设置初始图,0表示第一张图 imgs[0]
    var timer = null; //初始化计时器
    // 获取DOM
    var banner = document.getElementById("banner");

    //循环遍历获取到的数据,动态改变图片和a标签跳转地址
    function carousel(imgs) {
      // 下标越界处理
      if (idx >= imgs.length - 1) {
        idx = 0;
      } else {
        idx++;
      }
      console.log(idx);
      // 动态改变组图和a标签跳转地址
      banner.children[0].href = imgs[idx].url;
      banner.getElementsByTagName("img")[0].src = "images/" + imgs[idx].src;
    }

    // 设置计时器
    timer = setInterval("carousel(imgs)", 3000); //3S重复调用函数,相当于执行了循环
    // setInterval 调用函数时必须加引号

    // 鼠标悬停到组图时将停止轮播
    banner.onmouseover = function () {
      // 清除轮播实现,停止轮播
      clearInterval(timer);
    };
    // 鼠标移走时将继续轮播
    banner.onmouseout = function () {
      timer = setInterval("carousel(imgs)", 3000);
    };
  </script>
2)叠加轮播

​ 通过定位将组图叠加在一起,定时依次显示每一张图,通过修改不透明度(opacity)/display/[show,fadeIn JQ]写法实现。

实现逻辑:

​ ①通过定位将所有组图叠加在一起。

​ ②通过遍历循环,将当前定时器过后要显示的图片显示,其他图片隐藏。

​ ③通过mouseover和mouseout来设置鼠标悬停在图片时停止轮播,离开后继续轮播。

​ ④可以通过为(opacity)添加过渡动画transition来使图片更换效果更加流畅。

注意:

​ 若将一个函数作为参数设置为一个事件的回调函数时,不能在函数后面加(),若函数有参数时要加上“ "

​ eg:timer = setInterval(animation, 3000); timer = setInterval("carousel(imgs)", 3000);

​ 参数animation相当于将animation函数内容放在原来的回调函数的位置充当其回调函数,若写animation()则相当于立即执行animation函数。

html
 <style>
      * {
        margin: 0;
        padding: 0;
        text-decoration: none;
        list-style: none;
      }
      #carousel {
        width: 800px;
        height: 400px;
        margin: 0 auto;
        position: relative;
      }
      #carousel ul {
        width: 800px;
        height: 400px;
      }
      #carousel img {
        width: 100%;
        height: 100%;
        display: block;
        position: absolute;
        left: 0;
        top: 0;
        /* 为img的opacity添加过渡动画 */
        transition: opacity 1s linear;
      }
    </style>
  </head>
  <body>
    <div id="carousel">
      <ul>
        <li>
          <a href="#"><img src="" data-missing="1.jpg" alt="banner" /></a>
        </li>
        <li>
          <a href="#"><img src="" data-missing="2.jpg" alt="banner" /></a>
        </li>
        <li>
          <a href="#"><img src="" data-missing="3.jpg" alt="banner" /></a>
        </li>
        <li>
          <a href="#"><img src="" data-missing="4.jpg" alt="banner" /></a>
        </li>
      </ul>
    </div>
  </body>
  <script>
    var carousel = document.getElementById("carousel");
    var imgs = document.getElementsByTagName("img");
    var timer = null;
    var idx = 0;

    function animation() {
      if (idx >= imgs.length - 1) {
        idx = 0; //如果到了最后一张图将重新回到第一张图,从而实现循环
      } else {
        idx++;
      }
      // 当idx的值发生改变时,显示下标为idx的这张图,其它图隐藏
      // 通过修改不透明度/display/[show,fadeIn JQ]写法实现
      for (var i = 0, len = imgs.length; i < len; i++) {
        if (i === idx) {
          imgs[i].style.cssText = "opacity:1;filter:alpha(opacity=100)";
          // imgs[i].style.display ="none";
          // imgs[i].fadeIn(200);JQ写法,原生不支持
        } else {
          imgs[i].style.cssText = "opacity:0;filter:alpha(opacity=0)";
          // imgs[i].style.display ="block";
          // imgs[i].fadeOut(200);
        }
      }
    }

    timer = setInterval(animation, 3000);
    // 参数animation相当于将animation函数内容放在原来的回调函数的位置充当其回调函数,
    // 若写animation()则相当于立即执行animation函数.
    //计时器中的变量相当于静态变量(函数执行完后变量不会立即从内存中销毁,仍会驻留内存一段时间)

    carousel.onmouseover = function () {
      clearInterval(timer);
    };
    carousel.onmouseout = function () {
      timer = setInterval(animation, 3000);
    };
  </script>
</html>
3)BootStrap中的轮播插件

​ 官网:https://v3.bootcss.com/javascript/#carousel

操作步骤:

​ ①引入CSS

html
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet">

​ ②引入JQ

html
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>

​ ③引入JS

html
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.1/js/bootstrap.min.js"></script>

​ ④复制轮播图(carousel)静态代码

​ ⑤修改HTML代码

​ ⑥通过样式覆盖修改其样式

​ ⑦通过API进行轮播时间修改

html
  <script>
    // 默认切换图片时间为5s,可以修改时间
    $(".carousel").carousel({
      interval: 3000,
    });
  </script>
4)使用swiper插件实现轮播(如果客户没有特别的要求,建议使用这种方法实现轮播)

​ ①官网:https://www.swiper.com.cn/

​ ②中文教程 --> 使用方法

image-20231022222049438

​ ③点击CDN 从CDN引入CSS

html
<link rel="stylesheet" href="https://unpkg.com/swiper@8/swiper-bundle.min.css">

​ ④从CDN引入JS

html
<script src="https://unpkg.com/swiper@8/swiper-bundle.min.js"> </script>

image-20231024224743051

​ ⑤从官网复制HTML布局代码

image-20231022231114375

​ ⑥样式覆盖

image-20231022231215062

​ ⑦初始化Swiper

image-20231022231232743

​ ⑦设置自动轮播

​ 进入API文档 --> 选择组件 --> autoplay -->复制代码

JavaScript
autoplay:true,//等同于以下设置
  /*autoplay: {
    delay: 3000,
    stopOnLastSlide: false,
    disableOnInteraction: true,
    },*/

image-20231022223836025

​ ⑥pagination分页器

image-20231022225250701image-20231022225326537

​ ⑦切换效果

​ Effect(切换效果)

image-20231022230204361image-20231022230241152

​ ⑧使用其他API实现想要的效果

image-20231022224846067image-20231022224915781

5)无缝连续滚动轮播(走马灯)

​ 实现一:将组图克隆一份,通过定时器,和改变scrollLeft实现。

​ 原理:多张图按设定的时间,每次滚动一定的距离。如果这组图播放完后,要想得到连续的效果需要将这组图再复制出一组来。

​ 核心代码:

JavaScript
   // 封装一个用id获取元素的函数
    function $(id) {
      return document.getElementById(id);
    }
    var tab = $("scroll");
    var tab1 = $("scroll1");
    var tab2 = $("scroll2");

	var timer = null; //定义计时器
    var speed = 15; //设置速度

    // 克隆scroll1到scroll2中
    tab2.innerHTML = tab1.innerHTML;

    function marquee() {
      if (tab.scrollLeft <= 0) {
        // offsetWidth:获取元素的宽度,scrollLeft:获取左侧滚动条内的宽度(初始值为0)
        tab.scrollLeft = tab.offsetWidth; //scroll1跑完接着跑scroll2中的图片
        // 初始时将scroll1藏进左滚动条中,显示scroll2
        // 当scroll1显示完后再重复将scroll1藏进左滚动条中,显示scroll2
      } else {
        // 减少左滚动条的宽度,让scroll1显示出来隐藏掉scroll2,
        tab.scrollLeft--; //按设定的时间,每次减1个像素
        // tab.scrollLeft++;//改变滚动方向
      }
    }

    timer = setInterval(marquee, speed);

​ 设置鼠标悬停

javascript
	tab.onmouseover = function () {
      clearInterval(timer);
    };
    tab.onmouseout = function () {
      timer = setInterval(marquee, speed);
    };

​ 实现一:不克隆组图,直接将要展示的组图全部添加到轮播图中,通过定时器,和改变scrollLeft实现。

​ 原理:设置初始时左侧滚动条内的宽度为=组图总宽度-轮播图显示区域的宽度,通过定时器每次减少左侧滚动条内的宽度,使组图整体向左移动,当组图移动到左侧滚动条内的宽度为0时,再恢复初始状态

​ 核心代码:

JavaScript
 var tab = $("scroll");
    // 获取轮播图中的任意一张图片,用于获取其宽度
    var img = tab.getElementsByTagName("img")[0];
    // imgs用于获取轮播图中一共有多少张组图
    var imgs = $("inScroll").getElementsByTagName("a");
    // 用组图个数乘与每张组图的宽度,获取组图的总宽度
    var tab1Width = imgs.length * img.offsetWidth;
    console.log(tab1Width);

    var timer = null; //定义计时器
    var speed = 15; //设置速度

    function marquee() {
      if (tab.scrollLeft <= 0) {
        // 初始时左侧滚动条内的宽度为=组图总宽度-轮播图显示区域的宽度
        // 当组图移动到左侧滚动条内的宽度为0时,再恢复初始状态
        tab.scrollLeft = tab1Width - tab.offsetWidth;
      } else {
        //每次减少左侧滚动条内的宽度,使组图整体向左移动
        tab.scrollLeft--; //按设定的时间,每次减1个像素
        // tab.scrollLeft++;//改变滚动方向
      }
    }
6)手写轮播图

​ ①轮播图一般由组图,控制器和分页器三部分组成。

​ ②原理:每张图像幻灯片一样轮流播放,每张图在时间到来之间,向左或向右移动一张图的距离,如此循环。(技术点:计时器,图片定位)

布局:

​ 三部分组成:轮播组图,控制器,分页器

html
<div class="carousel">
      <!-- 1.轮播组图 -->
      <ul class="imgs">
        <li>
          <a href="#"><img src="" data-missing="1.jpg" alt="banner" /></a>
        </li>
        <li>
          <a href="#"><img src="" data-missing="2.jpg" alt="banner" /></a>
        </li>
        <li>
          <a href="#"><img src="" data-missing="3.jpg" alt="banner" /></a>
        </li>
        <li>
          <a href="#"><img src="" data-missing="4.jpg" alt="banner" /></a>
        </li>
      </ul>
      <!-- 2.控制器 -->
      <div class="prev"></div>
      <div class="next"></div>
      <!-- 3.分页器 -->
      <div class="count">
        <ul>
          <li class="active">1</li>
          <li>2</li>
          <li>3</li>
          <li>4</li>
        </ul>
      </div>
    </div>

CSS:

​ Ⅰ)在最外层盒子上添加position: relative; overflow: hidden; 作用:用于定位,清除浮动和超出部分隐藏。

​ Ⅱ)设置组图盒子的宽度width: 9999px; 组图的宽度要大于实际所有的图片的宽度,便于扩展图片使用。

​ Ⅲ)设置组图盒子绝对定位position: absolute; left: 0; top: 0;

​ Ⅳ)为组图中的每个li标签设置左浮动,让其显示在一行

​ Ⅴ)为控制器添加绝对定位改变其到合适的位置,和为其添加hover效果

​ Ⅵ)为分页器设置绝对定位改变其到合适的位置,设置分页器中li标签的样式并为其设置激活效果

css
* {
  margin: 0;
  padding: 0;
  list-style: none;
}
/* 整体布局 */
.carousel {
  width: 800px;
  height: 400px;
  margin: 50px auto;
  background-color: #333;
  position: relative;
  overflow: hidden;
  /* ①作用:清除浮动
     ②作用:超出部分隐藏 */
}
/* 组图 */
.carousel .imgs {
  width: 1000%;
  /* width: 9999px; */
  /* 要超出的宽度=每张图的宽度 × 图像个数,方便扩展 */
  position: absolute;
  left: 0;
  top: 0;
  transition: left .5s ease;
  /* linear:匀速 ease:慢快慢 */
}
.carousel .imgs li {
  float: left;
}
.carousel .imgs li a {
  cursor: default;
}
.carousel .imgs img {
  width: 800px;
  height: 400px;
  border: none;
}
/* 控制器 */
.carousel .prev,
.carousel .next {
  width: 45px;
  height: 45px;
  position: absolute;
  top: 177px;
  z-index: 10;
  cursor: pointer;
}
.carousel .prev:hover,
.carousel .next:hover {
  background-color: aquamarine;
  border-radius: 50%;
  opacity: 0.8;
  filter: alpha(opacity=80);
}
.carousel .prev {
  background: url(./images/l.png);
  left: 20px;
}
.carousel .next {
  background: url(./images/r.png);
  right: 20px;
}
/* 计数器(分页器) */
.carousel .count {
  width: 800px;
  height: 15px;
  position: absolute;
  bottom: 15px;
  z-index: 10px;
}
.carousel .count ul {
  width: 120px;
  margin: 0 auto;
  overflow: hidden;
}
.carousel .count li {
  width: 15px;
  height: 15px;
  margin: 0 6px;
  border-radius: 50%;
  background-color: #eee;
  opacity: 0.8;
  filter: alpha(opacity=80);
  float: left;
  cursor: pointer;
  z-index: 10;
  text-align: center;
  font-size: 10px;
}
.carousel .count li.active {
  background: aqua;
  opacity: 1;
  filter: alpha(opacity=100);
  color: purple;
}

JS:

​ Ⅰ)可以封装一个用于获取DOM的函数便于DOM的获取

JavaScript
  function $(className) {
    return document.getElementsByClassName(className)[0];
  }

​ Ⅱ)获取DOM

​ Ⅲ)定义计时器赋初值为null,设置速度,当前显示图片的下标和获取每张图片的下标。

JavaScript
  var timer1 = null; //计数器1
  var timer2 = null; //计数器2
  var speed = 20; //速度
  var now = 0; //初始显示的是第一张图的下标
  var imgWidth = imgsLi[0].offsetWidth; //每张图的宽度

​ Ⅳ)定义一个move方法,实现组图的移动,让其移动到指定位置

​ 实现原理:组图每隔speed通过绝对定位设置left的值,实现移动一定距离((distance - left) / 3.3331),直至移动够distance的距离。

​ distance = -(now * imgWidth);当前图片的下标 × 每张图片的宽度,反向移动加负号。

​ now下标从0开始,当前图片的下标为n则前面共有n张图片,需要向左移动 -(n * imgWidth)的距离

JavaScript
function move(imgs, distance, speed) {
    // 当再次调用move时要先清除上次的定时器
    clearInterval(timer2);
    timer2 = setInterval(function () {
      // 方法一:
      /*       var left = imgs.offsetLeft;
      // left为imgs在左侧滚动条内隐藏的距离 left<0
      // distance - left:要向左移动的距离-左侧滚动条内已有的距离=剩下还要移动的距离
      var nowSpeed = (distance - left) / 5.5555; //设置速度 3.333:减小计算误差
      // 设置每次定时器结束后,定位left要增加的值
      imgs.style.left = left + nowSpeed + "px";
	 */
      //方法二:
      imgs.style.left = distance + "px";
      //让left直接等于要移动的全部距离(没有过渡效果)需要为left添加过渡动画。
      //对应的CSS:  transition: left .5s ease;  /* linear:匀速 ease:慢快慢 */
      //好处:可以避免计算不精确导致的1-4px的偏差
    }, speed);
  }

​ Ⅴ)定义一个tab函数实现改变分页器的外观且让图轮播起来

​ 实现原理:先清空所有分页器上的active类名,再为当前图片所在的分页器加上active类名,最后调用move()方法实现组图的轮播。

javascript
  function tab() {
    // 清空所有li标签上的active类名
    for (var i = 0, len = countLi.length; i < len; i++) {
      countLi[i].classList.remove("active");
    }
    // 给当前li添加active类
    countLi[now].classList.add("active");//now为全局变量,记录当前组图的下标。
    // 轮播组图
    move(imgs, -(now * imgWidth), speed);
    // -(now*imgWidth) 向左反方向移动,left<0
  }

​ Ⅵ)单击分页器显示对应的组图

​ 实现原理:用for循环为每一个li标签添加一个index属性用于保存下标,当单击分页器下的li标签时将index赋值给now,实现将分页器的下标与组图的顺序关联起来(通过index)再调用tab()方法实现轮播切换。

JavaScript
  for (var i = 0, len = countLi.length; i < len; i++) {
    // 为每一个li标签添加一个index属性用于保存下标
    countLi[i].index = i;
    countLi[i].onclick = function () {
      console.log(this.index);
      // 将当前点击的分页器的下标传给now
      // 将分页器的下标与轮播图的图关联起来(通过下标关联)
      now = this.index;
      tab();
    };
  }

​ Ⅶ)使用控制器实现左右翻页

​ 实现原理:单击向左翻页时now--;单击向右翻页时now++;

​ 注意:需要越界处理,向左翻页时当now==0时 now=imgsLi.length-1;向右翻页时当now==length-1时 now=0;(先判断再进行++/--)

javascript
 // 切换到上一张图(向左翻页)
  prev.onclick = function () {
    // 越界处理
    if (now == 0) {
      now = imgsLi.length - 1;
    } else {
      now--;
    }
    tab();
  };
  // 切换到下一张图(向左翻页)
  next.onclick = function () {
    if (now == imgsLi.length - 1) {
      now = 0;
    } else {
      now++;
    }
    tab();
  };

​ Ⅷ)自动轮播

​ 实现原理:设置一个定时器,让其在固定时间后调用向 prev.onclick/next.onclick实现向左自动轮播或向右自动轮播。

JavaScript
 timer1 = setInterval(next.onclick, 3000);

​ Ⅸ)鼠标悬停

​ 实现原理:通过对carousel盒子设置onmouseover/onmouseout 清除或添加计时器来实现。

JavaScript
  carousel.onmouseover = function () {
    clearInterval(timer1);
  };
  carousel.onmouseout = function () {
    timer1 = setInterval(next.onclick, 3000);
  };

​ Ⅹ)按键盘左右键实现轮播切换效果

​ 实现原理:对window对象添加onkeydown/onkeyup时间,通过事件对象e.keyCode获取键盘的键值,与左右键的键值进行匹配,匹配成功后调用prev.onclick/next.onclick函数。

javascript
  window.onkeydown = function (e) {
    // 兼容处理
    e = e || window.event;
    console.log(e);
    console.log(e.keyCode);
    // 左键keyCode37,右键keyCode39
    if (e.keyCode == 37) {
      prev.onclick();
    } else if (e.keyCode == 39) {
      next.onclick();
    }
  };

​ XI)鼠标滚轮上下滚动实现轮播效果

​ 实现原理:对window对象添加onmousewheel事件,通过事件对象e.deltaY判断鼠标上/下滚动,调用prev.onclick/next.onclick函数。

JavaScript
  window.onmousewheel = function (e) {
    console.log(e);
    if (e.deltaY > 0) {
      prev.onclick();
    } else if (e.deltaY < 0) {
      next.onclick();
    }
  };

​ 完整代码:

JavaScript
window.onload = function () {
  // 获取DOM函数封装
  function $(className) {
    return document.getElementsByClassName(className)[0];
  }

  // 获取DOM
  var carousel = $("carousel");
  var imgs = $("imgs");
  var imgsLi = imgs.getElementsByTagName("li");
  var prev = $("prev");
  var next = $("next");
  var count = $("count");
  var countLi = count.getElementsByTagName("li");

  var timer1 = null; //计数器1
  var timer2 = null; //计数器2
  var speed = 20; //速度
  var now = 0; //初始显示的是第一张图的下标
  var imgWidth = imgsLi[0].offsetWidth; //每张图的宽度
  console.log(imgWidth);

  // 改变分页器的外观且让图轮播起来
  function tab() {
    // 清空所有li标签上的active类名
    for (var i = 0, len = countLi.length; i < len; i++) {
      countLi[i].classList.remove("active");
    }
    // 给当前li添加active类
    countLi[now].classList.add("active");
    // 轮播组图
    move(imgs, -(now * imgWidth), speed);
    // -(now*imgWidth) 往右移动,left<0
  }

  // 单击分页器显示对应的图片
  for (var i = 0, len = countLi.length; i < len; i++) {
    // 为每一个li标签添加一个index属性用于保存下标
    countLi[i].index = i;
    countLi[i].onclick = function () {
      console.log(this.index);
      // 将当前点击的分页器的下标传给now
      // 将分页器的下标与轮播图的图关联起来(通过下标关联)
      now = this.index;
      tab();
    };
  }

  // 切换到上一张图(向左翻页)
  prev.onclick = function () {
    // 越界处理
    if (now == 0) {
      now = imgsLi.length - 1;
    } else {
      now--;
    }
    tab();
  };
  // 切换到下一张图(向左翻页)
  next.onclick = function () {
    if (now == imgsLi.length - 1) {
      now = 0;
    } else {
      now++;
    }
    tab();
  };
  // 自动轮播(默认向右自动翻页)
  timer1 = setInterval(next.onclick, 3000);

  // 定义一个move方法,实现组图的移动,让其移动到指定位置
  // 组图每隔speed移动一定距离,直至移动完distance的距离
  function move(imgs, distance, speed) {
    // 当再次调用move时要先清除上次的定时器
    clearInterval(timer2);
    timer2 = setInterval(function () {
      // 方法一:
      /*       var left = imgs.offsetLeft;
      // left为imgs在左侧滚动条内隐藏的距离 left<0
      // distance - left:要向左移动的距离-左侧滚动条内已有的距离=剩下还要移动的距离
      var nowSpeed = (distance - left) / 5.5555; //设置速度 3.333:减小计算误差
      // 设置每次定时器结束后,定位left要增加的值
      imgs.style.left = left + nowSpeed + "px";
 */
      //方法二:
      imgs.style.left = distance + "px";
      //让left直接等于要移动的全部距离(没有过渡效果)需要为left添加过渡动画。
      //对应的CSS:  transition: left .5s ease;  /* linear:匀速 ease:慢快慢 */
      //好处:可以避免计算不精确导致的1-4px的偏差
    }, speed);
  }

  // 鼠标悬停
  carousel.onmouseover = function () {
    clearInterval(timer1);
  };
  carousel.onmouseout = function () {
    timer1 = setInterval(next.onclick, 3000);
  };

  // 按下键盘上的左右键,实现切换效果
  // 实现逻辑:通过获取左右键的键值,通过键值匹配调用控制器函数实现
  window.onkeydown = function (e) {
    // 兼容处理
    e = e || window.event;
    console.log(e);
    console.log(e.keyCode);
    // 左键keyCode37,右键keyCode39
    if (e.keyCode == 37) {
      prev.onclick();
    } else if (e.keyCode == 39) {
      next.onclick();
    }
  };

  // 鼠标滚轮上滚,下滚实现轮播效果
  // 实现原理:滚轮向下滚动:e.deltaY为正数,滚轮向上滚动:e.deltaY为负数
  window.onmousewheel = function (e) {
    console.log(e);
    if (e.deltaY > 0) {
      prev.onclick();
    } else if (e.deltaY < 0) {
      next.onclick();
    }
  };
};

面向对象编程(核心)

1.变量作用域

1)全局作用域

​ 在函数外部定义的变量或函数,叫做全局变量或全局函数。他们可以在当前程序的任意位置使用。

​ 在全局中定义变量可以用var,也可以直接添加window的属性。

​ 生命周期:他们会一直占用内存,只能在当前文件中使用。如果想在多个文件中使用变量,需要用到cookie或本地存储。

2)局部作用域

​ 在函数内部定义的变量或函数,叫做局部变量或局部函数,他们只能在函数内部使用。

​ 在函数内部用var定义局部变量,如果在函数内部用window定义变量,该变量为全局变量 eg:window.y="嘿嘿,我是藏在函数中的全局变量"。

​ 生命周期:从调用时开始创建,到函数执行完毕后立即销毁。

3)块级作用域

​ 在ES5中没有块级作用域,在ES6中有。

​ 一对花括号{}为一个块,块级作用域就是变量或函数只在当前这组{}中有效。

​ 在ES5中可以用IIFE实现块级作用域,ES6中用let实现。

2.IIFE

​ IIFE(Immediately Invoked Function Expression:立即执行函数表达式 )

1)什么是IIFE?

​ 声明函数的同时立即执行调用这个函数。

​ 定义的函数没有函数名只能执行一次,无法在其他地方被调用,也不会造成全局污染。

2)IIFE的作用

​ 可以用于解决运算过程中产生的全局污染问题,在函数内部无法访问函数外部的变量(实际上它是一个闭包的写法)。

​ 它也是ES5中实现块级作用域的方式

3)IIFE的特点

​ ①.将所有运算代码都放在匿名函数中;

​ ②.函数是一个表达式,没有函数名,只能被执行一次;

​ ③.执行一次后所产生的数据和变量就立即销毁,不会造成全局污染;

4)IIFE语法
javascript
	(function(形参){
	.......
	})(实参)
    //将函数的定义和调用写在一起。
5)IIFE的用法(推荐用前面两种写法)

​ 通过window和return 来返回IIFE运算的结果

javascript
 (function(形参列表){
        ...
        window.变量 = 表达式;//将运行结果赋给一个全局变量,IIFE执行后会立即销毁
    })(实参列表);

    var 变量名 = (function(形参列表){
        ...
        return 表达式;
    })(实参列表);

    !(function(形参列表){
        ...
        return 表达式;
    })(实参列表);

    +(function(形参列表){
        ...
        return 表达式;
    })(实参列表);

3.闭包(重要)

1)什么是闭包?

​ 闭包是指有权访问另一个函数作用域中的变量的函数。函数嵌套函数,内部函数可以引用外部函数的参数和变量,参数和变量不会被垃圾回收机制回收。

​ 闭包是定义在一个函数内部的函数,本质上,闭包是将函数内部和函数外部连接起来的桥梁。

​ 闭包就是一种作用域的体现,函数外部可以访问函数内部的数据。正常情况下函数外部不能访问函数内部的变量/数据,但是通过这种特殊的写法,将函数内的子函数暴露在全局上,可以在外部调用且可以访问到函数内部的变量/数据,即可以让全局访问局部的数据。

闭包的定义:

​ ①闭包是一个函数;

​ ②闭包可以使用在它外面定义的变量和数据且不会被垃圾回收;

​ ③闭包存在定义该变量的作用域中;

闭包满足的条件:

​ ①闭包是一个函数,比如下面的 func1 函数

​ ②闭包使用其他函数定义的变量,使其不被销毁。比如下面 func1 调用了变量 a

​ ③闭包存在定义该变量的作用域中,变量 a 存在 func 的作用域中,那么 func1 也必然存在这个作用域中。

现在可以说,满足这三个条件的就是闭包了。

JavaScript
<script type="text/javascript">
    var func = function(){
        var a = 'hqjy';
        var func1 = function(){
            a += ' a';
            console.log(a);
       }
        return func1;
   }
    var func2 = func();
    func2();          // hqjy a
    func2();          // hqjy a a
    func2();          // hqjy a a a
</script>

​ 可以看出,在第一次调用完 func2 之后,func 中的变量 a 变成 ' hqjy a',而没有被销毁。因为此时 func1 形成了一个闭包,导致了 a 的生命周期延续了。

2)闭包的作用

​ ①实现函数内部可以拿到函数外部的数据并保留不在调用后销毁;

​ ②减少了全局变量的使用,避免全局变量的污染。(闭包:在一个函数中嵌套函数,把全局变量写在第一个函数中,使其变为局部变量,从而减少全局变量的使用)

​ ③通过闭包可以实现在函数内拿到函数外部的数据。

​ ④当for循环内嵌套定时器/回调函数时,可通过闭包实现for循环和回调函数的同步执行。

​ (js是单线程语言,它先执行同步再执行异步,for是同步操作会先于回调函数执行完,因为回调函数属于异步操作,后于for执行,当for执行完毕后才会去执行回调函数)

3)闭包的特点

​ ①闭包一定是函数内嵌套函数;

​ ②闭包是一种作用域的体现,函数内可以访问函数外的数据;

​ ③闭包采用IIFE写法,由于内部数据被全局所调用,将延缓资源的回收(即闭包中的变量用完后不会立即销毁,会驻留内存一段时间)。

4)闭包的缺点

​ ①闭包中的变量会占用更多的内存空间。

​ ②可能会导致内存泄漏。

5)闭包的写法

​ ①第一种写法

javascript
(function(){	//一定是函数内嵌套函数可以通过IIFE内嵌套函数实现
	.....
	window.函数名 = function(){ //设置windows. 使用全局变量,便于在函数外部调用内部数据
        ...
	return 变量;
	}
})
console.log(函数名()); 



    for (var i = 0; i < 4; i++) {
       (function (i) {	//一定是函数内嵌套函数可以通过IIFE内嵌套函数实现
            setTimeout(function () { 
                console.log(i)
           }, 0)
       })(i)
   }



(function () {		//(常用)
      var box = document.getElementsByClassName("box")[0];
      //函数内嵌套函数,形成闭包  用闭包的好处可以减少全局变量的使用
      box.onclick = function () {
        console.log(this.innerHTML);
      };
      box = null; //间接实现垃圾回收
    })();

​ ②第二种写法

JavaScript
var 函数名1 = (function(){
	.....
	return 函数名 = function(){
        ...
	return 变量;
	}
})
console.log(函数名1());

​ ③第三种写法(不使用IIFE)

JavaScript
    function fn() {
      var i = 10;
      return function () {
        ++i;
        return i;
      };
    }
    // 通过闭包可以实现在函数外拿到函数内部的数据
    var rs = fn();
    console.log(rs());//11

4.内存管理

​ 在闭包中调用局部变量,会导致这个局部变量无法及时被销毁,相当于全局变量一样会一直占用着内存。如果 需要回收这些变量占用的内存,可以手动将变量设置为null。 ​ 然而在使用闭包的过程中,比较容易形成 JavaScript 对象和 DOM 对象的循环引用,就有可能造成内存泄露。 这是因为浏览器的垃圾回收机制中,如果两个对象之间形成了循环引用,那么它们都无法被回收。

​ 解决方案:

​ ①把循环引用中的变量设置为null即可;

​ ②把闭包写法改造成一个引用外部函数写法

5.面向对象编程(OOP)

​ 面向对象编程三大特征:封装,继承和多态。

1)什么叫OOP?

​ 是一种计算机编程架构,由单个能起到子程序作用的单元或对象组合而成。

​ 在JS中,一切皆对象。

​ 因为面向对象有:重用性,灵活性和扩展性这样一些特性。

​ JS既是一门面向过程,也是面向对象的编程语言,同时JS也是一门解释型编程语言。

2)面向对象与面向过程的区别

​ 面向过程采取的是“时间”换“空间”的做法,关注的是做事的过程。(过程)

​ 面向对象是一种对现实世界理解和抽象的方式,更关注的是一件事情的参与者和他们的行为。(参与者)

​ 面向过程与面向对象相当于员工和老板的关系。员工的目的是具体做某项工作,核心是做事的过程;老板不用关心这件事是怎么做的,他要做的就是指派谁去做。

面向过程的解决方法

​ 在面向过程的编程方式中实现“把大象放冰箱”这个问题答案是耳熟能详的,一共分三步:

​ ①开门(冰箱);

​ ②装进(冰箱,大象);

​ ③关门(冰箱)。

面向对象的解决方法

​ ①冰箱.开门();

​ ②冰箱.装进(大象);

​ ③冰箱.关门()。

6.Javascript对象

对象分为内置对象(构造函数/方法)和用户自定义对象。

1)内置对象(本质为构造函数)

​ String Date Math RegExp Function Object Array Number.....

​ eg:

​ var str1 = new String ('abc') ; //类型为Object

​ var str2 = 'abc' //类型为String

2)用户自定义对象
①字面量

​ var obj1 = {

​ name:"aaa",

​ sex:"male",

​ }

②用new构建

​ var obj2 = new Object({

​ name:"aaa",

​ sex:"male",

​ })

③构造函数(相当于ES6中的类)

​ a.创建方法

​ function 构造函数名/类名(形参列表){

​ //类的特征->属性

​ this.属性名 = 参数;

​ .......

​ //类的行为->方法

​ this.方法名 = function(){

​ ....

​ }

​ ....

​ }

​ b.使用构造函数实例化对象

​ var 对象名 = new 构造函数名/类名(实参列表)

​ c.构造函数特征

​ Ⅰ)构造函数名(类名)首字母要大写用大驼峰写法(W3C规定,使用大驼峰写法)

​ Ⅱ)属性和方法都要挂载到this上

​ Ⅲ)没有return语句(构造函数内的方法中可以有return语句)

​ tips:

​ 小驼峰写法:studentEnglishScore(第一个单词首字母小写,其他单词首字母都大写)

​ 大驼峰写法:StudentEnglishScore(每一个单词首字母都大写)

​ d.实例化机制

​ Ⅰ)创建一个对象;

​ Ⅱ)将构造函数中的this指向新对象;

​ Ⅲ)执行构造函数中的语句,将所有的属性和方法挂载到新的对象上;

​ Ⅳ)将新的对象的内存地址赋值给变量。

​ e.缺点

​ 如果构造函数实例化的对象中的属性和方法内容一样,因为他们会各自占用独立的空间,会造成内存的浪费。

④原型(重要)

​ a.什么是原型?

​ 原型就是prototype,所有function定义的函数都拥有这个属性。

​ prototype这个属性是一个对象,也是一个指针,是用来添加所有实例共享属性和方法的。

​ b.原型的作用

​ Ⅰ)解决方法过载(为每个对象都单独创建一个相同的方法,浪费了大量的内存资源,这种情况叫作方法过载)

​ Ⅱ)扩展构造函数的属性和方法(功能)。

​ c.原型的写法

​ Ⅰ)扩展属性

​ 构造函数名.prototype.属性名 = 表达式;

​ Ⅱ)扩展方法

​ 构造函数名.prototype.方法名 = function(){

​ ......

​ };

​ 注意:

​ 原型只能用于构造函数,内置对象(即构造函数)

​ 在开发过程中,私有属性和方法放在构造函数中,而共享属性和方法用原型添加。

​ d.可以修改/添加内置对象的属性和方法

⑤混合模式(最常用)

​ 混合模式 = 构造函数 + 原型

​ 在构造函数中定义私有的属性和方法,通过原型添加共享的属性和方法。

混合模式(构造函数 + 原型)在ECMAScript中是使用最广泛,认同度最高的一种创建自定义对象的方法。

⑥基本模式(JSON语法格式)

​ var 对象 = {

​ 属性:属性值,

​ .....,

​ 方法:function(){

​ .......

​ },

​ .....

​ }

​ 这样做的目的是为了减少全局变量和全局方法的使用,从而避免全局污染

​ eg:

html
<script>
    //需求:封装获取DOM
    var $ = {
      id: function (id) {
        return document.getElementById(id);
      },
      cls: function (className) {
        return document.getElementsByClassName(className);
      },
      tag: function (tagName) {
        return document.getElementsByTagName(tagName);
      },
    };

    $.id("box").innerHTML = "这是id";
    $.cls("box")[0].style.color = "aqua";
    $.tag("p")[1].style = "color :pink;border:1px solid #eee;padding:5px";
</script>
⑦工厂模式

​ 一般用于大批量创建对象(这批对象有相同的属性和方法)

​ 给定一些值,创造一个结果。

​ a.创建方法:

​ 创建一个函数,在函数中创建对象,最后再返回这个对象。

​ b.缺陷

​ 工厂模式很难实现让不同对象有不同属性或方法。

​ c.优点

​ 减少了重复性代码。

7.this(面试核心点)

​ this是一个地址指针。

1)全局中的this

​ 指向window对象

2)函数中的this

​ 原则:谁调用就指向谁。一般情况下是指向window对象的。

JavaScript
var name = "tom";
    function fn() { 
        var name = "mickle";
        console.log(this.name);//tom
        console.log(name);//mickle
     }
     fn();

​ 如果开启了“use strict;”严格代码格式,this指向window。

3)对象方法中的this

​ 指向当前对象。

4)构造函数中的this

​ 指向当前实例化的对象。

5)事件中的this

​ 指向触发当前事件的目标对象

6)借来的this

​ 所有的函数都有三个方法(call,apply和bind),可以用他们实现函数中this指向新的对象(用他们来修改函数中this的指向)。

1)call

​ 主动式将函数中的this指向新的对象,调用一次就立即执行一次(谁调用就指向谁)。

​ 语法:

​ 被调用对象的方法.call(当前对象,参数1,参数2,........)

2)apply

​ 用法与call一样,只是传参格式不一样。

​ 传参必须以伪数组的方式传递,不管有几个参数,都必须以这种格式进行传递。

​ 语法:

​ 被调用对象的方法.apply(当前对象,[参数1,参数2,........])

​ 被调用对象的方法.apply(当前对象,arguements)

​ 用arguments时 形参要先写父类的参数,再写子类的参数,且参数的顺序不能颠倒

3)bind(ES5新增)

​ 被动式改变this指向,实现对原对象的拷贝将函数内部的this指向括号内的对象。

​ 语法:

​ 回调函数.bind(对象,参数1,参数2,.....)

4)call,apply和bind的区别

​ 相同点:

​ ①都是函数原型的方法,可以改变函数中this的指向;

​ ②第一个参数都是this要修改的目标对象;

​ ③都可以传递参数;

​ 不同点:

​ ①call和apply是主动式的,修改this指向时立即调用执行;

​ ②bind是被动的,只修改this的指向,不执行需要调用才能执行;

​ ③call的参数是直接列举在对象的后面,而apply是以伪数组的形式传参;

​ ④bind一般用于回调函数;

面向对象编程应用(重要)

1.对象常用属性(掌握)

1)prototype

​ 用来获取和设置对象的原型属性。

​ 获取对象原型:

JavaScript
console.log(实例化对象名.__proto__); //__proto__是一个指向prototype的地址指针

​ 设置:

JavaScript
构造函数名.prototype.属性名 = 属性值;

​ tips:

​ prototype所有函数都拥有的属性,__ proto __ 是对象才拥有的。

2)constructor

​ 用来获取实例对象的构造函数类型,经常用于判断变量的数据类型是对象还是数组。

​ 用typeof测试引用类型的数据时,返回的都是Object

eg:

JavaScript
    //  constructor
      var arr = [];
      console.log(arr.constructor === Array); //true

      var isTrue = new Boolean();
      console.log(isTrue.constructor === Array); //false
      console.log(isTrue.constructor === Boolean);  //true

    // 用typeof测试引用类型的数据时,返回的都是object
      console.log(typeof arr); //object
      console.log(typeof isTrue); // object

2.对象常用方法(了解)

1)isPrototypeOf

​ isPrototypeOf()用于指示对象是否存在于另一个对象的原型链中。如果存在,返回true,否则返回false。

​ 语法:

JavaScript
prototypeObject.isPrototypeOf( object )

console.log(构造函数名.prototype.isPrototypeOf(对象名)); //作用类似于instanceof
2)hasOwnProperty

​ 每个实例对象都有一个hasOwnProperty()方法用来判断某一个属性到底是本地属性(私有的),还是继承自prototype对象的属性

​ 语法:

对象名.hasOwnProperty("属性名")
3)getOwnpropertyDescriptor

​ getOwnpropertyDescriptor用来获取对象(私有)属性的描述信息,其中包括属性的value(值),writable(是否可写),configurable(是否可设置),enumerable(是否可枚举)

​ 注意:必须通过Object对象调用。

​ 语法:

Object.getOwnPropertyDescriptor(对象名, “属性名”)
4)defineProperty

​ defineProperty用来修改对象属性的描述信息。

​ 语法:

JavaScript
Object.defineProperty(对象名, “属性名”, {
	//属性描述信息:
	value: "value",	//设置属性值
    writable: false, //是否可以修改
    configurable: false, //是否可以设置
    enumerable: false, //是否可以遍历输出(枚举)
})
5) propertyIsEnumerable

​ propertyIsEnumerable用来判断改对象的属性是否可以用for...in枚举(遍历)。

​ 语法:

对象名. propertyIsEnumerable(“属性名”)

3.对象运算符

1)in(了解)

​ 可以用来判断某个实例是否含有某个属性或方法,不管是不是本地(私有)属性或方法。返回true/false

​ 语法:

”属性名/方法名“  in  对象名;
2)delete(重要)

​ 用于删除对象的属性和私有方法(不能删除原型方法),当属性/私有方法失去引用之后会被垃圾回收,不再存在。

​ 语法:

delete  对象名. 属性名/私有方法名;
3)instanceof(重要)

​ 判断一个实例是否属于某种类型。返回true/flase

​ 语法:

对象/数据 instanceof 类型

4.继承(核心,面试重点)

1)什么是继承?

​ 让子类(构造函数)拥有父类(构造函数)所有的特性(包括属性和方法)。

​ 在JS中一切皆对象,所有的类都继承于Object。

​ 如:Array -> Object fn -> Function -> Object

​ instanceof用于判断指定对象属于哪一个类,或者判断是不是某一个类的原型。

2)继承实现

​ a.子类实例化出来的对象拥有父类中所有的属性和方法;

​ b.子类实例化出来的对象属于子类,也属于父类。

3)实现继承的方式

①构造继承

​ a.基本思想

​ 利用call或者apply把父类中通过this指定的属性和方法复制(借用)到子类创建的实例中。

​ b.核心

​ 使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)。

​ c.缺点

​ Ⅰ)属性和方法都在构造函数中定义;

​ Ⅱ)只能继承父类构造函数内的属性和方法,不能继承父类的原型属性/方法,无法实现函数复用;

​ Ⅲ)每个子类都有父类实例函数的副本,影响性能。

​ d.语法

JavaScript
	父类.call(this, 参数列表)
	父类.apply(this, [参数列表]) 
或: 父类.apply(this, arguments) 
	//arguments:获取传递过来的实参它是一个伪数组 适用于参数较多情况

​ 注意: ​ 用arguments时 形参要先写父类的参数,再写子类的参数,且参数的顺序不能颠倒

②原型链继承

​ a.基本思想

​ 每创建一个函数,该函数就会自动带有一个 prototype 属性。该属性是个指针,指向了原型对象,并可以访问原型对象上的所有属性和方法。

​ b.核心

​ 将父类的实例作为子类的原型。

​ c.缺点

​ 父类新增原型方法/原型属性,子类都能访问到,父类的原型属性/方法改变子类也会随之改变。

​ d.语法

	子类.prototype = new 父类();

​ 补充:

​ _ proto __ 是每个对象所特有的属性,函数有prototype属性。生成对象时,对象的 __ proto __ 指向函数的prototype

③对象冒充(对象伪造)

​ 对象冒充是指一个对象冒充另外一个对象来实行其他对象的方法,即一个对象将其他对象的方法当做是自身的方法来执行。

​ 他不是真正意义上的继承。子类实例化出来的对象拥有父类所有属性和方法,但子类并不属于这个父类。

​ 实现步骤:

​ Ⅰ)克隆父类构造器(原生)中的属性和方法

JavaScript
this.父类 = 父类;
this.父类(name, sex);
delete this.父类;

​ Ⅱ)克隆父类原型中的属性和方法

JavaScript
 for (var i in 父类.prototype) {
        子类.prototype[i] = 父类.prototype[i];
   }

④组合继承(构造继承+原型链继承)(真正的继承)(重要)

​ a.基本思想

​ 所有的实例都能拥有自己的属性,并且可以使用相同的方法,组合继承避免了原型链和借用构造函数的缺陷,结合了两个的优点,是最常用的继承方式。

​ b.核心

​ 通过调用父类构造,继承父类的属性并保留传参的优点,然后再通过将父类实例化作为子类原型,实现函数复用。

​ c.缺点

​ 调用了两次父类构造函数,生成两份实例(子类实例将子类原型上的那份屏蔽了)

​ d.实现步骤

​ Ⅰ)继承的第一步:继承父类构造函数上的属性和方法

JavaScript
父类.call(this, 参数列表);或
父类.apply(this, arguments); //arguments:获取传递过来的实参 适用于参数较多情况

​ Ⅱ)继承的第二步:继承父类原型链上的属性和方法(在构造函数外执行)

JavaScript
子类.prototype = Object.create(父类.prototype);

​ Ⅲ)继承的第三步:找回子类丢失的构造器(在构造函数外执行)

子类.prototype.constructor = 子类;

5.对象克隆(拷贝)(面试中可能被问到)

​ 对象克隆即把父类对象的所有属性和方法,拷贝进子对象。对象克隆分类浅克隆和深克隆。

1)浅克隆(浅拷贝)

​ 只复制基本数据类型的数据。如果用浅拷贝复制引用类型的数据,会出现修改被克隆对象的数据(即修改克隆后的引用类型数据,原本的引用类型数据也会被修改)。

​ 实现方法:

JavaScript
//  封装浅克隆的方法
      function cloneObj(obj) {
        var newObj = {};
        for (var key in obj) {
          newObj[key] = obj[key]; //此处不可以使用obj.key因为key是变量
          //上面语句相当于 newObj.name = obj.name;
        }
        return newObj;
      }

​ 注意:浅拷贝只适合克隆基本类型的数据。

​ tips:

​ JS中的数据类型分为基本类型和引用类型两种。基本类型的数据存放在栈中,而引用类型的数据存放在堆中(在栈中存放地址指向堆内的数据)。

2)深克隆(深拷贝)

​ 深克隆就是能够实现真正意义上的数组和对象的拷贝。

​ 思路:只要递归调用”浅克隆“就行了。

​ 实现方法:

JavaScript
 //  封装深克隆的方法
      function deepCloneObj(data) {
        //判断传进来的数据是对象还是数组,创建一个新的对象/数组
        var newData = data.constructor === Array ? [] : {};
        for (var key in data) {
          // 判断值是否为null,因为null的类型是Object
          if (data[key] === null) {
            newData[key] = null;
          } else {
            // 判断是否为引用类型
            if (typeof data[key] === "object") {
              //如果是引用类型用深拷贝
              newData[key] = deepCloneObj(data[key]);
            } else {
              //如果是基本类型的数据用浅拷贝
              newData[key] = data[key];
            }
          }
        }
        return newData;
      }

BOM操作

1.认识BOM

​ JS是由ECMASript(ECMA),DOM(W3C)和BOM(无规范)。

​ BOM(Browser Object Model:浏览器对象模型),提供了独立于内容而与浏览器进行交互的对象,用它来实现窗口与窗口之间的通信。

​ BOM的核心对象是window。

1)BOM组成

​ window

​ -frames history location navigator screen

2)BOM与DOM的区别

​ DOM通过脚本动态的访问和更新文档的内容,结构和样式的接口。

​ BOM通过脚本操作浏览器的各个功能组件的一个接口。

区别:

​ ①DOM提供了处理网页内容的接口,而BOM提供了与浏览器交互的接口;

​ ②DOM的核心是document,BOM的核心是窗口window。

2.window对象

​ 打开一个浏览器,就自动创建了一个window对象,window就是浏览器打开的窗口。

1)常用属性

​ document 文档结构

​ history 跳转的历史记录

​ location 操作地址栏

​ navigator 浏览器

​ screen 屏幕

​ opener 引用创建的这个新窗口的窗口

​ screenX 窗口位于屏幕的X坐标

​ screenY 窗口位于屏幕的Y坐标

​ name 窗口名

​ status 状态栏(目前主要是欧朋支持,其他浏览器都不支持。大多数浏览器都关闭了这一功能,目的是避免被钓鱼攻击)

​ (parent self top 用在iframe框架中)

2)常用方法
方法描述说明
alert()显示一个警告框
setInterval()计时器:按照指定的毫秒周期来调用函数或计算表达式
setTimeout()定时器:在指定的毫秒后调用函数或计算表达式
clearInterval()取消由 setInterval()方法设置的 interval
clearTimeout()取消由 setTimeout()方法设置的 timeout
close()关闭浏览器窗口
confirm()显示带有确认按钮和取消按钮的对话框(点击确定返回true,取消返回false)
open()打开一个新的浏览器窗口或查找一个已命名的窗口
prompt("请输入:")显示可接受用户输入的对话框(输入类型为string)
scrollTo()把内容滚动到指定的坐标,设置滚动条的位置

​ open(url,name,attr,replace)

​ url:要跳转的网址或要打开的文件或创建一个窗口(url为空时)

​ name:窗口名或 _blank _self _parent _top

​ attr:窗口设置

​ replace:true/false true表示添加到历史记录中,false则反

​ tips:

​ setTimeout与setInterval的区别:

​ 相同点:

​ ①都是计时(定时)器,都是指定的时间重复(一次)执行对应的代码。

​ ②都是异步执行的。

​ 不同点:

​ setTimeout在指定时间到了之后只执行一次对应的代码。(只执行一次)

​ setInterval在指定时间到来之后重复执行对应代码。(不停的循环)

​ 计时器如果不需要时,一定要做清除处理。定义计时器时,最好初值设置为null(垃圾回收机制)

3.location对象

​ 用来获取或设置url。

​ 获取或设置url地址信息,页面跳转(重定向),页面刷新等。

1)常用属性
属性名描述
location.hash设置或取得URL中的锚点/片段
location.host设置或取得 URL中主机(服务器名称/主机名+端口号)
location.hostname设置或取得 URL 中的主机名/服务器名称
location.href设置或取得完整的 URL (页面重定向应用/设置页面跳转)
location.pathname设置或取得 URL 中的路径/文件名
location.port设置或取得 URL 中的端口号
location.protocol设置或取得 URL 使用的协议
location.search设置或取得 URL 中的查询字符串(一般是?符号后面的内容)若要将?后面的字符串变为对象,请参考:JavaScript高级-面向对象/实训2.html)

​ eg:

​ 以这个网站为例:http://127.0.0.1:5500/JavaScript/A7.html?username=zhangsan&sex=male#/classic/type

​ hash: #/classic/type 哈希值

​ host: 127.0.0.1:5500 主机名+端口号

​ hostname: 127.0.0 .1 主机名

​ port: 5500 端口号

​ href: http://127.0.0.1:5500/JavaScript/A7.html?username=zhangsan&sex=male#/classic/type URL

​ pathname: JavaScript/A7.html 文件名

​ protocol: http: 协议

​ search: ?username=zhangsan&sex=male URL 中的查询字符串(一般是?符号后面的内容)

2)常用方法

①location.assign() :加载新页面文档

​ 与 location.href 功能一样,用法一样,只href是属性,而assign是一个方法

②location.reload():重新加载(刷新)当前页面

​ 用法:reload(true/false):默认为false。true表示重新从服务器下载,实现刷新,而false表示从缓存中获取刷新页面。

③location.replace():用新的文档替代当前文档(执行的是URL的替换,不会写入历史记录中)

​ 与location.assign()和 location.href的区别是:location.replace()不能将跳转的路径写到历史记录中,也即是跳转后不能返回。

4.navigator对象

​ 通过navigator对象获取浏览器相关信息,用于判断该浏览器是哪个浏览器,以及厂商等

1)常用属性

​ ①navigator.appName:获取浏览器的名称

​ ②navigator.appVersion:取得浏览器的平台和版本信息

​ ③navigator.userAgent:获得浏览器相关信息(常用)

​ eg:用Naviator属性判断不同的浏览器

JavaScript
    if (navigator.userAgent.toLowerCase().indexOf("chrome") >= 0) {
      console.log("这是谷歌浏览器");
    } else if (navigator.userAgent.toLowerCase().indexOf("trident") >= 0) {
      console.log("这是IE浏览器");
    } else {
      console.log("其他浏览器");
    }

5.Screen对象

​ 用于获取客户端屏幕相关信息。

1)常用属性

​ availHeight:屏幕高度(不包含任务栏)

​ availWidth:屏幕宽度(不包含任务栏)

​ height:显示屏幕的高度(包含任务栏)

​ width:显示器屏幕的宽度(包含任务栏)

​ colorDepth:颜色深度

6.History对象

​ 用来记录用户在客户端访问的url。

1)属性

​ length:返回历史记录的条数

2)方法

​ forward():跳转到下一个history中的url,如果history中没有url,将停留在当前页面

​ back():返回/跳转到上一个history中的url,如果history中没有url,将停留在当前页面

​ go(num|url):go(-1)相当于 back(),go(1)相当于 forward(),go(0)刷新当前页面

HTML5 API

1.媒体操作

1)audio(音频)(IE9以下版本不支持)

​ 支持的音频格式:

​ .mp3 .ogg .webm

​ 标签常用属性:(掌握)

属性名属性值描述
autoplayautoplay自动播放(IE可以自动播放,谷歌有些版本不支持自动播放)
controlscontrols控制条
loopn/loop循环
preloadmetadata/auto/none预加载(设置autoplay时,preload失效)
scrurl音频文件路径及文件名
mutedmuted静音

​ tips: 属性名=属性值 可以只写属性名/值 eg:muted = “muted” 直接写 muted

​ JS常用属性:

属性描述
audio.buffered.end(0)获取已缓冲的秒数
audio.buffered.length获取缓冲范围
audio.buffered.start(index)获取某个已缓冲返回的开始位置
audio.buffered.end(index)获取某个已缓冲范围的结束位置
audio.currentSrc返回当前音频的URL
audio.currentTime返回当前音频的现在时间
audio.ended音频是否结束
audio.duration返回音频总时长,以秒计
audio.networkState返回音频的网络状态 [0:尚未初始化;1:已经选取资源,但未使用网络;2:正在下载数据;3:未找到资源]
audio.paused是否处于暂停状态

​ 常用方法:

方法名描述
canPlayType(type)检测浏览器是否支持音频播放类型 返回["probable":浏览器最可能支持该类型;”maybe“:可能支持]
load()重新加载
paly()播放(掌握)
pause()暂停(掌握)

​ 注意:播放器使用play()和pause()方法时必须用JS原生获取DOM,不能用jQ获取

2)video(视频)

​ 支持视频格式:

​ .mp4 .mpeg .ogg

​ 标签常用属性:

属性名属性值描述
autoplayautoplay自动播放
controlscontrols进度条
heightpx/i/e/l设置播放器的高度
widthpx/i/e/l设置播放器的宽度
loopn/loop设置循环循环次数
scrurl视频播放路径
preloadmetadata/auto/none预加载视频。如果使用”autoplay“则忽略该属性
mutedmuted静音
posterposter="imgs/1.jpg"海报
starttimestarttime="00:05"开始时间

​ JS常用属性:

属性描述
Media.currentTime=value;(掌握)当前播放的位置,负值可以改变位置(以秒为单位)
Media.startTime;(掌握)开始播放时间,一般为0
Media.duration(掌握)视频长度
Media.paused;(掌握)是否暂停
Media.defaultPlaybackRate=value;默认的回放速度,可设置,默认为1.0
Media.playbackRate=value;(掌握)设置倍速,当前播放速度,设置后马上改变,默认为1.0
Media.played;返回已经播放的区域 返回一个对象TimeRanges
Media.seekable;返回可以seek的区域
Media.ended;是否结束
Media.autoplay;是否自动播放
Media.loop;是否循环播放
Media.controls;是否有默认进度条
Media.volume;(掌握)设置音量0.1-1.0
Media.muted;(掌握)设置静音,true/false

​ JS常用方法:

方法名描述
Media.play()播放
Media.pause()暂停

2.手势事件(移动端)

​ 移动端事件在使用前,必须先在< head>中添加移动优先的设置

html
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
1)click事件

​ click事件在移动端会有200-300ms的延时(主要在苹果浏览器上)。之所以会存在这300ms的延时是以为如果用户缩放页面之后想要回到原来的页面比例就需要点击两次页面,因此,移动端为了判断点击事件是否是缩回事件就会等在用户点击一次之后再去检测用户会不会点击第二次,所以会有一个300ms的延时。

​ 解决方案:

​ 引入fastclick插件。

html
  <script src="" data-missing="fastclick.js"></script>

​ 参考网址:https://www.jianshu.com/p/150c305f6930 ​ 下载faskclick的网址:https://github.com/ftlabs/fastclick

​ 不需要使用fastclick的情况: ​ ①fastclick是不会对PC浏览器添加监听事件 ②Android版Chrome 32+浏览器,如果设置viewport meta的值为width=device-width,这种情况下浏览器会马上出发点击事件,不会延迟300毫秒。

​ < meta name="viewport" content="width=device-width, initial-scale=1" > ③所有版本的Android Chrome浏览器,如果设置viewport meta的值有user-scalable=no,浏览器也是会马上出发点击事件。 ④IE11+浏览器设置了css的属性touch-action: manipulation,它会在某些标签(a,button等)禁止双击事件,IE10的为-ms-touch-action: manipulation

2)tap事件(移动端)

​ 因为click事件在移动端可能会有300ms左右的延时可以使用tap替代click事件。

①tap事件包括:

​ tap:轻击事件

​ singleTap:单击事件

​ doubleTap:双击事件

​ longTap:长按事件

②用法:

​ 原生中没有tap事件,需要引入zepto.js库实现。(zepto.js的用法与JQuery一致)

​ zepto.js官网:https://zeptojs.com/ zepto.js移动端的js库

​ 中文文档:https://www.runoob.com/manual/zeptojs.html

​ 注意:

​ Ⅰ)如果要使用zepto.js时有些API单独放在其他js模块中,使用时需要引入这些模块,然后才能调用对应的方法

​ Ⅱ)如果要使用zepto.js中的tap事件,需要引入touch.js插件。

​ Ⅲ)到bootcdn里搜索zepto在GitHub仓库里面下载zepto.js及相关插件。

​ Ⅳ)如果要使用常规事件,如click事件等,需要引入event.js插件。

​ Ⅴ)Tap事件在PC端使用是无效的,只能在移动端使用。

​ 引入zepto.js及插件:

html
  <script src="https://cdn.bootcdn.net/ajax/libs/zepto/1.2.0/zepto.js"></script>
  <script src="" data-missing="event.js"></script>
  <script src="" data-missing="touch.js"></script>

​ 注意: zepto.js必须先于其他插件的引入。

3)swipe事件(移动端)

​ 滑屏事件(只能用于移动端)

​ 分为:

事件名描述
swipe滑屏(上下左右四个方向都可以触发)
swipeLeft左滑屏
swipeRight右滑屏
swipeUp上滑屏
swipeDown下滑屏

​ swipe事件非原生事件(不识别原生写法),需要引入zepto.js库

html
<script>
    // 滑屏
    $(".box").swipe(function () {
      $(this).css({
        fontSize: "30px",
      });
    });

    //  右滑屏打开另一个文件
    $(document).swipeRight(function () {
      // location.assign("./03视频常用的属性和方法.html") 或
      location.href = "./03视频常用的属性和方法.html";
    });

    // 左滑屏
    $(document).swipeLeft(function () {
      // $(".box").css("background","aqua") 或
      $(".box").css({
        background: "aqua",
        fontSize: 20,
      });
    });

    // 上滑屏
    $(document).swipeUp(function () {
      $(".box").html("<span>上滑屏</span>");
    });

    // 下滑屏
    $(document).swipeDown(function () {
      $(".box").css("color", "pink");
      $(".box").html("<span>下滑屏</span>");
    });
  </script>
4)touch事件(移动端)

​ 分类:

事件名描述
touchstart当手指触摸到屏幕会触发
touchmove当手指在屏幕上移动时,会出触发
touchend当手指离开屏幕时,会触发
touchcancel可由系统进行触发,比如手指触摸屏幕的时候,突然alert了一下,或者系统中其他打断了touch的行为,则可以触发该事件

​ 采用原生写法不需要引入zepto.js:

JavaScript
    document.body.addEventListener( //事件监听写法
      "touchmove",
      function () {
        alert();
        console.log("touchmove");
      },
      false
    );

    document.body.ontouchend = function () { //事件绑定写法,要在事件前加on
      console.log("touchend");
    };

3.拖放事件

​ 必须要在被拖放的元素上添加属性:draggable = “true”

事件名描述作用对象
ondragstart当拖拽元素开始被拖拽的时候触发的事件被拖拽元素上
ondragenter当拖拽元素进入目标元素的时候触发的事件目标元素上
ondragover拖拽元素在目标元素上移动的时候触发的事件目标元素上
ondrop被拖拽的元素在目标元素上同时鼠标放开触发的事件目标元素上
ondragend当拖拽完成后触发的事件被拖拽元素上

​ 注意:

​ ①事件对象中包含DataTransfer对象,它是拖拽对象用来传递的媒介,一般使用Event.dataTransfer.setData 和 Event.dataTransfer.getData进行获取和设置。

​ ②Event.effectAllowed属性:就是拖拽的效果。

​ ③Event.preventDefault()方法:阻止默认事件的执行,在ondragover中一定要先执行preventDefault(),否则ondrop事件不会被触发。

​ ④被拖拽对象一定要加一个属性: draggable = "true"

​ ⑤获取被拖拽对象一般通过其id值

html
	//实现将一张图片通过拖拽移动到两个不同的盒子中
<script>
    var box1 = document.querySelector(".box1");
    var box2 = document.querySelector(".box2");
    var img = document.querySelector("#img");

    // 阻止默认行为
    box1.ondragover = function (e) {
      e.preventDefault();
    };

    box2.ondragover = function (e) {
      e.preventDefault();
    };
    
    // 将拖拽的目标对象存入dataTransfer中(一定要先给目标对象id值)
    img.ondragstart = function (e) {
      e.dataTransfer.setData("imgTarget", e.target.id);
    };

    // 将目标对象存入box2中
    // (要想让ondrop发挥作用,必须在ondragover事件中进行默认行为的阻止)
    box2.ondrop = function (e) {
      this.appendChild(
        document.getElementById(e.dataTransfer.getData("imgTarget"))
      );
    };

    // 将目标对象存入box1中(先从box1拖拽到box2再从box2拖拽到box1)
    box1.ondrop = function (e) {
      this.appendChild(
        document.getElementById(e.dataTransfer.getData("imgTarget"))
      );
    };
  </script>

4.定位

​ H5新增用 navigator.geolocation API实现定位。

1)浏览器支持:

​ IE9以上,Firefox,chrome,safari,opera支持地理定位。谷歌可以定位但不能获取地理位置值

2)判断浏览器是否支持定位:

​ 判断(navigator.geolocation 是否有返回值,如果有则支持,如果没有则不支持)

javascript
if (navigator.geolocation) {
    } else {
      console.log("您的浏览器不支持定位");
    }
3)定位方法一般有:

​ 服务器端/PC:IP地址

​ 移动客户端:GPS(精准)

4)方法:

①getCurrentPosition(success,error,option):获取当前位置

参数说明是否必须
successFn(position)定位成功回调函数,会返回一个位置数据对象
errorFn(msg)定位失败回调函数,返回一个msg对象包含错误信息和代码
option定位参数设置
html
 <script>
    var options = {
      timeout: 5000,
      enableHeightAccuracy: true,
    };

    // 判断当前浏览器是否支持定位
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(success, error, options);
    } else {
      console.log("您的浏览器不支持定位");
    }

    function success(info) {
      console.log(info); //谷歌定位很慢
      console.log(info.coords);
      console.log(info.coords.latitude); //维度
      console.log(info.coords.longitude); //经度
    }

    function error(err) {
      console.log(err);
    }
  </script>

​ 定位成功返回:

​ coords: GeolocationCoordinates {

​ latitude: 34.7449, 维度

​ longitude: 113.619, 经度

​ altitude: null, 海拔

​ accuracy: 15355, 位置的精准度

​ timestamp: 1694934345522, 时间戳

​ altitudeAccuracy: null, 海拔精准度

​ …}

​ option 参数

​ timeout:timer 请求超时时间,毫秒计

​ enableHeightAccuracy:true/false 是否获取更精准的位置

②watchPosition():跟踪用户的位置

​ 用法与getCurrentPosition方法一样

③clearWatch():停止跟踪用户的位置

​ 语法:

var watcher = navigator.geolocation.watchPosition(success, error, options);

​ navigator.geolocation.clearWatch(watcher)

5)百度地图API获取当前位置

​ ①进入网站

https://lbsyun.baidu.com/

​ ②注册并登陆

​ ③进入控制台 -> 我的应用 ->创建应用 ,用于获取AK(密钥)值

​ 注意:百度地图1.4以下的版本不需要AK,1.4以上的版本必须要AK

​ ④开发

​ a.引入百度地图API

html
    <script src="//api.map.baidu.com/api?type=webgl&v=1.0&ak=GFMVM687MNXyqAHjhXhwsLRtKppFNilf"></script>

​ b.js代码

​ 创建地图:https://lbsyun.baidu.com/jsdemo.htm#aCreateMap

​ 或http://api.map.baidu.com/lbsapi/createmap/

html
<script>
    // 创建一个百度地图对象
    var geolocation = new BMapGL.Geolocation();

    // 用百度地图获取位置
        geolocation.getCurrentPosition(function(ps){
            // 请求成功
            if(this.getStatus()== BMAP_STATUS_SUCCESS){
                console.log(ps);
                
         	var map = new BMapGL.Map("map"); // 创建Map实例 (必须用id)
       		map.centerAndZoom(new BMapGL.Point(ps.point.lng, ps.point.lat), 12); 
            // 初始化地图,设置中心点坐标和地图缩放级别 经度:ps.point.lng  维度:+ ps.point.la
        	map.enableScrollWheelZoom(true); // 开启鼠标滚轮缩放
            }else{
                console.log("定位失败");
            }
        },function(err){})
</script>

​ c.运行(必须在服务器端运行)

​ Ⅰ)安装node.js

​ 下载地址:https://nodejs.cn/

​ 查看node版本:node -v

​ Ⅱ)在VSCode打开终端,安装http-server(用来启动服务)

​ npm i http-server -g

​ Ⅲ)用http-server启动服务

​ http-server

​ 或:http-server -p 80/81/8080 (指定端口)

​ Ⅳ)在浏览器地址栏输入网址:http://127.0.0.1:5500

6)实训 音乐播放器

​ 具体代码见实训

HTML5 Canvas

1.认识canvas

​ canvas是H5新增的一个标签,它的作用是在浏览器上创建一个画布,用来绘画。

2.使用场景

1)简单游戏的开发;

2)简单图像;

3)验证码;

4)绘制图表;

5)绘制地图;

6)制作马赛克效果;

7)模拟帧动画;

3.基本用法

1)添加画布
html
<body>
    <canvas id="canvas" width="400px" height="300px"></canvas>
</body>

注意: 开发时使用行间样式设置width和height而不使用CSS 设置宽高

2)渲染上下文
html
  <script>
    //获取canvas
    var cv = document.querySelector("#canvas");
    //创建canvas对象
    var cts = cv.getContext("2d");
    //2d表示绘制的是2d图形,webgl表示绘制的是3d图形
  </script>

​ 2d表示绘制的是2d图形,webgl表示绘制的是3d图形

4.兼容处理

​ IE8及更早的版本不支持canvas标签

html
<canvas id="cv" width="300px" height="300px">您的浏览器不支持canvas</canvas>

​ 支持不显示文字,不支持时显示文字

html
  <script>
    var cv = document.getElementById("cv");
    if (cv.getContext("2d")) {
       //绘图代码处
    } else {
      console.log("您的浏览器不支持canvas");
    }
  </script>

5.canvas与svg的区别(了解)

Canvas 1)依赖分辨率 2)不支持事件处理器 3)弱的文本渲染能力 4)能够以 .png 或 .jpg 格式保存结果图像 5)最适合图像密集型的游戏,其中的许多对象会被频繁重绘

SVG 1)不依赖分辨率 2)支持事件处理器 3)最适合带有大型渲染区域的应用程序(比如谷歌地图) 4)复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快) 5)不适合游戏应用

6.canvas常用方法

1)绘制路径

​ 绘制路径方法:

​ 1)首先,你需要创建路径起始点。 ​ 2)然后你使用画图命令去画出路径。 ​ 3)之后你把路径封闭。 ​ 4)一旦路径生成,你就能通过描边或填充路径区域来渲染图形。

常用方法/属性

方法描述
beginPath()开启一条路径,或重置当前路径
closePath()创建从当前点回到起始点的路径(闭合最后一根线)
moveTo(x,y)移动笔触(x,y表示画笔起始位置)
stroke()描边
fill()填充
属性描述
lineWidth = 4设置描边线宽
font = “30px 楷体”设置字体,字号
strokeStyle = “color”设置描边/边框颜色
fillStyle = “color”设置填充颜色
textAlign = "left/center/right"设置文字对齐方式
textBaseline = “bottom top”设置文字基线
javascript
      // 创建canvas对象
      var ctx = cv.getContext("2d");
      ctx.beginPath(); //开始笔触(表示可以开始绘画了)
      ctx.arc(200, 200, 200, 0, 2 * Math.PI, true); //绘制圆

      ctx.moveTo(150, 100); //移动笔触
      ctx.arc(100, 100, 50, 0, 2 * Math.PI, true); //绘制圆弧
	  //arc(x,y,r,start angle,end angle,true/lase)(圆心x坐标,圆心y坐标,半径,圆起始角(弧度),圆终止角(弧度),true(逆时针)/flase(顺时针))x轴正方向为0度
	  ctx.lineWidth = 5;//设置线宽
      ctx.strokeStyle = "pink";//设置描边颜色
	  ctx.stroke(); //描边
	  ctx.fillStyle = "red";//设置填充色
      ctx.fill();//填充

​ 注意:样式设置要放在绘制之前

2)绘制直线

​ lineTo(x,y); 绘制一条从moveTo(x0,y0)指定的起点坐标(x0,y0)到点(x,y)的直线。

​ 注意:上一次绘制的终点就是下一次绘制的起点。

​ beginPath(); 重置路径绘制 新路径的效果不会影响到之前的路径

3)绘制圆弧

​ arc(x,y,r,start angle,end angle,true/lase); ( 圆心x坐标,圆心y坐标,半径,圆起始角(弧度),圆终止角(弧度),true(逆时针)/flase(顺时针))

4)贝塞尔曲线(了解)

​ ①绘制二次贝塞尔曲线:

​ quadraticCurveTo(cp1x,cp1y,x,y) (cp1x,cp1y为一个控制点,x,y为结束点)

​ 二次贝塞尔曲线需要三个点:第一个点是用于二次贝塞尔计算中的控制点,第二个点是曲线的结束点,第三个点是曲线的开始点即当前路径中最后一个点。如果路径还未开始绘制,则使用 beginPath() 和 moveTo() 方法来定义开始点。

二次贝塞尔曲线

​ 红色:控制点 黄色:开始点 绿色:结束点

​ ②绘制三次贝塞尔曲线:

​ bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y) (cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点)

​ 三次贝塞尔曲线需要四个点。前两个点是用于三次贝塞尔计算中的控制点,第三个点是曲线的结束点,第四个点为曲线的开始点是当前路径中最后一个点。如果路径还未开始绘制,则使用 beginPath() 和 moveTo() 方法来定义开始点。

三次贝塞尔曲线

​ 黄色:开始点 moveTo(20,20) 红色:控制点1 bezierCurveTo(20,100,200,100,200,20) 红色:控制点2 bezierCurveTo(20,100,200,100,200,20) 绿色:结束点 bezierCurveTo(20,100,200,100,200,20)

5)绘制矩形

​ fillRect(x,y,width,height) //绘制一个填充矩形

​ strokeRect(x,y,width,height) //绘制一个矩形的边框

​ clearRect(x,y,width,height) //清除指定矩形区域,让清除部分完全透明,通常与fillRect结合使用可以绘制镂空效果或执行删除效果。

JavaScript
 	  ctx.fillRect(100, 100, 200, 200);
      ctx.strokeRect(10, 10, 100, 100);
      ctx.clearRect(150, 150, 100, 100);

​ 注意:绘制一个完整图形不同于绘制路径,不需要 cts.beginPath(); 开启一个新路径

6)绘制文本

​ fillText(文本内容,x坐标,y坐标,[,最大宽度]);绘制填充文本

​ strokeText(文本内容,x坐标,y坐标,[,最大宽度]);绘制空心文本

JavaScript
	  ctx.font = "40px 楷体";//设置字体大小和样式,必须给两个参数
	  ctx.strokeStyle = "pink"; //注意:样式设置要放在绘制之前
      ctx.fillStyle = "red";
      ctx.textAlign = "left";//设置对齐方式
      ctx.textBaseline = "bottom";//设置基线
      ctx.fillText("好好学习,天天向上!", 10, 100);
      ctx.strokeText("好好学习,天天向上!",10,200)

​ 注意:样式设置要放在绘制之前

7)绘制图像

​ 支持.png,.jpg,.gif以及canvas源。

JavaScript
//第一步:
var img = new Image();		//创建一个img标签
//第二步
img.src = "图片源";			//设置图片源地址
//第三步
img.onload = function(){
   ctx.drawImage(img,100,100) //绘制图像
  }

​ ①基本绘制

​ ctx.drawImage(img,x,y);// img表示要绘制的图像,x,y表示图像绘制的起始坐标。

​ ②绘制时同时进行缩放(设定绘制图片的宽高)

​ ctx.drawImage(img,x,y,width,height);// img表示要绘制的图像,x,y表示图像绘制的起始坐标,width表示缩放后的宽度,height为高度。

​ ③生成切片

​ ctx.drawImage(img,sx,sy,swidth,sheight,dx,dy,dwidth,dheight);

​ sx,sy,swidth,sheight:用于对源图形成切片,多余部分会被裁切掉。

​ dx,dy,dwidth,dheight:表示切片在canvas画布绘制的位置及大小。

8)其他
方法描述
save()保存当前环境的状态和属性/设置一个还原点
restore()恢复到上一个保存的状态点的状态和属性/恢复到还原点

​ 对canvas中特定元素的操作实际上是对整个画布进行了操作,所以如果不对canvas进行save以及restore,那 么每一次绘图都会在上一次的基础上进行操作,最后导致错位。

JavaScript
	  ctx.fillStyle = "pink";
      ctx.fillRect(50, 50, 150, 150);
      ctx.save(); //设置还原点1
      ctx.fillStyle = "purple";
      ctx.fillRect(100, 100, 150, 150);
      ctx.save(); //设置还原点2
      ctx.fillStyle = "yellow";
      ctx.fillRect(150, 150, 150, 150);
      ctx.restore(); //恢复到还原点2
      ctx.fillRect(200, 200, 150, 150);
      ctx.restore(); //恢复到还原点1
      ctx.fillRect(250, 250, 150, 150);
9)绘制一个验证码

​ 见实训第二题

​ 生成一个n位随机字符串:

JavaScript
  //  2.用random产生一个指定长度随机数 字符串
        function randomStr(len) {
          if (len <= 11)
            //(转换后的字符串小数点后最长11位)
            return Math.random().toString(36).substr(2, len).padEnd(len, "0");
          // 转换为36进制(0-z)的字符串,再截取指定长度,长度不足len位的补0
          else {
            //长度不足再进行一次递归调用
            return randomStr(11) + randomStr(len - 11);
          }
        }
        var code = randomStr(4);

​ 完整代码如下:

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>
      .container{
        width: 100px;
        height: 50px;
        margin: 50px auto;
      }
      #cs {
        background-color: black;
        /* border: 1px solid black; */
      }
    </style>
  </head>
  <body>
    <div class="container">
      <canvas id="cs" width="100px" height="50px"
        >您的浏览器不支持canvas</canvas
      >
    </div>
  </body>
  <script>
    var cs = document.getElementById("cs");
    if (cs.getContext("2d")) {
      var ctx = cs.getContext("2d");
      // 初始绘制干扰线 绘制随机字符
      randomCode();
      randomLine();
      // 鼠标单击更新验证码
      cs.onclick = function () {
        // 先清空画布
        ctx.clearRect(0, 0, 100, 50);
        // 绘制随机字符
        randomCode();
        // 绘制干扰线
        randomLine();
      };

      // 封装随机生成数字和字母函数
      function randomCode() {
        // 随机生成数字和字母(4位)
        var code = "";
        for (var i = 0; i < 4; i++) {
          var flag = Math.ceil(Math.random() * 10);
          if (flag % 2 == 0) {
            //当随机生成一个(0-10)的数是偶数时验证码下一个字符为数字
            code += Math.floor(Math.random() * 10);
          } else {
            //当随机生成一个(0-10)的数是奇数时验证码下一个字符为字母
            code += String.fromCharCode(Math.ceil(Math.random() * 26) + 97); //a~z (97~122)
          }
        }
        console.log(code);
        // 将随机数绘制到验证码区域
        ctx.beginPath();
        ctx.font = "40px 楷体";
        textAlign = "center";
        ctx.fillStyle = "red";
        ctx.fillText(code, 10, 40);
      }

      // 封装随机生成干扰线函数
      function randomLine() {
        ctx.beginPath();
        ctx.moveTo(0, 0); //画笔移动到起始位置
        for (var i = 0; i < 20; i++) {  //20 干扰线数
          // 随机生成x,y坐标
          var x = Math.ceil(Math.random() * 100);
          var y = Math.ceil(Math.random() * 50);
          ctx.lineTo(x, y);
          ctx.strokeStyle = "white";
          ctx.stroke();
        }
      }
    }
  </script>
</html>

框架开发

1.什么是框架?什么是JS框架(库)?

​ 框架就是一个可复用的设计构件,它规定了应用的体系结构,阐明了整个设计,协作构件之间的依赖关系,责任分配和控制流程,表现为一组抽象类(构造函数)以及其实例之间协作方法,它为构件复用提供了上下文关系。

​ 简单地说,框架就是一套比较完备的体系结构。

​ JS框架就是对JS各种功能的封装和抽象,使得在使用的时候具有简便性和更好的兼容性,并且可以扩展框架的内容。

2.框架开发

1)代码写在自执行函数中(闭包写法)IIFE

​ 目的:避免变量的全局污染。

​ 语法格式:

JavaScript
;(function(global){ //传入global
	//核心代码
})(typeof window === 'undefined' ? this : window) //typeof返回字符串

​ 同时传入一个window对象,防止在IE情况下,window被修改

​ 以后再使用window就可以使用global来代替window了。

2)暴露统一的接口

​ 暴露统一的接口,目的是为了方便用户的记忆,减少污染外部变量的几率,简化操作。

​ 这个统一的接口,又可以叫做命名空间,给一个封闭的代码空间命名,就可以通过这个名字来找到里面的内容。

​ 语法格式:

JavaScript
(function (global) {
    "use strict"; //严格代码模式(变量必须先声明再使用)
    var framework = function(){
        核心代码
    }
    global.framework = global.f= framework; //暴露出去 挂在一个全局变量上
})(typeof window === "undefined" ? this : window);

<script>
    f();
	framework();
</script>

​ 添加:"use strict"; 严格代码模式。

​ 目的:如果在书写变量和方法的时候不加var 或 let 会默认为其加上window 变成全局变量,容易污染其他变量。使用严格代码模式时,变量必须先声明再使用。

3)核心代码(模块)封装

​ ①创建函数,返回一个对象(这个对象上挂载的有这个框架中的属性和方法,最终需要暴露出去)

JavaScript
  var myFramework = function () {
    return new myFramework.core.init();
  };

​ ②设置原型(为了后面方便操作原型对象,在这里用cre代替原型对象)

JavaScript
 myFramework.core=myFramework.prototype = {
  version: "1.0.0",
  constructor:myFramework,
  myFramework:myFramework,
 };

​ ③真正创建myFramework对象的构造函数

JavaScript
  var init =  myFramework.core.init = function(){
    //功能实现的代码
  }

​ ④让init和myFramework拥有共同的原型对象

JavaScript
init.prototype = myFramework.core

​ ⑤在核心代码中添加操作DOM的代码

​ ⑥扩展方法

JavaScript
myFramework.core.extend({
    first: function () {
      return this[0];
    },
    last: function () {
      return this[this.length - 1];
    },
    get: function (idx) {
      return this[idx];
    },
    each: function (callback) {
      for (var i = 0; i < this.length; i++) {
        callback(this[i], i, this);
      }
    },
  });
  global.myFramework = global.my = myFramework;

​ ⑦拓展事件

JavaScript
  myFramework.core.extend({
    click: function (callback) {
      this.each(function (item, index) {
        item.onclick = function (event) {
          callback(event);
        };
        // item.onclick = callback;
      });
    },
  });

JavaScipt设计模式

1.设计模式的意义

​ 让代码更加工程化。

2.设计模式的构成

​ 1)模式名称:模式的一个好记的名字。

​ 2)环境和问题:描述在什么环境下,出现什么特定的问题。

​ 3)解决方案:描述如何解决问题。

​ 4)效果:描述应用模式后的效果,以及可能带来的问题。

3.设计模式

1)单列模式

​ 单列模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。

①优点:

​ 可以来划分命名空间,从而清除全局变量所带来的危险。

​ 利用分支技术来封装浏览器之间的差异。

​ 可以把代码组织的更为一体,便于阅读和维护。

②缺点:

​ 可能导致模块间的强耦合。

③应用场景:

​ 单列模式在我们平时的应用中用的比较多的,相当于把我们的代码封装在一个函数中,只是暴露一个入口,从而避免全局变量的污染。

④单例模式目的:

​ Ⅰ)用来封装代码;

​ Ⅱ)减少全局变量的使用。

⑤特点:

​ Ⅰ)一个构造函数只有一个实例;

​ Ⅱ)只暴露一个入口。

2)装饰器模式

​ 装饰器模式在不改变原类和继承的情况下动态扩展对象功能,通过包装一个对象来实现一个新的具有原对象相同接口的新的对象。

​ 优点:

​ 不修改原对象的原本结构来进行功能添加。

​ 装饰对象和原对象具有相同的接口,可以使客户以与原对象相同的方式使用装饰对象。

​ 装饰对象中包含原对象的引用,即装饰对象为真正的原对象在此包装的对象。

​ 缺点:

​ 在遇到用装饰器包装起来的对象时,那些依赖于类型检查的代码会出问题。

​ 使用装饰器模式往往会增加架构的复杂程度。

​ 应用场景:

​ 如果需要为类增添特性或职责,而从该类派生子类的解决办法并不实际的话,就应该使用装饰器模式。派生子 类之所以会不实际,最常见的原因是需要增添的特性的数量和组合要求使用大量子类。

3)适配器模式

​ 适配器模式(Adapter)是将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口。

优点:

​ 适配器有助于避免大规模改写现有客户的代码。其工作机制是,用一个新的接口对现有类的接口进行包装。

缺点:

​ 可能有些工程师不想使用适配器,其原因主要在于他们实际上需要彻底重写代码。有人认为适配器是一种不必要的开销,完全可以通过重写现有代码避免。如果现有API还未定形,新接口还未定形,那么适配器可能不会一直管用。

与装饰器模式的区别:

​ 适配器模式能模拟实现简单的装饰模式的功能,也就是为已有功能增添功能。

两个模式有一个很大的不同:

​ 一般适配器适配过后是需要改变接口的,如果不改接口就没有必要适配了;而装饰模式是不改变接口的,无论多少层装饰都是一个接口。因此装饰模式可以很容易地支持递归组合,而适配器就做不到,每次的接口不同,无法递归。

适用场合:

​ 适配器适用于客户系统期待的接口与现有API提供的接口不兼容这种场合。它只能用来协调语法上的差异问 题。适配器所适配的两个方法执行的应该是类似的任务,否则的话它就解决不了问题。

4)观察者模式(发布/订阅者模式)

​ 观察者模式定义了对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖它的对象都将得到通知,完成自动更新。观察者属于行为型模式,或者叫“发布/订阅者模式”。

​ 今天的发布/订阅者模式发生了一些变化。发布者和订阅者不直接发生联系,而是通过消息中心或中间件来发生关系。

观察者模式和发布订阅模式

​ 观察者模式中存在两个角色:观察者和被观察者,又叫发布者和订阅者。

​ 观察者模式的调用顺序: ​ 1. 准备阶段(维护目标和观察者关系的阶段) ​ ⑴ 创建目标对象 ​ ⑵ 创建观察者 ​ ⑶ 向目标对象注册观察者对象 ​ 2. 运行阶段 ​ ⑴ 改变目标对象的状态 ​ ⑵ 通知所有注册的观察者对象进行相应的处理 ​ ⑶ 回调目标对象,获取相应的数据

​ 优点: ​ ① 观察者模式实现了观察者和目标之间的抽象耦合 ​ ② 观察者模式实现了动态联动 ​ ③ 观察者模式支持广播通信 ​ 缺点: ​ 可能会引起无谓的操作。

​ 适用场景: ​ 当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化,那么就可以选用观察者模式。

​ tips:

内聚:是一个模块内部各成分之间相关联程度的度量。

耦合:是模块之间依赖程度的度量。内聚和耦合的是密切相关联的,与其它模块存在强耦合的模块通常意味着弱内聚,而强内聚的模块通常意味着与其他模块之间存在弱耦合。模块设计追求高内聚低耦合。

5)策略模式

​ 定义一系列的算法,把它们封装起来,并且使它们可以相互替换。

优点: ① 策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句。 ② 策略模式提供了开放-封闭原则,使代码更容易理解和扩展。 ③ 策略模式中的代码可以复用。

策略模式指的是定义一系列的算法,并且把它们封装起来,但是策略模式不仅仅只封装算法,我们还可以对用来封装一系列的业务规则,只要这些业务规则目标一致,我们就可以使用策略模式来封装它们。

javascript
// 需求:公司的年终奖是根据员工的工资和绩效来考核的,绩效为A的人,年终奖为工资的四倍,绩效为B的人,年终奖为工资的三倍,绩效为C的人,年终奖为工资的2倍

    // 常规写法:
    /*  function finalSalary(salary,level) { 
      if(level === "A"){
        return salary*4;
      }else if(level === "B"){
        return salary*3;
      }else if(level === "C"){
        return salary*2;
      }
    } */

    // console.log(finalSalary(5000, "B"));

    /* 以上代码在处理工程化项目中可能会出现以下几个问题:
        1.如果增加条件判断,比如增加D,E,F等级别,上面的代码可能需要重写----不利于功能扩展
        2.如果某个条件的算法与其他的不一样,这时也需要重写代码,这就意味着上面代码不利于复用
     */

    //  采用策略模式实现上面的需求
    var obj = {
      //定义对象,用于封装算法
      A: function (salary) {
        return salary * 4;
      },
      B: function (salary) {
        return salary * 3;
      },
      C: function (salary) {
        return salary * 2;
      },
    };

    var finalSalary = function (salary, level) {
      return obj[level](salary);
    };

    // 扩展(增加条件)
    obj.D = function (salary) {
      return "没有这个算法"; //修改算法
    };
    // 修改算法
    obj.A = function (salary) {
      return salary * 4 - 1000;
    };

    console.log(finalSalary(5000, "D"));
    console.log(finalSalary(5000, "A"));
6)模板模式(也叫模板方法)

​ 定义一个操作中的算法骨架,而将一些步骤延迟到子类中(不一样的某些方法在子类中重新定义)。模板方法使得子类可以不改变一个算法的结构即可重新定义算法的某些特定步骤。

主要解决:定义通用方法,却在子类中重写这一方法。

优点: (1)封装不变的部分,扩展可变的部分。 (2)提取公共代码,便于维护。 (3)行为由父类控制,子类实现。

缺点: 每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

使用场景: (1)有多个子类共有的方法,且逻辑相同。 (2)重要的、复杂的方法,可以考虑作为模板方法。

钩子(Hook)操作 它提供了缺省的行为,子类在必要时进行扩展。

模板模式和策略模式的区别: 模板方法使用继承来改变算法的一部分,而策略模式使用委托来改变整个算法。

7)代理模式

​ 为其他对象提供一种代理以控制这个对象的访问。

使用场景: 1)远程代理,也就是为了一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空 间的事实,就像web service里的代理类一样。 2)虚拟代理,根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象,比如浏览器的渲 染的时候先显示问题,而图片可以慢慢显示(就是通过虚拟代理代替了真实的图片,此时虚拟代理保存了真实图片 的路径和尺寸。 3)安全代理,用来控制真实对象访问时的权限,一般用于对象应该有不同的访问权限。 4)智能指引,只当调用真实的对象时,代理处理另外一些事情。

8)外观模式

​ 为多个接口提供一个统一的接口的界面。

​ 主要用于处理浏览器的兼容问题(来做一些兼容性的封装处理)。

缺点:

​ 外观模式被开发者连续使用时会产生一定的性能问题,因为在每次调用时都要检测功能的可用性。

JavaScript
  // 需求:处理浏览器对事件触发的兼容处理。
	// 外观模式写法(将多个接口封装成一个接口)
    var addMyEvent = function (dom, event, fn) {
      if (dom.addEventListener) {
        dom.addEventListener(event, fn, false);
      } else if (dom.attachEvent) {
        dom.attachEvent("on" + event, fn);
      } else {
        dom["on" + event] = fn;
      }
    };

    addMyEvent(oBox, "click", function () {
      console.log("这是外观开发模式");
    });
    addMyEvent(oBox, "mouseover", function () {
      console.log("这是外观开发模式");
    });

1、什么是装饰器模式?装饰器模式的缺点是什么?在什么场景使用?

装饰器模式在不改变原类和继承的情况下动态扩展对象功能,通过包装一个对象来实现一个新的具有原对象相同接口的新的对象。

缺点: 1)在遇到用装饰器包装起来的对象时,那些依赖于类型检查的代码会出问题。 2)使用装饰器模式往往会增加架构的复杂程度。

使用场景: 1)如果需要为类增添特性或职责,而从该类派生子类的解决办法并不实际的话,就应该使用装饰器模式。 2)如果需要为对象增添特性而又不想改变使用该对象的代码的话,也可以采用装饰器模式。因为装饰器可以动态而又透明地修改对象,所以它们很适合于修改现有系统这一任务。

2、简述一下装饰器模式与适配器模式的区别。 1)适配器模式能模拟实现简单的装饰模式的功能,也就是为已有功能增添功能。 2)两个模式有一个很大的不同:一般适配器适配过后是需要改变接口的,如果不改接口就没有必要适配了;而装饰模式是不改变接口的,无论多少层装饰都是一个接口。

3、什么是观察者模式?观察者模式在什么场景使用? 观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。观察者模式在 javascript 中使用非常广泛。所有的浏览器时间就是该模式的实现。 观察者模式中存在两个角色:观察者和被观察者,又叫发布者和订阅者。

适用场景: 1)当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化,那么就可以选用观察者模式,将这两者封装成观察者和目标对象,当目标对象变化的时候,依赖于它的观察者对象也会发生相应的变化。这样就把抽象模型的这两个方面分离开了,使得他们可以独立地改变和复用。 2)如果在更改一个对象的时候,需要同时连带改变其他的对象,而且不知道究竟应该有多少对象需要被连带改变,这种情况可以选用观察者模式,被更改的哪一个对象很明显就相当于是目标对象,而需要连带修改的多个其他对象,就作为多个观察者对象了。 3)当一个对象必须通知其他对象,但是你又希望这个对象和其他被它统治的对象是松散耦合的。这个对象相当于是目标对象,而被它通知的对象就是观察者对象了。

4.策略模式的优点? (1)策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句。 (2)策略模式提供了开放-封闭原则,使代码更容易理解和扩展。 (3)策略模式中的代码可以复用。

5.模板模式的优点? (1)封装不变的部分,扩展可变的部分。 (2)提取公共代码,便于维护。 (3)行为由父类控制,子类实现。

6.何时使用外观模式呢? 一般用于浏览器的兼容处理等情况。

CSS预处理

Less

1.CSS预处理

​ 目前CSS预处理语言主要有:Less,SASS,Stylus等。

​ 浏览器不能编译.less/.scss/.sass/.styl等文件,在html文件引入前面这些样式文件,必须先编译成.css文件。

2.在VSCode中编译Less

1)安装插件

​ 在vscode的扩展商店中搜索 Easy Less插件安装。

2)修改设置文件(settings.json)

​ 在settings.json 中添加如下代码:

json
,
    "[less]": {
        "editor.suggest.insertMode": "replace"
    },
    "less.compile": {
        "compress": false,/*是否进行压缩处理*/
        "sourceMap": false,/*是否生成map(映射文件)文件,
        如果为true则可以在控制台看到less行数*/
        "out":true, /*true:表示编译后输出*/
        "outExt": ".css" /*设置编译后的文件的扩展名*/
    }

3.Less官网

https://lesscss.cn/

4.CSS预处理器

CSS预处理是一种将CSS作为目标生成文件的,使用变量、函数及简单的逻辑实现更加简洁、适应性更强、可读性更好、更易于代码维护的兼容浏览器的页面样式文件。

​ 通过编程来写CSS。

5.Less

Less 是一门 CSS 预处理语言,它扩充了 CSS 语言,增加了诸如变量、混合、函数等功能,让 CSS 更易维护、方便制作主题、扩充。Less 可以运行在 Node 或浏览器端。

​ 形成的文件的扩展名为:.less。

​ 使用的目的:是为了提升开发效率。

6.Less语法

1)变量

①定义变量:

​ 格式:@变量名:值;

less
// 定义一个变量
@cl :gray;
// 使用一个变量定义另一个变量
@bgCl: cl;
// 将一个变量的值赋给另一个变量
@lineCl :@cl;

div {
  color: gray;
  background-color: @bgCl; //编译为:cl
  background-color: @@bgCl; //编译为:gray
  border: 1px solid @lineCl; //编译为:gray
}

​ 注意:在less中引用变量时要把变量当作一个常量处理

②延迟评估(变量在使用前不必声明)

less
// 延迟评估
span{
  color: @a; //编译为:red
}
@a:red;

③作为变量的属性

​ 使用 $ 语法将属性视为变量

less
div{
  color: red;
  background-color: $color;//编译为:red
  //将属性color 当作一个变量
}
span{
  color: pink;
  i{
    background-color: $color; // 编译为aqua
  }
  color: aqua;
}

​ 注意:Less 将选择当前/父作用域内的最后一个属性作为 $变量名 的值。

2)混合用法

​ 在less中定义一些通用的属性放在一个class中,然后再在另一个样式中调用它。

用法:

​ .类名{

​ /* css样式表; */

​ }

​ 使用:

​ .类名;

image-202310271937097072

3)带参数的混合用法

用法1(不带参数):

​ .类名(){

​ /* css样式表; */

​ }

​ 使用:

​ .类名[()];

​ 注意:.类名()加括号 里面的内容不会被编译,不加括号里面的内容会被编译成css

less
// 封装为一个(函数) 括号可以省略不写
.txtOver(){
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.p1{
  .txtOver();
  .txtOver;
}

用法2(有参数):

​ .类名(参数列表){

​ 属性名:参数1;

​ 属性名:参数2;

​ .........

​ }

​ 使用:

​ .类名(实参1,实参2);

less
.style(@width, @height, @color) {
  width: @width;
  height: @height;
  color: @color;
}
div {
  .style(200px,100px,red);
}

用法3(为参数设置默认值):

​ .类名(参数1:默认值,参数2[:默认值],......){

​ 属性名:参数1;

​ 属性名:参数2;

​ ..........

​ }

less
.style(@width:200px, @height:200px, @color:red) {
  width: @width;
  height: @height;
  color: @color;
}
div {
  .style(200px,100px,red);//会覆盖默认值
  .style(); //全部为默认值
}
4)嵌套规则

​ Less中的嵌套规则允许在一个选择器中嵌套另一个选择器,通过缩进一个空格来实现父子级关系。

​ 父级选择器:使用 & 引用父选择器,通常在修改类或伪类应用于现有选择器时使用。

​ 格式:

​ 父级(父类){

​ 属性:属性值;

​ .....

​ 子级(子类){

​ 属性:属性值;

​ ......

​ &:hover{

​ 属性:属性值;

​ .......

​ }

​ &:before{

​ 属性:属性值;

​ .......

​ }

​ &:nth-of-child(n){

​ }

​ .......

​ }

​ }

less
.container {
  width: 200px;
  height: 400px;
  .section1 {   //.container .section1
    width: 150px;
    height: 300px;
    article { //.container .section1 article
      color: pink;
      line-height: 2em;
    }
  }
  .section2 {
    ul {  //.container .section2 ul
      width: 100px;
      float: left;
      li {  //.container .section2 ul li 
        width: 50px;
        &:hover { //.container .section2 ul li:hover
          background-color: aqua;
        }
      }
    }
  }
  &-.article{ //.container-.article 
    width: 200px;
  }
}
5)运算

​ 在Less中可以使用 +、-、 *、 /、 进行算数运算

​ 注意:在写减号时要在运算符两侧加空格,乘号和加号不需要,若参数进行了算数运算最好为其加上括号。

less
@w: 100px;
@num: 10px;
p {
  width: @w*2.5;
}
.container1 {
  margin: @num*2 @num*@w @num - 2;
}
.container2 {
  margin: ((@num+5) * 2) @num*1.5 (@num - 5) (@num / 3);
  // 注意:在写减号时要在运算符两侧加空格,乘号和加号不需要
  // 若参数进行了算数运算最好为其加上括号
}
6)作用域

​ 作用域在编译时采用就近原则。首先会从作用域内查找变量或混合模块,如果没找到,则去父级作用域中查找,直到找到为止。(与JS用法相似)

less
// 全局作用域
@var: red;
header {
  // 局部作用
  @var: blue;
  nav {
    background-color: @var; //blue
  }
}
section {
  @var: pink;
  border: 1px solid @var;//pink
}
footer {
  color: @var; //red
}

7.函数

1)Color函数

​ 颜色会先被转化为HSLA(色相,饱和度,亮度,不透明度)色彩空间,然后在通道级别中操作。

​ 取值范围:(h:0-360、s:0-100%、l:0-100%、a:0-1)

​ 设置颜色:

less
  //调整亮度
  lighten(@color, 10%); 返回一个比@color浅10%的颜色
  darken(@color, 10%); 返回一个比@color10%的颜色
  //调整饱和度
  saturate(@color, 10%); 返回一个比color饱和比高10%的颜色
  desaturate(@color, 10%); 返回一个比color饱和比低10%的颜色
  //调整不透明度
  fade(@color, 50%); 返回一个@color颜色的50%透明度的颜色
  //调整色调
  spin(@color, 10); 返回一个比@color 色调大10度的颜色
  spin(@color, -10); 返回一个比@color 色调小10度的颜色
  //混合颜色
  mix(@color1, @color2) 返回一个@color1和@color2混合的颜色

​ 提取颜色信息:

less
  hue(@color); 返回@color//颜色的色调 返回:0-360
  saturation(@color); //返回@color颜色的饱和度通道
  lightNess(@color); //返回@color颜色的亮度通道
2)Math函数
less
  round(x,y) //对x四舍五入,保留y个小数点
  ceil(x) //向上取整
  floor(x) //向下取整
  percentage(x) //取百分数。eg:percentage(0.6)返回60%
  min(a,b,c,d,e…); //取最小值
  max(a,b,c,d,e…); //取最大值
  sqrt(a);	//计算数字的平方根
  abs(a);	//计算数字绝对值
  pi()		//返回Π
  pow(a,b)	//返回a的b次方
  [a]sin(a);	//计算[反]正弦
  [a]cos(a);	//计算[反]余弦
  [a]tan(a);	//计算[反]正切值
less
@w: 10px;
@a: -2px;
@b: 50px;
div {
  border: round((@w/3), 2) solid #000; //3.33px
  height: max(@w, @a, @b);	//50px
  width: min(@w, @a, @b);	//-2px
}
.width(@width) {
  width: percentage(@width)	//80%
}
.box {
  .width(0.8);
}

@a:25px;
@b:30;
@c:30deg;
@d:-20px;
div{
  width: sqrt(@a); //5px
  height: sin(@b);  //-0.98803162
  margin: sin(@c);  //0.5
  padding: abs(@d); //20px
}
span{
  width: pi(); //3.14159265
  height: pow(2,3); //8
}
3)类型函数
less
isnumber(a);	//判断是否是数字 是数字返回true,不是返回flase
isstring(a);	//判断是否是字符串("string") 是字符串返回true,不是返回flase
iscolor(a);		//判断是否是颜色,是返回true,不是返回flase
.....

8.Less避免编译

​ 在开发时,可能会出现Less不识别的代码,或者不让编译,这是需要用到Less的专用语法:避免编译。

​ 用双引号引起来在前面加上~。

less
.box{
  width: ~"100px - 20px"; 
  //对应CSS: width: 100px - 20px;
}

实训:

1)任何在开发模式下引入less文件和less.js文件?
html
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>实训一</title>
    <!-- 
    注意:
    1)一般在生产环境中(上线),不直接引入less文件,而直接引用的是编译后的css文件;在开发环境中可以引入less文件
    2)less.js文件的引入一定要放在.less文件之后,且一般放在<head></head>标签中
  -->
    <!-- 引入.less -->
    <link rel="stylesheet/less" href="./实训1.less" />
    <!-- 引入less.js -->
    <script src="https://cdn.bootcdn.net/ajax/libs/less.js/4.2.0/less.min.js"></script>
  </head>
2)使用less语法将“故都的秋”选段设置样式。

​ 见实训