nth-last-child 和 nth-last-of-type 的区别

nth-last-child

从兄弟节点中从后往前匹配处于某些位置的元素

nth-last-of-type

从兄弟节点中从后往前匹配处于某些位置相同类型的元素

例子

<html>
  <style>
    p:nth-last-child(1) {
      color: red;
    }
    p:nth-last-of-type(1) {
      color: aquamarine;
    }
  </style>
  <body>
    <div id="app">
      <p>p1元素</p>
      <p>p2元素</p>
      <p>p3元素</p>
      <span>span元素</span>
    </div>
  </body>
</html>

image

从上面的图可以看出,nth-last-child(1) 并没有匹配,为什么呢?

进一步阐述这个选择器的含义:p 的父元素下的 p 元素 且 p 元素是倒数第 1 个子元素(如果不符合这个要求,则认为没匹配到)。

在上面的例子中,最后一个子元素是 span 而不是 p,故该选择器不生效。

那为什么nth-last-of-type(1)生效呢?

原因是 p 的父元素下的倒数第 1 个 p 元素,这个计数只会统计相同类型的,而上面的例子中 p 元素 p3 就是同级所有 p 元素的最后一个,所以可以匹配到。

下面这个例子应该可以更好地看出区别

p:nth-last-child(2) {
  color: red;
}
p:nth-last-of-type(2) {
  color: aquamarine;
}

image

由于 p 的兄弟节点中,p3 属性倒数第二个元素,且是 p 元素,所以nth-last-child(2)匹配到 p3,而 p2 在所有的同级 p 元素中属于倒数第二个,所以nth-last-of-type(2)匹配到 p2。

原型相关

两种方式创建的空对象的区别?

var obj1 = {};
var obj2 = Object.create(null);

第一种方式创建的对象会继承 Object 的原型链,也就是说 Object 上的方法和属性 obj1 也可以调用,也就是说{}是一个不完全空对象,原型链上还有 Object,而第二种方式创建的对象就真的是一个完全空对象,原型链上什么都没有(null 是原型链上的终点),

因此,下面代码obj2.toString()会报错,因为 obj2 上的原型没有 toString 方法。

console.log(obj1.toString()); // {}
//VS
console.log(obj2.toString()); // 报错

语义化是什么?

在 html 中,我理解的语义化就是让标签变得有含义,让文档的“可读性”更高。

掘金找到的一张图

语义化

header 和 div 的区别

header 元素代表“网页”或“section”的页眉。通常包含 h1-h6 元素或 hgroup,作为整个页面或者一个内容块的标题。

section 和 div 的区别

section 元素代表文档中的“节”或“段”,“段”可以是指一篇文章里按照主题的分段;“节”可以是指一个页面里的分组。

figure 和 article 的区别

<figure> 标签规定独立的流内容(图像、图表、照片、代码等等)。

figure 元素的内容应该与主内容相关,但如果被删除,则不应对文档流产生影响。

<article> 标签规定独立的自包含内容。一篇文章应有其自身的意义,应该有可能独立于站点的其余部分对其进行分发。
<article> 元素的潜在来源:

  • 论坛帖子
  • 报纸文章
  • 博客条目
  • 用户评论

div

div 本身没有语义,作为一个普通容器使用。

原型链是什么?

JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(proto),层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

题目:

let Person = {
  name: 'aaa',
  address: {
    province: '广东省'
    city: '深圳市'
  }
}
let p1 = new Person();
let p2 = new Person();

p1.name = 'tranvce';
p1.address.province = '云南省';

console.log(p1.name);  // tranvce
console.log(p2.name);  // aaa
console.log(p2.address.province);  // 云南省

输出是什么,为什么?

p1.name = 'tranvce'这里访问的是简单属性,并不会访问到 Person 的原型上,因此也不会修改到 Person 的原型属性,而p1.address.province = '云南省'访问的复杂属性,因此会沿着原型寻找到 province,这样 Person 原型上的属性就被修改了,然后 p1 和 p2 共享 Person 的原型,因此 p1 对 Person 原型的修改会影响到 p2。

其实逆过来想也可以,如果p1.address.province没有访问到 Person 的原型,它本身也没有这个属性,那么在访问 province 的时候就会报Cannot read property 'province' of undefined的错误了。

但是还是不是很明白这个简单属性和复杂属性访问的原理,p1.name = xxx 是不是相当于在构造函数里使用this.name = xxx呢。

发现面试官给的代码报错了,Person is not a constructor😢

冒泡是什么,什么时候停止,捕获呢?

起源

Netscape(网景)只使用事件捕获,而 Internet Explorer 只使用事件冒泡。当 W3C 决定尝试规范这些行为并达成共识时,他们最终得到了包括这两种情况(捕捉和冒泡)的系统,最终被应用在现在浏览器里。

捕获

  • 浏览器检查元素的最外层祖先<html>,是否在捕获阶段中注册了一个 onclick 事件处理程序,如果是,则运行它。
  • 然后,它移动到<html>中单击元素的下一个祖先元素,并执行相同的操作,然后是单击元素再下一个祖先元素,依此类推,直到到达实际点击的元素。

冒泡

  • 浏览器检查实际点击的元素是否在冒泡阶段中注册了一个 onclick 事件处理程序,如果是,则运行它
  • 然后它移动到下一个直接的祖先元素,并做同样的事情,然后是下一个,等等,直到它到达<html>元素。

stopPropagation()可以阻止冒泡。

两者执行顺序

捕获-目标-冒泡

好的又答错了。。。凉

HTTP/2

多路复用的实现

HTTP/2 设计是基于“二进制帧”进行设计的,帧的字节中保存了不同的信息,前 9 个字节对于每个帧都是一致的,“服务器”解析 HTTP/2 的数据帧时只需要解析这些字节,就能准确的知道整个帧期望多少字节数来进行处理信息。由于 HTTP/2 是分帧的,请求和响应可以交错甚至可以复用。

HTTP/2 是使用流进行传输的,“流”的概念:HTTP/2 连接上独立的、双向的帧序列交换。流 ID(帧首部的 6-9 字节)用来标识帧所属的流。

image

延伸问题:为什么 HTTP/1.1 不能实现“多路复用”?

  • HTTP/2 是基于二进制“帧”的协议,HTTP/1.1 是基于“文本分割”解析的协议。
  • HTTP/1.1 发送请求消息的文本格式:以换行符分割每一条 key:value 的内容,这种以分隔符分割消息的数据,在完成之前不能停止解析,所以一次只能响应一种请求。

管道机制是什么?和非管道运算的区别

HTTP/1.1 版引入了管道机制(pipelining),即在同一个 TCP 连接里面,客户端可以同时发送多个请求。

非管道传输的做法是在同一个 TCP 上面,进行串行的 HTTP 请求,就是说 A 请求发起,等待到服务器响应后,才发起 B 请求。而管道传输允许 HTTP 同时发起请求,B 发起请求不需要等待 A 收到响应,但是响应还是按顺序返回,因此存在线头阻塞的问题,就是比如 A 请求非常耗时,那么 A 后面的请求想要得到响应也需要等待很久。

管道机制怎么实现

将多个 HTTP 请求打包到一个 TCP 消息包中。

webpack 场景问题

真不熟,我尽力了。。

做题

找出数组重复元素

input: [3, 1, 2, 3, 1, 4, 4, 5];
output: [1, 3, 4];
返回结果不需要按顺序;

写完了,结果也没问题,我的思路是:遍历数组,通过哈希表存储出现过的数组,如果数组元素第二次出现,那么哈希表肯定存在这种元素,这时候将该元素 push 到结果数组里。最后通过Array.from(new Set(res))对象进行数组去重即可。

力扣 1047. 删除字符串中的所有相邻重复项

输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。

结果不符合预期,思路方向应该是对的,面试时间有点长了,就没给调试时间。

总结

面试真是发现自己不足的最佳途径,就是越面越没信心了 😢,这次可能凉了?