前端面试题7--JS-Web-API

198 阅读3分钟

从JS基础知识到JS Web API

  • JS基础知识,规定语法(ECMA262标准)
  • JS Web API,网页操作的API(W3C标准)
  • 前者是后者的基础,两者结合才能真正实际应用 JS Web API:
  • DOM
  • BOM
  • 事件绑定
  • ajax
  • 存储

DOM

DOM操作(Document Object Model)
知识点:

  • DOM 本质
  • DOM 节点操作
  • DOM 结构操作
  • DOM 性能 (DOM 是哪种数据结构:树(DOM树),DOM 本质是从HTML文件解析出的一棵树)

    DOM 节点操作

    获取DOM节点:
const div1 = document.getElementById('div1')    // 元素
const divList = document.getElementsByTagName('div')  // 标签集合
const containerList = document.geiElementsByClassName('.container') // 集合
const pList = document.querySelectorAll('p')  // 集合, css选择器 

DOM 节点的property:
(property不是API名,是一种形式,指获取DOM元素,通过修改或获取JS属性方式进行操作)

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
console.log(p.nodeName)      // 获取 nodeName 标签节点名称
console.log(p.nodeType)      // 获取 nodeType 类型

DOM 节点的 attribute:
(可以通过 getAttribute、setAttribute 两个 API 直接修改HTML结构、修改标签,能真正作用到 DOM 结构中)
property 对 DOM 元素的JS变量属性进行修改,不会对标签产生影响;attribute 修改的是标签属性,对 DOM 节点属性进行修改。

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 和 attribute:

  • property:修改对象属性,不会体现在 html 结构中
  • attribute:修改 html 属性,会改变 html结构
  • 两者都可能引起 DOM 重新渲染(尽量使用 property) DOM 结构操作
  • 新增/插入节点
  • 获取子元素列表,获取父元素
  • 删除子元素 新增/插入节点:
const div1 = document.getElementById('div1')
// 添加新节点
const newP = document.createElement('p') // 新建节点
newP.innerHTML = 'this is newP'
div1.appendChild(newP)   // 添加新创建的元素,插入节点
// 移动已有节点。注意是移动!!!
const p1 = document.getElementById('p1')
div2.appendChild(p1) // 对现有节点直接进行 appendChild,会移动节点

获取子元素列表&获取父元素

// 获取子元素列表
const div1 = document.getElementById('div1')
const childDiv1 = div1.childNodes

// div1.childNodes[0].nodeName  文本元素
// div1.childNodes[0].nodeType 值是3 文本元素,值是1 dom节点

const childArr = Array.prototype.slice.call(childDiv1).filter(child => {
    if (child.nodeType === 1) return true
    else false
})

// 获取父元素
const div1 = document.getElementById('div1')
const parent = div1.parentNode

删除节点

const div1 = document.getElementById('div1')
const childDiv1 = 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 i = 10; i < 10; i++) {
    const li = document.createElement('li')
    li.innerHTML = `List item ${i}`
    frag.appendChild(li) // 先插入文档片段中
}
// 都完成之后,再插入到 DOM 树中
listNode.appendChild(frag)

BOM 操作(Browser Object Model)浏览器对象模型

  • 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) // 协议
console.log(location.pathname) // 路径
console.log(location.host)     // 域名 host
console.log(location.search)   // 参数
console.log(location.hash)     // #后内容

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

事件

事件绑定

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', event => {
    console.log(event.target)   // 获取触发的元素
    event.preventDefault()      // 阻止默认行为
    alert('clicked')
})

事件冒泡

  • 基于DOM树形结构
  • 事件会顺着触发元素向上冒泡
  • 应用场景:代理
<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>
<script>
const p1 = document.getElementById('p1')
const body = document.body
bindEvent(p1, 'click', e => {
    e.stopPropagation()      // 阻止冒泡,注释掉这一行,来体会事件冒泡
    console.log('p1 clicked')
})
bindEvent(body, 'click', e => {
    e.stopPropagation()
    console.log('body clicked')
    console.log(e.target)   // 获取触发的元素
})
</script>

事件代理

  • 代码简洁
  • 减少浏览器内存占用
  • 但是不要滥用
<div id="div1">
    <a href="#">a1<a>
    <a href="#">a2<a>
    <a href="#">a3<a>
    <a href="#">a4<a>
    <button>加载更多...</button>
</div>

const div1 = document.getElementById('div1')
bindEvent(div1, 'click', 'a', event => {
    event.prevnetDefault()
    const target = event.target
    if(target.nodeName === 'a') {
        console.log(target.innerHTML)
    }
})

通用的事件监听函数

const bindEvent(elem, type, selector, fn) {
    if (fn == null) {
        fn = selector
        selector = null
    }
    elem.addEventListener(type, e => {
        let target
        if (selector) {
            // 需要代理
            target = e.target
            if(target.matches(selector)) {
                fn.call(target, e)
            }
        } else {
            // 不需要代理,普通绑定
            fn.call(target, e)
        }
    })
}
// 普通绑定
bindEvent(div1, 'click', event => {
    event.prevnetDefault()
    console.log(event.target.innerHTML)
})
bindEvent(div1, 'click', function (event) {
    event.prevnetDefault()
    console.log(this.innerHTML)
})
// 代理绑定
bindEvent(div1, 'click', 'a', function (event) {
    event.prevnetDefault()
    console.log(this.innerHTML)
})

无限下拉图片列表,如何监听每个图片的点击

  • 事件代理
  • 用e.target获取触发元素
  • 用matches来判断是否是触发元素

ajax

XMLHttpRequest

// get 请求
const xhr = new XMLHttpRequest()
xhr.open("GET", "api/", true) //true 异步, false 同步
xhr.onreadystatechange = function () {
    // 这里的函数异步执行,可参考之前JS基础中的异步模块
    if (xhr.readyState === 4) {
        if (xhr.state === 200) {
            console.log(xhr.responseText)
            console.log(JSON.parse(xhr.responseText))
        }
    }
}
xhr.send(null)
// post请求
const xhr = new XMLHttpRequest()
xhr.open("POST", "api/", true)
xhr.onreadystatechange = function () {
    // 这里的函数异步执行,可参考之前JS基础中的异步模块
    if (xhr.readyState === 4) {
        if (xhr.state === 200) {
            console.log(xhr.responseText)
            console.log(JSON.parse(xhr.responseText))
        }
    }
}
const postData = {
    uaerName: 'xxx',
    password: 'xxx'
}
xhr.send(JSON.stringify(postData)) // JSON.stringify字符串

xhr.readyState

  • 0 — (未初始化)还没有调用send()方法
  • 1 — (载入)已调用send()方法,正在发送请求
  • 2 — (载入完成)send()方法执行完成,已经接收到全部响应内容
  • 3 — (交互)正在解析响应内容
  • 4 — (完成)响应内容解析完成,可以在客户端调用 xhr.state
  • 2xx — 表示成功处理请求,如200
  • 3xx — 需要重定向,浏览器直接跳转,如301 302 304(301 永久重定向,302 临时重定向,304 资源位改变)
  • 4xx — 客户端请求错误,如404 403 (403客户端无权限)
  • 5xx — 服务端错误 跨域

    什么是跨域(同源策略):
  • ajax 请求时,浏览器要求当前网页和server必须同源
  • 同源:协议、域名、端口,三者必须一致
  • 加载图片、css、js可无视同源策略:
<img src="跨域的图片地址"/>
<link href="跨域的css地址"/>
<script src="跨域的js地址"></script>
// <img/>可用于统计打点,可使用第三方统计服务
// <link/><script>可使用CDN,CDN一般都是外域
// <script>可实现JSONP
  • 所有的跨域,都必须经过server端允许和配合
  • 未经server端允许就实现跨域,说明浏览器有漏洞,危险信号 跨域的实现方式:JSONP、CORS

    JSONP
  • script 可绕过跨域
  • 服务器可以任意动态拼接数据返回,只要符合html格式要求
  • 所以,script就可以获得跨域的数据,只要服务端愿意返回
<script>
window.callbank = function (data) {
    // 这是我们跨域得到的信息
    console.log(data)
}
</script>
<script src="https://xx.com/xx.js"></script>
// 将返回callbank({数据})
<script>
// callbank可以改名
window.abc = function (data) {
    console.log(data)
}
</script>
<script src="https://xx.com/xx.js?callbank=abc"></script>

jQuery实现jsonp

$.ajax({
    url: 'xxxx',
    dataType: 'jsonp',
    jsonpCallback: 'callbank',
    success: function (data) {
        console.log(data)
    }
})

CORS — 服务器设置http header

// 第二个参数填写允许跨域的域名称,不建议直接写"*"
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080")
response.setHeader("Access-Control-Allow-Headers", "X-Requested-Width")
response.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS")
// 接收跨域的cookie
response.setHeader("Access-Control-Allow-Credentials", "true")

手写简易 ajax

function ajax(url) {
    const p = 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(xhe.responseText))
                } else if (xhr.state === 404) {
                    reject(new Error('404 not found'))
                }
            }
        }
        xhr.send(null)
    })
    return p
}
const url = 'xxxx'
ajax(url)
.then(res => console.log(res))
.catch(err => console.error(err))

jQuery 完整 ajax 示例

$.ajax({
    // 请求方式
    type: "POST",
    // 请求的媒体类型
    contentType: "application/json;charset=UTF-8",
    // 请求地址
    url: 'xxxx',
    // 数据,json字符串
    data: JSON.stringify(list),
    // 请求成功
    success: function (result) {
        console.log(result)
    },
    //请求失败,包含具体的错误信息
    error: function (err) {
        console.log(err)
    }
})

fetch

fetch(url)
.then(function (response) {
    return response.json()
})
.then(function (myJson)) {
    console.log(myJson)
}

axios

axios.get(url, params: {username: xxx})
.then(function (response) {
    console.log(response)
})
.catch(function (error) {
    console.log(error)
})
.then(function () {
    // always executed
})

axios.post(url, {username: xxx})
.then(function (response) {
    console.log(response)
})
.catch(function (error) {
    console.log(error)
})

存储

cookie缺点:

  • 存储大小,最大4kb
  • http请求时需要发送到服务端,增加请求数据量
  • 只能用document.cookie=""来修改,只能追加不能覆盖 localStorage 和 sessionStorage:
  • HTML5专门为存储而设计,最大可存5M
  • API简单易用 setItem getItem
  • 不会随着 http 请求被发送出去
  • localStorage 数据会永久存储,除非代码或手动删除
  • sessionStorage 数据只存在于当前会话,浏览器关闭则清空