JavaScript基础

JS 的三种书写方式

行内式 JS 代码
  • a 标签,因为 a 标签本身就有行为出现,当点击的时候, 需要区分是跳转链接还是执行 JS 代码,在 href 属性里面书写一个 javascript: JS代码 ;
  • 非 a 标签,因为没有自己的行为, 需要给他加一个行为,写一个 onclick 属性, 表示当点击时执行属性值的位置书写的 JS 代码<div onclick="JS代码"></div>
内嵌式 JS 代码
  • 在页面内书写一个 script 标签,把 JS 代码书写在标签对内部
  • 注意:不需要任何行为, 只要打开页面就会执行
  • 特点:
    1. 在一个页面内可以书写无限个 script 标签,会按照从上到下的顺序依次执行
    2. 理论上 script 标签可以放在页面的任何位置,推荐放在 body 的末尾或者 head 的末尾
外链式 JS 代码
  • 把 JS 代码写在一个 .js 后缀的文件里面
  • 在页面上通过 script 标签的 src 属性引入页面
  • 注意: 不需要任何行为, 只要打开页面就会执行
  • 特点:和内嵌式一样,当一个 script 标签被当作外链式使用的时候, 那么写在标签对里面的内容没有意义哪怕你写了 src 属性, 但是没有引入文件, 也不能当作内嵌式使用了

JS 的输出语法

alert()

以浏览器弹出层的形式展示内容,小括号里面书写要输出的内容,只要不是纯数字, 都用引号包裹(单引号双引号无所谓)

console.log()

在浏览器控制台打印你要输出的内容,小括号里面书写你要输出的内容,只要不是纯数字, 都用引号包裹

document.write()

在页面直接写入你要输出的内容,小括号里面书写你要输出的内容,只要不是纯数字, 都用引号包裹

特殊:直接把内容输出在页面上,所以可以解析标签

变量的命名规则和命名规范

规则
  1. 一个变量只能由 数字(0-9), 字母(a-zA-Z), 美元符($), 划线下(_) 组成
  2. 一个变量不能由 数字 开头
  3. 在 JS 中严格区分大小写,num Num NUm NUM 这是四个变量
  4. 不能使用关键字或者保留字
规范
  1. 不要用中文命名
  2. 变量语义化,尽可能使用一个有意义的单词
  3. 驼峰命名法,当一个变量由多个单词组成的时候,第二个单词开始, 首字母大写

JS 的数据类型

基本数据类型(简单数据类型)

Number 数值、String 字符串、Boolean 布尔、Undefined 空、Null 空

Number 数值

一切十进制表示的数字、一切浮点数(小数)、其他进制表示的数字,十六进制, 以 0x 开头,八进制, 以 0 开头,二进制, 以 0b 开头、科学计数法,比较大的数字使用科学计数法表示,2e5 2 * 10的5次方

NaN,Not a Number: 非数字

复杂数据类型(地址数据类型 / 引用数据类型)

Object、Function

检测数据类型
  1. 关键字 typeof

    两种用法

    • typeof 变量,语法: typeof 要检测的变量,返回值以字符串的形式返回变量数据类型
    • typeof(变量),语法: typeof(要检测的变量),返回值以字符串的形式返回变量的数据类型
  2. 两种语法的区别

    • typeof 只能检测紧跟着的一个变量
    • typeof() 先运算小括号里面的结果, 然后使用 typeof 去检测结果的数据类型
  3. typeof 的返回值

    typeof 的返回值是一个字符串,当两个及以上 typeof 连用的时候, 一定得到 string,只能准确的检测基本数据类型,数值: number、字符串: string、布尔: boolean、undefined: undefined、null: object

数据类型转换
  1. 转数值,把其他数据类型转换成数值类型

    语法返回值特点
    Number(要转换的数据)转换好的数据会把要转化的内容当作一个整体来看代,能转换成数字结果, 就是数字结果,不能转换成数字结果, 就是 NaN
    parseInt(要转换的数据)转换好的数据把要转换的任何内容一位一位的看, 如果第一位就不能转换成数字, 那么直接给 NaN,如果第一位可以, 就看第二位, 第一位保留,以此类推, 直到一个不能转换成合法数字的位置为止
    注意: 不认识小数点
    parseFloat(要转换的数据)转换好的数据和 parseInt 的解析规则一模一样,只不过多认识一个小数点
    +变量 或者 -变量转换好的数据结果和 Number 的解析规则一模一样
    非 加法 的数学运算 a * 1、 a - 0、 a / 1转换好的数据结果和 Number 方法解析规则一模一样
  2. 转字符串,把其他数据类型转换成字符串类型

    语法返回值特点
    String(要转换的数据)转换好的数据任何数据类型都能转换
    要转换的数据.toString()转换好的数据undefined 和 null 不能转换
    用加号(+) 进行字符串拼接转换好的数据只要符号任意一边是字符串的时候, 就是字符串拼接
    只有两边都是数字或者布尔的时候, 会进行数学运算
  3. 转布尔,把其他数据类型转换成布尔数据类型

    Boolean(),语法: Boolean(你要转换的数据),返回值: 转换好的数据

    特点:在 JS 里面只有五个内容转换成 false,0、空字符串(’’)、NaN、undefined、null

    其他所有内容转换完毕都是 true

数据类型存储的区别

数据类型分成两种

基本数据类型:Number 数值、String 字符串、Boolean 布尔、Undefined 空、Null 空

复杂数据类型:Function 函数、Object 对象

JS 是一个脚本语言, 依赖于浏览器执行,本质是依赖浏览器里面的 JS 解析引擎,JS 本身不打开内存空间,因为浏览器在电脑上运行的时候, 会占用一段内存空间,JS 就是在这一段内存空间里面运行的,数据类型的存储, 就是存储在浏览器分配给 JS 存储的一段空间

浏览器的一段存储空间:栈内存,存储机制, 先进后出;堆内存,存储机制, 随机存储

数据类型的存储
  1. 基本数据类型,直接把值存储在栈内存里面
  2. 复杂数据类型(地址数据类型 / 引用数据类型),把数据放在了堆内存里面,把地址放在栈内存的变量里面,我们管这个地址叫做引用
  3. 只能直接访问栈里面的内容,要想访问某一个对象里面的成员,因为对象本身在堆内存里面,就需要利用栈里面的地址, 找到堆里面的空间,然后去访问内部的成员
数据类型赋值的区别
  1. 基本数据类型,就是把变量存储的值直接赋值给另一个变量,赋值过后两个变量没有关系了
  2. 复杂数据类型,因为复杂数据类型中, 变量存储的是地址,赋值的时候, 实际上是把一个变量的地址给了另一个变量,赋值过后, 两个变量操作一个空间
  3. 函数的形参和实参的关系,实参就是在函数调用的时候给形参赋值,实参和形参的交互, 和变量赋值时一个道理
  4. 函数的返回值也是变量赋值的一种,返回值是把函数内部的数据 return 出去,在函数外面有一个变量接收。
  5. 函数也是一个对象,函数是保存一段代码,对象是保存一段数据,函数本身也是一个对象, 可以保存一堆数据当你定义号一个函数以后,函数就有两个功能:函数名(),把函数当作一个函数来执行掉;函数名.成员名 = ‘值’,存储一些数据。

数组数据类型

也是 JS 的一种数据类型,复杂数据类型 Array,按照索引进行存储的(序号)

数组的创建
  1. 字面量创建,var arr = []
  2. 内置构造函数创建, JS 给我们提供了一个内置构造函数 Array,var arr = new Array()
创建的时候直接添加一些成员
  1. 字面量,直接写在中括号里面, 多个数据使用 逗号(,) 分隔
  2. 内置构造函数,不传递参数(var arr = new Array()),创建一个空数组;传递一个正整数(var arr = new Array(100)),这个参数表示数组的长度,传递多个数据(var arr = new Array(10, 20, 30)),每一个数据都是放在数组里面的数据,没有表示长度的数据了
数组的操作

数组有一个 length 属性,是一个读写的属性。读: 获取数组的长度;写: 设置数组的长度,当设置的比本身长度小, 那么就相当于删除,当你设置的比本身长度大, 那么多出来的就是用空补齐。

数组的排列,是按照索引进行排列的

索引也是一个读写的属性。

读: 读取到指定索引位置的数据,如果数组确实有这个索引位置, 那么就是这个位置的数据,如果数组没有这个索引位置, 那么就是 undefined。

写: 设置指定索引位置的数据,如果有这个索引位置, 那么就是给这个索引位置设置,如果没有这个索引位置, 那么就是添加,如果这个数字超出 length 很多, 那么中间的位置用空补齐。

数组的遍历

数组是按照索引进行排列的,for 循环能给我们提供一组有规律的数字,使用 for 循环遍历一个数组。

数组也是一个对象,数组除了可以按照索引排列一些数组,还可以当作对象使用, 使用 点语法 存储一些数据,你把数组当作对象使用的时候, 他的成员是不占用 length 位置的,当你把数组当作对象使用 for in 循环来遍历的时候,里面的每一个索引位置, 每一个 key 都会遍历出来,一般不会拿他当作对象使用

冒泡排序

双层 for 循环,一层减一次,里层减外层,,变量相交换

for (var i = 0; i < arr.length - 1; i++) {
   for (var j = 0; j < arr.length - 1 - i; j++) {
   		if (arr[j] > arr[j + 1]) {
          var tmp = arr[j]
          arr[j] = arr[j + 1]
          arr[j + 1] = tmp
        }
   }
}
计数排序

利用数组的索引,因为数组的索引也是一个数字

  1. 遍历原始数组,准备一个新的空数组,遍历原始数组,把原始数组里面的每一个数字当作索引填充到新数组里面
  2. 把临时数组反馈到原始数组里面,遍历临时数组
var arr = [100, 87, 66, 92, 35, 35, 24, 11, 2, 3, 87, 91]
// 1. 把原始数组的数字当作索引放到新数组里面
var tmpArr = []
for (var i = 0; i < arr.length; i++) {
    if (tmpArr[arr[i]] !== undefined) {
      tmpArr[arr[i]]++
    } else {
      tmpArr[arr[i]] = 1
    }
}

// 2. 把 tmpArr 里面的索引还原到 arr 里面
arr.length = 0
for (var i = 0; i < tmpArr.length; i++) {
    if (tmpArr[i] === undefined) continue
    for (var j = 1; j <= tmpArr[i]; j++) {
      arr[arr.length] = i
    }
}
选择排序

每次选择一个最小的放在前面

循环遍历数组,假设数字 [0] 位置最小,如果哪一个数字比我还小,那就用那个小的数字的索引,把我假设的索引替换掉

重复第一个步骤

for (var j = 0; j < arr.length - 1; j++) {
	var minIndex = j
    for (var i = j + 1; i < arr.length; i++) {
       if (arr[i] < arr[minIndex]) {
         minIndex = i
       }
    }
    var tmp = arr[j]
    arr[j] = arr[minIndex]
    arr[minIndex] = tmp
}
快速排序( 递归二分法排序)

先找停的条件,只要数组的 length <= 1, 就停下来,直接返回原数组;停以外的行为,找到一个数组长度一般的取整,从数组里面把这个数据拆出来 splice,准备两个新数组, 一个表示左边, 一个表示右边,循环遍历被拆除一个数字的数组,判断大小, 选择一个数组放进去

// 1. 准备一个函数
function quickSort(arr) {
	// arr 就是要排序的数组
	// 2-1. 递归停的条件
	if (arr.length <= 1) {
	return arr
	}
	// 2-2. 不到停的时候
	var centerIndex = parseInt(arr.length / 2)
	var center = arr.splice(centerIndex, 1)[0]
	// 准备两个数组
	var left = []  // 小
	var right = []  // 大
	// 循环遍历 arr
	for (var i = 0; i < arr.length; i++) {
		// 判断大小, 决定放在那个数组里面
		if (arr[i] < center) {
			left.push(arr[i])
		} else {
			right.push(arr[i])
		}
	}
	// 向里面递归
	// 因为 concat 不改变原始数组, 他是拼接好以后成为一个新数组
	return quickSort(left).concat(center, quickSort(right))
}
插入排序

从 1 开始循环原始数组,因为第一个数字不用比;把当前这个数字复制一份,为了覆盖以后还能有这个数字;留下一个从哪来开始比的索引,开始比较的索引就是当前索引的前一个;向前比较,遇到比我小的, 就停下来了,如果前一个比我大, 就用前一个把我覆盖掉

var arr = [1, 8, 5, 3, 2, 4, 6, 7, 9]
for (var i = 1; i < arr.length; i++) {
	var tmp = arr[i]
	var j = i - 1
	while (arr[j] > tmp) {
		arr[j + 1] = arr[j]
		j--
	}
	arr[j + 1] = tmp
}
数组常用方法
方法名语法作用返回值是否操作原始数组
push数组.push(数据1, 数据2, 数据3, ...)把所有得参数按照顺序追加到数组得末尾位置追加以后数组得长度直接操作原始数组
pop数组.pop()删除数组得最后一个数据被删除得数据
unshift数组.unshift(数据1, 数据2, 数据3, ...)从数组得最前面插入一些数据插入后得数组长度
shift数组.shift()删除数组得最前面一个数据被删除得数据
reverse数组.reverse()反转数组反转后的数组
sort 1. arr.sort() -> 按照每一个数据中得每一位数据得 ASCII 码进行排列 2. arr.sort(function (a, b) { return a - b }) -> 升序排列 3. arr.sort(function (a, b) { return b - a }) -> 降序排列数组排序排序后的数组
splice 1. 数组.splice(开始索引, 多少个),从开始索引, 截取多少个,第二个参数可以不写, 直接到末尾 2. 数组.splice(开始索引, 多少个, 替换数据1, 替换数据2, 替换数据3, ...),把替换数据按照顺序插入到你截取得位置。注意: 从哪个索引开始删除, 替换数据得第一个就插入哪个位置 有两个 1. 截取数组 2. 替换新内容 一定是一个数组 1.如果你截取多个数据, 数组里面有多个 2.如果你截取一个数据, 数组里面有一个 3.如果你一个都不截取, 那么是一个空数组
concat数组.concat(数组1, 数据2, ...)如果参数是数组, 那么把数组拆开, 里面每一项追加到原数组后面;如果参数数数据, 那么直接追加追加好得数组不改变原始数组
slice1. 数组.slice(开始索引, 结束索引) - 包前不包后,第一个参数可以不写, 表示从头,第二个参数可以不写, 表示到尾 2. 数组.slice(开始索引, 结束索引) - 包前不包后,参数可以写一个负整数,当你书写了一个负整数以后, 表示 length + 负整数获取数组里面得某些数据一个数组,如果你获取多个数据, 数组里面有多个;如果你获取一个数据, 那么数组里面有一个;如果你一个都不获取, 那么是个空数组
join数组.join('连接符号');不传递, 是按照 逗号(,) 连接;传递什么, 按照什么连接把数组里面得每一个数据使用连接符号连接在一起是一个连接好得内容, 是一个 String 类型
indexOf1. 数组.indexOf(数据) 2. 数组.indexOf(数据, 开始索引),从哪个索引开始向后查找正向查看数组里面指定这个数据得索引如果有这个数据, 是第一个满足条件得数据得索引;如果没有这个数据, 那么是 -1
lastIndexOf 1. 数组.lastIndexOf(数据) 2. 数组.lastIndexOf(数据, 开始索引),从哪一个索引开始向前查找反向查看数组里面指定这个数据得索引如果有, 就是找到得第一个数据得索引;如果没有就是 -1 注意: 虽然是从后向前查找, 但是索引还是正常索引
forEach数组.forEach(function (item, index, arr) {}) item: 数组得每一项 index: 数组每一项得索引 arr: 原始数组取代 for 循环得作用, 遍历数组没有返回值
map数组.map(function (item, index, arr) {})映射数组是一个新的数组,里面是对原始数组每一个数据得操作,返回值数组, 一定和原始数组长度一样
filter数组.filter(function (item, index, arr) {})过滤原始数组中得数据, 把满足条件得放在新数组里面新数组, 里面是所有原始数组中满足条件得项
every数组.every(function (item, index, arr) {})判断原始数组里面是不是每一个都满足条件 是一个布尔值,如果原始数组中每一个都满足条件, 那么返回 true;只要原始数组中有任意一个不满足条件, 那么就返回 false
some数组.some(function (item, index, arr) {})判断数组中是不是有某一个满足条件 一个布尔值,如果数组中有任意一个数据满足条件, 那么返回 true;如果数组中所有数据都不满足条件, 那么返回 false
copyWithin数组.copyWithin(目标位置, 开始索引, 结束索引);目标位置: 当你替换内容得时候, 从哪一个索引位置开始替换;开始索引: 数组哪一个索引位置开始当作替换内容, 默认值是 0;结束索引: 数组哪一个索引位置结束当作替换内容, 默认是末尾;包前不包后使用数组里面得内容替换数组里面得内容是一个新的数组,替换后得数组
fill数组.fill(要填充得数据, 开始索引, 结束索引);要填充得数据: 你想用什么数据填充数组里面每一位;开始索引: 从哪一个索引开始填充, 默认值是 0;结束索引: 填充到哪一个索引位置, 默认值是 末尾;前提: 数组要有 length;包前不包后使用指定数据区填充数组填充好得数组
includes数组.includes(数据)查看数组中是不是有某一个数据有这个数据, 就是 true;没有这个数据, 就是 false
flat数组.flat(数字)数字: 表示扁平化多少层, 默认是 1;数字这个参数还可以填写 Infinity拍平数组
flatMap数组.find(function (item) {})根据条件找到数组里面满足条件得数据找到得那个数据
findIndex数组.findIndex(function (item) {})根据条件找到数组里面满足条件得数据找到得那个数据得索引

字符串

JS 创建字符串有两种方式:字面量创建,var str = ‘hello world’;内置构造函数创建,var str = new String(‘hello world’);

字符串也有一个 length 属性,表示字符串的长度,也就是字符串里面有多少个字符,注意: 在字符串里面每一个空格都算一个字符;字符串里面的 length 属性是一个只读的属性,你如果要设置, 不会报错, 只是设置不成功。

字符串也是按照索引进行排列,可以使用 索引 获取字符串中某一个字符,字符串的索引只能获取, 不能设置,不会报错, 只是设置不成功

字符串因为按照索引排列,也可以使用 for 循环遍历

模板字符串

ES2015 以前, 我们拼接字符串使用 (+);ES2015 的标准中推出了一种新的字符串定义方式,使用 反引号(``)

反引号 定义的字符串叫做模板字符串,和 普通字符串的区别

  1. 单引号和双引号定义的字符串不能换行,模板字符串可以换行书写,当我需要使用 JS 组装一个 html 结构的时候。
  2. 单引号和双引号不能直接在字符串里面解析变量, 模板字符串可以直接在字符串内解析变量,当你需要解析变量的时候,只要写 ${ 变量 }。
  3. 兼容性问题,ES6 语法,IE 低版本不支持
字符串常用方法

所有字符串方法都不会改变原始字符串

方法名语法作用返回值
charAt字符串.charAt(索引)该索引位置的字符,如果有该索引位置, 就是索引位置字符,如果没有该索引位置, 是一个空
charCodeAt字符串.charCodeAt(索引)该索引位置的字符编码(UTF-8编码)
substr字符串.substr(开始索引, 多少个)截取字符串截取出来的字符串
substring字符串.substring(开始索引, 结束索引) - 包前不包后截取字符串截取出来的字符串
toUpperCase字符串.toUpperCase()把字符串里面的小写字母转换成大写字母转换好以后的字符串
toLowerCase字符串.toLowerCase()把字符串里面的大写字母转成小写字母转换好以后的字符串
replace字符串.replace(‘要被替换的字符’, ‘替换成的字符’)替换字符串内的某些字符,只能替换查找到的第一个替换好的字符串
concat字符串.concat(字符串)拼接字符串拼接好的字符串
slice字符串.slice(开始索引, 结束索引) - 包前不包后,和 substring 的区别就是可以写 负整数截取字符串截取好的字符串
split字符串.split(‘切割符号’, 多少个)切割符号, 按照你写的符号把字符串切割开,如果不写, 那么就直接切割一个完整的,如果写一个空字符串(’’), 按照一位一位的切割
多少个, 选填, 默认是全部, 表示你切割完以后保留多少个
一个数组的形式保存每一段内容,不管按照什么切割, 返回值一定是一个数组
indexOf字符串.indexOf(字符串片段)
字符串.indexOf(字符串片段, 开始索引)
在字符串里面查找指定字符串片段如果查询到了, 就是指定索引;如果没有, 就是 -1
lastIndexOf字符串.lastIndexOf(字符串片段)
字符串.lastIndexOf(字符串片段, 开始索引)
从后向前查找对应的字符串片段如果查询到了, 就是指定索引;如果没有, 就是 -1
includes字符串.includes(‘字符串片段’)字符串里面是否包含该字符串片段布尔值,有就是 true,没有就是 false
search字符串.search(‘字符串片段’)查找字符串里面有没有匹配的字符串片段如果有, 就是指定索引;如果没有, 就是 -1
和 indexOf 的区别,没有第二个参数,search 参数可以写正则
match字符串.match(‘字符串片段’)找到字符串里面的字符串片段是一个数组,里面是找到的字符串片段
trim字符串.trim()去除首尾空格去除空格以后的字符串
trimStart字符串.trimStart()去除开始的空格,别名: trimLeft()去除空格以后的字符串
trimEnd字符串.trimEnd()去除尾部空格,别名: trimRight()去除空格以后的字符串
padStart字符串.padStart(目标长度, ‘填充字符串’)
目标长度: 你想把字符串补充到多长;如果你写的长度小于字符串本身长度, 那么这个函数没有意义;超过长度以后, 用填充字符串补齐
填充字符串: 可以是一个字符, 也可以是多个,多个的时候, 如果超长后面的就不要了
从前面字符串补齐补齐以后的字符串
padEnd字符串.padEnd(目标长度, ‘填充字符串’)
目标长度: 你想把字符串补充到多长,如果你写的长度小于字符串本身长度, 那么这个函数没有意义,超过长度以后, 用填充字符串补齐
填充字符串: 可以是一个字符, 也可以是多个,多个的时候, 如果超长后面的就不要了
从后面字符串补齐补齐以后的字符串
startsWith字符串.startsWith(‘字符串片段’)判断该字符串是不是以这个字符串片段开始一个布尔值
endsWith字符串.endsWith(‘字符串片段’)判断该字符串是不是以这个字符串片段结尾一个布尔值

正则表达式 (Regular Expression)

创建一个正则表达式
  1. 字面量形式创建,var reg = /abcd/,不能进行字符串拼接,书写基础元字符的时候直接写 \xx
  2. 内置构造函数创建,var res = new RegExp(‘abcd’),可以进行字符串拼接,书写基础元字符的时候要书写 \\xx
正则表达式的标识符

标识符是写在正则表达式的外面, 用来修饰整个正则表达式的

  • i 忽略大小写
  • g 全局
  • y 粘性全局

语法:

/abcd/igy

new RegExp(‘abcd’, ‘igy’)

正则表达式的两个方法
  1. 匹配: 验证字符串是不是符合正则规则,语法: 正则.test(你要检测的字符串),返回值: 一个布尔值, true 或者 false

  2. 捕获: 从字符串里面获取符合正则规则的那一部分片段,语法: 正则.exec(你要捕获的字符串),

    返回值:

    • 字符串里面没有符合规则的片段,null
    • 字符串里面有符合规则的片段
      1. 基础捕获,返回值是一个数组,数组[0] 是捕获出来的片段,不管有多少个片段, 都只是捕获第一个片段,不管捕获多少次, 都是第一个片段
      2. 当正则表达式有 () 的时候,返回值是一个数组,从 索引[1] 开始依次是每一个小括号的单独捕获
      3. 当正则有全局标识符 g 的时候,第二次捕获会从第一次捕获的结束为开始继续向后查找,直到找不到了位置, 返回 null,再后面一次捕获, 依旧会从 [0] 位置开始查找
正则表达式的元字符

基础元字符

元字符: 组成正则的基本符号,以符号的形式来代替文本内容,把所有的文本内容归结成一些符号来代替

  • \s 表示一个空格
  • \S 表示一个非空格
  • \t 表示一个制表符(tab),一个制表符就是制表符, 不是多个空格
  • \d 表示一个数字
  • \D 表示一个非数字
  • \w 表示一个 数字字母下划线,表示 数字 字母 下划线 三选一 得有一个
  • \W 表示一个 非数字字母下划线,表示 数字 字母 下划线 以外的任意一个都行
  • 点(.) 表示非换行的任意字符
  • 斜线() 表示转义符,把没有意义的内容转换成有意义的内容,把有意义的内容转换成没有意义的内容

边界元字符

  • ^ 表示字符串开始
  • $ 表示字符串结束

限定元字符

写在普通元字符或者字母符号的后面,修饰前面 一个符号 的出现次数

  • * 表示出现 0 ~ 多次
  • + 表示出现 1 ~ 多次
  • ? 表示出现 0 ~ 1 次
  • {n} 表示出现 n 次
  • {n,} 表示出现 n ~ 多次
  • {0,} 等价于 *
  • {1,} 等价于 +
  • {n,m} 表示出现 n ~ m 次
  • {0,1} 等价于 ?
正则的贪婪和非贪婪

当你给一个符号使用限定符的时候,在你捕获的时候, 他会尽可能多的去捕获内容,这个特性叫做正则的贪婪性

非贪婪,正则在捕获的时候尽可能的按照最小值来捕获,写限定符的时候, 在后面多加一个 ?

特殊字符
  • (),一个整体,单独捕获,在你捕获一个字符串的时候,从左边开始每一个小括号依次是数组里面的 [1] 开始的内容,从左到右依次数小括号的开始括号
  • (?: ),整体匹配但不捕获,只是标志一个整体, 但是捕获的时候不会单独捕获出来
  • |,占位或,表示左边或者右边的都行,大部分时候和 () 连用, 表示一个整体或者另一个整体,注意: 分开的是左边或右边整个
  • [],注意: 一个 [] 占一个字符位置,表示里面的任意一个都行
  • [^],注意: 一个 [^] 占一个字符位置,表示非里面的任意一个都行
  • -,表示 至 或者 到,是使用在 [] 里面的一个符号,表示 从 哪一个字符 到 哪一个字符,前提是他们在 ASCII 码是连着的
组合形式
  1. [0-9a-zA-Z_] 等价于 \w_
  2. [^0-9a-zA-Z_] 等价于 \W
  3. [0-9] 等价于 \d
  4. [^0-9] 等价于 \D
  5. [ ] 等价于 \s
  6. [^ ] 等价于 \S

特殊说明

当 点(.) 出现在 [] 或者 [^] 里面的时候,表示一个 点 文本

正则表达式的预查
  • 正向预查 (?=),正向肯定预查,当我在捕获一个内容的时候, 后面必须跟着是我选择的某一个才可以
  • 正向否定预查 (?!),当我在捕获一个内容的时候, 后面必须跟着不是我选择的某一个才可以
  • 负向预查 (?<=),负向肯定预查,当我在捕获一个内容的时候, 前面必须是我选择的某一个才可以
  • 负向否定预查 (?<!),当我在捕获一个内容的时候, 前面必须不是我选择的某一个才可以
字符串和正则合作的方法

这些方法都是字符串的常用方法, 只不过参数位置可以写正则

  1. search(),语法:字符串.search(字符串片段),字符串.search(正则表达式)。返回值:如果有就是对应的索引,如果没有就是 -1
  2. replace(),语法:字符串.replace(字符串片段, 要替换的内容);字符串.replace(正则表达式, 要替换的内容)。返回值:只能替换第一个查找到的内容, 返回替换好的字符串;没有全局标识符 g 的时候, 只能替换第一个查找到的内容, 返回替换好的字符串;有全局标识符 g 的时候, 会把字符串内所有满足正则规则的内容全部替换, 返回替换好的字符串。
  3. match(),语法:字符串.match(字符串片段);字符串.match(正则表达式)。返回值:查找到字符串内一个满足字符串片段的内容返回, 返回格式和 exec 一模一样;当正则表达式没有全局标识符 g 的时候, 返回值和 exec 方法一模一样;当正则表达式有全局标识符 g 的时候, 返回一个数组, 里面是所有满足条件的内容

补: 正则匹配中文

正则表达式里面 \u 表示查找中文,后面带上中文的 四位 unicode 编码,[\u4e00-\u9fa5] 表示任意一个中文字符

json 格式

  1. 描述数组或者对象数据类型
  2. 对象中的 key 和 value 都使用 双引号 包裹,数字和布尔可以不需要引号
  3. 数组里面可以放多个对象
  4. 当多个数据的时候, 最后一个数据后面不能有 逗号(,)
  5. 一个 json 格式中, 可以使用符号, 只有 {}, [], “”, 逗号
  6. 转换 json 格式字符串的时候, 函数会被自动过滤

两个方法

JSON.parse(),语法: JSON.parse(要转换的 json 格式字符串),返回值: JS 格式的数组或者对象

JSON.stringify(),语法: JSON.stringify(要转换的数组或者对象),返回值: 一个 json 格式的字符串

本地缓存

把一些数据记录在浏览器中,多种本地缓存之一。

localStorage

sessionStorage

作用:浏览器给我们提供的一些本次存储数据的机制

区别:localStorage 永久缓存, 除非手动删除;essionStorage 会话缓存, 关闭浏览器就没有了

共同点:只能存储字符串格式的数据,想存储对象数据结构, 转换成 json 格式存储。

查看:控制台、application、localStorage

语法:

localStorage

  1. localStorage.setItem(‘名字’, ‘值’),存储一条数据,当你重复设置同一个名字的时候, 就是修改
  2. localStorage.getItem(‘名字’),获取一条数据,如果你获取一个没有的名字, 那么是 null
  3. localStorage.removeItem(‘名字’), 删除一条数据
  4. localStorage.clear(),清除所有数据

sessionStorage

  1. sessionStorage.setItem(‘名字’, ‘值’),增加一条数据
  2. sessionStorage.getItem(‘名字’),获取一条数据
  3. sessionStorage.removeItem(‘名字’),删除一条数据
  4. sessionStorage.clear(),清除所有数据

数学方法

JS 里面有一个内置对象叫做 Math,里面存储了一些操作数字的方法

方法名语法返回值
randomMath.random()0 ~ 1 之间的随机小数, 包含 0 不包含 1
roundMath.round(数字)四舍五入以后取整的数字
ceilMath.ceil(数字)向上取整以后的数字
floorMath.floor(数字)向下取整以后的数字
powMath.pow(数字, 多少次幂)数字取幂以后的结果
sqrtMath.sqrt(数字)数字的算术平方根,只能是平方根
absMath.abs(数字)数字的绝对值
maxMath.max(数字1, 数字2, 数字3, …)若干个数字中的最大值
minMath.min(数字1, 数字2, 数字3, …)若干个数字中的最小值
PI 属性Math.PI近似于 派 的,值使用不需要 (),两个字母都是大写
数字转化进制

进制是一种数字的表示方法,进制: 2 ~ 36 进制

js里面转换进制分成两种方法:十进制转换其他进制; 其他进制转换十进制

十进制转换其他进制,使用方法: toString()

语法: 数字.toString(你要转换的进制)

返回值: 以字符串的形式返回给你转换好进制的数字

如果不以字符串的形式返回, 那么在 JS 里面自动转换成 十进制

注意: 返回值是一个 字符串,不能直接加法,其他计算不能直接按照转数字的方法转换,如果想进行数学计算, 要按照转换进制的方法转换回来

其他进制转换十进制,使用方法: parseInt()

语法: parseInt(要转换的数字, 你把这个数字当作几进制)

返回值: 转换好以后十进制的数字

注意: 返回值虽然是一个数字,但是你要是进行数学运算, 考虑你是按照原先进制计算, 还是按照十进制计算

保留小数

指定保留几位小数,toFixed()

语法: 数字.toFixed(你要保留几位小数)

返回值: 以 字符串 的形式返回结果

如果不够指定小数位, 用 0 补齐,会以四舍五入的形式保留小数

封装范围内随机整数
function rangeRandom(a, b) {
	var max = Math.max(a, b)
	var min = Math.min(a, b)
	var res = Math.floor(Math.random() * (max - min + 1) + min)
	return res
}
生成一个随机颜色
function randomColor(type) {
	if (!type) {
    	// 返回一个 rgb 颜色
        var res = `rgb(${ rangeRandom(0, 255) }, ${ rangeRandom(0, 255) }, ${ rangeRandom(0, 255) })`
        return res
     }
	// 代码能来带这里, 表示 type 是一个 true
    // 生成一个 十六进制 的颜色, 返回
    // 将来我要返回一个 #ABCDEF
    // 前面 # 不变, 后面每两个数字是 0 ~ 255 的随机数字转成 16 进制
    var str = '#'
    // 循环一段代码执行三次
    for (var i = 0; i < 3; i++) {
        var n = rangeRandom(0, 255).toString(16)
        // toString 返回值是一个 字符串, 可以直接判断 length
        if (n.length === 1) {
          n = '0' + n
        }
        console.log(n)
        str += n
    }

    // 循环结束, str 就是拼接好的颜色
    return str
}

时间对象

JS 里面有一个内置构造函数叫做 Date(),专门用来创建时间对象的,时间对象是一个复杂数据类型

语法: var time = new Date()

返回值: 当前终端的当前时间,你把你的电脑的时间调整以后, 得到的是调整以后的时间

创建一个指定日期的时间对象,通过传递参数的方式获得

  1. 传递数字

    var time = new Date(2022, 1, 29, 12, 30, 1, 100)

    注意: 至少传递两个参数, 一个参数的时候, 获取的时格林威治时间

    注意: 除了年, 之外的每一个数字都会自动进位

  2. 传递字符串

    ‘yyyy-mm-dd HH:MM:SS’

    ‘yyyy/mm/dd HH:MM:SS’

    var time = new Date(‘2022-12-12 12:13:14’)

    注意:当你使用字符串这个形式的时候, 1 表示 1 月, 12 表示 12 月;年月日和时分秒中间有一个空格

获取时间对象信息的方法

从一个时间对象里面获取某些我需要的信息,JS 提供了一些方法, 专门获取时间对象内部信息

  1. getFullYear(),语法: 时间对象.getFullYear(),返回值: 该时间对象的年份信息, number 数据类型

  2. getMonth(),语法: 时间对象.getMonth(),返回值: 该时间对象的月份信息, number 数据类型

    注意: 0 表示 1 月, 11 表示 12 月

  3. getDate(),语法: 时间对象.getDate(),返回值: 该时间对象的日期信息, number 数据类型

  4. getHours(),语法: 时间对象.getHours(),返回值: 该时间对象的小时信息, number 数据类型

  5. getMinutes(),语法: 时间对象.getMinutes(),返回值: 该时间对象的分钟信息, number 数据类

  6. getSeconds(),语法: 时间对象.getSeconds(),返回值: 该时间对象的秒钟信息, number 数据类型

  7. getMilliseconds(),语法: 时间对象.getMilliseconds(),返回值: 该时间对象的毫秒信息, number 数据类型

获取世界标准时间的时间信息

getUTCFullyear()、getUTCMonth()、getUTCDate()、getUTChours()、getUTCminutes()、getUTCseconds()、getUTCmilliseconds()

获取时间对象的星期信息

getDay()

语法:时间对象.getDay()

返回值:该时间对象的一周中的第几天, 周几的信息, number 数据类型

注意:0 表示 周日, 1 表示 周一, 6 表示 周六

获取时间戳

getTime()

语法: 时间对象.getTime()

返回值: 该时间对象的时间戳

时间戳

格林威治时间: 1970 年 1 月 1 日 0 点 0 分 0 秒,计算机元年

时间戳:时间对象 到 格林威治 时间的 毫秒数

设置时间对象的信息

通过一些方法, 设置时间对象中某一个指定信息

  1. setFullYear(),语法: 时间对象.setFullYear(你要设置的年),作用: 修改该时间对象中的年份信息
  2. setMonth(),语法: 时间对象.setMonth(你要设置的月),作用: 修改该时间对象中的月份信息,注意: 0 表示 1 月, 11 表示 12 月
  3. setDate(),语法: 时间对象.setDate(你要设置的日),作用: 修改该时间对象中的日期信息
  4. sethours(),语法: 时间对象.sethours(你要设置的时),作用: 修改该时间对象中的小时信息
  5. setMinutes(),语法: 时间对象.setMinutes(你要设置的分),作用: 修改该时间对象中的分钟信
  6. setSeconds(),语法: 时间对象.setSeconds(你要设置的秒),作用: 修改该时间对象中的秒钟信息
  7. setMillseconds(),语法: 时间对象.setMillseconds(你要设置的毫秒),作用: 修改该时间对象中的毫秒信息
  8. setTime(),语法: 时间对象.setTime(时间戳),作用: 直接根据时间戳, 定位到指定时间
  9. 一整套按照 UTC 时间设置的方法
获取时间差

获取两个时间节点之间相差 xx 天 xx 小时 xx 分钟 xx 秒钟

思路:

  1. 准备两个时间对象

  2. 两个时间对象相减(可以, 有兼容问题, IE 低版本不好使),得到: 就是两个时间对象时间戳的差值,为了兼容, 获取两个时间节点的时间戳, 相减,相减以后取一个绝对值, 不需要考虑谁大谁小,结果是 ms, 最好 / 1000 以后再取整

  3. 换算

    换算天,diffTime / 一天的秒数, 取整

    换算小时,diffTime % 一天的秒数 / 一小时的秒数, 取整

    换算分钟,diffTime % 一小时的秒数 / 一分钟的秒数, 取整

    换算秒钟,diffTime % 一分钟的秒数

// 1. 准备函数
function getTimeDifference(time1, time2) {
	// 2. 计算时间差
	var diffTime = Math.round(Math.abs(time1.getTime() - time2.getTime()) / 1000)
    var day = parseInt(diffTime / (60 * 60 * 24))
    var hours = parseInt(diffTime % (60 * 60 * 24) / (60 * 60))
    var minutes = parseInt(diffTime % (60 * 60) / 60)
    var seconds = diffTime % 60
    // 3. 返回
    // 按字符串方式返回, 不够灵活
    // return day + '天' + hours + '小时' + minutes + '分钟' + seconds + '秒'
    // 按数组方式返回, 指向性不明确
    // return [day, hours, minutes, seconds]
    return {
        day: day,
        hours: hours,
        minutes: minutes,
        seconds: seconds
    }
}

Set 数据结构

ES6 新增的数据结构,迭代起结构的数据,语法: new Set(),可以在实例化的时候, 传递一个数组,数组里面的每一个数据就是 set 数据类型的每一个数据

特点: 不接受重复数据,用它可以去重

常用方法

  1. add(),语法: set数据类型.add(要添加的数据)
  2. delete(),语法: set数据类型.delete(要删除的数据)
  3. has(),语法: set数据类型.has(你要判断的数据),返回值: 你要判断的数据是不是存在
  4. clear(),语法: set数据类型.clear(),清除所有数据
  5. forEach(),语法: set数据类型.forEach(function (item, item, set) {})
  6. for of 循环来遍历
  7. size 属性,表示长度, Set 数据结构里面有多少数据

Map 数据结构

因为 Object 类型只能存储字符串作为 key,ES6 的时候出现了 Map 数据结构,叫做 值 = 值 的数据结构,它可以使用复杂数据类型来作为 key 使用

语法:new Map(),实例化的时候接收一个 二维数组,里层数组的 [0] 作为 key,里层数组的 [1] 作为 value

方法

  1. set(),语法: map数据结构.set(key, value)
  2. get(),语法: map数据结构.get(你要获取的 key)
  3. delete(),语法: map数据结构.delete(你要删除的 key)
  4. clear(),语法: map数据结构.clear(),清除所有属性
  5. forEach(),语法: map数据结构.forEach(function (value, key, map) {}),用来遍历 map 数据结构的
  6. for of 循环,遍历数据 map 结构,遍历出来的是里面每一个内容 key 和 value
  7. has(),语法: map数据结构.has(你要判断的 key),返回值: 一个布尔值, 有还是没有
  8. size 属性,表示 map 数据结构里面有多少成员

定时器

JS 是单线程同步代码机制,当你写一个死循环的时候,,后面的代码就全都不执行了

WEBAPI 给我们提供了一个队列的机制,用来模拟多线程,准备了一个队列,我们叫做单线程异步

异步:不会立即执行的代码,当代码从上到下的执行, 遇到异步代码的时候,会把他放在队列里面, 先不执行,等到所有同步代码执行完毕, 再从队列里面拿到代码来执行

JS 的定时器

JS 提供了两个异步定时器机制

  1. setTimeout(),语法: setTimeout(函数, 时间ms),时间到达的时候, 执行一遍函数就结束了,延时定时器 / 炸弹定时器
  2. setInterval(),语法: setInterval(函数, 时间ms),每间隔固定时间, 执行一遍函数,间隔定时器

定时器的返回值:不分定时器种类,只表示你时页面中的第几个定时器,就是一个 number 数据类型

返回值的作用:用来关闭定时器使用的,关闭定时器

  1. clearInterval(),语法: clearInterval(要关闭的定时器返回值)
  2. clearTimeout(),语法: clearTimeout(要关闭的定时器返回值)

关闭定时器时不分种类的, 随便关,只要你的 定时器返回值是对的就可以

BOM

Browser Object Model 浏览器对象模型

浏览器给我们提供的一套操作浏览器窗口的属性和方法

BOM 的顶级对象是 window,是一个对象,,当你打开一个页面的时候就有一个 window,你在全局定义的所有变量都在 window 下,所有和 BOM 相关的 API 都是 window.xxx,在 JS 代码书写的时候,可以省略 window. 不写

浏览器窗口尺寸

指的是 浏览器 可视窗口的尺寸,浏览器有可能会出现滚动条,在一般浏览器滚动条时算浏览器的一部分的,在 MAC 上, 是不算的

两个属性:innerWidth、innerHeight

共同点: 包含滚动条的尺寸

浏览器的弹出层

在BOM 里面, 给我们提供了三个弹出层,可以在浏览器弹出一些信息

  1. alert() 警告框,语法: window.alert(‘提示文本’);返回值: undefined;弹出一段提示文本,只有一个确定按钮,
  2. confirm() 选择框,语法: window.confirm(‘提示文本’);返回值: 布尔值;弹出一段提示文本,有确定和取消两个按钮,当用户点击确定的时候, 是 true
  3. prompt() 输入框, 语法: window.prompt(‘提示文本’); 返回值:如果用户点击确定, 那么就是文本框里面的内容;如果用户点击取消, 那么就是 null;弹出一个提示文本,有一个 input 输入框,有确定和取消按钮

共同点:会阻断程序的继续执行,因为 JS 单线程,弹出层弹出以后, 如果用户没有点击按钮表示当前弹出层没有结束,直到用户操作以后, 才会继续向下执行代码

浏览器的地址栏(重点)

地址里面包含的内容的作用

传输协议:前后端交互的方式;域名:找到一台服务器电脑;查询字符串:不影响你打开页面,打开这个页面的时候携带的信息;哈希: 锚点定位

在window 下有一个成员叫做 location

location 是一个对象, 里面存储着和网页地址所有内容相关的信息

  • hash: 当前页面的 hash 值;
  • href: 是一个读写的属性(当前地址栏地址),读: 获取当前打开的页面的地址(中文是 url 编码格式),写: 设置当前打开的页面的地址(跳转页面)
  • search: 当前地址中的 查询字符串(queryString),读: 查询到的是一个字符串,这个是其他页面跳转到当前页面的时候带来的信息

location 里面 还有一个方法

reload(),重新加载当前页面,就相当于按下了浏览器左上角的刷新按钮

注意: 不能写在打开页面就能执行的地方

浏览器的历史记录

操作浏览器前进后退,window 下有一个叫做 history 的成员,是一个对象,里面包含了一些操作历史记录的属性和方法

  1. back(),语法: window.history.back();作用: 回退到上一条历史记录, 相当于 ←;前提: 你需要有历史记录, 不然没的回退
  2. forward(),语法: window.history.forward();作用: 前进到下一条历史记录, 相当于 →;前提: 你需要会退过以后, 才可以操作
  3. go(),语法: window.history.go(整数);正整数: 表示前进;0: 表示刷新当前页面;负整数: 表示后退
浏览器的版本信息

用来区分浏览器,在 window 下有一个成员叫做 navigator,navigator 是一个对象, 里面存储着浏览器的版本信息

  1. userAgent,表示浏览器的版本及型号信息
  2. appName,所有浏览器都是统一的名字 netscape,IE 低版本浏览器,IE 高版本也是 netscape
  3. platform,表示浏览器所在的操作系统
浏览器的常见事件

由浏览器行为触发的事件

  1. window.onload = function () {},页面所有资源加载完毕后执行;作用: JS 前置,当你需要把 JS 代码写在 head 标签里面的时候,最好加上一个 window.onload
  2. window.onscroll = function () {},浏览器滚动条滚动的时候触发,不管横向还是纵向, 只要滚动就触发;作用:楼层导航,顶部通栏和回到顶部按钮的显示,渐近显示页面,瀑布流
  3. window.onresize = function () {},浏览器可视窗口改变的时候触发,只要改变就会触发,一般结合 innerWidth 和 innerHeight 来判断屏幕尺寸;移动端: 横屏;响应式布局: 判断窗口大小
浏览器卷去的高度和宽度

当页面比窗口宽或者高的时候,会有一部分是随着滚动被隐藏的,我们管 上面隐藏的叫做 卷去的高度,我们管 左边隐藏的叫做 卷去的宽度

获取卷去的高度:

  1. document.documentElement.scrollTop,使用必须要由 DOCTYPE 标签

  2. document.body.scrollTop,使用必须要没有 DOCTYPE 标签

  3. 兼容写法,var scrollTop = document.documentElement.scrollTop || documentElement.body.scrollTop

    || 当作短路表达式使用的,当前面为 true 的时候, 那么就直接返回前面的值;当前面为 false 的时候, 那么就返回后面的值, 不管后面是不是 false

获取卷去的宽度:

  1. document.documentElement.scrollLeft, 使用必须要有 DOCTYPE 标签
  2. document.body.scrollLeft,使用必须没有 DOCTYPE 标签
  3. 兼容的写法,var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
短路表达式

||

作用:可以用 || 运算符分隔两个表达式,如果前面的表达式结果为 true,那么后面的就不执行了,只有前面为 false 的时候,才会执行后面的表达式。

当你使用 || 短路表达式赋值的时候,前面表达式时 true, 那么就得到前面表达式的结果,前面表达式是 false, 那么就得到后面表达式的结果

&&

作用: 可以使用 && 运算符分隔两个表达式,如果前面是 true, 那么后面的才会执行;如果前面的是 false, 那么后面的就不执行了;当你使用 && 短路表达式赋值的时候;前面表达式是 true, 那么直接运算后面表达式的结果赋值;前面表达式时 false, 那么直接把前面的结果返回

浏览器滚动到

通过 JS 代码指定 浏览器滚动到什么位置

  1. scrollTo(),语法:window.scrollTo(横向坐标, 纵向坐标),书写不需要单位, 给一个数字就可以了;如果你传递数组, 必须两个参数, 一个参数报错;特点: 瞬间定位
  2. window.scrollTo({top: 纵向坐标,left: 横向坐标,}),对象里面写几个值无所谓;特点: 可以依靠第三个配置项来决定是瞬间定位还是平滑滚动,behavior: ‘smooth’, ‘instant’,不能决定滚动时间;如果你想自己操作滚动时间,需要自己来完成

DOM

Document Object Model 文档对象模型,一套操作页面元素的属性和方法

DOM 是一个以树状结构存在的内容,DOM 的顶级是 document 表示当前文档,因为我们 PC 端的文档是插入 chrome 浏览器里面运行,所以在 PC 端, document 上面还有一个 window

对 DOM 的操作,从 document ~ 各种标签, 文本, 属性, 样式 的操作

获取 DOM 元素

通过 JS 获取到页面中的元素, 进行操作

两类标签

  1. 非常规标签

    • html:document.documentElement;
    • head:document.head;
    • body:document.body;
  2. 常规标签:不是不能获取非常规标签, 只是一般不这么用

    方法名语法返回值
    getElementById查找范围.getElementById(‘id名称’);如果有这个 id 名匹配的元素, 就是这个元素;如果没有这个 id 名匹配的元素, 那么就是 null
    getElementsByTagName查找范围.getElementsByTagName(‘标签名’);是一个伪数组(数组常用方法用不了);如果有这个标签名匹配的元素, 有多少获取多少,如果没有这个标签匹配的元素, 返回一个空的伪数组。
    getElementsByClassName查找范围.getElementsByClassName(‘类名’);是一个伪数组(数组常用方法用不了);如果有这个类名匹配的元素, 有多少获取多少,如果没有这个类名匹配的元素, 返回一个空的伪数组。
    getElementsByName查找范围.getElementsByName(‘元素name属性的值’);是一个伪数组;如果有元素的 name 属性的值匹配, 那么由多少获取多少,如果没有元素的 name 属性值匹配, 那么就是空的伪数组
    querySelector查找范围.querySelector(‘选择器’)
    能在CSS里写的选择器,都可以查找
    如果找到选择器匹配的元素, 返回第一个找到的内容;如果没有选择器匹配的元素, 返回 null
    querySelectorAll查找范围.querySelectorAll(‘选择器’)如果找到选择器匹配的元素, 有多少获取多少;如果没有选择器匹配的元素, 返回一个空的伪数组
元素的属性

id / class / style / src / type / name / href / border / … 叫做原生属性,style 是属性名, 这个属性的作用就是给元素设置内联样式

index / abc / aaa / … 自定义属性,不是标签原生自带的属性, 是我们自己随便书写的一个属性;

data-xxx 开头的属性,我们都叫做 H5 自定义属性

操作元素属性

使用 JS 语法操作标签上的三种属性

  1. 原生属性,语法: 元素.属性名;读: 元素.属性名,获取元素该属性的值;写: 元素.属性名 = ‘值’,设置该元素的该属性的值;注意: class 除外, 操作 类名使用 元素.className
  2. 自定义属性,不能直接点语法操作三个方法
    • setAttribute(‘属性名’, ‘属性值’),给元素标签上设置属性
    • getAtrribute(‘属性名’),获取元素上的属性的值;
    • removeAttribute(‘属性名’),删除元素上的属性;
    • 特点:可以操作自定义属性, 可以操作原生属性,不管你设置什么数据类型, 当你再次从标签上拿到的时候, 都是字符串
  3. H5 自定义属性,每一个元素身上有一个属性叫做 dataset,里面包含了所有 H5 自定义属性,key 是除了 data- 以外的内容,value 就是这个属性的值;操作 H5 的自定义属性,直接在dataset 里面进行操作就可以
    • 获取:元素.dataset.名字,名字: 标签上写 data-a, 使用 a;
    • 设置:元素.dataset.名字 = ‘值’,名字: 如果你在这里写 a, 那么映射在标签上是 data-a
操作元素类名

我们有两种方式操作元素类名

  1. 按照原生属性操作

    • 设置类名元素.className = ‘box’

    • 修改类名元素.className = ‘新值’

    • 追加类名元素.className = 元素.className + ‘新类名’

      注意: 新类名前面要有一个 空格

    • 删除类名,获取类名截取字符串按照空格切开, 循环遍历, 找到一个你想删除的删除掉,再写一遍

  2. H5 标准提供给我们的 API,元素身上有一个属性叫做 classList,里面包含了所有元素身上设置的类名,这个 classList 提供了一系列方法来操作

    • add(),语法: 元素.classList.add(‘你要添加的类名’)
    • remove(),语法: 元素.classList.remove(‘你要移除的类名’)
    • toggle(),语法: 元素.classList.toggle(‘你要切换的类名’),当元素有这个类名的时候, 就删除,当元素没有这个类名的时候, 就添加
操作元素文本内容

分成三种

  1. innerHTML,一个读写的属性,操作元素的超文本内容

    读: 获取元素内部的所有内容 文本 + 标签全部内容 以字符串的形式返回 语法: 元素.innerHTML

    写: 设置元素内部的超文本内容完全覆盖是的书写,语法: 元素.innerHTML = ‘你要设置的内容’,当你的字符串里面出现 html 结构的时候, 会自动解析

  2. innerText,一个读写的属性,操作元素的文本内容

    读: 获取元素内部的所有文本内容,包括子元素所有后代元素里面的文本内容,标签内容不获取语法: 元素.innerText

    写: 设置元素内部的文本内容,完全覆盖式的写入,语法: 元素.innerText = ‘你要设置的值’,当你的字符串里面出现 html 结构的时候, 不会自动解析原样输出

  3. value,一个读写的属性,操作表单元素的 value 属性

    读: 获取表单元素的 value 值=>, 语法: 元素.value

    写: 设置表单元素的 value 值,语法: 元素.value = ‘你要设置的值’

操作元素样式

样式分成两个大类:行内样式;非行内样式。

获取元素的样式

style 的方式,利用原生属性的方式,元素.style,获取到的内容是一个对象, 里面包含元素所有的可设置样式,你想获取那一个样式的值, 就从对象里面获取就可以了,但是 只能获取到行内样式

获取非行内样式

window.getComputedStyle() 方法,标准浏览器,语法: window.getComputedStyle(要获取样式的元素),返回值: 一个对象, 里面包含所有可设置样式, 每一个样式都有值, 那你没有设置的有默认值,你需要那一个样式, 直接在这个对象里面访问就可以了

currentStyle 属性,IE 低版本,语法: 要获取样式的元素.currentStyle,得到的就是一个对象, 里面包含元素的所有可设置样式, 每一个样式都有值,你需要哪一个样式, 直接在对象里面查找就可以了

说明:当你获取样式的时候,如果你使用 点语法,你获取带有中划线的样式要转化成驼峰的方式;如果你使用 数组关联语法,你可以写中划线

设置元素的样式

只有一种方式,设置元素的行内样式,前端 JS 理论上是不可以设置元素的非行内样式,如果你要修改非行内样式,是需要修改 html 文件或者 css 文件的,但是我们前端 JS 不能操作电脑上的文件

设置行内样式,语法: 元素.style.样式名 = ‘值’

DOM 节点

我们的页面是由一个一个的节点组成,页面的每一个组成部分都是一个节点

  1. document,一个页面中最大的节点, 只能有一个,承载所有节点的容器, 不属于元素,根节点
  2. html,一个页面中最大的元素节点,承载所有其他节点的,根元素节点
  3. 元素节点,head / body / div / ul / table / …,只是不同的标签在页面中的表现形式不一样。 特点: 是页面的标签
  4. 文本节点,每一段文本内容都是一个文本节点,包含 换行 和 空格,一般作为元素节点的子节点存在, 用来表示该元素节点在页面上显示的内容
  5. 属性节点,注意: 属性节点不作为独立节点出现, 必须依赖元素,因为没有元素, 属性节点就是文本
  6. 注释节点,作为独立节点出现,最为说明文本使用
节点操作

获取节点的属性

属性名语法返回值
childNodes元素.childNodes元素的所有 子节点(伪数组)
children元素.children元素的所有 子元素节点(伪数组)
firstChild元素.firstChild元素的第一个 子节点
firstElementChild元素.firstElementChild元素的第一个 子元素节点
lastChild元素.lastChild元素的最后一个 子节点
lastElementChild元素.lastElementChild元素的最后一个 子元素节点
previousSibling元素.previousSibling元素的上一个 兄弟节点(哥哥节点)
previousElementSibling元素.previousElementSibling元素的上一个 兄弟元素节点(哥哥元素)
nextSibling元素.nextSibling元素的下一个 兄弟节点(弟弟节点)
nextElementSibling元素.nextElementSibling元素的下一个 兄弟元素节点(弟弟元素)
parentNode元素.parentNode该元素的 父节点,父节点: 大部分的时候是元素, 有特殊的 document
parentElement元素.parentElement该元素的 父元素节点
attributes元素.attributes该元素的所有 属性节点
节点属性

属性节点:元素身上放的属性, 每一个属性是一个节点;

节点属性:用来描述某个节点的信息,不同的节点可以有相同的属性名,但是值不一样

节点属性有三个:

  1. nodeType:以数字的形式来表示一个节点类型,一种节点的编号。元素节点: 1;属性节点: 2;文本节点: 3;注释节点: 8;
  2. nodeName:节点的名称。元素节点: 大写标签名(全大写);属性节点: 属性名;文本节点: 所有文本节点名称全部叫做 #text;注释节点: 所有注释节点名称全部叫做 #comment。
  3. nodeValue:节点的值。元素节点: null;属性节点: 属性值;文本节点: 文本内容(包含换行和空格);注释节点: 注释内容(包含换行和空格)
创建节点

使用 JS 的语法来创造一个节点出来

  1. createElement(),语法: document.createElement(‘标签名’);返回值: 一个元素节点
  2. createTextNode(),语法: document.createTextNode(‘文本内容’);返回值: 一个文本节点, 不是字符串
  3. createComment(),语法: document.createComment(‘注释内容’);返回值: 一个注释节点
  4. createAttribute(),语法: document.createAttribute(‘属性名’);添加属性值, 节点.value = ‘属性值’,返回值: 一个属性节点
插入节点

把一个节点插入到另一个节点里面

  1. appendChild(),语法: 父节点.appendChild(子节点);作用: 把子节点插入到父节点里面, 放在最后一个节点的位置。
  2. insertBefore(),语法: 父节点.innserBefore(要插入的子节点, 哪一个子节点前面);作用: 把子节点插入到指定父节点的指定子节点前面
删除节点

删除一个已经存在的节点,可以在创建的节点里面删除, 也可以直接在页面元素里面删除

  1. removeChild(),语法: 父节点.removeChild(子节点);作用: 把子节点从父节点里面移出
  2. remove(),语法: 节点.remove();作用: 把自己移出父节点
替换节点

用一个节点替换一个已经存在的节点,可以直接替换页面元素, 也可以替换我们自己创建的节点

replaceChild(),语法: 父节点.replaceChild(新节点, 旧节点);作用: 在父节点下, 用新节点替换旧节点

克隆节点

把某一个节点复制一份一摸一样出来

cloneNode(),语法: 节点.cloneNode(参数);参数选填, 默认是 false, 不克隆后代节点;我们可以选填 true 表示克隆所有后代节点;返回值: 一个克隆好的节点

获取元素尺寸

元素的占地面积: 内容区域 + padding + border

两组方式

  1. offsetWidth 和 offsetHeight,语法:元素.offsetWidth,元素.offsetHeight;得到:元素的 内容 + padding + border 区域的尺寸;

    注意:display: none 以后是 0。

  2. clientWidth 和 clientHeight,语法:元素.clientWidth,元素.clientHeight;得到:元素 内容 + padding 区域的尺寸

    注意:display: none 以后是 0

获取元素偏移量

一个元素相对于参考系的坐标位置

  1. offsetParent,语法: 元素.offsetParent;作用: 拿到该元素获取偏移量的时候的参考父级;当你想给这个元素设置一个绝对定位的时候,他会根据谁来定位, 他的 offsetParent 就是谁。

  2. offsetLeft 和 offsetTop,语法:元素.offsetLeft,元素.offsetTop;得到:元素相对于参考父级的左边和上边的偏移量。

    注意:当你定位 right 和 bottom 的时候,会自动给你换算成 left 和 top

获取浏览器窗口尺寸

BOM 级别的获取:innerWidth,innerHeight,拿到的是包含滚动条的尺寸

DOM 级别的获取:其实就是获取页面的那一部分尺寸,document.documentElement.clientWidth;document.documentElement.clientHeight

元素的常用事件
鼠标事件
事件名说明
click鼠标左键单击
dblclick鼠标左键双击
contextmenu鼠标右键单击
mousewheel滚轮滚动
mousedown鼠标按下,鼠标按下, 不光是左键,右键, 滚轮键, 功能键
mouseup鼠标抬起
mousemove鼠标移动
mouseover鼠标移入
mouseout鼠标移出
mouseenter鼠标移入
mouseleave鼠标移出
键盘事件

不是所有元素都能触发

表单元素(有选中效果), document, window

  1. keydown,键盘按下,只要是你键盘上的按键就可以触发,中文输入法下, 也好使
  2. keyup,键盘抬起
  3. keypress,键盘按下,必须要准确嵌入到文本框里面的内容才会触发,出现在文本框里面的内容要和你按下的按键一致
浏览器事件
事件名说明
load页面加载完毕
scroll滚动
resize窗口尺寸改变
offline网络断开
online网络恢复
hashchange当 hash 值改变的时候
表单事件

表单事件绑定给 表单元素 和 form 标签的

事件名说明
change表单内容改变,当表单失焦的时候, 如果和聚焦的时候不一样叫做改变
input表单输入事件,只要你在表单内部输入内容就会触发
focus表单聚焦事件
blur表单失焦事件
submit表单提交事件,事件是绑定给 form 标签使用的,当你点击 form 里面的 submit 的时候触发
reset表单重置事件,事件是绑定给 form 标签使用的,当你点击 reset 按钮的才能触发
拖拽事件

一般元素想触发拖拽行为, 要给元素加一个属性,draggable=“true”

需要两个元素完成一个完整的拖拽:拖拽元素;目标元素。

事件名说明绑定元素
dragstart拖拽开始绑定给拖拽元素的
drag拖拽移动绑定给拖拽元素
dragend拖拽结束绑定给拖拽元素
dragenter拖拽进入目标元素绑定给目标元素,光标离开目标元素
dragover拖拽元素在目标元素里面移动绑定给目标元素
drop拖拽元素在目标元素内放手绑定给目标元素,必须要在 dragover 事件里面阻止默认行为
触摸事件

只能在移动端使用

事件名说明
touchstart触摸开始
touchmove触摸移动
touchend触摸结束
其他事件
事件名说明触发时机
transitionend过渡结束当你有过渡属性的时候,过渡结束触发, 你过渡几个属性触发多少次
selectstart开始选择当你想在页面中框选文档的时候触发
visibilitychange窗口隐藏和显示只能绑定给 document
事件三要素

div.onclick = function () {}

  1. 事件源: 在谁的身上绑定事件,div: 事件源(绑定在 div 身上的事件)
  2. 事件类型: 什么事件,click: 事件类型
  3. 事件处理函数: 当行为发生的时候, 执行哪一个函数,function () {}: 事件处理函数, 当行为发生的时候, 执行这个函数
事件的绑定
  1. dom0级 事件,事件源.onclick = function () {}
  2. dom2级 事件
    • addEventListener(),标准浏览器使用,语法: 事件源.addEventListener(‘事件类型’, 事件处理函数);可以同时给一个事件类型绑定多个事件处理函数,多个事件处理函数的时候, 顺序绑定顺序执行,至少两个参数
    • attachEvent(),IE 低版本使用,语法: 事件源.attachEvent(‘on事件类型’, 事件处理函数);可以同时给一个事件类型绑定多个事件处理函数,多个事件处理函数的时候, 顺序绑定倒叙执行,只有两个参数
事件的解绑
  1. 解绑 dom0级 事件,因为是赋值的行为,所以直接再次给他赋值为 null,就把之前的事件处理函数干掉了
  2. 解绑 dom2级 事件
    • removeEventListener(‘事件类型’, 要解绑的事件处理函数);注意: 如果你想解绑事件, 那么在你绑定事件的时候, 一定要把函数单独书写,写成一个具名函数的形式, 以函数名的形式绑定事件处理函数
    • detachEvent(‘on事件类型’, 要解绑的事件处理函数);注意: 如果你想解绑事件, 那么在你绑定事件的时候, 一定要把函数单独书写,写成一个具名函数的形式, 以函数名的形式绑定事件处理函数
/**
 * 事件绑定的兼容处理
 * @param { ELEMENT } ele 事件源
 * @param { STRING } type 事件类型
 * @param { FUNCTION } handler 事件处理函数
 */
function on(ele, type, handler) {
  if (!ele) throw new Error('请按照规则传递参数')
  if (ele.nodeType !== 1) throw new Error('事件源有问题')
  if (ele.addEventListener) {
    ele.addEventListener(type, handler)
  } else if (ele.attachEvent) {
    ele.attachEvent('on' + type, handler)
  } else {
    ele['on' + type] = handler
  }
}

/**
 * 事件解绑的兼容处理
 * @param { ELEMENT } ele 事件源
 * @param { STRING } type 事件类型
 * @param { FUNCTION } handler 事件处理函数
 */
function off(ele, type, handler) {
  if (!ele) throw new Error('请按照规则传递参数')
  if (ele.nodeType !== 1) throw new Error('事件源有问题')

  // 处理解绑的兼容
  if (ele.removeEventListener) {
    ele.removeEventListener(type, handler)
  } else if (ele.detachEvent) {
    ele.detachEvent('on' + type, handler)
  } else {
    ele['on' + type] = null
  }
}
事件对象

当一个事件触发的时候, 对本次事件的描述

例子: 鼠标按下行为,当你在浏览器上触发点击行为的时候, 要执行事件处理函数,需要一些信息来记录,点击的是哪一个元素,你点击的坐标是什么,你按下的是哪一个按键,你当前触发的事件类型是什么

如何获取事件对象

标准浏览器:直接在事件处理函数上接收一个形参,会在事件触发的时候, 由浏览器自动传递实参

IE 低版本:不需要接收形参,直接使用 window.event,在标准浏览器下也可以使用,官方给的还是兼容,书写一个兼容方式,e = e || window.event

鼠标事件的事件对象信息

事件对象里面和鼠标事件相关的一些信息

  1. 按下的按键,事件对象中有一个叫做 button 的属性,他来表示你按下的是哪一个按键,0 表示左键,1 表示滚轮键,2 表示右键

  2. 光标的坐标(重点)

    只要是鼠标事件, 任何鼠标事件都好使

    • clientX 和 clientY,光标距离可视窗口左上角的位置
    • pageX 和 pageY,光标距离文档流左上角的位置
    • offsetX 和 offsetY,光标距离元素左上角的位置

    元素: 光标触发事件的元素(不是事件源)

    扩展: 如果你不想按照里面光标触发元素的左上角计算坐标,就想按照事件源来计算坐标,

    css 样式 pointer-event: none;

    纯靠 JS 完成

    offsetParent 是谁,获取偏移量的时候的参考父元素,根据 body 来,+offsetLeft - 父元素的 offsetLeft;根据 父元素,+offsetLeft

事件的传播

当你在一个元素上触发行为的时候,会按照 结构父级 的顺序向上传播 行为,直到 window 为止

当事件触发的时候, 会按照结构父级的顺序向上传递同类型事件,事件对象里面有一个信息叫做 path, 表示当前事件传播的路径

事件的目标冒泡和捕获

  1. 目标:准确触发事件的那个元素。当你给 center 绑定一个点击事件,点击 inner 会触发,点击 center 也会触发,两次事件触发的元素是不一样的,在事件对象里面有一个属性叫做 target,表示本次事件触发的时候, 准确触发的元素,我们叫做事件目标特点: IE 低版本不支持,IE 低版本使用 srcElement,处理兼容 var target = e.target || e.srcElement
  2. 冒泡:按照 从 目标 到 window 的顺序来执行所有的事件
  3. 捕获:按照 从 window 到 目标 的顺序来执行所有的事件,addEvenetListener() 的第三个参数,默认是 false, 表示冒泡,可以选true, 表示捕获。

鼠标 移入移出事件的区别

mouseover 移入、mouseout 移出、mouseenter 移入、mouseleave 移出

行为: 都是移入行为和移出行为会触发事件

事件: enter 和 leave 不会进行事件传播

阻止事件传播

因为事件的传播, 会导致我在一个元素上触发行为,会执行多个元素的事件处理函数,阻止事件传播

  1. e.stopPropagation(),标准浏览器使用
  2. ecacelBubble = trueIE 低版本使用

兼容:方式1: if (e.stopPropagation) { } else { };方式2: try {} catch (e) {}

阻止浏览器默认行为

浏览器默认行为:不需要我们手动绑定, 本身就带有的事件行为。a 标签, 自带点击行为; form 标签, 自带表单提交;框选, 自带框选效果;鼠标右键单击, 自动弹出一个菜单…

阻止浏览器默认行为:在同类型事件里面进行阻止,你要阻止 a 的自动跳转, 那么你就在 a 标签的点击事件里面阻止;你要阻止 form 标签的表单提交, 那么就在 form 标签的 submit 事件里面阻止

  1. e.preventDefault(),标准浏览器使用
  2. e.returnValue = false,IE 低版本使用
  3. 兼容方式1: if () {} else {}方式2: try {} catch (err) {}方式3: return false

函数

定义函数

声明式函数:语法: function 函数名() {}

赋值式函数:语法: var 函数名 = function () {}

调用函数

函数名()

声明式函数:可以在声明之前调用, 也可以在声明之后调用

赋值式函数:只能在声明之后调用, 声明之前会报错

注意:函数名 和 函数名() 是不一样的,函数名是一个变量, 表示这个函数的,函数名() 是把这个函数执行掉

函数的参数

在 JS 里面,函数的参数分成两种:

  1. 形参,写在函数定义阶段的 () 里面,就相当于一个只能在函数内部使用的变量,起名遵循变量命名规则和规范,值由函数的实参来决定
  2. 实参,写在函数调用阶段的 () 里面,就是一个准确的值, 是为了给函数的形参进行赋值

形参和实参, 都可以写多个,多个的时候, 中间用 逗号(,) 分隔,多个的时候, 是按照从左到右的顺序一一对应,函数每一次的调用,形参的值都有本次调用的时候传递的实参决定

函数参数的个数关系
  • 一样多:按照从左到右的顺序一一对应
  • 实参多:前面的按照顺序一一对应, 多出来的实参, 在函数内部没有形参接收,不能直接使用
  • 形参多:前面的按照顺序一一对应, 多出来的形参,因为没有实参赋值,所以使用的时候就是 undefined
arguments

在函数内部天生自带的变量,表示所有实参的集合(伪数组)

  1. arguments 的属性

    length:表示长度, arguments 里面由多少个数据,其实就是函数调用有多少个实参,是一个读写的属性,读取的时候, 就是读取 arguments 的长度,设置的时候, 是设置 arguments 的长度

  2. arguments 的排列

    按照序号进行排列,“序号” 从 0 开始, 依次 +1,专业名字管这个序号叫做 索引(下标)

  3. arguments 里面的某一个数据的操作

    可以依靠索引来操作 arguments 里面的某一个数据,读: arguments[索引],表示要获取对应索引位置的数据;写: arguments[索引] = 要设置的值,表示把 arugments 里面对应索引位置的数据改变

    写入注意:如果你写的索引是 arguments 里面没有的一个索引, 那么就是添加;如果这个索引有, 那么就是修改

  4. arguments 作用

    • arguments 里面的数据排列是一组有规律的数字
    • 我们的循环能为我们提供一组有规律的数字
    • arguments 的索引能获取到 arguments 里面的每一个数据
预解析

预解析:当代码在浏览器执行的时候,在所有代码开始执行之前, 先把声明做好。

  1. var 关键字,会把 var 关键字定义的变量在代码执行之前声明

  2. 声明式函数,会把这个函数名在所有代码执行之前声明, 并且赋值为一个函数

    注意: 赋值式函数, var fn = function () {}, 按照 var 的规则进行解析

  3. 当函数和变量重名的时候, 在预解析阶段以函数为准

  4. if 条件不管是不是成立, 里面的代码会进行预解析

  5. return 后面的代码虽然不执行, 但是会进行预解析

作用域

全局作用域,打开一个页面就是一个全局作用域,全局作用域, 叫做 window

私有作用域(局部作用域),只有函数生成私有作用域,每一个函数就是一个私有作用域

作用域里面的预解析,分成两个阶段

  1. 全局预解析,会在页面打开的时候就进行了,只解释属于全局的内容
  2. 私有作用域的预解析,当函数执行的时候,进行预解析,函数内部的预解析, 只属于函数内部

函数执行的时候, 会进行形参赋值, 会进行预解析,一旦函数的形参和定义的私有变量重名,先进行形参赋值, 在进行预解析

自执行函数

函数调用的一种方式,function fn() {}, fn()

语法:

  1. (function () {})()
  2. ~function () {}()
  3. !function () {}()

一般的作用是单独书写 js 文件的时候使用,为了保护变量不污染全局,每一个 js 文件里面初始化使用一个自执行函数包裹;一般涉及到文件使用的内容,把需要别的文件使用的变量挂载在全局

this 指向

定义:this 是一个使用再作用域内部的关键字。全局很少用, 大部分是在函数内部使用

指向:

  1. 全局使用: window
  2. 函数使用: 不管函数怎么定义, 不管函数在哪定义, 只看函数的调用(箭头函数除外)
    • 普通调用(直接调用/全局调用),函数名(): this -> window
    • 对象调用,xxx.函数名(): this -> 点前面是谁就是谁
    • 定时器处理函数,setTimeout(function () {}, 0): this -> window,setInterval(function () {}, 0): this -> window
    • 事件处理函数,xxx.onclick = function () {}: this: 事件源(绑定再谁身上的事件),xxx.addEventListener(’’, function () {}): this: 事件源
    • 自执行函数,(function () {})(): this -> window
改变 this 指向

this 有他本身的指向性,不管你本身指向哪里, 让你指向谁, 你就指向谁

三个方法:

方法名语法参数特点作用
call就直接连接再函数名后面使用fn.call(),obj.fn.call()第一个参数, 就是函数内部的 this 指向, 第二个参数开始, 依次给函数传递参数会立即执行函数(不适合用作定时器处理函数或者事件处理函数)伪数组借用数组方法
apply直接连接再函数名后面使用, fn.apply(),obj.fn.apply()第一个参数, 就是函数内部的 this 指向,第二个参数: 是一个数组或者伪数组都行, 里面的每一项依次给函数传递参数会立即执行函数可以以数组的形式给某些功能函数传参,Math.max()
bind直接连接再函数名后面使用,fn.bind(),obj.fn.bind()第一个参数. 就是函数内部的 this 指向,从第二个参数开始, 依次给函数传递参数不会立即调用函数,会返回一个新的函数, 一个已经被改变好 this 指向的函数改变事件处理函数或者定时器处理函数的 this 指向
ES6 的箭头函数

一种新的函数定义方式,对于函数表达式的简写方式(匿名函数)

匿名函数

  • var fn = function () {}
  • var obj = { fn: function () {} }
  • setTimeout(function () {}, 0)
  • setinterval(function () {}, 0)
  • [].forEach(function () {})
  • div.onclick = function () {}
  • div.addEventListener(‘click’, function () {})

语法: () => {}:(): 形参的位置;=>: 箭头函数的标志;{}: 代码段

箭头函数的特性
  1. 箭头函数如果只有一个形参,那么可以省略小括号不写;(a) => {},a => {}

  2. 箭头函数代码段里面只有一句话, 可以省略大括号不写,并且会自动 return 这一句话的结果。() => { return 123 },() => 123

  3. 箭头函数里面没有 arguments 这个东西

  4. 箭头函数里面没有 this 关键字

    官方: 箭头函数里面的 this 是 上下文(context), 外部作用域的 this 就是箭头函数内的 this

    私人: 箭头函数的 this 是, 你的箭头函数写在哪一行, 上一行的 this 就是箭头函数里面的 this

  5. 箭头函数里面的 this 任何方法改变不了,因为箭头函数没有 this,call / apply / bind 不能改变 箭头函数的 this 指向

函数的参数默认值

给函数的形参设置一个默认值,如果你传递了实参, 就使用你传递;如果你没有传递实参, 那么就使用默认值,直接再形参后面使用 等于号(=) 进行赋值

模板字符串

ES6 定义了一种声明字符串的方式,使用 反引号(``)

特点:

  1. 可以换行书写
  2. 可以直接进行变量的拼接
  3. 模板字符串可以调用函数,字符串里面的内容是函数的参数,${} 把字符串切开, 组合成一个数组当作第一个参数,从左到右开始依次是每一个 ${} 里面的内容作为函数后面的参数
变量
变量的定义机制

一个变量(函数),定义在哪一个作用域里面,只能在当前作用域, 或者下级作用域里面使用,上一级作用域不能使用

变量使用机制

当你需要使用一个变量(函数),会首先在自己作用域内查找, 如果有, 直接使用;如果没有, 取上一级作用域查找, 如果有, 直接使用;如果还没有, 再去上一级查找,直到 window 都没有, 那么就 报错

变量赋值机制

当你需要给一个变量(函数名)赋值的时候,会首先在自己作用域内查找, 如果有, 直接赋值;如果没有, 取上一级作用域查找, 有就赋值;还没有, 再去上一级作用域查找, 有就赋值;直到 window 都没有, 把这个变量定义为全局变量, 在进行赋值

ES6 定义变量

ES6 确认了两个定义变量的关键字:let 变量;const 常量

let/const 和 var 的区别

  1. var 会进行预解析,let/const 不会进行预解析, 必须先定义后使用
  2. var 可以声明重复变量名,let/const 不能声明重复的变量名
  3. var 没有块级作用域,let/const 有块级作用域

let 和 const 的区别

  1. let 叫做变量,const 叫做常量
  2. let 可以再声明的时候不进行赋值,const 在声明的时候必须进行赋值
  3. let 生命的变量可以被修改,const 声明的常量不能被修改, 一旦修改就报错
变量的定义规范
  1. 尽量使用 let 和 const 定义
  2. 书写代码的时候尽可能优先使用 const
块级作用域

被代码块限制变量的使用方法,var: 只有函数私有作用域才能限制使用范围,let/const: 只要是能书写代码段的 {} 都能限制使用范围,可以把 循环过程中 每一次的变量限制在每一次的 {} 里面

对象
对象的创建方式
  1. 字面量创建,var o = {}
  2. 内置构造函数创建,JS 给我们提供了一个内置构造函数叫做 Object,var o = new Object()
两种创建方式的区别
  1. 字面量创建可以在创建的时候就向直接添加一些数据,数据是以键值对(key: value)的形式出现,多个数据之间使用 逗号(,) 分隔
  2. 内置构造函数目前我们不好直接添加成员,直接创建一个空对象,后期通过对象的操作语法来进行增删改查
对象的操作语法
  1. 增: 向对象里面添加一个成员。点语法:对象名.成员名 = 值;数组关联语法:语法: 对象名[‘成员名’] = 值。
  2. 删: 删除对象里面的一个成员。点语法:delete 对象名.成员名;数组关联语法:delete 对象名[‘成员名’]。
  3. 改: 修改对象里面的一个成员。点语法:对象名.成员名 = 值(原先有就是修改, 原先没有就是添加);数组关联语法:对象名[‘成员名’] = 值(原先有就是修改, 原先没有就是添加)。
  4. 查: 获取对象里面某一个成员的值。点语法:对象名.成员名,当访问一个对象里面没有的成员的时候,会给一个 undefined;数组关联语法:对象名[‘成员名’]。

注意:因为对象数据类型是一个复杂数据类型,在控制台打印的时候, 会出现两种情况,现在的样子和最终的样子,在控制台上, 你不展开对象数据类型的时候, 是当前的样子,在控制台上, 你展开对象数据类型以后, 就是最终的样子。解决问题:直接打印你想看到的值;console.table()。

对象两种操作语法的区别
  1. 点语法:不能使用变量;不能拼接字符串;点的后面是什么, 这个成员名称就是什么
  2. 数组关联语法,可以使用变量,可以拼接字符串
循环遍历对象

语法:for (var 变量 in 对象) {}

根据对象内有多少个成员执行多少回;循环的每一次, key 分别是对象的成员名称(字符串类型),可以利用 key 和 数组关联语法 来获取每一个成员的值

判断一个成员是不是在这个对象里面

使用 in 语法,成员名 in 对象名(以字符串的形式书写),对象内的每一个成员名称都必须是字符串

window 的 name 属性是一个全局天生自带的属性:

作用: 在 iframe 标签和 name 属性合作进行跨域的

特点: 被固定为字符串类型了,不管你给 name 赋值为什么数据类型,他都会自动转换成 字符串

面向对象
构造函数

构造函数的书写和使用

明确: 构造函数也是函数, 只不过是在调用的时候和 new 关键字连用了

目的: 就是为了创建一个 有属性 有方法 合理的 对象

  1. 调用必须有 new 关键字,如果没有, 那么没有创建对象的能力;只要有, 就会自动创建一个对象
  2. 在构造函数内部不要写 return,如果 return 基本数据类型, 写了白写;如果 return 复杂数据类型, 构造函数白写
  3. 构造函数在调用的时候, 如果不需要传递参数, 最后的小括号可以不写,但是推荐我们都写上
  4. 构造函数推荐首字母大写,是为了直观看出和普通函数的区别,看到首字母大写的函数, 基本上就要和 new 连用
  5. 当函数和 new 关键字连用,会创造对象, 我们称创造出来的对象叫做 实例对象,我们称创造的过程叫做 实例化 的过程,构造函数体内的 this 指向当前实例对象,也就是本次 new 的时候创建的那个对象
prototype (原型 / 原型对象)

定义: 每一个函数天生自带一个属性叫做 prototype, 他是一个对象,只要函数定义好以后, 这个 prototype 就出生了

构造函数也是函数, 构造函数也有 prototype, 我们可以像里面添加一些内容,这个天生自带的 prototype 里面有一个属性叫做 constructor,表示是哪一个构造函数伴生的原型对象

__ proto__

定义: 每一个对象天生自带一个属性, 叫做 proto, 指向所属构造函数的 prototype

实例化对象也是一个对象,实例化对象也有 proto 属性

对象访问机制

当访问一个对象的成员的时候,如果对象自己本身有, 直接返回结果, 停止查询;如果对象自己本身没有, 会自动去 proto 上访问,有就返回结果, 停止查询;如果还没有,再去 proto 上找,一直找到顶级对象的 proto 都没有,就返回 undefined

原型链

任何一个对象开始出发,按照 proto 开始向上查找,最终都能找到 Object.prototype,我们管这个使用 proto 串联起来的对象链状结构, 叫做原型链

作用: 为了对象访问机制服务

在这里插入图片描述

判断数据类型
  1. typeof,准确的判断基本数据类型,对于复杂数据类型并不准确
  2. constructor,利用原型的属性,利用对象访问机制
  3. instanceof 语法: 对象 instanceof 构造函数
  4. Object.prototype.toString.call() 语法: Object.prototype.toString.call(要检测的数据类型)
对象的方法
  1. hasOwnProperty(),查看是不是自己的属性。语法: 对象.hasOwnProperty(‘你要检测的属性名’)
  2. defineProperty() 数据劫持,一种给对象添加属性的方法,可以给一个设置的属性设置各种各样的行为状态语法: Object.defineProperty(给哪一个对象添加, key, {添加的设置})
数据劫持
Object.defineProperty(obj, 'username', {
	// 对 obj.gender 属性进行一系列的配置
	get () {
        return prop.lastName + prop.firstName
    },
	// 当修改劫持的数据的时候,就会触发这个 set 函数,接收一个形参, 就是想修改的值
	set (val) {
        // 通过在 set 里面修改 prop 对象里面的内容,来达到修改 username 属性
        let a = val.slice(0, 1)
        let b = val.slice(1)
        prop.firstName = a
        prop.lastName = b
        let inp = document.querySelector('#username')
        inp.value = obj.username
    }
})
ES6 的类

ES5 以前, 用 函数 来充当 构造函数(类),ES6 引入了一个 类 的概念,就是使用一个新的关键字来定义 构造函数(类),定义完毕以后, 就是一个类, 不能当作函数来调用,只能通过 new 来得到一个对象

class Person {
	// 构造器, 等价于我们的 构造函数题
	constructor (name, age) {
        this.name = name
        this.age = age
        this.init()
    }
    // 原型上的方法
    init () {
        console.log(this)
    }

    setScale () {

    }

    move () {

    }
}

// 使用 Person 类去创建对象
let p1 = new Person('Jack', 18)
console.log(p1)

注意: 一个 class 定义的 类 不能被当作普通函数执行,不和 new 连用就报错了

闭包

一个函数的高级应用,官方的定义: 函数内部的函数

函数的两个步骤
  • 函数定义
    1. 在堆里面开辟一个空间
    2. 把函数体内的所有代码当作字符串存储在这个空间中
    3. 把空间地址赋值给栈里面的变量(函数名)
  • 函数调用
    1. 按照存储的地址找到 函数存储空间
    2. 在调用栈(不是栈内存) 里面再次开辟一个 函数执行空间
    3. 在函数执行空间内进行 形参赋值
    4. 在函数执行空间内进行 预解析
    5. 把 函数存储空间 的代码复制一份拿到 函数执行空间 里面执行
    6. 代码全部执行完毕, 这个新开辟的函数执行空间销毁

定义在函数内部的变量,会随着函数执行完毕, 函数执行空间的销毁而销毁

一个不会被销毁的函数执行空间

函数的每一次执行会创建一个函数执行空间,当函数内部返回一个 复杂数据类型 的时候, 并且函数外部还有变量在接收,这个函数执行空间不会被销毁

用处:延长了变量的生命周期

形成闭包的条件
  1. 一个不会被销毁的函数执行空间
  2. 函数内部 直接 或者 间接 的返回一个函数
  3. 内部函数操作(访问, 赋值)着外部函数的变量

当三个条件都满足的时候,我们管内部的函数叫做外部函数的 闭包函数

闭包的作用
  1. 保护变量私有化,定义在函数内部的变量就是私有变量
  2. 在函数外部访问函数内部的私有变量,利用闭包函数访问
闭包的特点
  1. 保护变量私有化。优点: 不去污染全局。缺点: 外部不能访问, 需要闭包函数
  2. 可以在函数外部访问函数内部的变量。优点: 不局限于私有变量。缺点: 外部访问需要闭包函数
  3. 变量的生命周期。优点: 变量的声明周期被延长了。缺点: 一个不会被销毁的函数空间

致命的缺点: 一个不会被销毁的函数空间,内存泄漏

闭包销毁

不销毁的空间,返回函数,内部函数引用外部函数变量

当你想销毁闭包的时候, 只要这个不销毁的空间不存在了, 闭包就没了

不销毁的空间:返回复杂数据类型,外部有变量接收

销毁空间:外部不再有变量接收,外部接收的变量重新赋值

闭包的语法糖

闭包的语法糖: getter 获取器和 setter 设置器

作用: 把你制作闭包想做的事情, 伪装成了一个对象内部的成员

语法:要形成闭包,返回值是一个对象,在对象里面以 getter 和 setter 的语法形式返回函数

{get 函数名() {},set 函数名() {}}

function fn() {
	var num = 100
	return {
		getNum () {
			return num
		},
		setNum (val) {
			num = val
		}
	}
}
函数柯理化

一种函数的封装形式,把一个函数的两个参数拆开成为两个函数,每个函数一个参数,多个参数的时候,把第一个参数单独提取出来

// 封装: 使用正则去验证用户名
function fn(reg, name) {
	return reg.test(name)
}
// 使用的时候
const reg = /[^_]\w{5,11}/
const res = fn(reg, 'guoxiang')

// 以闭包的形式进行封装
function testName(reg) {
	return function (username) {
		return reg.test(username)
	}
}

// 使用的时候
// res 接收的是 函数内部 返回的函数
const res = testName(/^[^_]\w{5,11}$/)
// 真正进行代码开发的时候
const res2 = res('guxiang')
利用闭包,循环绑定事件
var btns= document.querySelectorAll('button')
for (var i = 0; i < btns.length; i++) {
    btns[i].onclick = (function (index) {
	// 随着循环, 每一次这个自执行函数都会执行掉
	// 这个被 return 出去的函数才是事件处理函数呢
	return function () {
        console.log(index)
    	}
	})(i)
}
继承 extend

构造函数的应用,当多个构造函数需要使用一些共同的方法或者属性的时候,把这些共同的东西拿出来, 单独书写一个构造函数,让其他的构造函数去继承自这个公共的构造函数

概念:让 B 构造函数的实例能够使用 A 构造函数的属性和方法,我们管 B 构造函数叫做 A 构造函数的子类,我们关 A 构造函数叫做 B 构造函数的父类

目的:让 B 构造函数能够使用 A 构造函数的属性和方法

原型继承

利用改变 原型链 的方式来达到继承效果,直接把父类的实例当作子类的 prototype

构造函数的原型 对象,我把你的原型赋值为一个新的对象,new Perosn 的时候, 得到的也是一个新的对象,核心代码: 子类.prototype = new 父类

原型继承的优缺点

优点:构造函数体内和原型上的都可以继承

缺点:一个构造函数的内容, 在两个位置传递参数;继承来的属性不在子类实例的身上

function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
// 子类
function Student(gender) {
    this.gender = gender
}
// 直接把父类的实例当作子类的原型对象
Student.prototype = new Person('Jack', 18)
const s = new Student('男')
console.log(s)
借用构造函数继承(借用继承 / call继承)

通过改变 父类 构造函数的 this 指向来达到继承效果,核心代码 在字类构造函数体内, 父类.call(字类的实例)

构造函数的执行

  1. 是一个普通函数, 可以当作函数直接调用
  2. 当作普通函数执行的时候, this 指向谁, 就向谁身上添加内容
  3. call 方法可以改变函数的 this 指向

借用继承的优缺点

优点:继承来的属性是在自己身上;我们一个实例化过程在一个位置传递参数。

缺点:只能继承父类构造函数体内的内容;父类原型上的内容不能继承

function Student(gender, name, age) {
	this.gender = gender
	// Person 函数内部的 this 指向 Student 的实例 this === s
	Person.call(this, name, age)
}
Student.prototype.study = function () {
	console.log('study')
}
const s = new Student('男', 'Jack', 18)
console.log(s)
组合继承

把 原型继承 和 借用构造函数继承 合并在一起使用

组合继承的优缺点

优点:父类构造函数体内和原型上的内容都能继承;继承下来的属性放在自己身上;在一个位置传递所有参数

缺点:当你给字类添加方法的时候, 实际上是添加在了父类的实例身上

function Student(gender, name, age) {
	this.gender = gender
	// 借用继承, 目的: 把属性继承在自己身上
	Person.call(this, name, age)
}
// 原型继承, 目的: 继承父类原型上的方法
Student.prototype = new Person()
// 书写属于 Student 自己的方法
Student.prototype.study = function () { console.log('study') }
// 使用 Student 创建实例
const s = new Student('男', 'Jack', 18)
拷贝继承(for in 继承)

利用 for in 循环的特点, 来继承所有的内容,先实例化一个父类的实例,使用 for in 循环来遍历这个实例对象,因为 for in 循环不光遍历对象自己, 还会遍历 __proto__,直接把父类实例身上的所有内容直接复制到字类的 prototype

拷贝继承的优缺点

优点:父类的构造函数体内的和原型上的都可以继承;constructor 能正常配套;添加自己的方法的时候, 确实是在自己的原型身上

缺点:for in 循环: for in 循环需要一直遍历到 Object.prototype;不能继承 不可枚举 的属性;继承来的属性不再自己身上

function Student(gender, name, age) {
	this.gender = gender
	const p = new Person(name, age)
	for (let key in p) {
		Student.prototype[key] = p[key]
	}
}
Student.prototype.study = function () { console.log('study') }
const s = new Student('男', 'Jack', 18)
寄生继承

实际上是一种伪继承

构造函数不要写 return,return 一个基本数据类型, 写了白写,return 一个复杂数据类型, 构造函数没有意义

核心代码:const instance = new Person(name, age);return instance

寄生继承2:不直接寄生实例, 寄生原型

寄生继承的优缺点

优点:原型和构造函数体内的都能继承下来;寄生原型的话, 自己的属性和方法依旧可以添加和使用

缺点:寄生实例的时候, 没有自己的任何内容;寄生原型的时候, 一旦修改原型上, 父类的实例也会有这些方法

function Student(name, age) {
	this.gender=  '男'
	// 寄生继承
	const instance = new Person(name, age)
	return instance
}
// s 确实是 new Student 来的
// s 就是 Student 的实例, 但是真实的内容是 Person 的实例
const s = new Student('Jack', 18)
Student.prototype.study = function () {}

// 寄生继承2

function Student(gender) {
	this.gender = gender
}
// 寄生原型
Student.prototype = Person.prototype
Student.prototype.stduy = function () {}
const s = new Student('男')
寄生式组合继承(完美继承)

合并了 寄生继承 + 原型继承 + 独立第三方构造函数 + 借用继承

核心代码

(function () {
	function Abc(name, age) {}
	// 让 第三方构造函数 来寄生 父类 的原型
	Abc.prototype = Person.prototype
	Student.prototype = new Abc()
})()
function Student(gender, name, age) {
	this.gender = gender
	// 借用继承: 继承来了父类的属性
	Person.call(this, name, age)
}
(function () {
	function Abc(name, age) {}
	// 让 第三方构造函数 来寄生 父类 的原型
	Abc.prototype = Person.prototype
	const a = new Abc()
	Student.prototype = a
})()
// 当你去修改 Student 的 prototype 的时候
// 相当于在修改 Abc 的实例, 和 Person 父类没有任何关系
Student.prototype.study = function () { console.log('study') }
const s = new Student('男', 'Jack', 18)
ES6 类的继承

ES6 把继承这个使用变成了 关键字

extends:class 字类类名 extends 父类 {}

super(): 在 constructor 里面书写一个 super();super(name, age) 等价于 Person.call(this, name, age)

注意:

  1. super 需要写在 constructor 里面
  2. 如果你要写自己的属性, 必须写在 super 后面
  3. ES6 的继承可以继承 ES5 的构造函数也可以继承 ES6 的类
class Student extends Person {
	constructor (gender, name, age) {
		super(name, age)
		this.gender = gender
	}
	study () {
		console.log('study')
    }
}
const s = new Student('男', 'Jack', 18)
console.log(s)
class Abc extends Student {
	constructor () {
		super('女', 'Rose', 20)
	}
}
const a = new Abc()
函数防抖

在一个时间节点内, 多次触发同一事件,不需要每一次都执行, 只需要在一个固定时间内, 没有重复操作的时候,执行一次事件

例子: 滚动条事件,在一直快速滚动的时候, 不要触发,等到滚动结束了, 停下来以后再触发

实现:准备一个定时器, 把你要做的事情放在定时器里面做,当你在 300 ms 内做同一个行为的时候, 把定时器关闭掉,只有当你停下来的一瞬间, 不会再继续关闭定时器的时候, 才会触发事件

var timer = null
window.onscroll = function () {
   clearInterval(timer)
   timer = setTimeout(() => {
       console.log('触发')
   }, 300)
}
函数节流

在固定时间内, 重复触发同一事件,在固定时间内, 只有第一次时候执行, 后面的每一次都不再执行了,直到设定的固定时间到达以后, 再次允许执行下一个事件

例子: 滚动

随着滚动一直触发事件,只要添加一个定时器就可以了,定时器的作用, 只是为了模拟一个多少时间以后,准备一个开关, 事件里面的代码根据开关来决定是不是执行

// 做一个开关
var flag = true
window.onscroll = function () {
    if (flag === false) return
    // 能来到这里, 说明 flag 是 true
    flag = false
    setTimeout(() => {
        // 再次把开关打开
        flag = true
    }, 300)
}
回调函数 callback

定义: 把 A 函数当作参数传递到 B 函数内部,B 函数内部以形参的方式调用 A 函数,这种函数的调用方式, 我们叫做回调函数(callback)

回调函数的缺点:回调地狱,不停的再一个回调函数里面去进行第二个回调函数的操作,就是代码没有可读性和可维护性

为什么需要回调函数? 异步,要在异步的末尾或者中间做一些事情

Promise - 承诺

一个承诺多少个状态,持续 pending,成功 resolved,失败 rejected

ES6 的语法,专门用来解决回调地狱问题

Promise 的语法

Promise 是 ES6 内置的构造函数,语法: new Promise(function () { 你要执行的异步的事情 }),实例化的时候, 这个函数接收两个参数,resolve, reject

语法:实例化对象身上由两个方法

  1. then(),promise对象.then(function () {}),then 方法的函数传递给了 实例化的 resolve
  2. catch(),promise对象.catch(function () {}),catch 方法的函数传递给了 实例化的 reject
Promise 的进阶语法

当你再一个 promise 对象的 then 里面返回一个新的 promise 对象,你可以再这个 then 的后面继续来一个 then 接收第一个 then 里面 promise 对象的结果

改变封装异步代码的思路,按照 promise 的思想来封装异步代码

/**
 * ajax 发送 ajax 请求的方法
 * @param { OBJECT } options 请求的所有配置项
 * @return { PROMISE } promise 对象
 */
function pAjax(options = {}) {
    return new Promise(function (resolve, reject) {
        ajax({
            url: options.url,
            data: options.data,
            async: options.async,
            dataType: options.dataType,
            type: options.type,
            success (res) {
                resolve(res)
            },
            error (err) {
                reject(err)
            }
        })
    })
}
pAjax({
    url: './server/a.php',
    dataType: 'json'
})
.then(res => {
    console.log('需求1: ', res)
    return pAjax({
        url: './server/b.php',
        data: res,
        dataType: 'json'
    })
})
.then(res => {
    console.log('需求2: ', res)
    return pAjax({
        url: './server/c.php',
        data: res,
        dataType: 'json'
    })
})
.then(res => {
    console.log('需求3: ', res)
})
Promise 的另一种语法

Promise.all(),目的是把多个 promise 对象封装成一个

语法: Promise.all([ promise对象1, promise对象2, … ]).then(function () {}),then 里面会接收所有 promise 完成以后的结果, 以一个数组的形式给你返回

致命缺点: 必须三个全部成功, 由任何一个失败, 那么最终你一个结果也得不到

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('结果 1')
    }, Math.random() * 5 * 1000)
})
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        // resolve('结果 2')
        reject('结果 2 失败')
    }, Math.random() * 5 * 1000)
})
const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('结果 3')
    }, Math.random() * 5 * 1000)
})
//使用 all() 把他们合成一个
Promise.all([ p1, p2, p3 ]).then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})

async / await

ES7 的语法,ES6 提出的方案, 但是 ES6 实现的不是很好,在 ES7 的时候优化过

目的:回调地狱的终极解决办法,把异步代码写的看起来像同步代码

语法:

  1. async 书写再函数的前面, 是对这个函数的修饰关键字
  2. await 的使用, 必须有 async 关键字, await 才可以再函数内部使用
  3. await 等待的必须是一个 promise 对象, 才会有等待的结果, 不然没有意义

当满足了以上三个条件以后, promise 对象本该在then 里面接收的结果,就可以直接定义变量接收,promise 里面的异步代码没有结束之前,不会继续向下执行

async function fn() {
    const res1 = await pAjax({ url: './server/a.php', dataType: 'json' })
    console.log('需求1: ', res1)
    const res2 = await pAjax({ url: './server/b.php', dataType: 'json', data: res1 })
    console.log('需求2: ', res2)
    const res3 = await pAjax({ url: './server/c.php', dataType: 'json', data: res2 })
    console.log('需求3: ', res3)
}
console.log('start')
fn()
console.log('end')
const div = document.querySelector('div')
div.addEventListener('click', async () => {
    const res1 = await pAjax({ url: './server/a.php', dataType: 'json' })
    console.log('需求1: ', res1)
    const res2 = await pAjax({ url: './server/b.php', dataType: 'json', data: res1 })
    console.log('需求2: ', res2)
    const res3 = await pAjax({ url: './server/c.php', dataType: 'json', data: res2 })
    console.log('需求3: ', res3)
})

generator 函数

一种长得很像函数,但是不是函数, 函数生成器(迭代器)

语法:在定义函数的时候, 在 function 后面 或者 函数名前面加一个 星号(*),函数内部可以使用一个 yield 关键字,类似于 return 一样的作用,可以给你制造一个结果,让这个 generator 暂停,当你再次回到这个 generator 的时候, 从上次 yield 继续向后执行代码

generator 的返回值是一个迭代器,包含一个 next() 方法,每一次 next 执行, 就会执行到下一个 yield 位置为止

// 当有了星号以后, fn 不再是一个函数了
function* fn() {
    console.log('我是第一段 代码')
    yield '第一段结束'
    console.log('我是第二段 代码')
    yield '第二段结束'
    console.log('我是第三段 代码')
    return '第三段结束'
}
// result 就是 fn 给生成一个 迭代器
const result = fn()
// 第一次, 从 fn 的开头执行到第一个 yield,
// 把 yield 后面的东西当作返回值
const first = result.next()
console.log(first)
// 第二次, 从第一次的 yield 后面开始执行到第二个 yield 结束
// 把 第二个 yield 后面的东西当作返回值
const second = result.next()
console.log(second)
const third = result.next()
console.log(third)

事件轮询(客户端 Event Loop)

就是 JS 代码的同步异步执行机制

调用栈 : 专门用来执行代码的栈,LIFO: last in first out

队列 : 异步任务排队的位置。事件队列: Event queue;微任务队列: Microsoft Queue;Promise.then();宏任务队列: Macrosoft Queue;整体代码;定时器。FIFO: first in first out

WEB APIs : 提供异步机制的,分配任务到哪一个队列;会在每一次调用栈空的时候进行 计时 和 分配

Event Loop

轮流询问 宏任务队列 和 微任务队列;从 宏任务 开始, 一个宏任务, 清空一次微任务队列;再一个宏任务, 清空一次微任务队列;直到微任务队列清空完毕, 再次访问宏任务队列也没有任务的时候;EventLoop 暂停

模块化

  1. 没有模块化的时候,按照顺序引入文件,把整合的文件放在最后面

    问题:

    • 没有办法维护,一般把整合的 js 文件起名叫做 main.js,但并不知道依赖了哪一个文件
    • 全局变量污染,每定义一个变量
    • 依赖关系不清,只能知道 c.js 里面用到了 a.js 和 b.js 文件里面的内容,b.js 里面有没有依赖到 a.js 里面的内容我也不清楚
  2. IIFE 伪模块化标准(Immediaitely Invoked Function Expression),自执行函数 (function () {})()

    所有的内容放在一个自执行函数里面,但是外界不能使用,可以把向外暴露的内容直接暴露出来

    解决问题:

    • 依赖不清,直接在自执行函数的参数位置, 能看到依赖了哪些模块
    • 变量全局污染,你后面的代码该用什么变量用什么

    问题:

    • 文件顺序不能动
    • 只能知道我依赖的几个模块, 但是模块在哪一个文件中不好说
  3. CommonJS 模块化标准,2009 年, nodejs 出现了,使用 JS 去做服务端语言,伴生的是 CommonJS 模块化标准。缺点: 只能在后端 JS 里面用

  4. AMD 模块化标准 - Async Module Definition,2011 出现的, 社区里面发起的,因为非官方, 没有关键字, 大家书写了一套叫做 require.js 的第三方文件,来实现 模块化标准,把每一个 js 文件独立出来了,使用了导入导出的语法来实现模块化。在 JS 文件里面引入另一个 JS 文件,定义模块,调用 define 的方法

    • 独立模块定义,每一个模块文件开始执行 define(),不依赖其他文件, 就是一个单纯的模块,向外暴露的内容, 直接 return 出去
    • 依赖其他模块的模块,也是一个模块文件, 但是依赖其他模块的内容,使用 define() 定义。语法: define([ 依赖文件1, 依赖文件2, … ], function (模块A, 模块B, …) {})
    • 导入 其他模块,是一个模块整合文件,直接使用 a.js 文件里面的方法,使用方法 require()。语法: require([ 依赖文件1, 依赖文件2, … ], function (模块1, 模块2) {})

    页面只需要引入最后的整合文件就可以了

    解决问题:

    • 依赖很清晰,因为只有一个文件, 那么所有的东西都在一个文件里面出现
    • 变量全局污染,没有全局污染, 都在私有作用域

    问题:

    • 依赖前置,不管多少行以后使用的东西, 都会在打开页面的时候就加再进来

    缺点: 首屏加载时间长

    优点: 后期操作流畅

  5. CMD - Common Module Defineion - 通用模块定义,2011 左右, 社区里面出现的一个标准,淘宝 “玉伯”, 开发一了个 CMD 的模块化标准,依赖于一个叫做 sea.js 的文件来实现的模块化标准。使用: 文件需要引入一个 sea.js 的文件

    • 独立模块定义,define(function (require, exports, module) { }),require() 用来导入其他文件,module.exports 为本文件导出内容,exports 是 module.exports 的别名。var exports = module.exports

    • 依赖其他模块的模块,需要依赖其他文件模块

      define( function (require, exports, module) {
          //在你需要的位置使用 require() 方法来导入。
          var modA = require('地址')
      })
      
    • 资源整合,使用 seajs.use()。语法: seajs.use([‘你要依赖的模块’], function (模块A) {})

    解决问题

    • 依赖前置,按需加载, 在需要的时候, 再加载,也留下了依赖前置的 接口

    问题:

    • 即时依赖,首屏加载快,操作不够流畅
  6. ES6 Module,2015 年发布, ES6 语法里面自带了一个模块化标准。2016 年开始, Vue 出现了, 出现了一个脚手架(开发的大框架直接给你搭建好),搭建这个架子的时候, 内置了 ES6 模块化标。2018 年, 各大浏览器厂商开始原生支持 ES6 模块化标准。2018 年中, Chrome 率先原生支持 ES6 模块化

    • 语法: 变成了 JS 的语法, 和关键字, 不需要任何第三方文件的引入
    • 特点: 页面必须在服务器上打开,live server 插件,如果想使用模块化语法, script 标签要加一个属性 type=“module”
    • 使用:
      1. 每一个文件都可以作为独立模块, 页都可以作为整合文件
      2. 导出语法,export default 导出的内容;export var num = 200
      3. 导入语法,接收 export default 导出,import 变量 from ‘哪一个 JS 文件’;接收 export 导出的内容,import { 接收变量 } from ‘哪一个 JS 文件’。

    解决问题

    • 变成关键字, 不需要依赖第三方文件
    • 每一个文件都可以变成模块文件, 也可以是整合文件

    问题:

    • 浏览器支持不好
    • 必须要在服务器上打开,一旦项目上线, 肯定是服务器打开
    • 依赖前置
  7. 2020 年,ES2020 发布新的标准,多了一个 按需加载的模块化。语法: import(你要加载的文件).then(function (res) {})

网络

传输协议

必须经历四个步骤:建立连接、发送请求(前端给后端)、返回响应(后端给前端)、断开连接

只能由前端发起,不能由后端主动沟通前端

前后端交互只能交互字符串,所有其他数据类型都不可以,中文会转成 url 编码

一个请求的四个步骤
  1. 建立连接,基于 TCP / IP 协议的三次握手,浏览器和服务器做的,目的: 为了保证通道的连接

  2. 发送请求,前端发送请求给后端, 必须以 请求报文 的形式发送,一个特殊格式的字符串文件(由浏览器进行组装)

    请求报文

    请求行

    GET / POST: 请求方式 ./login.php: 请求地址 HTTP/1.1: 传输协议版本,

    请求头,对本次请求的描述信息

    Host: 请求主机

    Accapt: 期望的数据类型

    UserAgent: 请求终端

    Content-type: 请求体的数据格式

    Cookie:

    请求空行,分隔请求体和请求头的

    请求体,前端携带给后端的参数,有的有, 有的没有

  3. 接收响应,每一个响应是由服务端接收到前端的请求以后, 给出的结果,必须以响应报文的形式发送个前端。响应报文

    状态行,200: 响应状态码(未完待续) ok: 对响应状态码的简单描述 HTTP/1.1 传输协议版本

    响应头,对本次响应的描述信息

    Date: 服务器时间(世界标准时间)

    Server: 服务器信息

    Content-Type: 响应体的数据格式

    响应体,后端给前端的数据

    响应状态码

    以一个数字表示本次请求的响应状态,100 ~ 599, 分成五类

    • 100 ~ 199: 表示连接继续
    • 200 ~ 299: 表示各种意义上的成功
    • 300 ~ 399: 表示重定向
    • 400 ~ 499: 表示客户端错误
    • 500 ~ 599: 表示服务端错误

    常见状态码

    • 101 表示连接继续
    • 200 通用成功
    • 302 临时重定向,本次请求临时使用 服务器 来决定浏览器跳转的页面
    • 301 永久重定向,终身只要访问这个地址, 就会给重新切换到新的地址
    • 304 缓存,当你访问过一遍这个页面以后,浏览器会自动缓存,当你在其访问同一个地址的时候, 不会像服务器发送请求了, 而是从缓存里面获取
    • 403 访问权限不够
    • 404 访问地址不存在
    • 500 通用服务端错误
    • 501 维护或者过载
  4. 断开连接

    基于 TCP / IP 协议的四次挥手

    为了保证断开连接

    1. 前端给后端发一个消息: “响应体收到, 我要准备断开连接了”
    2. 后端给前端发一个消息: “好的, 我知道你收到响应体了”
    3. 后端在其给前端发一个消息: “我已经准备断开连接了, 当我再次收到你的消息的时候, 我就断了, 不会再次回复”
    4. 前端收到后端的第一个消息
    5. 前端收到后端的第二个消息: “好的, 我断开了, 别回了”
GET 和 POST 请求方式的区别(重点!!!)

GET

  1. 语义是获取
  2. GET 携带参数的方式是 queryString, 在地址栏后面直接拼接, 不再请求体里面
  3. GET 理论上携带数据无限, 但是因为浏览器地址栏有限, IE 2KB
  4. GET 会被浏览器主动缓存
  5. GET 明文发送
  6. GET 只能发送 url 编码的数据(ASCII 码), 如果是中文会自动转码

POST

  1. 语义是给
  2. POST 携带载时是 requestBody, 在地址栏没有, 在请求体里面
  3. POST 理论上携带的数据无限, 但是会被服务器限制
  4. POST 请求不会被浏览器主动缓存, 除非手动设置
  5. POST 暗文发送
  6. POST 理论上可以发送任意格式的数据, 但是要和请求头里面得 content-type 配套
Cookie

浏览器端的本地存储空间,用来存储一些数据

cookie 的特点

  1. 按照域名存储的,哪一个域名存储的, 在当前域名下就可以访问,换一个域名就不能访问
  2. 按照文件路径存储,你在 a 文件夹下存储的数据,只能在 a 文件夹及子文件夹访问,在 a 文件夹的上级目录不能访问
  3. cookie 的存储时按照字符串的形式存储,‘key=value; key2=value2; key3=value3’
  4. 存储大小,4KB 左右、50条左右
  5. 时效性,默认时会话级别的时效(关闭浏览器就没有了),可以手动设置 cookie 的时效(关闭浏览器以后依旧保存)
  6. 操作权限,前端可以操作,后端可以操作
  7. 请求自动携带,只要 cookie 空间里面有数据,那么在发送任何一个请求的时候, 自动携带
/**
 * setCookie 设置 cookie 的方法
 * @param {STRING} key cookie 的 key
 * @param {STRING} value cookie 的 value
 * @param {NUMBER} expires 多少秒以后过期
 * @param {STRING} path cookie 存储路径
 */
function setCookie(key, value, expires, path) {
  // 1. 准备一个标准cookie内容
  var str = key + '=' + value
  if (expires) {
    var time = new Date()
    time.setTime(time.getTime() - 1000 * 60 * 60 * 8 + expires * 1000)
    str += ';expires=' + time
  }
  if (path) {
    str += ';path=' + path
  }
  document.cookie = str
}


/**
 * getCookie 获取 cookie 的方法
 * @param {STRING}} key 选填: 你要获取的某一个cookie 的 key
 * @return {STRING | OBJECT} 填写参数是指定key 的值, 不填写参数是 对象
 */
function getCookie(key) {
  var tmp = document.cookie.split('; ')
  var o = key ? '' : {}
  tmp.forEach(function (item) {
    var t = item.split('=')
    if (key) {
      if (t[0] === key) {
        o = t[1]
      }
    } else {
      o[t[0]] = t[1]
    }
  })
  return o
}

session

一个存在于 服务器 端的存储空间,当打开的时候, 就会生成一个 “密码”,这个密码会自动存储到 cookie 里面,等到返回前端的时候, 会自动把这个带回去,只要想向 session 空间里面存储内容或者获取内容,必须要先开启, 后使用

开启 session 空间,session_start();

存储一些数据,PHP 有一个内置的 关联型数组就叫做 $_SESSION

开启 session 空间

当我在浏览器打开 localhost/01_session.php 文件的时候,就会执行 session_start() 这段代码,后端就会开启一个存储空间, 同时生成一段 密码,同时把密码的一半放在 cookie 里面

前端访问 localhost/01_session.php 本身就是一个请求,此时后端是会给回一个响应,响应回到前端了, 那么此时看到 cookie 空间里面就应该有一个 session_id 的存在

session_start();
// 2. 存储
$_SESSION['login'] = 1;
echo $_SESSION['login'];
ajax

前后端交互的一种手段,通过 JS 向服务端发起请求,所有服务端返回的响应都不会直接显示在页面上,而是返回给 js

说明: JS 和服务端交互,依赖于浏览器来发送请求

ajax(a: async、 j: javascript、a: and、x: xml)

使用方式

  1. 找到一个对象能帮我发送 ajax 请求,XMLHttpRequest() 内置构造函数,专门创建实例化对象帮你发送 ajax 请求
  2. 对本次请求进行一些配置,open() 的方法,语法: xhr.open(请求方式, 请求地址, 是否异步),请求方式: GET POST PUT …(大小写无所谓),请求地址: 要请求哪一个后端位置,是否异步: 选填, 默认是 true, 可以选填 false, 表示同步
  3. 把请求发出去,send() 方法,语法: xhr.send()
  4. 接收响应,onload 事件,语法: xhr.onload = function () {},本次请求结束以后触发(响应成功了以后触发),xhr 里面有一个属性叫做 responseText 就是响应体
// 1. 创建一个 ajax 实例化对象
const xhr = new XMLHttpRequest()
// 2. 配置本次请求的信息
xhr.open('GET', './server/get.php')
// 4. 接收结果
xhr.onload = function () {
}
// 3. 把这个请求发送出去
xhr.send()
封装 ajax
/**
 * creXhr 创建 ajax 对象
 * @return { OBJECT } 当前浏览器使用的 ajax 对象
 */
function creXhr() {
  var xhr = null
  var flag = false
  var arr = [
    function () { return new XMLHttpRequest() },
    function () { return new ActiveXObject('Microsoft.XMLHTTP') },
    function () { return new ActiveXObject('Msxml.XMLHTTP') },
    function () { return new ActiveXObject('Msxml2.XMLHTTP') }
  ]
  for (let i = 0; i < arr.length; i++) {
    try {
      xhr = arr[i]()
      creXhr = arr[i]
      flag = true
      break
    } catch (e) {}
  }
  if (!flag) {
    xhr = '您的浏览器不支持 ajax, 请更换浏览器重试'
    throw new Error(xhr)
  }
  return xhr
}


/**
 * ajax 发送 ajax 请求的方法
 * @param { OBJECT } options 请求的所有配置项
 */
function ajax(options = {}) {
  if (!options.url) {
    throw new Error('url 为必填选项')
  }
  if (!(options.type == undefined || options.type.toUpperCase() === 'GET' || options.type.toUpperCase() === 'POST')) {
    throw new Error('目前只接收 GET 或者 POST 请求方式, 请期待更新')
  }
  if (!(options.async == undefined || typeof options.async === 'boolean')) {
    throw new Error('async 需要一个 Boolean 数据类型')
  }
  if (!(options.dataType == undefined || options.dataType === 'string' || options.dataType === 'json')) {
    throw new Error('目前只支持 string 和 json 格式解析, 请期待更新')
  }
  if (!(options.data == undefined || typeof options.data === 'string' || Object.prototype.toString.call(options.data) === '[object Object]')) {
    throw new Error('data 参数只支持 string 和 object 数据类型')
  }
  if (!(options.success == undefined || typeof options.success === 'function')) {
    throw new Error('success 传递一个函数类型')
  }
  if (!(options.error == undefined || typeof options.error === 'function')) {
    throw new Error('error 传递一个函数类型')
  }

  // 2. 设置一套默认值
  var _default = {
    url: options.url,
    type: options.type || 'GET',
    async: typeof options.async === 'boolean' ? options.async : true,
    dataType: options.dataType || 'string',
    data: options.data || '',
    success: options.success || function () {},
    error: options.error || function () {}
  }
  if (typeof _default.data === 'object') {
    var str = ''
    for (var key in _default.data) {
      str += key + '=' + _default.data[key] + '&'
    }
    _default.data = str.slice(0, -1)
  }

  // 3. 发送请求
  var xhr = creXhr()
  if (_default.type.toUpperCase() === 'GET' && _default.data) {
    _default.url += '?' + _default.data
  }
  xhr.open(_default.type, _default.url, _default.async)
  xhr.onreadystatechange = function () {
    if (xhr.status >= 200 && xhr.status < 300 && xhr.readyState === 4) {
      if (_default.dataType === 'json') {
        var res = JSON.parse(xhr.responseText)
        _default.success(res)
      } else if (_default.dataType === 'string') {
        _default.success(xhr.responseText)
      }
    }
    if (xhr.readyState === 4 && xhr.status >= 400) {
      _default.error(xhr.status)
    }
  }
  if (_default.type.toUpperCase() === 'POST') {
    xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded')
  }
  xhr.send(_default.data)
}

跨域请求

同源策略:同源策略是浏览器给的一个行为。当发送请求的时候, 会涉及到两个地址:打开当前页面的地址,要请求的地址。两个地址中的 端口号 域名 传输协议,只要由任意一个不一样, 就是非同源请求,就会触发浏览器的同源策略,不允许获取这个服务器上的数据

触发了同源策略的请求叫做 跨域请求

解决浏览器不允许请求别人家服务器的情况

基于 http 协议,jsonp、cors、代理

jsonp 跨域

script 标签,script 标签可以执行 js 代码,script 标签有一个属性叫做 type=“text/javascript”,就会把里面的代码当作 js 来解析,当你不写 type 属性的时候, 默认是 text/javascript

src 属性,src 是引入外部资源的属性,不受同源策略的影响

当上面两个加一起,只要引入任何一个内容, 都会被当作 js 代码来解析

jsonp 的核心:利用 script 标签的 src 属性,去向一个非同源的服务器请求数据,只要这个服务器能返回一个字符串,就会把这个字符串当作 js 代码来执行

jsonp 请求数据,要求服务器返回一个 函数名() 这样的字符串,要求提前准备号一个函数,要求前端告诉后端你准备好的函数名是什么,再发送请求的时候, 以参数的形式告诉后端,准备好的函数名叫做什么

  1. jsonp 原理,src 不受同源策略影响,script 标签会把请求的内容当作 js 代码来执行

  2. jsonp 的返回值,字符串, 函数名() 形式的字符串,一段可以执行的 js 代码字符串

  3. jsonp 的优缺点,优点:绕开了同源策略, 实现跨域请求。方便, 因为是以 script 标签外部资源的形式请求

    缺点:不好做安全防范

代理

利用一个正向代理的机制来实现,任何一台服务器都可以做代理,apache 服务器 代理 http 协议的是免费的,apache 服务器 代理 https 协议需要证书的,配置代理使用 nginx 服务器来配置代理

配置:phpstudy 切换到 nginx 服务器,其他选项菜单 -> 打开配置文件 -> nginx.conf,找到当前服务器的 server 标签对, 找到闭合标签的上一行书写代理配置

location = /xx {
	proxy_pass 地址;
}

/xx: 代理标识符, 当请求 /xx 的时候, nginx 会发现你在请求代理标识符,就会自动帮你转发你的请求到 proxy_pass 后面的地址;proxy_pass: 代理目标地址,只要配置文件被修改了, 那么就要重启服务器

cors - 跨域资源共享

因为跨域请求, 不是请求发不出来,实际上: 请求已经发送了, 而且到了服务器了, 响应页回到浏览器了,但是浏览器判断了是非同源位置, 不允许你使用服务器给回的数据,由服务器告诉浏览器一个事情, 这个域名我允许请求我的内容

header("Access-Control-Allow-Origin:*");
header("Access-Control-Request-Methods:GET, POST");
header('Access-Control-Allow-Headers:x-requested-with,content-type,test-token,test-sessid');
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值