JS基础知识

一、js的发展

编程语言

概念:人与计算机沟通的语言,包括了:机器语言、汇编语言、高级语言

机器语言:

以二进制的编码对计算机硬件发出指令,是最原始,也是最高效的语言
img

汇编语言:

以英文的单词缩写对二进制代码进行了一层封装,也不太容易阅读,实现一个简单的功能都需要大量的逻辑代码,开发麻烦。也能直接对计算机发出指令,效率也不错。

高级语言:

以人类的语言作为基础,对汇编又再次封装,更容易被人理解。高级语言的执行效率低于汇编及机器语言。

常见的高级语言:C、C++、C#、java、php、python、go、javaScript…

JavaScript概念

  • javascript(简称js),是一种轻量级的解释性语言,也叫脚本语言。js是作为web网页开发而出名的,js是可以运行在浏览器环境中,现在也可以运行在其他环境:nodejs(后端环境)、adobe环境…

JavaScript发展

  1. 1995年js 诞生了,由网景公司的内部员工(布兰登-艾奇)花了10天时间就设计出了第一版js,当时叫做LiveScript、后期网景公司为了寻求发展,与SUN公司(java的研发公司)合作,改名JavaScript。
  2. 1996微软为了抢市场,也开发出了一个脚本语言Jscript。
  3. 网景公司后期把js交给了一个计算机协会:欧洲计算机制造协会(ECMA)—— ECMAScript。
  4. 欧洲计算机制造协会ECMA为了前端脚本语言的统一组织:网景和微软的研发人员来开会,形成了一个第一版统一的js标准:ECMA-262
  5. 1999年 ECMA推出了第三版js、包含了绝大部分的编程核心语法——ECMAScript 3 ,简称ES3
  6. 2009年才推出了ECMAScript 5,简称ES5,也是目前市场上使用度最高的版本,2012年到现在位置所有浏览器都支持这个版本
  7. 2015年 ECMAScript 2015,简称 ES6,更新了许多新的特性,IE全系不支持。
  8. 2015年之后,每一年都会发布一个版本,2016——ES7 … 2022年——ES13。ES14(草案中)

js的核心组成

一个完整的js的语法是由三部分组成:ECMA核心语法、BOM、DOM

  1. ECMA核心语法:主要包含了输入、输出、变量、数据类型、控制结构、数组、函数等…

  2. 浏览器专属:

    1. BOM:负责与浏览器窗口进行交互的,比如:新建窗口、关闭页面、页面跳转、刷新、定时器等…
    2. DOM:负责与页面里的标签、样式进行交互,比如:新建标签,修改样式等…

二、js的运行环境

nodejs:不支持 DOM语法 和 BOM语法

  • 服务器环境,可以单独运行js代码:

  • 需要安装node.js:

    1. 检查nodejs是否安装好:cmd——》node -v
  • 运行:

    1. cmd 找到对应的文件目录 node ./文件名.js

    2. 借助vscode里面的终端去允许 node ./文件名.js

    3. 借助插件运行,code runner 插件 在代码编辑区右键 run code

console.log('hello world')

浏览器环境:支持DOM、BOM 比如 document.write()

  1. 在body的结尾标签之上去编写一个 <script>标签,在这个标签里就可以编写js代码
<body>
    <!-- html代码 -->

    <script>
        console.log('hello world')
    </script>
</body>
  1. 通过script标签在外部引用js文件

    1. 创建js后缀结尾的文件,例如:index.js
    console.log('hello world2')
    
    1. 在body结尾标签上方去引入js文
    <body>
        <!-- html代码 -->
    
        <script src="./js/js基础.js"></script>
    </body>
    

js的输入输出

JS输出

js的三种输出方式

document.write():将内容输出在页面上
document.write('hello "lol"')

注意:

1.	文字内容需要引号来包裹,可以是单引号也可以是双引号。
1.	如果引号需要互相嵌套使用,外层是单(双)引号,里面必须双(单)引号,否则相反。

运用:还可以在页面中输出一个标签

document.write('<a href="https://www.baidu.com">百度一下</a>')
console.log():将内容输出在控制台里
console.log('hello world')
  1. 可以识别多个空格符
  2. 如果输出纯数字,可以不需要引号包裹
alert():将内容输出在一个警告提示框里
alert('hello')
confrim():
弹出一个选择框,选择确认输出真的true,取消输出的假的false

输入

prompt('提示性文字')

输出输入的内容到页面上

document.write(prompt('提示性文字'))

三、JS的变量

变量

一个保存数据的容器,方便后续重复使用

声明变量:

var 变量名;
变量名 = 数据;

var 变量名 = 数据;

使用变量:

console.log(变量名)
document.write(变量名)

修改变量:直接给变量重新赋值就行

变量名 = 新的内容

变量名的规范:

  1. 可以包含字母、数字、_、美元符号$

  2. 不能以数字开头

  3. 名字对大小写敏感(username和userName 是不同的变量)

  4. js的保留关键字不能用作变量名:for、var、if、class、name…

  5. 建议的命名方式:语义化命名

    1. 单个单词和缩写:user、password、data、num…
    2. 多个单词组合(小驼峰命名):userData、userAgeList…
    3. 大驼峰命名(多用在穿件类名上):UserData、UserAgeList…
    4. 驼峰法,从第二个单词开始首字母大写:userName

四、数据类型

js中数据类型分为了两大类

  1. 基本(基础、简单)数据类型
    1. 字符串 string
    2. 数字 number
    3. 布尔值 boolean
    4. 未定义 undefined
    5. 空 null
    6. 唯一值 symbol ES6新增 (了解)
    7. 超大数 bigInt ES10新增(了解)
  2. 引用(复杂)数据类型
    1. 对象 object
      • 原生对象 object 、数组 Array、函数Function…

字符串 string

所有用引号包裹的都是字符串类型

var str = 'hello'
var str = '123';
var str = "";

转义符:

var str = '我叫"提莫"';
var str = "我叫\"提莫\"";
var str = "我叫\\提莫\\";
var str = "我叫\\\\提莫\\\\";

换行符:

var str = "我叫\n提莫"

字符串拼接: +

var str1 = "hello";
var str2 = "world"
console.log(str1 + ' ' + str2)

注意:如果一个数字和字符串相加,会得到另一个字符串

数字类型 number

var num = 10
var num = 10.1 //浮点数
var num = 1.23e5  //eX  10的X次方
console.log(0.3-0.2 == 0.2-0.1)  //false
var num = -12345e1000  //-Infinity

布尔值 boolean

只有两个值 :true false

var boo = true
var boo = false

未定义 undefined

只有一个值 undefined,声明了一个变量,没有赋值会得到一个undefined

var str;
var str = undefined;

空 null

只有一个值,就是null,一般表示为空

var str = null;

console.log(null == undefined) //true
console.log(null === undefined) //false

typeof 判读数据类型,只能检测基本数据类型

typeof "123"  //string
typeof 123    //number
typeof true   //number
typeof undefined   //undefined
typeof null   //object

对象:

  • 原生对象 {}
  • 数组 []
  • 函数 function(){}
var obj = {}
console.log(obj, typeof obj)  //object

var arr = []
console.log(arr, typeof arr) //object

var func = function(){}
console.dir(func, typeof func) //function   prototype——object

数据类型之间存储的区别(重点)

既然我们区分了基本数据类型和复杂数据类型
那么他们之间就一定会存在一些区别
他们最大的区别就是在存储上的区别
我们的存储空间分成两种

  • 栈: 主要存储基本数据类型的内容
    栈会自动分配内存空间,会自动释放,存基本类型,简单的数据段,占据大小固定的空间
    基本数据类型:null undefined string boolean number
  • 堆: 主要存储复杂数据类型的内容
    动态分配的内存,大小不定,也不会自动释放.值由多个值组成的对象,保存在堆的内存中
    包括引用类型的变量,实际上保存的不是变量本身,认识指向该对象的指针(地址)

基本数据类型在内存中的存储情况

  • var num = 100,在内存中的存储情况
  • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
  • 直接在 栈空间 内有存储一个数据

复杂数据类型在内存中的存储情况

  • 下面这个 对象 的存储

    var obj = {
      name: 'Jack',
      age: 18,
      gender: '男'
    }
    
  • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 复杂数据类型的存储

    1. 在堆里面开辟一个存储空间
    2. 把数据存储到存储空间内
    3. 把存储空间的地址赋值给栈里面的变量
  • 这就是数据类型之间存储的区别

数据类型之间的比较

  • 基本数据类型是 之间的比较

    var num = 1
    var str = '1'
    
    console.log(num == str) // true
    
  • 复杂数据类型是 地址 之间的比较

    var obj = { name: 'Jack' }
    var obj2 = { name: 'Jack' }
    
    console.log(obj == obj2) // false
    
    • 因为我们创建了两个对象,那么就会在 堆空间 里面开辟两个存储空间存储数据(两个地址)
    • 虽然存储的内容是一样的,那么也是两个存储空间,两个地址
    • 复杂数据类型之间就是地址的比较,所以 objobj2 两个变量的地址不一样
    • 所以我们得到的就是 false

五、数据类型的转换

强制转换

通过一些方法(函数),强制让数据类型变化

转字符串

  • toString()
  • String()
var str =  num.toString()
// console.log(window)
// window.String()
var str = String(num)

console.log(str ,typeof str)

区别:

toString()这个方不能转换nullundefined

String()可以转换任意类型为字符串

转数字类型

  • Number() 转为数字类型
  • parseInt() 取整,并且转为数字类型
  • parseFloat() 保留浮点数,并且转为数字类型
// var str = '10.1'      10.1    10    10.1
// var str = '10.1a'     NaN    10    10.1
var str = 'a10.1'	//   NaN     NaN    NaN

console.log(Number(str)); 
console.log(parseInt(str));
console.log(parseFloat(str));

NaN:not a number

转布尔值

  • Boolean()
var num = 1   //true
var num = 0   //false

var str = 'abc'  //true
var str = ''  //false

var a = undefined  //false
var a = null  //false
var a = -0  //false
var a = false
var a = true

var boo = Boolean(num)
var boo = Boolean(str)
var boo = Boolean(a)

除了 0、-0、“”、undefined、false、null、NaN ,这7中转布尔值都是false; 除了这7种以外转成布尔值 都是 true。

隐式转换

通过运算符和其他的方式,隐式转换

转字符串

// var num = 10
// var boo = true
// boo = boo + ""
var boo = null
boo = boo + ""

// console.log(typeof (num + "") ) //string
// console.log(typeof boo, boo) //string
console.log(typeof boo, boo) //string
  • 任何类型只要 通过 +号,拼接了一个空的字符串,那都会转成字符串类型

转数字

// var str = '10a'
// var str = undefined

console.log(str - 0)
console.log(str * 1)
console.log(str / 1)
console.log( ~~str )
console.log( +str )
console.log( parseInt(str) )

转布尔值

取反:!,任意类型要取反时 都会转成布尔值再去取反

var num = 0 
!num 	//  true
!!num   // false

var str = ""
!str  	//true
!!str 	//false

六、JS的基础运算符

分类

  • 算术运算符
  • 赋值运算符
  • 比较运算符
  • 逻辑运算符

算术运算符:完成数学计算

  • 加 +

  • 减 -

  • 乘 *

  • 除 /

  • 余(取模) % :得到两数相除的余数

  • 自增 ++

  • 自减 –

赋值运算符:可以给变量赋值或者修改变量

  • =:等于,把右边的结果赋值给左边的变量
  • +=
  • -=
  • *=
  • /=
  • %=

比较运算符:数据与数据之间进行比较,得到一个布尔值(true false)

  • ==:判断两个内容是否相等,不考虑数据类型(5==‘5’ true)

  • ===:全等于,判断两个内容是否相等,要考虑数据类型(5===‘5’ false)

  • <:判断前面的值是否小于后面的值

  • <=:判断前面的值是否小于等于后面的值

  • >:判断前面的值是否大于后面的值

  • >=:判断前面的值是否大于等于后面的值

  • !=:判断两个内容是否不等于,如果不等于返回true,等于false

  • !==:不全等于,考虑数据类型

var a = 5
var b = '5'
var c = 6
var d = 5

console.log( a == b )  //true
console.log( a === b )  //false

console.log( a < c ) //true
console.log( a > c ) //false
console.log( a <= c ) //true
console.log( a >= c ) //false

console.log(a != c) //true
console.log(a !== d) //false
console.log(a !== b) //true

逻辑运算符:用于对多个条件的判断

  • &&:与运算,并且的意思,既要满足前面的条件又要满足后面的条件,才会返回true,只要有一个条件不满足就会返回false
var  num = 5

console.log(num >= 5 && num == 5  && num <= 5 && num === 5) //true
console.log(num >= 5 && num === '5') //false
  • ||:或运算,或者的意思,只要满足条件中的任意一个即可,就会返回true,每个条件都不满足才会返回false
console.log( num > 5 || num == 5)  //true
console.log( num > 5 || num < 5 )  //false

七、控制结构——分支结构

image-20220324104138946

分支结构:

根据不同的条件执行不同的代码块

if 语句

  • 通过一个 if 语句来决定代码是否执行

  • 语法: if (条件) { 要执行的代码 }

  • 通过 () 里面的条件是否成立来决定 {} 里面的代码是否执行

    // 条件为 true 的时候执行 {} 里面的代码
    if (true) {
      alert('因为条件是 true,我会执行')
    }
    
    // 条件为 false 的时候不执行 {} 里面的代码
    if (false) {
    	alert('因为条件是 false,我不会执行')    
    }
    

if else 语句

  • 通过 if 条件来决定,执行哪一个 {} 里面的代码

  • 语法: if (条件) { 条件为 true 的时候执行 } else { 条件为 false 的时候执行 }

  • 两个 {} 内的代码一定有一个会执行

    // 条件为 true 的时候,会执行 if 后面的 {} 
    if (true) {
      alert('因为条件是 true,我会执行')
    } else {
      alert('因为条件是 true,我不会执行')
    }
    
    // 条件为 false 的时候,会执行 else 后面的 {}
    if (false) {
      alert('因为条件为 false,我不会执行')
    } else {
      alert('因为条件为 false,我会执行')
    }
    

if else if … 语句

  • 可以通过 ifelse if 来设置多个条件进行判断

  • 语法:if (条件1) { 条件1为 true 的时候执行 } else if (条件2) { 条件2为 true 的时候执行 }

  • 会从头开始依次判断条件

    • 如果第一个条件为 true 了,那么就会执行后面的 {} 里面的内容
    • 如果第一个条件为 false,那么就会判断第二个条件,依次类推
  • 多个 {} ,只会有一个被执行,一旦有一个条件为 true 了,后面的就不在判断了

    // 第一个条件为 true,第二个条件为 false,最终会打印 “我是代码段1”
    if (true) {
      	alert('我是代码段1')
    } else if (false) {
    	alert('我是代码段2')           
    }
    
    // 第一个条件为 true,第二个条件为 true,最终会打印 “我是代码段1”
    // 因为只要前面有一个条件满足了,就不会继续判断了
    if (true) {
      	alert('我是代码段1')
    } else if (true) {
      	alert('我是代码段2')
    }
    
    // 第一个条件为 false,第二个条件为 true,最终会打印 “我是代码段2”
    // 只有前一个条件为 false 的时候才会继续向后判断
    if (false) {
      	alert('我是代码段1')
    } else if (true) {
      	alert('我是代码段2')
    }
    
    // 第一个条件为 false,第二个条件为 false,最终什么也不会发生
    // 因为当所有条件都为 false 的时候,两个 {} 里面的代码都不会执行
    if (false) {
      	alert('我是代码段1')
    } else if (false) {
      	alert('我是代码段2')
    }
    

if else if … else 语句

  • 和之前的 if else if ... 基本一致,只不过是在所有条件都不满足的时候,执行最后 else 后面的 {}

    // 第一个条件为 false,第二个条件为 false,最终会打印 “我是代码段3”
    // 只有前面所有的条件都不满足的时候会执行 else 后面的 {} 里面的代码
    // 只要前面有一个条件满足了,那么后面的就都不会执行了
    if (false) {
      	alert('我是代码段1')
    } else if (false) {
      	alert('我是代码段2')
    } else {
      	alert('我是代码段3')
    }
    

SWITCH 条件分支结构(重点)

  • 也是条件判断语句的一种

  • 是对于某一个变量的判断

  • 语法:

    switch (要判断的变量) {
      case 情况1:
        情况1要执行的代码
        break
      case 情况2:
        情况2要执行的代码
        break
      case 情况3:
        情况3要执行的代码
        break
      default:
        上述情况都不满足的时候执行的代码
    }
    
    • 要判断某一个变量 等于 某一个值得时候使用
    • 主要判断某一个条件是否全等于,需要考虑数据类型,而且这个条件是一个固定的值

案例:

// 输入今天星期几数字 提示今天星期几
var num = prompt('提示今天星期几')
console.log(num)

switch(num){
    case '1':
        console.log('今天星期1')
        break;
    case '2':
        console.log('今天星期2')
        break;
    case '3':
        console.log('今天星期3')
        break;
    case '4':
        console.log('今天星期4')
        break;
    case '5':
        console.log('今天星期5')
        break;
    case '6':
        console.log('今天星期6')
        break;
    case '7':
        console.log('今天星期7')
        break;
    default:
        console.log('输入有误,再见')
        break;
}
  • 该分支只能用于判断股固定选项,如果条件是一个范围switch不适用

  • break:用于阻断代码继续向下执行

三元运算符(扩展)

  • 三元运算符,就是用 两个符号 组成一个语句

  • 三元运算符只是对 if else 语句的一个简写形式

  • 语法: 条件 ? 条件为 true 的时候执行 : 条件为 false 的时候执行

    var age = 18;
    age >= 18 ? alert('已经成年') : alert('没有成年')
    

if语句和switch语句的区别

  • 一般情况下,这两个语句是可以相互替换的;
  • switch语句通常处理case为比较确定的值的情况,而if…else…语句更加灵活,常用于范围判断;
  • switch语句进行条件判断后直接执行对应的case语句,而if语句根据条件按照顺序执行;
  • 所以当分支比较少的时候if语句的执行效率比switch语句高;反之switch效果更高。

八、控制结构——循环结构

  • 循环结构,就是根据某些给出的条件,重复的执行同一段代码
  • 循环必须要有某些固定的内容组成
    1. 初始化
    2. 条件判断
    3. 要执行的代码
    4. 自身改变
  • 死循环:无法靠自身的控制终止的循环即死循环。
  • 注意循环的特点:程序的循环速度非常快,大部分循环需要执行完成,才显示最终的结果。
image-20220324104138946

while循环

while(循环继续的条件){
    当条件为true时,会执行的代码
}

var num = 1000

while(num > 0){
    //满足上面条件会执行的代码
    console.log(num)  //输出1000 - 1 之间所有的数字
    num = num - 1
}

do while循环

和while的区别:会先执行一次do里面的代码

do{
    会先执行一次的代码,满足条件会再次循环执行
}while(循环继续的条件)

var num = 5
do{
   console.log(num)
   num = num - 1
}while(num > 0)

for循环

// for('循环的初始条件';'决定是否循环的条件';'循环的次数操作'){
//     //循环的主体代码
//     console.log(1)
// }

for(var i = 1; i <= 5 ; i = i + 1){
    //循环的主体代码
    console.log(i,1)
}

break、continue

break:跳出整个循环,循环不再继续向下执行

  • 比如:我要吃五个包子,吃到三个的时候,不能在吃了,我就停止吃包子这个事情
  • 要终止循环,就可以直接使用 break 关键字

continue:跳出当前这一次循环的后续代码,但是会继续执行后续的循环

  • 比如:吃五个包子,到第三个的时候,第三个掉地下了,不吃了,跳过第三个,继续吃第四个和第五个
  • 跳过本次循环,就可以使用 continue 关键字
for(var i = 1 ; i <= 5 ; i++){
    console.log(i)  //  1  ,  2   ,  3
    if(i == 3){
        break; //不再继续循环下去   
    }
}

for(var i = 1 ; i <= 5 ; i++){
    if(i == 3){
        continue //跳出这一次的循环主体代码,然后会继续下一次循环
    }
    console.log(i)  //  1,  2,  4,  5
}

双重for循环

//99乘法表
for(var i=1 ; i <=9 ; i++){
    var str =  ''
    for(var j = 1; j<=i ; j++){
        str = str + j +'*'+ i + '=' + i * j + '     '
    }
    console.log(str)
}

九、数组

概念:

数组,是一些有序数据的集合,即数组能保存多个数据,并且保持一定的顺序

定义数组

  1. 构造函数法
var arr = new Array(数据1,数据2,数据3,...)
  1. 字面量法
var arr = [数据1,数据2,数据3,...]
  • 数组的特点:
    1. 数组可以储存任意数据类型的数据
    2. 数组里可以通过一些手段名,对数组进行:增、删、改、查

数组的下标:

  • 表示存放数据的位置,通过数字来表示,0代表第一条数据,第3条数据下标就是2
  • 最后一条数据,就是数组的长度length - 1

数组的长度:length

  • 数据有多少,length就是多少。

数据的处理

查询数据

var arr = [1,'2',true,undefined,null,NaN,[1,2,3]]

console.log(arr.length)  //7
console.log(arr[1])   // '2'
console.log(arr[arr.length-1][0])  // [1,2,3]--->1

查询数组中没有的数据时,会返回undefined

新增数据

var arr = [1,'2',true,undefined,null,NaN,[1,2,3]]
arr[arr.length] = 'hello'
console.log(arr)  //[1,'2',true,undefined,null,NaN,[1,2,3],'hello']

修改数据

var arr = [1,'2',true,undefined,null,NaN,[1,2,3]]
arr[0] = 9
arr[arr.length-1] = 'ABC'
console.log(arr[0])  //9
console.log(arr[arr.length-1])  //'ABC'

删除数据

var arr = [1,'2',true,undefined,null,NaN,[1,2,3]]
arr.length = arr.length - 1
arr.length--
--arr.length

delete arr[0]  //删除对应下表的数据
console.log(arr[0]) //undefined
console.log(arr) //[空白,'2',true,undefined]

遍历数组

  • 通过循环,依次查询数组里的每一条数据
var arr = [3,4,7,9,0,1,2,5,6,8]

for(var i = 0; i < arr.length;i++){
    //console.log(arr[i]) 
    //的到为7的数字
    if(arr[i] == 7){
        console.log(i , arr[i]);
        break;
    }
}

二维数组(多维数组)

  • 一层数组下的数据又是一层数组
var arr = [[1,2,3],[4,5],[6]]
  • 遍历二维数组
var  arr = [[1,2,3],[4,5],[6]]

for(var i = 0; i < arr.length ; i++){
    // console.log(arr[i])
    for(var j = 0; j < arr[i].length; j++){
        console.log(arr[i][j])
    }
}

十、数组的API

api:application progarmming interface

每一个api就是一个函数(方法),是由程序内部提供的,每一个api就对应了一个功能:alert()、parseInt()、toString()…

数组的增删改api

方 法语 法说 明改变原数组返回值js版本
push()array.push(item1, item2, ..., itemX)向数组末尾添加新元素truearray.lengthECMAScript 1
unshift()array.unshift(item1, item2, ..., itemX)向数组开头添加新元素truearray.lengthECMAScript 1
pop()array.pop()删除数组的最后一个元素true删除的元素ECMAScript 1
shift()array.shift()删除数组的第一个元素true删除的元素ECMAScript 1
splice()array.splice(开始下标(包含), 删除元素的个数, 新增元素1, ....., 新增元素n)截取数组中的某些内容,按照数组的索引来截取 1.只有一个参数会将指定下标元素和之后的元素删除; 2.有两个参数时可以删除指定位置的元素 3.多个参数时可以修改任意位置的元素true删除的元素ECMAScript 1

案例:

var arr = ['提莫','亚索','德玛西亚','贾克斯','男枪']

//向数组的最后一位添加一个英雄名字
arr.push('女枪')
//向数组的开头添加一个数据
arr.unshift('艾瑞莉娅')
//push和unshift的返回值都是改变后的数组长度

//删除数组中最后一条数据
arr.pop()
//删除数组中第一条数据
arr.shift()
//pop和shift的返回都是被删除的具体数据

  • splice 是截取数组中的某些内容,按照数组的索引来截取

  • 语法: splice(从哪一个索引位置开始,截取多少个,替换的新元素) (第三个参数可以不写)

    var arr = [1, 2, 3, 4, 5]
    
    // 使用 splice 方法截取数组
    arr.splice(1, 2)
    
    console.log(arr) // [1, 4, 5]
    
    • arr.splice(1, 2) 表示从索引 1 开始截取 2 个内容
    • 第三个参数没有写,就是没有新内容替换掉截取位置
    var arr = [1, 2, 3, 4, 5]
    
    // 使用 splice 方法截取数组
    arr.splice(1, 2, '我是新内容')
    
    console.log(arr) // [1, '我是新内容', 4, 5]
    
    • arr.splice(1, 2, '我是新内容') 表示从索引 1 开始截取 2 个内容
    • 然后用第三个参数把截取完空出来的位置填充

数组的查询api

查询数组中,是否存在这个数据,还有查询数据的位置
方 法语 法说 明改变原数组返回值js版本
indexOf()array.indexOf(item,index)查询元素在数组中第一次出现的下标:item要查询的元素falseindex(下标)、没找到返回-1ECMAScript 5
lastIndexOf()array.lastIndexOf(item,index)查询元素在数组中最后一次出现的下标:item要查询的元素falseindex(下标) 、没找到返回-1ECMAScript 5
includes()array.includes(element)查询元素在数组中是否存在falsetrue/falseECMAScript 7
var arr = ['提莫','亚索','德玛西亚','贾克斯','男枪','德玛西亚']

var res1 = arr.indexOf('德玛西亚')      // 2
var res = arr.indexOf('皇子')          // -1
var res2 = arr.lastIndexOf('德玛西亚')  // 5

var res =  arr.includes('皇子') //false

数组的排序api

方 法语 法说 明改变原数组返回值js版本
reverse()array.reverse()反转数组中元素的顺序true反转后的数组ECMAScript 1
sort()array.sort()array.sort(function(a, b){return a-b})array.sort(function(a, b){return b-a})1.适用于全小写或全大写的字母排序 2. a - b 从小到大排序 3. b - a 从大到小排序true

更多排序方法:https://www.runoob.com/w3cnote/selection-sort.html

数组的遍历api

方 法语 法说 明改变原数组返回值js版本
for循环for(var i = 0; i < array.length; i++) {console.log(arr[i]);}/
for offor (var item of array) {console.log(item);}其中 item 是数组中的每一个元素,arr 是要遍历的数组名。false/ECMAScript 6
forEach()array.forEach(function (item, index) {console.log(item,index);})其中 item 是数组中的每一个元素名,index 是对应元素的下标。falseundefinedECMAScript 5
map()array.map(function (item, index) {console.log(item,index); return item})其中 item 是数组中的每一个元素名,index 是对应元素的下标。返回一个长度一致的新数组falsenew arrayECMAScript 5
filter()array.filter(function (item, index) {console.log(item,index); return 筛选条件})其中 item 是数组中的每一个元素名,index 是对应元素的下标。返回一个筛选过后的新数组falsenew arrayECMAScript 5
every()array.every(function (item, index) {console.log(item,index); return 判断条件})其中 item 是数组中的每一个元素名,index 是对应元素的下标。根据判断条件返回真假,一假则假falsebooleanECMAScript 5
some()array.some(function (item, index) {console.log(item,index); return 判断条件})其中 item 是数组中的每一个元素名,index 是对应元素的下标。根据判断条件返回真假,一真则真falsebooleanECMAScript 5
find()array.find(function(item, index){console.log(item,index); return 判断条件}})其中 item 是数组中的每一个元素名,index 是对应元素的下标。根据判断条件返回第一个满足条件的itemfalse返回第一个满足条件的item,没有符合的返回 undefinedECMAScript 6
reduce()array.reduce(function(total, item, index), 初始值)其中 item 是数组中的每一个元素名,index 是对应元素的下标。total为初始值或先前返回的值false遍历完最后一次的返回结果ECMAScript 5

数组的拼接和截取api

方 法语 法说 明改变原数组返回值js版本
concat()array1.concat(array2, array3, ..., arrayX)方法用于拼接两个或多个数组。false合并后的新数组ECMAScript 1
slice()array.slice(开始下标(包含), 结束下标(不包含))根据对应下标(可以是负数),截取数组中的某一个部分false截取后的新数组ECMAScript 1
//1. 拼接数组
var arr1 = [1,2,3,4];
var arr2 = [5,6,7,8];
var arr3 = [9,0];

var newArr = arr1.concat(arr2,arr3,);

//扩展运算符    浅拷贝   ES6新增
var newArr = [...arr1,...arr2,...arr3];

//2.数组的截取
var arr = [1,2,3,4,5,6,7,8,9]

var newArr = arr.slice(0,3)
var newArr2 = arr.slice(3,arr.length)
console.log(newArr)
console.log(newArr2)

数组转字符串api

方 法语 法说 明改变原数组返回值js版本
join()array.join(分隔符(默认逗号))将数组里的元素按照指定字符拼接成一个字符串falsestringECMAScript 1
//3.数组转字符串
// var str = arr.toString()  //1,2,3,4,5
// var str = String(arr) //1,2,3,4,5
var str = arr.join('') //12345

console.log(str)

判断是不是数组 Array.isArray()

var arr = [1,2,3,4,5,6,7,8,9]
//var arr = 1234

// typeof arr //object
var res = Array.isArray(arr) 
console.log(res) //true

数组塌陷

  • 当你删除数组前面位置的索引数据的时候

    • 从你删除的索引位置开始, 后面的每一个数据会向前递进索引
    • 当你从数组的前面开始删除数据
    • 如果只是删除一个, 以后不再删除了, 那么没有问题
    • 如果你删除一个以后, 还要继续遍历并删除内容, 就会出现问题
    var arr = [ 1, 2, 2, 3, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6 ]
    console.log('原始数组 : ', arr)
    
    for (var i = 0; i < arr.length; i++) {
      arr.splice(i, 1)
    }
    
  • 解决方案:

    • 方案1: 倒着循环
    var arr = [ 1, 2, 2, 3, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6 ]
    console.log('原始数组 : ', arr)
    
    for (var i = arr.length - 1; i >= 0; i--) {
        arr.splice(i, 1)
    }
  • 方案2: i–, i-- 必须要伴随 splice 使用
    var arr = [ 1, 2, 2, 3, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6 ]
    console.log('原始数组 : ', arr)
    
    for (var i = 0; i < arr.length; i++) {
        arr.splice(i, 1)
        i--
    }

数组去重的方法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    利用Set的数据结构(Set集合)
    数据结构特点:
    1.长得像数组
    2.天生不接受任何重复数据    语法 new Set([数据1,数据2...])
    还原成数组

    把Set集合还原成数组
    =>1.Array.from(数据)
        作用:把其他类型的数据还原车数组
        返回值还原为数组
    =>2.使用es6提供给我们展开运算符(rest参数...)
        作用:可以把一个数据展开
    <script>
        // 1.利用Set的数据结构,把数组内部的重复的数据删除
        var arr=[3,3,3,3,3,3,1,21,4,4,3,3,3];
        var s=new Set(arr)
        console.log(s);//集合Set(4) {3, 1, 21, 4}

        // 2.还原Set集合为数组
        var arr2=Array.from(s);
        console.log(arr2);//[3, 1, 21, 4]

        var arr3=[...s];
        console.log(arr3);//[3, 1, 21, 4]
		
		const arry = [1,2,1,3,7,8,3,4,1,2,3,5]
		    let newArry = [...new Set(arry)]		//实现数组去重
		    console.log(newArry);
    </script>
</body>
</html>

十一、function函数

作用:

  1. 当代码量庞大时,我们可以针对其中一些核心逻辑,提取出来封装成函数。
  2. 当我们在使用不断重复的代码时,通过函数将这些代码包装起来,后续只需要调用即可

创建函数

函数声明式

function 函数名(){
    
}

函数名()		//调用

函数表达式

var 变量 = function(){
    
}

变量()		//调用

区别:

  1. 函数声明式有函数声明提升,在代码的最顶部,声明了这个函数,这个函数可以在当前作用域的任意位置调用
  2. 函数表达式:把一个匿名函数交给了一个变量,会将这个变量提升到代码的最顶部,并且赋给这个变量为undefined,这种情况在函数表达式之前去调用函数时就会报错,not a function不是一个函数

调用函数:可以在后续调用多次

函数的参数:

对数据进行传递,外部的数据可以传递个给函数内部。

作用:当创建一个函数后,希望里面的某些数据是动态变化的。这个时候就可以利用函数的参数来解决。

语法:

function 函数名(形参1,形参1,形参1,...){
    console.log(形参1,形参1,形参1)
}
函数名(实参1,实参2,实参3,...)

注意:

1. 从语法上来讲,函数的参数数量可以不一致
1. 函数的参数可以是任意类型的数据

函数的return

return 返回的意思,其实就是给函数一个 返回值终断函数

在函数外部使用函数内部的数据时,可以通过return关键字将数据返回出来

var s = sum(2,5)

function sum(num1,num2){
    var total = num1 + num2
    return total
}

console.log(s)

注意:

  1. return可以打断函数里的代码继续向下执行
  2. 一个函数如果没有return真正的数据出去,外部得到的返回值是一个undefined

arguments

function func(){
	console.log(arguments)
}

arguments是一个类数组对象,你传进来的所有参数都会被这个arguments所接收。

作用域:变量的活动范围

当一个函数创建成功时,该函数就会把函数的内部和外部分为两个区域,就是分为了两个作用域:

函数外部:全局作用域,变量在任意位置都可以使用,包括另一个局部作用域里

函数内部:局部作用域(函数作用域),内部的变量只能在这个作用域内部使用

箭头函数:ES6的新特性,通过=>取代了function关键字

  • 箭头函数是 ES6 里面一个简写函数的语法方式

  • 重点: 箭头函数只能简写函数表达式,不能简写声明式函数

    function fn() {} // 不能简写
    const fun = function () {} // 可以简写
    const obj = {
      fn: function () {} // 可以简写
    }
    
  • 语法: (函数的行参) => { 函数体内要执行的代码 }

    const fn = function (a, b) {
      console.log(a)
      console.log(b)
    }
    // 可以使用箭头函数写成
    const fun = (a, b) => {
      console.log(a)
      console.log(b)
    }
    
    const obj = {
      fn: function (a, b) {
        console.log(a)
        console.log(b)
      }
    }
    // 可以使用箭头函数写成
    const obj2 = {
      fn: (a, b) => {
        console.log(a)
        console.log(b)
      }
    }
    

箭头函数的特殊性

箭头函数内部没有 this,箭头函数的 this 是上下文的 this

// 在箭头函数定义的位置往上数,这一行是可以打印出 this 的
// 因为这里的 this 是 window
// 所以箭头函数内部的 this 就是 window
const obj = {
  fn: function () {
    console.log(this)
  },
  // 这个位置是箭头函数的上一行,但是不能打印出 this
  fun: () => {
    // 箭头函数内部的 this 是书写箭头函数的上一行一个可以打印出 this 的位置
    console.log(this)
  }
}

obj.fn()
obj.fun()

按照我们之前的 this 指向来判断,两个都应该指向 obj
但是 fun 因为是箭头函数,所以 this 不指向 obj,而是指向 fun 的外层,就是 window

箭头函数内部没有 arguments 这个参数集合

const obj = {
  fn: function () {
    console.log(arguments)
  },
  fun: () => {
    console.log(arguments)
  }
}
obj.fn(1, 2, 3) // 会打印一个伪数组 [1, 2, 3]
obj.fun(1, 2, 3) // 会直接报错

函数的行参只有一个的时候可以不写 () 其余情况必须写

const obj = {
  fn: () => {
    console.log('没有参数,必须写小括号')
  },
  fn2: a => {
    console.log('一个行参,可以不写小括号')
  },
  fn3: (a, b) => {
    console.log('两个或两个以上参数,必须写小括号')
  }
}

函数体只有一行代码的时候,可以不写 {} ,并且会自动 return

const obj = {
  fn: a => {
    return a + 10
  },
  fun: a => a + 10
}

console.log(fn(10)) // 20
console.log(fun(10)) // 20

this 关键字

每一个函数内部都有一个关键字是 this,可以让我们直接使用的

  • 重点: 函数内部的 this 只和函数的调用方式有关系,和函数的定义方式没有关系

函数内部的 this 指向谁,取决于函数的调用方式

  • 全局定义的函数直接调用,this => window
    function fn() {
      console.log(this)
    }
    fn()
    // 此时 this 指向 window
  • 对象内部的方法调用,this => 调用者
    var obj = {
      fn: function () {
        console.log(this)
      }
    }
    obj.fn()
    // 此时 this 指向 obj
  • 定时器的处理函数,this => window
    setTimeout(function () {
      console.log(this)
    }, 0)
    // 此时定时器处理函数里面的 this 指向 window
  • 事件处理函数,this => 事件源
    div.onclick = function () {
      console.log(this)
    }
    // 当你点击 div 的时候,this 指向 div
  • 自调用函数,this => window
    (function () {
      console.log(this)
    })()
    // 此时 this 指向 window

call 和 apply 和 bind

  • 刚才我们说过的都是函数的基本调用方式里面的 this 指向
  • 我们还有三个可以忽略函数本身的 this 指向转而指向别的地方
  • 这三个方法就是 call / apply / bind
  • 是强行改变 this 指向的方法
call
  • call 方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向

  • 语法: 函数名.call(要改变的 this 指向,要给函数传递的参数1,要给函数传递的参数2, ...)

    var obj = { name: 'Jack' }
    function fn(a, b) {
      console.log(this)
      console.log(a)
      console.log(b)
    }
    fn(1, 2)
    fn.call(obj, 1, 2)
    
    • fn() 的时候,函数内部的 this 指向 window
    • fn.call(obj, 1, 2) 的时候,函数内部的 this 就指向了 obj 这个对象
    • 使用 call 方法的时候
      • 会立即执行函数
      • 第一个参数是你要改变的函数内部的 this 指向
      • 第二个参数开始,依次是向函数传递参数
apply
  • apply 方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向

  • 语法: 函数名.apply(要改变的 this 指向,[要给函数传递的参数1, 要给函数传递的参数2, ...])

    var obj = { name: 'Jack' }
    function fn(a, b) {
      console.log(this)
      console.log(a)
      console.log(b)
    }
    fn(1, 2)
    fn.call(obj, [1, 2])
    
    • fn() 的时候,函数内部的 this 指向 window
    • fn.apply(obj, [1, 2]) 的时候,函数内部的 this 就指向了 obj 这个对象
    • 使用 apply 方法的时候
      • 会立即执行函数
      • 第一个参数是你要改变的函数内部的 this 指向
      • 第二个参数是一个 数组,数组里面的每一项依次是向函数传递的参数
bind
  • bind 方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向

  • 和 call / apply 有一些不一样,就是不会立即执行函数,而是返回一个已经改变了 this 指向的函数

  • 语法: var newFn = 函数名.bind(要改变的 this 指向); newFn(传递参数)

    var obj = { name: 'Jack' }
    function fn(a, b) {
      console.log(this)
      console.log(a)
      console.log(b)
    }
    fn(1, 2)
    var newFn = fn.bind(obj)
    newFn(1, 2)
    
    • bind 调用的时候,不会执行 fn 这个函数,而是返回一个新的函数
    • 这个新的函数就是一个改变了 this 指向以后的 fn 函数
    • fn(1, 2) 的时候 this 指向 window
    • newFn(1, 2) 的时候执行的是一个和 fn 一摸一样的函数,只不过里面的 this 指向改成了 obj

十二、对象object

概念:

对象:可以储存相对复杂的数据,{}

对象中每一条数据都有自己的名字,称为 ,数据本身称为,在对象中保存的数据都是以键值对的方式来保存的,对象中的数据时无序的。

创建对象

  1. 字面量法(常用)
//字面量法
var student = {
    'name': 'kunkun',
    'age': 18,
    gender: 'man'
}

console.log(student)
  1. 构造函数法
//构造函数法
var obj  = new Object()
obj.name = 'lisi'

console.log(obj)

对象中的数据

var stu = {1:1,
    键2:2,
    键3:3...
}

注意:

  1. 对象的键 是一个字符串类型,但是在js对象中可以省去这个引号
  2. js对象的值 可以是任意类型的数据类型
var student = {
    'name': 'kunkun',
    'age': 18,
    gender: 'man',
    hobby:['唱','跳','rap','ball'],
    isTrue:true,
}

操作对象:增删查改

var student = {
    'name': 'kunkun',
    'age': 18,
    gender: 'man',
    hobby:['唱','跳','rap','ball'],
    isTrue:true,
}
//点运算

//访问对象数据
console.log(student.name)
// console.log(student.age)

//修改对象数据
student.age = 25
// console.log(student)

//新增
student.id = 1
// console.log(student)

//删除
delete student.isTrue
console.log(student)

通过[]去访问对象

基础语法:

student['键名']

通常来说,如果访问的键名是一个变量来保存的,这时候才使用[]

var str = 'name'
student[str]

//通过变量去设置键名
var str2 = 'sex'

var student2 = {
    'name': 'kunkun',
    'age': 18,
    [str2]: 'man',
    hobby:['唱','跳','rap','ball'],
    isTrue:true,
}

console.log(student2)

对象的属性和方法

因为对象中可以保存任意数据类型,所以也可以是一个函数,我们就可以根据数据是不是函数来决定,数据的分类:

  1. 对象的属性
  2. 对象的方法
var student = {
    'name': 'kunkun',
    'age': 18,
    gender: 'man',
    hobby:['唱','跳','rap','ball'],
    isTrue:true,
    study:function(){
        console.log('来凡云上网课')
    },
    sleep:function(str){
        console.log( str + '睡')
    }
}

student.study()
student.sleep('上班')

拓展:

遍历对象(了解)

  1. for in
for(var key in student){
    // console.log(key)
    console.log(student[key])
}
  1. Object.keys()
Object.keys(student).forEach(function(key){
     console.log(key,student[key])
})

对象的简写 ES6提供的方式

  1. 对属性的简写
var stuName = 'kunkun'
var age = 18
var gender = 'man'

var student = {
    stuName,
    age,
    gender,
    hobby:['唱','跳','rap','ball'],
    isTrue:true,
}

console.log(student)
  1. 对象中方法的简写:
var student = {
    study(){
        console.log('来凡云上网课')
    },
    sleep(str){
        console.log( str + '睡')
    }
}
student.study()
student.sleep('放学')

对象中的this(了解)

this是存在于函数中的动态指针,指代当前调用的对象,this的目的是在函数内部指代这个函数的运行环境

  • 在自建创建的对象中的方法如果是使用了this,this就指向这个创建的对象
  • 是在全局作用域中创建的函数,这里的this就执行window

特殊:箭头函数没有this,如果非得在箭头函数中使用this,这个this指向所在父级的作用域上,就指向window

对象分类:

JS中万物皆对象。

宿主对象 host object:

  • js的运行环境决定了js的宿主对象是谁。浏览器提供:window对象

原生对象:

  • 自定义对象:js运行的时候创建的对象——自己编写的。
  • 内部对象:由js语言规范(ECMA)提供的对象,不是宿主对象,也不是自己写的
    • 本地对象 native object:通过new创建的对象:Array、Object、Date、RegExp…
    • 内置对象 build-in object:不需要new:Math、JSON、Global…

Array:数组对象、通过数组对象上的属性和方法操作数组

数组属性

属性描述
length设置或返回数组元素的个数。

数组方法

方法描述
concat()连接两个或更多的数组,并返回结果。
every()检测数值元素的每个元素是否都符合条件。
filter()检测数值元素,并返回符合条件所有元素的数组。
find()返回符合传入测试(函数)条件的数组元素。
findIndex()返回符合传入测试(函数)条件的数组元素索引。
forEach()数组每个元素都执行一次回调函数。
includes()判断一个数组是否包含一个指定的值。
indexOf()搜索数组中的元素,并返回它所在的位置。
isArray()判断对象是否为数组。
join()把数组的所有元素放入一个字符串。
keys()返回数组的可迭代对象,包含原始数组的键(key)。
map()通过指定函数处理数组的每个元素,并返回处理后的数组。
pop()删除数组的最后一个元素并返回删除的元素。
push()向数组的末尾添加一个或更多元素,并返回新的长度。
reduce()将数组元素计算为一个值(从左到右)。
reverse()反转数组的元素顺序。
shift()删除并返回数组的第一个元素。
slice()选取数组的一部分,并返回一个新数组。
some()检测数组元素中是否有元素符合指定条件。
sort()对数组的元素进行排序。
splice()从数组中添加或删除元素。
toString()把数组转换为字符串,并返回结果。
unshift()向数组的开头添加一个或更多元素,并返回新的长度。

十三、String:字符串

字符串的常用方法

  • 我们操作字符串,也有一堆的方法来帮助我们操作
  • 字符串和数组有一个一样的地方,也是按照索引来排列的

charAt

  • charAt(索引) 是找到字符串中指定索引位置的内容返回

    var str = 'Jack'
    
    // 使用 charAt 找到字符串中的某一个内容
    var index = str.charAt(2)
    
    console.log(index) // c
    
    • 因为字符串也是按照索引进行排列的,也是同样从 0 开始
    • 所以索引 2 的位置就是 c
  • 如果没有对应的索引,那么就会返回 空字符串

    var str = 'Jack'
    
    // 使用 charAt 找到字符串中的某一个内容
    var index = str.charAt(10)
    
    console.log(index) // ''
    
    • 这个字符串根本没有索引 10 的位置
    • 所以就会返回一个空字符串 ''

charCodeAt

  • charCodeAt(索引) 就是返回对应索引位置的 unicode 编码

    var str = 'Jack'
    
    // 使用 charAt 找到字符串中的某一个内容
    var index = str.charCodeAt(0)
    
    console.log(index) // 74
    
    • 因为 Junicode 对照表里面存储的是 74,所以就会返回 74

indexOf

  • indexOf 就是按照字符找到对应的索引

    var str = 'Jack'
    
    // 使用 indexOf 找到对应的索引
    var index = str.indexOf('J')
    
    console.log(index) // 0
    
    • 因为字符 J 在字符串 Jack 中的索引位置是 0
    • 所以会返回 0

substring

  • substring 是用来截取字符串使用的

  • 语法: substring(从哪个索引开始,到哪个索引截止),包含开始索引,不包含结束索引

    var str = 'hello'
    //         01234
    
    // 使用 substring 截取字符串
    var newStr = str.substring(1, 3)
    
    console.log(newStr) // el
    
    • 从索引 1 开始,到索引 3 截止,包含前面的索引不包含后面的索引
    • 所以返回的是 el

substr

  • substr 也是用来截取字符串的

  • 语法:substr(从哪个索引开始,截取多少个)

    var str = 'hello'
    //         01234
    
    // 使用 substr 截取字符串
    var newStr = str.substr(1, 3)
    
    console.log(newStr) // ell
    
    • 这个方法和 substring 不一样的是,第二个参数是截取多少个
    • 从索引 1 开始,截取 3 个,所以得到的是 ell

toLowerCase 和 toUpperCase

  • 这两个方法分别使用用来给字符串转成 小写字母大写字母

    var str = hello
    
    // 使用 toUpperCase 转换成大写
    var upper = str.toUpperCase()
    
    console.log(upper) // HELLO
    
    // 使用 toLowerCase 转换成小写
    var lower = upper.toLowerCase()
    
    console.log(lower) // hello
    

十四、Math:数学

  • Math 是 js 的一个内置对象,提供了一堆的方法帮助我们操作 数字

  • 没有什么多余的东西,就是一堆的方法来操作数字

random:生成随机数

  • Math.random() 这个方法是用来生成一个 0 ~ 1 之间的随机数

  • 每次执行生成的数字都不一样,但是一定是 0 ~ 1 之间的

  • 生成的数字包含 0 ,但是不包含 1

    var num = Math.random()
    console.log(num) // 得到一个随机数
    

round:生成整数

  • Math.round() 是将一个小数 四舍五入 变成一个整数

    var num = 10.1
    console.log(Math.round(num)) // 10
    
    var num2 = 10.6
    console.log(Math.round(num2)) // 11
    

abs:生成绝对值

  • Math.abs() 是返回一个数字的 绝对值

    var num = -10
    console.log(math.abs(num)) // 10
    

ceil:向上取整

  • Math.ceil() 是将一个小数 向上取整 得到的整数

    var num = 10.1
    console.log(Math.ceil(num)) // 11
    
    var num2 = 10.9
    console.log(Math.ceil(num2)) // 11
    

floor:向下取整

  • Math.floor() 是将一个小数 向下取整 的到的整数

    var num = 10.1
    console.log(Math.floor(num)) // 10
    
    var num2 = 10.9
    console.log(Math.floor(num2)) // 10
    

max:找最大值

  • Math.max() 得到的是你传入的几个数字之中 最大 的那个数字

    console.log(Math.max(1, 2, 3, 4, 5)) // 5
    

min:找最小值

  • Math.min() 得到的是你传入的几个数字之中 最小 的那个数字

    console.log(Math.min(1, 2, 3, 4, 5)) // 1
    

PI

  • Math.PI 得到的是 π 的值,也就是 3.1415936...

    console.log(Math.PI) // 3.141592653589793
    
    • 因为计算机的计算精度问题,只能得到小数点后 15 位
    • 使用 Math.PI 的时候,是不需要加 () 的

数字转换进制

  1. toString() 方法可以在数字转成字符串的时候给出一个进制数

    • 语法: toString(你要转换的进制)

      var num = 100
      console.log(num.toString(2)) // 1100100
      console.log(num.toString(8)) // 144
      console.log(num.toString(16)) // 64
      
  2. parseInt() 方法可以在字符串转成数字的时候把字符串当成多少进制转成十进制

    • 语法: parseInt(要转换的字符串,当作几进制来转换)

      var str = 100
      console.log(parseInt(str, 8)) // 64 把 100 当作一个 八进制 的数字转换成 十进制 以后得到的
      console.log(parseInt(str, 16)) // 256 把 100 当作 十六进制 的数字转换成 十进制 以后得到的
      console.log(parseInt(str, 2)) // 4 把 100 当作 二进制 的数字转换成 十进制 以后得到的
      

十五、Date:日期

  • js 提供的内置构造函数,专门用来获取时间的

new Date()

  • new Date() 在不传递参数的情况下是默认返回当前时间

    var time = new Date()
    console.log(time) // 当前时间 Fri Mar 01 2019 13:11:23 GMT+0800 (中国标准时间)
    
  • new Date() 在传入参数的时候,可以获取到一个你传递进去的时间

    var time = new Date('2019-03-03 13:11:11')
    console.log(time) // Sun Mar 03 2019 13:11:11 GMT+0800 (中国标准时间)
    
  • new Date() 传递的参数有多种情况

    1. 传递两个数字,第一个表示年,第二个表示月份

      var time = new Date(2019, 00) // 月份从 0 开始计数,0 表示 1月,11 表示 12月
      console.log(time) // Tue Jan 01 2019 00:00:00 GMT+0800 (中国标准时间)
      
    2. 传递三个数字,前两个不变,第三个表示该月份的第几天,从 1 到 31

      var time = new Date(2019, 00, 05) 
      console.log(time) // Sat Jan 05 2019 00:00:00 GMT+0800 (中国标准时间)
      
    3. 传递四个数字,前三个不变,第四个表示当天的几点,从 0 到 23

      var time = new Date(2019, 00, 05, 22) 
      console.log(time) // Sat Jan 05 2019 22:00:00 GMT+0800 (中国标准时间)
      
    4. 传递五个数字,前四个不变,第五个表示的是该小时的多少分钟,从 0 到 59

      var time = new Date(2019, 00, 05, 22, 33) 
      console.log(time) // Sat Jan 05 2019 22:33:00 GMT+0800 (中国标准时间)
      
    5. 传递六个数字,前五个不变,第六个表示该分钟的多少秒,从 0 到 59

      var time = new Date(2019, 00, 05, 22, 33, 55) 
      console.log(time) // Sat Jan 05 2019 22:33:55 GMT+0800 (中国标准时间)
      
    6. 传入字符串的形式

      console.log(new Date('2019')) 
      // Tue Jan 01 2019 08:00:00 GMT+0800 (中国标准时间)
      console.log(new Date('2019-02')) 
      // Fri Feb 01 2019 08:00:00 GMT+0800 (中国标准时间)
      console.log(new Date('2019-02-03')) 
      // Sun Feb 03 2019 08:00:00 GMT+0800 (中国标准时间)
      console.log(new Date('2019-02-03 13:')) 
      // Sun Feb 03 2019 13:00:00 GMT+0800 (中国标准时间)
      console.log(new Date('2019-02-03 13:13:')) 
      // Sun Feb 03 2019 13:13:00 GMT+0800 (中国标准时间)
      console.log(new Date('2019-02-03 13:13:13')) 
      // Sun Feb 03 2019 13:13:13 GMT+0800 (中国标准时间)
      

将日期字符串格式化成指定内容

  • 比如我们得到的时间字符串是 Sun Feb 03 2019 13:13:13 GMT+0800 (中国标准时间)
  • 我指向得到这个日期中是那一年,我们就要靠截取字符串的形式得到
  • 但是现在 js 为我们提供了一系列的方法来得到里面的指定内容

getFullYear

  • getFullYear() 方式是得到指定字符串中的哪一年

    var time = new Date(2019, 03, 03, 08, 00, 22)
    console.log(time.getFullYear()) // 2019
    

getMonth

  • getMonth() 方法是得到指定字符串中的哪一个月份

    var time = new Date(2019, 03, 03, 08, 00, 22)
    console.log(time.getMonth()) // 3
    
    • 这里要有一个注意的地方
    • 月份是从 0 开始数的
    • 0 表示 1月,1 表示 2月,依此类推

getDate

  • getDate() 方法是得到指定字符串中的哪一天

    var time = new Date(2019, 03, 03, 08, 00, 22)
    console.log(time.getDate()) // 3
    

getHours

  • getHours() 方法是得到指定字符串中的哪小时

    var time = new Date(2019, 03, 03, 08, 00, 22)
    console.log(time.getHours()) // 8
    

getMinutes

  • getMinutes() 方法是得到指定字符串中的哪分钟

    var time = new Date(2019, 03, 03, 08, 00, 22)
    console.log(time.getMinutes()) // 0
    

getSeconds

  • getSeconds() 方法是得到指定字符串中的哪秒钟

    var time = new Date(2019, 03, 03, 08, 00, 22)
    console.log(time.getSeconds()) // 22
    

getDay

  • getDay() 方法是得到指定字符串当前日期是一周中的第几天(周日是 0,周六是 6)

    var time = new Date(2019, 03, 08, 08, 00, 22)
    console.log(time.getDay()) // 1
    

getTime

  • getTime() 方法是得到执行时间到 格林威治时间 的毫秒数

    var time = new Date(2019, 03, 08, 08, 00, 22)
    console.log(time.getTime()) // 1554681622000
    

获取时间差

  • 是指获取两个时间点之间相差的时间
  • 在 js 中是不能用时间直接做 减法 的
  • 我们需要一些特殊的操作
  • 在编程的世界里面,有一个特殊的时间,是 1970年01月01日00时00分00秒
  • 这个时间我们叫做 格林威治时间
  • 所有的编程世界里面,这个时间都是一样的,而且 格林威治时间 的数字是 0
  • 格林威治时间 开始,每经过1毫秒,数字就会 + 1
  • 所以我们可以获取到任意一个时间节点到 格林威治时间 的毫秒数
  • 然后在用两个毫秒数相减,就能得到两个时间点之间相差的毫秒数
  • 我们在通过这个毫秒数得到准确的时间

计算时间差

  • 例如:我们现在计算一下 2019-01-01 00:00:002019-01-03 04:55:34 的时间差
  1. 先获取两个时间点到 格林威治时间 的毫秒数

    var time1 = new Date('2019-01-01 00:00:00')
    var time2 = new Date('2019-01-03 04:55:34')
    
    time1 = time1.getTime()
    time2 = time2.getTime()
    
    console.log(time1) // 1546272000000
    console.log(time2) // 1546462534000
    
  2. 两个时间相减,得到两个时间点之间相差的毫秒数

    var differenceTime = time2 - time1
    console.log(differenceTime) // 190534000
    
    • 现在我们计算出了两个时间点之间相差的毫秒数
  3. 把我们计算的毫秒数换算成时间

    • 先计算出有多少天

    • 以为一天是 1000 * 60 * 60 * 24 毫秒

    • 用总的毫秒数除以一天的毫秒数,就能得到多少天了

      var time1 = new Date('2019-01-01 00:00:00')
      var time2 = new Date('2019-01-03 04:55:34')
      time1 = time1.getTime()
      time2 = time2.getTime()
      var differenceTime = time2 - time1
      
      // 计算整的天数
      var day = differenceTime / (1000 * 60 * 60 * 24) // 2.20525462962963
      day = Math.ceil(day) // 2
      
      • 因为得到的是有小数的天数,我们向下取整,得到有多少个整的天数
    • 使用 differenceTime 减去两天所包含的毫秒数,剩下的就是不够一天的毫秒数

    • 用不够一天的毫秒数计算出有多少个小时

    • 因为一个小时是 1000 * 60 * 60 毫秒

    • 用不够一天的毫秒数除以一小时的毫秒数,就能得到多少小时了

      var time1 = new Date('2019-01-01 00:00:00')
      var time2 = new Date('2019-01-03 04:55:34')
      time1 = time1.getTime()
      time2 = time2.getTime()
      var differenceTime = time2 - time1
      
      // 计算整的天数
      var day = differenceTime / (1000 * 60 * 60 * 24) // 2.20525462962963
      day = Math.floor(day) // 2
      
      // 计算整的小时数
      var afterHours = differenceTime - (1000 * 60 * 60 * 24 * 2)
      var hours = afterHours / (1000 * 60 * 60)
      hours = Math.floor(hours) // 4
      
      • 和刚才一样的道理,我们需要向下取整
    • 同理,使用 afterHours - 4个小时包含的毫秒数,剩下的就是不够一个小时的毫秒数

    • 用不够一个小时的毫秒数计算出有多少分钟

    • 因为一分钟是 1000 * 60 毫秒

    • 用不够一个小时的毫秒数除以一分钟的毫秒数就能得到多少分钟了

      var time1 = new Date('2019-01-01 00:00:00')
      var time2 = new Date('2019-01-03 04:55:34')
      time1 = time1.getTime()
      time2 = time2.getTime()
      var differenceTime = time2 - time1
      
      // 计算整的天数
      var day = differenceTime / (1000 * 60 * 60 * 24) // 2.20525462962963
      day = Math.floor(day) // 2
      
      // 计算整的小时数
      var afterHours = differenceTime - (1000 * 60 * 60 * 24 * 2)
      var hours = afterHours / (1000 * 60 * 60)
      hours = Math.floor(hours) // 4
      
      // 计算整分钟数
      var afterMinutes = afterHours - (1000 * 60 * 60 * 4)
      var minutes = afterMinutes / (1000 * 60)
      minutes = Math.floor(minutes) // 55
      
    • 和之前一样的道理计算出秒

      var time1 = new Date('2019-01-01 00:00:00')
      var time2 = new Date('2019-01-03 04:55:34')
      time1 = time1.getTime()
      time2 = time2.getTime()
      var differenceTime = time2 - time1
      
      // 计算整的天数
      var day = differenceTime / (1000 * 60 * 60 * 24) // 2.20525462962963
      day = Math.floor(day) // 2
      
      // 计算整的小时数
      var afterHours = differenceTime - (1000 * 60 * 60 * 24 * 2)
      var hours = afterHours / (1000 * 60 * 60)
      hours = Math.floor(hours) // 4
      
      // 计算整分钟数
      var afterMinutes = afterHours - (1000 * 60 * 60 * 4)
      var minutes = afterMinutes / (1000 * 60)
      minutes = Math.floor(minutes) // 55
      
      // 计算整秒数
      var afterSeconds = afterMinutes - (1000 * 60 * 55)
      var seconds = afterSeconds / 1000
      seconds = Math.floor(seconds) // 34
      
    • 最后,同理减去整秒的数,剩下的就是毫秒数

      var time1 = new Date('2019-01-01 00:00:00')
      var time2 = new Date('2019-01-03 04:55:34')
      time1 = time1.getTime()
      time2 = time2.getTime()
      var differenceTime = time2 - time1
      
      // 计算整的天数
      var day = differenceTime / (1000 * 60 * 60 * 24) // 2.20525462962963
      day = Math.floor(day) // 2
      
      // 计算整的小时数
      var afterHours = differenceTime - (1000 * 60 * 60 * 24 * 2)
      var hours = afterHours / (1000 * 60 * 60)
      hours = Math.floor(hours) // 4
      
      // 计算整分钟数
      var afterMinutes = afterHours - (1000 * 60 * 60 * 4)
      var minutes = afterMinutes / (1000 * 60)
      minutes = Math.floor(minutes) // 55
      
      // 计算整秒数
      var afterSeconds = afterMinutes - (1000 * 60 * 55)
      var seconds = afterSeconds / 1000
      seconds = Math.floor(seconds) // 34
      
      // 计算毫秒数
      var milliSeconds = afterSeconds - (1000 * 34) // 0
      
    • 最后我们把结果输出一下就可以了

      document.write('2019-01-01 00:00:00 和 2019-01-03 04:55:34 之间相差')
      document.write(day + '天' + hours + '小时' + minutes + '分钟' + seconds + '秒' + milliSeconds + '毫秒')
      

十六、定时器

  • js 里面,有两种定时器,倒计时定时器间隔定时器

倒计时定时器

  • 倒计时多少时间以后执行函数

  • 语法: setTimeout(要执行的函数,多长时间以后执行)

  • 会在你设定的时间以后,执行函数

    var timerId = setTimeout(function () {
      console.log('我执行了')
    }, 1000)
    console.log(timerId) // 1
    
    • 时间是按照毫秒进行计算的,1000 毫秒就是 1秒钟
    • 所以会在页面打开 1 秒钟以后执行函数
    • 只执行一次,就不在执行了
    • 返回值是,当前这个定时器是页面中的第几个定时器

间隔定时器

  • 每间隔多少时间就执行一次函数

  • 语法: setInterval(要执行的函数,间隔多少时间)

    var timerId = setInterval(function () {
      console.log('我执行了')
    }, 1000)
    
    • 时间和刚才一样,是按照毫秒进行计算的
    • 每间隔 1 秒钟执行一次函数
    • 只要不关闭,会一直执行
    • 返回值是,当前这个定时器是页面中的第几个定时器

定时器的返回值

  • 设置定时器的时候,他的返回值是部分 setTimeoutsetInterval

  • 只要有一个定时器,那么就是一个数字

    var timerId = setTimeout(function () {
      console.log('倒计时定时器')
    }, 1000)
    
    var timerId2 = setInterval(function () {
      console.log('间隔定时器')
    }, 1000)
    
    console.log(timerId) // 1
    console.log(timerId2) // 2
    

关闭定时器

  • 我们刚才提到过一个 timerId,是表示这个定时器是页面上的第几个定时器

  • 这个 timerId 就是用来关闭定时器的数字

  • 我们有两个方法来关闭定时器 clearTimeoutclearInterval

    var timerId = setTimeout(function () {
      console.log('倒计时定时器')
    }, 1000)
    clearTimeout(timerId)
    
    • 关闭以后,定时器就不会在执行了
    var timerId2 = setInterval(function () {
      console.log('间隔定时器')
    }, 1000)
    clearInterval(timerId2)
    
    • 关闭以后定时器就不会在执行了
  • 原则上是

    • clearTimeout 关闭 setTimeout
    • clearInterval 关闭 setInterval
  • 但是其实是可以通用的,他们可以混着使用

    var timerId = setTimeout(function () {
      console.log('倒计时定时器')
    }, 1000)
    // 关闭倒计时定时器
    clearInterval(timerId)
    
    var timerId2 = setInterval(function () {
      console.log('间隔定时器')
    }, 1000)
    // 关闭间隔定时器
    clearTimeout(timerId2)
    

十七、BOM和DOM

BOM

  • BOM(Browser Object Model): 浏览器对象模型
  • 其实就是操作浏览器的一些能力
  • 我们可以操作哪些内容
    • 获取一些浏览器的相关信息(窗口的大小)
    • 操作浏览器进行页面跳转
    • 获取当前浏览器地址栏的信息
    • 操作浏览器的滚动条
    • 浏览器的信息(浏览器的版本)
    • 让浏览器出现一个弹出框(alert / confirm / prompt
  • BOM 的核心就是 window 对象
  • window 是浏览器内置的一个对象,里面包含着操作浏览器的方法

window对象

  • 属性
属性名说明
innerWidth/innerHeight浏览器视口(文档)宽高 (包含滚动条的)
outerWidth/outerHeight整个浏览器外边框的宽高
screenLeft/screenTop浏览器距离屏幕左边和上边的间距
  • 方法
方法名说明
alert()打开一个警告弹框
prompt()打开一个可输入的弹框
open()跳转页面、默认就是打开新窗口、通过第二个参数可以控制打开方式
confirm在浏览器弹出一个询问框

window四大内置对象

location :url地址栏对象
  • 属性:
属性说明
href获取到浏览器地址栏里的路径,对这个href重新赋值可以达到跳转页面的效果
search获取url地址栏?后面的数据的
  • 方法
方法说明
assign()跳转页面的方法,保留历史记录
replace()跳转页面的方法,不保留历史记录
reload()刷新页面
history:历史记录对象
  • 属性
属性名说明
length获取当前浏览器窗口的历史记录条数
console.log(history.length);
  • 方法
方法名说明
forward()前进一步
back()后退一步
go(n)传入的数字来决定是前进还是后退并指定步数
history.go(2);前进2步
history.go(-1);后退一步
history.forward():前进一步
history.back();后退一步
screen:屏幕对象
navigator:浏览器信息对象
属性说明
userAgent获取浏览器版本信息

浏览器卷去的尺寸

卷去的高度

语法:
document.documentElement.scrollTop
当 html 页面有 DOCTYPE 标签的时候能获取到尺寸

document.body.scrollTop
当 html 页面没有 DOCTYPE 标签的时候能获取到尺寸

兼容:
利用 或(||) 运算符进行兼容
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop

卷去的宽度

语法:
document.documentElement.scrollLeft
当 html 页面有 DOCTYPE 标签的时候能获取到尺寸
document.body.scrollLeft
当 html 页面没有 DOCTYPE 标签的时候能获取到尺寸
兼容:
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft

浏览器滚动到

浏览器滚动到
专门用来进行浏览器滚动条定位(定位卷去的尺寸)

语法: window.scrollTo()

  • 参数方案1: 传递数字

语法: window.scrollTo(x, y)

   x 表示横向卷去的尺寸

   y 表示纵向卷去的尺寸

 注意: 必须传递两个数字, 一个报错

 注意: 只能进行瞬间定位
  • 参数方案2: 传递对象

语法: window.scrollTo({ left: yyy, top: xxx })

 可以选择一个方向填写

 对象内可以填写 `behavior` 属性, 值填写 `'smooth'` 属性来表示平滑滚动

获取浏览器窗口尺寸

我们之前学过一个 innerWidthinnerHeight
他们获取到的是窗口包含滚动条的尺寸
下面我们学习两个不包含滚动条的尺寸获取方式

document.documentElement.clientWidth : 可视窗口的宽度
document.documentElement.clientHeight : 可视窗口的高度

DOM

DOM(Document Object Model): 文档对象模型
其实就是操作 html 中的标签的一些能力
我们可以操作哪些内容

  • 获取一个元素
  • 移除一个元素
  • 创建一个元素
  • 向页面里面添加一个元素
  • 给元素绑定一些事件
  • 获取元素的属性
  • 给元素添加一些 css 样式

  • DOM 的核心对象就是 docuemnt 对象
    document 对象是浏览器内置的一个对象,里面存储着专门用来操作元素的各种方法
    DOM: 页面中的标签,我们通过 js 获取到以后,就把这个对象叫做 DOM 对象

DOM树

DOM HTML 树

通过dom来操作html中的所有节点:增、删、查、改

在DOM中,节点包含了:html标签、标签上的属性、标签包裹的文本…node节点

获取元素

Document对象
属性
属性名说明
body获取body标签节点
title获取title标签里的文本节点
URL获取URL地址
documentElement获取html标签节点
获取html标签节点的方法
方法名语法说明返回值
getElementById()document.getElementById(id名)通过 id 获取元素元素节点
getElementsByClassName()document.getElementsByClassName(class名)通过 class 获取元素数组(元素节点)
getElementsByTagName()document.getElementsByTagName(标签名)通过标签名获取元素数组(元素节点)
querySelector()document.querySelector(css选择器)通过 CSS 选择器获取到第一个匹配的元素元素节点
querySelectorAll()document.querySelectorAll(css选择器)通过 CSS 选择器获取到所有匹配的元素数组(元素节点)
createElement()document.createElement(标签名)创建html节点元素节点
createTextNode()document.createTextNode(标签名)创建文本节点文本节点
write()document.write(字符串)向页面输出内容,可以是一个html节点undefined
element对象
获取关系节点的属性:
属性名说明
parentElement获取父节点
firstElementChild获取第一个子节点
lastElementChild获取最后一个子节点
children获取所有的子节点
previousElementSibling获取前一个兄弟节点
nextElementSibling获取后一个兄弟节点
操作元素里的内容:
属性名说明
innerText获取、新增、修改、删除(修改为空)指定标签里的文本节点
innerHTML获取、新增、修改、删除(修改为空)指定标签里的标签及文本节点
value获取、新增、修改、删除(修改为空)指定表单元素里的value值
操作元素上的属性节点
属性名说明
className获取、新增、修改、删除(修改为空)指定元素的class属性
style获取、新增、修改、删除(修改为空)指定元素的style样式属性
标签属性名获取、新增、修改、删除(修改为空)指定元素的属性节点:href、src、title、id、type....
checked获取、修改 input选框 是否选中
classList每一个元素身上天生自带一个属性叫做 classList, 记录了该元素的所有类名
<div class="a b c d e f g">hello world</div>

// classList
console.log(ele.classList)
// 1. 追加
ele.classList.add('box')
// 2. 删除
ele.classList.remove('d')
// 3. 切换
// 执行切换类名操作
ele.classList.toggle('active')

元素盒模型相关的属性
属性名说明
clientWidth、clientHeight盒子元素的宽高——只包含内边距和内容区
offsetWidthoffsetHeight盒子元素的宽高——包含内边距、内容区、边框
offsetLeft、offsetTop盒子距离整个html的左边和上边的间距
clientLeft、clientTop盒子边框的宽度
  • 注意:
    获取到的尺寸是没有单位的数字
    当元素在页面中不占位置的时候, 获取到的是 0
    display: none; 元素在页面不占位
    visibility: hidden; 元素在页面占位
操作元素节点的方法:添加和删除
方法名说明
父节点.appendChild(子节点)向父节点的最后添加新节点
父节点.insertBefore(新节点,旧节点)向父节点中的某一个旧节点前添加新节点
父节点.removeChild(指定节点)从父节点中删除一个指定节点
操作元素节点的新方法:(ie全系不兼容)
方法名语法说明
append()节点.append(子节点)往指定父节点的末尾添加一个子节点
prepend()节点.prepend(子节点)往指定父节点的开头添加一个子节点
before()节点.before(兄弟节点)往指定节点的前面添加一个兄弟节点
after()节点.after(兄弟节点)往指定节点的后面添加一个兄弟节点
remove()节点.remove()删除该节点
replaceWith()节点.replaceWith(新节点)用新节点替换指定的节点
操作元素的属性节点:
方法说明
节点对象.getAttribute(属性名)获取元素的指定属性的值
节点对象.setAttribute(属性名,属性值)设置元素一个属性和属性值
节点对象.removeAttribute(属性名)移出元素上的一个指定属性
节点对象.hasAttribute(属性名)判断元素有没有该指定属性,返回布尔值

自定义属性:data-属性名

我们渲染列表,点击列表,可以跳转对应项目的详情,实际上我们可以通过渲染的时候,把id之类的值通过data-id绑定到模板标签上,
再通过js的dataset.id去获取该属性,再去做相应的跳转逻辑。
以前我们绑定一些数据的时候,通常都会使用隐藏的输入框去做。
有了data-*,我们再也不用通过一个hidden的input的value去绑定数据,减少了页面的元素数量,简化dom结构,提升渲染性能.

十八、事件

概念:

事件是指用户与页面之间交互做出的一系列反应,比如:点击元素、鼠标移入移出、滚轮滑动…

DOM中的事件,指的是给元素设置事件的监听。因为事件其实是一直存在的,只是后续没有监听和其他的操作。

其实添加事件监听的意思就是,监听用户做出的动作,然后对这个动作设置一些反应。

例如:

var btn = document.getElementById('btn')
btn.onclick = function(){
    console.log('btn被点击了')
}

上述的function不需要手动去设置调用,当浏览器检测到用户点击了这个按钮时,就会触发这个函数,执行函数里的代码

事件的三要素

  1. 事件源:实际触发事件的元素
  2. 事件类型:点击、鼠标移入、键盘按下…
  3. 事件处理函数:事件发生后的一些操作

——事件处理程序

事件处理程序

HTML事件处理程序(非标准DOM0)

<button id="btn" onclick="console.log('按钮被点击了')">按钮</button>
<button id="btn" onclick="clickBtn()">按钮</button>
function clickBtn(){
    console.log('按钮被点击了')
}

缺点:html和js 代码没有分离,造成后期维护困难

DOM0级事件处理程序

var btn = document.getElementById('btn')
btn.onclick = function(){
    console.log('按钮被点击了1')
}

优点:html、js代码分离;兼容好ie全系兼容

缺点:没有办法绑定多个相同事件的监听

DOM2级事件处理程序

var btn = document.getElementById('btn')
btn.addEventListener('click',function(){
    console.log('按钮被点击了1')
})
btn.addEventListener('click',function(){
    console.log('按钮被点击了2')
})

优点:可以绑定多个相同时间监听,触发多个事件函数

缺点:ie8以下不兼容

移除事件监听

DOM0:

btn.onclick = null

DOM2:移出事件监听需要用removeEventListener方法来移除,移除的这个事件执行函数必须保证是统一个函数

var clickBtn = function(){
    console.log('按钮被点击了2')
}
btn.addEventListener('click',clickBtn)
btn.removeEventListener('click',clickBtn)

事件类型

UI事件
事件类型说明
load当页面html标签全部加载完毕之后才去执行事件监听里面的代码
window.onload = function(){
    console.log(123)
}
鼠标事件
事件类型说明
click鼠标单击事件
dblclick鼠标双击事件
mousedown鼠标左键按下事件
mouseup鼠标左键松开事件
contextmenu鼠标右键点击事件
mouseout鼠标移出
mousemove鼠标移动
mouseover鼠标移入事件
键盘事件
事件类型说明
keydown键盘中某个键被按下了
keyup键盘中某个键被按松开了
keypress键盘中某个键被按住了,按住后会一直触发
表单事件
事件类型说明
focus输入框获取到焦点的时候
blur输入框失去焦点时
change表单元素只要内容发生改变就可以触发事件
input输入框内容在输入时会触发

事件流

指的是事件的流向,分为了两种主要的事件流向:冒泡流、捕获流

冒泡流:事件冒泡(IE事件流)

事件会进行传递:从最具体的元素(div),一直向上进行冒泡传递,传递没有那么具体的元素(window),例如:
也就是从下向上的执行事件处理函数

<!DOCTYPE html> 
<html> 
    <head> 
        <title>事件流</title> 
    </head> 
    <body> 
        <div id="outer">
            <div id="inner"></div>
        </div> 
    </body> 
</html>

从inner开始 ——》outer——》body——》html——》document——》window

image-20220604160353392
捕获流:事件捕获

从没有那么具体的元素(window),依次传递到具体的元素上(div)
也就是从上向下的执行事件处理函数

image-20220604161503754
DOM事件流:同时包含冒泡和捕获

DOM2event 规范提出,事件流包含了3个阶段:捕获、目标阶段、冒泡

事件捕获是最先发生,然后实际触发事件的元素才执行(目标阶段),最后此案时冒泡阶段

image-20220604161904569
inner.addEventListener('click',function(){
    console.log('inner被点击了')
},true)
outer.addEventListener('click',function(){
    console.log('outer被点击了')
},true)
document.body.addEventListener('click',function(){
    console.log('body被点击了')
},true)
document.documentElement.addEventListener('click',function(){
    console.log('html被点击了')
},true)
document.addEventListener('click',function(){
    console.log('doc被点击了')
},true)
window.addEventListener('click',function(){
    console.log('win被点击了')
},true)

true:代表目标执行在捕获阶段执行的。(捕获)
false:代表目标执行在冒泡阶段执行的。(冒泡)

事件对象:event对象

属性
属性名说明
pageX、pageY获取鼠标相对于页面的位置
clientX、clientY获取鼠标相对于视口(文档)的位置
offsetX、offsetY获取鼠标相对于实际触发事件元素(event.targer)的位置(不包含边框)
keyCode获取键盘的对应的按键码
target获取用户操作的节点(实际触发事件的元素)
方法
方法说明
stopPropagation()阻止事件流的传播
preventDefault()阻止事件的默认行为
<a id="link" href="https://www.baidu.com">百度一下</a>
<script>
    var link = document.getElementById('link')
    link.addEventListener('click',function(event){
        //阻止事件的默认行为
        event.preventDefault()
        console.log(link.href)
    })
</script>


<div id="outer">
    <div id="inner"></div>
</div>
<script>
    var inner = document.getElementById('inner');
    var outer = document.getElementById('outer');

    inner.onclick = function(event){
        console.log('inner被点击了')
        // event.stopPropagation();
    }
    outer.onclick = function(){
        console.log('outer被点击了')
    }
    document.body.onclick = function(){
        console.log('body被点击了')
        //阻止事件流的传播
        event.stopPropagation();
    }
    document.documentElement.onclick = function(){
        console.log('html被点击了')
    }
</script>

事件委托:

利用了事件冒泡的特点,将事件绑定委托给所有要触发事件的节点的父节点。将事件绑定在父节点上

<ul id="hobby">
    <li>
        <button id="sing">我要唱歌</button>
    </li>
    <li>
        <button id="dance">我要跳舞</button>
    </li>
    <li>
        <button id="rap">我要rap</button>
    </li>
    <li>
        <button id="ball">我要篮球</button>
    </li>
</ul>
<script>
    //事件委托
    var hobby = document.getElementById('hobby')

    hobby.addEventListener('click',function(event){
        console.log(event.target.id,event.target.innerText)
        switch(event.target.id){
            case 'sing': console.log(event.target.innerText)
                break
        }
    })
</script>

应用场景:

  1. 当我们向将事件监听设置给多个同样类型的元素上时,就可以使用事件委托,委托给它们的父元素
  2. 当我们要将事件绑定给动态渲染的元素上时,就可以使用事件委托,委托给它们的已存在的父元素

十九、ES5和ES6

this 关键字

每一个函数内部都有一个关键字是 this, 可以让我们直接使用的

  • 重点: 函数内部的 this 只和函数的调用方式有关系,和函数的定义方式没有关系

  • 函数内部的 this 指向谁,取决于函数的调用方式

全局定义的函数直接调用,this => window

    function fn() {
      console.log(this)
    }
    fn()
    // 此时 this 指向 window

对象内部的方法调用,this => 调用者

    var obj = {
      fn: function () {
        console.log(this)
      }
    }
    obj.fn()
    // 此时 this 指向 obj

定时器的处理函数,this => window

    setTimeout(function () {
      console.log(this)
    }, 0)
    // 此时定时器处理函数里面的 this 指向 window

事件处理函数,this => 事件源

    div.onclick = function () {
      console.log(this)
    }
    // 当你点击 div 的时候,this 指向 div

自调用函数,this => window

    (function () {
      console.log(this)
    })()
    // 此时 this 指向 window

call 和 apply 和 bind

刚才我们说过的都是函数的基本调用方式里面的 this 指向
我们还有三个可以忽略函数本身的 this 指向转而指向别的地方
这三个方法就是 call / apply / bind
是强行改变 this 指向的方法

call

call 方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向

语法: 函数名.call(要改变的 this 指向,要给函数传递的参数1,要给函数传递的参数2, ...)

var obj = { name: 'Jack' }
function fn(a, b) {
  console.log(this)
  console.log(a)
  console.log(b)
}
fn(1, 2)
fn.call(obj, 1, 2)

fn() 的时候,函数内部的 this 指向 window
fn.call(obj, 1, 2) 的时候,函数内部的 this 就指向了 obj 这个对象
使用 call 方法的时候
会立即执行函数
第一个参数是你要改变的函数内部的 this 指向
第二个参数开始,依次是向函数传递参数

apply

apply 方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向

语法: 函数名.apply(要改变的 this 指向,[要给函数传递的参数1, 要给函数传递的参数2, ...])

var obj = { name: 'Jack' }
function fn(a, b) {
  console.log(this)
  console.log(a)
  console.log(b)
}
fn(1, 2)
fn.apply(obj, [1, 2])

fn() 的时候,函数内部的 this 指向 window
fn.apply(obj, [1, 2]) 的时候,函数内部的 this 就指向了 obj 这个对象
使用 apply 方法的时候
会立即执行函数
第一个参数是你要改变的函数内部的 this 指向
第二个参数是一个 数组,数组里面的每一项依次是向函数传递的参数

bind

bind 方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向

和 call / apply 有一些不一样,就是不会立即执行函数,而是返回一个已经改变了 this 指向的函数

语法: var newFn = 函数名.bind(要改变的 this 指向); newFn(传递参数)

var obj = { name: 'Jack' }
function fn(a, b) {
  console.log(this)
  console.log(a)
  console.log(b)
}
fn(1, 2)
var newFn = fn.bind(obj)
newFn(1, 2)

bind 调用的时候,不会执行 fn 这个函数,而是返回一个新的函数
这个新的函数就是一个改变了 this 指向以后的 fn 函数
fn(1, 2) 的时候 this 指向 window
newFn(1, 2) 的时候执行的是一个和 fn 一摸一样的函数,只不过里面的 this 指向改成了 obj

ES6新增的内容

之前的都是 ES5 的内容
接下来我们聊一下 ES6 的内容

let 和 const 关键字

我们以前都是使用 var 关键字来声明变量的

在 ES6 的时候,多了两个关键字 letconst,也是用来声明变量的

只不过和 var 有一些区别

  1. letconst 不允许重复声明变量
     // 使用 var 的时候重复声明变量是没问题的,只不过就是后面会把前面覆盖掉
     var num = 100
     var num = 200
     // 使用 let 重复声明变量的时候就会报错了
     let num = 100
     let num = 200 // 这里就会报错了
     // 使用 const 重复声明变量的时候就会报错
     const num = 100
     const num = 200 // 这里就会报错了
  1. letconst 声明的变量不会在预解析的时候解析(也就是没有变量提升)
     // 因为预解析(变量提升)的原因,在前面是有这个变量的,只不过没有赋值
     console.log(num) // undefined
     var num = 100
     // 因为 let 不会进行预解析(变量提升),所以直接报错了
     console.log(num) // undefined
     let num = 100
     // 因为 const 不会进行预解析(变量提升),所以直接报错了
     console.log(num) // undefined
     const num = 100
  1. letconst 声明的变量会被所有代码块限制作用范围
     // var 声明的变量只有函数能限制其作用域,其他的不能限制
     if (true) {
       var num = 100
     }
     console.log(num) // 100
     // let 声明的变量,除了函数可以限制,所有的代码块都可以限制其作用域(if/while/for/...)
     if (true) {
       let num = 100
       console.log(num) // 100
     }
     console.log(num) // 报错
     // const 声明的变量,除了函数可以限制,所有的代码块都可以限制其作用域(if/while/for/...)
     if (true) {
       const num = 100
       console.log(num) // 100
     }
     console.log(num) // 报错

letconst 的区别

  1. let 声明的变量的值可以改变,const 声明的变量的值不可以改变
     let num = 100
     num = 200
     console.log(num) // 200
     
     const num = 100
     num = 200 // 这里就会报错了,因为 const 声明的变量值不可以改变(我们也叫做常量)
     
  1. let 声明的时候可以不赋值,const 声明的时候必须赋值
     let num
     num = 100
     console.log(num) // 100
     const num // 这里就会报错了,因为 const 声明的时候必须赋值
     

箭头函数

箭头函数是 ES6 里面一个简写函数的语法方式

重点: 箭头函数只能简写函数表达式,不能简写声明式函数

  function fn() {} // 不能简写
  const fun = function () {} // 可以简写
  const obj = {
    fn: function () {} // 可以简写
  }
  

语法: (函数的行参) => { 函数体内要执行的代码 }

  const fn = function (a, b) {
    console.log(a)
    console.log(b)
  }
  // 可以使用箭头函数写成
  const fun = (a, b) => {
    console.log(a)
    console.log(b)
  }
  const obj = {
    fn: function (a, b) {
      console.log(a)
      console.log(b)
    }
  }
  // 可以使用箭头函数写成
  const obj2 = {
    fn: (a, b) => {
      console.log(a)
      console.log(b)
    }
  }
箭头函数的特殊性
  • 箭头函数内部没有 this,箭头函数的 this 是上下文的 this
  // 在箭头函数定义的位置往上数,这一行是可以打印出 this 的
  // 因为这里的 this 是 window
  // 所以箭头函数内部的 this 就是 window
  const obj = {
    fn: function () {
      console.log(this)
    },
    // 这个位置是箭头函数的上一行,但是不能打印出 this
    fun: () => {
      // 箭头函数内部的 this 是书写箭头函数的上一行一个可以打印出 this 的位置
      console.log(this)
    }
  }
  
  obj.fn()
  obj.fun()
  
  • 按照我们之前的 this 指向来判断,两个都应该指向 obj

  • 但是 fun 因为是箭头函数,所以 this 不指向 obj,而是指向 fun 的外层,就是 window

  • 箭头函数内部没有 arguments 这个参数集合

  const obj = {
    fn: function () {
      console.log(arguments)
    },
    fun: () => {
      console.log(arguments)
    }
  }
  obj.fn(1, 2, 3) // 会打印一个伪数组 [1, 2, 3]
  obj.fun(1, 2, 3) // 会直接报错
  
  • 函数的行参只有一个的时候可以不写 () 其余情况必须写
  const obj = {
    fn: () => {
      console.log('没有参数,必须写小括号')
    },
    fn2: a => {
      console.log('一个行参,可以不写小括号')
    },
    fn3: (a, b) => {
      console.log('两个或两个以上参数,必须写小括号')
    }
  }
  
  • 函数体只有一行代码的时候,可以不写 {} ,并且会自动 return

    const obj = {
      fn: a => {
        return a + 10
      },
      fun: a => a + 10
    }
    
    console.log(fn(10)) // 20
    console.log(fun(10)) // 20
    
    

函数传递参数的时候的默认值

  • 我们在定义函数的时候,有的时候需要一个默认值出现

  • 就是当我不传递参数的时候,使用默认值,传递参数了就使用传递的参数

  function fn(a) {
    a = a || 10
    console.log(a)
  }
  fn()   // 不传递参数的时候,函数内部的 a 就是 10
  fn(20) // 传递了参数 20 的时候,函数内部的 a 就是 20
  
  • 在 ES6 中我们可以直接把默认值写在函数的行参位置
  function fn(a = 10) {
    console.log(a)
  }
  fn()   // 不传递参数的时候,函数内部的 a 就是 10
  fn(20) // 传递了参数 20 的时候,函数内部的 a 就是 20
  
  • 这个默认值的方式箭头函数也可以使用
  const fn = (a = 10) => {
    console.log(a)
  }
  fn()   // 不传递参数的时候,函数内部的 a 就是 10
  fn(20) // 传递了参数 20 的时候,函数内部的 a 就是 20
  • 注意: 箭头函数如果你需要使用默认值的话,那么一个参数的时候也需要写 ()

解构赋值

  • 解构赋值,就是快速的从对象或者数组中取出成员的一个语法方式
解构对象
  • 快速的从对象中获取成员

    // ES5 的方法向得到对象中的成员
    const obj = {
      name: 'Jack',
      age: 18,
      gender: '男'
    }
    
    let name = obj.name
    let age = obj.age
    let gender = obj.gender
    
    
    // 解构赋值的方式从对象中获取成员
    const obj = {
      name: 'Jack',
      age: 18,
      gender: '男'
    }
    
    // 前面的 {} 表示我要从 obj 这个对象中获取成员了
    // name age gender 都得是 obj 中有的成员
    // obj 必须是一个对象
    let { name, age, gender } = obj
    
    
解构数组
  • 快速的从数组中获取成员

    // ES5 的方式从数组中获取成员
    const arr = ['Jack', 'Rose', 'Tom']
    let a = arr[0]
    let b = arr[1]
    let c = arr[2]
    
    
    // 使用解构赋值的方式从数组中获取成员
    const arr = ['Jack', 'Rose', 'Tom']
    
    // 前面的 [] 表示要从 arr 这个数组中获取成员了
    // a b c 分别对应这数组中的索引 0 1 2
    // arr 必须是一个数组
    let [a, b, c] = arr
    
    
注意
  • {} 是专门解构对象使用的
  • [] 是专门解构数组使用的
  • 不能混用

模版字符串

  • ES5 中我们表示字符串的时候使用 '' 或者 ""

  • 在 ES6 中,我们还有一个东西可以表示字符串,就是 ``(反引号)

    let str = `hello world`
    console.log(typeof str) // string
    
    
  • 和单引号好友双引号的区别

    1. 反引号可以换行书写
     // 这个单引号或者双引号不能换行,换行就会报错了
     let str = 'hello world' 
     
     // 下面这个就报错了
     let str2 = 'hello 
     world'
     let str = `
     	hello
     	world
     `
     
     console.log(str) // 是可以使用的
  1. 反引号可以直接在字符串里面拼接变量
     // ES5 需要字符串拼接变量的时候
     let num = 100
     let str = 'hello' + num + 'world' + num
     console.log(str) // hello100world100
     
     // 直接写在字符串里面不好使
     let str2 = 'hellonumworldnum'
     console.log(str2) // hellonumworldnum
     // 模版字符串拼接变量
     let num = 100
     let str = `hello${num}world${num}`
     console.log(str) // hello100world100
 - 在 **``** 里面的 `${}` 就是用来书写变量的位置

展开运算符

  • ES6 里面号新添加了一个运算符 ... ,叫做展开运算符

  • 作用是把数组展开

  let arr = [1, 2, 3, 4, 5]
  console.log(...arr) // 1 2 3 4 5
  • 合并数组的时候可以使用
  let arr = [1, 2, 3, 4]
  let arr2 = [...arr, 5]
  console.log(arr2)
  
  • 也可以合并对象使用
  let obj = {
    name: 'Jack',
    age: 18
  }
  let obj2 = {
    ...obj,
    gender: '男'
  }
  console.log(obj2)
  
  • 在函数传递参数的时候也可以使用
  let arr = [1, 2, 3]
  function fn(a, b, c) {
    console.log(a)
    console.log(b)
    console.log(c)
  }
  fn(...arr)
  // 等价于 fn(1, 2, 3)

模块化语法

  • 导出语法
  export default xxx
  
  export const num = xxx
  
  • 导入语法
  import xxx from '地址'
  
  import { xxx, yyy } from '地址'
  

二十、正则表达式

概念:正则对象,Regular Expression regExp

我们一般用这则对象来定义一个规则,可以用来对 字符串进行查找、验证、替换等操作

js通常用正则来进行表单验证

创建正则对象

字面量法

var reg = /规则/修饰符

构造函数法

var reg = new RegExp('规则','修饰符')

在正则中:规则是必须有的,修饰符是可选的

案例:

var reg = /a///某一个字符串中必须包含小写字母a

正则的验证:test()

方法名参数说明返回值
test(字符串)字符串判断字符串是否匹配正则表达式布尔值
var reg = /a/; //某一个字符串中必须包含小写字母a

var str1 = 'hello world'
var str2 = 'halo world'

var res1 = reg.test(str1)  
var res2 = reg.test(str2)  
console.log(res1) //false
console.log(res2) //true

正则的规则:

[]包含中括号中的任意一个字符即可

表达式说明
/[abc]/包含a、b、c任意一个字母即可
/[0-9]/包含任意数字
/[a-z]/包含小写 a - z 之间的任意小写字母
/[A-Z]/包含大写 A - Z 之间的任意大写字母
/[a-zA-Z]/包含任意大小写字母
/[a-zA-Z0-9]/包含任意一个大小写字母或数字
/[0-9][0-9][0-9][0-9][0-9]/连续匹配5个数字
/[^abc]/包含除了a、b、c 中以外的其他任意字符(取反)

元字符

元字符说明
/\w/包含数字、字母、下划线中的任意字符。等同于 /[0-9a-zA-Z_]/
/\W/包含除了数字、字母、下划线以外的其他任意字符。等同于 /[^0-9a-zA-Z_]/
/\d/包含任意数字。等同于/[0-9]/
/\D/包含除了数字以外的其他任意字符。等同于 /[^0-9]/
/\s/包含空白字符(空格)。
/\S/包含除了空白字符以外的其他字符。

量词

量词说明
/n+/至少包含一个指定字符, n >= 1
/n*/指定字符可有可无, n >= 0
/n?/指定字符出现0次或1次 ,n=0 || n=1
/n{x}/指定字符连续出现x次 ,n = x
/n{x,}/至少连续出现x次,n >= x
/n{x,y}/至少连续出现x次,最多y次 n >= x && n <= y

开始与结束

当有了开始和结束符号的时候,我们的正则表达式才有了真正的意义

符号说明
^n^[0-9]
n$表示以指定字符为结尾

其他符号

符号说明
.大体上可以将 . 看作是通配符(实际上不能匹配一些特殊符号,例如换行符 \n、回车符、制表符等)。
``
()对规则进行分组
\转义符d

示例代码:

var regExp = /^(a|b)$/;
console.log(regExp.test('a'))

修饰符

修饰符说明
i不区分大小写。
g对所有字符串进行匹配(而非在找到第一个匹配后停止)。
m多行匹配(即在到达一行文本末位时还会继续查找下一行)。

示例代码:

var regExp = /^[a-z]{6,10}$/i;
console.log(regExp.test('ABCDEFG'));  // true

正则测试:https://c.runoob.com/front-end/854/
正则大全:https://any86.github.io/any-rule/

正则表达式的方法

正则提供了一些方法给我们使用
用来检测和捕获字符串中的内容的

test

test 是用来检测字符串是否符合我们正则的标准

  • 语法: 正则.test(字符串)

  • 返回值: boolean

    console.log(/\d+/.test('123')) // true
    console.log(/\d+/.test('abc')) // false
    

exec

exec 是把字符串中符合条件的内容捕获出来

  • 语法: 正则.exec(字符串)

  • 返回值: 把字符串中符合正则要求的第一项以及一些其他信息,以数组的形式返回

    var reg = /\d{3}/
    var str = 'hello123world456你好789'
    var res = reg.exec(str)
    console.log(res)
    /*
    	["123", index: 5, input: "hello123world456你好789", groups: undefined]
        0: "123"
        groups: undefined
        index: 5
        input: "hello123world456你好789"
        length: 1
      	__proto__: Array(0)
    */
    

    数组第 0 项就是匹配到的字符串内容
    index 属性表示从字符串的索引几开始是匹配的到字符串

字符串的方法

字符串中有一些方法也是可以和正则一起使用的

search

search 是查找字符串中是否有满足正则条件的内容

  • 语法: 字符串.search(正则)

  • 返回值 : 有的话返回开始索引,没有返回 -1

    var reg = /\d{3}/
    var str = 'hello123'
    var str2 = 'hello'
    console.log(str.search(reg)) // 5
    console.log(str2.search(reg)) // -1
    
    

match

match 找到字符串中符合正则条件的内容返回

  • 语法: 字符串.match(正则)

  • 返回值 :

    • 没有标示符 g 的时候,是和 exec 方法一样
    • 有标示符 g 的时候,是返回一个数组,里面是匹配到的每一项
    var reg = /\d{3}/
    var str = 'hello123world456'
    var str2 = 'hello'
    console.log(str.match(reg)) 
    // ["123", index: 5, input: "hello123wor456", groups: undefined]
    console.log(str2.match(reg)) // null
    
    var reg = /\d{3}/g
    var str = 'hello123world456'
    var str2 = 'hello'
    console.log(str.match(reg)) 
    // ["123", "456"]
    console.log(str2.match(reg)) // null
    

replace

replace 是将字符串中满足正则条件的字符串替换掉

  • 语法: 字符串.replace(正则,要替换的字符串)

  • 返回值 : 替换后的字符串

    var reg = /\d{3}/
    var str = 'hello123world456'
    var str2 = 'hello'
    console.log(str.replace(reg)) // hello666world456
    console.log(str2.replace(reg)) // hello
    
    var reg = /\d{3}/g
    var str = 'hello123world456'
    var str2 = 'hello'
    console.log(str.replace(reg)) // hello666world666
    console.log(str2.replace(reg)) // hello
    

二十一、运动函数

什么是运动

通过 js 的方式,让物体属性发生变化,比如说物体匀速运动,淡入淡出,反弹,抛物线等动作。

一.运动原理

通过定时器改变元素 css 属性。

定时器的应用(setInterval/setTimeout)-- 开启和关闭/多个定时器。

window.setInterval(函数名 / 函数体, 时间ms);
window.clearInterval(定时器的返回值);
  • 读写元素的 css 属性

offsetLeft/offsetTop/offsetWidth/offsetHeight 获取盒子的位置和盒模型的宽高 没有单位

window.getComputedStyle(元素对象)[css属性词] 获取任意的css属性值,带单位

逐个写入:elementobj.style.left = '100px'
整体写入:elementobj.style.cssText = 'width:100px;height:100px;';  弊端:覆盖已经写入的css样式,通过+=拼接写入。
选择器写入:elementobj.className = ''   elementobj.id=''

二. JS常见运动介绍

基础运动函数封装: 边界判断-碰撞运动

  1. 物体运动过程中碰到了边界,选择速度取反,进行反向运动。

  2. 物体运动起来

    1. 事件 - 可选

    2. 定时器不断的改变 - 时间

    3. 速度

  3. 思考

    1. 定时器的叠加,通过点击的时候清除前面的定时器实现

    2. 模拟加速度 + 力的损耗

多属性运动函数封装: 缓冲运动 - 封装

  1. 离目标点越远,速度越快,越接近目标,速度越慢,最终停下来。

  2. 速度:根据目标点从快到慢

Swiper 介绍

官方网址 https://www.swiper.com.cn/

Swiper是纯javascript打造的滑动特效插件,实现触屏焦点图、触屏Tab切换、触屏轮播图切换等常用效果

二十二、面向对象基础和原型

首先,我们要明确,面向对象不是语法,是一个思想,是一种 编程模式
面向: 面(脸),向(朝着)
面向过程: 脸朝着过程 =》 关注着过程的编程模式
面向对象: 脸朝着对象 =》 关注着对象的编程模式
实现一个效果
在面向过程的时候,我们要关注每一个元素,每一个元素之间的关系,顺序,。。。
在面向对象的时候,我们要关注的就是找到一个对象来帮我做这个事情,我等待结果

  • 例子 🌰: 我要吃面条
    • 面向过程
      • 用多少面粉
      • 用多少水
      • 怎么和面
      • 怎么切面条
      • 做开水
      • 煮面
      • 吃面
    • 面向对象
      • 找到一个面馆
      • 叫一碗面
      • 等着吃
    • 面向对象就是对面向过程的封装
      我们以前的编程思想是,每一个功能,都按照需求一步一步的逐步完成
      我们以后的编程思想是,每一个功能,都先创造一个 面馆,这个 面馆 能帮我们作出一个 面(完成这个功能的对象),然后用 面馆 创造出一个 ,我们只要等到结果就好了

创建对象的方式

因为面向对象就是一个找到对象的过程
所以我们先要了解如何创建一个对象

调用系统内置的构造函数创建对象

js 给我们内置了一个 Object 构造函数

这个构造函数就是用来创造对象的

当 构造函数 和 new 关键字连用的时候,就可以为我们创造出一个对象

因为 js 是一个动态的语言,那么我们就可以动态的向对象中添加成员了

  // 就能得到一个空对象
  var o1 = new Object() 
  
  // 正常操作对象
  o1.name = 'Jack'
  o1.age = 18
  o1.gender = '男'

字面量的方式创建一个对象

直接使用字面量的形式,也就是直接写 {}

可以在写的时候就添加好成员,也可以动态的添加

// 字面量方式创建对象
var o1 = {
  name: 'Jack',
  age: 18,
  gender: '男'
}

// 再来一个
var o2 = {}
o2.name = 'Rose'
o2.age = 20
o2.gender = '女'

使用工厂函数的方式创建对象

先书写一个工厂函数

其实就是创建一个函数,在函数里面创造出一个对象,并且给对象添加一些属性,还能把对象返回

使用这个工厂函数创造对象

// 1. 先创建一个工厂函数
function createObj() {
  // 手动创建一个对象
  var obj = new Object()

  // 手动的向对象中添加成员
  obj.name = 'Jack'
  obj.age = 18
  obj.gender = '男'

  // 手动返回一个对象
  return obj
}

// 2. 使用这个工厂函数创建对象
var o1 = createObj()
var o2 = createObj()

使用自定义构造函数创建对象

工厂函数需要经历三个步骤

  • 手动创建对象
  • 手动添加成员
  • 手动返回对象

构造函数会比工厂函数简单一点

  • 自动创建对象
  • 手动添加成员
  • 自动返回对象

先书写一个构造函数

在构造函数内向对象添加一些成员

使用这个构造函数创造一个对象(和 new 连用)

构造函数可以创建对象,并且创建一个带有属性和方法的对象

面向对象就是要想办法找到一个有属性和方法的对象

面向对象就是我们自己制造 构造函数 的过程

// 1. 先创造一个构造函数
function Person(name, gender) {
  this.age = 18
  this.name = name
  this.gender = gender
}

// 2. 使用构造函数创建对象
var p1 = new Person('Jack', 'man')
var p2 = new Person('Rose', 'woman')

构造函数详解

我们了解了对象的创建方式
我们的面向对象就是要么能直接得到一个对象
要么就弄出一个能创造对象的东西,我们自己创造对象
我们的构造函数就能创造对象,所以接下来我们就详细聊聊 构造函数

构造函数的基本使用

和普通函数一样,只不过 调用的时候要和 new 调用,不然就是一个普通函数调用

function Person() {}
var o1 = new Person()  // 能得到一个空对象
var o2 = Person()      // 什么也得不到,这个就是普通函数调用
  • 注意: 不写 new 的时候就是普通函数调用,没有创造对象的能力

  • 首字母大写

    function person() {}
    var o1 = new person() // 能得到一个对象
    
    function Person() {}
    var o2 = new Person() // 能得到一个对象
    
    • 注意: 首字母不大写,只要和 new 连用,就有创造对象的能力

当调用的时候如果不需要传递参数可以不写 (),建议都写上

function Person() {}
var o1 = new Person()  // 能得到一个空对象
var o2 = new Person    // 能得到一个空对象 
  • 注意: 如果不需要传递参数,那么可以不写 (),如果传递参数就必须写

构造函数内部的 this,由于和 new 连用的关系,是指向当前实例对象的

function Person() {
  console.log(this)
}
var o1 = new Person()  // 本次调用的时候,this => o1
var o2 = new Person()  // 本次调用的时候,this => o2
  • 注意: 每次 new 的时候,函数内部的 this 都是指向当前这次的实例化对象

因为构造函数会自动返回一个对象this,所以构造函数内部不要写 return

你如果 return 一个基本数据类型,那么写了没有意义
如果你 return 一个引用数据类型,那么构造函数本身的意义就没有了

构造函数的书写: 构造函数需要注意什么??

    1. 构造函数的名称(首字母大写),目的就是和普通函数做区别
    2. 构造函数的内 不要写return
       如果return 是一个基本数据类型,写了也没用
       如果return 是一个引用数据类型,写了导致构造函数没用
    3.构造函数调用时,必须和new关键字去连用
    4.构造函数的this
        当一个函数和new关键字连用的时候,那么我们说明这个函数是 构造函数
        然后这个函数的内部的this 指向本次调用被自己创建的那个对象

    5.构造函数不能使用箭头函数
        因为箭头函数内部没有this

使用构造函数创建一个对象

我们在使用构造函数的时候,可以通过一些代码和内容来向当前的对象中添加一些内容

function Person() {
  this.name = 'Jack'
  this.age = 18
}

var o1 = new Person()
var o2 = new Person()

我们得到的两个对象里面都有自己的成员 nameage

我们在写构造函数的时候,是不是也可以添加一些方法进去呢?

function Person() {
  this.name = 'Jack'
  this.age = 18
  this.sayHi = function () {
    console.log('hello constructor')
  }
}

var o1 = new Person()
var o2 = new Person()

显然是可以的,我们得到的两个对象中都有 sayHi 这个函数
也都可以正常调用

但是这样好不好呢?缺点在哪里?

function Person() {
  this.name = 'Jack'
  this.age = 18
  this.sayHi = function () {
    console.log('hello constructor')
  }
}

// 第一次 new 的时候, Person 这个函数要执行一遍
// 执行一遍就会创造一个新的函数,并且把函数地址赋值给 this.sayHi
var o1 = new Person()

// 第二次 new 的时候, Person 这个函数要执行一遍
// 执行一遍就会创造一个新的函数,并且把函数地址赋值给 this.sayHi
var o2 = new Person()

这样的话,那么我们两个对象内的 sayHi 函数就是一个代码一摸一样,功能一摸一样
但是是两个空间函数,占用两个内存空间
也就是说 o1.sayHi 是一个地址,o2.sayHi 是一个地址
所以我们执行 console.log(o1 === o2.sayHi) 的到的结果是 false
缺点: 一摸一样的函数出现了两次,占用了两个空间地址

怎么解决这个问题呢?

就需要用到一个东西,叫做 原型

原型

原型的出现,就是为了解决 构造函数的缺点
也就是给我们提供了一个给对象添加函数的方法
不然构造函数只能给对象添加属性,不能合理的添加方法就太 LOW 了

原型:prototype
又名:原型空间 原型对象
什么是原型?
=> 每一个函数天生拥有一个属性:prototype,他的属性是一个 对象
=> 我们通常把这个对象叫做 这个函数的原型(空间/对象)
=> 原型这个对象中 有一个属性 叫做 constructor,这个属性表示的是:当前这个原型 是那个函数的原型

如何使用原型中的属性?
    => 每一个对象 天生拥有一个属性 __proto__
    => 这个属性指向 自己构造函数的 原型

prototype

每一个函数天生自带一个成员,叫做 prototype,是一个对象空间

即然每一个函数都有,构造函数也是函数,构造函数也有这个对象空间

这个 prototype 对象空间可以由函数名来访问

function Person() {}

console.log(Person.prototype) // 是一个对象

即然是个对象,那么我们就可以向里面放入一些东西

function Person() {}

Person.prototype.name = 'prototype'
Person.prototype.sayHi = function () {}

我们发现了一个叫做 prototype 的空间是和函数有关联的

并且可以向里面存储一些东西

  • 重点: 在函数的 prototype 里面存储的内容,不是给函数使用的,是给函数的每一个实例化对象使用的

proto

每一个对象都天生自带一个成员,叫做 __proto__,是一个对象空间

即然每一个对象都有,实例化对象也是对象,那么每一个实例化对象也有这个成员

这个 __proto__ 对象空间是给每一个对象使用的

当你访问一个对象中的成员的时候

如果这个对象自己本身有这个成员,那么就会直接给你结果
如果没有,就会去 __proto__ 这个对象空间里面找,里面有的话就给你结果

那么这个 __proto__ 又指向哪里呢?

这个对象是由哪个构造函数 new 出来的
那么这个对象的 __proto__ 就指向这个构造函数的 prototype

function Person() {}

var p1 = new Person()

console.log(p1.__proto__ === Person.prototype) // true

我们发现实例化对象的 __proto__ 和所属的构造函数的 prototype 是一个对象空间
我们可以通过构造函数名称来向 prototype 中添加成员
对象在访问的时候自己没有,可以自动去自己的 __proto__ 中查找
那么,我们之前构造函数的缺点就可以解决了
我们可以把函数放在构造函数的 prototype
实例化对象访问的时候,自己没有,就会自动去 __proto__ 中找
那么也可以使用了

function Person() {}

Person.prototype.sayHi = function () {
  console.log('hello Person')
}

var p1 = new Person()
p1.sayHi()

p1 自己没有 sayHi 方法,就会去自己的 __proto__ 中查找
p1.__proto__ 就是 Person.prototype
我们又向 Person.prototype 中添加了 sayHi 方法
所以 p1.sayHi 就可以执行了

到这里,当我们实例化多个对象的时候,每个对象里面都没有方法

都是去所属的构造函数的 protottype 中查找
那么每一个对象使用的函数,其实都是同一个函数
那么就解决了我们构造函数的缺点

function Person() {}

Person.prototype.sayHi = function () {
  console.log('hello')
}

var p1 = new Person()
var p2 = new Person()

console.log(p1.sayHi === p2.sayHi)

p1Person 的一个实例
p2Person 的一个实例
也就是说 p1.__proto__p2.__proto__ 指向的都是 Person.prototype
p1 去调用 sayHi 方法的时候是去 Person.prototype 中找
p2 去调用 sayHi 方法的时候是去 Person.prototype 中找
那么两个实例化对象就是找到的一个方法,也是执行的一个方法

结论:

  • 当我们写构造函数的时候
  • 属性我们直接写在构造函数体内
  • 方法我们写在原型上

对象的访问规则:

访问一个对象的某一个属性的时候,先先在对象本身去查找,找到就直接去使用
如果没有找到,对象的__proto__中查找,找到就使用,
如果没有找到,对象的原型__proto__中查找,找到就使用,
直到找到js的顶层对象,Object.prototype,如果还没找到,不再查找


原型的作用:把构造函数中 的公共方法,提取出来,放到原型里面
为什么要这么做?  
构造函数的原型上的方法或者属性,在每一个实例化的对象中都能正常访问

原型链

我们说构造函数的 prototype 是一个对象
又说了每一个对象都天生自带一个 __proto__ 属性
那么 构造函数的 prototype 里面的 __proto__ 属性又指向哪里呢?

原型链的概念:
实例对象和原型(prototype)的链接,依靠__proto__的属性

一个对象所属的构造函数

每一个对象都有一个自己所属的构造函数

  • 比如: 数组

    // 数组本身也是一个对象
    var arr = []
    var arr2 = new Array()
    

    以上两种方式都是创造一个数组
    我们就说数组所属的构造函数就是 Array

  • 比如: 函数

    // 函数本身也是一个对象
    var fn = function () {}
    var fun = new Function()
    

    以上两种方式都是创造一个函数
    我们就说函数所属的构造函数就是 Function

constructor

对象的 __proto__ 里面也有一个成员叫做 constructor
这个属性就是指向当前这个对象所属的构造函数

链状结构

当一个对象我们不知道准确的是谁构造的时候,我们呢就把它看成 Object 的实例化对象
也就是说,我们的 构造函数 的 prototype 的 __proto__ 指向的是 Object.prototype
那么 Object.prototype 也是个对象,那么它的 __proto__ 又指向谁呢?
因为 Object 的 js 中的顶级构造函数,我们有一句话叫 万物皆对象
所以 Object.prototype 就到顶了,Object.prototype__proto__ 就是 null

原型链的访问原则

访问一个对象的成员的时候,构造函数没有就会利用___proto__去构造函数的原型里面找。

如果构造函数原型里面没有就再去Object原型里面找

如果Object.prototype 里面都没有,那么就会返回 undefined

对象的赋值

到这里,我们就会觉得,如果是赋值的话,那么也会按照原型链的规则来
但是: 并不是!并不是!并不是! 重要的事情说三遍
赋值的时候,就是直接给对象自己本身赋值
如果原先有就是修改
原先没有就是添加
不会和 __proto__ 有关系

es6面向对象的写法

语法糖:也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。
通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

ES6 提供了更接近传统语言(后端)的写法,引入了 Class(类)这个概念,作为对象的模板。
通过class关键字,可以定义类。基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

静态方法:
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        class Person2{
            constructor(name,age,sex){
                this.name=name
                this.age = age
                this.sex = sex
            }
            // 实例方法
            init(){
                console.log('初始化方法')
            }
            show(){
                console.log('实例方法')
            }

            // 静态方法
            static show1(){
                console.log('静态方法')
            }
        }

        let p2 = new Person2('千峰',2023,2)
        console.log(p2)
        p2.init()                   //初始化方法
        console.log(p2.name)        //千峰

        // p2.show1()      //报错
        Person2.show1()     //静态方法
    </script>
</body>
</html>

链式调用

1,当我们在调用同一对象的多个属性和方法的时候,我们可以采用链式操作
2.链式调用是一种简化过程的一种编码方式,简洁和易读
3.链式调用的核心是return this(将实例对象返回出来)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    链式调用:
    1,当我们在调用同一对象的多个属性和方法的时候,我们可以采用链式操作
    2.链式调用是一种简化过程的一种编码方式,简洁和易读
    3.链式调用的核心是return this(将实例对象返回出来)

    <script>
        //   class Person{
        //     constructor(name,age,sex){//取代传统方式的里面的构造函数,直接用原型对象的constructor属性,来进行传参
        //         this.name=name
        //         this.age=age
        //         this.sex=sex
        //     }
        //     showname(){
        //         console.log(this.name);
        //         // console.log(this);
        //         return this;//每个方法再将实例对象返回出来,继续调用属性和方法
        //     }
        //     showage(){
        //         console.log(this.age);
        //         return this;//每个方法再将实例对象返回出来,继续调用属性和方法
        //     }
        //     showsex(){
        //         console.log(this.sex);
        //     }
        // }
        // let p2=new Person('阿册',2,"女");
        // p2.showname()
        // p2.showage()
        // p2.showsex()

        // p2.showname().showage().showsex()


        // 案例:某个数字加减乘除的计算
        class Calc{
            constructor(value){//取代传统方式的里面的构造函数,直接用原型对象的constructor属性,来进行传参
                this.value=value//获取到了数字
            }
            add(n){
               this.value+=n
               return this
            }
            sub(n){
                this.value-=n
                return this
            }
            mul(n){
                this.value*=n
                return this
            }
            div(n){
                this.value/=n
                return this.value
            }
        }
        let c1=new Calc(5);
        console.log(c1.add(10).sub(1).mul(10).div(10));//14
    </script>
</body>
</html>

相关属性方法介绍

toString() :系统对象下都是自带的 , 自己写的对象都是通过原型链继承Object的,可以把对象转成字符串,进制转换或者类型的判断。
constructor:实例对象的构造函数
instanceof:判断一个对象是否是一个构造函数(类)的实例。
hasOwnProperty() : 看是不是实例对象自身下面的属性, 只在属性存在于实例中时才返回 true,不会继承原型链上面的。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>


    1.toString()
    <script>
        // 1.对象转成字符串
        // let arr1=["zhangsa",'lisi',"wangwu"];
        // let arr2=["zhangsa",'lisi',"wangwu"];
        // console.log(arr1.toString());
        // console.log(arr1===arr2);//false
        // // 比较数组相等
        // console.log(arr1.toString()===arr2.toString());//true


        // // 2.数组扁平化
        // let arr3=[1,[2,3,[4,5,[6,7,[8,[9,[10]]]]]]];
        // // console.log(arr3.flat());
        // // console.log(arr3.toString().split(","));
        // // console.log(arr3.toString().split(",").map(function(v){
        // //     return +v
        // // }));
        // console.log(arr3.toString().split(",").map(v=> +v))

        // // 3.进制转换
        // let num=10;//十进制
        // console.log(num.toString(2));//1010
        // console.log(num.toString(8));//1010
        // console.log(num.toString(16));//a 0-9 a-f

        // // 4.检查数据类型 typeof
        // let arr5=new String("hello");
        // let arr6=[1,2,3];
        // let arr7={
        //     a:1
        // }
        // let arr8=new Date()
        // let arr9=new RegExp()
        // let arr10=null

        // console.log(typeof arr5);
        // console.log(typeof arr6);
        // console.log(typeof arr7);
        // console.log(typeof arr8);
        // console.log(typeof arr9);
        // console.log(typeof arr10);

        // // toString 
        // // 自己写的对象都是原型链继承的Object下面的toString,利用toString()进行数据类型检查
        // // console.log(Object.prototype);
        // // console.log(this);
        // console.log(Object.prototype.toString.call('hello'));//[object String]
        // console.log(Object.prototype.toString.call({}));//[object Object]
        // console.log(Object.prototype.toString.call([]));//[object Array]

        // console.log(Object.prototype.toString.call(123));//[object Number]
        // console.log(Object.prototype.toString.call(true));// [object Boolean]
        // console.log(Object.prototype.toString.call(null));//[object Null]
        // console.log(Object.prototype.toString.call(/\d/));//[object RegExp]
        
    </script>

    2.`constructor`:实例对象的构造函数 
    <script>
        // const Person=function(name,age,sex){
        //     this.name=name
        //     this.age=age
        //     this.sex=sex
        // }
        // let p1=new Person()
        // let p2=new Person()
        // console.log(Person);//输出构造函数体
        // console.log(p1.constructor);//输出构造函数体
        // console.log(Person.prototype.constructor);//输出构造函数体
        // // 重点:实例对象和构造函数的原型的constructor都是构造函数
    </script>

    3.`instanceof`:判断一个对象是否是一个构造函数(类)的实例。
    简单来讲:从属关系
    <script>
        // console.log(p1 instanceof Person);//true
        // console.log(p2 instanceof Person);//true
    </script>

    <script>
        // function fn(n1,n2) {
        //     console.log(n1+n2);
        // }
        // fn(2,3)

        // 构造函数创建函数
        // let fn1=new Function('n1','n2','console.log(n1+n2)');
        // fn1(4,5)

        // // console.log(fn1 instanceof Function);//true
        // // console.log(Function instanceof Object);//true
        // console.log(Object instanceof Function);//true
        // // let o1=new Object

        // console.log(new Array  instanceof Object);//true 
        // console.log(new Array  instanceof Function);//false 对象不会从属于函数
        // console.log(Array  instanceof Function);//true 构造函数从属于Function
    </script>
    

    - 4.`hasOwnProperty()`  : 看是不是实例对象自身下面的属性, 只在属性存在于实例中时才返回 true,不会继承原型链上面的。 
    <script>
        const Person=function(name){
            this.name=name
        }
        Person.prototype.age=18
        Object.prototype.sex="男"

        let p1=new Person("zhangsan")
        console.log(p1.name);
        console.log(p1.age);//18
        console.log(p1.sex);//"男"

        console.log(p1.hasOwnProperty('name'));//true
        console.log(p1.hasOwnProperty('age'));//false
        console.log(p1.hasOwnProperty('sex'));//false
    </script>

    5.in  和hasOwnProperty是一样的,判断属性是否存在原型链上面(继承原型链上面的),存在是true,不存在是false

    <script>
        console.log('name' in p1);//true
        console.log('age' in p1);//true
        console.log('sex' in p1);//true
        console.log('hhe' in p1);//true
    </script>
</body>
</html>

Set和Map集合

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    1.数组 []
    Set集合 和 Map集合
    <script>
        // Set是集合,是一种没有重复的元素的列表,使用new Set()创建Set的集合
        // let set=new Set()
        // set.add(1);
        // set.add(2);
        // set.add(2);
        // set.add("2");
        // set.add("c");
        // console.log(set); //Set{1,2,"2","c"}
        // console.log(set.size);//4

        // 使用构造函数
        // let set=new Set([1,2,4,55,63,23,22,2,2]);
        // console.log(set);

        // // has()查找是否存在指定元素,注意2和"2"两个元素,不会隐式转换
        // console.log(set.has(2));//true

        // // delete方法,删除指定的元素
        // set.delete(2);
        // console.log(set);
        // clear清空元素
        // set.clear(2);
        // console.log(set);

        // 将set转成数组
        // let array=[...set];
        // console.log(array);

        // 可以使用for和forEach遍历set的集合
        // for...of 遍历
        // for (let i of set){
        //     console.log(i);
        // }
        
    </script>


    <!-- Map数据集合 -->
    <script>
        // 创建Map集合
        // let map=new Map();
        // map.set("name","找钱笋丝");
        // map.set('age',100)
        // console.log(map);//集合.html:56 Map(2) {'name' => '找钱笋丝', 'age' => 100}
        // console.log(map.get('name'));//找钱笋丝

        // 使用构造函数初始化Map
        let map=new Map([
            ['name','lili'],
            ['age','1111'],
        ])
        console.log(map);

        // has delete clear
        // console.log(map.has('name'));
        // console.log(map.size)
        // map.clear
        // console.log(map)

        // 遍历Map
        // 用forEach
        map.forEach((key,value,m)=>{
            console.log(key);//lili  1111
            console.log(value);//name   age
        })
    </script>
</body>
</html>

总结

到了这里,我们就发现了面向对象的思想模式了

当我想完成一个功能的时候
先看看内置构造函数有没有能给我提供一个完成功能对象的能力
如果没有,我们就自己写一个构造函数,能创造出一个完成功能的对象
然后在用我们写的构造函数 new 一个对象出来,帮助我们完成功能就行了

二十三、面向对象继承

继承的概念:
继承是和构造函数相关的一个应用,让一个构造函数去继承另一个构造函数的属性和方法
所以继承一定出现在 两个构造函数之间

一个小例子

  • 我们之前说,构造函数(类)是对一类行为的描述
  • 那么我们类这个概念其实也很抽象
  • 比如:
    • 我们说 国光 / 富士 都是 苹果的品种,那么我们就可以写一个 苹果类 来实例化很多品种出来
    • 苹果 / 这些东西都是水果的一种,那么我们就可以写一个 水果类
    • 说过的统一特点就是 / 水分大 ,而不同的水果有不同的特征
    • 那么我们就可以让 苹果类 来继承 水果类 的内容,然后再用 水果类 去实例化对象
    • 那么实例化出来的就不光有 苹果类 的属性和方法,还有 水果类 的属性和方法

继承的作用

  • 其实说到底,到底什么是继承

  • 我们之前说,在我们书写构造函数的时候,为了解决一个函数重复出现的问题

  • 我们把构造函数的 方法 写在了 prototype

  • 这样,每一个实例使用的方法就都是来自构造函数的 prototype

  • 就避免了函数重复出现占用内存得到情况

  • 那么,如果两个构造函数的 prototype 中有一样的方法呢,是不是也是一种浪费

  • 所以我们把构造函数䣌 prototype 中的公共的方法再次尽心提取

  • 我们准备一个更公共的构造函数,让构造函数的 __proto__ 指向这个公共的构造函数的 prototype

常见的继承方式

  • 我们有一些常见的继承方式来实现和达到继承的效果

  • 我们先准备一个父类(也就是要让别的构造函数使用我这个构造函数的属性和方法)

    function Person() {
        this.name = 'Jack'
    }
    
    Person.prototype.sayHi = function () {
        cosnole.log('hello')
    }
  • 这个 Person 构造函数为父类

  • 让其他的构造函数来继承他

  • 当别的构造函数能够使用他的属性和方法的时候,就达到了继承的效果

原型继承

原型继承:
利用自定义的原型的方法实现继承关系
核心:将子类的原型修改为父类的实例化对象
优点:可以使用父类的数学经合方法,实现继承
缺点:1.原本原型上的方法不能是用来(因为原型对象被改变了)
2.继承到的属性并不在自己身上,而是在原型对象上(不过他能用)

  • 原型继承,就是在本身的原型链上加一层结构
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- 原型继承:
    利用自定义的原型的方法实现继承关系
    核心:将子类的原型修改为父类的实例化对象
    优点:可以使用父类的数学经合方法,实现继承
    缺点:1.原本原型上的方法不能是用来(因为原型对象被改变了)
        2.继承到的属性并不在自己身上,而是在原型对象上(不过他能用)

    解释缺点1 -->
    <!-- 在我们书写构造函数的时候,为了解决一个函数重复出现的问题 -->
    <script>
        // 构造函数1--父类
        function Person(name){
            this.name=name
        }
        Person.prototype.init=function(){
            console.log('我是Person原型上的方法');
        }

        // 构造函数2--子类
        function Stu(age){
            this.age=age
        }

        Stu.prototype.sayHi=function(){
            console.log('我是学生,我是帅哥,我是美女');
        }

        // Stu.prototype={
        //     a:1,
        //     b:2
        // }
        // console.log( Stu.prototype);

        Stu.prototype=new Person("zhangsna");

        const s1=new Stu(18)
        console.log(s1);
        // console.log(s1.age);
        // console.log(s1.name);
        // s1.init()
        // s1.sayHi()//caught TypeError: s1.sayHi is not a function
 


        // 如果说Stu构造函数可以继承来自Person的构造函数
        // Stu是Person的子类
        // Person是Stu的父类

        // 访问规则:
        // console.log(s1);
        // 1,先在对象自身去查找,找到就是用,但是自身只有一个属性,age属性,所以没找到
        // 2.会自己的__proto__中查找,也就是自己构造函数的原型对象
        //  2,1但是现在自己的构造函数的原型对象已经被我们修改为Person这个构造函数的实例化对象
        //  2.2所以这个实例化具有name属性还有一个方法init的方法
        // 3.返回回来发现name值为zhangsan

    </script>
</body>
</html>

借用构造函数继承

借用构造函数继承:
核心:把父类的构造函数当做普通的函数调用,并利用call修改这个函数内部的this指向,(如果不修改的话,函数的this指向其他的对象)
优点:把父类的属性全都继承在了自己身上

缺点:1.只能继承父类的属性,不能继承父类的方法
    2.每次调用Stu的时候,Stu内部还会自己调用一次Person的函数
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- 借用构造函数继承:
    核心:把父类的构造函数当做普通的函数调用,并利用call修改这个函数内部的this指向,(如果不修改的话,函数的this指向其他的对象)
    优点:把父类的属性全都继承在了自己身上

    缺点:1.只能继承父类的属性,不能继承父类的方法
        2.每次调用Stu的时候,Stu内部还会自己调用一次Person的函数-->

    <script>
        // 构造函数1--父类
        function Person(name){
            this.name=name
        }
        Person.prototype.init=function(){
            console.log('我是Person原型上的方法');
        }

        // 构造函数2--子类
        function Stu(age,name){
            // 1.自动创建一个对象

            // 2.手动给对象添加一些属性
            this.age=age
            // 将Person的内部的this修改为了第一步被自动创建出来的对象,
            // 并传递一个参数为name给Person的函数去使用
            Person.call(this,name)
            //this,指向Stu

            // 3.自动返回对象
        }

        Stu.prototype.sayHi=function(){
            console.log('我是学生,我是帅哥,我是美女');
        }
        let s1=new Stu("18","lisi")
        console.log(s1);
         console.log(s1.name);  
         console.log(s1.init);  

        // 实例化Stu构造函数的时候,得到一个实例化的对象,对象存储在s1的内部了
        // 1.调用Stu构造函数的时候,给对象s1天生加一个属性age属性,并将形参赋值给了age,
        // 2.调用Person函数的内部的this就相当于Stu构造函数内部被自己动创建这个对象
        // 调用Person函数 this.name=形参
        // 3,代码执行完毕之后,new Stu内部被自动创建出来的对象添加了2个属性

        // age name
    </script>
</body>
</html>

组合继承

  • 就是把 原型继承借用构造函数继承 两个方式组合在一起
  • 优点:实例化的对象具有继承的属性,并且能够继承到父类原型上的方法
    缺点:实例化对象上,与原型对象上,都有父类属性(多一个属性,不会影响使用)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    组合继承:
    核心:把原型和借用构造函数继承会给你结合起来使用
    优点:实例化的对象具有继承的属性,并且能够继承到父类原型上的方法
    缺点:实例化对象上,与原型对象上,都有父类属性(多一个属性,不会影响使用)

    <script>
        // 构造函数1--父类
        function Person(name){
            this.name=name
        }
        Person.prototype.init=function(){
            console.log('我是Person原型上的方法');
        }

        // 构造函数2--子类
        function Stu(age,name){
            this.age=age
            // 1.借用构造函数,得到父类的属性,(没有继承到父类的方法)
            Person.call(this,name)
        }
        // 2.利用原型继承,得到父类的属性与方法
        Stu.prototype=new Person("这个字符串没意义的")
        Stu.prototype.sayHi=function(){
            console.log('我是学生,我是帅哥,我是美女');
        }

        const s1=new Stu(18,"丽丝")
        console.log("Stu实例化对象",s1);
        console.log("Stu的原型对象",Stu.prototype);
        s1.init()
        console.log(s1.name);

        // 如果说Stu构造函数可以继承来自Person的构造函数
        // Stu是Person的子类
        // Person是Stu的父类

        // 访问s1的对象的name属性的时候,
        // 先去对象的自身内部去找,现在找到了直接使用,并且停止查找
    </script>
</body>
</html>

拷贝继承 for in

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- 拷贝  for-in 对象上的属性和方法-->

    <script>
        // 构造函数1--父类
        function Person(name){
            this.name=name
        }
        Person.prototype.init=function(){
            console.log('我是Person原型上的方法');
        }
        // const p1=new Person("zhangsan")
        // // console.log(p1);
        // for(let i in p1){
        //     console.log(i,p1[i]);
        // }

        // 构造函数2--子类
        function Stu(age,name){
            this.age=age
            // 在子类构造函数实例化父类的构造函数
            // 得到父类构造函数的实例化对象
            // 然后利用for-in 可以遍历原型上的属性这个特点,
            // 将实例化对象的属性和原型上的方法
            // 一起拷贝的子类的构造函数原型中
            const p1=new Person("zhangsan")
            for(let key in p1){
                Stu.prototype[key]=p1[key];
            }
        }
        Stu.prototype.sayHi=function(){
            console.log('我是学生,我是帅哥,我是美女');
        }

        const s1=new Stu(18,"丽丝")
        console.log("Stu实例化对象",s1);
        console.log("Stu的原型对象",Stu.prototype);
        console.log("Stu的原型对象",s1.__proto__);
        // s1.init()
        // console.log(s1.name);

        // 如果说Stu构造函数可以继承来自Person的构造函数
        // Stu是Person的子类
        // Person是Stu的父类

        // 访问s1的对象的name属性的时候,
        // 先去对象的自身内部去找,现在找到了直接使用,并且停止查找
    </script>
</body>
</html>

ES6 的继承

  • es6 的继承很容易,而且是固定语法
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- es6类语法继承
    语法规范:class 子类类名 extends 父类的类名{}
            书写constructor,内部需要书写super('父类需要的参数')
    注意点:extends和 super同时出现才能完成
            super必须要在constructor的第一行 -->
            <!-- ES6 要求,子类的构造函数必须执行一次super函数。 -->
    
    <script>
        class Person{
            constructor(name){
                this.name=name
            }
            init(){
                console.log('我是Person原型上的方法');
            }
        }
        class Stu extends Person{
            constructor(age){
                super('父类需要的参数,都写在里面')
                this.age=age
            }
            sayHi(){
                console.log('nihao');
            }
        }
        const s1=new Stu(18);
        console.log(s1);
        console.log(s1.name);
        s1.init()
    </script>
</body>
</html>

二十四、闭包

闭包是我们函数的一种高级使用方式
在聊闭包之前我们要先回顾一下 函数

函数的两个阶段

  • 我们一直说函数有两个阶段
    1. 定义阶段
    2. 调用阶段

函数定义阶段

  1. 在堆中开辟一个 存储空间
  2. 将函数的函数体内的代码,保存在堆里面
  3. 将堆内存的地址保存在变量名(函数名),最后这个变量名存储在栈内存当中

函数调用阶段

  1. 根据变量名和函数名的地址,找到对应的函数
  2. 然后在调用栈中开一个新的空间(函数的执行空间)
  3. 在执行空间中,对函数的形参进行赋值
  4. 在执行空间中,对变量的预解析
  5. 在执行空间中,执行函数的代码
  6. 销毁掉当前函数的执行空间

重新定义函数调用阶段

  1. 按照函数名的地址找到函数的 存储空间

  2. 形参赋值

  3. 预解析

  4. 在内存中开辟一个 执行空间

  5. 将函数 存储空间 中的代码拿出来在刚刚开辟的 执行空间 中执行

  6. 执行完毕后,内存中开辟的 执行空间 销毁

    function fn() {
      console.log('我是 fn 函数')
    }
    
    fn() 
    
    • 函数执行的时候会开辟一个 执行空间 (我们暂且叫他 xxff00
    • console.log('我是 fn 函数') 这个代码就是在 xxff00 这个空间中执行
    • 代码执行完毕以后,这个 xxff00 空间就销毁了

函数执行空间

  • 每一个函数会有一个 存储空间
  • 但是每一次调用都会生成一个完全不一样的 执行空间
  • 并且 执行空间 会在函数执行完毕后就销毁了,但是 存储空间 不会
  • 那么这个函数空间执行完毕就销毁了,还有什么意义呢?
    • 我们可以有一些办法让这个空间 不销毁
    • 闭包,就是要利用这个 不销毁的执行空间

函数执行空间不销毁

  • 函数的 执行空间 会在函数执行完毕之后销毁

  • 但是,一旦函数内部返回了一个 引用数据类型,并且 在函数外部有变量接受 的情况下

  • 那么这个函数 执行空间 就不会销毁了

    function fn() {
      const obj = {
          name: 'Jack',
          age: 18,
          gender: '男'
      }
      
      return obj
    }
    
    const o = fn()
    
    • 函数执行的时候,会生成一个函数 执行空间 (我们暂且叫他 xxff00
    • 代码在 xxff00 空间中执行
    • xxff00 这个空间中声名了一个 对象空间(xxff11
    • xxff00 这个执行空间把 xxff11 这个对象地址返回了
    • 函数外部 o 接受的是一个对象的地址没错
      • 但是是一个在 xxff00 函数执行空间中的 xxff11 对象地址
      • 因为 o 变量一直在和这个对象地址关联着,所以 xxff00 这个空间一直不会销毁
    • 等到什么时候,执行一句代码 o = null
      • 此时, o 变量比在关联在 xxff00 函数执行空间中的 xxff11 对象地址
      • 那么,这个时候函数执行空间 xxff00 就销毁了

闭包

闭包概念

  • 闭包的概念:闭包就是能够读取其他函数内部变量的函数。或者把闭包理解成"定义在一个函数内部的函数"。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- 自执行函数不会污染全局IIFE -->
    闭包:
    1.需要直接或者间接的返回一个函数
    2.内部函数引用 外部函数的局部变量
    好处:延长变量的生命周期 在函数外部可以使用函数内部的变量
    缺点"执行空间不会被销毁,如果大量使用会造成内存泄漏
    <script>
        //1. 利用了不销毁的空间(直接或者间接)
        // 2.内部函数需要访问 外部函数的局部变量(函数嵌套函数)
        // function outer(){
        //     let a=10
        //     let obj={
        //         name:"outer函数",
        //         age:18
        //     }
        //     function inner(){
        //         // console.log(a);
        //         // console.log(obj);
        //         return obj
        //     }
        //     // 1.. 利用了不销毁的空间(直接或者间接)
        //     return inner;
        // }
        // let a=outer();//outer的函数 return inner;有一个不被销毁的空间
 
        // console.log(a());//1次
        //  console.log(a());//2次
        //  console.log(a());//3次
        //  console.log(a());//4次
        // let num=a();//重新开辟的执行空间
        // let num1=a()
        // console.log(num);

        // 闭包就是能够读取其他函数内部的变量的函数
        // 可以把闭包理解成"定义在一个函数的内部的函数"


        // 闭包不是刻意为之,自然而然会发生的
        // 简单来讲,需要函数套函数,需要访问内部函数的变量
        // 需要return
        

        // for(var i=1;i<=5;i++){//for循环同步的
        //     setTimeout(function(){//是异步的
        //         console.log(i);//5个6
        //     },1000)
        // }

        // for(let i=1;i<=5;i++){
        //     setTimeout(function(){
        //         console.log(i);//let具有块级作用域,所以声明的变量都会被绑定在块里面
        //     },1000)
        // }


         for(var i=1;i<=5;i++){
            function fn(i){//参数类似于函数内的变量,i是一直存在,根本没有被销毁
                //var i
                window.setTimeout(function(){
                    // 定时器返回值是一个数字,没有不被销毁的执行空间,只有函数嵌套函数,内部函数是用来外部函数的变量
                    console.log(i);
                },0)
            }
            fn(i)
        }


    </script>


  </body>
</html>

永不销毁的空间

  • 闭包的第一个条件就是利用了不销毁空间的逻辑

  • 只不过不是返回一个 对象数据类型

  • 而是返回一个 函数数据类型

    function fn() {
        
      return function () {}
    }
    
    const f = fn()
    
    • f 变量接受的就是一个 fn的执行空间 中的 函数

内部函数引用外部函数中的变量

  • 涉及到两个函数

  • 内部函数要查看或者使用着外部函数的变量

    function fn() {
      const num = 100
      
      // 这个函数给一个名字,方便写笔记
      return function a() {
        console.log(num)
      }
    }
    
    const f = fn()
    
    • fn() 的时候会生成一个 xxff00 的执行空间
    • xxff00 这个执行空间内部,定义了一个 a 函数的 存储空间 xxff11
    • 全局 f 变量接受的就是 xxff00 里面的 xxff11
    • 所以 xxff00 就是不会销毁的空间
    • 因为 xxff00 不会销毁,所以,定义再里面的变量 num 也不会销毁
    • 将来 f() 的时候,就能访问到 num 变量

闭包的特点

  • 为什么要叫做特点,就是因为他的每一个点都是优点同时也是缺点
    1. 作用域空间不销毁
      • 优点: 因为不销毁,变量也不会销毁,增加了变量的生命周期
      • 缺点: 因为不销毁,会一直占用内存,多了以后就会导致内存溢出
    2. 可以利用闭包在一个函数外部访问函数内部的变量
      • 优点: 可以在函数外部访问内部数据
      • 缺点: 必须要时刻保持引用,导致函数执行栈不被销毁
    3. 保护私有变量
      • 优点: 可以把一些变量放在函数里面,不会污染全局
      • 缺点: 要利用闭包函数才能访问,不是很方便

闭包梳理

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <ul>
        <li>11111</li>
        <li>2222</li>
        <li>3333</li>
        <li>44444</li>
        <li>5555</li>
    </ul>
    梳理闭包的形成
    1.不被销毁的空间
    2.函数内部返回一个引用的数据类型(不是能基本类型)
    3.函数外部有变量在接受

    闭包形成的条件
    1.需要不被销毁的空间
    2.函数套函数
    3.内部的函数使用外部函数的变量

    特点:
    延长变量的作用域,同时一直占着内存,可以手动销毁(null)
    浏览器的垃圾回收机制
      <!-- 什么是垃圾回收
    垃圾回收机制也称Garbage Collection简称GC。在JavaScript中拥有自动的垃圾回收机制,通过一些回收算法,找出不再使用引用的变量或属性,由JS引擎按照固定时间间隔周期性的释放其所占的内存空间。在C/C++中需要程序员手动完成垃圾回收。

    垃圾产生
    当一个对象没有任何的变量或属性对它进行引用,此时我们将永远无法操作该对象,这种对象就是一个垃圾,这种对象过多会占用大量的内存空间,导致程序变慢。 -->


    <!-- 使用场景 -->
    <!-- 利用闭包获取循环变量值.(闭包获取到元素对象的索引) -->
    <script>
        var list=document.querySelectorAll('li');
        for(let i=0;i<list.length;i++){//i: 01234
            // list[i].οnclick=function(){//循环结束之后,才能访问事件
            //     console.log(i);//6
            // }

            // !function(i){//i:相当于函数的内部的变量 i不会
            //     list[i].οnclick=function(){//循环结束之后,才能访问事件
            //         console.log(i);//6
            //     }
            // }(i)//实参
        }
    </script>

</body>
</html>

二十五、对象详解和深浅拷贝

Json对象

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    1.Json对象
    2.值传递和地址传递
    3.深浅拷贝
    4.Object.defineproperty
    5.数据劫持

    <script>
        // 1.json的特点:
        // json里面的字符串都需要双引号
        // json不能包js相关的语法
        // json描述数据 由简单值+"对象"+"数组"组成
        // json是轻量级数据交换格式,不是js独有的
        // 2.在线json检查的网站
        // https://www.bejson.com/
        // 3.json格式的工具 json-handler 扩展程序
        // 4.json的静态方法
        // JSON.parse()
        let str=`{
            "success":"1",
            "result":{
                "status":"ATT",
                "phone":"1234567",
                "ctype":"中国移动"
            }
        }`
        let obj=JSON.parse(str)
        console.log(obj);

        // 注意:JSON.parse()方法具有功能,如果数据不是json会报错
        // let obj1='{a:1,b:2}'
        // console.log(eval('('+obj1+')'));//转成对象
        // console.log(JSON.parse(obj1));

        // JSON.stringify():用于从一个对象里解析json格式的字符串
        let obj2={
            a:1,
            b:2,
            c:3
        }
        console.log(JSON.stringify(obj2));//{"a":1,"b":2,"c":3}




    </script>

</body>
</html>

JSON的静态方法:

JSON.parse()
JSON.stringify():用于从一个对象里解析json格式字符串

值传递和引用地址传递

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    值传递
    1.基本数据类型
    核心:值传递将基本类型的值copy,赋值操作
    <script>
        let a=10;
        let b=a;//将a 的值copy一份,赋值给了b a和b两个东西
        b++;
        console.log(a,b);//10 11
    </script>

    引用传递
    1.复杂类型 址传递
    <script>
        let arr1=[1,2,3,4];
        let arr2=arr1;//将arr1的地址赋址给了aarr2,arr1和arr2指向的相同的地址,引用类型值通过地址去找对应的值
        arr2.push(5);
        console.log(arr1);//[1,2,3,4,5]
        console.log(arr2);///[1,2,3,4,5]
    </script>.深浅拷贝 copy 解决引用传递的问题

    浅拷贝:仅支持一层拷贝,对象的里面的值赋值给两一个对象,但是如果嵌套,浅拷贝实现不了
    深拷贝:支持多层拷贝
</body>
</html>

对象的深浅拷贝

javascript变量包含两种不同数据类型的值:基本类型和引用类型。

基本类型值指的是简单的数据段,包括新增的一共是有7种,具体如下:

  number、string、boolean、null、undefined、(新增symbol、新增BigInt)

引用类型值指那些可能由多个值构成的对象,只有一种如下:

  object

在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值。基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。

引用类型的值是保存在内存中的对象。与其他语言不同,JavaScript 不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。 在操作对象时, 实际上是在操作对象的引用(地址)而不是实际的对象。

javascript的变量的存储方式–栈(stack)和堆(heap)

  • 栈:自动分配内存空间,系统自动释放,里面存放的是基本类型的值和引用类型的地址
  • 堆:动态分配的内存,大小不定,也不会自动释放。里面存放引用类型的值。

javascript值传递与址传递

基本类型与引用类型最大的区别实际就是传值与传址的区别

值传递:基本类型采用的是值传递。
基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中

从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本

let a = 10;//定义变量a
let b = a;//创建一个a值的副本,然后将a的值赋值给b(基本类型:值传递)
b++;
console.log(a); //10
console.log(b); //11

址传递:引用类型则是址传递,将存放在栈内存中的地址赋值给接收的变量。
引用类型的值是对象,保存在堆内存中
包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针(地址)
从一个变量向另一个变量复制引用类型的值,复制的其实是指针(地址),因此两个变量最终都指向同一个对象

let a = [1, 2, 3];
let b = a; //引用传递,传递的是a的地址
b.push(4); //b数组追加一个值4
console.log(a); //[1,2,3,4]
console.log(b); //[1,2,3,4]

分析:由于a和b都是引用类型,采用的是地址传递,即a将地址传递给b,那么a和b必然指向同一个地址(引用类型的地址存放在栈内存中),而这个地址都

指向了堆内存中引用类型的值。当b改变了这个值的同时,因为a的地址也指向了这个值,故a的值也跟着变化。

就好比是a租了一间房,将房间的地址给了b,b通过地址找到了房间,那么b对房间做的任何改变(添加了一些绿色植物)对a来说肯定同样是可见的。

那么如何解决对象间的赋值问题,通常使用浅拷贝或者深拷贝了来进行对象间的赋值操作。

浅拷贝解决方式:

  • 浅拷贝:将一份数据拷贝到另一个变量中,修改第一层数据不会互相影响,但是修改第二层数据时就会互相影响

  • 逐个赋值

function shallowCopy(obj1, obj2) { //obj1:拷贝的对象  obj2:接收赋值的对象
    for (let key in obj1) { //遍历拷贝的对象obj1
        if (obj1.hasOwnProperty(key)) { //key属于obj1下面的属性或者索引
        	obj2[key] = obj1[key]; //逐个赋值
        }
    }
}
let arr1 = [1, 2, 3];
let arr2 = [];
shallowCopy(arr1, arr2);
arr2.push(4)
console.log(arr1); //[1, 2, 3]
console.log(arr2); //[1, 2, 3, 4]

如果是单纯的数组或者类数组可以采用扩展运算符进行浅拷贝
function shallowCopy(obj1) { //obj1:拷贝的对象
    return [...obj1]
}
let arr1 = [1, 2, 3];
let arr2 = shallowCopy(arr1);

arr2.push(4)
console.log(arr1); //[1, 2, 3]
console.log(arr2); //[1, 2, 3, 4]
  • Object.assign:ES6新增的方法

  • 用于对象的合并,将源对象的所有可枚举属性,复制到目标对象

  • 但上面代码只能实现一层的拷贝,无法进行深层次的拷贝,封装函数再次通过对象数组嵌套测试如下:

function shallowCopy(obj1, obj2) { //obj1:拷贝的对象  obj2:接收赋值的对象
    for (let key in obj1) { //遍历拷贝的对象obj1
        if (obj1.hasOwnProperty(key)) { //key属于obj1下面的属性或者索引
        	Object.assign(obj2, obj1);
        }
    }
}

let obj1 = {
    fruits: ['apple', 'banana'],
    names: ['zhangsan', 'lisi']
};
let obj2 = {};
shallowCopy(obj1, obj2);
obj2.fruits[0] = 'orange';
console.log(obj1.fruits[0]); //orange
console.log(obj2.fruits[0]); //orange

结果证明,无法进行深层次的拷贝,这个时候我们可以使用深拷贝来完成,所谓深拷贝,就是能够实现真正意义上的数组和对象的拷贝

深拷贝的方式代码封装实现如下:

  • 深拷贝:将一份数据拷贝到另一个变量当中,不管修改那一层,两个对象之间互相之间都不会影响

  • 递归遍历方式

function deepcopy(obj) { //obj:传值对象
    //判断obj是否是一个对象(包括数组对象[]和自定义对象{})
    if (typeof obj !== 'object' || obj === null) return obj; //不是数组和对象直接返回
    let result = Array.isArray(obj) ? [] : {}; 
    for (let key in obj) { 
        if (obj.hasOwnProperty(key)) { //防止属性继承自原型链
             result[key] = (typeof obj[key] === 'object' && obj[key] !== null) ? deepCopy(obj[key]) : obj[key];//递归操作,解决对象的属性值还是对象
        }
    }
    return result; //返回拷贝的对象
}
var obj1 = {
	fruits: ['apple', 'banner']
}
var obj2 = deepCopy(obj1);
obj2.fruits[0] = 'orange'
console.log(obj2.fruits[0]); //orange
console.log(obj1.fruits[0]); //apple

结果证明上面的代码可以实现深层次的克隆。

  • 还有一种简单的原生方式,用JSON.stringify
// 简洁封装 - 大部分数据都可以使用,除非属性值是undefiend或者函数,或者日期对象
function deepCopy(obj) {
	return JSON.parse(JSON.stringify(obj))
}

var obj1 = {
    fruits: ['apple', 'banner'],
    num: 100
}
var obj2 = deepCopy(obj1);
obj2.fruits[0] = 'orange'
console.log(obj2.fruits[0]); //orange
console.log(obj1.fruits[0]); //apple


Object.defineProperty

  • 数据劫持定义:指的是在访问或者修改对象的某个属性时,通过一段代码(getter/setter)拦截这个行为,进行额外的操作或者修改返回结果。

  • 数据双向绑定原理:vue 2.x 使用的是Object.defineProperty()(Vue 在 3.x 版本之后改用Proxy 进行实现)。

对象的定义与赋值

let Person = {};
Person.name = 'zhangsan';
Person['sex'] = 'men';
console.log(Person.name); //zhangsan
console.log(Person.sex); //man
  • 经常使用的定义与赋值方式obj.prop =value或者obj['prop']=value

Object.defineProperty()语法说明

  • Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性
Object.defineProperty(obj, prop, desc)
  • obj 需要定义属性的当前对象

  • prop 当前需要定义的属性名

  • desc 属性描述符

  • 一般给对象都可以自由赋值属性,对对象的属性可以修改也可以删除,但是通过Object.defineProperty()定义属性,通过描述符的设置可以进行更精准的控制对象属性。

属性描述符

  • value : 属性对应的值,可以使任意类型的值,默认为undefined

  • writable:属性的值是否可以被重写。设置为true可以被重写;设置为false,不能被重写。默认为false。

  • configurable 描述属性是否配置,以及可否删除

  • enumerable 描述属性是否会出现在for in 或者 Object.keys()的遍历中

let Person = {};
Object.defineProperty(Person, 'name', {
	name: 'jack'
});
Person.name = 'rose';
//writable 默认是false,不能改变属性的值
console.log(Person.name); //undefined
let Person = {};
Object.defineProperty(Person, 'name', {
    name: 'jack',
    writable:true
});
Person.name = 'rose';
console.log(Person.name); //rose
let Person = {};
Object.defineProperty(Person, 'name', {
    value: 'jack',
    configurable: false,
    writable: true
});
Object.defineProperty(Person, 'name', {
    value: 'rose',
});
let Person = {};
Object.defineProperty(Person, 'name', {
    value: 'jack',
    configurable: true,
    writable: false,
});

Object.defineProperty(Person, 'name', {
	value: 'rose',
});
//通过属性定义的形式可以修改name的属性值
console.log(Person.name); //rose
//通过赋值的形式,不可以修改,因为writable为false
Person.name = 'tom';
console.log(Person.name); //rose
let Person = {};
Object.defineProperty(Person, 'name', {
    value: 'jack',
    configurable: false,
    writable: true,
});

Object.defineProperty(Person, 'name', {
	value: 'rose',
});
//通过属性定义的形式可以修改name的属性值
console.log(Person.name); //rose
//通过赋值的形式,可以修改,因为writable为true
Person.name = 'tom';
console.log(Person.name); //tom
let Person = {};
Object.defineProperty(Person, 'name', {
    value: 'jack',
    enumerable: false
});

Person.sex = 'men';

Object.defineProperty(Person, 'age', {
    value: '26',
    enumerable: true
});
console.log(Object.keys(Person)); //['sex','age']
for (let k in Person) {
	console.log(k);
}
// sex,age
let Person = {};
Person.sex = 'men';
//等价于
Object.defineProperty(Person, 'sex', {
    value: 'men',
    configurable:true,
    writable:true,
    enumerable: true
});
Object.defineProperty(Person, 'age', {
	value: '26'
});
//等价于
Object.defineProperty(Person, 'age', {
    value: '26',
    configurable:false,
    writable:false,
    enumerable: false
});

存取器描述:定义属性如何被存取

最后还有最重要的两个属性 set和get,这两个属性是做什么用的呢?

注意:当使用了getter或setter方法,不允许使用writable和value这两个属性(如果使用,会直接报错滴)

get 是获取值的时候的方法,类型为 function ,获取值的时候会被调用,不设置时为 undefined

set 是设置值的时候的方法,类型为 function ,设置值的时候会被调用,undefined

get或set不是必须成对出现,任写其一就可以

let Person = {};
let temp = null;
Object.defineProperty(Person, 'name', {
    get: function() {
    	return temp;
    },
    set: function(val) {
    	temp = val;
    }
});
Person.name = 'jack';
console.log(Person.name); //jack
<input id="box" type="text" />
<div id="content"></div>

var obj = {};
box.addEventListener("input", function() {
    obj.value = this.value; //写入属性值触发set
});
Object.defineProperty(obj, "value", {
    get() {
        return "";
    },
    set(v) {
        content.innerHTML = v; //v写入的值
    },
});

数据劫持

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <input type="text" id="inp1" >

    <div class="box"></div>
    <script>
        // 一,数据劫持
        // 指的是在访问或者修改某个对象的某个属性的时候,通过一段代码(getter,setter)拦截这个行为,进行一些额外的操作或者修改返回结果
        // 理解:将原始的数据复制一份,通过复制的数据操作原始的数据
        const box=document.querySelector('.box');
        const obj={//原始对象
            name:"zhangsan",
            age:19,
            sex:"women"
        }
        const target={} //目标对象
        Object.defineProperty(target,'name',{
            // 将原始的数据复制一份,利用劫持的行为获取对应的原始对象的值
            get(){//获取
                return obj.name;
            },
            set(val){
                obj.name=val;
                box.innerHTML=`我的姓名是${target.name},我今年是${target.age},我是${target.sex}` 
            }
        })
        Object.defineProperty(target,'age',{
            // 将原始的数据复制一份,利用劫持的行为获取对应的原始对象的值
            get(){//获取
                return obj.age;
            },
            set(val){
                obj.age=val;
                box.innerHTML=`我的姓名是${target.name},我今年是${target.age},我是${target.sex}` 
            }
        })
        Object.defineProperty(target,'sex',{
            // 将原始的数据复制一份,利用劫持的行为获取对应的原始对象的值
            get(){//获取
                return obj.sex;
            },
            set(val){
                obj.sex=val;
                box.innerHTML=`我的姓名是${target.name},我今年是${target.age},我是${target.sex}` 
            }
        })
        console.log(target);

        inp1.addEventListener("input", function() {
            target.value = this.value; //写入属性值触发set
        });
        Object.defineProperty(target,'value',{
            // 将原始的数据复制一份,利用劫持的行为获取对应的原始对象的值
            get(){//获取
                console.log("get");
                // return val;
            },
            set(val){
                box.innerHTML=val
            }
        })
        console.log(target.value);

        
    </script>
</body>
</html>

函数的节流与防抖

1.节流:

事件在执行的时候,从第一次开始执行时,在第一次执行结束之前或者在指定时间之间前,无法触发下一次
除非等到第一次执行结束或者在指定的时间到达后,才可以进行下一次
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <input type="text" class="inp" />
    <script>
      // const inp=document.querySelector('.inp');
      // inp.oninput=function(){
      //     console.log(this.value);
      // }

      // 1.节流
      // let flag=true
      // inp.oninput=function(){
      //     if(flag===false) return
      //     flag=false

      //     setTimeout(()=>{
      //         flag=true
      //         console.log(this.value);
      //     },300)
      // }

      // 2.自执行函数里面
      // !(function (num) {
      //     console.log("哈哈哈",num);
      // })(99)

      // 3.节流代码的优化
         const inp = document.querySelector(".inp");
         inp.oninput = (function (flag) {
           return function () {
             // 使用flag的时候,现在{}里面去找,作用域么有,往上,然后去上一级做那作用域,找到flag,在自执行函数里,初始值为true

             if (flag === false) return;
             flag = false;

             setTimeout(() => {
               flag = true;
               console.log(this.value);
             }, 300);
           };----
         })(true);
    </script>
  </body>
</html>

2.防抖:

事件在开始执行的时候,如果快速触发了第二次,那么第二次取代第一次,只会执行最后一次
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <input type="text" class="inp" />
    

    <!-- 防抖  -->
    <script>
      const inp = document.querySelector(".inp");
      // inp.oninput=function(){
      //     console.log(this.value);
      // }

      // 2.防抖
      // let timer=0
      // inp.oninput=function(){
      //     clearInterval(timer)

      //     timer= setTimeout(()=>{
      //         console.log(this.value);
      //      },300)

      // }

      // 3.封装
      inp.oninput = (function (timer) {
        return function () {
          clearInterval(timer);

          timer = setTimeout(() => {
            console.log(this.value);
          }, 300);
        };
      })(0);
	  
    </script>
  </body>
</html>

深浅拷贝,节流和防抖的封装

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        // 浅拷贝的封装:将一份数据拷贝到另一个变量中,修改第一层数据不会互相影响,但是修改第二层数据时就会互相影响
        function shallowCopy(obj1,obj2){
            for(let key in obj1){
                if (obj1.hsaOwnProperty(key)) {
                    obj2[key] = obj1[key]
                }
            }
        }

        // 深拷贝的封装:将一份数据拷贝到另一个变量当中,不管修改那一层,两个对象之间互相之间都不会影响
        function deepCopy(obj){
            return JSON.parse(JSON.stringify(obj))
        }
    </script>

    <script>
        // 函数的节流:指的是事件从第一次开始执行,在结束之前和指定时间之前,无法触发下一次。
        
        function fn(flag){
            return function(){
                if (flag === false) return

                flag = false
                setTimeout(()=>{
                    flag = true
                    console.log('节流')
                },300)
            }
        }

        // 函数的防抖:事件在开始执行时,如果快速触发了第二次,那么第二次取代第一次,只会执行最后一次
        function fn(timer){
            return function(){
                clearTimeout(timer)

                timer = setTimeout(()=>{
                    console.log('防抖')
                },300)
            }
        }
    </script>
</body>
</html>

二十六、设计模式

  • 设计模式是我们在 解决问题的时候针对特定问题给出的简洁而优化的处理方案
  • 我们有很多的设计模式
    • 单例模式
    • 组合模式
    • 观察者模式
  • 今天我们就聊一下这三个设计模式
    • 单例模式 / 组合模式 / 观察者模式

单例模式

  • 什么是单例模式呢?
  • 我们都知道,构造函数可以创造一个对象
  • 我们 new 很多次构造函数就能得到很多的对象
  • 单例模式: 就是使用构造函数实例化的时候,不管实例化多少回,都是同一个对象
    • 也就是一个构造函数一生只能 new 出一个对象
  • 也就是说,当我们使用构造函数,每一次 new 出来的对象 属性/功能/方法 完全一样 的时候,我们把他设计成单例模式

核心代码

  • 单例模式的核心代码很简单

  • 其实就是判断一下,他曾经有没有 new 出来过对象

  • 如果有,就还继续使用之前的那个对象,如果没有,那么就给你 new 一个

    // 准备一个构造函数
    // 将来要 new 的
    function Person() {}
    
    // 准备一个单例模式函数
    // 这个单例模式函数要把 Person 做成一个单例模式
    // 将来再想要 new Person 的时候只要执行这个 singleton 函数就可以了
    function singleton () {
      let instance
      
      if (!instance) { // 如果 instance 没有内容
        // 来到这里,证明 instance 没有内容
        // 给他赋值为 new Person
          instance = new Person()
      }
      
      // 返回的永远都是第一次 new Person 的实例
      // 也就是永远都是一个实例
      return instance
    }
    
    const p1 = singleton()
    const p2 = singleton()
    console.log(p1 === p2) // true
    

应用

  • 我们就用这个核心代码简单书写一个 demo

    // 这个构造函数的功能就是创建一个 div,添加到页面中
    function CreateDiv() {
        this.div = document.createElement('div')
        document.body.appendChild(this.div)
    }
    
    CreateDiv.prototype.init = function (text) {
        this.div.innerHTML = text
    }
    
    // 准备把这个 CreateDiv 做成单例模式
    // 让 singleton 成为一个闭包函数
    const singleton = (function () {
    
        let instance
    
        return function (text) {
            if (!instance) {
                instance = new CreateDiv()
            }
            instance.init(text)
            return instance
        }
    })()
    
    singleton('hello') // 第一次的时候,页面中会出现一个新的 div ,内容是 hello
    singleton('world') // 第二次的时候,不会出现新的 div,而是原先的 div 内容变成了 world
    

组合模式

  • 组合模式,就是把几个构造函数的启动方式组合再一起

  • 然后用一个 ”遥控器“ 进行统一调用

    class GetHome {
    
        init () {
            console.log('到家了')
        }
    }
    
    class OpenComputer {
    
        init () {
            console.log('打开电脑')
        }
    }
    
    class PlayGame {
    
        init () {
            console.log('玩游戏')
        }
    }
    
    • 上面几个构造函数的创造的实例化对象的 启动方式 都一致
    • 那么我们就可以把这几个函数以组合模式的情况书写
    • 然后统一启动
  • 准备一个 组合模式 的构造函数

    class Compose {
        constructor () {
            this.compose = []
        }
        	
        // 添加任务的方法
        add (task) {
            this.compose.push(task)
        }
        
        // 一个执行任务的方法
        execute () {
            this.compose.forEach(item => {
                item.init()
            })
        }
    }
    
  • 我们就用我们的组合模式构造函数来吧前面的几个功能组合起来

    const c = new Compose()
    // 把所有要完成的任务都放在队列里面
    c.add(new GetHome())
    c.add(new OpenComputer)
    c.add(new PlayGame)
    
    // 直接器动任务队列
    c.execute()
    // 就会按照顺序执行三个对象中的 init 函数
    

观察者模式

  • 观察者模式,通常也被叫做 发布-订阅模式 或者 消息模式
  • 英文名称叫做 Observer
  • 官方解释: 当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,解决了主体对象与观察者之间功能的耦合,即一个对象状态改变给其他对象通知的问题
  • 听起来很迷糊,但是其实没有很难

一个例子

  • 当你想去书店买书,但是恰巧今天你要买的书没有了
  • 我们又不能总在书店等着,就把我们的手机留给店员
  • 当你需要的书到了的时候,他会打电话通知你,你去买了就好了
  • 你买到数了以后,就告诉他,我买到了,那么以后再来了书就不会通知你了

addEventListener

  • 上面的例子可能还不是很明确

  • 但是 addEventListener 是一个我们都用过的东西

  • 这个东西其实就是一个标准的 观察者模式

    btn.addEventListener('click', function () {
        console.log('btn 被点击了')
    })
    
    • 上面这个就是有一个 无形的观察者 再观察着 btn 的一举一动
    • 当这个 btn 被点击的时候,就会执行 对应的函数
    • 我们也可以多绑定几个函数
  • 说白了: 观察者模式就是我们自己实现一个 addEventListener 的功能

    • 只不过 addEventListaner 只有固定的一些事件,而且只能给 dom 元素绑定
    • 而我们自己写的可以随便绑定一个事件名称,自己选择触发时机而已

书写代码

  • 首先我们分析功能

    • 我们要有一个观察者(这里抽象为一个对象 {}

    • 需要有一个属性,存放消息的盒子(把你绑定的所有事件放在里面)

    • 需要一个 on 方法,用于添加事件

    • 需要一个 emit 方法,用于发布事件(触发)

    • 需要一个 off 方法,把已经添加的方法取消

    const observer = {
        message: {},
        on: function () {},
        emit: function () {},
        off: function () {}
    }
  • 我们把它写成一个构造函数的形式
    class Observer {
        constructor () {
            this.message = {}
        }
        
        on () {}
        
        emit () {}
        
        off () {}
    }
  • 现在,一个观察者的雏形就出来了

  • 接下来完善方法就可以了

ON
  • 先来写 ON 方法

  • 添加一个事件

  • 我们的 on 方法需要接受 两个参数

    • 事件类型
    • 事件处理函数
  class Observer {
      constructor () {
          this.message = {}
      }
      
      on (type, fn) {
          // 判断消息盒子里面有没有设置事件类型
          if (!this.message[type]) {
              // 证明消息盒子里面没有这个事件类型
              // 那么我们直接添加进去
              // 并且让他的值是一个数组,再数组里面放上事件处理函数
              this.message[type] = [fn]
          } else {
              // 证明消息盒子里面有这个事件类型
              // 那么我们直接向数组里面追加事件处理函数就行了
              this.message[type].push(fn)
          }
      }
      
      emit () {}
      
      off () {}
  }
EMIT
  • 接下来就是发布事件

  • 也就是让我们已经订阅好的事件执行一下

  • 同样需要接受两个参数

    • 要触发的事件类型
    • 给事件处理函数传递的参数
  class Observer {
      constructor () {
          this.message = {}
      }
      
      on (type, fn) {
          // 判断消息盒子里面有没有设置事件类型
          if (!this.message[type]) {
              // 证明消息盒子里面没有这个事件类型
              // 那么我们直接添加进去
              // 并且让他的值是一个数组,再数组里面放上事件处理函数
              this.message[type] = [fn]
          } else {
              // 证明消息盒子里面有这个事件类型
              // 那么我们直接向数组里面追加事件处理函数就行了
              this.message[type].push(fn)
          }
      }
      
      emit (type, ...arg) {
          // 判断你之前有没有订阅过这个事件
          if (!this.message[type]) return
  
          // 如果有,那么我们就处理一下参数
          const event = {
              type: type,
              arg: arg || {}
          }
  
          // 循环执行为当前事件类型订阅的所有事件处理函数
          this.message[type].forEach(item => {
              item.call(this, event)
          })
      }
      
      off () {}
  }
  
OFF
  • 最后就是移除事件

  • 就是把已经订阅的事件处理函数移除掉

  • 同样需要接受两个参数

    • 要移除的事件类型
    • 要移除的事件处理函数
  class Observer {
      constructor () {
          this.message = {}
      }
      
      on (type, fn) {
          // 判断消息盒子里面有没有设置事件类型
          if (!this.message[type]) {
              // 证明消息盒子里面没有这个事件类型
              // 那么我们直接添加进去
              // 并且让他的值是一个数组,再数组里面放上事件处理函数
              this.message[type] = [fn]
          } else {
              // 证明消息盒子里面有这个事件类型
              // 那么我们直接向数组里面追加事件处理函数就行了
              this.message[type].push(fn)
          }
      }
      
      emit (type, ...arg) {
          // 判断你之前有没有订阅过这个事件
          if (!this.message[type]) return
  
          // 如果有,那么我们就处理一下参数
          const event = {
              type: type,
              arg: arg || {}
          }
  
          // 循环执行为当前事件类型订阅的所有事件处理函数
          this.message[type].forEach(item => {
              item.call(this, event)
          })
      }
      
      off (type, fn) {
          // 判断你之前有没有订阅过这个事件
          if (!this.message[type]) return
  
          // 如果有我们再进行移除
          for (let i = 0; i < this.message[type].length; i++) {
              const item =  this.message[type][i]
              if (item === fn) {
                  this.message[type].splice(i, 1)
                  i--
              }
          }
      }
  }
  
  • 以上就是最基本的 观察者模式

  • 接下来我们就使用一下试试看

使用一下
const o = new Observer()

// 准备两个事件处理函数
function a(e) {
    console.log('hello')
}

function b(e) {
    console.log('world')
}

// 订阅事件
o.on('abc', a)
o.on('abc', b)

// 发布事件(触发)
o.emit('abc', '100', '200', '300') // 两个函数都回执行

// 移除事件
o.off('abc', 'b')

// 再次发布事件(触发)
o.emit('abc', '100', '200', '300') // 只执行一个 a 函数了

二十七、基于SPA的单页面路由

  • 什么是路由和前端路由

    • 路由(英文:router)就是对应关系
    • 路由的概念来源于服务端,在服务端中路由描述的是 URL 与处理函数之间的映射关系
    • 前端路由就是浏览器地址栏中的 URL 与所见网页的对应关系(Hash地址与组件之间的对应关系)
      • 组件(英文:components):指对具体的某个功能进行封装,来达到组件复用,提高开发效率。
  • 什么是SPA

    • SPA(single page application),翻译过来就是单页应用,SPA是一种网络应用程序或网站的模型,它通过动态重写当前页面来与用户交互,这种方法避免了页面之间切换打断用户体验
    • 在单页应用中,所有必要的代码(HTML、JavaScript和CSS)都通过单个页面的加载而检索,或者根据需要(通常是为响应用户操作)动态装载适当的资源并添加到页面
    • 页面在任何时间点都不会重新加载(刷新),也不会将控制转移到其他页面
    • 我们熟知的JS框架如react,vue,angular都属于SPA
    • 举个例子来讲就是一个杯子,早上装的牛奶,中午装的是开水,晚上装的是茶,我们发现,变的始终是杯子里的内容,而杯子始终是那个杯子
  • 如何实现前端路由?要实现前端路由,需要解决两个核心:

    • 如何改变 URL 却不引起页面刷新?
    • 如何检测 URL 变化了?
  • 实现路由的方式有两种:

    • 依赖 hash 的改变(锚点)
    • 依赖历史记录(history)

hash 实现

  • 当一个窗口的hash (URL 中 # 后面的部分)值改变时就会触发 hashchange 事件。

  • 改变 URL 中的 hash 部分不会引起页面刷新。

  • 触发 hashchange 事件的几种方式:通过浏览器前进后退改变 URL、通过a标签改变 URL、通过window.location改变URL。

<body>
  <ul>
    <!-- 定义路由 -->
    <li><a href="#/home">home</a></li>
    <li><a href="#/about">about</a></li>
 
    <!-- 渲染路由对应的 UI -->
    <div id="routeView"></div>
  </ul>
</body>
// 监听路由变化
window.addEventListener('hashchange', onHashChange)
 
// 路由视图
var routerView = null
 
function onLoad () {
  routerView = document.querySelector('#routeView')
  onHashChange()
}
 
// 路由变化时,根据路由渲染对应 UI
function onHashChange () {
  switch (location.hash) {
    case '#/home':
      routerView.innerHTML = 'Home'
      return
    case '#/about':
      routerView.innerHTML = 'About'
      return
    default:
      return
  }
}

history 实现

  • history 提供了 pushState方法改变 URL 的 path 部分不会引起页面刷新。

  • pushState() 方法三个参数: 一个状态对象(一般为null), 一个标题 (目前被忽略), 和 (可选的) 一个URL.

  • popstate 事件每当处于激活状态的历史记录条目发生变化时,popstate事件就会在对应window对象上触发.

  • 需要后端配合

二十八、cookie和本地存储

cookie

概念:

Cookie又叫会话跟踪技术是由Web服务器保存在用户浏览器上的小文本文件,它可以包含相关用户的信息。
小甜饼 cookie的大小, 不能超过4k

服务器:一台电脑,服务器->ip(域名)--进行访问
本地服务器:live-server 是vscode里面web静态服务器插件

cookie的特点

  • 禁用Cookie后,无法正常注册登陆。

  • cookie是与浏览器相关的,不同浏览器之间所保存的cookie也是不能互相访问的;

  • cookie可以被删除。因为每个cookie都是硬盘上的一个文件;

  • cookie安全性不够高-xss攻击

cookie的查看

控制面板–application—左侧侧边栏—Cookie
热更新–实时加载

Application面板介绍

- 通过控制面板--application--左侧cookie查看浏览器存储的cookie
  - name:自定义的键值
  - value:键对应的值
  - domain:主机(主域名)
  - path:路径
  - expires:过期时间
  - size: cookie的大小, 不能超过4k
  - httpOnly:跟后端关系比较密切,如果后端设置httpOnly, js代码无法获取,参数为布尔值。
  - secure:安全,跟http/https设置有关。
  - samesite:防止CSRF攻击和用户追踪,chrome 51新加的属性。

cookie 注意事项

  1.允许跨页面去访问信息,信息会被储存在浏览器里面
  2.cookie存在有效过期时间,
  3.存储的格式都是字符串格式,存储的大小4kb

cookie的使用及封装

Cookie以字符串的形式进行存储的。Key值相同,存储时就会覆盖。

Cookie名称和值可以自己定义,存储的是字符串。不能超过4KB

如果cookie不存在,输出空白.

  • 添加cookie
语法格式:document.cookie=‘key = value ; expires=过期时间 ; path=路径’;
// 封装
const setCookie = function(key, value, day) {
    let d = new Date();
    d.setDate(d.getDate() + day);
    document.cookie = `${key}=${value};expires=${d};path=/`;
};
  • 获取cookie
语法格式:document.cookie 
const getCookie = function(key) {
    let arr = document.cookie.split('; ');
    for (let value of arr) {
        let newArr = value.split('=');
        if (key === newArr[0]) {
            return newArr[1];
        }
    }
};
  • 删除cookie
//将cookie的过期时间设为一个过去的时间。
const delCookie = function(key) {
    setCookie(key, '', -1);
};

xss攻击

  • XSS(Cross Site Script)攻击全称跨站脚本攻击

  • 通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。

  • 这些恶意网页程序通常是JavaScript。

  • 实施XSS攻击需要具备两个条件:

    • 需要向web页面注入恶意代码;
    • 这些恶意代码能够被浏览器成功的执行。
  • 总结:输入过滤、输出转义

localStorage(本地存储)

localStorage的概念和特点

  • 在HTML5中,新加入了一个localStorage对象,这个对象主要是用来作为本地存储来使用的, 即能够在用户浏览器中对数据进行本地的存储
    • localStorage永久存储,除非手动删除
    • localStorage会将5M大小的数据直接存储到本地,但只有在高版本的浏览器中才支持的。
    • 目前所有的浏览器中都会把localStorage的值类型限定为string类型。
    • 不同的浏览器本地存储不能相互进行访问。

localStroage读写及删除操作。

写入操作

window.localStorage.setItem('key',value)   - 写入本地存储

获取操作

window.localStorage.getItem('key') - 获取本地

删除操作

window.localStorage.clear();   将localStorage的所有内容清除    
window.localStorage.removeItem('key');    将localStorage中的某个键值对删除   

对比cookie,localStorage和sessionStorage

1.localStorage和sessionStorage都是本地存储,唯一的区别是localStorage是永久的,
sessionStorage=浏览器关闭就消失
2.大小方法:cookie是4kb localStorage和sessionStorage是5MB
3.存储方式: cookie服务器存储. localStorage和sessionStorage是存在浏览器里面的
4.存储时间:cookie是自己设置过期的时间, localStorage永久的 sessionStorage关闭浏览器就消失
5.兼容问题:cookie没有兼容的, localStorage是sessionStorageh5新增的

二十九、AJAX 网络请求

  • ajax 全名 async javascript and XML
  • 是前后台交互的能力
  • 也就是我们客户端给服务端发送消息的工具,以及接受响应的工具
  • 是一个 默认异步 执行机制的功能

AJAX 的优势

  1. 不需要插件的支持,原生 js 就可以使用
  2. 用户体验好(不需要刷新页面就可以更新数据)
  3. 减轻服务端和带宽的负担
  4. 缺点: 搜索引擎的支持度不够,因为数据都不在页面上,搜索引擎搜索不到

AJAX 的使用

  • 在 js 中有内置的构造函数来创建 ajax 对象
  • 创建 ajax 对象以后,我们就使用 ajax 对象的方法去发送请求和接受响应

AJAX使用的四部曲

1、创建一个 ajax 对象(实例对象),需要类(XMLHttpRequest)

// IE9及以上
const xhr = new XMLHttpRequest()

// IE9以下
const xhr = new ActiveXObject('Mricosoft.XMLHTTP')
  • 上面就是有了一个 ajax 对象
  • 我们就可以使用这个 xhr 对象来发送 ajax 请求了

2、打开接口:配置链接信息,利用xhr对象下面的open方法,打开接口

const xhr = new XMLHttpRequest()

// xhr 对象中的 open 方法是来配置请求信息的
// 第一个参数是本次请求的请求方式 get / post / put / ...
// 第二个参数是本次请求的接口地址 url 
// 第三个参数是本次请求是否异步,默认 true 表示异步,false 表示同步
// xhr.open('请求方式', '请求地址', 是否异步)
xhr.open('get', './data.php')
  • 上面的代码执行完毕以后,本次请求的基本配置信息就写完了

3、发送请求:利用xhr的send方法,发送给服务器,解析请求

const xhr = new XMLHttpRequest()
xhr.open('get', './data.php')

// 使用 xhr 对象中的 send 方法来发送请求
xhr.send()
  • 上面代码是把配置好信息的 ajax 对象发送到服务端

一个基本的 ajax 请求

  • 一个最基本的 ajax 请求就是上面三步
  • 但是光有上面的三个步骤,我们确实能把请求发送的到服务端
  • 如果服务端正常的话,响应也能回到客户端
  • 但是我们拿不到响应
  • 如果想拿到响应,我们有两个前提条件
    1. 本次 HTTP 请求是成功的,也就是我们之前说的 http 状态码为 200 ~ 299
    2. ajax 对象也有自己的状态码,用来表示本次 ajax 请求中各个阶段

4、监听第三步的事件,onreadystatechange当上面的就绪状态码发送改变触发

const xhr = new XMLHttpRequest()
xhr.open('get', './data.php')

// 使用 xhr 对象中的 send 方法来发送请求
xhr.send()

xhr.onreadystatechange=function(){
            if(xhr.readyState===4){//通过xhr.readyState属性,判断解析完成,获取响应
                //获取响应,responseText利用属性响应字符串格式的数据
                console.log(xhr.responseText);
                console.log(JSON.parse(xhr.responseText ));

            }
        }

        // 实际工作的时候,记得,内网的,
        // 网段,域名不一样,要接口域名地址
ajax 状态码
  • ajax 状态码 - xhr.readyState
  • 是用来表示一个 ajax 请求的全部过程中的某一个状态
    • readyState === 0: 表示未初始化完成,也就是 open 方法还没有执行
    • readyState === 1: 表示配置信息已经完成,也就是执行完 open 之后
    • readyState === 2: 表示 send 方法已经执行完成
    • readyState === 3: 表示正在解析响应内容
    • readyState === 4: 表示响应内容已经解析完毕,可以在客户端使用了
  • 这个时候我们就会发现,当一个 ajax 请求的全部过程中,只有当 readyState === 4 的时候,我们才可以正常使用服务端给我们的数据
  • 所以,配合 http 状态码为 200 ~ 299
    • 一个 ajax 对象中有一个成员叫做 xhr.status
    • 这个成员就是记录本次请求的 http 状态码的
  • 两个条件都满足的时候,才是本次请求正常完成
http状态码: 用来表示网页服务器超文本传输协议相应状态码3位数字代码
1开头:表示消息,对通讯没有影响
2开头:成功
3开头:重定向,对通讯没有影响
4开头:前端错误,客户端错误 
5开头:服务端错误 

常见的: 
	101:请求收到,继续处理
	200:请求已经成功,请求所希望响应头或者相应数据,返回,出现状态码正常
	304:如果客户端发送一个带条件的get请求且请求已被允许,而文档上的内容并没有改变,服务器返回这个
	403:服务器理解请求,但是拒绝执行,禁止访问 权限问题
	404:请求失败.请求的资源未在服务器上发现 500:服务器错误
	503:由于临时的服务器维护或者过载,服务器当时无法处理,一般情况下重启服务器可以解决
readyStateChange
  • 在 ajax 对象中有一个事件,叫做 readyStateChange 事件

  • 这个事件是专门用来监听 ajax 对象的 readyState 值改变的的行为

  • 也就是说只要 readyState 的值发生变化了,那么就会触发该事件

  • 所以我们就在这个事件中来监听 ajax 的 readyState 是不是到 4 了

    const xhr = new XMLHttpRequest()
    xhr.open('get', './data.php')
    
    xhr.send()
    
    xhr.onreadyStateChange = function () {
      // 每次 readyState 改变的时候都会触发该事件
      // 我们就在这里判断 readyState 的值是不是到 4
      // 并且 http 的状态码是不是 200 ~ 299
      if (xhr.readyState === 4 && /^2\d{2|$/.test(xhr.status)) {
        // 这里表示验证通过
        // 我们就可以获取服务端给我们响应的内容了
      }
    }
    
responseText
  • ajax 对象中的 responseText 成员

  • 就是用来记录服务端给我们的响应体内容的

  • 所以我们就用这个成员来获取响应体内容就可以

    const xhr = new XMLHttpRequest()
    xhr.open('get', './data.php')
    
    xhr.send()
    
    xhr.onreadyStateChange = function () {
      if (xhr.readyState === 4 && /^2\d{2|$/.test(xhr.status)) {
        // 我们在这里直接打印 xhr.responseText 来查看服务端给我们返回的内容
        console.log(xhr.responseText)
      }
    }
    

使用 ajax 发送请求时携带参数

  • 我们使用 ajax 发送请求也是可以携带参数的
  • 参数就是和后台交互的时候给他的一些信息
  • 但是携带参数 get 和 post 两个方式还是有区别的

发送一个带有参数的 get 请求

  • get 请求的参数就直接在 url 后面进行拼接就可以

    const xhr = new XMLHttpRequest()
    // 直接在地址后面加一个 ?,然后以 key=value 的形式传递
    // 两个数据之间以 & 分割
    xhr.open('get', './data.php?a=100&b=200')
    
    xhr.send()
    
    • 这样服务端就能接受到两个参数
    • 一个是 a,值是 100
    • 一个是 b,值是 200

发送一个带有参数的 post 请求

  • post 请求的参数是携带在请求体中的,所以不需要再 url 后面拼接

    const xhr = new XMLHttpRequest()
    xhr.open('get', './data.php')
    
    // 如果是用 ajax 对象发送 post 请求,必须要先设置一下请求头中的 content-type
    // 告诉一下服务端我给你的是一个什么样子的数据格式
    xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
    
    // 请求体直接再 send 的时候写在 () 里面就行
    // 不需要问号,直接就是 'key=value&key=value' 的形式
    xhr.send('a=100&b=200')
    
    • application/x-www-form-urlencoded 表示的数据格式就是 key=value&key=value

前后端交互

前后端交互
1.前端 :用户可见的
1.1获取接口地址,(带有协议的ip路径信息的完整的底子,包括携带数据),拿到数据,进行渲染
1.2将收集到的数据给后端,后端插入到数据库

2.后端 :管理和维护前端.后端服务器
	2.1制作接口,将接口地址给前端
	2.2接收前端传入的数据,提交给数据库
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        img{width: 200px;height: 200px;}
    </style>
</head>
<body>
    前后端交互
    1.前端 :用户可见的
    1.1获取接口地址,(带有协议的ip路径信息的完整的底子,包括携带数据),拿到数据,进行渲染
    1.2将收集到的数据给后端,后端插入到数据库

    2.后端 :管理和维护前端.后端服务器
    1.1制作接口,将接口地址给前端
    1.2接收前端传入的数据,提交给数据库

    数据格式:JSON格式(字符串格式)


    <!-- <div>
        <li>
            <img src="" >
            <span></span>
        </li>
    </div> -->
    二.渲染页面
    <script>
        let xhr=new XMLHttpRequest();//创建对象
        xhr.open("get","http://localhost:8888/goods/list",true);//打开接口
        xhr.send();
        xhr.onload=function(){
            if(xhr.readyState===4){
                console.log(JSON.parse(xhr.responseText).list);
                let arr=JSON.parse(xhr.responseText).list;
                let str="";
                // console.log(arr);
                for(let value of arr){
                    // console.log(value);
                    // console.log(value.img_big_logo)
                    str+=`
                        <div>
                            <li>
                                <img src="${value.img_big_logo}" >
                                <span>${value.title}</span>
                            </li>
                        </div>
                        `
                }
                document.body.innerHTML=str;
            }
        }

    </script>


</body>
</html>

ajax的异步机制

同步和异步
js是单线程的,只有一个主线程,一次只能执行一个任务,同步和异步指的就是在线程上面的执行代码顺序

1.同步:阻塞模式,后一个任务必要要等前一个任务完成才执行,顺序执行
2.异步:非阻塞模式,异步任务先进入队列等候,等到主线程上面的任务完成,才被通知执行,定时器是异步的
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    同步和异步
    js是单线程的,只有一个主线程,一次只能执行一个任务,同步和异步指的就是在线程上面的执行代码顺序


    1.同步:阻塞模式,后一个任务必要要等前一个任务完成才执行,顺序执行
    2.异步:非阻塞模式,异步任务先进入队列等候,等到主线程上面的任务完成,才被通知执行,定时器是异步的

    <script>
        // console.log(1);
        // let xhr=new XMLHttpRequest();
        // console.log(2);
        // xhr.open('get','http://localhost:8888/goods/list',true);
        // console.log(3);
        // xhr.send();
        // console.log(4);
        // xhr.onload=function(){
        //     console.log(5);
        //     if(xhr.readyState===4){
        //         console.log(xhr.responseText);
        //         console.log(6);
        //     }
        // }
        // console.log(7);
        // 证明:ajax监听事件是异步的,从第四步是异步的


    </script>


<script>
    // 异步是需要事件监听的
    // let xhr=new XMLHttpRequest();//创建对象
    // xhr.open("get","http://localhost:8888/goods/list",true);//打开接口
    // xhr.send();
    // xhr.onload=function(){
    //     if(xhr.readyState===4){
    //         console.log(JSON.parse(xhr.responseText).list);
    //         let arr=JSON.parse(xhr.responseText).list;
    //     }
    // }


    // 同步不需要事件监听,同时也不需要判断
    let xhr=new XMLHttpRequest();//创建对象
    xhr.open("get","http://localhost:8888/goods/list",false);//打开接口
    xhr.send();
    console.log(xhr.responseText);


</script>
</body>
</html>

AJAX的梳理

http请求的三次握手:
1.三次握手—连接 ,相互发送数据
客户端说:“我要向你的服务器端发送数据,(发起请求)”
服务端说:“可以的,但是我也要向你发送数据(建立请求)”
客户端说:“好的,那我们互相发送数据(相互通讯)”

http请求的四次挥手:
1.断开连接
客户端说:“我没有数据了,我要断开连接”
服务端说:“好的.我还有数据没有发送完”
服务端继续发送数据给客户端.直到服务端数据发送完成了,我没有数据了
服务端说:“我数据全发送完了.我空了,我啥也没有了”
客户端说:“好的,那我们就断开连接”

三十、回调函数和AJAX的封装

回调函数

  • 什么是回调函数?

  • 就是把函数 A 当作参数传递到 函数 B 中

  • 在函数 B 中以行参的方式进行调用

  function a(cb) {
    cb()
  }
  
  function b() {
    console.log('我是函数 b')
  }
  
  a(b)
  • 为什么需要回调函数

    • 当我们执行一个异步的行为的时候,我们需要在一个异步行为执行完毕之后做一些事情
    • 那么,我们是没有办法提前预知这个异步行为是什么时候完成的
    • 我们就只能以回调函数的形式来进行
    • 就比如我们刚刚封装过的那个 ajax 函数里面的 success
    • 我们并不知道 ajax 请求什么时候完成,所以就要以回调函数的形式来进行

回调地狱

  • 当一个回调函数嵌套一个回调函数的时候

  • 就会出现一个嵌套结构

  • 当嵌套的多了就会出现回调地狱的情况

  • 比如我们发送三个 ajax 请求

    • 第一个正常发送
    • 第二个请求需要第一个请求的结果中的某一个值作为参数
    • 第三个请求需要第二个请求的结果中的某一个值作为参数
  ajax({
    url: '我是第一个请求',
    success (res) {
      // 现在发送第二个请求
      ajax({
        url: '我是第二个请求'data: { a: res.a, b: res.b },
        success (res2) {
          // 进行第三个请求
          ajax({
            url: '我是第三个请求',
            data: { a: res2.a, b: res2.b },
    				success (res3) { 
              console.log(res3) 
            }
          })
        }
      })
    }
  })
  • 回调地狱,其实就是回调函数嵌套过多导致的

封装 AJAX

  • ajax 使用起来太麻烦,因为每次都要写很多的代码
  • 那么我们就封装一个 ajax 方法来让我们使用起来简单一些

确定一下使用的方式

  • 因为有一些内容可以不传递,我们可以使用默认值,所以选择对象传递参数的方式

    // 使用的时候直接调用,传递一个对象就可以
    ajax({
      url: '', // 请求的地址
      type: '', // 请求方式
      async: '', // 是否异步
      data: '', // 携带的参数
      dataType: '', // 要不要执行 json.parse
      success: function () {} // 成功以后执行的函数
    })
    
    • 确定好使用方式以后,就开始书写封装函数

封装

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    封装ajax 请求地址 请求方式 是否异步 携带的参数  {}直接输出  JSON.parse""
    成功之后要执行的函数
    <script>  
        function ajax(options){//options配置参数,
            // 1.判断请求方式
            options.type= options.type || 'get';//如果传入的options.type就使用,如果没有使用默认的get

            // 2.接口地址
            if(!options.url){
                throw new Error("接口地址必须要存在")//抛出错误,new Error生成一个错误的对象
            }

            // 3.是否异步
            if(options.async==='false' || options.async===false){//同步请求
                options.async=false
            }else{
                options.async=true
            }

            //4.判断数据
            if(options.data && Object.prototype.toString.call(options.data)==='[object Object]'){//数据存在,同时是对象格式
                let arr=[];
                for(let key in options.data){
                    arr.push(key+ "=" + options.data[key]);
                }
                options.data=arr.join("&");
            }

            // 5.数据存在,同时数据可以是get和post方法方式进行发送
            // get方式
            if(options.data && options.type=="get"){
                options.url+="?"+options.data
            }
            let xhr=new XMLHttpRequest()
            xhr.open(options.type,options.url,options.async)

            // post方式
            if(options.data && options.type=="post"){
                xhr.setRequestHeader('content-type','application/x-www-form-urlencoded');
                xhr.send(options.data);
            }else{
                xhr.send()
            }

            //获取接口数据.接收返回值
            xhr.onreadystatechange=function(){//异步数据
                if(xhr.readyState==4){
                    if(/2\d{2}/.test(xhr.status)){
                         //接口地址正确的
                        options.success(xhr.responseText) 
                    }else{
                        options.error('接口地址错误') 
                    }
                }
            }

        }



        ajax({
            type:'get',//请求方式
            url:"http://localhost:8888/goods/list",//接口地址
            data:{//传参的参数,数据
                a:1,
                b:2,
                c:3
            },
            async:"true",//是否异步
            success:function(res){//成功获取数据的方法
                console.log(res);
            },
            error:function(err){
                console.log(err);//接口失败的方法
            }
        })
    </script>
    <br>
    封装函数实现将对象格式的数据转换成ajax执行的&符号拼接的数据
	
	
    <script>
        // const obj={
        //     name:"zhangsan",
        //     age:18,
        //     sex:'男'
        // }
        // let arr=[];
        // for(let key in obj){
        //     arr.push(key+ "=" + obj[key]);
        // }
        // console.log(arr.join("&"));

        // name=zhangsan&age=18&sex=男
    </script>
 


</body>
</html>

事件轮询

事件轮询(客户端)
时间:从开始执行代码就开始事件轮询
规则:
从宏任务开始
每执行完一个 宏任务,清空一次微任务队列,不管里面有多少个任务,都执行完毕
再次执行一个宏任务
循环往复,直到所有的任务队列清空结束

单线程:js是一个单线程的代码执行机制,逐行运行的代码,会阻塞代码执行
调用栈:用来执行代码的,所有的代码进栈执行,执行完毕就会出栈
队列:用来存在异步任务的,先进先出
    宏任务:script setTimeout,setInterval,..
    微任务:Promise.then()
轮询:轮流询问,
WEBApi:用来负责提供异步机制,计时,分配任务去到指定的队列

三十一、Promise

promise 是一个 ES6 的语法
承诺的意思,是一个专门用来解决异步 回调地狱 的问题
Promise是异步编程的解决方案,比传统的解决方法–回调函数和事件—更加的强大和合理

  • 语法:
    new Promise(function (resolve, reject) {
      // resolve 表示成功的回调
      // reject 表示失败的回调
    }).then(function (res) {
      // 成功的函数
    }).catch(function (err) {
      // 失败的函数
    })
    

promise 就是一个语法
Promise 代表一个异步操作,有三种状态:pending进行中、fulfilled(resolve)成功、rejected失败。
对象的状态不受外界影响,一旦状态设定,就不会再变。
Promise对象的状态改变有两种可能:从pending变为fulfilled和从pending变为rejected。

promise原型下面三个常用的方法:

原型方法(实例方法,方法):实例对象拥有then,和catch以及finally方法
同时这些方法都可以进行链式调用,而且每个方法里面都是函数作为参数

et p=new Promise(function(resolve,reject){
	// resolve:成功解决,resolve 函数名()
	// reject:失败或者未解决的意思,reject 函数名()
	})
	p.then(fn).catch(fn).finally(fn)
    p.then(fn)
    p.then(fn).then(fn)

    Promise.prototype.then

    Promise.prototype.catch

    Promise.prototype.finally

promise对象下面的静态方法:

    Promise.all
    用于将多个 Promise 实例,包装成一个新的 Promise 实例,接受一个数组作为参数,只有数组里面的每个状态都变成resolve,则新的 Promise 实例状态才会变成resolve.

    Promise.race
    方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例,接受一个数组作为参数,只要其中有一个实例率先改变状态,则整个的状态就跟着改变。

    Promise.resolve
    有时需要将现有对象转为 Promise 对象,该方法就起到这个作用。

    Promise.reject
    方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
	
	Promise.allSelected():
	方法接受一个数组作为参数,数组的每个成员都是一个promise对象,并返回一个新的promise对象,只有等参数数组的所有的Promise对象都发生状态变更(不管是fulfilled还是rejected状态),返回的Promise对象才会发生改变
	
	Promise.any():该方法接受一组promise实例作为参数,包装成一个新的Promise实例返回,Promise.any不会因为某个promise变成rejected状态结束,必须等到所有参数promise变成rejected状态结束
	
	    如果遇到的成功的请求,通过then返回resolve传递给then对应的值,
	    如果都是失败的状态,最终走向catch输出的默认的字符 All promises were rejected,执行catch里面的代码,无法获取reject的值
	    
	
	
  • promise 来解决多个 ajax 发送的问题

    new Promise(function (resolve, reject) {
      ajax({
        url: '第一个请求',
        success (res) {
          resolve(res)
        }
      })
    }).then(function (res) {
      // 准备发送第二个请求
      return new Promise(function (resolve, reject) {
        ajax({
          url: '第二个请求',
          data: { a: res.a, b: res.b },
          success (res) {
            resolve(res)
          }
        })
      })
    }).then(function (res) {
      ajax({
        url: '第三个请求',
        data: { a: res.a, b: res.b },
        success (res) {
          console.log(res)
        }
      })
    })
    

try/catch/finally

try/catch/finally 语句用于处理代码中可能出现的错误信息。
如果try里面的代码有错误,那么就执行catch里面的代码,同时catch的参数会返回try里面的错误信息。
finally 语句在 try 和 catch 之后无论有无异常都会执行。

  • 注意:
    • catch 和 finally 语句都是可选的,但你在使用 try 语句时必须至少使用一个。
    • try、catch、finally三段代码中都含有return时,则以finally中的retrun为准,其它return均无效;但是其它return之前的代码有效。
    • try和catch中含有return,但是finally中不含return;则代码会在执行try/catch中return之前去执行finally中的代码块,然后再执行try中的return操作
      “try catch的作用:当程序发生错误时,能够保证程序继续执行下去。”
1.try...catch....
    try里面的代码没有错误,执行try里面的代码,如果有错误,执行catch里面的代码,catch里面的返回try的错误信息
    *****try catch的作用:当程序发生错误时,能够保证程序继续执行下去。”*****
    <script>
        // try{
        //     console.log(a);
        // }catch(e){//e:try里面的如果存在错误,e就是try里面错误信息
        //     console.log(e);//ReferenceError: a is not defined
        //     console.log("这里是catch的输出");
        //     console.log(12222);
        // }
        // console.log(1);
        // console.log(1);
        // console.log(1);
        // console.log(1);

    </script>

    2.try..catch..finally (finally可以省略)
    finally:里面的语法不管执行try还是执行catch,finally里面的代码一定会执行
    <script>
        // try{
        //     console.log("a");
        // }catch(e){
        //     console.log(e);
        //     console.log("这里是catch的输出");
        // }finally{//收尾的工作,不再使用的对象,变量设置null
        //     console.log("finally执行");
        // }
    </script>

    <!-- 使用场景 -->
    <script>
        // xhr.οnlοad=function () {  
        //     if(xhr.readyState===4){
        //         try{
        //              const obj= JSON.parse( xhr.reponseText)
        //              console.log(obj);
        //         }catch(e){
        //             const str= xhr.reponseText;
        //             console.log(str);
        //         }
              
        //     }
        // }
    </script>

处理兼容性条件的方式:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    条件兼容

    1.if语句 if()else{}
    2.三目运算符 条件 ? 正确的 :错误的
    3.try...catch..
    4.&&(条件)和||(兼容)

    <script>
        // 更多写这个cb && typeof cb==="function" && cb()

        if(cb){
            if(typeof cb==="function"){
                cb();
            }
        }
    </script>

</body>
</html>

promise解决回调地狱

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="./ajax_promise.js"></script>
    回调地狱:回调函数多层嵌套,代码可读性比较差,很难修改
    <script>
    ajax({//封装的ajax函数返回promise对象
        type:"get",
        url:"http://api.k780.com/?app=phone.get&phone=13800138000&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json ",
        async:true,
        data:{
            content:"获取的第一个接口的数据"
        }
    }).then(res=>{//获取的上面的ajax传来的数据,then返回的promise对象,下面继续链式调用then
        console.log("第一条数据获取成功");
        console.log(res);
        console.log('------------------------------------------');
        // 手动返回promise对象,下面采用的就是手动返回的promise对象
        return ajax({
            type:"get",
            url:"http://api.k780.com/?app=life.time&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json ",
            async:true,
            data:{
                content:"获取的第二个接口的数据"
            }
        }).then(res=>{
            console.log("第二条数据获取成功");
            console.log(res);
            console.log('------------------------------------------');
            // 手动返回promise对象,下面采用的就是手动返回的promise对象
            return ajax({
                type:"get",
                url:"http://api.k780.com/?app=life.time&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json ",
                async:true,
                data:{
                    content:"获取的第三个接口的数据"
                }
            }).then(res=>{
                console.log("第三条数据获取成功");
                console.log(res);
                console.log('------------------------------------------');
            })
        })
    })
</script>
</body>
</html>

async/await

  • async/await 是一个 es7 的语法

  • async 其实就是promise的语法糖。函数前面必须加一个async,异步操作的方法前加一个await 关键字。顾名思义,就是让你等一下,执行完了再继续往下走。注意:await 只能在async函数中执行,否则会报错。

  • async:异步的意思,作用是申明一个异步函数,函数的返回值是promise 对象

  • await:等待

    await是 async+wait 的结合 即异步等待,async和await 二者必须是结合着使用

    await就是promise里面then的语法糖。

    await直接拿到promise的返回值。

    async和await在一个函数中存在。

    匿名函数也可以使用async和await.

  • 这个语法是 回调地狱的终极解决方案

  • 语法:

    async function fn() {
      const res = await promise对象
    }
    

这个是一个特殊的函数方式
可以 await 一个 promise 对象
可以把异步代码写的看起来像同步代码
只要是一个 promise 对象,那么我们就可以使用 async/await 来书写

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  async异步:  await等待:
  1.是promise的语法糖.函数前加一个async,函数里面的一部操作的方法前加一个await关键字
  await,就让你等一下,执行完了再继续往下走,
  注意:await只能在async函数中执行,否则会报错
  让异步代码像同步代码一样执行

  <script>
      // function fn(){}
      // console.log(fn());//undefined
  </script>

  2.async :异步的意思,作用是声明一个异步的函数,函数的返回值promise对象
  <script>
      // async function fn1(){}
      // console.log(fn1());//隐式返回一个promise对象
  </script>

  3.await :等待,await是async和await的结合,即异步等待
  <script>
      // !async function(){
      //     let h= await new Promise((resolve,reject)=>{
      //         resolve("hello")
      //     })
      //     console.log(h);//hello 证明await 是promise对象里面的语法糖
      // }()
      //** await就是promise的里面的then的语法糖,await直接拿到promise的返回值


      // (async function(){})
      // document.οnclick=async function()
      // const fn=async function()
      // setTimeout(async function(){},1000)
      // const fn1=async function(){
      //     let h= await new Promise((resolve,reject)=>{
      //         resolve("hello")
      //     })
      //     console.log(h);//hello 证明await 是promise对象里面的语法糖
      // }
      // fn1()
  </script>

  <script>
      // let p1=new Promise((resolve,reject)=>{
      //     setTimeout(()=>{
      //         resolve('11111111')
      //     },5000)
      // })

      // let p2=new Promise((resolve,reject)=>{
      //     setTimeout(()=>{
      //         resolve('222222')
      //     },10000)
      // })
      
      // // p1.then(res=>{console.log(res);})
      // // p2.then(res=>{console.log(res);})

      // async function fn(){
      //     let c1=await p1;//await让你等一下,执行完了再继续往下走
      //     let c2=await p2;//await让你等一下,执行完了再继续往下走
      //     console.log(c1);
      //     console.log(c2);
      // }
      // fn()
  </script>

  二.回调终极的解决方案
  <script src="./ajax_promise.js"></script>
  <script>
      // async function fn(){
      //     let p1=await ajax({//p1:接口返回的数据
      //         type:"get",
      //         url:"http://api.k780.com/?app=phone.get&phone=13800138000&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json",
      //         async:true,
      //         data:{content:"我是第一个接口的数据"}
      //     });
      //     let p2=await ajax({//p1:接口返回的数据
      //         type:"get",
      //         url:"http://api.k780.com/?app=phone.get&phone=13800138000&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json",
      //         async:true,
      //         data:{content:"我是第2个接口的数据"}
      //     });
      //     let p3=await ajax({//p1:接口返回的数据
      //         type:"get",
      //         url:"http://api.k780.com/?app=phone.get&phone=13800138000&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json",
      //         async:true,
      //         data:{content:"我是第3个接口的数据"}
      //     });
      //     console.log(p1);
      //     console.log(p2);
      //     console.log(p3);
      // }
      // fn()
  </script>

  <!-- promise.all -->
   <script>
      async function fn(){
          let p1=ajax({//p1:接口返回的数据
              type:"get",
              url:"http://api.k780.com/?app=phone.get&phone=13800138000&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json",
              async:true,
              data:{content:"我是第一个接口的数据"}
          });
          let p2=ajax({//p1:接口返回的数据
              type:"get",
              url:"http://api.k780.com/?app=phone.get&phone=13800138000&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json",
              async:true,
              data:{content:"我是第2个接口的数据"}
          });
          let p3=ajax({//p1:接口返回的数据
              type:"get",
              url:"http://api.k780.com/?app=phone.get&phone=13800138000&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json",
              async:true,
              data:{content:"我是第3个接口的数据"}
          });

          let newPromise=await Promise.all([p1,p2,p3]);
          console.log(newPromise);
      }
      fn()
      // 这样写异步看起来像同步代码,
  </script>




</body>
</html>
  • 这样的异步代码写的就看起来像一个同步代码了

ajax和promise的封装

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
    <script>
        // 把对象进行拼接:a=100&b=200
        function queryString(obj){
            if (Object.prototype.toString.call(obj) === '[object Object]') {
                let arr = []
                for(let key in obj){
                    arr.push(key + '=' +obj[key])
                }
                return arr.join('&')
            }
            return obj
        }

        // console.log(queryString({
        //     a:100,
        //     b:200,
        //     c:300
        // }))             
        //输出结果为:a=100&b=200&c=300


        // promise封装AJAX
        // 确定要传递的参数
        function ajax(options={}){      设置默认值为空对象,options:配置参数
            // 一、配置要传递的参数
            const settings={
                // 1.配置请求方式,默认为get请求
                type:options.type || 'get',
                // 2.配置接口地址,没有默认值
                url:options.url,
                // 3.配置是否异步,默认是异步
                async:options.async || true,
                // 4.配置获取的数据类型,默认传字符串
                dataType:options.dataType || 'string',
                // 5.配置数据,默认为空(传递的参数)
                data:options.data || '',
                // 6.配置请求头
                headers:{
                    ...options.headers
                }
            };

            // 二、检查属性值是否符合规范
            // 1.检查type属性,是否为get或者post,使用正则来验证
            if (!/^(get|post)$/i.test(settings.type)) {
                throw new Error('目前的请求方式,仅仅支持get和post,其他方式以后再说')
            }

            // 2.检查url,必须写,不能为空
            if (!settings.url) {
                throw new Error('接口地址必要填写,不能为空')
            }

            // 3.检查是否异步.值必须是布尔值
            if (!typeof settings.async === 'boolean') {
                throw new Error('是否异步的值只能是true或者false')
            }

            // 4.检查数据类型dataType
            // 获取的数据类型只能是字符串和json对象输出
            if (!/^(json|string)$/i.test(settings.dataType)) {
                throw new Error("获取的数据类型只能是字符串和json对象输出")
            }

            // 5.检查数据格式,只支持字符串或对象
            if (!typeof settings.data === 'string' && Object.prototype.toString.call(settings.data) !== '[object Object]') {
                throw new Error("传输的数据格式是不对的,请采用字符串或者对象格式")
            }

            // 6.检查headers格式,必须是对象
            const queryFormat=['application/json',"application/x-www-form-urlencoded","multipart/form-data",'text/plain']
            if (settings.headers['content-type'] && queryFormat.indexOf(settings.headers['content-type'])===-1) {
                throw new Error("请检查header里面的content-type的类型是否正确")
            }

            // 7.如果存在数据,是对象格式,转换成拼接的格式,判断请求方式是get,拼接在地址栏后面
            if (settings.data && /^get$/i.test(settings.type)) {
                settings.url += '?' + queryString(settings.data)
            }


             // 三.利用ajax+promise进行参数的应用
             const promise = new Promise((resolve,reject)=>{
                const xhr = new XMLHttpRequest()
                xhr.open(settings.type,settings.url,settings.async)
                // 如果数据存在,同时post方式,设置请求头,利用send发送
                // 判断是否存在header属性
                // 判断是否存在content-type,获取属性值,添加到请求头里面去,post请求
                if (settings.headers['content-type'] && /^post$/i.test(settings.type)) {
                    xhr.setRequestHeader('content-type',settings.headers['content-type'])
                    
                }
                // 判断token
                if (settings.headers['authorization']) {
                    xhr.setRequestHeader('authorization',settings.headers['authorization'])
                }

                // 是post携带参数发送,不是post,直接发送
                /^post$/i.test(settings.type)? xhr.send(queryString(settings.data)):xhr.send()

                xhr.onload = function() {
                    if (xhr.readyState === 4 && /^2\d{2}$/.test(xhr.status)) {
                        let res = null
                        if (settings.dataType.toLowerCase() === 'json') {
                            try {
                                res = JSON.parse(xhr.responseText)
                            } catch (error) {
                                res = xhr.responseText
                            }
                        }else{
                            res = xhr.responseText
                        }
                        resolve(res)
                    }else{
                        reject('接口地址错误')
                    }
                }
             })
             return promise
        }

        // 调用样式
        const token = "fejifjweoifjaweiofjweiojfwia"
        ajax({
            type:'get',     //请求类型
            url:"http://localhost:8888/goods/list",     //接口地址
            async:true,
            dataType:'json',        //获取数据类型json/string
            data:{                  //传递的参数
                a:1,
                b:2,
                c:3
            },
            headers:{                //设置请求头
                'content-type':"application/x-www-form-urlencoded",
                'authorization':token
            }

        }).then(res=>{
            console.log(res)
        }).catch(err=>{
            console.log(err)
        })
        

    </script>
</body>
</html>

严格模式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    "use strict"; 
    <script>
    //     es 解决一些js不合理或者不严谨的地方,提升js的性能和安全性
    //    js文件的上面  有"use strict" 整个js代码以严格模式开始执行

        // 1.声明变量必须要添加关键字
        // "use strict"    
        // var a=10;
        // console.log(a);// a is not defined

        // 2.普通函数内部的this指向(谁调用this就指向谁)
        // 严格模式上面指向undefined
        // "use strict"   
        // function fn(){
        //     console.log(this);
        // }
        // fn();//undefined
        // window.fn();//window

        // 3.函数有参数重名的函数,严格模式不允许
        // "use strict";   
        // function fn(a,a,b){
        //     console.log(a+a+b);//7
        // }
        // fn(1,2,3)// Duplicate parameter name not allowed in this context
        

        // 4.eval和arguments不能重新赋值
        // eval("var a=1;var b=2;alert(a+b)");//eval不够安全
        // eval("location.href='http://www.baidu.com'");//eval不够安全,比较麻烦,先将js字符串解析成js代码,然后再执行的代码,性能不好

        // "use strict";   
        // eval("var x=1; ");
        // alert(x);

        // "use strict";   
        // function fn(){
        //     arguments++
        //     // console.log(arguments.length);
        // }
        // fn(1,2,3)

        // 5.不允许严格模式下面使用八进制
        // "use strict";   
        // var num=012;
        // console.log(num);//10

        // 6.不能使用arguments.callee,函数内部表示当前的函数的名字
        // 求阶乘 求5的阶乘
        // "use strict";   
        function fn4(n){
            if(n===1){
                return 1
            }
            return n*arguments.callee(n-1)
        }
        console.log(fn4(5));


    </script>
</body>
</html>

三十二、Promise封装和跨域

1、首先引出问题,到底什么是跨域?

	跨域,指的是浏览器不能执行其他网站的脚本。
	举例:
	比如在网站中使用 ajax 请求其他网站的天气、快递或者其他数据接口

2、产生跨域的原因

	它是由浏览器的同源策略造成的,是浏览器对 javascript 施加的安全限制。
	引出什么是同源策略?
		同域名,同端口,同协议,举个例子:
		http://www.123.com/index.html 调用 http://www.123.com/server.php (非跨域)
		http://www.123.com/index.html 调用 http://www.456.com/server.php (主域名不同:123/456,跨域)
		http://abc.123.com/index.html 调用 http://def.123.com/server.php (子域名不同:abc/def,跨域)
		http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.php(端口不同:8080/8081,跨域)
		http://www.123.com/index.html 调用 https://www.123.com/server.php (协议不同:http/https,跨域)
		
	同源策略:阻止从一个域上加载的脚本获取或操作另一个域上的文档属性。也就是说,受到请求的URL 的域必须与当前 Web 页面的域相同。同源策略是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。
		
	产生跨域的条件
		
		- 端口不同
		- 域名不同
		- 协议不同
		- 域名和域名对应的ip地址
		- 一级和二级域名

3、使用什么方法可以解决跨域?

3.1、后端代理

	后端不存在跨域

3.2、CORS 跨域资源共享

a) CORS 跨域资源共享
	HTML5 中提供的 XMLHTTPREQUEST Level2(及 XHR2)已经实现了跨域访问。但 ie10以下不支持。只需要在服务端填上响应头:header("Access-Control-Allow-Origin:*");/*星号表示所有的域都可以接受,*/
	优点:代表任何域。也可以指定地址
		Access-Control-Allow-Methods: POST,GET //支持的方法
	缺点:ie10 以下不支持
		解决方法:可以用 XDR 实现,XDR 是 IE8、IE9 提供的一种跨域解决方案,但是功能较弱只支持 get 跟 post 请求,而且对于协议不同的跨域是无能为力的(比如在 http 协议下发送 https 请求)

3.3、JSONP

b) 动态创建 script 标签——》只支持 GET 请求
	由于 script/img/link 标签的 src 属性都不受同源策略的影响,所以可以动态创建
	- script标签引入js文件不受跨域影响。不仅如此,带src属性的标签都不受同源策略的影响。

正是基于这个特性,我们通过script标签的src属性加载资源,数据放在src属性指向的服务器上,使用json格式。
由于我们无法判断script的src的加载状态,并不知道数据有没有获取完成,所以事先会定义好处理函数。服务端会在数据开头加上这个函数名,等全部加载完毕,
便会调用我们事先定义好的函数,这时函数的实参传入的就是后端返回的数据了。

script 标签,代码如下:
	var script = document.createElement('script');
	script.src = "http://localhost:8080/kuayu2/test.php";
	document.body.appendChild(script);
	使用 jQuery 的 jsonp 请求
	$("#btn").click(function(){
		$.ajax({
			url:"http://localhost:8080/kuayu3/test.php",
			type:"get",
			dataType:'jsonp', //定义要发送的 jsonp 请求
			jsonp:'kyFn', //更改定义的参数名
			jsonpCallback:'hyly', //指定 jsonp 发送的回调函数名
			success:function(info){
				console.log(info);
			}
		});
	})
服务器端脚本:
<?php
	header("Content-type: text/html;charset=utf-8");
	$callback=$_GET['kyFn'];
	echo $callback.'("哈哈哈")';
?>
优点:
	它不像 XMLHttpRequest 对象实现的 Ajax 请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要 XMLHttpRequest 或 ActiveX 的支持;并且在请求完毕后可以通过调用 callback 的方式回传结果

缺点:
	只能用 get 方法,不能使用 post 方法

3.4、基于 iframe 实现跨域

c) 基于 iframe 实现跨域:
	应用场景:
		基于 iframe 实现的跨域要求两个域具有 aa.xx.com,bb.xx.com 这种特点,也就是两个页面必须属于一个基础域(例如都是 xxx.com,或是 xxx.com.cn),使用
		同一协议(例如都是 http)和同一端口(例如都是 80),这样在两个页面中同时添加 document.domain,就可以实现父页面调用子页面的函数
	缺点:只有在主域名相同的情况下才能使用该方法

3.5、web sockets 解决跨域

d) web sockets 解决跨域:
	原理:在 JS 创建了 web sockets 之后,会有一个 HTTP 请求发送到浏览器以发起链接,取得服务器响应后,建立的连接会使用 HTTP 升级从 HTTP 协议交换为 web sockets协议
	缺点:只有在支持 web sockets 协议的服务器上才能正常工作

三十三、ES6模块化开发

模块化的概述

一.模块化的概述(package包)以及意义
1.函数编程-闭包-命名空间-模块化编程(非常优秀的解决命名冲突或依赖)
1.1函数编程:产生冲突,看不出依赖
1.2闭包:闭包避免冲突,每一块的代码包都可以在自执行函数中,但是使用闭包有缺点,也看不出来依赖关系
1.3命名空间:根据名称不同避免冲突,名称很长,企业不太合适,看不出来依赖关系
1.4模块化编程:解决冲突和依赖

二.模块化编程-思想
就是指将页面根据内容的关联性分解成不同的且相互独立的模块进行开发
每个模块之间没有必然的联系,互不影响,想要什么功能,就加载什么模块
每个js文件就是独立的模块
大家必须要以相同的方式编写模块,否则达不到效果--协助

三.es6提供的模块化意义
在es6之前,社区制定了.主要有CommonJS和AMD两种
前者服务于服务器,后者用于浏览器
CommonJS规范,指出来js文件就是一个独立的模块,提出如何定义模块,调用模块,配置模块
Nodejs实现了规范

基于CommonJS规范实现了AMD(异步模块定义)规范

3.2ES6完全了取代有CommonJS和AMD两种规范,成为浏览器和服务器的通用的模块解决方案.

四.模块化开发过程
1.定义模块(输出模块)---export
1.2export定义的模块.
   import使用的时候.必须要知道输出的模块里面的变量名或者函数名等信息
1.3export default:暴露一个变量或者函数,调用的时候自定义名称,一个模块只有一个默认输出号,因为export default命令只能使用一次

2.调用模块----import
2.1使用export命令定义了模块对外的接口以后,其他的js文件都要使用import命令加载这个模块
2.2如果想为输出的变量重新取一个名字,import命令要使用关键字as

3.配置模块:
路径配置,说明当前模块是自定义的模块,还是第三方的,./表示是当前

4.HTML页面的加载规则
4.1浏览器去加载es6模块,也使用script标签,但是要加入"type=module"属性
4.2"type=module"的script都是异步加载,不会造成浏览器的堵塞.放置在任意位置
4.3 等到整个页面渲染完成之后,再执行脚本加载模块,等同于打开script标签defer(异步)属性,defer属性加载在script标签里面.异步加载
4.4如果网页有多个script,"type=module",依次执行

模块化开发

1、定义模块化

// 1.定义模块.输出模块,暴露模块
// 一个模块就是一个独立的文件夹
// 改文件夹内内部的所有的变量,外部都是无法获取的,如果你想外部能够读取模块内部的某一个变量,就要使用export关键字输出该变量
// 2.export命令用于规定模块的对外接口

//变量
let username="zhangsna";

//函数
const fn111=function(){
    return "我是函数的返回值"
}


// 数组,对象
const arr=[1,2,3,4,5];
const obj={
    a:1,
    b:2,
    c:3
}
// 构造函数
function Person(){
    this.name="构造函数"
}
Person.prototype.show=function () { 
    console.log("构造函数Person的原型里面的方法show输出");
 }

//  类
class Student{
    constructor(){
        this.grade="1111111";
    }
    init(){
        console.log("类里面的init的方法输出");
    }
}

export{
    username,
    arr,
    obj,
    Person,
    Student,
    fn111
}

暴露一个变量或者函数

// export default :暴露一个变量或者函数,调用的时候给自定义名称
export default function fn(){
    console.log("hheheh");
}

2、使用模块

// 使用expoer命令输出的模块,其他js文件都需要通过import命令加载这个模块

对象
import { obj,username,Person,Student } from "./define_module.js";
console.log(obj);
new Student().init();

// 单独某一个
// import fn from "./exportdefault.js";
// fn()

// 改名 
import {arr as arrNum} from "./define_module.js";
console.log(arrNum);


import {fn111} from "./define_module.js"
console.log(fn111())

三十三、Node

什么是 node

Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine.
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
这个是 node 官网的解释
其实 node 就是用 javascript 语言写 *后端
也就是说,我们的 javascript 有了 node 以后,不光是一个前端语言,也是一个后端语言
前端 javascript
三大核心:
ECMAScript
DOM
BOM
操作内容:
浏览器
解决兼容问题
后端 javascript (node)
核心:
ECMAScript
操作内容:
后端代码
数据库
也就是说,node 我们不需要解决兼容问题,不需要 DOM 和 BOM,只关注业务逻辑就可以了

下载node安装包

我们的电脑是没有自带 node 环境的
需要我们手动下载一个 node 安装包,安装 node 环境
有了 node 环境以后,我们就可以运行 node 了

常用的Linux操作命令、

目录操作

- 目录操作就是操作我们的命令行路
  1. 查看当前目录下所有文件
     $ dir
  1. 以树状结构展示当前目录下的所有文件及子目录下的所有文件
     $ tree
  1. 进入当前目录下的某一个目录
     $ cd 文件夹名称
  1. 返回上一级目录
     $ cd ..
  1. 切换盘符
     $ 盘符:
     $ d:

文件操作

文件操作就是通过指令创建文件或者文件夹

  1. 创建文件夹

    # 表示在当前目录下创建一个叫做 test 的文件夹
    $ mkdir test
    
    
  2. 移除文件夹

    # 表示移除当前文件夹下的 test 文件夹
    $ rd test
    
    
  3. 复制文件夹

    # 表示复制一份 test 文件夹起名为 test2
    $ xcopy test test2
    
    
  4. 创建文件

    # 表示在当前目录下创建一个叫做 index.js 的文件
    $ type nul> index.js
    
    
  5. 拷贝一份文件

    # 表示复制一份 index.js 文件起名为 ceshi.js
    $ copy index.js ceshi.js
    
    
  6. 向文本中写入内容

    # 表示向 index.js 中写入一段文本 console.log('hello world')
    $ echo console.log("hello world") > index.js
    
    
  7. 查看文件内的文本内容

    # 表示查看 index.js 文件中的文本内容是什么
    $ type index.js
    
    
  8. 给文件或者目录重命名

    # 表示把 index.js 更名为 abc.js
    $ ren index.js abc.js
    
    
  9. 删除文件

    # 表示把当前目录下的 index.js 删除
    $ del index.js
    
    
  10. 移动文件或文件夹

    # 表示把当前目录下的 index.js 文件移动到当前目录下的 a 文件夹下
    $ move index.js a

其他指令

做一些其他事情的时候使用的

  1. 清屏
     # 表示把当前屏幕的所有内容都清除
     $ cls
  1. 查看当前电脑 IP 信息
     # 表示查看当前电脑的 IP 信息
     $ ipconfig
  1. 测试某一个链接地址的网速
     # 表示查看访问 百度 网站的速度
     $ ping www.baidu.com
  1. 查看电脑信息
     # 表示查看当前电脑的信息
     $ systeminfo

node的导入导出

node 的开发是模块化开发
每一个 js 文件都是一个独立的模块
都有自己独立的作用域
我们可以通过 导入导出 的方式把多个 js 文件合并在一起

如果一个JS文件将来需要运行在Node中,那么这个文件内部天生自带一个变量module
这个变量中有一个属性exports默认值是一个空对象,我们可以向这个对象中添加一个属性
来达到导出的目的

导入

运行命令:node 文件名
当前文件需要导入out.js 文件内部向外暴露的内容
如果一个JS文件将来运行到node内,天生自带一个方法 require("导入文件的地址");
语法:require("导入文件的地址");
返回值:导入文件的module.exports这个对象里面的内容

在 node 里面,我们使用 require 来导入一个文件

  // 我是 index.js 文件
  require('./a.js')
  
  console.log('我是 index.js 文件')

当我在命令行运行 index.js 文件的时候

  • 首先会把 a.js 文件运行一遍
  • 然后再继续执行我自己文件内部的代码

也可以再导入的时候接受另一个文件导出的内容

// a 接受到的内容就是 a.js 这个文件导出的内容
// 如果 a.js 文件中什么都没有导出,那么接受到的就是一个 空对象
const a = require('./a.js')

导出

我们在写一个 js 文件的时候,可以向外导出一些内容
将来在这个文件被导入的时候,就可以接受到一些内容
// 我是 a.js

// 每一个 js 文件都会有一个对象叫做 module
// 在 module 里面有一个成员,叫做 exports
// 每一个 js 文件会默认把 module.exports 导出
// 也就是说,我们向 module.exports 中添加什么内容
// 那么就会导出什么内容

module.exports.name = 'Jack'
module.exports.age = 18

将来这个文件被导入的时候,接受到的内容就是一个对象,里面有两个成员

// 我是 index.js

const a = require('./a.js')

console.log(a) // { name: 'Jack', age: 18 }

// 如果一个JS文件将来需要运行在Node中,那么这个文件内部天生自带一个变量module
// 这个变量中有一个属性exports默认值是一个空对象,我们可以向这个对象中添加一个属性
// 来达到导出的目的


// console.log(module);

// module.exports.name="zhangsan";
// exports.age=18;
// console.log(module);

module.exports=[1,2,3];
exports=[4,5,6];

// 分析:
// module.exports这个变量 默认的值是空对象,在12行这里将这个空对象更改为了数组[1,2,3]
// 将来导出的时候,还是导出这个变量的值,但是现在修改为了[1,2,3];

// exports这个变量 原本内部储存的是 指向module.exports的那个空对象
// 然后现在我将这个变量内部的值 重新修改了为了一个数组[4,5,6];相当于切断了和原本对象的联系

// 所以这个数组[4,5,6]跟导出的这个就没有任何关系,只不过是修高了一个天生自带变量的值而已
// 所以这个导出的文件内容为[1,2,3]




// Module{
//     ...
//      exports: { name: 'zhangsan', age: 18 },
//     ...
// }

node的模块化

  • 在 node 的开发过程中
  • 我们是把每一个功能独立做成一个模块
  • 然后在使用 导入导出 的方式把他们关联在一起
    • 利于维护
    • 准确定位
  • 我们一般把模块分为三种
    1. 内置模块 (node 天生就带有的模块)
    2. 自定义模块 (我们自己写的文件)
    3. 第三方模块 (从网上下载的别人写好的模块)

node 常用的内置模块

FS 模块

fs 是 node 的一个内置模块
专门用来操作文件的
使用的时候直接导入就可以使用了

const fs = require('fs')

// 接下来就可以使用 fs 这个变量去操作文件了

异步读取文件内容

异步的读取某一个文件内的内容

const fs = require('fs')

// 因为是异步读取,所以要在回调函数里面获取结果
fs.readFile('./text.txt', 'utf8', function (err, data) {
    // err 表示读取的时候出现的错误
    // data 表示读取到的内容,如果出现错误,那么是 data 是没有内容的
})

同步读取文件内容

同步读取某一个文件的内容

const fs = require('fs')

// 因为是同步读取,所以直接以返回值的形式接收读取的内容就可以
const res = fs.readFileSync('./text.txt', 'utf8')
// 同步读取的时候,如果出错会直接在控制台报错,并中断程序继续执行
// 如果没有错误,res 就会得到文件中的内容

异步写入文件

异步的向某一个文件中写入内容

const fs = require('fs')

// 写入内容的时候,一般不会出现错误
// 因为如果没有这个文件的话,会创建一个这个文件在向里面写入内容
// 所以回调函数一般没什么用处,只不过是在写入文件结束后做些事情而已
// 虽然没有用处,但是必须要写
fs.writeFile('./text.txt', '我是要写入的内容', function () {
    console.log('写入完成')
})

同步写入文件

同步的向某一个文件内写入内容

const fs = require('fs')

// 因为是写入文件
// 没有返回值,因为一般都会写入成功
fs.writeFileSync('./text.txt', '我是要写入的内容')

HTTP 模块

因为 node 是一个服务端语言
所以 node 一定也可以开启一个服务器,开启一个服务
http 这个模块就是专门用来开启服务,并且接受请求,返回响应的
http 也是一个内置模块,直接导入使用就行

1.导入到当前模块内
const http = require('http')

// 接下来就可以使用 http 这个模块去开启服务了

2.利用http模块 搭建一个简易的服务器

2.1 先要创建一个服务

const http = require('http')

// 创建一个服务
// 这个服务默认监听 http 协议
// 这个服务默认监听 localhost 域名
// 返回值就是这个服务
const server = http.createServer(function (request, response) {
    // 前端发来的每一个请求都会触发这个函数
    // request 包含着所有的请求信息
    // response 是所有的响应信息
})

2.2 监听一个端口

  • 确定这个服务监听哪一个端口

    const http = require('http')
    
    // 创建一个服务
    const server = http.createServer(function (request, response) {
        // 前端发来的每一个请求都会触发这个函数
    })
    
    server.listen(8080, function () {
        // 这个函数会在服务开启成功以后执行
       console.log("服务器开启成功,端口号:8080");
    })
    
    

2.3 给出一个响应

  • 简单给出一个响应

    const http = require('http')
    
    // 创建一个服务
    const server = http.createServer(function (request, response) {
        // 前端发来的每一个请求都会触发这个函数
        // 接受到请求以后给出一个响应
        response.end('hello world')
    })
    
    server.listen(8080, function () {
        // 这个函数会在服务开启成功以后执行
        console.log('lintening on port 8080')
    })
    
    

此时,打开浏览器
地址栏输入 localhost:8080
浏览器就会响应文字 hello world

认识 nodemon

认识nodemon
可以理解为一个"软件",but它不能被看到
这个软件可以帮助我们提供一个与node一样的环境,去运行我们的js代码
安装命令:npm i nodemon -g (全局安装,这种方式安装完毕之后,你在电脑任意目录里面都可以使用nodemon)

 如何使用:
 以前 =>node index.js
 现在 =>nodemon index.js

 使用区别:
    node启动的文件 在修改代码之后,需要重新再去启动才能看到最新的效果
    nodemon就是热更新,所以不需要重新再去启动才能看到最新的效果

使用场景:
node项目书写完毕之后,使用一次
nodemon 项目正在开发的时候,使用,这样可以每一次修改代码后需要重新再终端执行一次node

认识 node-dev

安装命令:npm i node-dev -g
node-dev index.js
不需要重新再去启动才能看到最新的效果

node总结

什么是node.js
一个基于chromeV8引擎的 JS的运行环境
就是给了我们除了除浏览器之外的一个地方运行js,
曾经的人伟大的靓仔,把chrome的v8引擎,拿出来和其他的做了合并,叫做Node.js

Node.js和我们在浏览器端运行前端js的区别?
区别1:
前端js:把js引入一个html文件,然后运行在浏览器中
此时的js文件.bom+dom+es
Node.js:把js直接运行到电脑系统中,所以bom和dom就不能使用了,只剩下es的规范了.
同时因为它是直接运行在电脑系统中,所以还有一些别的功能
可以操作电脑的文件/文件夹
直接操作数据库

    区别2:
    前端js:运行的是可以采用模块化的语法,也可以不用模块化
    不采用模块化的语法也能运行的原因:我们把很多个js文件引入到某一个js文件,再把这个文件放到html里面

    nodejs:没有这个html文件,所以在node里面,必须要使用模块化的语法.
            模块化的语法基于CommonJS 
            es6推行的时间15年
            nodejs 09年


总结如何运行nodejs
win:
1.window+r 输出cmd回车
    2.进入到一个小黑窗口,输入node 敲回车
    3.进行到node运行环境里面,相当于浏览器的控制台
    4.退出. 按下两次的ctrl+c
    弊端:书写的代码,无法保存

mac:1.打开终端
2.输入node回车

win:
1.将要运行的Js代码,书写在一个.js文件内部
2.打开文件目录,在地址栏里输入cmd敲回车
3.输入指令:node 文件名

mac:
1.将要运行的Js代码,书写在一个.js文件内部
2.拖拽啊文件所在目录到 终端 这个图标上
3.输入指令:node 文件名

win和mac都能使用:
1.将要运行的Js代码,书写在一个.js文件内部
2.在vscode编辑器里面,左侧目录树,右键你的.js文件,点击:"在集成终端中打开"
3.输入指令:node 文件名

三十四、npm

在我们安装 node 的环境的时候,会自动帮我们一起安装一个 npm 环境
就好像我们安装一些软件的时候,会自动在帮我们安装一些什么 xxx软件管家/xxx游戏 之类的东西
但是 npm 不是垃圾软件,而是一个我们超级实用的工具

检测是否安装

和检测 node 一样
在命令行输入指令

 npm -v || npm --version

能够得到一个版本号就可以了

了解 npm

什么是 npm 呢
我们可以把他想象成一个大超市,一个装着所有我们需要用到的 插件//框架 的超市
我们要下载一个 jQuery-validation 插件
我们可以选择去官网进行下载
可以选择去 GitHub 上查找并下载
也可以选择直接在命令行用 npm 下载

  • 我们要下载一个 bootstrap
    • 我们可以选择去官网进行下载
    • 可以选择去 GitHub 上查找并下载
    • 也可以选择直接在命令行用 npm 下载
  • 也就是说,npm 包含着我们所有的第三方的东西
  • 我们需要的时候,只要打开终端,就可以使用指令来帮我们下载
    • 再也不需要去官网找了
  • 而且,npm 不光可以在后端使用,也可以在前端使用
  • npm 只不过是一个依赖于 node 环境的大型的包管理器

使用 npm

1.需要初始化,切换到项目的根目录

//输入一个命令,
npm init (需要手动配置一些信息) 
//或者
npm init -y (所有的配置信息按照默认的去处理)

2.输入命令进行下载

npm install 包名字
//下载完成之后,根目录里面多一个文件package-lock.json 和一个目录node_modules

//例如下载如下两个包
npm i lodash
npm i express

package-lock.json 下载文件的小版本
node_modules :下载的地方包都在这个目录里面

npm的完整使用步骤:

1.npm init -y
2.npm i 包名/npm install 包名/
3.用  步骤:导入  require ("包");
        用:按照官方文档
4.运行 node 文件名字 .js结尾的文件

下载包

  • 打开命令行
  • 输入下载的指令

命令:npm i 包名@你需要下载的版本

表示使用 npm 这个工具下载一个 jquery
 npm install jquery

下载完毕以后,就会在当前目录下多出一个文件夹

  • 叫做 node_modules
  • 在这个目录下就会有一个文件夹叫做 jquery
  • 就是我们需要的东西了

npm 的下载默认是下载最新版本的包
我们也可以在下载的时候指定一下我要下载哪一个版本

# 表示使用 npm 这个工具下载一个 3.3.7 版本的 jquery
$ npm install bootstrap@3.3.7

删除包

在删除包的时候,我们可以直接去 node_modules 文件夹中找到对应的包的文件夹删除掉
但是这样做并不好,我们还是应该使用命令行的指令来删除包

命令:npm uninstall 包名 || npm un 包名

# 表示我要删除 jquery 这个包
$ npm uninstall jquer

npm 下载所有的包 (package.json记录的所有的包)

1.右键json文件 终端打开(这一步的目的是为了确保终端的路径是正确的)
2.输入命令:

npm install || npm i

npm 清除缓存

有的时候,有些包下载到一半,因为各种原因失败了(比如突然没有网了)
那么这个下载了一半的包 有可能 会被缓存下来
那么以后你再次下载的时候,就都是失败的状态
那么我们就要清除掉缓存以后,在重新下载

# 表示清除 npm 的缓存
$ npm cache clear -f

nrm

  • 我们的 npm 虽然好用
  • 但是有一个缺点
    • 就是,他虽然在帮我们下载东西
    • 但是他的下载地址是在国外
    • 也就是说,每次使用 npm 下载的时候,都是去国外的服务器上进行下载
    • 那么就会有很多不稳定的因素
    • 而且相对时间比较长
  • nrm 就是一个用来切换 npm 下载地址的工具(切换镜像源工具)

安装 NRM

  • nrm 如果想使用,那么需要我们自己安装一下

  • 因为是我们的工具,所以使用 npm 就可以安装

  • 依旧是使用指令的方式来进行安装

  • 只不过这里要把这个 nrm 安装成一个全局的依赖,而不再是项目内部的依赖了

    • 全局依赖,一个电脑安装一次,就一直可以使用
  • 我们使用指令安装一个全局 nrm

    # 表示安装一个全局 nrm
    $ npm install --global nrm
    
    

检测安装

  • 安装完毕之后,我们检测一下是否安装成功

  • 和检测 node npm 的时候一样

  • 在命令行使用指令查看一下版本号

    $ nrm --version
    
    
  • 能出现版本号,表示安装成功

使用 nrm

  • nrm 里面存着好几个镜像源地址
  • 我们要挑一个比较快的使用

检测镜像源地址

  • 我们直接在命令行使用指令来查看所有镜像源地址的网速

    # 表示查看 nrm 镜像源地址网速
    $ nrm test
    
    

切换镜像源

  • 我们检测完毕以后,就直到哪个比较快了

  • 我们就使用指令切换一下镜像源地址就好了

    # 表示切换到 taobao 镜像源地址
    $ nrm use taobao
    
    
  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值