从零开始のJavaScript

149 阅读19分钟

一、书写位置

1、行内式(不推荐)

(1)联想学习:类似于 CSS 的行内式,直接添加在 html 的标签之中使用,CSS 使用的 style 属性进行引入

(2)触发条件:需要依靠事件(行为)来触发

(3)举例(onclick 点击事件)

<a href="javascript:alert('我是一个弹出层');">点击一下试试</a>
<div onlick="alert('我是一个弹出层');">点一下试试</div>

2、内嵌式

(1)联想学习:与 CSS 的内嵌式不同,JS 的可以放在 body 之中

(2)触发条件:打开页面时直接触发

(3)引入方式:(一般放在 body 的末尾,防止过多代码加载延后页面内容显示)

<script type="text/javascript"> 
    /*在HTML5中可以不用写type*/
    alert('我是一个弹出层') 
</script>

3、外链式

(1)联想学习:类似于 CSS 的 link 引入

(2)触发条件:只要在 HTML 中引入了 JS 文件,打开页面时直接触发

(3)引入方式:

<script src="file.js"></script>
/*和CSS的link引入相同,会直接隐藏scrip中的详细内容*/

(4)节省流量的原理:浏览器只有第一次运行的时候需要下载文件,下载好的文件会在缓存之中,下次再次运行时会调用缓存中的文件,从而达到节省流量的效果

(5)注意:使用外链式引入后 html 文件中的 script 标签中不能再写属性,会产生冲突!

二、书写规范

1.分号:

大部分的 JS 代码之间都可以用换行符代替分号,但是还是有很多的代码前/后的换行符是代替不了分号的——如[]前就会不起效果,所以一般规范就是所有语句之后都要求添加分号作为分隔

2 .注释:

(1)单行注释:

①说明:使用"// 我是被注释的内容"来进行注释,注意可能有一些的//在语句末尾,此时其起到修饰下面一行语句的作用

②快捷键:ctrl + /,跟 html 和 css 的单行不同,显然没有按两个/方便

(2)多行注释:

①说明:跟 html 和 css 的多行注释不同,反而是用/* 我是被注释的内容*/来进行多行注释

②快捷键:shift+alt+a,这个应该就是看个人喜好了,都差不多

三、"use strict"(严格模式)

1.功能:

激活 ES5 规范中添加的新特性和对一些旧特性的修改(默认是不启用的)

2.启用方法:

一般习惯于在 JS 的顶部写"use strict"并独立成行,当然也可以对某个函数单独使用严格模式,但是容易失效。。

四、变量

1.功能:用来作为储存数据的容器

2.定义和赋值:

(1)常见的定义&赋值

let num=66;

(2)多变量的定义&赋值

①单行:

let user='Lancelot',age=18,message='hello';

②多行:

let user='Lancelot',
    age=18,
    message='hello';

多个变量的定义可以单行定义,但是为了便于阅读,还是多行阅读为妙

(3)注意

①赋值可以多次赋值但是声明只能声明一次

var 和 let 都可以用来定义变量,前者全局可用,后者只在块作用域中起效(比如在for 循环中用 let 定义了一个变量,在循环之外是不能使用的)

额外需要强调的是:var 可以多次声明,因为没有块作用域,可以先使用再声明(但是赋值不起效 )

③变量内容也可以通过某些函数来进行锁定,比如 scala()——有点像 C 中的 const()

3.命名规则&规范

规则:

(1)可以由数字、字母、下划线“_”、美元符号“$”组成

(2)严格区分大小写

(3)不能由数字开头

(4)不能是保留字或关键字

规范:

(1)尽量做到语义化

(2)第二个单词首字母大写

五、数据类型

(变量在定义后的数据类型可以改变)

1.Number

(1)常规类型:整数和浮点数,可以使用基础运算符进行计算

支持二进制,八进制等常见进制的写法

(2)特殊类型:

①Infinity:无穷大(可以通过直接书写或者 1/0 来使用)

②NaN:表示计算错误,是一个不正确的或者一个未定义的数学操作所得到的结果

alert( "not a number" / 2 ); // NaN,这样的除法是错误的 

NaN 是粘性的,只要第一步出现了 NaN,无论后面进行什么操作都会返 NaN,但是要注意,JS 中的数学运算是安全的,不会因为一个严重的错误而报错停止运行程序,最坏也只会得到 NaN 的返回值(跑肯定是能跑起来的,出啥结果就看造化了)

2.BigInt(了解即可,不常用)

(1)用途:用来定义极其大的 number,这种情况极其少见

(2)使用:在数值的末尾加“n”即可

3.String(结合函数式字符串学习)

(1)用途:用来使用字符串(字符串必须放在引号中使用)

(2)类型:

①单引号/双引号(和在 python 中一样没有什么区别,一般在引号嵌套时交替使用)

反引号:

Ⅰ 用途:用于功能性拓展

Ⅱ 说明${}中可以放置任何东西(比如变量和表达式),会将其计算后放入反引号中的所在位置上

Ⅲ使用(见下方代码)(具体位置在键盘数字 1 的左边)

let name = "John"; // 嵌入一个变量 
alert( `Hello, ${name}!` ); // Hello, John! // 嵌入一个表达式 
alert( `the result is ${1 + 2}` ); // the result is 3

(3)注意: JS 中没有 char 类型,引号内可以不放字符或者放置多个字符

4.Boolean(结合逻辑运算符学习)

1.用途:储存 true/false

2.使用:可以直接定义,也可以是运算符运算的结果

let nameFieldChecked = true; 
let ageFieldChecked = false; 
//上面的是直接定义,下面的是运算符运算结果
let isGreater = 4 > 1; 
alert( isGreater );

5.Null&Undefined

(1)Null:相比较于其他编程语言,JavaScript 中的 null 不是一个“对不存在的 object 的引用”或者 “null 指针”。 JavaScript 中的 null 仅仅是一个代表“无”、“空”或“值未知”的特殊值。

(2)Undefined:

①含义:违背赋值

②得到方式:

Ⅰ已被声明,但未被赋值的变量的值

Ⅱ直接赋值:不建议赋值使用,因为赋值后将保留作为未进行初始化的事物的默认初始值

6.Object&Symbol(结合对象学习)

(1)Object:

区别于其他的“原始类型”(值只包含一个单独的内容:字符串、数字或者其他)。object 用于储存数据集合和更复杂的实体。

(2)Symbol:

用于创建对象的唯一标识符

六、数据类型的判断和转换

1.判断(typeof 关键字)

(1)使用方式:

typeof "foo" //第一种
typeof("foo")//第二种

(2)注意:

①判断 null 会返回 object

②检测结果是字符串

③两种方式对复杂检测有一定区别(具体见下面代码)

let num=100;
typeof num+100; 
//第一种输出结果是“number100”
//加号起拼接作用,没有括号会先左结合判断类型再拼接
typeof(num+100)//第二种输出结果是“200”

2.转换

使用类型转换时一定不要忘记给其定义!!!

(1)Number()

①把变量强制转换成数值类型

②可以转换小数,会保留小数(?这是什么转换法)

③可以转换布尔值

④转换失败会返回NaN

(2)parseInt()

①从第一位开始检查,是数字就转换,一直到不是数字的内容为止

②开头不是数字会直接返回 NaN

③不认识小数点,只能保留整数

(3)parseFloat()

和 parseInt 的唯一不同就是认识小数点

(4)变量.toString()

undefined 和 null 不能用其转换为字符串

(5)String()

各种数据类型都可用

(6)Boolean()

除了''0nullundefinedNaN,都是 true

可以使用!!变量来快速转换为布尔值

七、运算符

1.数学运算符

几乎和其他语言一致,注意以下两点即可

(1)+功能和 python 中的一致,同样可以拼接字符串

(2)所有数学运算符都会在计算之前把两边转化为 number

2.赋值运算符

和 C 语言一模一样,同样支持复合赋值(比如+=)

3.比较运算符

(1)和 C 语言唯一不同——可以使用"==="来比较两边的类型和数值是否相同

(2) JS 中会把待比较的值转化为数字后再做比较(因此 "0" 变成了 0 )。若只是将一个变量转化为 Boolean 值,则会使用其他的类型转换规则。

(3) 注意:①JS 中字符串同样可以进行比较,从左往右一位一位进行比较,第一位大就不再比较后面的字母直接输出 true(如果是字母,根据①小写>大写②a>b>c>...>z, 如果是数字比该位的大小即可,在第一位大的时候会出现短路现象alert('2'>'12');的结果为真。。)

②普通相等性检查(==,!=等等)无法区分0false''——都会返回真,只有严格相等性检查能够区分

4.逻辑运算符

(1)类型:和 C 语言的类型相同(同样有&& ||的短路现象)

(2)注意:

①非零的&&短路只会输出左边的值 (待删改)

alert(alert(1)&&alert(2));//先输出1,再输出undifined??
alert( null || 2 && 3 || 4 );//输出为3??

5.自增自减运算符

和 C 语言中相同,不再赘述

八、简单交互函数(已转至弹出层一节)

1.alert

(1)作用:跳出一个弹窗,等待用户点击确认

模态窗——窗口会被强制置顶,在用户进行相关操作之后解除

(2)使用方法:

alert("你好啊qwq");

2.prompt

(1)作用:跳出一个弹窗,可以接收 1 个来自用户的数据(含有 1 个 input 并可以指定默认参数

(2)使用方法:

result = prompt(title, [default]);
//title——展示给用户的文本
//default——可选参数,指定默认参数
 //Number直接输入即可,字符串需要添加引号
 //会将结果自动转换成字符串,所以之后判断的时候注意类型是否匹配

(3)注意:prompt 可以用来赋值,这样就可以起到类似于 C 和 Python 中输入函数的效果;如果用户选择了“取消”,那么会返回 null

3.confirm

(1)作用:跳出一个弹窗,获得用户在是或否中的选择

(2)使用方法:和 aler 类似

简单交互函数十分便捷,同样有很大的弊端:无法更改确切位置,也无法更改外观

九、分支语句

1.if/else if/else

(1)纯 if 语句

①作用:通过是否符合条件来判断是否执行代码块中的内容

②使用方法

let num = prompt('请输入你猜的数字', [50]);
        num = Number(num);
        // console.log(typeof num);
        if (num === 50) 
        //if后的括号内可以添加表达式,结果需为真或假!!
        {
           alert('你猜对了')
        } 

(2)杂 if 语句(含有 else、else if)

说明:

①else 只能添加在最末尾,且只能添加一个

②else if 需要添加在 if 语句后,可以添加多个,但是逐行进行条件判断之后才会进行下一行,,如果全部的条件都无法满足而且没有 else 将不会有有效输出内容

(3)案例:简单用户登录判断

    //弹出输入框,并把用户名放入容器a中
    let userId=prompt('请输入你的用户名','');
    //判断用户名是否合法
    if (userId=='Admin')
    {
        //判断密码是否正确
        //弹出输入框,并把密码放入容器a中
        let password=prompt('请输入你的密码','')
        if (password=='TheMaster')
        {
            alert('欢迎回家,管理员先生');
        }
        else if (password==null&&password=='')
        {
            alert('已经取消密码输入');
        }
        //3次重新输入的机会
        else
        {
            let i=1;
            while (password=='TheMaster'||i<=3)
            {
                let password=prompt(`密码输入已错误${i}次,请重新输入`,'');
                i+=1;
            }
            alert('多次输入错误,已锁定');
        }
    }
    else if(userId==''&&userId==null) //为什么空字符和'esc'不起效?? 
    {
        alert('已经取消用户名输入')
    }
    else
    {
        alert('非法用用户,请确认您是否输入正确')
    }

2.switch/case

(1)作用

通过判断变量值是否严格等于 case 中的值来决定是否执行 case 中的内容

(2)和 if/else/else if 的区别

①switch 只能判断是否严格相等

②若不添加[break]而且满足多个条件会输出多个 case 中的内容

③可以通过给某个 case 去[break]的方式实现多个条件输出相同结果的效果

(else if 用逻辑运算符也可以得到===3&&===5 即可)

(3)使用方法:

switch(x) 
 //x是想要判断的变量或者表达式
{ 
    case 'value1': // if (x === 'value1') 
        ... [break] //[break]可以选择性添加
    case 'value2': // if (x === 'value2') 
        ... [break]    
    default:  //default也可以选择性添加
        ... [break] 
}

十、循环语句

1.while

(1)执行流程

(2)语法结构

while (表达式)
{
  语句
}

(3)注意

①表达式可以为任何输出为真或假的式子,甚至可以是变量本身(i等价于i!=0)

②若语句只有一条可以不加花括号,但是为了直观,习惯于加花括号

2.do...while

除了执行流程是先执行语句再进行判断,其余和while规则相同

一般用于循环体至少要执行一次的情况下,不常见

3.for

(1)语法结构

for (let i=0;i<3;i++)
{
  alert(i);//语句
}

(2)注意

①括号内由三部分组成——begin;condition;step

②begin:可以在括号内声明((内联变量声明)只能在此代码块中使用)也可以使用之前声明的变量

若在代码块上面已经进行声明和赋值了,那么其可以省略,但是要保留";"

在for中再次嵌套dor的时候,要重新声明变量和变量值

③condition:在每次循环开始时进行判断

可以省略,同样的要保留“;” ,省略后可以实现无线循环,中间可以搭配if...break来在中间终止循环

④step:决定了循环的步长,可以省略

若在step中使用i++/i--会和while (i++/i--) 有明显区别,结合下面例子说明

for (let i = 0; i < 5; i++) //输出结果:依次弹出0,1,2,3,4
{
alert(i);
}
——————————————————————————————————————————————————
let i = 0; 
while (i++ < 5) //输出结果:依次弹出1,2,3,4,5
{
alert( i );
}

根据两个代码块的输出结果不难发现:

在for的step位置的i++的逻辑是先保留原值进行一次循环,循环末尾+1,再进行下一次循环

在while ()中的i++的逻辑是先+1进行一次循环,再进行下一次循环(反正常i++的逻辑)

4.break&continue

(1)break: 在语句或语句嵌套函数的末尾,起到终止循环的作用

(2)continue: 停止本次循环,强制进入下一次循环,可以减少嵌套

(3)注意: 不能和三元运算符?一起使用

5.break&continue标签

(1)作用

通过给最大的外循环定义标签,来实现当内循环达到某条件时,跳出终止所有循环的作用

(2)语法结构

outer: for (let i = 0; i < 3; i++) 
{
   for (let j = 0; j < 3; j++)
    {
        let input = prompt(`Value at coords (${i},${j})`, '');
        // 如果是空字符串或被取消,则中断并跳出这两个循环。
        if (!input)
        {
         break outer; // (*)
        }
        // 用得到的值做些事……
    }
 }

十一、自定义函数

1.函数的声明和调用

function 函数名(行参)
{
    函数内语句
}
函数名();

行参和 实参 *的区别:

(1)行参:行参是在function自定义函数中定义了新的变量,只能在function中使用(当然function也可以调用外部的全局变量,假如行参名和某一全局变量名相同,会遮盖全局变量,在未调用自定义函数时全局变量还是原先的值

可以设置默认值,如(a,b='我是b的默认值哦')

(2)实参:在调用函数时,在括号中输入的值,自定义函数相当于一个加工厂,行参以及内部的表达式是菜谱,输入实参相当于照着菜谱做一遍菜,整个过程下来,只有实参经过了改变

当行参有多个变量时, 实参****可以只写部分变量,剩下的取默认值,行参未提前定义时为undifined

2.函数的返回值(return)

(1)自定义函数中没有return来规定返回值的时候,默认调用后没有返回值

(2)视情况来给函数设置return,可以是自定义函数中的局部变量,也可以不设置

(3)return能起到类似于break的效果,会终止自定义函数的运行(如果return后还有语句,将不再运行)

3.箭头函数(特殊而又简洁的函数创建方法)

(1)创建方法

①单行式

let sum = (a, b) => a + b;
//等价于下面的式子
let sum = function(a, b) 
{ 
    return a + b; 
}; 

②多行式(类似于赋值式创建自定义函数,少了一次新变量的定义赋值)

let sum = (a, b) => 
{ // 花括号表示开始一个多行函数
     let result = a + b;
     return result; // 如果我们使用了花括号,那么我们需要一个显式的 “return”
};
alert(sum(1, 2)); // 3

4.预解析

对于var(let 不起效)和自定义函数,会默认吧声明置于开头(定义还是在原位置),假若在书写时先调用函数或引入变量不会报错,只会提示未定义

5.变量使用规则

(1)访问规则(获取一个变量的值的行为叫做“访问”)

①首先,在自身所在的作用域内部查找

②然后,依次向上一级作用域中查找,如果有就获取使用,没有就继续向上查找

③最后,如果全局作用域中都没有此变量,那么会直接报错(显示未定义)

(2)赋值规则

①首先,在自身所在的作用域内部查找,有就给其赋值

②然后,依次向上一级作用域中查找,如果有就给其赋值,没有就继续向上查找

③最后,如果全局作用域中都没有此变量,那么会把此变量定义为全局变量,再给其赋值

十二、简单对象

壹、对象

1.用途

用来储存键值对和更复杂实体的容器,容器中有一个个小房间用来储存,通过其门牌号(标识符)来进行对其进行操作

可以用来展示各种各样的数据

2.创建方法

(1)构造函数法(不常用)

let user = new Object();

(2)字面量法

let user = {};

3.键(属性名)&属性值

(1)属性名:

①可以是单个单词或者多个单词,多个单词必须加引号

②也可以是从其他变量中动态获取

③命名不受任何变量命名的规则限制,可以取for之类的属性名,非字符串类型的会自动转换,比如 0 就会转换成 '0'

④通常用已存在的变量名作为属性名 name:name, 前面的是key后面的是value,后者从变量name中获得值

let user = { 
  name: "John",
  age: 30, 
  "likes birds": true // 多词属性名必须加引号 
}; 

let fruit = prompt("Which fruit to buy?", "apple"); 
let bag = {
  [fruit]: 5, // 属性名是从 fruit 变量中得到的 
}; 
alert( bag.apple ); // 5 如果 fruit="apple"

(2)属性值:

属性值可以是任何形式,注意用const声明的对象的属性值是可以更改的

4.基本操作(增删改查)

(1)增添

点增添:可以使用点符号对简单属性名进行访问和增添

第二次对同样的key进行增添会更改value

//通过点符号进行访问
alert( user.name ); // John 
alert( user.age ); // 30
//通过点符号进行增添
user.lct='苏州高新区';//增添了用户地址为苏州高新区

(2)删减

delete user.age;

(3)查询

document.write('key的中文意思'+user.name);

(4)存在性检查(检查key是否存在)

返回真或假来表达key是否存在

优势在于,如果key的value是undifined的时候也同样会返回真(区别于===)

let user = { name: "John", age: 30 };
alert("age" in user); // true,user.age 存在
alert("blabla" in user); // false,user.blabla 不存在。

(5)方括号的特殊语法

let user = {}; // 设置 
user["likes birds"] = true; // 读取或创建

alert(user["likes birds"]); // true 
delete user["likes birds"];// 删除 
————————————————————————————————————————————————
let user = {
  name: "John",
  age: 30,
};
let key = prompt("What do you want to know about the user?", "name");
// 访问变量
alert(user[key]); // John(如果输入 "name")
 //这里的Key可以是程序运行时计算得到的,也可以是根据用户的输入得到的。然后我们可以用它来访问属性。 
—————————————————————————————————————————————————————————————————————————————————————————
let fruit = prompt("Which fruit to buy?", "apple"); 
let bag = { 
  [fruit]: 5, // 属性名是从 fruit 变量中得到的 
   //因为[]的强大能力,甚至可以在[]中用+进行pi获得key名
}; 
alert( bag.apple ); // 5 如果 fruit="apple"
 //可以起到显示特定的key的作用,是apple就显示不是就不显示

(6)遍历

①用途

通过for in对对象中所有key进行操作

②语法结构
let user = {
  name: "John",
  age: 30,
  isAdmin: true,
};
for (let key in user) {
// keys
  alert(key); // name, age, isAdmin
   // 属性键的值
  alert(user[key]); // John, 30, true
}
③排列顺序

除了整数按从大到小顺序排列(整数属性),其他都按创建顺序排列,可以通过把整数变成如 '+49' 的形式来变更排列顺序为按创建顺序排列

贰、对象的引用&克隆

1.引用

(1)对引用的理解:

①赋值了对象的变量储存了对象在内存的地址,每次都通过地址对对象进行操作

②当此变量被复制后,对象本身并没有被复制,实际上复制的是对象的地址(即引用),复制前只有1个末影箱可以对你存储物品的末影箱进行操作,但是复制后有2个末影箱可以进行操作了,而且操作的都是同一个装物品的箱子(虽然跟MC的设定不符,但是很合适)

(2)引用的比较:

只要赋值了对象的变量名不同,即使都是空对象,比较结果也是假,但是如果都是指向同一个地址的变量,那么则输出为真

let a = {}; 
let b = a; // 复制引用 
alert( a == b ); // true,都引用同一对象 
alert( a === b ); // true        

let a = {}; 
let b = {}; // 两个独立的对象 
alert( a == b ); // false 

2.拷贝

(1)浅层拷贝(无法复制对象中的对象)

①遍历拷贝

流程: 创建新的空对象→使用for in遍历拷贝所有的属性

let user = { 
  name: "John", 
  age: 30,
}; 
let clone = {}; // 新的空对象 
// 将 user 中所有的属性拷贝到其中 
for (let key in user) { 
  clone[key] = user[key]; 
} // 现在 clone 是带有相同内容的完全独立的对象 
clone.name = "Pete"; // 改变了其中的数据 
alert( user.name ); // 原来的对象中的 name 属性依然是 John
②assign拷贝
语法:
object.assign(target,former1,former2,...,formerN);
 //target为需要进行拷贝的新对象,可以为空(如果为空则会创建新的对象) 
 //former为被拷贝的原对象,可以设置多个(如果设置多个则会进行合并操作) 
Ⅱ 注意点:

A.如果目标对象中有和原对象相同的key,则会对目标对象key的value值进行覆盖

B.不会在源对象值为 nullundefined 时报错,但是会进行忽略。

C.报错会提示TypeError,如果在报错前就拷贝了的key会储存在新对象中

(2)深层拷贝(可以复制对象中的对象)

使用循环递归来进行拷贝(看样子是可以用的,但是不知道怎么用)

function deepClone(obj, newObj) {
    var newObj = newObj || {};
    for (let key in obj) {
        if (typeof obj[key] == 'object') {
            newObj[key] = (obj[key].constructor === Array) ? [] : {}
            deepClone(obj[key], newObj[key]);
        } else {
            newObj[key] = obj[key]
        }
    }
    return newObj;
}

叁、垃圾回收

1.可达性

(1)介绍

储存在内存中,可以通过某种方式被访问或被使用

如果不可达会被垃圾回收器删除

(2)固有的可达值(根)

①当前函数的局部变量和参数

②嵌套调用时,当前调用链上所有函数的变量和参数

③全局变量

(3)可达性的获得

通过引用或者引用链可以从根访问任何值时获得可达性

(4)案例

①两个引用
let user = { 
  name: "John" 
}; 
let admin = user

这里对象{name:"John"}就是通过被全局变量(根)user引用而获得了可达性,当然,因为admin复制了user对此对象的引用,所以admin也可以对其提供可达性。假如这个2个全局变量的值都被重写,那么此对象将失去可达性,因而被删除。

②多个引用
function marry(man, woman) { 
  woman.husband = man; 
  man.wife = woman; 
  //因为在语句部分已经提前添加key了,所以函数变量必须为对象
  return { 
  father: man, 
  mother: woman 
  } 
} 
let family = marry({ name: "John" }, { name: "Ann" });

图中{ name: "John" }通过marry函数和let获得了2个引用,只有将2个引用同时删去,使其完全独立才会导致对象被删除

③独立岛屿

在②的代码基础上定义family = null; 那么整个下方的引用链将因为失去和根的联系而被删除,成为一个独立岛屿

2.回收器的内部算法

(1)标记所有根

(2)标记所有根所引用的对象以及对象所引用的对象

(3)寻找未被标记的对象并将其删除

肆、方法&this

1.方法

(1)说明

作为对象key的value的函数称为方法

(2)声明

①常用的简写声明方法
let user = { 
  sayHi() { // 与 "sayHi: function()" 一样 
    alert("Hello"); 
  } 
};
user.sayHi();
②预先声明函数的声明方法
//创建变量和空对象并将两者连接
let user = {
// ...
};
// 声明函数
function sayHi() {
  alert("Hello!");
};
// 将其作为一个方法添加
user.sayHi = sayHi;
user.sayHi(); // Hello!

2.this

(1)作用

调用方法的对象(点之前的对象)

(2)语法结构

let user = { 
  name: "John", 
  age: 30, 
    sayHi() { 
    // "this" 指的是“当前的对象” 
      alert(this.name); 
    } 
}; 
user.sayHi(); // John 

(3)注意点

①特指当前对象:

理论上,alert(this.name);是可以用alert(user.name);来替换的,但是user所引用的对象不能被拷贝!如果拷贝将访问到错误的对象(而我们实际编写时的拷贝是很常见的,所以使用this为妙)

②不受限制:
A函数直接使用

可以直接在函数中使用,不拘泥于方法

B自动分配给不同对象

在预声明式的方法使用时,同一个函数的this可以自动分配给不同对象(2个创建的方法的对象不同,而this又是指向当前方法的对象的,因此有了自动分配的作用)

/*注意:这是一个预声明式方法声明,并非是常见的简写式,
所以当第一眼看到user.f = sayHi;的时候会有一点懵。。*/
let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() {
  alert(this.name);
}
// 在两个对象中使用相同的函数
  // 在两个对象中分别创建key为f的方法
  user.f = sayHi;
  admin.f = sayHi;
  // 这两个调用有不同的 this 值
  // 函数内部的 "this" 是“点符号前面”的那个对象
  user.f(); // John(this == user)
  admin.f(); // Admin(this == admin
C无对象调用

在非严格模式下,可以进行无对象调用函数,this == undefined

③箭头函数没有自己的"this"

在箭头函数中使用this实际上是借用了方法来进行指向的

(当我们并不想要一个独立的 this ,反而想从外部上下文中获取时, 它很有用)

没理解怎么个有用法,等之后再说吧。。

let user = {
        firstName: "Ilya",
        sayHi() {
            alert(this.firstName);
        }
    }
user.sayHi();//调用方法

let user = {
        firstName: "Ilya",
        sayHi() {
            let arrow = () => alert(this.firstName);
            arrow();
        }
}
user.sayHi();
④this只有在函数调用时才会有值,声明时没有
A函数调用举例
//方法内使用this
let usr = {
  name:"阿巴阿巴"
  findName () {
    alert(this.name);
  }
};
//方法外使用(需要在方法内部用return this)
let usr = {
  name:"阿巴阿巴"
  findName () {
    return this;
  }
};
alert(user.findName().name);
B常见错误举例
//下面这段代码有2个错误
function makeUser() { 
return { 
  name: "John", 
  ref: this //因为this只在函数调用的时候才有值,这里作为value是没有值的
  }; 
} 
let user = makeUser(); 
alert( user.ref.name );//没有用ref()

3.实用案例

(1)简单计算器

<!doctype html>
<script src="https://zh.js.cx/test/libs.js"></script>
<script src="test.js"></script>
<script>
    let calculator = {
        sum() {
            return (this.a + this.b);
        },
//sum方法,返回对象中a,b的value之和
        mul() {
            return (this.a * this.b);
        },
       //计算器改进.改进创建方法的方式:
       /*this.mul = function () {
           return (this.a * this.b);
         }*/
        minus() {
            return (this.a - this.b);
        },

        mod() {
            return (this.a % this.b);
        },
        
        divide() {
            return (this.a / this.b);
        },

        read() {
            this.a = +prompt('a?', 0);
            this.b = +prompt('b?', 0);
        },
    };
    calculator.read();
    let control = prompt("请输入你想要进行的操作(加/减/乘/除/模)","加");
    if (control==="加") {
        alert(calculator.sum());
    }
    else if (control==="减") {
        alert(calculator.minus());
    }
    else if (control==="乘") {
        alert(calculator.mul());
    }
    else if (control==="除") {
        alert(calculator.divide());
    } else {
        alert(calculator.mod());
    }
</script>

</html>

(2)链式调用

<!doctype html>
<script src="https://zh.js.cx/test/libs.js"></script>
<script src="test.js"></script>
<script>
//只有在方法内添加return this才能实现外部链式调用的效果
  let ladder = {
    step: 0,
    up: function() { 
      this.step++;
      return this;
    },
    down: function() { 
      this.step--;
      return this;
    },
    showStep: function() { 
      alert(this.step);
      return this;
    }
  };

</script>

</html>

伍、构造器和操作符"new"

1.用途

实现可重用的对象创建代码,而且简短,便于阅读

2.约定

①命名以大写字母开头

②只能由"new"来执行

3.语法规范

<script>
  function User(name) {
    this.name = name;
    this.isAdmin = false;
    //还可以给this添加方法!!!
  }
  let user = new User("Jack");
  alert(user.name); // Jack
  alert(user.isAdmin); // false

</script>

4.创建逻辑

①创建新的空对象并分配给this

②执行函数体(一般会给this所指向的对象添加新属性)

③返回this

<script>
  function User(name) {
    // this = {};(隐式创建)
    // 添加属性到 this
    this.name = name;
    this.isAdmin = false;
    // return this;(隐式返回)
  }
</script>

5.注意:

虽然构造器中没有return,但是实际上是隐藏执行了return this

可以通过额外设置return(返回对象)来进行对原来的对象进行覆盖 (整体覆盖,并非是只覆盖相同key的值!)

陆、可选链"?."

1.优势

可以安全的调用一个可能不存在的对象的key或方法

2.常见语法

(1)obj?.prop —— 如果 obj 存在则返回 obj.prop ,否则返回 undefined 。

(2)obj?.prop —— 如果 obj 存在则返回 obj.prop ,否则返回 undefined 。

(3)obj.method?.() —— 如果 obj.method 存在则调用 obj.method() ,否则返回 undefined 。

3.运算逻辑和注意点

(1)运算逻辑:先判断左边是否为null/undefined,如果是则进行访问或调用对象

(2)注意点:最好在左边不存在也没问题的情况下使用,避免隐藏错误

柒、Symbol

1.用途

确保对象属性使用唯一标识符,不会发生属性冲突的危险

2.特点

(1)具有唯一性

①即使有很多描述相同的symbol,但是它们对应的key一定是不同的

<script>
  let id1 = Symbol("id");
  let id2 = Symbol("id");
  alert(id1 == id2); // false
</script>

②即使定义的变量名相同,symbol依然不会被后面新添加的symbol顶掉(存疑)

(2)防转换性

①不支持隐性转换为字符串

let id = Symbol("id"); 
alert(id); // 类型错误:无法将 Symbol 值转换为字符串。

②显示symbol的方法

//显示symbol和括号内描述
let id = Symbol("id"); 
alert(id.toString()); // Symbol(id),现在它有效了
//只显示描述
let id = Symbol("id"); 
alert(id.description); // id 

(3)“隐藏”性

symbol作为key在对象之中不会被意外访问和遍历

(4)防遍历性

不会参与for...in循环,但是和字符串一样会被object.assign({},对象变量名)拷贝

3.语法

(1)创建(在对象中添加symbol)

<script>
  //先创建对象
  let user = { 
    name: "John"
  };
  //定义symbol的变量名
  let id = Symbol("id");
  //用[]将symbol和它的值添加到对象中
  user[id] = 1;
  ————————————————————————————————————
  //也可以通过[]在字面量法创建的对象之中添加
  let id2 = Symbol("id");
  let user2 = {
    name: "John",
    [id2]: 123 // 而不是 "id":123
  };
</script>

(2)访问

使用引用对象的变量名[定义symbol的变量名]来访问symbol

十三、数据类型

壹、原始类型的方法

贰、数字类型

1.巨大长整数表示

2.八进制&十六进制

3.编码转换

4.舍入

toFixed

①用途:舍入到第n位,并以字符串的形式返回,缺则补零
②语法:num.toFixed(n)/num.toFixde(n)-0(后者通过计算将其转换回数字)

5.Math对象

(1)random

①用途:返回0-1之间的随机数

②语法:Math.random()(添加参数无效,依旧是0-1之间的随机小数)

※应用:随机整数

①不包含有边界的随机整数
function getRnd1(min,max) {
    if (min > max) {
        console.error("参数有误")
        return
    }
    return Math.floor(Math.random()*(max-min))+min
}

console.log(getRnd1(200,300))
②包含有边界的随机整数
function getRnd2(min,max) {
    if (min > max) {
        console.error("参数有误")
        return
    }
    return Math.floor(Math.random()*(max-min+1))+min
}

console.log(getRnd2(200,300))

(2)round

①用途:四舍五入取整

②语法:Math.round()

(3)ceil/floor

①用途:向上取整/向下取整

②语法:Math.ceil/floor()

(4)abs

取绝对值,语法略

(5)sqrt

取平方根

(6)pow

①用途:得到数的次幂
②语法:Math.pow(底数,指数)

(7)max/min

(8)PI

叁、字符串

1.构造函数式字符串

(1)声明

let str = new String("hello");

(2)特点

类数组:一般情况下为字符串,可以进行字符串特有的操作,在特定情况下会转换为数组进行只读操作,最后重新转回字符串

(3)特点类数组操作

str1.length

只读性取得字符串的长度(空格和标点也算字符)

str1[index]

只读性通过索引访问字符串中的某个字符

③for循环遍历
for (var i = 0; i <str1.length; i++){
    console.log(i,str1[i]);
});

(4)应用:判断字符串中字符出现个数

let str =new String(prompt("请输入想要确定的字符串:","abcabcab"));
//能用对象展示的最好用对象展示,key+value应有尽有!
let obj = {};
for (let i = 0; i < str.length; i++) {
  //虽然字符串可以在特定情况下变成数组,但是最好把元素拿出来放到一个一次性变量中
  let key = str[i];
  //根据需求:字符串存在就统计个数,不存在就放入对象
  if (obj[key] === undefined) {
    obj[key] = 1
  }else {
    obj[key]++
  }
}
console.log(obj);

2.常用字符串方法

(1)charAt/charCodeAt

①用途:返回索引对应的字符/字符编码
②语法:str.charAt(index)/str.charCodeAt(index)
③字符编码与字符间转换

str.charCodeAt(index)——转换为字符编码

String.fromCharCode(数字)——编码转换为字符

可以和循环结合有规律的输出字符,最典型的应用就是输出26个字母

(2)toUpperCase/toLowerCase

①用途:大小写转换
②语法:str.toUpperCase()/str.toLowerUpperCase()

(3)substr/substring/slice

①用途:都是截取,只不过方式有所不同
②语法
let str = "lancelot";
//substr(开始的索引处,长度)
let str1 = str.substr(1,2);
//substring(开始索引处,结束索引处)——同样是取左不取右
let str1 = str.substring(1,2);
let str1 = str.substring(1);//一个参数就是从开始索引处到最后的字符
//slice和subtring唯一的不同就是支持负数

(4)应用:字符串首字母大写

let str = "lancelot";
console.log(str.subtring(0,1).toUpperCase()+str.substring(1));
//由此可以联想,截取+大写+拼接,可以在任意处实现字母大写

(5)replace

①用途:从前往后替换,第一个替换完成后后面一样的不会替换(全部替换需要用正则表达式)
②语法:str.replace("需要替换的字符","替换的结果")

(6)split

①用途:分隔字符串并将其放入一个数组中
②语法
let str = "abcd";
console.log(str.split(""))

(7)indexOf/lastIndexOf

①用途

用来从前往后/从后往前查找数组中的元素

②语法

str.indexOf/lastIndexOf(要查询的元素,开始的索引的位置)

返回-1不在里面,返回索引值则是在里面的

(8)trim/trimStart/trimEnd

①用途:去掉空格
②语法:str.trim()/trimStart()

(9)应用:模糊查询

let arr = ["aaa","abc","bcc","bcd","ddd","ccd"];

let input = prompt("请输入您需要查询的内容");
let res = arr.filter(function(item){
    //模糊查询的功能分为:模糊查询和返回查询值
    //模糊意味着我们需要查询数组中是否有符合条件的字符串,
    //item是每个字符串,".indexOf()"是对字符串进行查询
    return item.indexOf(input) > -1;
});

console.log(res);

3.json格式字符串

(1)用途:json作为前后端交换对象信息的桥梁

(2)语法

let str = '{"name":"kerwin","age":100}';
let obj = JSON.parse(str);
console.log(obj);

4.模版字符串(反引号)

支持换行和添加变量

肆、数组

1.理解

数组是有序存储数据的集合(当然也是最重要的是可以存储对象)

2.声明

(1)内置构造函数创建法

<script>
  //创建一个空数组
  var arr1 = new Array()
  //创建一个含有10个空的数组
  var arr2 = new Array(10)
  //创建一个有初始内容的数组
  var arr3 = new Array(1,2,3)
</script>

(2)字面量创建法

<script>
//创建一个空数组
var arr1 = []
//创建一个含有初始内容的数组
var arr2 = [1,2,3]

3.索引

元素从0开始编号,[num]代表了数组中的某一元素

4.索引操作

(1)获取元素

<script>
  let fruits = ["Apple", "Orange", "Plum"];
  alert(fruits[0]); // Apple
  alert(fruits[1]); // Orange
  alert(fruits[2]); // Plum
</script>

(2)替换元素

<script>
fruits[2] = 'Pear'; // 现在变成了 ["Apple", "Orange", "Pear"] 

(3)添加元素

语法和替换元素一样,但是索引数必须>=数组当前元素个数。会起到在末尾添加新元素的效果,>当前个数会进行补空(中间显示,empty,)

5.length

(1)可以用来确定数组内元素的个数(几个就输出几个,和索引不同)

(2)可以用arr.length = 0来清空数组

6.两大应用(双端队列)

(1)队列

①push 在末端添加一个元素

②shift 从首端取出一个元素,使整个队列前移

③先进先出

④常见应用:在屏幕上显示消息队列

(2)栈

①push

②pop 从末端取出一个元素,队列不变

③后进先出

7.作用于首末端的操作(已经转至数组方法中)

(1)末端(速度更快)

①pop 取出并返回数组的最后一个元素

②push 在数组末端添加元素(支持多个)

(2)首端(因为操作后要对所有元素再次进行索引,所以更慢)

①shift 取出并返回数组的第一个元素

②unshift 在数组的首端添加元素(支持多个)

8.遍历

(1)数组有自己的专属遍历函数for...of

(2)因为数组的本质是一种对象,所以同样可以用for...in

(速度更慢,而且可能会列出类数组)

(3)也可以使用for循环遍历

let arr = ['a','b','c']
//创建一个新数组
let arrOther = [];
//for循环遍历
for (let i = 0; i < arr.length; i++){
  arrOther[i] = arr[i];
}

9.字符串转换

(1)string

let arr = [1, 2, 3]; 
alert( arr ); // 1,2,3 
alert( String(arr) === '1,2,3' ); // true 

(2)索引+数字

 //[]会被视作"",总之很神奇
 alert([] + 1); // "1"
 alert([1] + 1); // "11"
 alert([1, 2] + 1); // "1,21"

11.简单排序法

(1)冒泡排序法(解决简单问题的流程事例)

①观察效果

A数组从首到末依次进行两个两个的比较

B将大的数移到后面去

②从特殊情况入手推导核心代码

A特殊情况:假设数组中只有[4,3]

B用if进行判断,大小作为判断条件

C交换顺序需要再定义一个容器,然后依次交换数据达到目的

(经过最简单的情况已经推导出了核心代码:分支加交换)

if (arr[0] > arr[1]) {  //判断第一个元素和第二个元素哪个大
 //交换大小元素,把大的沉下去
  var tmp = arr [0];
  arr[0] = arr [1];
  arr[1] = tmp;
  
}
③在从特殊到一般的途中寻找规律

A增强情况复杂度:数组中有3个元素[4,3,2]的情况

B发现问题并尝试解决:a问题:发现4沉到最后面去了,但是3和2还是没有交换

a分析:3个元素经历1次交换并不能解决问题,需要经历2次才能彻底交换成功——让我们联想到了数组的遍历

a方案:遍历数组并把大的沉到后面去似乎就可以解决我们的问题了,于是我们推导出了下面的升级版代码

for (var i = 0; i < arr.length; i++) {
  if (arr[i] > arr[i+1]) {
    var tmp = arr [i];
    arr[i] = arr [1+1];
    arr[i+1] = tmp;
  }
}

b问题:在分析a问题的过程中我们又发现了一个问题,第1次遍历全部数组没有问题,但是第2次,在第一次遍历把最大的数沉到底部后,我们不需要再次进行全部遍历了!我们完全可以少1次循环的次数来优化我们的代码。

b分析:这是特殊情况,一般情况下会少几次循环次数呢?经过从3个元素到4个元素再到5个元素的测试,可以推出是length-1-0,length-1-1,length-1-2...

b方案:我们完全可以在遍历循环的外面再套一个循环用来确定遍历的轮数和每次遍历的循环数,于是推导出了最终的代码

for (var z = 0; z < arr.length - 1;z++) {
  for (var i = 0; i < arr.length - 1 - z; i++) {
    if (arr[i] > arr[i + 1]) {
      var tmp = arr [i];
      arr[i] = arr [1 + 1];
      arr[i + 1] = tmp;
    }
  }
}

(2)选择排序法

介绍:假设第一个元素为最小元素,然后开始第一轮遍历找出最小的数并放到第一位,继续遍历找最小的数放到下一位,最终实现正序排列的效果

for (let j = 0; j < arr.length - 1; j++) {
  var minIndex = j;
  
  for (let i = j + 1; i < arr.length; i++) {
    if (arr[i] < arr[minIndex]) {
      minIndex = i;
    }
  }
  
  if (minIndex !== j) {
    var tmp = arr[minIndex];
    arr[minIndex] = arr[j];
    arr[j] = tmp
  }
}

伍、数组方法

1.增添/移除

(1)末端 (速度更快)

①pop 取出并返回数组的最后一个元素

②push 在数组末端添加元素(支持多个)

(2)首端(因为操作后要对所有元素再次进行索引,所以更慢)

①shift 取出并返回数组的第一个元素

②unshift 在数组的首端添加元素(支持多个)

2.任意位置的增添/删除

(1)用处

很万能,在任意位置截取数组内容并替换

(2)语法

splice(开始的索引位置,截取数量,替换的新元素)

(3)举例

var arr = [1, 2, 3, 4, 5]
//删除
arr.splice(1,2)
//增添
arr.splice(1,0,'aaa')//[1, 'aaa', 2, 3, 4, 5]

3.倒序

arr.reverse()

4.sort(简单使用)

(1)arr.sort() 是元素根据一定原则进行排序(比如31和13,先比第一位13的1小,所以排前面)

(2)简单使用回调函数实现正序/倒序

arr,sort(function(a,b) {
  return a-b //正序,b-a倒序
})

5.concat(不改变原数组)

(1)用途

拼接数组

(2)语法

let arrNew = arr1.concat(arr2,arr3,...,arrN)

①顺序:根据[arr1,arr2,...,arrN]的顺序进行拼接

②括号内的数组可以根据需要改为[]或数字

(3)举例

let arr1 = [1,2,3]
let arr2 = [4,5,6]
//拼接2个定义过的数组
let arr3 = arr1.concat(arr2);
//拼接1个定义过的数组和1个数
let arr4 = arr1.concat(1);
//拼接1个定义过的数组和1个未定义过的数组
let arr5 = arr1.concat([7,8,9]);

6.join(不改变原数组)

(1)用途:把数组更改为字符串,并且可以更改间隔符

(2)语法:arr.join("间隔符类型")

7.slice(不改变原数组)

(1)用途

在不改变原数组的情况下截取出部分或全部元素

(2)语法

let arrNew = arr.slice(开始索引处,结束索引处)

(3)举例

let arr = ['aaa','sss','ddd','fff','ggg']
//2个行参,去左不取右
let arr1 = arr.slice(0,2);//['ddd','fff','ggg']
//1个行参(默认从0开始,参数为结束的索引处)
let arr2 = arr.slice(2);//['ddd','fff','ggg']
//无行参,直接拷贝原数组,能拷贝对象但是不能拷贝方法!
let arr3 = arr.slice();
//行参可以为负,虽然很傻但是没问题,-1可以理解为数组的最后1个

8.indexOf/lastIndexOf

(1)用途

用来从前往后/从后往前查找数组中元素的索引

(2)语法

arr.indexOf/lastIndexOf(要查询的元素)

返回-1不在里面,0则是在里面的

9.应用:数组去重

(1)方法1

let arr = [1,2,3,4,3,5,6,2,1];

let arr2 = [];

for (let i = 0; i < arr.length; i++) {
    if (arr2.indexOf(arr[i]) === -1){
        arr2.push(arr[i]);
    }
}

(2)方法2

let obj = {};

for (let i = 0, i < arr.length; i++) {
    obj[arr[i]] = "随便";
}

let arr2 = [];
for (let i in obj) {
    arr2.push(i-0);
}
console.log(arr2);

(3)方法3

let set1 = new Set(arr);
let arr1 = Array.from(set1);
console.log(arr2);

10.forEach

(1)用途

起到遍历的效果

(2)语法(使用回调函数——不需要调用,直接执行的函数)

arr.forEach(function(item,index){
    console.log(item,index,arry);
    //这里以控制台输出来举例
    //第一个是元素,第二个是索引值,第三个是原数组(参数不要删去即可)
})

11.map(不改变原数组)

(1)用途

把原数组的元素映射到新数组中

(2)语法

//map也能起到遍历的效果,但是因为有return所以更加灵活
let arrNew = arr.map(function(item,index){
    return item
})
//甚至可以完成一些前后端交接的操作
let arrNew = arr.map(function(item,index){
    return "<li>"+item+"</li>"
});
document.write(arr2.join(""));
//这样就可以成功把后端给的数据放入页面的列表中了!!

12.filter

(1)用途:过滤出符合条件的元素并放入新数组中

(2)语法

//一般都是对对象进行过滤,所以以对象为例
var arr2 = arr.filter(function(item){
    return item.对象中需要过滤的key>200
    //这样会将所有符合条件的对象筛选出来
})

13.every/some

(1)用途

每一个/某一个满足条件即返回真,否则为假

(2)语法

var arr2 = arr.every(function(item){
    return item>=90
});
——————————————————————————————————————
var arr2 = arr.some(function(item){
    return item>=90
});

14.find

(1)用途:查找并返回符合条件的元素

(2)语法

var arr2 = arr.find(function(item){
    return item.对象中需要过滤的key===200
})
//只能找到第一项,不是很好用

15.reduce

(1)用途:叠加数组中的元素

(2)语法

var arr2 = arr.reduce(function(prev,item){
    return prev+item
    //prev的值为上一次的return值,相当于:f(n)=f(n-1)+item
    //加减乘除都可,根据需要自行替换
},初始值)
//可以把初始值设置为""来输出拼接在一起的字符串

16.伪数组转换

<script>
    let items = document.getElementsByClassName("newsiem")
    let newitems = Array.from(item)

陆、可迭代对象(lte法rable object)

柒、映射和集合(map and set)

八、弱映射和弱集合(weekmap and weekset)

玖、

十、解构赋值

十壹、时间和日期

1.声明

①语法

let date = new Date() (会自动转为字符串)

②参数

A.无参数:返回当前时间
B.数字形式参数 (2019,00,27,16,02,00)

返回设定的年,月,日,时,分,秒

C.字符串形式参数('2019-02-03 13:13:13')

返回设定的年,月,日,时,分,秒 (形式必须和额定格式完全相同,保留空格)

2.常识

①中国为东八区,时差为8小时

②格林威治时间:1970年01月01日00时00分00秒

3.常见方法

①get

(获得时间中的年、月、日、时、分、秒和星期)

A.time.getFullYear() 获得年

B.time.getMonth() 获得月

C.time.getDate() 获得日

D.time.getHours() 获得小时

E.time.getMinutes() 获得分钟

F.time.getSeconds() 获得秒

   G.time.getDay() 获得星期(周日为0,周一周六:16)

H.time.getTime() 当前时间到格林威治时间差距的 毫秒 数(时间戳)

②set

设置方法和获得方法几乎相同,除了星期不能单独设置,因为当年月日确定时星期就已经自动计算出来了

设置全部时间可以用时间戳设置,可以设置在格林威治后的所有时间

date.setTime

4.定时器

①倒计时定时器(等待一段时间后执行1次)

A.语法
let timerId = setTimeout(function () {
    console.log('我执行了')
},1000)//设置的时间的单位都是毫秒
console.log(timerId) //1
B.返回值:第几个定时器就返回几,顺序从上到下算

②间隔定时器 (每隔一段时间执行1次,重复执行)

A.语法
let timerId = setInterval(function () {
    console.log('我执行了')
},1000)//设置的时间的单位都是毫秒
console.log(timerId) //1
B.返回值:第几个定时器就返回几,顺序从上到下算(无论是什么类型)

③定时器的关闭

clearTimeout(定时器的变量名)/clearInterval(定时器的变量名)

※按钮功能绑定

<body>
    <button id="btn">关闭</button>
    <script>
        btn.onclick = function() {
            console.log(btn);//通过id拿到用户对象
            clearTimeout(time1);
        }
    </script>
</body>

※倒计时

<body>
    <div id="box"></div>
    <script>
        let targetDate = new Date('2023-11-4');
        
        function diffTime (current,target) {
            //转换差值为日期
            let sub = Math.ceil((target - target)/1000);
            let day = parseInt(sub/(60*60*24));
            let hours = parseInt(sub%(60*60*24)/(60*60));
            let minutes = parseInt(sub%(60*60)/60);
            let seconds = sub%60;
            //建立对象便于展示
            let obj = {
                day:day,
                hours:hours,
                minutes:minutes,
                seconds:seconds,
            };
            return obj
        }
        //间隔定时器每1秒更改一次值
        setInterval (function() {
            let currentDate = new Date();
            let res = diffTime(currentDate,targetDate);
            //不停填充盒子内文本
            box.innerHTML = `距离实验室考试还有
                            %{res.day}天%{res.hours}小时
                            %{res.minutes}分钟%{res.seconds}秒`
        },1000)
    </script>
</body>

十贰、JSON方法,toJSON

十四、

十五、

十六、

十七、

十八、

十九、

二十、

二十一、

二十二、

二十三、BOM(基于窗口)

1.宽高检测

(1)用途

获取屏幕的宽高(不包括滚动条和f12界面)

(2)语法

直接输入innerWidth/innerHeight即可

2.弹出层

(1)alert

①作用

跳出一个弹窗,等待用户点击确认

模态窗——窗口会被强制置顶,在用户进行相关操作之后解除

②使用方法

alert("你好啊qwq");

(2)prompt

①作用

跳出一个弹窗,可以接收 1 个来自用户的数据(含有 1 个 input 并可以指定默认参数

②使用方法

result = prompt(title, [default]);
//title——展示给用户的文本
//default——可选参数,指定默认参数
 //Number直接输入即可,字符串需要添加引号
 //会将结果自动转换成字符串,所以之后判断的时候注意类型是否匹配

③注意

prompt 可以用来赋值,这样就可以起到类似于 C 和 Python 中输入函数的效果;如果用户选择了“取消”,那么会返回 null

(3)confirm

①作用

跳出一个弹窗,获得用户在是或否中的选择

②使用方法

和 aler 类似

简单交互函数十分便捷,同样有很大的弊端:无法更改确切位置,也无法更改外观

3.地址信息

(1)location

一个包含很多键值对的大对象

(2)location.href

①说明

储存浏览器地址栏内url地址的信息

②使用

A.获取地址console.log(location.href);

(中文会变成 url 编码格式不要惊讶)

B.跳转页面location.href = "www.baidu.com "

(3)location.reload

强制刷新页面,不要写在全局中,防止浏览器一直处于刷新状态

4.常见事件

(1)onload事件

在页面所有资源加载完后执行

onload = function () {
    console.log();
}

(2)onresize事件

①说明:每次更改屏幕大小都会触发事件

是媒体查询的备选方案

②语法

window.onresize = function () {
    console.log();
}

(3)onscroll事件

①触发条件

有滚动条(内容高度/宽度>视窗)

②滚动的理解:

浏览器(窗口)是没有滚动的,是网页内容像窗帘一样被滚动条一圈一圈地卷上去了

③scrollTop/scrollLeft

A. 用来获取页面向上滚动的距离
B. 获取方式
//第一种
document.body.scrollTop//没声明的Chrome、FireFox
//第二种
document.documentElement.scrollTop//无DOCTYPE声明的IE、有声明的Chrome、FireFox
//特立独行的Safari
widow.pageYOffset
//用一个||更保险
document.documentElement.scrollTop || document.body.scrollTop
C.返回值:滚出屏幕的距离D
D.搭载盒子的返回顶部功能
<body>
    <aiv id="main"></div>
    <button>返回顶部</button>
    <script>
        console.log(main);
        if ((document.documentElement.scrollTop ||
              document.body.scrollTop) > 100) {
            console.log("显示返回顶部");
        }else {
            console.log("隐藏返回顶部");
        }
        
        main.onclick = function() {
            window.scrollTo({
                left:0,
                top:0,
            });
        }
    </script>
</dody>

5.标签页

(1)open("路径"); 打开路径所指向的新标签页

(2)close("路径"); 关闭原始的标签页

6.历史记录

(1)返回上个页面 histroy.back (在跳转过一次以上后才可以使用)

histroy.go(负数)(返回到第几个页面)

(2)跳转至下个页面histroy.forward

histroy.go(正数)(跳转到第几个页面)

7.本地存储

(1)增得删清

①增添:localStorage.setItem("name","Lancelot")

②获取:localStorage.getItem("name")

③删除:localStorage.removeItem("name")

④清空:localStorage.clear()

(2)对增的理解

增添是在本地(local/session storage)中储存数据,不同网址的本地数据是孤立的(不能相互查询),在计算机中存储的具体数据是加密的,很难找到。

(3)用JSON增添对象

正常增添的value是不能为对象的,会显示[object,Object],可以用JSON来转换

//JSON增添对象
localStorage.setItem("obj",JSON.stringify({
    name:"Lancelot",
    age:18,
}))
//JSON增添对象
JSONparse(lacalStorage.getItem("obj"))

(4)lacal与session的区别

前者是永久的本地存储,后者是暂时的本地存储(关闭页面后就消失),当然两者的语法一致

(5)案例:记住用户名

①流程分析

A.搭建外部样式——两个输入框和一个按钮

B.编写“记住用户名”功能——点击登录按钮后获取用户名,并存储在本地中,在刷新之后获取用户名

②代码

<!--外部样式-->
<div>
    用户名:
    <input type="text" id="username">
</div>
<div>
    密码:
    <input type="password" id="password">
</div>
<div>
    <button id="login">登录</button>
</div>

<script>
//先从本地中获取用户名并赋值到变量中
let uservalue = localStorage.getItem("username");
let paasvalue = localStorage.getItem("password");

//把存储的用户名通过变量赋值给输入框的value
username.value = uservalue;
password.value = passvalue;//如果本地没有存储,赋null也没有事

//点击登录按钮后获取用户名,并存储在本地中
login.onclick = function () {
    localStorage.setItem("username",username.value);
    localStorage.setItem("password",password.value);
}
</script>

二十四、DOM(基于文档)

1.获取元素的方式

(1)获取html,body,head

document.documentElement/head/body

(2)id/class获取法

①id获取法

A.语法document.getElementsById()

B.说明:通过标签的id选择器来获取其标签

②class获取法

A.语法document.getElementsByClassName()

B.说明:通过标签的class选择器来获取其标签

C.特点:a.因为可能有多个相同class名的元素,所以class获取法能够获得一组元素,由于和数组长得一样,被称为“伪数组”

b.即使获取的class只有一个,同样也是获取了一组元素,只是其中只有一个DOM元素而已

c.同样可以用索引来准确获得伪数组中的标签

(3)标签名获取法

①语法:document.getElementsByTagName()

②说明:和class获取法类似,获取的也是伪数组

(4)名获取法(适用于表单,不常用)

<input name="username" type="text"></input>
<script>
let item = document.getElementsByname("username")

(5)选择器获取法(兼容性较弱)

querySelector/querySelectorAll

A.说明

按照写css时的选择器格式来获取元素

前者只能获取第一个满足条件的元素,后者获取全部

B.语法
<script>
    let aaa = document.querySelector('div');
    let bbb = document.querySelector('div div');
    //只要css支持的选择器都可以用

2.操作元素的属性

(1)innerHTML

①用途

获取或者设置元素内部的HTML结构

②语法

<input type="password" id="mima">
<script>
//第一种:先获取元素标签并赋值给变量,再通过变量进行操作
let div = document.querySelector('div');
console.log(div.innerHTML)
//第二种:直接通过属性来进行操作,很离谱,好像只对id起效
console.log(mima.innerHTML)
//这种方法对于checked会有奇妙的化学反应,详见应用
——————————————————————————————————————————————————
//在div标签内嵌套元素
div.innerHTML = '<P>hello<p>';
//给标签的属性赋值
username.type = "text"; //赋值好像只能用变量操作
rember.checked = false
photo.src = "图片路径"

(2)innerText

①用途

获取或设置标签内的文本内容

②语法

<script>
//获取
let div = document.querySelector('div')
console.log(div,innerText)
//设置
div.innerText = '<p>hello</P>'
//只会把标签当成文本,不会做解析,更加安全

③表单value

<script>
//获取
console.log(username.value)
//设置
username.value = "22222"

(3)get/set/removeAttribute

①用途

获取/设置/移除元素(包括自定义元素)的属性

②语法

<div a="100" class="box">阿巴阿巴</div>
<script>
   let littlebox = document.querySelector('div')
   //获取
   console.log(littlebox.getAttribute('a')) // 100
   //设置
   littlebox.setAttribute('id','magicalSetting')
   //移除
   littlebox.removeAttribute('id','magicalSetting')

③多个重复元素添加属性(使用"循环和伪数组性质")

<ul>
    <li>aaa</li>
    <li>bbb</li>
    <li>ccc</li>
    <li>ddd</li>
</ul>
<script>
    let oitems = document.getElementsByTagName('li');
    for (let i = 0; i < oitems.length; i++) { //.length就是用了伪数组的性质
        oitems[i].setAttribute("index",i);    // oitems[i]通过索引指向伪数组中的元素
    }

(4)dataset

①查询

id名.dataset

(只有形式为data-变量名才可以被查询,返回结果是一个对象)

②添加

id名.dataset.想要创建的变量名 = 变量值

(自动创建为data-变量名的形式)

③删除

delete id名.dataset.变量名

(5)应用:密码可视

<body>
    <input name="password" type="password" id="miMa">
    <button id="view">密码可见</button>
<script>
    let passInput = document.getElementById("miMa");
    let eyeBtn = document.querySelector("#view");
    eyeBtn.onclick = function () {
        if (passInput.type === "password") {
            passInput.type = "text";
        }else {
            passInput.type = "password";
        }
    }
</script>
</body>

(6)应用:购物车全选功能

暂时无法在飞书文档外展示此内容

(7)应用:渲染页面

暂时无法在飞书文档外展示此内容

3.操作元素的样式

(1)行内样式(支持读写)

<div style="width: 100px;color: black:">1111</div>
<script>
    let div =document.querySelector('div');
    //获取
    console.log(div.style.某个样式)
    //对于text-align之类的有-的样式的获取
    console.log(div.style["text-align"]);
    console.log(div.style.textAlign)
    //设置
    div.style.height = "200px";

(2)通用方法(只读)

<script>
    let obox = document.getElementById('box');
    let res = getComputedStyle(obox).height
    console.log(res)

4.操作元素的类名

(1)className

<script>
  //获取
  console.log(box.className)
  box.className = "item item2"//能直接全部覆盖

(2)classList

<script>
  //获取
  console.log(box.classList)//获得一个数组,但是无法根据数组性质进行操作
  //添加
  box.classList.add("item2")//有自动去重,数组内没有重复的数据
  //删除
  box.classList.remove("item2")
  //切换(有则删除,无则添加)

(3)应用:选择高亮选项卡(简易半成品)

暂时无法在飞书文档外展示此内容

(4)应用:靠谱の选项卡

暂时无法在飞书文档外展示此内容

5.DOM节点

(1)类型

①根节点(document)

②元素节点(各种标签)

③文本节点 (包括空格和回车!)

④属性节点(各种标签的属性)

⑤注释节点

(2)简单判断

//请问div有几个子节点?——5个
 <div>
     kerwin
     <p>111111</P>
     <!--我是注释-->
 </div>
//1.  2~3行的回车+空格+3行的kerwin——文本节点
//2.  p标签——元素节点
//3.  3~4行的回车+空格——文本节点
//4.  注释节点
//5.  5~6行的回车+空格——文本节点

(3)获取节点

元素变量名.childNodes所有子一级节点(获得一个伪数组)

    <div>
        <p>hello</p>
    </div>
    <script>
        let oDiv = document.querySelector('div')
        console.log(oDiv.childNodes)
    /* 
        NodeList(3) [text, p, text]
      0: text
      1: p
      2: text
      length: 3
      _proto_: NodeList
    */

元素变量名.children所有子一级的元素节点(获得一个伪数组)

上面那个案例中,使用console.log(oDiv,children)会获得一个只包含p标签的伪数组

元素变量名.firstchild下子一级的第一个节点(非伪数组)

元素变量名.lastchild下子一级的最后一个节点(非伪数组)

元素变量名.firstElementchild下子一级的第一个元素节点(非伪数组)

元素变量名.lastElementchild下子一级的最后一个元素节点(非伪数组)

元素变量名.nextSibling下一个兄弟节点(非伪数组)

元素变量名.previousSibling上一个兄弟节点(非伪数组)

元素变量名.nextElementSibling下一个兄弟元素节点(非伪数组)

元素变量名.previousElementSibling上一个兄弟元素节点(非伪数组)

元素变量名.parentNode节点的父节点(非伪数组)(也有parentElement)

元素变量名.attributes 节点的所有属性节点(获得一个伪数组)

(4)节点操作

①添加节点

A.创建节点creatElement('标签')/createTextNode('文本')
<script>
    let oDiv =document.creatElement('div');
    console.log(oDiv);//<div></div>
    //可以结合innerHTML向标签中嵌套文本或标签
    oDiv.innerHTML ='你好呀!我是可达鸭~'//还可以使用createTextNode来创建一个文本节点
    let oText = document.createTextNode('我是一个文本');
B.插入节点appendchild(插入节点的变量名)/insertBefore(插入的标签,在哪个标签前插入)
<script>
//appendchild 元素节点末尾插入一个节点(需要有相应的父节点)
let oDiv =document.creatElement('div');//创建父节点
let oText = document.creatTextNode('我是一个文本捏~')
//插入
oDiv.appendchild(oText)
——————————————————————————————————————————————————————
//insertBefore 在某个节点前插入一个节点
let oP = document.createElement('span')
oDiv.insertBefore(oSpan,oP)//在p标签前插入span标签

②删改节点

A.删除节点removeChild(删除的节点)
<div>
    <p>我是一个p标签</p>
</div>
<script>
   let oDiv = document.querySelector('div')
   let oP = oDiv.querySelector('p')
   //移除某一节点下的某个个节点
   oDiv.removeChild(oP)
B.替换节点replaceChild(新节点,旧节点)
<div>
    <p>我是一个p标签</p>
</div>
<script>
   let oDiv = document.querySelector('div')
   let oP = oDiv.querySelector('p')
   //创建一个span节点
   let oSpan =document.createElement('span')
   //向span中添加文字
   oSpan.innerHTML = '阿巴阿巴,你好玛卡巴卡'
   //替换
   oDiv.replaceChild(oSpan,oP)

③克隆节点克隆节点.cloneNode()

<script>
   let oDiv = document.querySelector('div')
   let oClone = oDiv.cloneMode(true) //不写参数默认不克隆后代
   console.log(oCloneBox)
   document.body.appendChild(oClone)//插入到body后

(5)应用:动态删除

<body>
    <!-- <button>aaa</button> -->
    <ul id="list">

    </ul>
    <script>
        var arr = ["111","2222","333"]

        for(var i=0;i<arr.length;i++){
            var obutton = document.createElement("button")//创建按钮,不用map便于后面绑点击事件
            obutton.innerHTML = "delete"
            obutton.onclick = handler
            
            var oli = document.createElement("li")//创建3个li标签
            list.appendChild(oli)                 //把li塞到ul里面去
            oli.innerHTML = arr[i]                //往li里面塞arr数组中的数据
            oli.appendChild(obutton)              //往li里面塞button
        }

        function handler(){
            console.log(this.parentNode)           
            this.parentNode.remove()
        }
    </script>
</body>

(6)节点属性

①最常用的就是通过节点.nodeType===1/2/3来判断是那种类型的节点

②也可以通过nodeValue来获得属性节点的属性值

6.获取尺寸

(1)获取元素的尺寸

①offsetWith/offsetHeight

获取含有内边距和框的宽高

<div id="box">
</div>
<script>
    console.log(box.offsetWidth,box.offsetHeight)
//获取的是没有单位的数字——CSS文件中截去单位的数字部分
//无法拿到display:none
//可以拿到visibility:hidden

②clientWidth/clientHeight

用法和offset相同,但是不含border的宽高

(2)获取浏览器窗口的尺寸

①innerWidth/innerHeight(含滚动条)

②document.documentElement.clientWidth/clientHeight(不含滚动条)

7.获取元素偏移量

(1)offsetLeft/offsetTop

分别获取相对于定位父级的左偏移量和上偏移量

(2)clientLeft/clientTop

分别获取左边框/上边框的宽度

8.应用:懒加载

暂时无法在飞书文档外展示此内容

9.事件

(1)事件的绑定方式

①含on的绑定方式

元素.onXXX = function () {}

<script>
    //绑定:以onclick为例
    oDiv.onclick = function () {
        console.log('我是第一个事件')
    }
    //特点:同一个事件源和类型,后一个事件处理函数会覆盖前面那个

②事件监听的绑定方式

元素.addEventListener('事件类型',function () {},冒泡还是捕获)

<script>
    oDiv.addEventListener('click',function () {
        console.log('我是第一个事件');
    },false)
    //多个事件处理函数,会在事件触发时按顺序执行

(2)事件的解绑方式

①dom0解绑

dom节点.onXXX = null (利用dom0后面的会覆盖前面的特性把前面的事件赋为空进行解绑)

①dom2解绑

dom节点.removeEventListener("之前的事件",函数名) (必须在外面封装一个函数,不能内置一模一样的函数,后者无效)

<script>
    function handler () {
        this.removeEventListener("click",handler)
    }
    btn.addEventListener("click",handler)
</script>

(3)常见的事件类型

①浏览器事件

见BOM

②鼠标事件

A.click/dblclick/contextmenu ——点击/双击/右键单击

B.mousedown/mouseup ——左键按下/左键抬起 (down不支持按下在外面松开可以取消)

C.mouseover/mouseout ——鼠标进入/鼠标移出(支持子元素,冒泡)

D.mouseenter/mouseleave ——鼠标移入/鼠标移出 (不支持子元素,不冒泡)

E.mousemove —— 鼠标移动

③键盘事件

A.keydown/ketup ——键盘按下/按键抬起

B.keypress ——键盘按键按下后再次抬起

④表单事件

A.focus/blur ——失去焦点/获取焦点 (用于验证用户名是否被注册)

B.change ——内容改变(得焦和失焦点后有无差别)

C.input ——内容不一样就触发

D.submit/reset —— 提交/重置 (必须有form结构,里面有input的type是submit或reset,而且是只能对form用)

⑤触摸事件

A.touchstart/touchend ——触摸开始/触摸结束

B.touchmove ——触摸移动

(4)事件对象

①解释

就是在事件函数中的一个行参,如果进行某个事件,有这个行参就会给你返回相应的对象

②举例

<script>
    oDiv.onclick = function(event) {
        console.log(event)//会返回一个鼠标点击的对象,里面有各种相关的信息
    }
</script>

③鼠标点击事件的对象

A.offsetX/offsetY ——点击位置位于此盒子的左边距和上边距

B.clientX/clientY ——点击位置位于视窗的左边距和上边距

C.pageX/pageY ——点击位置位于文档的左边距和上边距

(5)案例:鼠标跟随

<body>
    <div id="box">
        头像
        <p>
        介绍
        </p>
    </div>

    <div>111111</div>
    <script>

        box.onmouseover = function () {
            this.firstElementChild.style.display = "block"
        }
        box.onmouseout = function () {
            this.firstElementChild.style.display = "none"
        }

        box.onmousemove = function (evt) {
            // console.log(evt.offsetX,evt.offsetY)

            this.firstElementChild.style.left = evt.offsetX + "px"
            this.firstElementChild.style.top = evt.offsetY + "px"
        }
    </script>
</body>

(6)案例:鼠标拖拽

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

    <style>
        * {
            margin: 0;
            padding: 0;
        }

        div {
            width: 100px;
            height: 100px;
            background: skyblue;
            position: absolute;
        }
    </style>
</head>

<body>

    <div id="box">

    </div>
    <script>
        box.onmousedown = function () {
            console.log("down")

            document.onmousemove = function (evt) {
                // console.log("move")
                var x = evt.clientX - box.offsetWidth/2
                var y = evt.clientY - box.offsetHeight/2

                if(y<=0) y=0
                if(x<=0) x=0

                if(x>= document.documentElement.clientWidth-box.offsetWidth)
                x = document.documentElement.clientWidth-box.offsetWidth

                if(y>= document.documentElement.clientHeight-box.offsetHeight)
                y = document.documentElement.clientHeight-box.offsetHeight

                box.style.left = x + "px"
                box.style.top = y + "px"
            }
        }
        box.onmouseup = function () {
            console.log("up")

            document.onmousemove = null
        }
    </script>
</body>

(7)DOM事件流

①标准事件流:先捕获,再目标,最后冒泡

window→document→body→outer→inner→outer→body→document→window

②说明:流程如此,但是触发只会触发冒泡,而且必须设置相同的事件才会触发

③捕获:只有dom2才能用true开启捕获触发

 <script>
     oDiv.addElementListener("click",function(){
         console.log("aaa")
     },ture)
 </script>

(8)阻止事件传播(阻止冒泡)

使用一个事件对象evt.stopPropagation()即可

<script>
    oLi.onclick = function (){
        console.log("aaa")
    }
    oBtn.onclick = handler
    function handler (evt) {
        console.log('bbb')
        evt.stopPropagation()s
    }
</script>

(9)阻止默认行为

①DOM0的阻止方法

在回调函数的末尾添加一行return false即可

②DOM2的阻止方法

<script>
    cocument.addEventListener('contextmenu',functon(evt) {
        console.log('阿巴阿巴')
        evt.preventDefault()
    })

(10)案例:右键菜单

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{
            margin:0;
            padding:0;
        }
        ul{
            list-style: none;
            width: 200px;
            padding:10px;
            border:1px solid black;
            display: none;
            position: absolute;
        }

        ul li:hover{
            background:skyblue;
        }
    </style>
</head>
<body>
    <ul id="list">
        <li class="aaa">1111</li>
        <li class="bbb">2222</li>
        <li class="ccc">3333</li>
    </ul>
    <script>
        document.addEventListener("contextmenu",function(evt){
            evt.preventDefault()
            list.style.display = "block"
            var x = evt.clientX
            var y = evt.clientY
            if(x >= document.documentElement.clientWidth-list.offsetWidth)
            x = document.documentElement.clientWidth-list.offsetWidth

            if(y >= document.documentElement.clientHeight-list.offsetHeight)
            y = document.documentElement.clientHeight-list.offsetHeight
            list.style.left = x + "px"
            list.style.top = y + "px"
        })

        document.addEventListener("click",()=>{
            list.style.display = "none"
        })

        list.onclick = function(){
            console.log("list")
        }
    </script>
</body>

(11)事件委托

①原理

基于冒泡现象,利用target,把子元素的事件委托给父元素做

②优势

减少多个函数绑定的性能损耗,动态添加的子元素也能享受之前设置的事件委托

③target

A.介绍:事件对象的一个属性

B.用途:返回你当前执行事件的事件源

C.举例(父元素的target才有用)

<div id="list">
    <ul>
        <li></li>
    </ul>
</div>
<script>
    list.onclick = function (evt) {
        console.log(evt.target)
    }
</script>

④精确委托

target可以替代常规的遍历事件绑定,但是也有坏处,就是不加操作无法精确指定哪个子元素进行事件委托

可以通过evt.target.nodeName获取nodeName,再判断nodeName是不是我们想要的来准确进行委托

<script>
    list.onclick = function (evt) {
        if (evt.target.nodeName === 'BUTTON') {
            evt.target.parentNode.remove()
        }else {
        
        }
    }

二十五、正则表达式

1.正则表达式的创建

(1)字面量法

regexp = /pattern/(表示没有修饰符)

(2)构造函数法

regexp = new RegExp('pattern','flags')

(3)两者的优缺点

①字面量法不接受任何变量插入,所以我们必须在写代码的时候就知道确切的 regexp

②构造函数法允许从字符串中动态地构造模式

<script>
        let search = prompt("What you want to search?", "love");
        let regexp = new RegExp(search);
        // 找到用户想要的任何东西
        alert("I love JavaScript".search(regexp));

2.正则表达式的简单用法

(1)搜索

使用search(regexp)搜索并返回符合条件的字符在字符串中的位置

<script>
    let str = "I love JavaScript!"; // 将在这里搜索 
    let regexp = /love/; 
    alert( str.search(regexp) ); // 2
    //它是整体检测,有空格的时候被空格相隔的字符视作一个整体,无空格则视为一个字符
    let str = "IloveJavaScript!"; // 将在这里搜索 
    let regexp = /love/; 
    alert( str.search(regexp) ); // 1
    //也可以搜索'love',两者是等价的 

(2)判断(主要功能)

使用test(regexp)判断是否符合要求,并返回真或假

<script>
    let regexp = /\d.\d/
    console.log(regexp.test('测试的内容')

(3)匹配

使用match(regexp)

搜索并返回符合条件的字符 (也是数组)

<script>
        let str = "+7(903)-123-45-67";
        let regexp = /\d/;
        alert(str.match(regexp)); // 7
        //甚至可以使用join('')捕获符合条件的字符并进行连接
        alert( str.match(/\d/g).join('') ); // 79031234567

(4)替换

使用replace(正则表达式,替换的字符)搜索并返回符合条件的字符

(5)捕获

使用exec(regexp)

搜索并返回包含符合条件的字符的数组,字符在第一位

可以结合(),除了获得符合整体条件的字符放在"0",还会把符合小括号内条件的字符放在"1","2",...的位置上

(5)获得指定方式连接的时间

        var datestr = "time is from 2029-01-01 12:20:20 to 2029-11-01 12:20:20"
        var reg = /\d{4}-\d{1,2}-\d{1,2}/
        var newdatestr = reg.exec(datestr)
        console.log(newdatestr)
        console.log(newdatestr[0].split("-").join("/"))

3.基本元字符 (没有g的情况下都只判断一位)

(1)/\d/ /\D/ 判断是否含有一位数字/是否含有一位非数字

(2)/\s/ /\S/ 判断是否含有一位空格(包含换行和tab)/是否含有一位非空格

(3)/\w/ /\W/ 判断是否含有一位字母/是否含有一位非字母

(4)/./ /./判断是否含有除空格以外的任意字符/是否含有一位小数点

4.边界符

(1)/..../m

——多行模式,仅仅会影响边界符,使其对每一行都起效

(2)/^ /

——开头边界,匹配文档的开头

<script>
    let str=`1st place: Winnie 
             2nd place: Piglet`
    alert( str.match(/^\d+/g))//1

(3)/ $/

——结尾边界,匹配文档的结尾

(4)/^ $/

——开头、结尾边界,匹配开头和结尾

<script>
    let regexp = /^a\dc$/
    console.log(regexp.test('a2c')//true
    //没搞懂它的逻辑,按理来说它是开头结尾都是a数字c就行,但是为什么a2ca2c不行呢?

(5)/b

——词边界,可以用来捕获匹配条件的单词或数字

<script>
    //捕获单词
    alert( "Hello, Java!".match(/\bHello\b/) ); // Hello 
    alert( "Hello, Java!".match(/\bJava\b/) ); // Java 
    //捕获的是独立的单词,两边有空格或者标点
    //捕获数字(以捕获2位数字为例)返回的结果会带有数字周围非数字的部分
    alert("1 23 456 78".match(/\b\d\d\b/g) )// 23,78 
    

5.限定符

(1)/ +/

——查找符合条件的所有字符

let str = "+7(903)-123-45-67"; 
alert( str.match(/\d+/g) ); // 7,903,123,45,67

(2)/ ?/

——查找符合条件的零个或者一个字符

let str = "Should I write color or colour?"; 
alert( str.match(/colou?r/g) ); // color, colour

(3)/ */

——查找符合条件的零个或者多个字符

alert( "100 10 1".match(/\d0*/g) ); // 100, 10, 1

(4)/ {n}/

——查找符合n次前面条件的字符

alert( "I'm 12345 years old".match(/\d{5}/) ); // "12345" 
//也可以用{n1,n2}来限定次数的范围
alert( "I'm not 12, but 1234 years old".match(/\d{3,5}/) ); // "1234" 
//{n1,}即大于n1次
alert( "I'm not 12, but 345678 years old".match(/\d{3,}/) ); // "345678"

6.特殊符

(1)/ ()/

——将括号内的视作整体

let reg = /(abc){2}/
console.log(reg.test('abcabc')//true

(2)/a|b/

——有左边或者右边都可

let reg = /html|php|css|java(script)?/gi; 
let str = "First HTML appeared, then CSS, then JavaScript"; 
alert( str.match(reg) ); // 'HTML', 'CSS', 'JavaScript

(3)/[abcd]/ (不需要转义,'.' '/' '+'都可以直接使用)

——判断是否含有a、b、c、d字符,[]内视为一个整体,算作一个字符

(4)/[^abc]/

——不包含abc的都可以

7.标识符(修饰符)

(1)/.../i ——不区分大小写

(2)/.../g ——查找所有匹配项

(3)/.../m ——多行模式

(4)/.../u ——开启完整的Unicode支持

4 个字节长的字符将被正确地处理,同时也能够用上 Unicode 属性来进行查找

8.Unicode 属性

\p{...} ——可以用它来匹配各种稀奇古怪的字符

(1)应用1:捕获16进制数字

let regexp = /x\p{Hex_Digit}{2}/u;
alert("number: xAF".match(regexp)); // xAF

(2)应用2:捕获中文字符

let regexp = /\p{sc=Han}/gu; // returns Chinese hieroglyphs
let str = `Hello Привет 你好 123_456`;
alert(str.match(regexp)); // 你,好

(3)应用3:捕获含有货币符号的价格文本

let regexp = /\p{Sc}\d/gu;
let str = `Prices: $2, €1, ¥9`;
alert(str.match(regexp)); // $2,€1,¥9

9.两大特性

(1)贪婪模式

如果有多个符合条件的字符,会将它们放到一个字符串中输出

let reg = /".+"/g; 
let str = 'a "witch" and her "broom" is one'; 
alert( str.match(reg) ); // "witch" and her "broom" 

(2)懒惰模式

如果有多个符合条件的字符,会分开来输出

通过在末尾添加?来启用懒惰模式 ,只会给最近的启用,不会给全部的都启用

        let reg = /".+?"/g;
        let str = 'a "witch" and her "broom" is one';
        alert(str.match(reg)); // witch, broom

10.案例:简单验证密码强度

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    form {
      width: 500px;
      padding: 20px;
      border: 3px solid #333;
      margin: 50px auto;
    }

    form > label {
      width: 100%;
      height: 70px;
    }

    form > label > input {
      width: 100%;
      box-sizing: border-box;
      padding-left: 30px;
      font-size: 20px;
      height: 50px;
    }

    form > p {
      width: 100%;
      margin-top: 10px;
      display: flex;
      justify-content: space-between;
    }

    form > p > span {
      width: 30%;
      background-color: #ccc;
      color: #fff;
      height: 30px;
      font-size: 20px;
      line-height: 30px;
      text-align: center;
    }

    form > p > span:nth-child(1).active {
      background-color: red;
    }

    form > p > span:nth-child(2).active {
      background-color: orange;
    }

    form > p > span:nth-child(3).active {
      background-color: green;
    }
  </style>
</head>
<body>
  <form>
    <label>
      <input type="text">
    </label>
    <p>
      <span ></span>
      <span ></span>
      <span ></span>
    </p>
  </form>

  <script>
    var oinput =document.querySelector("input")
    var reg1 = /\d/
    var reg2 = /[a-z]/i
    var reg3 = /[!@#$%^&*()]/

    var ospan = document.querySelectorAll("span")
    oinput.oninput = function(evt){
      console.log(this.value)
      // console.log(evt.target.value)
      var level = 0

      if(reg1.test(this.value)) level++
      if(reg2.test(this.value)) level++
      if(reg3.test(this.value)) level++

      // console.log(level)
      for(var i=0;i<ospan.length;i++){
        ospan[i].classList.remove("active")
        if(i<level){
          ospan[i].classList.add("active")
        }
      }
    }
  </script>
</body>

二十六、ES6

1.新增的变量定义方式let,const

(1)和var的区别

①必须先定义再使用

②不会有变量重名的问题

③拥有块级作用域限制

for (var i = 0; i <6; i++){
   oHeaderItem[i].onclick = function () {
       console.log(i)//点击哪个输出结果都为6(在i=5符合条件进行最后一次循环后还会++变成6)
   }
}
for (let i = 0; i <6; i++){
   oHeaderItem[i].onclick = function () {
       console.log(i)//1、2、3、4、5
   }
}

(2)let和const的区别

前者定义变量后者定义常量

2.箭头函数

(1)创建

除了function fn() { }形式的不能简写为箭头函数,其他的都可以

let test = () => {
    console.log('你好')
}

(2)省略

//当行参为一个时可以省略()
let test = num => {
    console.log('你好')
}
//当函数体内为一行代码/只有return返回值时可以省略{}
let test = num => `<li>${num}</li>` 
//当返回值为对象时,需要在两侧加()来澄清{}为对象的{}
let test = () => ({
    name:"lancelot",
})

(3)特点

①没有arguement

arguement可以在自定义函数没有行参时获得实参并以伪数组的形式返回(可以用Array.from(argument)转换为真数组,没啥必要)

②没有this(属于父级作用域)

mytext.oninput = function(){
    var that = this
    setTimeout(function(){
    console.log(that.value)
    },1000)
}
//正常的回调函数的this指向的是window对象,想要指向父级必须在外面先存一份父级的this再放到里面使用
mytext.oninput = function(){
    setTimeout(()=>{
    console.log(this.value)//由于箭头函数没有this,所以会直接指向父级元素,比较省事
    },1000)
}

3.解构赋值

(1)用途

快速地从复杂对象或数组中取得内容的方法

(2)语法

//从多维数组中获取内容
let arr2 = [1,2,[3,4,[5]]];
let [a,b,[c,d,[e]]] = arr2; //数组解构时可以用任意字母,只要格式一致即可
console.log(e)//要啥拿啥即可
//从复杂对象中获取内容
var obj = {
    name:"lancelot",
    age:18,
    location:{
        province:"jiangsu",
        city:"suzhou",
    },
    hobby:["摄影","书法","羽毛球"]
}

var {
    name:,
    age:,
    location:{
        province:,
        city:,
    },
    hobby:[a,b,c]
} = obj

(3)说明

在解构时习惯用let的话要注意对象的key有没有和定义过的变量或者是JS内置的函数名重复,如果有则要重命名——如:let {location:mylocation} = obj ,使用时就用mylocation

4.对象简写

如果对象的key的value为和key名相同的变量,则可以省略为一个

方法也可以简写为fn ( ) { }的形式

<body>
    <input type="text" id="myusername">
    <input type="password" id="mypassword">
    <button id="mybtn">login</button>
    <script>
        mybtn.onclick = function () {
            let username = myusername.value
            let password = mypassword.value

            console.log(username,password)

            var obj = {
                username,
                password
            }

            console.log("发给后端的结构",obj)
        }

        var obj = {
            a:1,
            getName(){
                console.log(this.a)
            }
        }

        obj.getName()
    </script>
</body>

5.展开运算符

(1)数组操作

①合并

var a = [1,2,3]
var b = [4,5,6]
var c = [...a,...b]
console.log(c)//[1,2,3,4,5]

②复制

var a = [1,2,3]
var b = [...a]
b[0] = "lancelot"
console.log(a,b)//[1,2,3] ["lancelot",2,3]

③行参-实参

//行参的使用
var test = function(a,b,...arr){
    //如果使用展开符,最后一位行参必须为展开符
    //没有(a,b,...arr,c)这种写法
    console.log(arr)//[3,4,5]
    console.log(a)//1
}
test(1,2,3,4,5)

var arr = [1,2,3]
var test = function(a,b,c){
    console.log(a,b,c)
}
test(...arr)//将数组导入行参,个数一一对应

④伪数组转换

function test(){
    var arr = [...arguments]
    console.log(arr)
}
test(1,2,3,4,5)

var oli = document.querySelectorAll("li")
var oliarr = [...oli]
console.log(oliarr.filter)

(2)对象操作

合并

//重复的key的value会进行覆盖
//因为会有相同信息的用户存在,为了区分会在对象中随机生成一串不重复数字作为id
var obj1 = {
    name:"Lancelot",
    age:18
}
var obj2 = {
    name:"saltfish",
    location:"suzhou"
}

 var obj = {
    ...obj1,
    ...obj2
        }

6.模块化

(1)优点

隐私不漏,重名不怕,依赖不乱

(2)模块化方法

//先创建一个module文件夹用来存放各种模块
//模块1
function fnA1() {
}
function fnA2() {
}
function test() {
}
function _finderA() {
}//这个是想要隐藏的
export {
    fnA1()
    fnA2()
    test()
}
//模块2
function fnB1() {
}
function fnB2() {
}
function test() {
}
function _finderB() {
}//这个是想要隐藏的
export {
    fnB1()
    fnB2()
    test()
}
//当我们需要调用模块1、2中的函数时
import {fnA1,fnA2,test as testA} from '路径'
import {fnB1,fnB2,test as testB} from '路径'
//要啥就在{}中写啥,当然export中不包含的写不了
//如果模块中的函数名重复,可以用as来重命名

二十七、面向对象

1.再谈创建对象

(1)字面量

(2)内置object函数 new object()

(3)工厂函数式创建

function createObj () {
    let obj = new Object()
    obj.name = 'lancelot';
    obj.age = 18;
    return obj
}
let o1 = createObj()

(4)自定义构造函数创建对象

function Person(age,name,gender) {
    this.age = age
    this.name = name
    this.gender = gender
}
let p1 = new Person(18,'lancelot','male')
let p2 = new Person(20,'daisy','female')

2.构造函数的注意事项

(1)首字母大写、和new一起使用

不加new直接调用的效果和普通函数一样

(2)构造函数中的this指向的是new调用的变量

function Person() {
    console.log(this)
}
let o1 = new Person()//this指向o1这个实例化对象

3.原型&prototype、proto

(1)用途

用来解决构造函数的一个缺点——假如构造函数中有一个方法,后续每次实例化都会出现一模一样的函数,并且分别占用空间。原型

(2)解释

每次声明一个构造函数,浏览器都会创建一个原型对象,构造函数中会有一个属性prototype,其指向构造函数的原型对象;而对象有一个属性__proto__,其也可以指向原型对象的构造函数

//将函数挂到prototype原型对象之中可以减少多次创建函数对内存的占用
//构造函数的目的是将数组中的数据逐一用数组中的方法CreateList插入到列表中
<body>
    <div class="box1">
        <h1></h1>
        <ul></ul>
    </div>

    <div class="box2">
        <h1></h1>
        <ul></ul>
    </div>
    <script>
        var data1 = {
            title: "体育",
            list: ["体育-1", "体育-2", "体育-3"]
        }

        var data2 = {
            title: "综艺",
            list: ["综艺-1", "综艺-2", "综艺3"]
        }

        function CreateList(select, data) {
            this.ele = document.querySelector(select)
            this.title = data.title,
                this.list = data.list
        }

        //原型
        CreateList.prototype.render = function () {
        
            var h1 = this.ele.querySelector("h1")
            var ul = this.ele.querySelector("ul")

            h1.innerHTML = this.title//map遍历数组中的元素并将其一一插入到li标签中
            ul.innerHTML = this.list.map(item => `<li>${item}</li>`).join("")
        }

        var obj1 = new CreateList(".box1", data1)

        console.log(obj1.__proto__ === CreateList.prototype)
        obj1.render()
        var obj2 = new CreateList(".box2", data2)
        obj2.render()
    </script>
</body>

(3)注意

①通过fn.prototype.key=value可以在原型中添加一个键值对,可以通过obj.key进行访问,但是不能进行删改,当然obj._proto_是可以的

obj.key的访问逻辑是先从对象中进行寻找,没有的时候才会到原型对象中进行寻找,如果对象和原型对象中有重名的key,那么对象中key的value会造成覆盖(仅仅是对obj.key访问而言,另外两个访问方法依然有效)

③一般属性就放在函数中,方法则挂在prototype下

④方法中的this依旧遵循“谁调用就指向谁的原则”,但是有时为了需要也会用箭头函数指向对象

(4)案例:面向对象の选项卡

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        ul {
            list-style: none;
        }

        .header {
            display: flex;
            width: 500px;
        }

        .header li {
            flex: 1;
            height: 50px;
            line-height: 50px;
            text-align: center;
            border: 1px solid black;
        }

        .box {
            position: relative;
            height: 200px;
        }

        .box li {
            position: absolute;
            left: 0;
            top: 0;
            width: 500px;
            height: 200px;
            background-color: yellow;
            display: none;
        }

        .header .active {
            background-color: red;
        }

        .box .active {
            display: block;
        }
    </style>
</head>

<body>
    <div class="container1">
        <ul class="header">
            <li class="active">1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
            <li>5</li>
            <li>6</li>
        </ul>
        <ul class="box">
            <li class="active">111</li>
            <li>222</li>
            <li>333</li>
            <li>444</li>
            <li>555</li>
            <li>666</li>
        </ul>
    </div>

    <div class="container2">
        <ul class="header">
            <li class="active">1</li>
            <li>2</li>
            <li>3</li>
            <li>4</li>
            <li>5</li>
            <li>6</li>
        </ul>
        <ul class="box">
            <li class="active">111</li>
            <li>222</li>
            <li>333</li>
            <li>444</li>
            <li>555</li>
            <li>666</li>
        </ul>
    </div>

    <script type="module">
        import MyTabs from './Tabs.js'
        
        new MyTabs(".container1","click")
        new MyTabs(".container2","mouseover")     
    </script>
</body>
function Tabs(select,type) {
    var container = document.querySelector(select)
    this.oHeaderItems = container.querySelectorAll(".header li")
    this.oBoxItems = container.querySelectorAll(".box li")
    this.type = type
    this.change()
}

Tabs.prototype.change = function () {
    for (let i = 0; i < this.oHeaderItems.length; i++) {
        //自定义属性
        this.oHeaderItems[i].addEventListener(this.type,() => {
            // console.log(this)
            var index = i
            for (var m = 0; m < this.oHeaderItems.length; m++) {
                this.oHeaderItems[m].classList.remove("active")
                this.oBoxItems[m].classList.remove("active")
            }
            this.oHeaderItems[index].classList.add("active")
            this.oBoxItems[index].classList.add("active")
        })
    }
}

export default Tabs

4.继承

(1)构造函数继承

function Person(name,age) {
    this.name = name
    this.age = age
}
Person.prototype.say = function () {
    console.log(this.name,"hello")
}
function Student (name,age,grade) {
    Person.call(this,name,age)//将Person函数的this的指向改变为指向Student并执行Person()
    //或者是Person.apply(this,[name,age])
    this.grade = grade//继承函数后可以继续往里面添加键值对
}

(2)原型继承

//会将Person创建的对象的键值对和方法全部拷贝
Student.prototype = new Person() //在Student构造函数的原型对象中实例化Person

//可以用这种方式给原型对象增添新方法
Student.prototype.printGrade = function () {
    console.log(this.name,this.grade)
}
//当然,如果方法名相同,内容会覆盖掉上面的内容

//也可以不增添方法,把原方法拷贝然后再写方法(增强)
Student.prototype.say2 = function() {
    this.say()
    console.log(this.name,"您好")
}//当然必须创建一个新方法才能用,不然会死循环

(3)组合继承

同时使用构造和原型继承就是组合继承。。

5.ES6继承

class Student extends Person{ //不加内容继承Person的方法
    constructor(name,age,grade){
        super(name,age) //用来继承Person的属性
        this.grade = grade //用来添加新属性
        this.say()//可以把方法放到里面进行调用
    }
    say(){
        super.say()//先执行一次原来的say方法
        console.log(this.name,"你好"//依旧是相同方法名进行覆盖
    }
}
let obj = new Student(name,age,grade)

二十八、AJAX

1.XMLHttpRequest 对象的常用的属性

(1)onreadystatechange属性

储存有处理服务器响应的函数,下面两个属性都要在其函数中使用

(2)readyState属性

储存有服务器响应的状态信息,每次改变都会执行并返回0~4(4表示请求过程完成)

(3)responseText属性

通过这个属性来取回由服务器返回的数据(或字符串)

(4)status属性

①404:服务器找不到请求的网页

②500:服务器遇到错误,无法完成请求

③200:一般正确的状态都为200

2.xmlhttprequst的方法

(1)open()方法

open(method,url[,async,user,password])
//method用来请求方法(get,post,put,delete等)
//url发送请求的url
//是否异步
//用于认证用途的用户名
//密码

(2)send()方法

send([body])
//body:在XHR请求中要发送的数据体,不传默认为null
//如果使用get请求发送数据的时候,要注意将数据放到open的url中,send不放参数

3.编程常用步骤

var xhr = new XMLHttpRequest()
xhr.open("GET","http://localhost:5500/136-ajax/1.json")
xhr.send()
btn.onclick = function(){} 
xhr.onreadystatechange = function(){
    if(xhr.readyState===4 && xhr.status=== 200){
        console.log("数据解析完成",xhr.responseText)
        document.write(xhr.responseText)
    }else if(xhr.readyState===4 && xhr.status=== 404){
        console.error("没有找到这个页面")
    }
}

4.案例:模拟后端数据获取

    <button id="btn">获取数据</button>
    <ul id="mylist"></ul>
    <!-- <form action="http://www.xiongmaoyouxuan.com:80/api/tabs">
        <input type="submit">
    </form> -->
    <script>
        // http://www.xiongmaoyouxuan.com:80/api/tabs

        btn.onclick = function(){
            // console.log("cllick")
            var xhr = new XMLHttpRequest()

            xhr.open("GET","http://www.xiongmaoyouxuan.com:80/api/tabs")

            xhr.send()

            xhr.onload = function(){
                // 
                if(xhr.status===200){
                    // console.log(xhr.responseText)
                    var jsondata = JSON.parse(xhr.responseText)//将JOSN格式转换成需要的数组形式
                    render(jsondata)
                }
            }
        }

        function render(jsondata){
            console.log(jsondata.data.list)
            var html = jsondata.data.list.map(item=>` 
                <li>
                    <img src="${item.imageUrl}"/>
                    <div>${item.name}</div>
                </li>
            `) //利用map映射将需要的list中的数据导入到对应的标签中
            
            mylist.innerHTML = html.join("")//把li标签拼接后导入到ul节点中
        }

5.数据请求方式

(1)get 偏向于获取数据

(使用?关键字直接在url上添加)

xhr.open("GET","http://localhost:3000/users?username=gangdaner&password=123")
xhr.onload = function(){
     if(xhr.status===200){
         console.log(JSON.parse(xhr.responseText))
     }
}
xhr.send()

(2)post 偏向于提交数据

var xhr = new XMLHttpRequest()
xhr.open("POST","http://localhost:3000/users")
//第一种提交数据的方法
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
xhr.send(`username=shanzhen&password=456`)
//第二种
xhr.setRequestHeader("Content-Type","application/json") 
xhr.send(JSON.stringify({
    username:"ximen",
    password:"789"
}))//使用JSON,stringify()将对象转换为JSON格式
//提交之后会自动获得id,提交一个,在JSON文件中就会增添一个

(3)put 偏向于更新数据(完全覆盖)

var xhr = new XMLHttpRequest()
xhr.open("PUT","http://localhost:3000/users/1")//user制定在端口,数字为id
xhr.setRequestHeader("Content-Type","application/json") 
xhr.send(JSON.stringify({
    username:"ximen11111111",
    password:"123456"
}))

(4)patch 偏向于部分修改数据

var xhr = new XMLHttpRequest()
xhr.open("PATCH","http://localhost:3000/users/2")
xhr.setRequestHeader("Content-Type","application/json") 
xhr.send(JSON.stringify({
    username:"xiaoming11111111",
}))

(5)delete 偏向于删除数据

var xhr = new XMLHttpRequest()
xhr.open("DELETE","http://localhost:3000/users/1")

6.ajax封装

function queryStringify(obj) {
  let str = ''
  for (let k in obj) str += `${k}=${obj[k]}&`//如果data使用的对象形式,自动转化为name=lancelot&&age=18的形式
  return str.slice(0, -1)
}
// 封装 ajax
function ajax(options) {
  let defaultoptions = {
    url: "",
    method: "GET",
    async: true,
    data: {},
    headers: {},
    success: function () { },
    error: function () { }
  }
  let { url, method, async, data, headers, success, error } = {
    ...defaultoptions,
    ...options
  }
  if (typeof data === 'object' && headers["content-type"]?.indexOf("json") > -1) {
    data = JSON.stringify(data)
  }
  else {
    data = queryStringify(data)
  }
  //如果是 get 请求, 并且有参数, 那么直接组装一下 url 信息
  if (/^get$/i.test(method) && data) url += '?' + data
  
  //发送请求
  const xhr = new XMLHttpRequest()
  xhr.open(method, url, async)
  xhr.onload = function () {
      if (!/^2\d{2}$/.test(xhr.status)) {
          error(`错误状态码:${xhr.status}`) //回调
      return
      }
  // 执行解析
      try {
          let result = JSON.parse(xhr.responseText)
          success(result)
      } catch (err) {
          error('解析失败 ! 因为后端返回的结果不是 json 格式字符串')
      }
  }
  //设置请求头内的信息
  for (let k in headers) xhr.setRequestHeader(k, headers[k])
  if (/^get$/i.test(method)) {
    xhr.send()
  } else {
    xhr.send(data)
  }
<script>
        ajax({
            url:"http://localhost:3000/users",
            method:"POST",
            data:{
                username:"kerwin3333",
                password:"789"
            },
            headers:{
                "content-type":"application/json"
            },
            success:function(res){
                console.log("sucess",res)
            },
            error:function(err){
                console.log("error",err)
            }
        })
</script>

7.回调地狱

(1)说明:回调地狱指JS中嵌套2个以上的函数后出现的问题

(2)产生原因:第二个嵌套函数需要第一个函数的数据,第三个嵌套函数需要第二个嵌套函数的数据,以此类推导致程序很难维护

(3)解决方法:使用promise语法编写嵌套函数

8.promise语法

var q = new Promise(function(resolve,reject){
    //resolve是已成功,执行resolve()回调函数,reject是已失败,执行reject()回调函数
    setTimeout(()=>{
        //成功兑现承诺
        resolve(["1111","222","3333"])//()中内容作为下面function(res)的res的实参
        //失败拒绝承诺
        reject("errrror")
    },2000)
})
q.then(function(res){
    //兑现承诺,这个函数被执行
    console.log("success",res)
}).catch(function(err){
    //拒绝承诺,这个函数就会被执行
    console.log("fail",err)
})
//——————————————下面是pajax的调用方法——————————————//
pajax({
    url: "http://localhost:3000/news",
    data: {
        author: "tiechui"
    }
}).then(res => {
    // console.log(res[0])
    return pajax({ //只要加return返回promise对象,那就随便用
        url: "http://localhost:3000/comments111",
        data: {
            newsId: res[0].id
        }
    })
}).then(res=>{
    console.log("success",res)
    //.then是可以重复使用的,通过不停地加.then可以把多层嵌套函数变为多层调用
}).catch(err=>{//如果执行不成功就直接到.catch了
    console.log("error",err)
})
// console.log(1111)
//——————————————下面是pajax的封装——————————————//
//需要在封装ajax函数的基础上进行封装(ajax封装见上)
function pajax(options){
    return  new Promise((resolve,reject) => {
        ajax({
            ...options,//使用...将输入的option复制展开
            success(res){
                resolve(res)
            },
            error(err){
                reject(err)
            }
        })
    })
}

9.案例:promise语法使用

   import {pajax} from './util.js'

    // console.log(ajax)

    class TodoList{
        constructor(select){
            this.listEle = document.querySelector(select)
            this.listdata = [] //列表数据

            this.init()
        }

        init(){
            //初始化
            this.bindEvent()
            //获取数据的方法
            this.getList().then(res=>{
                this.listdata = res
                this.render()
            })
        }

        bindEvent(){
            this.listEle.onclick = (evt) => {
                // console.log(evt.target)
                if(evt.target.nodeName==="BUTTON"){
                    this.removeItem(evt.target)
                }
            }
        }

        getList(){
            //获取
            return pajax({
                url:"http://localhost:3000/list",
            })
        }
        //渲染页面
        render(){
            // console.log("render")
            this.listEle.innerHTML = this.listdata.map(item=>`
            <li>
                ${item.text}
                <button data-index=${item.id}>del</button>
            </li>
            `).join("")
        }

        addItem(text){
            // console.log(text)

            //在数据库添加后, 成功回调里, 页面添加
            pajax({
                url:`http://localhost:3000/list`,
                method:"POST",
                data:{
                    text//要传入添加的数据,本来是test:test,对象简写成了现在的形式
                }
            }).then(res=>{
                this.listdata = [...this.listdata,res]//res就是获取输入框中输入的数据所构成的一个含有id的对象
                this.render()//...this.listdata是
            })

        }

        removeItem(target){
            target.parentNode.remove()
            // console.log(target.dataset.index)
            //删除任务
            pajax({
                url:`http://localhost:3000/list/${target.dataset.index}`,
                method:"DELETE"
            }).then(()=>{
                console.log("删除成功")
            })

        }
        updateItem(){

        }
    }

    var obj = new TodoList(".list")
    console.log(obj)

    myadd.onclick = function(){
        // console.log(mytext.value)
        obj.addItem(mytext.value)
    }

    console.log("显示加载中")

    var q1 = pajax({
        url:"http://localhost:3000/looplist"
    })

    var q2 = pajax({
        url:"http://localhost:3000/datalist"
    })

    Promise.all([q1,q2]).then(res=>{//promise.all([],[]).then()可以做到[]中的promise都结束后再执行()中内容
        console.log(res)

        console.log("隐藏加载中...")
    }).catch(err=>{
        console.log(err)
    })

10.async&await

(1)async

①作用

在自定义函数前加async,调用函数会返回一个promise对象

②特点

声明函数是异步的,函数promise对象状态转化需要时间,这个时候函数后的语句不受影响,会先执行

 //第一种不用回调函数的情况
 async function timeout() {
     return 'hello world!'
 }
 timeout()
 console.log('我虽然在后面,但是先执行')//只输出'我虽然在后面,但是先执行'
 //第二种使用.then()注册回调函数
 async function timeout() {
    return 'hello world!'
 }
 timeout().then(val => {//当timeout的promise状态为resolved的时候会把'hello world'作为实参取代val,并自动执行函数
    console.log(val)
 })
 console.log('我虽然在后面,但是先执行')//先输出'我虽然在后面,但是先执行',再输出'hello world'
//第三种不注册回调函数,正常调用
async function timeout () {
   for (let index = 0; index < 3; index++) {
     console.log('async', +index)
   }
}
console.log(timeout())
console.log('outer')//先像调用一般自定义函数一样执行循环并返回值,但是当循环结束后会返回一个promise对象,最后输出'outer'

(2)await

①说明

await后能放任意表达式,但是一般放一个返回promise对象的表达式

②作用

当后接promise时,会阻塞后面的语句(必须是同一函数中的,起到类同步的效果;如果是不是同一函数,该异步的还是异步)等待其执行完毕,并返回promise的结果

// 2s 之后返回双倍的值
function doubleAfter2seconds (num) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(num * 2)
        }, 2000)
      })
    }

async function testResult() {
    let first = await doubleAfter2seconds(30);
    let second = await doubleAfter2seconds(50);
    let third = await doubleAfter2seconds(30);//await使异步语句起到同步效果
    console.log(first + second + third);
}
testResult()
console.log('我先执行!!!')//函数外的异步,因为上面的函数太慢了,所以先执行这个

11.fetch

(1)新のget

fetch('http://localhost:8080/端口')
.then(res=>res.json()) //箭头函数只有return,省略了{}和return
.then(res=>{//必须用两次.then()才能输出端口中包含的对象
    console.log(res)
})

(2)新のpost

fetch('http://localhost:8080/端口',{
    method:'POST',
    headers:{
        'content-tyepe':'apllication/json'
    },
    body:JSON.stringfy({
        username:'kerwin',
        password:'123'
    })
})
.then(res=>res.json()) //箭头函数只有return,省略了{}和return
.then(res=>{//必须用两次.then()才能输出端口中包含的对象
    console.log(res)
})

(3)新のput

fetch('hettp://localhost:8080/端口/id',{
     method:"PUT",
     headers:{
         'content-type':'application/json'
     },
     body:JSON.stringfy({
         username:'kerwin',
         password:'456'
     })
})
.then(res=>res.json())
.then(res=>{
    console.log(res)
})

(4)新のdelete

fetch('hettp://localhost:8080/端口/id',{
     method:"DELETE",
})
.then(res=>res.json())
.then(res=>{
    console.log(res)
})

(5)错误处理

fetch('hettp://localhost:8080/端口/id'
     then(res=>{ 
     if(res.ok){ 
         return res.json() 
     }else{ 
         return Promise.reject({ 
         status:res.status, 
         statusText:res.statusText
         })
     }
})
.then(res=>{
    console.log(res)
})
.catch(res+.{
    console.log(err)
})