浏览器相关
一、认识浏览器运行态下的js
包含:BOM、DOM、ECMAScript
js
(function (content, undefined) {
const _class = ["js", "browser", "vue"]
// 1.想全局作用域中存储一个class变量
window.classArr = _class.map(item => item)
// 2.获取当前页面地址
const _url = location.href;
// 3.改变页面标题
document.title = "pipi class"
// 4.获取渲染节点
document.getElementById("app");
})(this)
// 简述:
// ECMAScript - 处理了基础逻辑、数据处理
// BOM - 对浏览器本身功能区域的汇总处理
// DOM - 对浏览器内部各个元素功能区域的汇总处理
二、BOM
1.location
js
location.href => "https://www.pipi.com/search?class=browser#comments" // 当前页面的完整URL
.origin => "https://www.pipi.com" // URL的起源,即协议、主机和端口
.protocol => "https:" // 协议部分
.host => "www.pipi.com" // 主机名
.port => "" // 端口号
.pathname => "/search" // 路经
.search => "?class=browser" // 参数
.hash => "#comments" // SPA(单页应用) / iframe
location.assign("${url}") // 跳转到指定path,替换pathname
.replace("${url}") // 效果同assign,同时替换掉浏览器历史
.reload()
.toString() // 阐述当前地址=>字符串
- 面试场景: 手写拆解路径题目
- 面试方向:
- location 本身的api操作
- 路由相关:跳转、参数、操作 => 场景题:可返回、是否可刷新 => 框架
- url处理 - 手写处理 & 正则
2.history
history.state => 存储当前页面状态
history.pushState() ------ 跳转到指定的状态页
.replaceState() ------ 替换当前状态
- 面试方向 - 路由方向hsitory和hash模式的利弊分析:
- history需要服务器配置,否则直接访问路由可能会404,hash不需要
- history更干净没有
#
- hash兼容性更好
3.navigator
浏览器信息大集合
jsnavigator.userAgent // 获取当前用户环境信息
面试方向
- UA读取信息 => 浏览器兼容性
- 剪切板 & 键盘操作 => 登录 or 验证码
- 环境 => 微信 => unionId appid
4.screen
表示显示区域 - 屏幕
- 面试方向 - 判断区域大小、方位 window 视窗判断:
- 全局入口处获取
window.innerHeight
window.innerWidth
- 文本处获取
document.documentElement.clientHeight
document.body.clientHeight
- 网页的
size => offsetHeight = clientHeight + 滚动条 + 边框
document.documentElement.offsetHeight
document.body.offsetHeight
- 全局入口处获取
定位:
scrollLeft / scrollTop - 距离常规左上滚动的距离
offsetLeft / offsetTop - 距离常规左上相对距离
el.getBoundingClientRect()
.top .left .bottom .right 直接拿到元素上下左右
- 但是存在兼容性问题 - IE 会多2px
三、Event事件模型
js
<div id="app">
<p id="dom">Click</p>
</div>
// 冒泡 - ms: p -> div -> body -> HTML -> document
// 捕获 - ns: document -> HTML -> body -> div -> p
// button.addEventListener('click', handleClick, {
// capture: true, // 在捕获阶段调用事件监听器,不填默认是false,也就是冒泡
// once: true, // 只调用一次事件监听器
// passive: true // 事件监听器不会调用 preventDefault()
// });
// 追问:
// 1.如何阻止事件的传播
event.stopPropagation();
// 注意:无论向上还是向下都是可以阻止的 => 无法阻止默认事件的发生
// 2.如何阻止默认事件的传播
event.preventDefault();
// 3.相同节点绑定多个同类事件,如何阻止
event.stopImmediatePropagation();
// 追问 => 样式上 & 性能上
// 4.手写事件绑定兼容
// attachEvent & addEventListener
// 区别
// (1) 传参 attachEvent对于事件名加上"on"
// (2) 执行顺序,attachEvent - 后绑定先执行;addEventListener - 先绑定先执行
// (3) 解绑 detachEvent vs removeEventListener
// (4) 阻断 e.cancelBubble = true vs e.StopPropagation()
// (5) 默认事件打断 e.returnValue = true vs e.preventDefault()
class BindEvent {
constructor(element) {
this.element = element;
}
addEventListener = (type, handler) => {
if (this.element.addEventListener) {
this.element.addEventListener(type, handler, false)
} else if (this.element.attachEvent) {
const element = this.element;
this.element.attachEvent("on" + type, () => {
handler.call(element);
})
} else {
this.element["on" + type] = handler;
}
}
removeEventListener = (type, handle) => {
if (this.element.removeEventListener) {
this.element.removeEventListener(type, handler, false)
} else if (this.element.detachEvent) {
const element = this.element;
this.element.detachEvent("on" + type, handler)
} else {
this.element["on" + type] = null;
}
}
static stopPropagation(e) {
if (e.stopPropagation) {
e.stopPropagation();
} else {
e.cancelBubble = true;
}
}
static preventDefault(e) {
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = true;
}
}
}
// 5. 性能优化 - 事件代理
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
// 也就是若干年前,直接给父dom绑定事件,利用冒泡
(() => {
function onClick(e) {
var e = e || window.event;
if (e.target.nodeName.toLowerCase() === "li") {
const liList = this.querySelectorAll("li");
const index = Array.prototype.indexOf.call(liList, e.target);
}
}
const list = document.querySelector(".list");
list.addEventListener("click", onClick, false);
})()
四、网络层
js
// 实例化
const xhr = new XMLHttpRequest();
// 初始化链接
// xhr 有一个open方法
// oepn - 5个参数:
// 1. method - get/post ;
// 2. url - 请求的地址;
// 3. async - 是否异步请求
xhr.open(method,url,async);
// send发送请求
// 内容:post - 将请求体的参数传入:get - 可以不传 or 传入null
xhr.send(data);
// 接受
// xhr.readyStatus:(返回参数:)
// 0 - 尚未调用open
// 1 - 已调用open
// 2 - 已发送请求(已调用send)
// 3 - 已接收到请求返回的数据
// 4 - 请求已完成
xhr.onreadystatuschange = () => {
if (xhr.readyStatus === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
console.log("请求成功", xhr.responseText)
}
}
}
xhr.timeout = 1000;
xhr.ontimeout = ()=>{}
js
// 手写ajax
function ajax(options) {
let {
url,
method = 'get',
async = true,
timeout = 3000,
data = {},
} = options;
const xhr = new XMLHttpRequest();
if (timeout && timeout > 0) xhr.timeout = timeout;
return new Promise((resolve, reject) => {
// 1.阶段处理
// 成功
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
resolve && resolve(xhr.responseText);
} else {
reject && reject();
}
}
}
// 失败
xhr.ontimeout = () => reject && reject("超时");
xhr.onerror = (err) => reject && reject(err);
// 2.传参处理
let _params = [];
let encodeData;
if (data instanceof Object) {
for (let key in data) {
// 参数拼接
_params.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]))
}
encodeData = _params.join("&");
}
// get类型处理
if (method === "get") {
const index = url.indexOf("?");
if (index === -1) url += "?";
else if (index != url.length - 1) url += "&";
url += encodeData;
}
// 3.初始化链接
xhr.open(method, url, async);
// 4.发送连接
if (method === "get") {
xhr.send(null);
} else {
xhr.setRequestHeader(
"Content-type", "application/x-www-form-urlencoded;charset=UTF-8"
);
xhr.send(encodeData);
}
})
}
// 封装
ajax({
url: "",
method: "get",
async: true,
timeout: 3000,
data: {},
}).then(res => { }, err => { })