# DOM 树
- 将 HTML 文档以树形结构直观的表现出来,我们称之为文档树或 DOM 树
- 描述网页内容的名词
- 作用:文档树直观的体现了标签与标签之间的关系
DOM 对象:游览器根据 html 标签生成的 JS 对象
- 所有的标签属性都可以在这个对象上面找到
- 修改这个对象的属性会自动映射到标签身上
DOM 的核心思想 - 把网页内容当作对象来处理
document 对象 - 是 DOM 里提供的一个对象
- 所以它提供的属性和方法都是用来访问和操作网页内容的
- 网页的所有内容都在 document 里面
# 根据 CSS 选择器来获取 DOM 元素
- 选择匹配的第一个元素
语法:
document.querySelector('css选择器'); |
参数:
包含一个或多个有效的 CSS 选择器字符串
返回值:
CSS 选择器匹配的第一个元素,一个 HTMLElement 对象
如果没有匹配到,则返回 null
2. 选择匹配多个元素
语法:
document.querySelectorAll('css选择器'); |
参数:
包含一个或多个有效的 CSS 选择器字符串
返回值:
CSS 选择器匹配的 NodeList 对象集合
例如
document.querySelectorAll(`ul li`); |
得到的是一个伪数组
- 有长度有索引号的数组
- 但是没有 pop () push () 等数组方法
循环遍历
# 操作元素内容
- 元素
innerText属性
- 将文本内容添加 / 更新到任意标签位置
- 显示纯文本,不解析标签
- 元素
innerHTML属性
- 将文本内容添加 / 更新到任意标签位置
- 会解析标签,多标签建议使用模板字符
# 操作元素常用属性
可以通过 JS 设置 / 修改标签元素属性,比如通过 src 更换图片
最常见的属性比如:href、title、src 等
语法:对象.属性 = 值
# 操作元素样式属性
可以通过 JS 设置 / 修改标签元素的样式属性
- 通过 style 属性操作 CSS
语法:对象.style.属性 = 值
注意:- 修改样式通过 style 属性引出
- 如果属性有 - 连接符,需要转换小驼峰命名法
- 赋值的时候,需要的时候不要忘记 CSS 单位。
注意:不要忘记单位
缺点:
在修改样式比较少的情况下有优势
生成的是行内样式表,权重比较高
- 操作类名 (className) 操作 CSS
- 如果修改的样式比较多,直接通过 style 属性修改比较繁琐,我们可以通过借助于 css 类名的形式
- 语法
元素.calssName = '类名' - 注意:
class 是关键字,所以用 className 代替
className 是使用新值换旧值,如果需要添加一个类需要保留之前的类名。
缺点:多个类名操作麻烦
- 通过 classList 操作控制 CSS
- 为了解决 className 容易覆盖以前的类名,我们可以通过 classList 方式追加和删除类名
- 语法:
// 追加一个类 | |
元素.classList.add('类名') | |
// 删除一个类 | |
元素.classList.remove('类名') | |
// 切换一个类,有还是没有,有就删掉,没有就加上 | |
元素.classList.toggle('类名') |
# 🌳 Web API - DOM 核心指南
# ▎DOM 树体系
<pre class="mermaid">graph TD
Document --> HTML
HTML --> Head
HTML --> Body
Head --> Title
Body --> Div[div.container]
Div --> Header[h1]
Div --> Content[p#main]</pre>
核心概念:
DOM对象:浏览器将 HTML 标签转换的 JavaScript 对象document:全局入口对象,包含整个页面内容- 双向绑定:DOM 属性变更自动映射到页面元素
# 🔍 元素选择器
# 选择器方法对比
| 方法 | 返回值 | 特点 | 性能排序 |
|---|---|---|---|
querySelector() | HTMLElement/null | 首个匹配元素 | 3 |
querySelectorAll() | NodeList (静态) | 需转换为数组操作 | 4 |
getElementById() | HTMLElement/null | 唯一元素快速定位 | 1 |
getElementsByClassName | HTMLCollection | 动态集合实时更新 | 2 |
// 选择器最佳实践 | |
const nav = document.getElementById('nav'); // 最快选择方式 | |
const buttons = Array.from(document.querySelectorAll('.btn')); // 转换为真数组 |
# ✏️ 内容操作
# 文本 vs HTML
// XSS 安全文本注入 | |
div.innerText = '<script>alert(1)</script>'; // 显示为纯文本 | |
// 富文本内容操作 | |
article.innerHTML = ` | |
<h2>最新动态</h2> | |
<p>${new Date().toLocaleString()}</p> | |
`; |
| 属性 | 解析标签 | 防 XSS | 适用场景 |
|---|---|---|---|
innerText | ❌ | ✅ | 用户输入展示 |
innerHTML | ✅ | ❌ | 动态模板渲染 |
# 🎨 样式操作
# 样式控制方式
| 方式 | 示例 | 权重 | 适用场景 |
|---|---|---|---|
| style 属性 | el.style.padding = '10px' | 1000 | 少量行内样式 |
| className | el.className = 'active' | 类权重 | 整体样式替换 |
| classList API | el.classList.toggle() | 类权重 | 交互式样式控制 |
# classList 完整方法
// 类名操作 | |
element.classList.add('fade-in'); // ✅ 添加 | |
element.classList.remove('hidden'); // ✅ 移除 | |
element.classList.toggle('active'); // ✅ 切换 | |
element.classList.contains('error'); // ✅ 检测 | |
element.classList.replace('old', 'new'); // ✅ 替换 |
# ⚙️ 属性操作
# 属性操作方法
// 通用属性操作 | |
img.getAttribute('data-src'); // 获取 | |
img.setAttribute('alt', 'logo'); // 设置 | |
img.removeAttribute('title'); // 删除 | |
// 特殊属性处理 | |
input.checked = true; // ✅ 布尔属性 | |
div.dataset.userId = 123; // ✅ 自定义数据属性 |
# ⚠️ 关键注意事项
# 性能优化
// 错误示例:布局抖动 | |
const elements = document.querySelectorAll('.item'); | |
elements.forEach(el => { | |
const width = el.offsetWidth; // 触发重排 | |
el.style.width = width * 2 + 'px'; | |
}); | |
// 正确做法:批量读取→批量写入 | |
const widths = []; | |
elements.forEach(el => widths.push(el.offsetWidth)); | |
elements.forEach((el, i) => el.style.width = widths[i] * 2 + 'px'); |
# NodeList 处理
// 转换为真数组的三种方式 | |
const nodelist = document.querySelectorAll('.item'); | |
const arr1 = Array.from(nodelist); // ES6 推荐 | |
const arr2 = [...nodelist]; // 扩展运算符 | |
const arr3 = [].slice.call(nodelist); // 传统方式 |
# 💡 最佳实践
- 选择器优先级:ID > Class > Tag > 属性选择器
- 样式操作:优先使用 classList,避免频繁操作行内样式
- 属性访问:标准属性直接访问,自定义属性使用 dataset
- 内容安全:用户输入内容务必使用 innerText
- 动画优化:使用 requestAnimationFrame 替代定时器
// 动画优化示例 | |
function animate(element) { | |
let start = Date.now(); | |
function step() { | |
const progress = Date.now() - start; | |
element.style.left = Math.min(progress / 10, 500) + 'px'; | |
if (progress < 5000) { | |
requestAnimationFrame(step); | |
} | |
} | |
requestAnimationFrame(step); | |
} |
完整 DOM 规范参考:MDN Web API
# 操作表单元素 属性
- 表单很多情况,也需要修改属性,比如点击眼睛,可以看到密码,本质是把表单类型转换为文本框
- 正常的有属性有取值,跟其他的标签属性没有任何区别
- 获取:
DOM对象.属性名 - 设置:
DOM对象.属性名 = 新值
- 获取:
表单.value = '用户名'; | |
表单.type = 'text'; |
表单中添加就有效果、移除就没有效果,一律使用布尔值表示,如果为 true 代表添加了该属性,如果是 false 代表移除了该属性
比如: disabled、checked、selected
# 自定义属性
标准属性:标签天生自带的属性 比如 class id title 等,可以直接使用点语法操作比如: disabled checked selected
自定义属性:
- 在 html5 中推出了专门的
data-自定义属性 - 在标签上一律以
data-开头 - 在 DOM 对象上一律以
dataset对象方式获取
const box = document.querySelector('.box'); | |
console.log(box.dataset.id); |
# 定时器 - 间歇函数
定时器函数可以开启和关闭定时器
- 开启定时器
setInterval(函数, 间隔时间)
每隔一段时间调用这个函数
间隔时间单位是毫秒
fucntion repeat() { | |
console.log('重复执行'); | |
} | |
setInterval(repeat, 1000); |
注意:函数名字不需要加括号、定时器返回的是一个 id 数字
- 关闭定时器
let timer = setInterval(function() { | |
console.log('hi'); | |
}, 1000); | |
clearInterval(timer); |
注意:函数名字不需要加括号、定时器返回的是一个 id 数字
# 事件监听
- 什么是事件?
事件是在编程时系统内发生的动作或者发生的事情
比如用户在网页上单击一个按钮 - 什么是事件监听?
就是让程序检测是否由事件产生,一旦有事件触发,就立即调用一个函数做出相应
语法: DOM对象.addEventListener(事件类型, 函数)
事件监听三要素:
- 事件源:哪个 dom 元素被事件触发了,获取 dom 元素
- 事件类型:用什么方法触发,单机
click,鼠标经过mouseover - 事件调用的函数:要做什么事
事件监听版本
DOM L0
事件源.on事件 = function(){}DOM L2
事件源.addEventListener(事件,事件处理函数)区别
on 方式会被覆盖,addEventListener方式可以绑定多次,拥有事件更多特性
# 事件类型
鼠标事件:
click:鼠标点击mouseover:鼠标经过mouseout:鼠标移出
焦点事件:
focus:获得焦点blur:失去焦点
键盘事件:
keydown:按下键盘keyup:抬起键盘
文本事件:
input:文本输入
# 获取事件对象
事件对象是什么
- 也是对象,这个对象里有事件触发时的相关信息
- 例如:鼠标点击事件中,事件对象就存放了鼠标点在哪个位置等信息
使用场景 - 判断用户按下的哪个键
- 判断鼠标点击哪个元素,从而做相应的操作。
语法:如何获取
- 在事件绑定的回调函数的第一个参数就是事件对象
- 一般命名为
event,ev,e
元素.addEventListener('click', function(e) { | |
}); |
部分常用属性
type:获取当前的事件类型clientX/clientY:获取光标相对于浏览器可见窗口左上角的位置offsetX/offsetY:获取光标相对于当前 DOM 元素的位置key:用户按下的键盘键的值
# String.prototype.trim()
String 的 trim() 方法会从字符串的两端移除空白字符,并返回一个新的字符串,不会修改原始字符串
要返回仅从一端修剪空白字符的新字符串,使用 trimStart() 或 trimEnd()
const greeting = " Hello world! "; | |
console.log(greeting); | |
// Expected output: " Hello world! "; | |
console.log(greeting.trim()); | |
// Expected output: "Hello world!"; |
# 环境对象
环境对象:指的是函数内部特殊的变量 this,它代表着当前函数运行时所处的环境
作用:函数内部使用 this,可以获取到函数运行时所处的环境
- 函数的调用方式不同,this 指向的对象也不同
- [谁调用就指向谁],这是粗略化规则
- 严格模式下,this 指向 undefined
- 直接调用函数,this 指向 window
# 回调函数
如果 函数A 作为 函数B 时,我们称 A 为回调函数
简单理解:当一个函数当作参数来传递给另外一个函数的时候,这个函数就是 回调函数
常见的使用场景
function fn() { | |
console.log('回调函数'); | |
} | |
//fn 传递给 setInterval,fn 是回调函数 | |
setInterval(fn, 1000); |
# 事件流
# 事件流和两个阶段
事件流指的是事件完整执行的过程中的流动路径
说明:假设页面中有个 div ,当触发事件时,会经历两个阶段,分别是捕获阶段、冒泡阶段
简单来说:捕获阶段是 从子到父 冒泡阶段是 从父到子
# 事件捕获
- 事件捕获概念:从 DOM 的根元素开始执行对应的事件 (从外到里)
- 代码:
DOM.addEventListener('事件类型', 事件处理函数,是否使用捕获机制) |
- 说明:第三个参数传入 true 代表捕获阶段触发
# 事件冒泡
当一个元素的事件被触发时,同样的事件会在该元素的所有祖先元素中依次被触发
- 简单理解:当一个元素被触发事件后,会依次向上调用所有父级元素的
同名事件 - 事件冒泡是默认存在的
# 阻止冒泡
- 问题:因为默认就有冒泡模式的存在,所以容易导致事件影响到父级元素
- 需求:若想把事件限制在当前元素内,就需要组织事件冒泡
- 前提:阻止事件冒泡需要拿到事件对象
- 语法:
事件对象e.stopPropagation(); |
- 注意:此方法可以阻断事件流动传播,不光在冒泡阶段有效,捕获阶段也有效。
我们某些情况下需要阻止默认行为的发生,比如阻止链接的跳转,表单域跳转
- 语法:
e.preventDefault()
# 解绑事件
addEventListener 方式,必须使用:removeEventListener (事件类型, 事件处理函数,[获取捕获或者冒泡阶段])
例如:
function fun() { | |
alert('hi'); | |
} | |
btn.addEventListener('click', fun); | |
btn.removeEventListener('click', fun); |
注意:匿名函数无法被解绑
# 两种注册事件的区别
- 传统 on 注册
- 同一个对象,后面注册的会覆盖前面的
- 直接使用 null 覆盖就可以实现事件的解绑
- 都是冒泡阶段执行的
- 事件监听注册
- 语法:
DOM.addEventListener('事件类型', 事件处理函数, 是否捕获) - 后面注册的事件不会覆盖前面的,而是会绑定到一起
- 可以通过第三个参数来确定是在冒泡或者捕获阶段执行
- 必须使用
removeEventListener来解绑 - 匿名函数无法被解绑
- 语法:
# 事件委托
事件委托时利用事件流的特征解决一些开发需求的技巧
- 优点:减少注册次数,可以提高程序性能
- 原理:事件委托其实是利用事件冒泡的特点
- 给
父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件
实例代码:
- 给
// 这里通过使用 ul 的父元素 ul 注册事件,当触发 li 的时候,会冒泡到 ul 身上,从而触发 ul 的事件 | |
const ul = document.querySelector('.ul'); | |
ul.addEventListener('click', function(e) { | |
if(e.target.tagName === 'LI') { | |
e.target.style.color = 'red'; | |
} | |
// console.log(e.target.tagName); | |
}); |
# 页面加载事件
- 加载外部资源(如图片、外联 CSS 和 javascript 等)加载完毕时触发的事件
- 为什么要学?
- 有些时候需要等页面资源全部处理完了做一些事情
- 事件名:
load - 监听页面所有资源加载完毕
- 给 window 添加 load 事件
window.addEventListener('load', function() { | |
// 操作 | |
}); |
注意:不光可以监听整个页面资源加载完毕,也可以针对某个资源绑定 load 事件
- 当初始的 HTML 文档被完全加载和解析完成之后,
DOMContentLoaded事件被触发,而无需等待样式表、图像等完全加载 - 事件名:
DOMContentLoaded - 监听页面 DOM 加载完毕
- 给
document添加DOMContentLoaded事件
- 给
document.addEventListener('DOMContentLoaded', function() { | |
// 执行的操作 | |
}) |
# 页面滚动事件
- 滚动条在滚动的时候持续触发的事件
- 事件名:
scroll - 监听页面滚动事件
window.addEventListener('scroll', function() { | |
// 执行的操作 | |
}); |
scrollLeft和scrollTop- 获取被卷去的大小
- 获取元素内容往左、往上出去看不到的距离
- 这两个值是可读写的
# 获取位置
开发中,我们需要检测页面滚动的距离
window.addEventListener('scroll', function() { | |
const n = document.documentElement.scrollTop; | |
console.log(n); | |
}) |
注意: document.documentElement HTML 文档返回对象为 HTML 元素
# 滚动到指定坐标
scrollTo方法可把内容滚动到指定的坐标- 语法:
元素.scrollTo(x坐标, y坐标)
# 页面尺寸事件
- 会在窗口尺寸改变的时候触发事件:
resize
window.addEventListener('resize', function() {}) |
# 获取元素宽高
获取宽高
- 获取元素的可见部分宽高(不包含边框、margin、滚动条等)
clientWidth和clientHeight
获取宽高
- 获取元素的自身宽高、包含元素自身设置的宽高、padding、border
offsetWidth和offsetHeight- 获取出来的是数值
- 注意:获取的是可视宽高,如果盒子是隐藏的,获取的结果是 0
获取位置:
- 获取元素距离自己定位父级元素的左、上距离
offsetLeft和offsetTop,都是只读属性
获取位置:
element.getBoundingClientRect()- 方法返回元素的大小及其相对于视口的位置
# 总结
| 属性 | 作用 | 说明 |
|---|---|---|
scrollLeft 和 scrollTop | 被卷去的头部和左侧 | 配合页面滚动来用,可读写 |
clientWidth 和 clientHeight | 获取元素宽度和高度 | 不包含 border, margin,滚动条。用于 js 获取元素大小,只读属性 |
offsetWidth 和 offsetHeight | 获取元素宽度和高度 | 包含 border、padding,滚动条等,只读 |
offsetLeft 和 offsetTop | 获取元素距离自己定位父级元素的左、上距离 | 获取元素位置的时候使用,只读属性 |
# 日期对象
- 创建一个时间对象并获取时间
const date = new Date() - 获取指定时间
const date = new Date('2008-8-8')
# 日期对象方法
| 方法 | 作用 | 说明 |
|---|---|---|
getFullYear() | 获得年份 | 获取四位年份 |
getMonth() | 获得月份 | 取值为 0 ~ 11 |
getDate() | 获取月中的每一天 | 不同月份取值也不相同 |
getDay() | 获取星期 | 取值为 0 ~ 6(星期日为 0) |
getHours() | 获取小时 | 取值为 0 ~ 23 |
getMinutes() | 获取分钟 | 取值为 0 ~ 59 |
getSeconds() | 获取秒 | 取值为 0 ~ 59 |
const date = new Date(); | |
date.toLocaleString(); // 2020/8/8 16:08:09 | |
date.toLocaleDateString(); // 2020/8/8 | |
date.toLocaleTimeString(); // 16:08:09 |
# 时间戳
- 时间戳:1970 年 1 月 1 日 0 时 0 分 0 秒到当前时间的毫秒数
- 算法:
- 将来时间 - 现在时间戳 = 剩余时间
- 剩余时间毫秒数转换为剩余时间的年月日就是倒计时时间
# 三种获取时间戳的方法:
- 使用
getTime()方法
const date = new Date(); | |
console.log(date.getTime()); |
- 简写
+new Date()
无需实例化
console.log(+new Date()); |
- 使用
Date.now()
无需实例化
但是只能得到当前的时间戳,而前面两种可以返回指定时间的时间戳
console.log(Date.now()); |
# 节点
# 查找节点
父节点查找:
parentNode属性- 返回最近一级的父节点 找不到返回为 null
子元素.parentNode
子节点查找:
childNodes:获取所有子节点、包括文本节点(空格、换行)、注释节点children属性:仅仅获得所有元素节点,返回的还是一个伪数组父元素.children
兄弟关系查找:
- 下一个兄弟:
nextElementSibling属性 - 上一个兄弟:
previousElementSibling属性
- 下一个兄弟:
# 增加节点
- 创建节点
- 即创造出一个新的网页元素,再添加到网页内,一般先创建节点,然后插入节点
- 创建元素节点方法:
document.createElement('标签名'); |
- 追加节点
- 想要再界面看到,还得插入到某个父元素中
- 插入到父元素的最后一个子元素:
// 插入到这个父元素的最后 | |
父元素.appendChild(子元素); |
- 插入到父元素中子元素的前面:
// 插入到某个子元素的前面 | |
父元素.insertBefore(新元素, 哪个元素的前面); |
# 增加节点
# 克隆节点
元素.cloneNode(布尔值)cloneNode 会克隆出一个跟原来标签一样的元素,括号内传入布尔值
- 若为
true,则克隆出来的节点会保留标签内的内容 - 若为
false,则克隆出来的节点不会保留标签内的内容 - 默认为
false
# 删除节点
- 若一个节点在页面中已不需要时,可以删除它
- 在
JS原生DOM操作中,要删除元素必须通过父元素删除 - 语法:
父元素.removeChild(子元素) - 注:
- 如果不存在父子关系则删除不成功
- 删除节点和隐藏节点 (display:none) 是有区别的:隐藏节点还是存在的,但是删除节点则不存在了
# M 端事件
移动端也有自己独特的地方。比如触屏事件 touch ,Android 和 iOS 都有
touch对象代表一个触摸点。触摸点可能是一根手指,也可能是一根触摸笔。触屏事件可相应用户手指对屏幕或者触控板操作- 常见的触屏事件如下:
| 触屏 touch 事件 | 说明 |
|---|---|
touchstart | 手指触摸到一个 DOM 元素时触发 |
touchmove | 手指在一个 DOM 元素上滑动时触发 |
touchend | 手指从一个 DOM 元素上移开时触发 |
# BOM
- BOM 是浏览器对象模型
- window 对象是一个全局对象,也可以说是 JavaScript 的顶级对象
- 像
document、alert()、console.log()这些都是 window 的属性,基本 BOM 的属性和方法都是 window 的 - 所有通过
var定义在全局作用域中的变量、函数都会变成 window 对象的方法和属性 - window 对象下的属性和方法在调用的时候可以省略 window
# 定时器 - 延时 hansh
- 语法
setTimeout(回调函数, 延时时间) setTimeout仅仅只执行一次,所以可以理解为就是把一段代码延迟执行- 清除延时函数:
let timer = setTimeout(回调函数, 等待毫秒数); | |
clearTimeout(timer); |
# JS 执行机制
<pre class="mermaid">graph TD
A [执行栈] --> B (同步任务)
A --> C {异步任务}
C -->| 宏任务 | D [定时器 / I/O]
C -->| 微任务 | E [Promise.then]
subgraph 任务队列
D --> F[宏任务队列]
E --> G[微任务队列]
end
F --> H{事件循环}
G --> H
H -->|优先| G
H -->|次之| F
G --> I[执行微任务]
F --> J[执行宏任务]
I --> K[页面渲染]
J --> H
style A fill:#f9f,stroke:#333
style D fill:#f96,stroke:#333
style E fill:#6f9,stroke:#333</pre>
console.log('1'); | |
setTimeout(() => { | |
console.log('2'); | |
}, 1000); | |
console.log('3'); | |
// 1 3 2 | |
console.log('1'); | |
setTimeout(() => { | |
console.log('2'); | |
}, 0); | |
console.log('3'); | |
// 1 3 2 |
JS 语言的一大特点就是 单线程 ,也就是说,同一时间只能做一件事
这是因为 javascript 是为处理页面中用户的交互,以及操作 DOM 而诞生的。比如我们对某个 DOM 元素进行添加和删除操作,不能同时进行。因该先进行添加,之后再删除
单线程就意味着,所有任务都需要排队,前一个任务结束,才会执行后一个任务。这样会导致:如果 JS 执行的事件过长,导致页面渲染不连贯,加载阻塞
为了解决这个问题,利用多核 CPU 的计算能力,HTML5 提出了 Web Worker 标准,允许 JS 脚本创建多个线程,于是,JS 中出现了同步和异步
同步:
前一个任务结束再执行后一个任务,程序的顺序和执行的顺序是一致的,同步的。异步:
在执行一个任务时,不会等待这个任务执行完毕,而是继续往下执行,当这个任务执行完毕后,再执行后续的任务。异步的。同步任务:
同步任务都在主线程上执行,形成一个执行栈。异步任务:
JS 的异步是通过回调函数实现的。
一般而言,异步任务有以下三种类型:
- 普通事件,如比如
click、mouseover、mouseout等 - 资源加载,如
load、error、abort等 - 定时器,如
setTimeout、setInterval等
异步任务相关添加到任务队列中 (任务队列也称消息队列)
# 执行顺序
- 先执行
执行栈中的同步任务 - 异步任务放入任务队列中
- 一旦执行栈中的所有同步任务执行完毕,系统就会被按次序读取任务队列中的
异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行。
由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为 事件循环(event loop)
# location 对象
location 的数据类型是对象,它拆分并保存了 URL 地址的各个组成部分
# location.href
href 属性获取完整的 URL 地址,对其赋值时用于地址的跳转
location.href = 'http://www.baidu.com'; |
# search
search 属性获取地址中携带的参数,符号 ? 后面的部分
console.log(location.search); | |
// 比如 http://www.baidu.com?name=zhangsan&age=18 | |
// 输出:?name=zhangsan&age=18 |
# hash
hash 属性获取地址中的哈希值,符号 # 后面的部分
console.log(location.hash); | |
// 比如 http://www.baidu.com#name=zhangsan&age=18 | |
// 输出:#name=zhangsan&age=18 |
# reload
reload 方法用来刷新当前页面,传入参数 true 时表示强制刷新
语法: location.reload(true)
强制刷新和普通刷新的区别:强制刷新会重新加载页面,而普通刷新只会重新加载页面的资源,不会重新加载页面的脚本。
# navigator 对象
navigator的数据类型是对象,该对象记录了浏览器自身的相关信息- 常用的属性和方法:通过
userAgent检测浏览器版本和平台
// 检测 userAgent(浏览器信息) | |
!(function () { | |
const userAgent = navigator.userAgent; | |
// 验证是否为 Android 或 iPhone | |
const android = userAgent.match(/(Android);?[\s\/]+(.+)?/); | |
const iphone = userAgent.match(/(iPhone|iPod);?[\s\/]+(.+)?/); | |
// 如果是 Android 或 iPhone,则跳转至移动站点 | |
if (android || iphone) { | |
location.href = 'http://m.itcast.cn'; | |
} | |
})(); |
# history 对象
history的数据类型是对象,主要管理历史记录,该对象与浏览器地址栏的操作相对应,如前进、后退、历史记录等- 常用属性和方法:
| history 对象方法 | 作用 |
|---|---|
| back() | 后退功能 |
| forward() | 前进功能 |
| go (参数) | 前进后退功能 参数如果是 1 前进一个页面 如果是 - 1 后退一个页面 |
# 本地存储
- 数据存储在用户浏览器中
- 设置、读取方便、刷新不丢失
- 容量较大,
sessionStorage和localStorage约为 5M 左右
# localStorage
作用:可以将数据永久存储在本地 (用户的电脑), 除非手动删除,否则关闭页面也会存在
特性:可以多窗口共享、以键值对的形式存储使用
存储数据: localStorage.setItem('key', 'value')
读取数据: localStorage.getItem('key')
删除数据: localStorage.removeItem('key')
# sessionStorage
特性:生命周期为关闭浏览器窗口、在同一个窗口下数据可以共享、以键值对的形式存储使用、用法和 localStorage 一样
# 存储复杂数据类型
本地只能存储字符串,无法存储复杂数据类型
解决:需要将复杂数据类型转换乘 JSON 字符串,再存储到本地
语法:
JSON.stringify(复杂数据类型)解决:把取出来的字符串转换为对象
语法:
JSON.parse(JSON字符串)
# 数组 map 方法
语法:
const arr = ['red', 'blue', 'green']; | |
const newArr = arr.map((ele, index) => { | |
// 这里 ele 是元素,index 是索引 | |
return ele; | |
}); |
注意:map 有返回值
# 数组 join 方法
作用: join() 方法用于把数组中的所有元素转换为一个字符串
语法:
console.log(newArry.join('-')); | |
// 输出:red-blue-green | |
console.log(newArry.join('')); | |
// 输出:redbluegreen | |
console.log(newArry.join()); | |
// 输出:red,blue,green |
# 正则表达式
正则表达式是匹配字符串的模式
# 基本使用
定义规则: const reg = /表达式/
其中 // 时正则表达式的字面量
正则表达式也是对象
# test 方法
作用:用来匹配字符串,返回布尔值
语法: reg.test(字符串)
// 正则表达式的基本使用 | |
const str = 'web前端开发' | |
// 1. 定义规则 | |
const reg = /web/ | |
// 2. 使用正则 test () | |
console.log(reg.test(str)) //true 如果符合规则匹配上则返回 true | |
console.log(reg.test('java开发')) //false 如果不符合规则匹配上则返回 false |
# exec 方法
作用:用来匹配字符串,返回数组
语法: reg.exec(字符串)
//exec () 方法 | |
const str = 'web前端开发' | |
const reg = /web/ | |
console.log(reg.exec(str)) | |
// 输出:["web", index: 0, input: "web 前端开发", groups: undefined] | |
console.log(reg.exec('java开发')) | |
// 输出:null |
# 元字符
- 是一些具有特殊含义的字符,可以极大提高了灵活性和强大的匹配功能。
- 比如,规定用户只能输入英文 26 个英文字符,元字符写法:
/[a-z]
# 边界符
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符
| 边界符 | 作用 |
|---|---|
| ^ | 表示匹配字符串的开头 |
| $ | 表示匹配字符串的结尾 |
// 元字符之边界符 | |
// 1. 匹配开头的位置 ^ | |
const reg = /^web/ | |
console.log(reg.test('web前端')) // true | |
console.log(reg.test('前端web')) // false | |
console.log(reg.test('前端web学习')) // false | |
console.log(reg.test('we')) // false | |
// 2. 匹配结束的位置 $ | |
const reg1 = /web$/ | |
console.log(reg1.test('web前端')) // false | |
console.log(reg1.test('前端web')) // true | |
console.log(reg1.test('前端web学习')) // false | |
console.log(reg1.test('we')) // false | |
// 3. 精确匹配 ^ $ | |
const reg2 = /^web$/ | |
console.log(reg2.test('web前端')) // false | |
console.log(reg2.test('前端web')) // false | |
console.log(reg2.test('前端web学习')) // false | |
console.log(reg2.test('we')) // false | |
console.log(reg2.test('web')) // true | |
console.log(reg2.test('webweb')) // flase |
# 量词
| 量词 | 说明 |
|---|---|
| * | 重复零次或更多次 |
| + | 重复一次或更多次 |
| ? | 重复零次或一次 |
| 重复 n 次 | |
| 重复 n 次或更多次 | |
| 重复 n 到 m 次 |
注意:逗号左右两侧不要有空格
// 元字符之量词 | |
// 1. * 重复次数 >= 0 次 | |
const reg1 = /^w*$/ | |
console.log(reg1.test('')) // true | |
console.log(reg1.test('w')) // true | |
console.log(reg1.test('ww')) // true | |
console.log('-----------------------') | |
// 2. + 重复次数 >= 1 次 | |
const reg2 = /^w+$/ | |
console.log(reg2.test('')) // false | |
console.log(reg2.test('w')) // true | |
console.log(reg2.test('ww')) // true | |
console.log('-----------------------') | |
// 3. ? 重复次数 0 || 1 | |
const reg3 = /^w?$/ | |
console.log(reg3.test('')) // true | |
console.log(reg3.test('w')) // true | |
console.log(reg3.test('ww')) // false | |
console.log('-----------------------') | |
// 4. {n} 重复 n 次 | |
const reg4 = /^w{3}$/ | |
console.log(reg4.test('')) // false | |
console.log(reg4.test('w')) // flase | |
console.log(reg4.test('ww')) // false | |
console.log(reg4.test('www')) // true | |
console.log(reg4.test('wwww')) // false | |
console.log('-----------------------') | |
// 5. {n,} 重复次数 >= n | |
const reg5 = /^w{2,}$/ | |
console.log(reg5.test('')) // false | |
console.log(reg5.test('w')) // false | |
console.log(reg5.test('ww')) // true | |
console.log(reg5.test('www')) // true | |
console.log('-----------------------') | |
// 6. {n,m} n =< 重复次数 <= m | |
const reg6 = /^w{2,4}$/ | |
console.log(reg6.test('w')) // false | |
console.log(reg6.test('ww')) // true | |
console.log(reg6.test('www')) // true | |
console.log(reg6.test('wwww')) // true | |
console.log(reg6.test('wwwww')) // false | |
// 7. 注意事项: 逗号两侧千万不要加空格否则会匹配失败 |
# 范围
| 范围 | 说明 |
|---|---|
| [abc] | 匹配 a 或者 b 或者 c |
| [^abc] | 匹配除了 a 或者 b 或者 c 之外的字符 |
| [a-z] | 匹配 a 到 z 之间的字符 |
# 字符类
| 字符类 | 说明 |
|---|---|
| \d | 匹配数字 |
| \D | 匹配非数字 |
| \w | 匹配字母数字下划线 |
| \W | 匹配非字母数字下划线 |
| \s | 匹配空格(包括换行符、制表符、空格符等) |
| \S | 匹配非空格(包括换行符、制表符、空格符等) |
# 替换和修饰符
replace 替换方法,可以完成字符的替换字符串.replace(/正则表达式/, '替换内容')
注意:只能替换一个字符,如果要替换多个字符,则需要多次调用 replace 方法
修饰符约束正则执行的某些细节行为,如是否区分大小写,是否支持多行匹配等
- i 是单词
ignore缩写,正则匹配时字母不区分大小写 - g 是单词
global缩写,正则匹配时全局匹配,即匹配所有符合规则的字符串
// 替换和修饰符 | |
const str = '欢迎大家学习前端,相信大家一定能学好前端,都成为前端大神' | |
// 1. 替换 replace 需求:把前端替换为 web | |
// 1.1 replace 返回值是替换完毕的字符串 | |
//const strEnd = str.replace (/ 前端 /, 'web') 只能替换一个 | |
// 2. 修饰符 g 全部替换 | |
const strEnd = str.replace(/前端/g, 'web') | |
console.log(strEnd) |
# change 事件
给 input 注册 change 事件,值被修改并且失去焦点后触发
# 判断是否有类
元素.classList.contains('类名'); |
# 作用域
作用域规定了变量能够被访问的 “范围”,离开了这个 “范围” 变量便不能被访问,作用域分为全局作用域和局部作用域
# 局部作用域
局部作用域分为函数作用域和块级作用域
# 函数作用域
在函数内部声明的变量只能在函数内部访问,外部无法直接访问。
// 声明 counter 函数 | |
function counter(x, y) { | |
// 函数内部声明的变量 | |
const s = x + y | |
console.log(s) // 18 | |
} | |
// 设用 counter 函数 | |
counter(10, 8) | |
// 访问变量 s | |
console.log(s)// 报错 |
总结:
- 函数的参数也是函数内部的局部变量
- 不同函数内部声明的变量无法互相访问
- 函数执行完毕后,函数内部的变量实际清空
# 块级作用域
使用 {} 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问
{ | |
//age 只能在该代码块中被访问 | |
let age = 18; | |
console.log(age); // 正常 | |
} | |
// 超出了 age 的作用域 | |
console.log(age) // 报错 | |
let flag = true; | |
if(flag) { | |
//str 只能在该代码块中被访问 | |
let str = 'hello world!' | |
console.log(str); // 正常 | |
} | |
// 超出了 age 的作用域 | |
console.log(str); // 报错 | |
for(let t = 1; t <= 6; t++) { | |
//t 只能在该代码块中被访问 | |
console.log(t); // 正常 | |
} | |
// 超出了 t 的作用域 | |
console.log(t); // 报错 |
除了变量之外还有常量,常量与变量的本质区别就是【常量必须有值且不允许被重新赋值】,常量值为对象时其属性和方法允许重新赋值。
总结:
let声明的变量会产生块级作用域,var不会产生块级作用域const声明的常量也会产生块级作用域- 不同代码块之间的变量无法互相访问
# 全局作用域
script 标签和 .js 文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
javascript 中作用域是程序被执行时的底层机制
# 作用域链
// 全局作用域 | |
let a = 1 | |
let b = 2 | |
// 局部作用域 | |
function f() { | |
let c | |
// 局部作用域 | |
function g() { | |
let d = 'yo' | |
} | |
} |
函数内部允许创建新的函数, f 函数内部创建的新函数 g ,会产生新的函数作用域,由此可知作用域发生了嵌套
作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查不到就会逐级查找父级作用域直到全局作用域
# 垃圾回收机制
javascript 中内存管理机制是自动的,不需要程序员去管理内存,当内存不再被使用, javascript 引擎会自动回收内存。
不再用到的内存,没有及时释放,就叫做内存泄露
# 内存的生命周期
- 内存分配:当我们声明变量、函数、对象时,系统会自动分配内存
- 内存使用:即读写内存,也就是使用变量、函数
- 内存回收:使用完毕,由垃圾回收自动回收
- 说明:全局变量一般不会自动回收(关闭页面回收)
# 垃圾回收机制
- 栈:由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面
- 堆:一般由程序员分配释放、若程序员不释放,由垃圾回收机制回收,复杂数据类型放到堆里面
# 引用计数算法
- 跟踪记录引用次数
- 引用一次,++
- 少一个引用 --
- 引用次数为 0,则回收内存
存在一个致命问题,嵌套引用
如果两个对象相互引用,尽管已不再使用,垃圾回收器不会回收,导致内存泄露。
function fn() { | |
let o1 = {} | |
let o2 = {} | |
o1.a = o2 | |
o2.a = o1 | |
return '引用计数无法回收' | |
} | |
fn() |
# 标记清除算法
- 将 "不再使用的对象" 定义为 "无法到达的对象"
- 就是从根部出发定时扫描内存中的对象,凡是能从根部到达的对象,都还是需要使用的。
- 那些无法从根部出发触及到的对象标记为不再使用,回收
# 闭包
闭包是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量。从代码形式上看闭包是一个作为返回值的函数
// 3. 闭包的写法 统计函数的调用次数 | |
function outer() { | |
let count = 1 | |
function fn() { | |
count++ | |
console.log(`函数被调用${count}次`) | |
} | |
return fn | |
} | |
const re = outer() | |
// const re = function fn() { | |
// count++ | |
// console.log (`函数被调用 ${count} 次`) | |
// } | |
re() | |
re() | |
//const fn = function () {} 函数表达式 | |
// 4. 闭包存在的问题: 可能会造成内存泄漏 |
总结:
- 闭包 = 内层函数 + 外层函数的变量
- 闭包的作用?
- 封闭数据:实现数据私有,外部也可以访问函数内部的变量
- 闭包很有用,因为它允许将函数与其所操作的某些数据关联起来
- 闭包可能会引起内存泄露
# 变量提升
它允许在变量声明之前被访问
// 只提升变量声明,不提升变量表达式 | |
// 访问变量 str | |
console.log(str + 'world!'); | |
// 声明变量 str | |
var str = 'hello '; |
总结:
- 变量在未声明即被访问时会报语法从错误
- 变量在声明之前即被访问,变量值未
undefined let声明的变量不存在变量提升,推荐使用let- 变量提升出现在相同作用域中
# 函数
# 函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用
// 调用函数 | |
foo() | |
// 声明函数 | |
function foo() { | |
console.log('声明之前即被调用...') | |
} | |
// 只提升函数声明,不提升函数表达式 | |
// 不存在提升现象 | |
bar() // 错误 | |
var bar = function () { | |
console.log('函数表达式不存在提升现象...') | |
} |
总结:
- 函数提升能够使函数的声明调用更灵活
- 函数表达式不存在提升的现象
- 函数提升出现在相同作用域
# 动态参数
arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
// 求生函数,计算所有参数的和 | |
function sum() { | |
// console.log(arguments) | |
let s = 0 | |
for(let i = 0; i < arguments.length; i++) { | |
s += arguments[i] | |
} | |
console.log(s) | |
} | |
// 调用求和函数 | |
sum(5, 10)// 两个参数 | |
sum(1, 2, 4) // 两个参数 |
总结:
arguments是一个伪数组arguments的作用是获取函数的实参
# 剩余参数
function config(baseURL, ...other) { | |
console.log(baseURL) // 得到 'http://baidu.com' | |
console.log(other) //other 得到 ['get', 'json'] | |
} | |
// 调用函数 | |
config('http://baidu.com', 'get', 'json'); |
总结:
...是语法符号,用于获取多余的实参- 借助
..., 获取的剩余参数,是一个真数组
# 展开运算符
展开运算符 ... ,用于将一个数组或类数组展开为多个参数
const arr = [1, 2, 3, 4, 5]; | |
console.log(Math.max(...arr)); | |
const arr2 = [6, 7, 8, 9, 10]; | |
const arr3 = [...arr, ...arr2]; | |
console.log(arr3); |
# 箭头函数
1. 箭头函数 基本语法 | |
const fn = () => { | |
console.log(123) | |
} | |
fn() | |
const fn = (x) => { | |
console.log(x) | |
} | |
fn(1) | |
2. 只有一个形参的时候,可以省略小括号 | |
const fn = x => { | |
console.log(x) | |
} | |
fn(1) | |
// 3. 只有一行代码的时候,我们可以省略大括号 | |
const fn = x => console.log(x) | |
fn(1) | |
4. 只有一行代码的时候,可以省略return | |
const fn = x => x + x | |
console.log(fn(1)) | |
5. 箭头函数可以直接返回一个对象 | |
const fn = (uname) => ({ uname: uname }) | |
console.log(fn('刘德华')) |
总结:
- 箭头函数属于表达式函数,因此不存在函数提升
- 箭头函数只有一个参数时可以省略圆括号 ()
- 箭头函数函数体只有一行代码时可以省略花括号 {},并自动作为返回值返回
# 箭头函数参数
箭头函数中没有 arguments , 只能使用 ... 动态获取实参
// 1. 利用箭头函数来求和 | |
const getSum = (...arr) => { | |
let sum = 0 | |
for (let i = 0; i < arr.length; i++) { | |
sum += arr[i] | |
} | |
return sum | |
} | |
const result = getSum(2, 3, 4) | |
console.log(result) // 9 |
# 箭头函数 this
箭头函数不会创建自己的 this ,它只会从自己的作用域链的上一层沿用 this
2. 箭头函数的this 是上一层作用域的this 指向 | |
const fn = () => { | |
console.log(this) // window | |
} | |
fn() | |
对象方法箭头函数 this | |
const obj = { | |
uname: 'pink老师', | |
sayHi: () => { | |
console.log(this) //this 指向谁? window | |
} | |
} | |
obj.sayHi() |
# 数组解构
数组结构是将数组的单元值快速批量赋值给一系列变量的简洁语法
let arr = [1, 2, 3] | |
// 批量声明变量 a b c | |
// 同时将数组单元值 1 2 3 依次赋值给变量 a b c | |
let [a, b, c] = arr | |
console.log(a); // 1 | |
console.log(b); // 2 | |
console.log(c); // 3 |
总结:
- 赋值运算符 = 左侧 [] 用于批量声明变量,右侧数组的单元值被赋值给左侧变量
- 变量的顺序对应数组单元值的位置一次进行赋值操作。
- 变量的数量大于单元值数量是,多余的变量被赋值于
undefined - 变量的数量小于单元值时,可以通过
...获取剩余单元值,但只能置于最末位 - 允许初始化变量的默认值,且只有单元值为
undefined时才会生效
注:支持多维解构赋值
# 对象解构
对象解构是将对象属性和方法快速批量赋值给一系列的简洁语法
// 普通对象 | |
const user = { | |
name: '小明', | |
age: 18 | |
}; | |
// 批量声明变量 name age | |
// 同时将数组单元值 小明 18 依次赋值给变量 name age | |
const {name, age} = user | |
console.log(name) // 小明 | |
console.log(age) // 18 |
总结:
- 赋值运算符
=左侧的{}用于批量声明变量,右侧对象的属性值会被赋值给左侧的变量 - 对象属性的值被赋值给属性名相同的变量
- 对象中找不到与变量名一致的属性时变量为
undefined - 允许初始化变量的默认值,属性不存在或单元值为
undefined时默认值才会生效
支持多维解构赋值
// 1. 这是后台传递过来的数据 | |
const msg = { | |
"code": 200, | |
"msg": "获取新闻列表成功", | |
"data": [ | |
{ | |
"id": 1, | |
"title": "5G商用自己,三大运用商收入下降", | |
"count": 58 | |
}, | |
{ | |
"id": 2, | |
"title": "国际媒体头条速览", | |
"count": 56 | |
}, | |
{ | |
"id": 3, | |
"title": "乌克兰和俄罗斯持续冲突", | |
"count": 1669 | |
}, | |
] | |
} | |
// 需求 1: 请将以上 msg 对象 采用对象解构的方式 只选出 data 方面后面使用渲染页面 | |
// const { data } = msg | |
// console.log(data) | |
// 需求 2: 上面 msg 是后台传递过来的数据,我们需要把 data 选出当做参数传递给 函数 | |
// const { data } = msg | |
//msg 虽然很多属性,但是我们利用解构只要 data 值 | |
function render({ data }) { | |
// const { data } = arr | |
// 我们只要 data 数据 | |
// 内部处理 | |
console.log(data) | |
} | |
render(msg) | |
// 需求 3, 为了防止 msg 里面的 data 名字混淆,要求渲染函数里面的数据名改为 myData | |
function render({ data: myData }) { | |
// 要求将 获取过来的 data 数据 更名为 myData | |
// 内部处理 | |
console.log(myData) | |
} | |
render(msg) |
# forEach 遍历数组
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
//forEach 就是遍历 加强版的 for 循环 适合于遍历数组对象 | |
const arr = ['red', 'green', 'pink'] | |
const result = arr.forEach(function (item, index) { | |
console.log(item) // 数组元素 red green pink | |
console.log(index) // 索引号 | |
}) | |
// console.log(result) |
# filter 过滤数组
filter() 方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素。
const arr = [10, 20, 30] | |
// const newArr = arr.filter(function (item, index) { | |
// // console.log(item) | |
// // console.log(index) | |
// return item >= 20 | |
// }) | |
// 返回的符合条件的新数组 | |
const newArr = arr.filter(item => item >= 20) | |
console.log(newArr) |
# 深入对象
# 构造函数
构造函数是专门用于创建对象的函数,如果一个函数用 new 关键字调用,那么这个函数就是构造函数
// 定义函数 | |
function foo() { | |
console.log('通过 new 也能调用函数...'); | |
} | |
// 调用函数 | |
new foo; |
总结:
- 使用
new关键字调用函数的行为被称为实例化 - 实例化构造函数时没有参数时可以省略 ()
- 构造函数的返回值即为创建的对象
- 构造函数中的
return无效
注:实践中为了从视觉上区分构造函数和普通函数,习惯将构造函数的首字母大写。
# 实例成员
通过构造函数创建的对象称为实例对象,实例对象中的属性和方法被称为实例成员
// 构造函数 | |
function Person() { | |
// 构造函数内部的 this 就是实例对象 | |
// 实例对象中动态添加属性 | |
this.name = '小明' | |
// 实例对象动态添加方法 | |
this.sayHi = function () { | |
console.log('大家好~') | |
} | |
} | |
// 实例化,p1 是实例对象 | |
//p1 实际就是 构造函数内部的 this | |
const p1 = new Person() | |
console.log(p1) | |
console.log(p1.name) // 访问实例属性 | |
p1.sayHi() // 调用实例方法 |
总结:
- 构造函数中的
this就是实例对象,为其动态添加的属性和方法即为实例成员 - 为构造函数传入参数,动态创建结构相同但值不同的对象
# 静态成员
在 javascript 中底层函数本质上也是对象类型,因此允许直接为函数动态添加属性或方法,构造函数的属性和方法被称为静态成员
// 构造函数 | |
function Person(name, age) { | |
// 省略实例成员 | |
} | |
// 静态属性 | |
Person.eyes = 2 | |
Person.arms = 2 | |
// 静态方法 | |
Person.walk = function () { | |
console.log('^_^人都会走路...') | |
//this 指向 Person | |
console.log(this.eyes) | |
} |
总结:
- 静态成员指的是添加到构造函数本身的属性和方法
- 一般公共特征的属性和方法静态成员设置为静态成员
- 静态成员中的
this指向构造函数本身
# 内置构造函数
# Object
Object 是内置的构造函数,用于创建普通对象
// 通过构造函数创建普通对象 | |
const user = new Object({name: '小明', age: 15}) | |
// 这种方式声明的变量称为【字面量】 | |
let student = {name: '杜子腾', age: 21} | |
// 对象语法简写 | |
let name = '小红'; | |
let people = { | |
// 相当于 name: name | |
name, | |
// 相当于 walk: function () {} | |
walk () { | |
console.log('人都要走路...'); | |
} | |
} | |
console.log(student.constructor); | |
console.log(user.constructor); | |
console.log(student instanceof Object); |
总结:
- 推荐使用字面量方式声明对象,而不是
Object构造函数 Object.assign静态方法创建新的对象Object.keys静态方法获取对象中所有属性Object.values表态方法获取对象中所有属性值
# Array
Array 是内置的构造函数,用于创建数组
数组赋值后,无论修改哪个变量另一个对象的数据值也会相当发生改变
# reduce
作用: reduce 返回函数累计处理的结果,经常用于求和等
基本语法: arr.reduce(function(){}, 起始值)
参数:起始值可以省略,如果写就作为第一次累计的起始值
const arr = [1, 5, 6]; | |
const count = arr.reduce((prev, item) => prev + item); | |
console.log(count); |
# from
from 静态方法用于将类数组对象或可遍历对象转换为数组Array.from()
总结:
推荐使用字面量方式声明数组,而不是
Array构造函数实例方法
forEach用于遍历数组,替代for循环 (重点)实例方法
filter过滤数组单元值,生成新数组 (重点)实例方法
map迭代原数组,生成新数组 (重点)实例方法
join数组元素拼接为字符串,返回字符串 (重点)实例方法
find查找元素, 返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined (重点)实例方法
every检测数组所有元素是否都符合指定条件,如果所有元素都通过检测返回 true,否则返回 false (重点)实例方法
some检测数组中的元素是否满足指定条件 如果数组中有元素满足条件返回 true,否则返回 false实例方法
concat合并两个数组,返回生成新数组实例方法
sort对原数组单元值排序实例方法
splice删除或替换原数组单元实例方法
reverse反转数组实例方法
findIndex查找元素的索引值
# String
String 是内置的构造函数,用于创建字符串。
<script> | |
// 使用构造函数创建字符串 | |
let str = new String('hello world!'); | |
// 字面量创建字符串 | |
let str2 = '你好,世界!'; | |
// 检测是否属于同一个构造函数 | |
console.log(str.constructor === str2.constructor); // true | |
console.log(str instanceof String); // false | |
</script> |
总结:
- 实例属性
length用来获取字符串的度长 (重点) - 实例方法
split('分隔符')用来将字符串拆分成数组 (重点) - 实例方法
substring(需要截取的第一个字符的索引[,结束的索引号])用于字符串截取 (重点) - 实例方法
startsWith(检测字符串[, 检测位置索引号])检测是否以某字符开头 (重点) - 实例方法
includes(搜索的字符串[, 检测位置索引号])判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false (重点) - 实例方法
toUpperCase用于将字母转换成大写 - 实例方法
toLowerCase用于将就转换成小写 - 实例方法
indexOf检测是否包含某字符 - 实例方法
endsWith检测是否以某字符结尾 - 实例方法
replace用于替换字符串,支持正则匹配 - 实例方法
match用于查找字符串,支持正则匹配
注:String 也可以当做普通函数使用,这时它的作用是强制转换成字符串数据类型。
# 构造函数方法浪费内存
function Star(uname, age) { | |
this.uname = uname; | |
this.age = age; | |
this.sing = function() { | |
console.log('11'); | |
} | |
} | |
const ldh = new Star('ldh', 18); | |
const zxy = new Star('zxy', 18); |
这里当构造函数内部存有复杂数据类型时(放入到堆中),每次创建实例都会创建新的数据,浪费内存
# 原型
- 构造函数通过原型分配的函数是所有对象共享的
JS中规定,每一个构造函数都有一个prototype属性,所以我们也成为原型对象- 这个对象可以挂载函数,对象的实例化不会多次创建原型上函数,节约内存
- 我们可以把哪些不变的方法,直接定义在
prototype上,这样所有的对象的实例就可以共享这些方法 - 构造函数和原型对象中的
this都指向实例化对象。
let that | |
function Person(name) { | |
this.name = name; | |
that = this | |
} | |
const o = new Person('小明'); | |
console.log(that === o); // true |
let that | |
function Person(name) { | |
this.name = name; | |
} | |
Person.prototype.sing = function() { | |
that = this; | |
} | |
const o = new Person('小明'); | |
o.sing(); | |
console.log(that === o); |
# constructor 属性
每个原型对象里都有一个 constructor 属性,指向构造函数。
构造函数 => prototype中的属性和方法 | |
prototype中的constructor属性 => 构造函数 |
使用场景:
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值。
但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。
此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数
function Star(name) { | |
this.name = name; | |
} | |
Star.prototype = { | |
constructor: Star, | |
sing: function() {} | |
dance: function() {} | |
} | |
console.log(Star.prototype.constructor) // 指向 Star |
# 对象原型
对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__ 原型的存在
注意:
__proto__是JS非标准属性[[prototype]]和__proto__意义相同- 用来表明当前实例对象指向哪个原型对象
prototype __proto__对象原型里也有一个constructor属性,指向创建该实例对象的构造函数
构造函数 => 原型prototype | |
=> 实例对象 | |
实例对象.__proto__.constructor => 构造函数 | |
实例对象.__proto__ => 原型prototype | |
prototype.constructor => 构造函数 |
# 原型继承
- 封装 - 抽取公共部分
const People = { | |
head: 1, | |
eye: 2, | |
} | |
function Man() { | |
} | |
function Woman() { | |
this.baby = function() {} | |
} |
- 继承 - 让男人和女人都能继承人类的一些属性和方法
- 把男人女人公共的属性和方法抽取出来
- 赋值给 Man 的原型对象,可以共享这些属性和方法
- 注意让
constructor指回Man这个构造函数
const People = { | |
head: 1, | |
eye: 2, | |
} | |
function Man() { | |
} | |
Man.prototype = People; | |
Man.prototype.constructor = Man; |
# 问题:
如果我们给男人添加一个吸烟的方法,发现女人也自动添加
原因:男人和女人使用了同一个对象,根据引用类型的特点,他们指向同一个对象,修改一个就会都影响
# 解决:
构造函数new 每次都会创建一个新的对象
function Star() { | |
this age = 18; | |
this.say = function() {} | |
} | |
const ldh = new Star(); | |
const zxy = new Star(); |
# 最终版本
function Person() { | |
this.head = 1; | |
this.eyes = 2; | |
} | |
function Man() { | |
} | |
Man.prototype = new Person(); // 用 new 生成对象 | |
Man.prototype.constructor = Man; // 修改 constructor 指向构造函数 | |
Woman.prototype = new Person(); | |
Woman.prototype.constructor = Woman; |
# 原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链
由于每个对象中都有一个`__proto__`属性,
这个属性指向构造函数的`prototype`原型对象,
所以`__proto__`对象原型里也有一个`constructor`属性,
指向创建该实例对象的构造函数
层层递推
# 查找规则
- 访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性
- 如果没有就查找它的原型(也就是
__proto__指向的prototype原型对象) - 如果还没有就查找原型对象的原型(
Object的原型对象) - 以此类推一直找到
Object位置 (null) __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说是一条路径- 可以用
instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。
# 深浅拷贝
# 浅拷贝:
浅拷贝和深拷贝只针对引用类型
浅拷贝:拷贝的是地址
常见方法:
- 拷贝对象:
Object.assign()/ 展开运算符{...obj}拷贝对象 - 拷贝数组:
Array.prototype.concat()/[...arr]
若是简单数据类型拷贝值,引用数据类型拷贝的是地址(简单理解,如果是单层对象,没问题,如果有多层就有问题)
# 深拷贝
首先浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象,不是地址
常见方法:
- 通过递归实现
lodash/clneDeep- 通过
JSON.stringify()实现
# 递归实现深拷贝
function deepCopy(newObj, oldObj) { | |
for(let k in oldObj) { | |
// console.log(1); | |
if(oldObj[k] instanceof Array) { | |
// console.log(1); | |
newObj[k] = []; | |
deepCopy(newObj[k], oldObj[k]); | |
} | |
else if(oldObj[k] instanceof Object) { | |
newObj[k] = {}; | |
deepCopy(newObj[k], oldObj[k]); | |
} | |
else newObj[k] = oldObj[k]; | |
} | |
} | |
deepCopy(o, obj); | |
o.family["baby"] = 'xiaohong'; | |
console.log(o); | |
console.log(obj); |
# js 库 lodash 里面 cloneDeep 内部实现深拷贝
const o = _.cloneDeep(obj); |
# JSON 序列化
const o = JSON.parse(JSON.stringify(obj)) | |
// 这里是先将 obj 转化为 JSON 字符串,然后再将字符串转化为对象,所以不会出现循环引用的问题,相当于 new 了一个对象 |
# 异常处理
# throw
throw抛出异常信息,程序也会终止执行throw后面跟的是错误提示信息Error对象配合throw使用,能够设置更详细的错误信息
function counter(x, y) { | |
if(!x || !y) { | |
//throw ' 参数不能为空!'; | |
throw new Error('参数不能为空!') | |
} | |
return x + y | |
} |
# try...catch
function foo() { | |
try { | |
// 查找 DOM 节点 | |
const p = document.querySelector('.p') | |
p.style.color = 'red' | |
} catch (error) { | |
//try 代码段中执行有错误时,会执行 catch 代码段 | |
// 查看错误信息 | |
console.log(error.message) | |
// 终止代码继续执行 | |
return | |
} | |
finally { | |
alert('执行') | |
} | |
console.log('如果出现错误,我的语句不会执行') | |
} | |
foo() |
try...catch用于捕获错误信息- 将预估可能错误的代码写在
try代码段中 - 如果
try代码段中出现错误,会执行catch代码段,并截获到错误信息
# debugger
相当于断电调试
# 处理 this
# 普通函数
普通函数的调用方式决定了 this 的值,即【谁调用,this 就指向谁】
// 普通函数 | |
function sayHi() { | |
console.log(this) | |
} | |
// 函数表达式 | |
const sayHello = function () { | |
console.log(this) | |
} | |
// 函数的调用方式决定了 this 的值 | |
sayHi() // window | |
window.sayHi() | |
// 普通对象 | |
const user = { | |
name: '小明', | |
walk: function () { | |
console.log(this) | |
} | |
} | |
// 动态为 user 添加方法 | |
user.sayHi = sayHi | |
uesr.sayHello = sayHello | |
// 函数调用方式,决定了 this 的值 | |
user.sayHi() | |
user.sayHello() |
注意:普通函数没有明确调用者时 this 默认指向 window , 严格模式下没有调用者时 this 指向 undefined
# 箭头函数
箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this ,箭头函数中访问的 this 不过是箭头函数所在作用域 this 变量。
console.log(this) // 此处为 window | |
// 箭头函数 | |
const sayHi = function() { | |
console.log(this) // 该箭头函数中的 this 为函数声明环境中 this 一致 | |
} | |
// 普通对象 | |
const user = { | |
name: '小明', | |
// 该箭头函数中的 this 为函数声明环境中 this 一致 | |
walk: () => { | |
console.log(this) | |
}, | |
sleep: function () { | |
let str = 'hello' | |
console.log(this) | |
let fn = () => { | |
console.log(str) | |
console.log(this) // 该箭头函数中的 this 与 sleep 中的 this 一致 | |
} | |
// 调用箭头函数 | |
fn(); | |
} | |
} | |
// 动态添加方法 | |
user.sayHi = sayHi | |
// 函数调用 | |
user.sayHi() | |
user.sleep() | |
user.walk() |
在开发中【使用箭头函数前要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window ,因此 DOM 事件回调函数不推荐使用箭头函数
// DOM 节点 | |
const btn = document.querySelector('.btn') | |
// 箭头函数 此时 this 指向了 window | |
btn.addEventListener('click', () => { | |
console.log(this) | |
}) | |
// 普通函数 此时 this 指向了 DOM 对象 | |
btn.addEventListener('click', function () { | |
console.log(this) | |
}) |
同样由于箭头函数 this 的原因,基于原型的面向对象也不推荐采用箭头函数
function Person() { | |
} | |
// 原型对像上添加了箭头函数 | |
Person.prototype.walk = () => { | |
console.log('人都要走路...') | |
console.log(this); // window | |
} | |
const p1 = new Person() | |
p1.walk() |
# 改变 this 指向
# call
使用 call 方法调用函数,同时指定函数中 this 的值
// 普通函数 | |
function sayHi() { | |
console.log(this); | |
} | |
let user = { | |
name: '小明', | |
age: 18 | |
} | |
let student = { | |
name: '小红', | |
age: 16 | |
} | |
// 调用函数并指定 this 的值 | |
sayHi.call(user); //this 值为 user | |
sayHi.call(student); //this 值为 student | |
// 求和函数 | |
function counter(x, y) { | |
return x + y; | |
} | |
// 调用 counter 函数,并传入参数 | |
let result = counter.call(null, 5, 10); | |
console.log(result); |
总结:
call方法调用函数,同时指定函数中this的值- 使用
call方法调用函数时,第 1 个参数为this指定的值 call方法调用函数时,剩余参数为函数的参数
# apply
使用 apply 方法调用函数,同时指定函数中 this 的值
// 调用函数并指定 this 的值 | |
sayHi.apply(user) //this 值为 user | |
sayHi.apply(student) //this 值为 student |
总结:
apply方法能够在调用函数的同时指定this的值- 使用
apply方法调用函数时,第一个参数为this指定的值 apply方法调用函数时,第二个参数为函数的参数,参数为数组
# bind
bind 方法并不会调用函数,而是创建一个指定了 this 值的新函数
// 普通函数 | |
function sayHi() { | |
console.log(this) | |
} | |
let user = { | |
name: '小明', | |
age: 18 | |
} | |
// 调用 bind 指定 this 的值 | |
let sayHello = sayHi.bind(user); | |
// 调用使用 bind 创建的新函数 | |
sayHello() |
注意: bind 方法不会改变原函数,与原函数的唯一变化是改变了 this 的值
# 节流 (throttle)
节流:在单位时间内,只能触发一次函数,如果单位时间内触发多次,则只执行一次
应用场景:滚动事件,resize 事件,拖拽事件,点击事件,mousemove 事件
# 手写 throttle 节流函数
function throttle(fn, t) { | |
// 起始时间 | |
let timeId = null; | |
return function () { | |
if(timeId) { | |
} | |
else { // 没有定时器的时候才执行一次 | |
timeId = setTimeout(function() { | |
fn(); | |
timeId = null; // 这里清除定时器需要使用这个,不能使用 clearTimeout (timeId) | |
}, t); | |
} | |
} | |
} |
# lodash throttle 节流函数
_.throttle(function() {}, 1000); |
# 防抖 (debounce)
防抖:在单位时间内,只触发一次函数,如果单位时间内触发多次,会重新计算时间,直到单位时间内没有触发函数,才执行函数
应用场景:搜索框输入事件,resize 事件,拖拽事件,点击事件,mousemove 事件
# 手写 debounce 防抖函数
function debounce(fn, t) { | |
let timeId; | |
return function() { | |
if(timeId) clearTimeout(timeId); // 有定时器的时候清除 | |
timeId = setTimeout(function() { // 无论怎样都会重新开一个定时器 | |
fn(); | |
}, t); | |
} | |
} |
# lodash debounce 防抖函数
_.debounce(function() {}, 1000); |
# 事件节流与防抖
// 1. 获取元素 要对视频进行操作 | |
const video = document.querySelector('video') | |
video.ontimeupdate = _.throttle(() => { | |
//console.log (video.currentTime) 获得当前的视频时间 | |
// 把当前的时间存储到本地存储 | |
localStorage.setItem('currentTime', video.currentTime) | |
}, 1000) | |
// 打开页面触发事件,就从本地存储里面取出记录的时间, 赋值给 video.currentTime | |
video.onloadeddata = () => { | |
// console.log(111) | |
video.currentTime = localStorage.getItem('currentTime') || 0 | |
} |