Skip to content

JS Web API

  • JS基础知识,规定语法(ECMA 262标准)
  • JS Web API,网页操作的API(W3C标准)
  • 前者是后者的基础,两者结合才能真正实际应用

DOM

前言

  • vue 和 React 框架应用广泛,封装了 DOM 操作
  • 但 DOM 操作一直都是前端工程师的基础、必备知识
  • 只会 vue 而不懂 DOM 操作的前端程序员,不会长久

题目

  1. DOM是哪种数据结构
    • 树(DOM树)
  2. DOM操作的常用API
    • DOM节点操作
    • DOM结构操作
  3. attr 和 property的区别
    • property:修改对象属性,不会体现到 html 结构中
    • attribute:修改 html 属性,会改变 html 结构
    • 两者都有可能引起 DOM 重新渲染
  4. 一次性插入多个DOM节点,考虑性能

知识点

DOM本质

<!-- 可自定义标签 -->
<?xml version="1.0" encoding="UTF-8" ?>
<note>
    <to>Tove</to>
    <form>Jani</from>
    <heading>Reminder</heading>
    <body>Don't forget me this weekend!</body>
    <other>
        <a></a>
        <b></b>
    </other>
</note>
<!-- 不可自定义标签 -->
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
    </head>
    <body>
        <div>
            <p>this is a p</p>
        </div>
    </body>
</html>
- 本质是棵树

DOM节点操作

  • 获取DOM节点
    const div1 = document.getElementById('div1') // 元素
    const divList = document.getElementsByTagName('div') // 集合
    console.log(divList.length)
    console.log(divList[0])
    
    const containerList = document.getElementsByClassName('container')
    // 集合
    const pList = document.querySelectorAll('p') // 集合
    
  • attribute
    const pList = document.querySelectorAll('p')
    const p = pList[0]
    p.getAttribute('data-name')
    p.setAttribute('data-name', 'imooc')
    p.getAttribute('style')
    p.setAttribute('style', 'font-size:30px')
    
  • property 形式
    const pList = document.querySelectorAll('p')
    const p = pList[0]
    console.log(p.style.width) // 获取样式
    p.style.width = '100px' // 修改样式
    console.log(p.className) // 获取class
    p.className = 'p1' // 修改class
    
    // 获取 nodeName 和 nodeType
    console.log(p.nodeName)
    console.log(p.nodeType) // 1
    

DOM结构操作

  • 新增/插入节点
    const div1 = document.getElementById('div1')
    // 添加新节点
    const p1 = document.createElement('p')
    p1.innerHTML = 'this is p1'
    div1.appendChild(p1) // 添加新创建的元素
    // 移动已有节点。注意是移动!!!
    const p2 = document.getElementById('p2')
    div1.appendChild(p2)
    
  • 获取子元素列表,获取父元素
    // 获取父元素
    console.log( p1.parentNode )
    // 获取子元素列表
    const div1ChildNodes = div1.chindNodes
    console.log( div1.childNodes )
    const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child => {
        if (child.nodeType === 1){
            return true
        }
        return false
    })
    console.log('div1ChildNodesP', div1ChildNodesP)
    
  • 删除子元素
    const div1 = document.getElementById('div1')
    const child = div1.childNodes
    div1.removeChild(child[0])
    

DOM性能

  • DOM操作非常“昂贵”,避免频繁的DOM操作
  • 对DOM查询做缓存
    // 不缓存 DOM 查询结果
    for (let i = 0; i < document.getElementsByTagName('p').length; i++) {
        // 每次循环,都会计算 length,频繁进行 DOM 查询
    }
    
    // 缓存 DOM 查询结果
    const pList = document.getElementsByTagName('p')
    const length = pList.length
    for (let i = 0; i < length; i++) {
        // 缓存 length,只进行一次 DOM 查询
    }
    
  • 将频繁操作改为一次性操作
    const listNode = document.getElementById('list')
    
    // 创建一个文档片段,此时还没有插入到 DOM 树中
    const frag = document.createDocumentFragment()
    
    // 执行插入
    for(let x = 0; x < 10 x++) {
        const li = document.createELement("li")
        li.innerHTML = "List item " + x
        frag.appendChild(li)
    }
    
    // 都完成之后,再插入到 DOM 中
    listNode.appendChild(frag)
    

BOM

题目

  1. 如何识别浏览器的类型
  2. 分析拆解url各个部分

知识点

  • navigator
    const ua = navigator.userAgent
    const isChrome = ua.indexOf('Chorme')
    console.log(isChrome)
    
  • screen
    console.log(screen.width)
    console.log(screen.height)
    
  • location
    location.href // https://coding.imooc.com/class/chapter/115.html?a=100&b=200#Anchor
    location.protocol  // https
    location.host  // 'coding.imooc.com'
    console.log(location.pathname) // "/class/chapter/115.html"
    console.log(location.search) // "?a=100&b=200"
    console.log(location.hash) // "#Anchor"
    
  • history
    history.back() // 后退
    history.forward() // 前进
    

事件

题目

  1. 编写一个通用的事件监听函数
    // 通用的事件绑定函数
    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', fucntion(event) {
        // console.log(e.target) // 获取触发的元素
        event.preventDefault() // 阻止默认行为
        alert(this.innerHTML)
    })
    
    // 代理绑定
    const div3 = document.getElementById('div3')
    bindEvent(div3, 'click', 'a', fucntion(event) {
        event.preventDefault()
        alert(this.innerHTML)
    })
    
  2. 描述事件冒泡的流程
    • 基于DOM树形结构
    • 事件会顺着触发元素往上冒泡
    • 应用场景:代理
  3. 无限下拉的图片列表,如何监听每个图片的点击?
    • 事件代理
    • e.target 获取触发元素
    • metches 来判断是否是触发元素

知识点

事件绑定

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

// 通用的绑定函数
function bindEvent(elem, type, fn) {
    elem.addEventListener(type, fn)
}
const a = document.getElementById('link1')
bindEvent(a, 'click', e => {
    // console.log(e.target) // 获取触发的元素
    e.preventDefault() // 阻止默认行为
    alert('clicked')
})

事件冒泡

<body>
    <div id="div1">
        <p id="p1">激活</p>
        <p id="p2">取消</p>
        <p id="p3">取消</p>
        <p id="p4">取消</p>
    </div>
    <div id="div2">
        <p id="p5">取消</p>
        <p id="p6">取消</p>
    </div>
</body>
const p1 = document.getElementById('p1')
const body = document.body
bindEvent(p1, 'click', e => {
    e.stopPropagation() // 阻止冒泡
    alert('激活')
})
bindEvent(body, 'click', e => {
    alert('取消')
})

事件代理

  • 代码简洁
  • 减少浏览器内存占用
  • 但是,不要滥用
    <div id="div1">
        <a href="#">a1</a><br/>
        <a href="#">a2</a><br/>
        <a href="#">a3</a><br/>
        <a href="#">a4</a><br/>
    </div>
    <button>加载更多...</button>
    
    const div1 = document.getElementById('div1')
    div1.addEventListener('click', e => {
        event.preventDefault()
        const target = e.target
        if (target.nodeName === 'A') {
            alert(target.innerHTML)
        }
    })
    

ajax

题目

  1. 手写一个简易的ajax
    function ajax(url, successFn) {
        const xhr = new XMLHttpRequest()
        xhr.open("GET", url, true)
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if(xhr.status == 200) {
                    successFn(xhr.responseText)
                }
            }
        }
        xhr.send(null)
    }
    
    function ajax(url) {
        const p = new Primise((resolve, reject) => {
            const xhr = XMLHttpRequest()
            xhr.open("GET", url, true)
            xhr.onreadtstatechangee = function () {
                if(xhr.readyState === 4) {
                    if(xhr.status === 200) {
                        resolve(
                            JSON.prase(xhr.responseText)
                        )
                    } else if (xhr.status === 404) {
                        reject(new Error('404 not found'))
                    }
                }
            }
        })
        return p
    }
    const url = '/data/test.json'
    ajax(url).then(
        res => console.log(res)
    ).catch(
        err => console.error(err)
    )
    
  2. 跨域的常用实现方式
    • JsonP
    • CORS

知识点

XMLHttpRequest

// get 请求
const xhr = new XMLHttpRequest()
xhr.open("GET", "/api", true) // true 代表异步请求 false 同步
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            // console.log(
            //     JSON.parse(xhr.responseText)
            // )
            alert(xhr.responseText)
        } else {
            console.log('其他情况')
        }
    }
}
xhr.send(null)

// post 请求
const xhr = new XMLHttpRequest()
xhr.open("POST", "/login", true) // true 代表异步请求 false 同步
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            // console.log(
            //     JSON.parse(xhr.responseText)
            // )
            alert(xhr.responseText)
        } else {
            console.log('其他情况')
        }
    }
}
const postData = {
    userName: 'zhangsan',
    password: 'xxx'
}
xhr.send(JSON.stringify(postData)) // 字符串

状态码

  • xhr.readyState
    • 0 - (未初始化)还没有调用send()方法
    • 1 - (载入)已调用send()方法,正在发送请求
    • 2 - (载入完成)send()方法执行完成,已经接受到全部响应内容
    • 3 - (交互)正在解析响应内容
    • 4 - (完成)响应内容解析完成,可在客户端调用
  • xhr.status
    • 2xx - 表示成功处理请求,如 200
    • 3xx - 需要重定向,浏览器直接跳转,如 301 302 304
    • 4xx - 客户端请求错误,如404(url不对) 403(没权限)
    • 5xx - 服务端错误

跨域

  • 跨域:同源策略,跨域解决方案(仅浏览器机制)
    • 所有的跨域,都必须经过server端允许和配合
    • 未经server端允许就实现跨域,说明浏览器有漏洞,危险信号
  • 什么是跨域(同源策略)
    • ajax请求时,浏览器要求当前网页和server必须同源(安全)
    • 同源:协议、域名、端口,三者必须一致
    • 前端:http://a.com:8080/ ; server: https://b.com/api/xxx (存在跨域)
  • 加载图片css js可无视同源策略
    • img 标签 可用于统计打点,可使用第三方统计服务
    • link 标签 可使用CDN,CDN一般都是外域
    • script 标签 可使用CDN,CDN一般都是外域;可实现JSONP
  • JSONP
    • srcipt可绕过跨域限制
    • 服务端可以任意动态拼接数据返回
    • 所以,script就可以获得跨域的数据,只要服务端愿意返回
  • CORS(服务端设置 http header)

cors

cors

存储

题目

  • 描述cookie localStorage sessionStorage区别
    1. 容量
    2. API易用性
    3. 是否跟随http请求发送出去

知识点

  • cookie
    1. 本身用于浏览器和server
    2. 被“借用”到本地存储来
    3. 可用document.cookie = '...'来修改
  • cookie的缺点
    1. 存储大小,最大4KB
    2. http请求时需要发送到服务端,增加请求数据量
    3. 只能用 document.cookie = '...' 来修改,太过简陋
  • localStorage 和 sessionStorage
    1. HTML5专门为存储而设计,最大可存5M
    2. API简单易用 setItem getItem
    3. 不会随着 http 请求被发送出去
  • localStorage 和 sessionStorage 区别
    1. localStorage 数据会永久存储,除非代码或手动删除
    2. sessionStorage 数据只存在于当前对话,浏览器关闭则清空
    3. 一般用 localStorage 会更多一些