[TOC]

一、DOM

DOM,即文档对象模型(Document Object Model),它是从 HTML 解析出来的一个树结构

1、DOM 节点操作

1.1 获取 DOM 节点

const div1 = document.getElementById('div1') // 元素
console.log('div1', div1)

const divList = document.getElementsByTagName('div') // 集合
console.log('divList.length', divList.length)
console.log('divList[1]', divList[1])

const containerList = document.getElementsByClassName('container') // 集合
console.log('containerList.length', containerList.length)
console.log('containerList[1]', containerList[1])

const pList = document.querySelectorAll('p') // 集合
console.log('pList', pList)

1.2 DOM 节点的 property

property 是 DOM 中的属性,是一个 js 对象。我们可以用对象形式修改/获取 DOM 节点的属性。它修改对象属性,不会体现到 html 结构中

const pList = document.querySelectorAll('p')
const p1 = pList[0]

// property 形式
p1.style.width = '100px'
console.log(p1.style.width)
p1.className = 'red'
console.log(p1.className)
console.log(p1.nodeName)
console.log(p1.nodeType) // 1

1.3 DOM 节点的 attribute

attribute 是 html 标签的某个特性,如:id、value等,值只能是字符串,可通过 getAttributesetAttribute 对其进行获取或修改,它修改 html 属性,会改变 html 结构

// attribute
p1.setAttribute('data-name', 'hello')
console.log(p1.getAttribute('data-name'))
p1.setAttribute('style', 'font-size: 50px;')
console.log(p1.getAttribute('style'))

2、DOM 结构操作

<div id="div1" class="container">
    <p id="p1">一段文字 1</p>
    <p>一段文字 2</p>
    <p>一段文字 3</p>
</div>
<div id="div2">
    <img src="url"/>
</div>
<ul id="list"></ul>

2.1 新增/插入/移动节点

const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')

// 新建节点
const newP = document.createElement('p')
newP.innerHTML = 'this is newP'
// 插入节点
div1.appendChild(newP)

// 移动节点
const p1 = document.getElementById('p1') // 对现有节点执行 appendChild 会移动节点
div2.appendChild(p1)

2.2 获取/删除子元素、获取父元素

// 获取父元素
console.log(p1.parentNode)

// 获取子元素列表
const div1ChildNodes = div1.childNodes
console.log(div1.childNodes)
const div1ChildNodesP = [...div1.childNodes].filter(child => {
    if (child.nodeType === 1) {
        return true
    }
    return false
})
console.log('div1ChildNodesP', div1ChildNodesP)

// 删除子元素
div1.removeChild(div1ChildNodesP[0])

3、DOM 性能优化

3.1 DOM 查询做缓存

// 不缓存 DOM 查询结果
for (let i = 0; i < document.getElementsByTagName('p').length; i += 1) {
    // 每次循环,都会计算 length,频繁进行 DOM 查询
}

// 缓存 DOM 查询结果
const len = document.getElementsByTagName('p').length
for (let i = 0; i < len; i += 1) {
    // 缓存 length, 只进行一次 DOM 查询
}

3.2 频繁操作改为一次性操作

使用 createDocumentFragment 创建文档片段进行优化

const list = document.getElementById('list')

// 创建一个文档片段,此时还没有插入到 DOM 结构中
const frag = document.createDocumentFragment()

for (let i  = 0; i < 20; i++) {
    const li = document.createElement('li')
    li.innerHTML = `List item ${i}`

    // 先插入文档片段中
    frag.appendChild(li)
}

// 都完成之后,再统一插入到 DOM 结构中
list.appendChild(frag)

console.log(list)

二、BOM

BOM,即浏览器对象模型(Browser Object Model),常见的 window 对象中的 navigator、screen、location、history等

// navigator
const ua = navigator.userAgent // 当前浏览器信息
const isChrome = ua.indexOf('Chrome')
console.log(isChrome)

// screen
console.log(screen.width)
console.log(screen.height)

// location
console.log(location.href) // 整个网址
console.log(location.protocol) // 协议 http: / https:
console.log(location.pathname) // 路径
console.log(location.host) // 域名
console.log(location.search) // 查询参数
console.log(location.hash) // 哈希(#号后面)

// history
history.back() // 后退
history.forward() // 前进

三、事件

1、事件绑定

1.1 基本绑定语法

const btn = document.getElementById("btn")
btn.addEventListener('click', event => {
  console.log('clicked')
})

1.2 通用绑定函数

基础版:

function bindEvent(elem, type, fn) {
    elem.addEventListener(type, fn)
}

const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click', function (event) {
    event.preventDefault() // 阻止默认行为
    alert('clicked')
})

进阶版(支持事件代理逻辑):

function bindEvent(elem, type, selector, fn) {
    if (fn == null) {
        fn = selector
        selector = null
    }
    elem.addEventListener(type, event => {
        const target = event.target
        if (selector) {
            // 代理绑定
            if (target.matches(selector)) {
                fn.call(target, event)
            }
        } else {
            // 普通绑定
            fn.call(target, event)
        }
    })
}

// 普通绑定
const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click', function (event) {
    // console.log(event.target) // 获取触发的元素
    event.preventDefault() // 阻止默认行为
    alert(this.innerHTML)
})

// 代理绑定
const div3 = document.getElementById('div3')
bindEvent(div3, 'click', 'a', function (event) {
    event.preventDefault()
    alert(this.innerHTML)
})

2、事件冒泡

如果没有调用 stopPropagation ,则点击 p1 会逐级 p1 -> div1 -> body 往上冒泡,效果就像依次点击了p1、div1、body,逐个触发点击事件
事件冒泡.png

3、事件代理

事件代理(事件委托)即把子元素的事件绑定到父元素上,原理是基于事件冒泡
事件代理.png

四、Ajax

1、XMLHttpRequest

1.1 基本使用

// get 请求
const xhr = new XMLHttpRequest()
xhr.open('GET', '/test.json', true)
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      alert(xhr.responseText)
    } else {
      alert('其他情况')
    }
  }
}
xhr.send(null)

// post 请求
xhr.open('POST', '/login', true)
xhr.onreadystatechange = function () {
  // ...
}
const postData = {
  userName: 'zhangsan',
  password: '123'
}
xhr.send(JSON.stringify(postData))

1.2 简易 ajax 手写

function ajax(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url, true)
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(
            JSON.parse(xhr.responseText)
          )
        } else if (xhr.status === 404) {
          reject(new Error('404 Not Found'))
        }
      }
    }
    xhr.send(null)
  })
}

ajax('./test.json')
  .then(res => console.log(res))
  .catch(err => console.error(err))

1.3 readyState

  • 0 -(未初始化)还没有调用 send() 方法
  • 1 -(载入)已调用 send() 方法,正在发送请求
  • 2 -(载入完成)send() 方法执行完成,已经接收到全部响应内容
  • 3 -(交互)正在解析响应内容
  • 4 -(完成)响应内容解析完成,可以在客户端调用

1.4 status(状态码)

  • 2xx - 表示成功处理请求,如:200
  • 3xx - 需要重定向,浏览器直接跳转
    • 301 - 永久重定向
    • 302 - 临时重定向
    • 304 - 资源未改变,浏览器会使用缓存
  • 4xx - 客户端请求错误
    • 403 - 客户端无权限
    • 404 - 请求地址错误
  • 5xx - 服务器端错误

2、跨域

跨域产生的原因是由于浏览器的同源策略

  • 同源策略:ajax 请求时,浏览器要求当前网页和 server 必须同源
  • 同源:协议、域名、端口,三者必须都一致

但加载图片、CSS、JS 可无视同源策略

<img src="跨域的图片地址" />
<link href="跨域的css地址" />
<script src="跨域的js地址"></script>

利用特性,可有以下这些使用场景:

  • 浏览器 API - 图3 可用于统计打点,可使用第三方统计服务