# 每日一题-HTML篇

# H5新增的特性

标签:新增语义化标签(aside / figure / section / header / footer / nav等),增加多媒体标签videoaudio,使得样式和结构更加分离

属性:增强表单,主要是增强了input的type属性(number/color/range);meta增加charset以设置字符集;script增加async以异步加载脚本

存储:增加localStoragesessionStorageindexedDB,引入了application cache对web和应用进行缓存

API:增加拖放API地理定位SVG绘图canvas绘图Web WorkerWebSocket

# DIV+CSS布局的好处

  1. 代码精简,且结构与样式分离,易于维护
  2. 代码量减少了,减少了大量的带宽,页面加载的也更快,提升了用户的体验
  3. 对SEO搜索引擎更加友好,且H5又新增了许多语义化标签更是如此
  4. 允许更多炫酷的页面效果,丰富了页面
  5. 符合W3C标准,保证网站不会因为网络应用的升级而被淘汰(也算是重点)

缺点: 不同浏览器对web标准默认值不同,所以更容易出现对浏览器的兼容性问题。

# DOM和BOM的区别

DOM:文档对象模型,描述了处理网页内容的方法和接口。最根本对象是document(window.document)。

由于DOM的操作对象是文档,所以DOM和浏览器没有直接关系。

它表示部署在服务器上的文件夹、右键查看源代码等。

BOM:浏览器对象模型,描述了与浏览器进行交互的方法和接口。由navigator、history、screen、location、window五个对象组成的,最根本对象是 window (前面四个都是window下的属性,也就是说window.history===history)。用来获取或设置浏览器的属性、行为,例如:新建窗口、获取屏幕分辨率、浏览器版本号等;另一方面作为一个全局对象,有权访问isNaN()、parseInt()等方法。

DOM是W3C的标准,BOM没有相关标准。

# docoment,window,html,body的层级关系

层级关系

window > document > html > body
  • windowBOM的核心对象,它一方面用来获取或设置浏览器的属性和行为,另一方面作为一个全局对象。
  • document对象是一个跟文档相关的对象,拥有一些操作文档内容的功能。但是地位没有window高。
  • html元素对象和document元素对象是属于html文档的DOM对象,可以认为就是html源代码中那些标签所化成的对象。他们跟div、select什么对象没有根本区别。

(我是这样记的,整个浏览器中最大的肯定就是窗口window了,那么进来的我不管你是啥,就算你是document也得给我盘着)

# 如何解决a标点击后hover事件失效的问题?

改变a标签css属性的排列顺序

只需要记住LoVe HAte原则就可以了(爱恨原则):

link→visited→hover→active

比如下面错误的代码顺序:

a:hover{
  color: green;
  text-decoration: none;
}
a:visited{ /* visited在hover后面,这样的话hover事件就失效了 */
  color: red;
  text-decoration: none;
}

正确的做法是将两个事件的位置调整一下。

注意⚠️各个阶段的含义:

a:link:未访问时的样式,一般省略成a a:visited:已经访问后的样式 a:hover:鼠标移上去时的样式 a:active:鼠标按下时的样式

# 点击一个input依次触发的事件

const text = document.getElementById('text');
text.onclick = function (e) {
  console.log('onclick')
}
text.onfocus = function (e) {
  console.log('onfocus')
}
text.onmousedown = function (e) {
  console.log('onmousedown')
}
text.onmouseenter = function (e) {
  console.log('onmouseenter')
}

答案:

'onmouseenter'
'onmousedown'
'onfocus'
'onclick'

# 有写过原生的自定义事件吗

创建自定义事件

原生自定义事件有三种写法:

  1. 使用Event
let myEvent = new Event('event_name');
  1. 使用customEvent (可以传参数)
let myEvent = new CustomEvent('event_name', {
	detail: {
		// 将需要传递的参数放到这里
		// 可以在监听的回调函数中获取到:event.detail
	}
})
  1. 使用document.createEvent('CustomEvent')和initCustomEvent()
let myEvent = document.createEvent('CustomEvent');// 注意这里是为'CustomEvent'
myEvent.initEvent(
	// 1. event_name: 事件名称
	// 2. canBubble: 是否冒泡
	// 3. cancelable: 是否可以取消默认行为
)
  • createEvent:创建一个事件
  • initEvent:初始化一个事件

可以看到,initEvent可以指定3个参数。

(有些文章中会说还有第四个参数detail,但是我查看了W3C上并没有这个参数,而且实践了一下也没有效果)

事件的监听

自定义事件的监听其实和普通事件的一样,使用addEventListener来监听:

button.addEventListener('event_name', function (e) {})

事件的触发

触发自定义事件使用dispatchEvent(myEvent)

注意⚠️,这里的参数是要自定义事件的对象(也就是myEvent),而不是自定义事件的名称('myEvent')

案例

来看个案例吧:

// 1.
// let myEvent = new Event('myEvent');
// 2.
// let myEvent = new CustomEvent('myEvent', {
//   detail: {
//     name: 'lindaidai'
//   }
// })
// 3.
let myEvent = document.createEvent('CustomEvent');
myEvent.initEvent('myEvent', true, true)

let btn = document.getElementsByTagName('button')[0]
btn.addEventListener('myEvent', function (e) {
  console.log(e)
  console.log(e.detail)
})
setTimeout(() => {
  btn.dispatchEvent(myEvent)
}, 2000)

# addEventListener和attachEvent的区别?

  • 前者是标准浏览器中的用法,后者IE8以下
  • addEventListener可有冒泡,可有捕获;attachEvent只有冒泡,没有捕获。
  • 前者事件名不带on,后者带on
  • 前者回调函数中的this指向当前元素,后者指向window

# addEventListener函数的第三个参数

第三个参数涉及到冒泡和捕获,是true时为捕获,是false则为冒泡。

或者是一个对象{passive: true},针对的是Safari浏览器,禁止/开启使用滚动的时候要用到。

# DOM事件流是什么?

事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程就叫做DOM事件流。

DOM事件流分为三个阶段:

  1. 捕获阶段:事件从window发出,自上而下向目标节点传播的阶段

  2. 目标阶段:真正的目标阶段正在处理事件的阶段

  3. 冒泡阶段:事件从目标节点自下而上向window传播的阶段

(注意⚠️:JS代码只能执行捕获或者冒泡其中一个阶段,要么是捕获要么是冒泡)

# 冒泡和捕获的具体过程

冒泡指的是:当给某个目标元素绑定了事件之后,这个事件会依次在它的父级元素中被触发(当然前提是这个父级元素也有这个同名称的事件,比如子元素和父元素都绑定了click事件就触发父元素的click)。

捕获则是从上层向下层传递,与冒泡相反。

(非常好记,你就想想水底有一个泡泡从下面往上传的,所以是冒泡)

来看看这个例子:

<!-- 会依次执行 button li ul -->
<ul onclick="alert('ul')">
  <li onclick="alert('li')">
    <button onclick="alert('button')">点击</button>
  </li>
</ul>
<script>
  window.addEventListener('click', function (e) {
    alert('window')
  })
  document.addEventListener('click', function (e) {
    alert('document')
  })
</script>

冒泡结果:button > li > ul > document > window

捕获结果:window > document > ul > li > button

# 所有的事件都有冒泡吗?

并不是所有的事件都有冒泡的,例如以下事件就没有:

  • onblur
  • onfocus
  • onmouseenter
  • onmouseleave

# 关于一些兼容性

  1. event的兼容性
  • 其它浏览器window.event
  • 火狐下没有window.event,所以用传入的参数ev代替
  • 最终写法:var oEvent = ev || window.event
  1. 事件源的兼容性
  • 其它浏览器event.target
  • IE下为event.srcElement
  • 最终写法:var target = event.target || event.srcElement
  1. 阻止事件冒泡
  • 其它浏览器event.stopPropagation()
  • IE下为window.event.cancelBubble = true
  1. 阻止默认事件
  • 其它浏览器e.preventDefault()
  • IE下为window.event.returnValue = false

(参考:JS事件对象兼容性 (opens new window))

# 如何阻止冒泡和默认事件(兼容写法)

阻止冒泡:

function stopBubble (e) { // 阻止冒泡
  if (e && e.stopPropagation) {
    e.stopPropagation();
  } else {
    // 兼容 IE
    window.event.cancelBubble = true;
  }
}
function stopDefault (e) { // 阻止默认事件
  if (e && e.preventDefault) {
    e.preventDefault();
  } else {
    // 兼容 IE
    window.event.returnValue = false;
    return false;
  }
}

# 事件委托知道吗?

# 拖拽有哪些知识点

  1. 可以通过给标签设置draggable属性来实现元素的拖拽,img和a标签默认是可以拖拽的
  2. 拖拽者身上的三个事件:ondragstartondragondragend
  3. 拖拽要放到的元素:ondragenterondragoverondragleaveondrap

# 实现一个拖拽(兼容写法)

css

<style>
  html, body {
    margin: 0;
    height: 100%;
  }
  #box {
    width: 100px;
    height: 100px;
    background-color: red;
    position: absolute;
    top: 100px;
    left: 100px;
  }
</style>

html

<div id="box"></div>

javascript

window.onload = function () {
  var box = document.getElementById('box');
  box.onmousedown = function (ev) {
    var oEvent = ev || window.event; // 兼容火狐,火狐下没有window.event
    var distanceX = oEvent.clientX - box.offsetLeft; // 鼠标到可视区左边的距离 - box到页面左边的距离
    var distanceY = oEvent.clientY - box.offsetTop;
    document.onmousemove = function (ev) {
      var oEvent = ev || window.event;
      var left = oEvent.clientX - distanceX;
      var top = oEvent.clientY - distanceY;
      if (left <= 0) {
        left = 0;
      } else if (left >= document.documentElement.clientWidth - box.offsetWidth) {
        left = document.documentElement.clientWidth - box.offsetWidth;
      }
      if (top <= 0) {
        top = 0;
      } else if (top >= document.documentElement.clientHeight - box.offsetHeight) {
        top = document.documentElement.clientHeight - box.offsetHeight;
      }
      box.style.left = left + 'px';
      box.style.top = top + 'px';
    }
    box.onmouseup = function () {
      document.onmousemove = null;
      box.onmouseup = null;
    }
  }
}

# offset、scroll、client的区别

client:

oEvent.clientX是指鼠标到可视区左边框的距离。

oEvent.clientY是指鼠标到可视区上边框的距离。

clientWidth是指可视区的宽度。

clientHeight是指可视区的高度。

clientLeft获取左边框的宽度。

clientTop获取上边框的宽度。

offset:

offsetWidth是指div的宽度(包括div的边框)

offsetHeight是指div的高度(包括div的边框)

offsetLeft是指div到整个页面左边框的距离(不包括div的边框)

offsetTop是指div到整个页面上边框的距离(不包括div的边框)

scroll:

scrollTop是指可视区顶部边框与整个页面上部边框的看不到的区域。

scrollLeft是指可视区左边边框与整个页面左边边框的看不到的区域。

scrollWidth是指左边看不到的区域加可视区加右边看不到的区域即整个页面的宽度(包括边框)

scrollHeight是指上边看不到的区域加可视区加右边看不到的区域即整个页面的高度(包括边框)

# target="_blank"有哪些问题?

存在问题:

  1. 安全隐患:新打开的窗口可以通过window.opener获取到来源页面的window对象即使跨域也可以。某些属性的访问被拦截,是因为跨域安全策略的限制。 但是,比如修改window.opener.location的值,指向另外一个地址,这样新窗口有可能会把原来的网页地址改了并进行页面伪装来欺骗用户。
  2. 新打开的窗口与原页面窗口共用一个进程,若是新页面有性能不好的代码也会影响原页面

解决方案:

  1. 尽量不用target="_blank"

  2. 如果一定要用,需要加上rel="noopener"或者rel="noreferrer"。这样新窗口的window.openner就是null了,而且会让新窗口运行在独立的进程里,不会拖累原来页面的进程。(不过,有些浏览器对性能做了优化,即使不加这个属性,新窗口也会在独立进程打开。不过为了安全考虑,还是加上吧。)

(参考来源:慎用target="_blank" (opens new window)

# children以及childNodes的区别

  • children和只获取该节点下的所有element节点
  • childNodes不仅仅获取element节点还会获取元素标签中的空白节点
  • firstElementChild只获取该节点下的第一个element节点
  • firstChild会获取空白节点

# JS三种加载方式的区别

(答案参考来源:前端性能优化-页面加载渲染优化 (opens new window))

正常模式

这种情况下 JS 会阻塞浏览器,浏览器必须等待 index.js 加载和执行完毕才能去做其它事情。

<script src="index.js"></script>

async(异步) 模式

async 模式下,JS 不会阻塞浏览器做任何其它的事情。它的加载是异步的,当它加载结束,JS 脚本会立即执行。

<script async src="index.js"></script>

defer(延缓) 模式

defer 模式下,JS 的加载是异步的,执行是被推迟的。等整个文档解析完成、DOMContentLoaded 事件即将被触发时,被标记了 defer 的 JS 文件才会开始依次执行。

<script defer src="index.js"></script>

从应用的角度来说,一般当我们的脚本与 DOM 元素和其它脚本之间的依赖关系不强时,我们会选用 async;当脚本依赖于 DOM 元素和其它脚本的执行结果时,我们会选用 defer。

# 加载CSS会阻塞DOM的解析吗?

css是由单独的下载线程异步下载的,由于DOM树的解析和构建这一步与css并没有关系,所以它并不会影响DOM的解析。不过最终的布局树是需要DOM树和DOM样式的,因此它会阻塞布局树的建立。

# CSS资源一直没响应,那页面会怎样呢?

# 了解loading="lazy"吗?

(答案参考:一、Lazy loading Chrome 76支持啦 (opens new window))

  1. Lazy loading加载数量与屏幕高度有关,高度越小加载数量越少,但并不是线性关系。
  2. Lazy loading加载数量与网速有关,网速越慢,加载数量越多,但并不是线性关系。
  3. Lazy loading加载没有缓冲,滚动即会触发新的图片资源加载。
  4. Lazy loading加载在窗口resize尺寸变化时候也会触发,例如屏幕高度从小变大的时候。
  5. Lazy loading加载也有可能会先加载后面的图片资源,例如页面加载时滚动高度很高的时候。

判断浏览器是否支持loading="lazy":

下面三种方法都可以:

var isSupportLoading = 'loading' in document.createElement('img');

或者:

var isSupportLoading = 'loading' in new Image();

或者:

var isSupportLoading = 'loading' in HTMLImageElement.prototype;
阅读全文