一、JS是什么?
js语法名称为:ECMA Script
Ecma国际(前身为欧洲计算机制造商协会,European Computer Manufacturers Association)
ECMA Script 缩写为 ES
见说法ES5、ES6,后面的数字,代表的是语法版本号
二、JS基本操作
1.数据类型
数据类型是什么?js中对不同种类数据的一个类型分类,mdn 为数据类型一共定义了 8 种:
undefined,Boolean、Number、String、BigInt、Symbol、null,最后一种类型是:Object
数据按照参数的传递方式,可以分为 `值传递类型` 和 `引用传递类型,可以使用 `typeof` 来判断数据类型
2.获取文档元素并修改其属性
代码如下(示例):
// 1. 查询一个指定选择器的元素
let box = document.querySelector('.box')
console.log(box);
// 2. 查询所有指定选择器的元素
let boxs = document.querySelectorAll('.box')
console.log(boxs);
// 3. 通过id查询元素
box = document.getElementById('myBox')
console.log(box);
// 4. 通过类名查询元素
boxs = document.getElementsByClassName('box')
console.log(boxs);
// 5. 通过标签名查询元素
boxs = document.getElementsByTagName('div')
console.log(boxs);
// 修改属性值
console.log(box.id);
// 改id
box.id = 'my-box'
// 改样式
box.style.color = '#00f'
box.style.backgroundColor = '#000'
// 改class名称
box.className = 'box myClass'
3.通过简单的点击事件修改标签体
<script>
let box = document.querySelector('.box')
// 声明事件处理程序
function onClick() {
// alert('点击了按钮')
// 1. textContent 修改和读取标签体里的文本内容
// console.log(box.textContent);
// box.textContent = '<div style="background-color: #00f;">ok</div>'
// 2. innerText 修改和读取标签体里的文本内容
// console.log(box.innerText)
// box.innerText = '<div style="background-color: #00f;">ok</div>'
// 3. innerHTML 修改和读取标签体里的html内容
console.log(box.innerHTML)
box.innerHTML = '<div style="background-color: #00f;">ok</div>'
}
</script>
4.简单的元素事件
<script>
// 1. 通过元素的 on... 属性,例如 onclick
function clickHandler() {
console.log('按钮被点击了');
}
// 2. dom对象的 on... 属性,例如 dom.onclick
let btn = document.querySelector('button')
btn.onclick = function () {
console.log('按钮被点击了1');
}
// 3. dom.addEventListener
btn.addEventListener('click', () => {
console.log('按钮被点击了2');
})
</script>
5.读取元素样式的三种方法
方法一: dom 对象的 style 属性,style 属性只能读取元素的 style 无法读取 <style> 标签中的内容
let box = document.querySelector('.box')
console.log(box.style.width);
console.log(box.style.height);
console.log(box.style.backgroundColor);
方法二: document.styleSheets,只能读取 html 文档中 <style> 标签中的属性 无法读取元素身上的 style 属性
console.log(document.styleSheets);
// document.styleSheets[0] 代表第一个 style 标签
let styleTag = document.styleSheets[0]
// styleTag.cssRules[0] 代表第一个样式表
let boxStyles = styleTag.cssRules[0]
console.log(boxStyles.style.backgroundColor);
// 可以赋值属性
boxStyles.style.backgroundColor = '#0f0'
方法三: getComputedStyle, 可以读取元素身上所有的属性 但是无法赋值
let style = getComputedStyle(box)
console.log(style.backgroundColor);
console.log(style.width);
console.log(style.height);
6.数据类型的转换
不同类型之间的数据是可以互相转换类型的,这个过程叫数据转换
数据转换的方法分为两种:显示转换,隐式转换 显示转换:在代码中有明确的代码用于转换类型
, 隐式转换:在代码中没有明确的代码用于转换类型
// boolean 转换成其他类型
let b = true
// 转换数字
b = Number(b)
// 除了 Number 以外 还能使用 parseInt parseFloat 进行转换
console.log(b); // => 1
b = false
b = Number(b)
console.log(b) // => 0
// true 转换数字为 1 false 转换数字为 0
// 转换字符串
b = true
console.log(b);
b = String(b)
console.log(b); // => true 的字符串
b = false
b = b + '' // 连接字符串时,js会自动转换数据格式为字符串,这是一个隐式转换
console.log(b); // => false 的字符串
// 数字类型转其他类型
let num = -123
// 转boolean
num = Boolean(num)
console.log(num); // => true
num = 0
num = Boolean(num)
console.log(num); // => false
// 数字为0 结果为 false 否则结果为 true
// 转字符串
num = 1314
num = String(1314)
console.log(num); // => 1314
// 字符串转其他类型
let str = 'hello world'
// 转boolean值
str = Boolean(str)
console.log(str); // => true
str = ''
str = Boolean(str)
console.log(str); // => true
// 空字符串为 false 其余为 true
// 转数字
str = 'hello world'
str = Number(str)
console.log(str); // => NaN
str = '6e+2'
str = Number(str)
console.log(str); // => 600
// 加法运算符直接写在字符串前,可以产生转换成数字的效果
str = '6e+2'
str = +str
console.log(str);
// 若字符串代表的是合法数字则转换为对应数字 否则为 NaN
// 对象转其他类型
let obj = { a: 1, b: 2 }
// 转 boolean 值
obj = Boolean(obj)
console.log(obj); // => true
obj = null // 此处赋值 undefined 效果一样
obj = Boolean(obj)
console.log(obj); // => false
// 变量是 null 或 undefined 时 转换结果为 false;对象只要存在 结果就为 true
// 转数字
obj = { a: 1, b: 2 }
obj = Number(obj)
console.log(obj); // => NaN
// 转字符串
obj = { a: 1, b: 2 }
obj = String(obj)
console.log(obj); // [object Object]
// 对象强转字符串结果始终为 [object Object]
// 若希望将对象转换成字符串后能够查看其数据,则可以使用 “序列化”
7.节点操作
7.1插入节点:创建节点、 插入节点到文档中
// 创建节点
// 参数是标签名
// 返回一个dom对象
let img = document.createElement('img')
// 设置图片源
img.src = '../img/head.png'
// 插入节点
// document.body 就是body标签的dom对象
// appendChild 追加一个子元素
// 参数是要追加的元素
document.body.appendChild(img)
let ok = document.querySelector('.ok')
// insertBefore 插入节点到另一个节点前
// 第一个参数:要插入的元素
// 第二个参数:插入位置的子节点
document.body.insertBefore(img, ok)
// 创建元素并插入元素的用途
// 1. 异步加载 js css 等文件
// 例如异步加载js
let loadJsBtn = document.querySelector('.load-js')
loadJsBtn.addEventListener('click', () => {
// 异步加载js
// 创建标签
let script = document.createElement('script')
script.src = '../js/main.js'
// 插入script标签到body中,这样js脚本将自动加载
document.body.appendChild(script)
})
// 2. 动态追加一些以html作为模板的元素
7.2替换节点
替换节点语法:parent.replaceChild(newNode, child)、 parent: 要替换节点的父节点、newNode: 新的要插入文档的节点、child: 被替换的节点
let tr = document.querySelector('.tr')
let _td = document.querySelector('.tr>td:nth-of-type(2)')
// 构造一个新的元素用于替换
let td = document.createElement('td')
td.textContent = 'hello world'
tr.replaceChild(td, _td)
7.3查询节点
dom对象中可以查询其父节点和子节点
// 获取tr
let tr = document.querySelector('.tr')
// 访问子节点使用 children 属性
console.log(tr.children);
// children 属性包含一个子节点的集合
// 所以访问具体子节点可以使用索引值
let node = tr.children[1]
node.remove()
// 访问父节点使用 parentElement 属性
// 例如 访问tr树节点 tbody 如下:
console.log(tr.parentElement);
console.log(tr.parentElement.parentElement);
7.4异步加载JS
let btn = document.querySelector('button')
btn.addEventListener('click', () => {
// 创建script
let script = document.createElement('script')
script.src = '../js/main.js'
// load 加载完成事件
script.addEventListener('load', () => {
console.log('加载完成');
// 删除脚本
script.remove()
})
// 插入页面
document.body.appendChild(script)
})
8.浏览器操作
8.1 location
location 代表浏览器地址栏
let input = document.querySelector('input')
let button = document.querySelector('button')
button.addEventListener('click', () => {
// 给 location.href 赋值页面就会调转
window.location.href = input.value
})
// 跳转网页不计入历史
// window.location.replace('https://www.bilibili.com')
// 刷新页面
// window.location.reload()
// 地址栏参数
// location.search
8.2 localStorage
数据持久化到浏览器中,关闭窗口或浏览器,都不会消失
// 设置数据
// 第一个参数:key
// 第二个参数:value 且必须是字符串
// window.localStorage.setItem('name', '张三')
// 通过索引方式赋值 等价于 setItem
// localStorage['sex'] = 'male'
// 读取数据
let r = localStorage.getItem('name')
r = localStorage['sex']
console.log(r);
// sessionStorage 用法和localStorage相同,但是窗口关闭数据就消失了
// window.sessionStorage.setItem('age', '17')
console.log(sessionStorage['age']);
9.三目运算符
// 三元运算符: 用于判断一个表达式结果,为真时,返回结果1,为假时返回结果2
// 语法:bool expression? case1: case2
// 作用:主要用在给变量赋值
let obj = {
name: '张三',
sex: 'other',
age: 16
}
console.log(obj.sex === 'male' ? '男' : '女');
// 嵌套三元运算符 在问号或冒号处可以换行
console.log(obj.sex === 'male' ? '男' :
obj.sex === 'female' ? '女' : '其他');
// 当嵌套三元运算符在同一行内书写,请给嵌套的三元运算符的表达式用圆括号包起来
console.log(obj.sex === 'male' ? '男': (obj.sex === 'female' ? '女' : '其他'));
10.数组
10.1数组的基本操作
let arr = [1, 2, true, { name: 'Amy' }, 2]
// push 追加数据到数组末尾
// 参数:被添加的新数组成员
arr.push('hello world')
console.log(arr);
// pop 从尾部取出一个成员
// 返回值是取出的成员
let r = arr.pop()
console.log(r);
console.log(arr);
// unshift 在头部添加数据
// 参数:被添加的新数组成员
arr.unshift('my best day')
console.log(arr);
// shift 从头部取出一个成员
// 返回值是取出的成员
r = arr.shift()
console.log(r);
console.log(arr);
// push 和 unshift 可以批量添加成员
arr.push('a', 'b', 'c')
arr.unshift('x', 'y', 'z')
console.log(arr);
// splice 删除指定位置的成员,并用新成员替换,或插入新成员到指定成员的前面
// 第一个参数:删除成员的起始位置
// 第二个参数:删除成员的个数
// 第三个参数:用于替换被删除成员的新数据,该参数可以省略
// 删除一个成员:
// r = arr.splice(6, 3)
// splice的返回值 就是被删除的成员数组
// console.log(r);
// 在指定成员前追加新数据
// 若第二个参数为0,则可以实现在指定位置的前面添加成员的功能
arr.splice(6, 0, { name: 'Bob' })
console.log(arr);
// concat 连接数组
// 参数:多个被追加进数组的成员,若成员是数组,该数组中每个成员将被加入原数组
// concat 返回一个新数组
// r = arr.concat(7, 8, 9)
// r = arr.concat([7, 8, 9], [10, 11])
// console.log(r);
// console.log(arr);
// concat 的应用场景多用于克隆数组
let arr2 = [].concat(arr)
console.log(arr2);
console.log(arr === arr2);
// join 使数组成员用一个字符连接起来
// join 函数接收一个参数,该参数就是连接数组成员时使用的字符
arr2 = ['abc', 'xyz', '123']
r = arr2.join('-*-')
console.log(r);
// includes 判断是否包含某成员
r = arr.includes('z')
console.log(r);
// indexOf 查询指定数组成员的索引
r = arr.indexOf('b')
console.log(r);
// 可以使用indexOf判断是否包含某个数组成员 若不包含 返回 -1
r = arr.indexOf('g')
console.log(r); // => -1
if (arr.indexOf('g') === -1) {
console.log('该数组成员不存在');
}
// lastIndexOf 查询最后一个指定数组成员的索引(应为数组成员可能重复)
console.log(arr.indexOf(2));
console.log(arr.lastIndexOf(2));
// slice 数组切片 获取子数组
// 切片遵循“前截后不截”原理: 起始位置包含在结果内,结束位置不包含
r = arr.slice(5, 8)
console.log(r);
// 参数只有一个,代表从该位置开始 一直截取到最后
r = arr.slice(5)
console.log(r);
10.2 数组的迭代操作
以下所有的遍历函数的参数都是相同的回调函数, 回调函数接收以下参数、el 被遍历的当前
数组成员, index 被遍历的当前成员的索引,arr 被遍历的数组对象
let students = [
{ id: 0, name: '张三', sex: 'male', age: 16 },
{ id: 2, name: '隔壁老王', sex: 'other', age: 30 },
{ id: 1, name: '李四', sex: 'female', age: 20 },
]
// forEach 循环遍历每一个数组成员
students.forEach((el, index, arr) => {
console.log(el);
console.log(index)
console.log(arr);
})
// every 和 forEach 一样遍历每个数组成员,但是中途可以跳出循环
// students.every((el, index, arr) => {
// console.log(el);
// console.log(index)
// console.log(arr);
// if (el.sex === 'female') {
// // every 的回调函数中需要返回一个bool值 false 类似于循环语句中的 break 用于跳出循环
// return false
// }
// // 类似于 continue 继续循环
// return true
// })
// map 映射数组到新数组中
// map 的回调函数将返回一个值,代表当前被遍历的数组成员在新数组中的投影
// map 函数将返回一个新的投影数组
// 例如将students中所有的年龄放入一个新数组
// let r = students.map((el, index, arr) => {
// console.log(el);
// console.log(index)
// console.log(arr);
// // return 的内容将被放到新的数组中
// return el.age
// })
// console.log(r);
// filter 过滤器
// filter 返回过滤完后的新数组
// let r = students.filter((el, index, arr) => {
// console.log(el);
// console.log(index)
// console.log(arr);
// // 过滤掉年龄大于25岁的成员
// // if (el.age > 25) {
// // // return false 代表过滤掉该数据
// // return false
// // }
// // return true // return true 代表保留该数据
// return el.age <= 25
// })
// console.log(r);
// find 查找
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/find
// some 查询是存在满足条件的成员
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/some
// find 查找符合回调函数条件的数组成员并返回它
// 返回值为查询结果
// let r = students.find((el, index, arr) => {
// // // 查找女同学
// // if (el.sex === 'female') {
// // return true // return true 代表当前成员就是要查找的元素
// // }
// // return false
// return el.name === '隔壁老王'
// })
// console.log(r);
// findIndex 查找对应成员所在索引
// 使用方法和 find 相同,返回结果为查找到的成员索引
// let r = students.findIndex((el, index, arr) => {
// return el.name === '隔壁老王'
// })
// console.log(r);
// some 方法测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回的是一个Boolean类型的值。
// 给出一个判断条件,some 函数将对每个成员都做出相同的判断
// 任意成员符合条件返回 true 时 some函数则返回true 否则为 false
// 请判断 arr2 中是否有成员存在于 arr1 中
let arr1 = ['x', 'y', 'z']
let arr2 = ['a', 'b', 'z']
// let r = arr2.some((el, index, arr) => {
// // 判断当前数组成员是否再 arr1 中存在
// if (arr1.includes(el)) {
// // 返回 true 代表 找到一个满足条件的数组成员
// return true
// }
// return false
// })
// console.log(r);
// sort 排序
// 若调用 sort 函数不传参数时,会使用浏览器默认的排序规则
let numList = [90, 12, 110, 9, 40, 214]
// let numList = [90, 12, 11, 92, 40, 21]
numList.sort()
console.log(numList);
// 自定义排序
// 排序规则:按年龄从小到大排序
// sort 参数是一个排序规则的函数
// el1 和 el2 代表的是排序时两两比较的两个数组成员
// 排序原理是,将每个成员都和剩下所有成员进行比较,每一对 el1 和 el2 决定他们的先后顺序
// 也叫“冒泡排序”
// sort 函数执行完后将返回新数组
students.sort((el1, el2) => {
if (el1.age > el2.age) {
// el1.age 若大于 el2.age 则 若从小到达排列 el1 应该放到 el2 的右侧
// 右侧联想 右箭头 >;即大于符号,则此处应该返回一个大于零的值
return 1
} else if (el1.age < el2.age) {
// el1.age 若小于 el2.age 则 若从小到大排列 el1 应该放到 el2 的左侧
// 左侧联想到 左箭头 < ;即小于符号,所以返回一个小于 0 的值
return -1
} else {
// 若 el1 和 el2 不需要交换顺序 则返回 0
return 0
}
})
console.log(students);
10.3 数组去重
let arr = [1, 5, 1, 2, 2, 4, 6, 5, 6, 4]
// 有以下两种方法供参考
// 1. 缓存重复数据
// 缓存
let temp = {}
// 结果数组
let result = []
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
// 判断 item 充当 key 是否存在于 temp 中
// if (temp[item]) {
if (!Reflect.has(temp, item)) {
// item 不存在于 temp 的 key 中
result.push(item)
// 添加缓存
temp[item] = true
}
}
console.log(result);
// 使用 filter 去重
temp = {}
result = arr.filter(el => {
// 若缓存中没有el
if (!temp[el]) {
// 加入缓存
temp[el] = true
return true
}
return false
})
console.log(result);
// 2. 使用 set 集
// 构造set对象,set对象会自动去重
let set = new Set(arr)
console.log(set);
// 将set转换为数组
result = Array.from(set)
console.log(result);
// 合成一句话
console.log(Array.from(new Set(arr)));
11. 字符串操作
let str = 'hello world !!!'
// 字符串可以被视为字符数组
// 查看字符串长度
console.log(str.length);
// 通过索引访问字符串中的字符
console.log(str[4]);
// charAt 函数可以获取指定索引处的字符
// 等价于 str[4]
console.log(str.charAt(4));
// split: 分割字符串
// 参数:用于分割字符串的字符
// 返回值:字符串数组
let r = str.split(' ')
console.log(r);
// split + join 替换字符
// 例如:替换字符 *|& 为 _
str = 'hello*|&world*|&!!!'
r = str.split('*|&')
r = r.join('_')
console.log(r);
// trim: 去掉字符串首尾空格
str = ' hello world !!! '
console.log(str);
r = str.trim()
console.log(r);
// substring: 截取子字符串
// 第一个参数:截取字符串的起始索引位置
// 第二个参数:截取字符串的结束索引位置
// 口诀:前截后不截
// 返回值:截取出来的子字符串
str = 'hello world !!!'
r = str.substring(4, 9)
console.log(r);
// 第二个参数可以省略,如果只写一个参数,substring将从该参数位置一直截取到字符串末尾
r = str.substring(6)
console.log(r)
// indexOf: 查询字符串中指定字符在字符串中的索引位置
// 参数:要查询的字符串
// 返回值:被查询字符串的索引
console.log(str.indexOf('o'));
// lastIndexOf
console.log(str.lastIndexOf('o'))
// 举例: 截取字符串从 o ~ r
console.log(str.substring(str.indexOf('o'), str.lastIndexOf('r') + 1));
// startsWith: 用于判断字符串是否以指定字符串开头
// 参数:指定开头的字符串
// 返回值:bool值,true代表是以指定字符串开头的,false代表不是
console.log(str.startsWith('hello'));
// endsWith: 用于判断字符串是否以指定字符串结尾
console.log(str.endsWith('!!!!'));
// toUpperCase toLowerCase 将字符串中的英文转成全为大写或小写
str = str.toUpperCase()
console.log(str);
str = str.toLowerCase()
console.log(str);
// 例如: 统计一个字符串中出现了多少个a字符,忽略大小写
str = 'alhdAkdjfalKHgladhfdjAhg'
str = str.toLowerCase()
let count = 0
for (let i = 0; i < str.length; i++) {
const char = str[i];
if (char === 'a') count++
}
console.log(count);
// 补充:
// 数字操作:
// toFixed 保留小数点后多少位的函数
// 参数:指定小数点后保留几位
// 返回值:是一个保留了指定小数点位数的字符串
let num = 3.1415926
r = num.toFixed(3)
console.log(r);
12. 闭包
闭包也叫函数闭包,通过函数产生一个封闭的内存空间,包裹一些需要被保存的数据、且函数需要返回一个持续引用的对象,这就叫闭包
const demo = (function A() {
let B = '张三'
return () => {
// 函数内引用变量B 则函数A是闭包的
console.log(B)
}
// 当返回一个值时 函数A将返回 A中没被使用的变量将被垃圾回收掉,则A不是闭包的
// return 'hello world'
// 当返回的是直接声明的对象或数组的时候,声明时将B作为对象属性的值或数组成员传入对象或数组
// 当A返回后,变量B则没有再被引用了,则被垃圾回收掉,A不是闭包的
// return { name: B }
})()
// 使用自调用函数,返回一个函数
const sayMessage = (() => {
// 声明一个用于记录调用次数的变量
// 闭包的函数空间内声明的变量,在内存中将持续存在
// 垃圾回收不会回收该变量
// 该变量在外部无法访问
let count = 0
// 在自调用函数中返回一个函数
// 返回的函数就是一个闭包后的函数
return (msg) => {
console.log(msg)
count++
console.log(`sayMessage 调用次数为 ${count}`);
}
})()
sayMessage('hello world')
const obj = (function () {
let name = '张三'
// 在函数空间内 可以返回任意的内容
return {
// 获取名称
getName() {
return name
},
// 改名
setName(newName) {
name = newName
}
}
})()
console.log(obj);
console.log(obj.getName());
obj.setName('隔壁老王')
console.log(obj.getName());
console.log(sum([1, 2, 3]));
console.log(avg([4, 5, 6]));
13. 函数
13.1bind call apply函数的应用
// 函数内可以使用 this 关键字,不同的地方 this 关键字的指代不一样
function fn(a, b) {
console.log(this);
return a + b
}
// fn()
// 函数内的 this 所指代的内容可以通过 bind call apply 来修改
// bind 绑定函数的 this 指代
// 语法 function.bind(thisArg, x, y, ...)
// thisArg: 要修改的this的指代
// x, y, ... : 函数的参数,选填
// 返回值: 一个被修改了this指代后的新函数
let fn2 = fn.bind('hello bind', 1, 2)
console.log(fn2(4, 5));
// call 调用函数并指定 this 指代
// 语法: function.call(thisArg, x, y, ...)
// thisArg: 要修改的this的指代
// x, y, ... : 函数的参数,选填
// 返回值: 函数本身的返回值
console.log(fn.call('hello call', 5, 6))
// apply 调用函数并指定 this 指代
// 语法: function.apply(thisArg, [x, y, ...])
// thisArg: 要修改的this的指代
// x, y, ... : 函数的参数,选填
// 返回值: 函数本身的返回值
console.log(fn.apply('hello apply', [8, 9]))
13.2节流和防抖
防抖: 函数将在一个固定时间后被调用,若计时未完成又执行该函数,则取消上次计时,重新开始计时,用于限制频繁的网络请求,例如:搜索功能,用户停止输入的一段时间后才会执行搜索任务。
// 步骤:
// 1. 取消计时
// 2. 重新计时
const fd = (() => {
// 计时器id
let timerId
return () => {
// 取消计时
clearTimeout(timerId)
// 重新计时
timerId = setTimeout(() => {
// 计时完成后要执行的代码 写在此处
console.log('hello world');
}, 3000)
}
})()
console.log(fd);
// 封装防抖函数
// 这样任何函数都能被添加防抖功能
function add(x, y) {
console.log('ok');
console.log(x, y);
return x + y
}
// 定义一个函数来封装防抖
// fn: 要添加防抖的函数
// delay: 防抖延迟多久
// that: 防抖函数内的 this 指代
function fdPlus(fn, delay, that) {
let timerId
return function () {
// 清除计时器
clearTimeout(timerId)
// 重新计时
timerId = setTimeout(() => {
// 调用参数fn函数
// 将当前函数的参数 arguments 作为 fn 的参数传入进去
fn.apply(that, arguments)
}, delay)
}
}
add = fdPlus(add, 3000, 'helloworld')
add(1, 2)
节流:固定时间内只能调用一次的函数,可以使用时间戳或计时器的方式实现。作用同样是限制用户频繁的网络请求,例如:发送验证码
// 计时器
// 1. 判断是否可以运行节流代码(cd好没有)
// 2. 执行节流的内容
// 3. 计时
function jlTimer() {
// 计时器id
let timerId
let cd = 3000
return () => {
// 若已经开始计时 则timerId 存在
if (timerId) return
// 执行被节流的函数内容
console.log('hello 节流');
// 开始计时
timerId = setTimeout(() => {
// 清空计时器id 允许再次调用
timerId = undefined
}, cd)
}
}
fn = jlTimer()
// 封装节流函数
function sub(x, y) {
console.log(this);
console.log('sub');
console.log(x, y);
return x - y
}
// 定义一个函数来封装节流
// fn: 要添加防抖的函数
// delay: 节流的cd
// that: 节流函数内的 this 指代
function jlPlus(fn, delay, that) {
// 计时器id
let timerId
return function () {
if (timerId) return
// 执行节流的代码
fn.apply(that, arguments)
// 计时
timerId = setTimeout(() => {
timerId = undefined
}, delay)
}
}
fn = jlPlus(sub, 5000, 'hello this')
14.正则表达式语法
let regex = /^\\dcom$/
// \ 斜杠:转义
console.log(/\\abc/.test('\\abc'));
// ^ :匹配字符串的开头
regex = /^abc/
console.log(regex.test('abcd')); // => true
console.log(regex.test('abcefg')); // => true
console.log(regex.test('aabcd')); // => false
// $ :匹配字符串的结尾
regex = /xyz$/
console.log(regex.test('123xyz')); // => true
console.log(regex.test('666abcxyz')); // => true
console.log(regex.test('666abcxyz.')); // => false
// ------------------- 匹配字符个数的符号
// 这些匹配字符个数的符号,代表的意思是:匹配前一个字符多少次
// * :匹配任意次
regex = /^o*k$/
console.log(regex.test('k')); // => true
console.log(regex.test('ok')); // => true
console.log(regex.test('ooook')); // => true
console.log(regex.test('ooookk')); // => false
// ? : 匹配0次或1次
regex = /^o?k$/
console.log(regex.test('k')); // => true
console.log(regex.test('ok')); // => true
console.log(regex.test('ooook')); // => false
// + : 匹配至少1次
regex = /^o+k$/
console.log(regex.test('k')); // => false
console.log(regex.test('ok')); // => true
console.log(regex.test('ooook')); // => true
// {n} : 匹配指定次数
regex = /^o{2}k$/
console.log(regex.test('k')); // => false
console.log(regex.test('ok')); // => false
console.log(regex.test('ook')); // => true
console.log(regex.test('oook')); // => false
// {n,} : 匹配至少n次
regex = /^o{2,}k$/
console.log(regex.test('k')); // => false
console.log(regex.test('ok')); // => false
console.log(regex.test('ook')); // => true
console.log(regex.test('oook')); // => true
// {n,m} : 匹配至少n次,至多m次
regex = /^o{1,3}k$/
console.log(regex.test('k')); // => false
console.log(regex.test('ok')); // => true
console.log(regex.test('ook')); // => true
console.log(regex.test('oook')); // => true
console.log(regex.test('ooook')); // => false
// ------------------- 匹配字符个数的符号 - end
// [xyz]: 匹配字符集合,匹配一个字符,该字符在方括号内
regex = /^[xyz]$/
console.log(regex.test('z')); // => true
console.log(regex.test('yy')); // => false
// x|y : 或
regex = /^(good|bad)$/
console.log(regex.test('good')); // => true
console.log(regex.test('bad')); // => true
console.log(regex.test('ok')); // => false
// [^xyz]: 匹配负值集合,匹配一个字符,该字符不在方括号内
regex = /^[^xyz]$/
console.log(regex.test('z')); // => false
console.log(regex.test('a')); // => true
console.log(regex.test('abc')); // => false
// [a-z] [0-9] : 取范围值,匹配一个字符,该字符在指定范围内
regex = /^[A-Z][0-9]$/
console.log(regex.test('E7')); // => true
console.log(regex.test('G8')); // => true
console.log(regex.test('c5')); // => false
// [^5-7]: 取范围负值,匹配一个字符,该字符不在指定范围内
regex = /^[^5-7]$/
console.log(regex.test('6')); // => false
console.log(regex.test('x')); // => true
console.log(regex.test('a')); // => true
// ------------------- 分组 (pattern)
// (pattern): 将pattern里面的所有字符当作一个字符处理
regex = /^abc(123)+xyz$/
console.log(regex.test('abc123xyz')); // => true
console.log(regex.test('abc123123xyz')); // => true
console.log(regex.test('abc112233xyz')); // => false
// 站在字符串的角度看,圆括号不仅有分组的作用,同时,它将被取值
// regex = /abc(123)+xyz/
regex = /abc123xyz/
let r = '000123abc123xyz444555'.match(regex)
console.log(r);
// (?:pattern): 匹配分组内容,但不获取圆括号中的值
regex = /abc(?:123)+xyz/
r = '000123abc123123xyz444555'.match(regex)
console.log(r);
// assert 断言 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions/Assertions
// 什么是断言? 断言就是一段下笃定的言论,例如: 今天一定会下雨
// 先行断言
// 语法: x(?=y)
// x 跟随 y 时 匹配 x
// /小明(?=小红)/ 该正则理解为: 小明后面一定跟随了一个小红
console.log('小明小红'.match(/小明(?=小红)/));
// 先行否定断言
// 语法: x(?!y)
// x 后没有 y 跟随时 匹配 x
// /小明(?!小红)/ 小明后面一定没有跟随小红
console.log('小明小芳'.match(/小明(?!小红)/));
// 后行断言
// 语法: (?<=y)x
// x 的前有 y 则 匹配 x
// /(?<=小红)小明/ 小明的前面一定有小红
console.log('小红小明'.match(/(?<=小红)小明/));
// 后行否定断言
// 语法: (?<!y)x
// x 前没有 y 则 匹配 x
// /(?<!小红)小明/ 小明的前面一定没有小红
console.log('小芳小明'.match(/(?<!小红)小明/));
15.this关键字
this 关键字代表什么?
根据不同的场景,this关键字代表东西不同,有以下几种情况
<script>
// 参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this
// this 关键字代表什么?
// 根据不同的场景,this关键字代表东西不同,有以下几种情况
// js 执行上下文中的 this
console.log(this); // => window
console.log(this === window);
// 函数内的this
function fn() {
// 非严格模式下 函数内 this => window
console.log(this);
}
fn()
// json对象方法的this
let obj = {
name: '张三',
fn() {
console.log(this); // => 对象自己
}
}
obj.fn()
// 实例对象方法的this
class A {
name
constructor(name) {
this.name = name
}
fn() {
// 类方法中的 this => 调用该方法的实例对象
console.log(this);
}
}
let a = new A('a')
let b = new A('b')
a.fn()
b.fn()
// 对象方法中的this始终是自己
// 声明变量存储 A 类的 fn 方法,再次调用并观察 this
// 给变量赋值对象方法,默认情况下 this 为 undefined
const fnVar = a.fn
fnVar() // => undefined
// 给变量赋值函数值,默认情况下 this 为 window
const fn2 = function () {
console.log(this);
}
fn2()
// 有些情况下,回调函数中的this可能被其他函数赋值,所以this不是undefined
// 此处 事件回调函数被 addEventListener 赋值了其中的 this
// 所以这里的this指的是绑定事件的那个dom对象
document.querySelector('button').addEventListener('click', function () {
console.log(this);
})
// 自定义函数也可以修改回调函数内的this指代
function custom(callback) {
// call apply bind
callback.call('hello world')
}
custom(function () { console.log(this); })
// 总结:
// 1. js 执行上下文 => window
// 2. 函数内 this => window (函数=>window)
// 3. 方法内的this指向调用方法的对象 (方法=>实例)
// 4. this 可能被其他函数赋值 例如 addEventListener 这种时候 this 既不是 window 也不是 undefined,它取决于函数 addEventListener 自己
</script>