# DOM 树

  • 将 HTML 文档以树形结构直观的表现出来,我们称之为文档树或 DOM 树
  • 描述网页内容的名词
  • 作用:文档树直观的体现了标签与标签之间的关系

DOM 对象:游览器根据 html 标签生成的 JS 对象

  • 所有的标签属性都可以在这个对象上面找到
  • 修改这个对象的属性会自动映射到标签身上
    DOM 的核心思想
  • 把网页内容当作对象来处理
    document 对象
  • 是 DOM 里提供的一个对象
  • 所以它提供的属性和方法都是用来访问和操作网页内容的
  • 网页的所有内容都在 document 里面

# 根据 CSS 选择器来获取 DOM 元素

  1. 选择匹配的第一个元素
    语法:
document.querySelector('css选择器');

参数:
包含一个或多个有效的 CSS 选择器字符串
返回值:
CSS 选择器匹配的第一个元素,一个 HTMLElement 对象
如果没有匹配到,则返回 null
2. 选择匹配多个元素
语法:

document.querySelectorAll('css选择器');

参数:
包含一个或多个有效的 CSS 选择器字符串
返回值:
CSS 选择器匹配的 NodeList 对象集合
例如

document.querySelectorAll(`ul li`);

得到的是一个伪数组

  • 有长度有索引号的数组
  • 但是没有 pop () push () 等数组方法
    循环遍历

# 操作元素内容

  1. 元素 innerText 属性
  • 将文本内容添加 / 更新到任意标签位置
  • 显示纯文本,不解析标签
  1. 元素 innerHTML 属性
  • 将文本内容添加 / 更新到任意标签位置
  • 会解析标签,多标签建议使用模板字符

# 操作元素常用属性

可以通过 JS 设置 / 修改标签元素属性,比如通过 src 更换图片
最常见的属性比如:href、title、src 等
语法:
对象.属性 = 值

# 操作元素样式属性

可以通过 JS 设置 / 修改标签元素的样式属性

  1. 通过 style 属性操作 CSS
    语法: 对象.style.属性 = 值
    注意:
    1. 修改样式通过 style 属性引出
    2. 如果属性有 - 连接符,需要转换小驼峰命名法
    3. 赋值的时候,需要的时候不要忘记 CSS 单位。

注意:不要忘记单位

缺点:
在修改样式比较少的情况下有优势
生成的是行内样式表,权重比较高

  1. 操作类名 (className) 操作 CSS
  • 如果修改的样式比较多,直接通过 style 属性修改比较繁琐,我们可以通过借助于 css 类名的形式
  • 语法 元素.calssName = '类名'
  • 注意:
    class 是关键字,所以用 className 代替
    className 是使用新值换旧值,如果需要添加一个类需要保留之前的类名。

缺点:多个类名操作麻烦

  1. 通过 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
getElementsByClassNameHTMLCollection动态集合实时更新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少量行内样式
classNameel.className = 'active'类权重整体样式替换
classList APIel.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);  // 传统方式

# 💡 最佳实践

  1. 选择器优先级:ID > Class > Tag > 属性选择器
  2. 样式操作:优先使用 classList,避免频繁操作行内样式
  3. 属性访问:标准属性直接访问,自定义属性使用 dataset
  4. 内容安全:用户输入内容务必使用 innerText
  5. 动画优化:使用 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);

# 定时器 - 间歇函数

定时器函数可以开启和关闭定时器

  1. 开启定时器
    setInterval(函数, 间隔时间)
    每隔一段时间调用这个函数
    间隔时间单位是毫秒
fucntion repeat() {
  console.log('重复执行');
}
setInterval(repeat, 1000);

注意:函数名字不需要加括号、定时器返回的是一个 id 数字

  1. 关闭定时器
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()

Stringtrim() 方法会从字符串的两端移除空白字符,并返回一个新的字符串,不会修改原始字符串
要返回仅从一端修剪空白字符的新字符串,使用 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() {
  // 执行的操作
});
  • scrollLeftscrollTop
    • 获取被卷去的大小
    • 获取元素内容往左、往上出去看不到的距离
    • 这两个值是可读写的

# 获取位置

开发中,我们需要检测页面滚动的距离

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、滚动条等)
    • clientWidthclientHeight
  • 获取宽高

    • 获取元素的自身宽高、包含元素自身设置的宽高、padding、border
    • offsetWidthoffsetHeight
    • 获取出来的是数值
    • 注意:获取的是可视宽高,如果盒子是隐藏的,获取的结果是 0
  • 获取位置:

    • 获取元素距离自己定位父级元素的左、上距离
    • offsetLeftoffsetTop ,都是只读属性
  • 获取位置:

    • element.getBoundingClientRect()
    • 方法返回元素的大小及其相对于视口的位置

# 总结

属性作用说明
scrollLeftscrollTop被卷去的头部和左侧配合页面滚动来用,可读写
clientWidthclientHeight获取元素宽度和高度不包含 border, margin,滚动条。用于 js 获取元素大小,只读属性
offsetWidthoffsetHeight获取元素宽度和高度包含 border、padding,滚动条等,只读
offsetLeftoffsetTop获取元素距离自己定位父级元素的左、上距离获取元素位置的时候使用,只读属性

# 日期对象

  • 创建一个时间对象并获取时间 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 秒到当前时间的毫秒数
  • 算法:
    • 将来时间 - 现在时间戳 = 剩余时间
    • 剩余时间毫秒数转换为剩余时间的年月日就是倒计时时间

# 三种获取时间戳的方法:

  1. 使用 getTime() 方法
const date = new Date();
console.log(date.getTime());
  1. 简写 +new Date()
    无需实例化
console.log(+new Date());
  1. 使用 Date.now()
    无需实例化
    但是只能得到当前的时间戳,而前面两种可以返回指定时间的时间戳
console.log(Date.now());

# 节点

# 查找节点

  • 父节点查找:

    • parentNode 属性
    • 返回最近一级的父节点 找不到返回为 null
    • 子元素.parentNode
  • 子节点查找:

    • childNodes :获取所有子节点、包括文本节点(空格、换行)、注释节点
    • children 属性:仅仅获得所有元素节点,返回的还是一个伪数组
    • 父元素.children
  • 兄弟关系查找:

    • 下一个兄弟: nextElementSibling 属性
    • 上一个兄弟: previousElementSibling 属性

# 增加节点

  1. 创建节点
  • 即创造出一个新的网页元素,再添加到网页内,一般先创建节点,然后插入节点
  • 创建元素节点方法:
document.createElement('标签名');
  1. 追加节点
  • 想要再界面看到,还得插入到某个父元素中
  • 插入到父元素的最后一个子元素:
// 插入到这个父元素的最后
父元素.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 的顶级对象
  • documentalert()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 中出现了同步和异步

  1. 同步:
    前一个任务结束再执行后一个任务,程序的顺序和执行的顺序是一致的,同步的。

  2. 异步:
    在执行一个任务时,不会等待这个任务执行完毕,而是继续往下执行,当这个任务执行完毕后,再执行后续的任务。异步的。

  3. 同步任务:
    同步任务都在主线程上执行,形成一个 执行栈

  4. 异步任务:
    JS 的异步是通过回调函数实现的。
    一般而言,异步任务有以下三种类型:

  • 普通事件,如比如 clickmouseovermouseout
  • 资源加载,如 loaderrorabort
  • 定时器,如 setTimeoutsetInterval
    异步任务相关添加到任务队列中 (任务队列也称消息队列)

# 执行顺序

  1. 先执行 执行栈 中的同步任务
  2. 异步任务放入任务队列中
  3. 一旦执行栈中的所有同步任务执行完毕,系统就会被按次序读取任务队列中的 异步任务 ,于是被读取的异步任务结束等待状态,进入执行栈,开始执行。

由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为 事件循环(event loop)

# location 对象

location 的数据类型是对象,它拆分并保存了 URL 地址的各个组成部分

# location.href

href 属性获取完整的 URL 地址,对其赋值时用于地址的跳转

location.href = 'http://www.baidu.com';

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 的数据类型是对象,该对象记录了浏览器自身的相关信息
  • 常用的属性和方法:通过 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 后退一个页面

# 本地存储

  1. 数据存储在用户浏览器中
  2. 设置、读取方便、刷新不丢失
  3. 容量较大, sessionStoragelocalStorage 约为 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)// 报错

总结:

  1. 函数的参数也是函数内部的局部变量
  2. 不同函数内部声明的变量无法互相访问
  3. 函数执行完毕后,函数内部的变量实际清空

# 块级作用域

使用 {} 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问

{
    //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); // 报错

除了变量之外还有常量,常量与变量的本质区别就是【常量必须有值且不允许被重新赋值】,常量值为对象时其属性和方法允许重新赋值。

总结:

  1. let 声明的变量会产生块级作用域, var 不会产生块级作用域
  2. const 声明的常量也会产生块级作用域
  3. 不同代码块之间的变量无法互相访问

# 全局作用域

script 标签和 .js 文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。

javascript 中作用域是程序被执行时的底层机制

# 作用域链

// 全局作用域
  let a = 1
  let b = 2
  // 局部作用域
  function f() {
    let c
    // 局部作用域
    function g() {
      let d = 'yo'
    }
  }

函数内部允许创建新的函数, f 函数内部创建的新函数 g ,会产生新的函数作用域,由此可知作用域发生了嵌套

作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查不到就会逐级查找父级作用域直到全局作用域

# 垃圾回收机制

javascript 中内存管理机制是自动的,不需要程序员去管理内存,当内存不再被使用, javascript 引擎会自动回收内存。
不再用到的内存,没有及时释放,就叫做内存泄露

# 内存的生命周期

  1. 内存分配:当我们声明变量、函数、对象时,系统会自动分配内存
  2. 内存使用:即读写内存,也就是使用变量、函数
  3. 内存回收:使用完毕,由垃圾回收自动回收
  4. 说明:全局变量一般不会自动回收(关闭页面回收)

# 垃圾回收机制

  1. 栈:由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面
  2. 堆:一般由程序员分配释放、若程序员不释放,由垃圾回收机制回收,复杂数据类型放到堆里面

# 引用计数算法

  1. 跟踪记录引用次数
  2. 引用一次,++
  3. 少一个引用 --
  4. 引用次数为 0,则回收内存

存在一个致命问题,嵌套引用
如果两个对象相互引用,尽管已不再使用,垃圾回收器不会回收,导致内存泄露。

function fn() {
let o1 = {}
let o2 = {}
o1.a = o2
o2.a = o1
return '引用计数无法回收'
}
fn()

# 标记清除算法

  1. 将 "不再使用的对象" 定义为 "无法到达的对象"
  2. 就是从根部出发定时扫描内存中的对象,凡是能从根部到达的对象,都还是需要使用的。
  3. 那些无法从根部出发触及到的对象标记为不再使用,回收

# 闭包

闭包是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量。从代码形式上看闭包是一个作为返回值的函数

// 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. 闭包存在的问题: 可能会造成内存泄漏

总结:

  1. 闭包 = 内层函数 + 外层函数的变量
  2. 闭包的作用?
  • 封闭数据:实现数据私有,外部也可以访问函数内部的变量
  • 闭包很有用,因为它允许将函数与其所操作的某些数据关联起来
  1. 闭包可能会引起内存泄露

# 变量提升

它允许在变量声明之前被访问

// 只提升变量声明,不提升变量表达式
// 访问变量 str
  console.log(str + 'world!');
  // 声明变量 str
  var str = 'hello ';

总结:

  1. 变量在未声明即被访问时会报语法从错误
  2. 变量在声明之前即被访问,变量值未 undefined
  3. let 声明的变量不存在变量提升,推荐使用 let
  4. 变量提升出现在相同作用域中

# 函数

# 函数提升

函数提升与变量提升比较类似,是指函数在声明之前即可被调用

// 调用函数
  foo()
  // 声明函数
  function foo() {
    console.log('声明之前即被调用...')
  }
  // 只提升函数声明,不提升函数表达式
  // 不存在提升现象
  bar()  // 错误
  var bar = function () {
    console.log('函数表达式不存在提升现象...')
  }

总结:

  1. 函数提升能够使函数的声明调用更灵活
  2. 函数表达式不存在提升的现象
  3. 函数提升出现在相同作用域

# 动态参数

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) // 两个参数

总结:

  1. arguments 是一个伪数组
  2. arguments 的作用是获取函数的实参

# 剩余参数

function config(baseURL, ...other) {
    console.log(baseURL) // 得到 'http://baidu.com'
    console.log(other)  //other  得到 ['get', 'json']
  }
  // 调用函数
  config('http://baidu.com', 'get', 'json');

总结:

  1. ... 是语法符号,用于获取多余的实参
  2. 借助 ... , 获取的剩余参数,是一个真数组

# 展开运算符

展开运算符 ... ,用于将一个数组或类数组展开为多个参数

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('刘德华'))

总结:

  1. 箭头函数属于表达式函数,因此不存在函数提升
  2. 箭头函数只有一个参数时可以省略圆括号 ()
  3. 箭头函数函数体只有一行代码时可以省略花括号 {},并自动作为返回值返回

# 箭头函数参数

箭头函数中没有 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

总结:

  1. 赋值运算符 = 左侧 [] 用于批量声明变量,右侧数组的单元值被赋值给左侧变量
  2. 变量的顺序对应数组单元值的位置一次进行赋值操作。
  3. 变量的数量大于单元值数量是,多余的变量被赋值于 undefined
  4. 变量的数量小于单元值时,可以通过 ... 获取剩余单元值,但只能置于最末位
  5. 允许初始化变量的默认值,且只有单元值为 undefined 时才会生效
    注:支持多维解构赋值

# 对象解构

对象解构是将对象属性和方法快速批量赋值给一系列的简洁语法

// 普通对象
  const user = {
    name: '小明',
    age: 18
  };
  // 批量声明变量 name age
  // 同时将数组单元值 小明  18 依次赋值给变量 name  age
  const {name, age} = user
  console.log(name) // 小明
  console.log(age) // 18

总结:

  1. 赋值运算符 = 左侧的 {} 用于批量声明变量,右侧对象的属性值会被赋值给左侧的变量
  2. 对象属性的值被赋值给属性名相同的变量
  3. 对象中找不到与变量名一致的属性时变量为 undefined
  4. 允许初始化变量的默认值,属性不存在或单元值为 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;

总结:

  1. 使用 new 关键字调用函数的行为被称为实例化
  2. 实例化构造函数时没有参数时可以省略 ()
  3. 构造函数的返回值即为创建的对象
  4. 构造函数中的 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() // 调用实例方法

总结:

  1. 构造函数中的 this 就是实例对象,为其动态添加的属性和方法即为实例成员
  2. 为构造函数传入参数,动态创建结构相同但值不同的对象

# 静态成员

javascript 中底层函数本质上也是对象类型,因此允许直接为函数动态添加属性或方法,构造函数的属性和方法被称为静态成员

// 构造函数
  function Person(name, age) {
    // 省略实例成员
  }
  // 静态属性
  Person.eyes = 2
  Person.arms = 2
  // 静态方法
  Person.walk = function () {
    console.log('^_^人都会走路...')
    //this 指向 Person
    console.log(this.eyes)
  }

总结:

  1. 静态成员指的是添加到构造函数本身的属性和方法
  2. 一般公共特征的属性和方法静态成员设置为静态成员
  3. 静态成员中的 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);

总结:

  1. 推荐使用字面量方式声明对象,而不是 Object 构造函数
  2. Object.assign 静态方法创建新的对象
  3. Object.keys 静态方法获取对象中所有属性
  4. 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()

总结:

  1. 推荐使用字面量方式声明数组,而不是 Array 构造函数

  2. 实例方法 forEach 用于遍历数组,替代 for 循环 (重点)

  3. 实例方法 filter 过滤数组单元值,生成新数组 (重点)

  4. 实例方法 map 迭代原数组,生成新数组 (重点)

  5. 实例方法 join 数组元素拼接为字符串,返回字符串 (重点)

  6. 实例方法 find 查找元素, 返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined (重点)

  7. 实例方法 every 检测数组所有元素是否都符合指定条件,如果所有元素都通过检测返回 true,否则返回 false (重点)

  8. 实例方法 some 检测数组中的元素是否满足指定条件 如果数组中有元素满足条件返回 true,否则返回 false

  9. 实例方法 concat 合并两个数组,返回生成新数组

  10. 实例方法 sort 对原数组单元值排序

  11. 实例方法 splice 删除或替换原数组单元

  12. 实例方法 reverse 反转数组

  13. 实例方法 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>

总结:

  1. 实例属性 length 用来获取字符串的度长 (重点)
  2. 实例方法 split('分隔符') 用来将字符串拆分成数组 (重点)
  3. 实例方法 substring(需要截取的第一个字符的索引[,结束的索引号]) 用于字符串截取 (重点)
  4. 实例方法 startsWith(检测字符串[, 检测位置索引号]) 检测是否以某字符开头 (重点)
  5. 实例方法 includes(搜索的字符串[, 检测位置索引号]) 判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false (重点)
  6. 实例方法 toUpperCase 用于将字母转换成大写
  7. 实例方法 toLowerCase 用于将就转换成小写
  8. 实例方法 indexOf 检测是否包含某字符
  9. 实例方法 endsWith 检测是否以某字符结尾
  10. 实例方法 replace 用于替换字符串,支持正则匹配
  11. 实例方法 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 => 构造函数

# 原型继承

  1. 封装 - 抽取公共部分
const People = {
  head: 1,
  eye: 2,
}
function Man() {
}
function Woman() {
  this.baby = function() {}
}
  1. 继承 - 让男人和女人都能继承人类的一些属性和方法
  • 把男人女人公共的属性和方法抽取出来
  • 赋值给 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`属性,
指向创建该实例对象的构造函数
层层递推

# 查找规则

  1. 访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性
  2. 如果没有就查找它的原型(也就是 __proto__ 指向的 prototype 原型对象)
  3. 如果还没有就查找原型对象的原型( Object 的原型对象)
  4. 以此类推一直找到 Object 位置 ( null )
  5. __proto__ 对象原型的意义就在于为对象成员查找机制提供一个方向,或者说是一条路径
  6. 可以用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

# 深浅拷贝

# 浅拷贝:

浅拷贝和深拷贝只针对引用类型
浅拷贝:拷贝的是地址
常见方法:

  1. 拷贝对象: Object.assign() / 展开运算符 {...obj} 拷贝对象
  2. 拷贝数组: Array.prototype.concat() / [...arr]

若是简单数据类型拷贝值,引用数据类型拷贝的是地址(简单理解,如果是单层对象,没问题,如果有多层就有问题)

# 深拷贝

首先浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象,不是地址
常见方法:

  1. 通过递归实现
  2. lodash/clneDeep
  3. 通过 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

  1. throw 抛出异常信息,程序也会终止执行
  2. throw 后面跟的是错误提示信息
  3. 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()
  1. try...catch 用于捕获错误信息
  2. 将预估可能错误的代码写在 try 代码段中
  3. 如果 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);

总结:

  1. call 方法调用函数,同时指定函数中 this 的值
  2. 使用 call 方法调用函数时,第 1 个参数为 this 指定的值
  3. call 方法调用函数时,剩余参数为函数的参数

# apply

使用 apply 方法调用函数,同时指定函数中 this 的值

// 调用函数并指定 this 的值
  sayHi.apply(user) //this 值为 user
  sayHi.apply(student) //this 值为 student

总结:

  1. apply 方法能够在调用函数的同时指定 this 的值
  2. 使用 apply 方法调用函数时,第一个参数为 this 指定的值
  3. 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
    }
更新于 阅读次数