重拾js-浏览器JS对象

145 阅读10分钟

简言

最近在做前端知识的复习和整理,有了一些自己新的体会。更多在于记录,通过反复的温习,写笔记消除自己以前学习知识点的误区

浏览器JS对象

JS在浏览器的运行环境中,一般由以下三部分组成

  1. ECMAScript核心: 作为JS的标准规范,描述了JS中的语法和基本对象
  2. BOM(Browser Object Model): 浏览器对象模型,提供了用以控制浏览器行为的API
  3. DOM(Document Object Model): 浏览器对象模型,提供了用以编辑网页文档的接口

ECMAScript核心

ECMAScript是一种由Ecma国际通过ECMA-262标准化的脚本语言规范。

ECMAScript 与 JavaScript有什么区别?

名称定义规则拓展性
ECMAScriptECMAScript是由ECMA国际标准化的脚本语言规范ECMA提供了脚本语言必须遵守的规范,准则和细节ECMAScript作为一种开放的,被国际广为接受的脚本语言规范,具有很大的灵活性
JavaScriptECMAScript规范下的具体实现JavaScript不仅具备ECMAScript的核心功能和特性,还具备与特定环境(浏览器)的交互能力虽然JavaScript也是一种流行的脚本语言,但他基于ECMAScript的规范

BOM

常见的BOM对象

Navigator-浏览器系统导航信息集合

  • appCodeName:返回浏览器代码名
  • appName:返回浏览器名称--Netscape
  • appVersion:返回浏览器的平台和版本信息
  • cookieEnabled:返回指明浏览器是否启用cookie的布尔值
  • platform:返回运行浏览器的操作系统平台
  • onLine:判断浏览器是否在线
  • userAgent:返回由客户端发送服务端的user-agent头部信息(获取用户身份信息,浏览器运行环境)
判断浏览器并返回浏览器名称

以下两段代码引用于juejin.cn/post/706808…

const getCurrentBrower = () => {
  const ua = navigator.userAgent;
  let browser;

  if (ua.indexOf("Firefox") > -1) {
    browser = "Mozilla Firefox";
  } else if (ua.indexOf("Opera") > -1 || ua.indexOf("OPR") > -1) {
    browser = "Opera";
  } else if (ua.indexOf("Trident") > -1) {
    browser = "Microsoft Internet Explorer";
  } else if (ua.indexOf("Edge") > -1) {
    browser = "Microsoft Edge";
  } else if (ua.indexOf("Chrome") > -1) {
    browser = "Google Chrome or Chromium";
  } else if (ua.indexOf("Safari") > -1) {
    browser = "Apple Safari";
  } else {
    browser = "unknown";
  }

  return browser;
};
判断当前是否为移动端
const mobileFlags = [
  /AppleWebKit.*Mobile.*/, // 移动终端
  /\(i[^;]+;( U;)? CPU.+Mac OS X/, // ios终端
  /Android/, // 安卓终端
  /iPhone/, // iPhone
  /iPad/, // iPad
];

const isMobile = () => {
  const ua = navigator.userAgent;

  for (let flag of mobileFlags) {
    if (flag.test(ua)) {
      return true;
    }
  }

  return false;
};

由此我们就可以在实际开发中得知:

什么设备下,做什么设备的样式布局;什么环境下,针对当前环境进行浏览器兼容处理,统一浏览器默认行为等

经典剪切板问题

大家都知道剪切板功能可以通过以下步骤实现:

  1. Dom获取Input对象
  2. obj.select()
  3. document.execCommand('Copy', false, null)

但是某些极端情况下(iPhone5 10.2),ios无法通过上述步骤实现剪切功能(第二步未生效),那么我们就需要针对这种特殊情况做处理

// 以下是伪代码
if(isIOSDevice){
    selectRange()
    
    try{
        document.execCommand('Copy', false, null)
    }
    
    // ....
}

function selectRange() {
    let obj = document.getElementById("textAreas");
    // 获取元素内容是否可编辑或只读
    let editable = obj.contentEditable; 
    let readOnly = obj.readOnly;
    
    // 改变元素内容可编辑性和非只读
    obj.contentEditable = true
    obj.readOnly = false;
    
    // 创建Range对象,Range对象表示文档的连续范围区域
    let range = document.createRange()
    // 获取obj作为用户选中范围
    range.selectNodeContents(obj)
    
    // 获取用户选中文本范围或光标当前位置
    let selection = window.getSelection()
    // 清除之前已选范围
    selection.removeAllRanges()
    // 添加当前已选范围
    selection.addRange(range)
    
    // 为文本元素设置文本中被选中的范围(起始位置,结束位置),这个方法用于input,textarea
    obj.setSelctionRange(0, 999999)
}

经典键盘输入问题

微信小程序-iOS键盘弹出遮挡输入框

可以使用input的cursor-spacing属性

指定光标与键盘的距离,取 input 距离底部的距离和 cursor-spacing 指定的距离的最小值作为光标与键盘的距离

location - 浏览器地址,提供获取浏览器地址信息的能力

  • href: 返回完整url
  • origin:返回protocol + host部分
  • protocol:返回url中的协议名称(xxx:),例如 https: http:
  • host: 返回url的域部分,通常为域名或是ip+端口的形式
  • hostname:返回url的域名或端口
  • port: 返回url的端口,如果使用的是域名,则返回' '
  • hash: 返回url的hash值,一般为 # + hash值,多用于跳转a链接指定的锚点或者是hash路由
  • pathname:返回url的pathname部分,一般为host后从第一个匹配到的/开始 如: /search/productName
  • search:返回url的search query部分,一般格式为 ?xxx=xxx&yyy=yyy&zzz=zzz
location常用方法
  • location.assign(url):替换url,再跳转到指定的path,会添加一条新的历史记录 image.png

  • location.reload():重新刷新页面,相当于左上角刷新按钮,不会增加新的历史记录

  • location.replace(url):替换当前url,用新的历史记录替换旧的历史记录, 不会增加新的历史记录

    上图应该是,jd的历史记录覆盖百度的历史记录,此时百度已无历史记录,从下面与location.href的对比中可以看出

  • location.href: 跳转,会添加新的页面历史记录

location.replace和location.href的区别

location.href和location.replace本质上都是浏览器中的页面重定向 当进行重定向的时候,浏览器会发起一个HTTP请求,通常会收到一个301或者302的状态码,这取决于服务器返回的类型,如果是返回301,表示之前资源的路径被销毁或永久移动,服务器返回了新的资源地址回来,在未来请求中会直接访问新的资源地址

而location.href在重定向完成之后会添加新的历史记录

而location.replace则是现将当前的历史记录移出,在将新的历史记录移入,完成历史记录替换


如果当前页面是C,页面是由A -> B -> C跳转的

B -> C用的replace,则从C返回上一个页面,则回到A

B -> C用的href,则从C返回上一个页面,则回到B

screen - 表示显示区域

  • avaiHeight:返回系统整块屏幕的可用高度
  • avaiWidth:返回系统整块屏幕的可用宽度
  • height:返回屏幕的总高度,可用高度+不可用高度(非网页区域)
  • width:返回屏幕的总宽度,可用宽度+不可用宽度(非网页区域)
  • pixelDepth:返回屏幕的颜色分辨率(每像素的位数)8 * 8 * 8

History - 网页的历史记录

  • state: 用来存储当前页面的存储状态
  • back():返回上一页
  • forward():返回下一页
  • go():加载history列表中的某个具体页面
  • pushState():在不重新加载页面的情况下修改浏览器的历史记录,一般用于SPA的导航,将新的状态信息添加到浏览器的历史记录堆栈中,用户可以通过浏览器的“后退”按钮回到之前的状态

window.history.pushState(stateObject, title, url)

与新历史记录关联的状态对象,title名称可以设置为null,新URL且必须与当前域名同域

  • replaceState():跟上面一样,但不会添加新的状态信息到历史记录堆栈中而是对当前历史记录堆栈中的状态信息进行替换,无法通过“后台”返回到之前的状态

window.history.replaceState(stateObject, title, url)

路由方向 history 和 hash模式的对比,不存在利弊,只存在区别
  1. 展示形态不同,hash会在路径上总是展示一个#,用以描述当前的hash值,而history更贴近普通路由(给用户有一致性的感觉,因此感受更加)
  2. 原理上的不同,hash改变的是锚点(使用hashChange监听),而history是改变的页面栈中栈顶的状态(使用onPopState监听)
  3. history使用时,需要后台指向一个标准的文件(匹配到了文件就读取,没有匹配到文件就回退)或者是api
// 在ngixn中
location / {
    root  /usr/share/nginx/html;  # 静态文件目录
    try_files $uri /index.html;    
}

上述try_files会按照给定的文件顺序去检查文件是否存在,如果文件存在则返回文件,如果文件不存在则返回一个默认文件,上述代码$uri表示当前匹配到的pathname,/index.html表示如果pathname没有匹配到文件,则返回root路径下 /usr/share/nginx/html目录下的index.html文件,然后通过前端JS控制路由切换

如果页面白屏了,应该从哪些部分排查
  1. 打开浏览器控制台,查看日志

    检查是否因为js代码语法错误导致,或是方法,变量类型不正确(一般是访问了undefined或null的属性),引用了未定义的变量或函数

  2. 网络请求

    检查是否是因为请求pending或是响应404,5xx之类的错误,导致静态资源并未完全加载或加载错误

  3. JS加载和执行

    如果使用了异步加载,确认异步加载的资源都能够被正确的加载,并且需要确认JS加载的顺序是否正确,可能某些脚本并未加载完就被执行导致白屏

  4. CSS问题

    有时候白屏可能是因为设置了display: none,或者是被错误地定位到了页面外部,或者是CSS文件并未加载或者加载错误,导致白屏

  5. 构建工具

    确保使用webpack等构建工具在构建输出时没有错误,或者因为意外导致文件压缩错误等

  6. 缓存问题

    确保成功加载的文件都是最新的,尤其是SPA引用中,可能存在index文件更新了,但是引用的css文件以及其他脚本文件没有更新,导致白屏的错误

  7. 兼容性问题

    检查是否在不同的浏览器中都出现了白屏问题,如果只是某个特定的浏览器出现白屏问题,则可能因为浏览器不支持某些Web新特性,或者是存在不兼容的特性

  8. 前端框架问题

    确保路由配置是否都正确,是否存在重定向错误或者是路由路径的解析问题

  9. 后端问题

    确认后端返回的是否有错误代码,或是接口调用超时,或是返回的数据不符合预期,导致前端渲染失败

  10. Web服务器配置问题 如果使用了Nginx,Apache等代理服务器,检查配置文件中是否存在问题,尤其是静态文件的路径等

总结一下就是: 从三个方向查看前端与浏览器、构建与网络、后端与Web服务器

image.png

DOM事件模型

浏览器一般有三种绑定事件的方式

// 1.在HTML标签上定义
<div onclick="handler"></div>


// 2.js获取dom,将事件绑定在dom上
<div id="box1"></div>
<script>
  const box1 = document.getElementById("#box1")
  box1.onclick = () => {
    console.log('box1')
  }
</script>

// 3.js获取dom,并给dom添加事件监听
<div id="box2"></div>
<script>
  const box2 = document.getElementById("#box2")
  box1.addEventListener('click', ()=>{}, false)
</script>

其他用法

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    #divA {
      width: 200px;
      height: 200px;
      border: 1px solid red;
    }
    #divB {
      width: 100px;
      height: 100px;
      border: 1px solid green;
    }
    #divC {
      width: 50px;
      height: 50px;
      border: 1px solid blue;
    }
  </style>
  <body>
    <div id="divA">
      <div id="divB">
        <div id="divC">
          <a href="https://www.baidu.com" id="aA">点我</a>
        </div>
      </div>
    </div>
  </body>
  <script>
    const divA = document.getElementById("divA");
    const divB = document.getElementById("divB");
    const divC = document.getElementById("divC");
    const aA = document.getElementById("aA");

    divA.addEventListener(
      "click",
      () => {
        console.log("divA");
      },
      false
    );
    divB.addEventListener(
      "click",
      () => {
        console.log("divB");
      },
      false
    );
    divC.addEventListener(
      "click",
      () => {
        console.log("divC");
      },
      false
    );
    aA.addEventListener(
      "click",
      (e) => {
        // e.stopPropagation();
        e.stopImmediatePropagation();
        e.preventDefault();
        console.log("aA-1");
      },
      false
    );
    aA.addEventListener(
      "click",
      () => {
        console.log("aA-2");
      },
      false
    );
  </script>
</html>

image.png

  1. 阻止冒泡事件

    e.stopPropagation()

  2. 阻止浏览器默认事件

    e.precentDefault() a标签的默认事件是跳转,阻止a标签的默认点击事件后,就不会跳转了

  3. 如果相同节点上绑定了多个同类型事件Click2,Click3等,如果既要阻止事件冒泡,又要阻止除自身(Click1)外其他同类型事件(Click2, Click3)触发

    e.stopImmediatePropagation() 此时只触发Click1;Click2与Click3甚至上层父级节点的事件不触发

image.png