DD每周前端七题详解-第六期

语言: CN / TW / HK

DD每周前端七题详解-第六期

系列介绍

你盼世界,我盼望你无bug。Hello 大家好!我是霖呆呆!

呆呆每周都会分享七道前端题给大家,系列名称就是「DD每周七题」。

系列的形式主要是:3道JavaScript + 2道HTML + 2道CSS,帮助我们大家一起巩固前端基础。

所有题目也都会整合至 LinDaiDai/niubility-coding-jsissues中,欢迎大家提供更好的解题思路,谢谢大家😁。

一起来看看本周的七道题吧。

正题

一、[,,,]的长度

(题目来源:https://github.com/CavsZhouyou/Front-End-Interview-Notebook)

咋了小伙伴们,感觉这道题目很简单是吗?哈哈,数一数逗号的间隙好像就能得出答案了,比如这样:

但是这道题的答案并不是4哟,而是3

console.log([,,,].length) // 3
复制代码

所以最终我们是需要把它想象成这样的:

也就是最后一个逗号的后面是不算一项的。

这里其实涉及到了一个名为:尾后逗号的概念,或者说是叫做终止逗号。上面👆这道题好像看不出它有什么作用,让我来看看实际上为什么会有这个用法。

比如现在你的项目中这么一个文件:

config.js:

const types = [
  {
    name: '帅'
  },
  {
    name: '阳光'
  },
]
export { types };
复制代码

大家可以看到,我在阳光这一项的的后面是多加了一个","的,此时我将这个代码提交到git上并标记为版本1

如果这时候types中又要添加一项名为"可爱"的配置项,我只需要在它下面再加上就行了,不需要去改动到原来代码,此时你提交的代码的diff是长这样的:

const types = [
  {
    name: '帅'
  },
  {
    name: '阳光'
  },
+ {
+   name: '可爱'
+ },
]
export { types };
复制代码

大家可以看到,只有简单的三行增量代码,如果你没有使用尾后逗号的话,你提交的代码的diff会是这样:

const types = [
  {
    name: '帅'
  },
  {
    name: '阳光'
- }
+ },
+ {
+   name: '可爱'
+ }
]
export { types };
复制代码

因此我们可以得出尾后逗号它的作用:

使得版本控制更加清晰,以及代码维护麻烦更少。

(当然,这种用法在.json后缀的文件中是不能用的哈,因为JSON它严格遵循它自己的语法要求)

所以回归到这道题中来,像这种使用了多于一个尾后逗号的数组,我们就称之为稀疏数组,稀疏数组它的长度是等于逗号的数量的。

因此:

console.log([,,,].length) // 3
复制代码

github.com/LinDaiDai/n…

二、如何判断当前脚本运行在浏览器还是 node 环境中?

这道题呆呆其实在很多地方都看到了,但是有的回答好像并不那么靠谱。

回答这道题首先我们需要知道一个概念:

浏览器环境:全局对象为window;而在node环境下,是有一个名为global的对象,它的内部Class属性是为"global"

内部Class属性也就是我们通过Object.prototype.call(obj)这种方式来获取到的内容,比如:

console.log(Object.prototype.toString.call([1, 2, 3])); // "[object Array]"
复制代码

(关于它的用法呆呆在《【精】从206个console.log()完全弄懂数据类型转换的前世今生(上)》中的toString用法时说的也很详细咯)

因此我们可以得出这种判断方式:

var isBrowser = typeof window !== 'undefined'
    && ({}).toString.call(window) === '[object Window]';

var isNode = typeof global !== "undefined"
    && ({}).toString.call(global) == '[object global]';
复制代码

({}).toString.call()Object.prototype.toString.call()用法一致,只不过在{}的外面最好加上一个(),也是为了预防JS将大括号{}认为是一个空的代码块(额,呆呆试了一下貌似也没有这方面的问题)。

github.com/LinDaiDai/n…

三、reduce方法有初始值和没有初始值的区别?

reduce函数的第一个参数是一个回调函数,第二个参数为可选的初始值。

如果有初始值的话,回调函数就会从数组的第0项开始执行,也就是会执行arr.length次;

但是如果没有初始值的话,会默认取数组的第0项为初始值,回调函数会从数组的第1项开始执行,也就是会执行arr.length - 1次。

这点从我们手写一个reduce的实现就可以看出来,代码如下:

Array.prototype.MyReduce = function (fn, initialValue) {
  var arr = Array.prototype.slice.call(this);
  var pre, startIndex;
  pre = initialValue ? initialValue : arr[0];
  startIndex = initialValue ? 0 : 1;
  for (var i = startIndex; i < arr.length; i++) {
    pre = fn.call(null, pre, arr[i], i, this)
  }
  return pre
}
复制代码

过程分析:

  • 首先,map、reduce这种方法都是数组原型对象上的方法,所以我将MyReduce定义在Array.prototype 上,这样你就可以直接使用ary.MyReduce()这样的方式调用它了(ary是一个类似于这样的数组[1, 2, 3])。
  • 对于参数,我们参考原生reduce,它接收的第一个参数是一个回调函数,第二个是初始值
  • var arr = ...的作用是获取调用MyReduce函数的那个变量,也就是说this会指向那个变量,例如ary.MyReduce(),那么此时this就为ary
  • 至于为什么不使用var arr = this;的方式而是使用Array.prototype.slice.call(this),算是实现一个浅拷贝吧,因为reduce是不会改变原数组的。
  • 然后就是定义传入reduce中的回调函数的第一个参数pre,也就是上一次运行结果的返回值,可以看到这里就用到了初始值initialValue,如果存在初始值就取初始值,不存在则默认取数组第0项。(当然这里直接用initialValue ?来判断存不存在并不准确,因为我们知道0也会被判断为false)
  • 接着是定义循环开始的下标startIndex,若是不存在初始值,则初始值是会取数组中的第0项的,相当于第0项并不需要运行,所以startIndex会是1,而如果有初始值的话则需要将数组的每一项都经过fn运行一下。
  • 最后,for循环中使用fn.call()来调用fn函数,并且最后一个参数是要把原来的数组传递到回调函数中,也就是这里的this

github.com/LinDaiDai/n…

四、form表单中的label标签的作用?

label标签不会向用户呈现任何特殊效果,它的作用是为鼠标用户改进了可用性。

也就是说当你使用了一个label标签和一个input绑定起来之后,点击label标签上的文字就会自动聚焦到input上。

如下:

绑定的方式:

  • label标签上设置for属性
  • input标签上设置和for属性一样的id

例如:

<label for="username">username:</label>
<input type="text" name="username" id="username"/>
复制代码

这里有两点需要注意的:

  • 这两个标签不一定非要在form标签内才会生效
  • for是和id对应的,并不是和name

github.com/LinDaiDai/n…

五、HTML5中的自动完成功能autocomplete是做什么的?

大家在听到自动完成这个词,会有一点迷糊,自动完成什么?

其实这个功能的作用是这样的:

首先,autocomplete是一个属性,这个属性可以设置在form标签上,也可以设置在其它的input标签上。

被设置了自动完成的标签,会允许浏览器预测对字段的输入。也就是说浏览器会根据你在这个输入框中已经输入过的值,留有一个"历史记录",方便我们下次还想要输入同样的值。

例如下面这段代码:

<form autocomplete="on">
  testaccount: <input type="text" name="testaccount" /><br />
  testpassword: <input type="text" name="testpassword" autocomplete="off" /><br />
  <input type="submit" />
</form>
复制代码
  • 我把form标签的autocomplete打开,那么这个表单下的所有元素都开启了autocomplete
  • 接着我把testpassword这一项的autocomplete关闭。

此时testaccout是有自动完成功能的,而testpassword没有。那么当我们第一次在这两个输入框中输入了内容并提交后。再次点击testaccout输入框,就会出现我们上一次输入并提交的那个值,而点击testpassword时却没有。

效果如下:

所以我们来做下总结吧😊:

  • autocomplete属性规定输入字段是否应该启用自动完成功能;
  • 它的默认值是启用,也就是"on",另一个值是"off"关闭;
  • 作用是:允许浏览器预测对字段的输入。当用户在字段开始键入时,浏览器基于之前键入过的值,应该显示出在字段中填写的选项。
  • autocomplete 属性适用于<form>,以及下面的 <input> 类型:text, search, url, telephone, email, password, datepickers, range , color

github.com/LinDaiDai/n…

六、CSS中的visibility有个collapse属性值是干嘛用的?

visibility会有这么几个个属性值:

  • visible
  • hidden
  • collapse
  • inherit

比较常用的可能是前面两个,用于控制元素的显示隐藏。且我们知道,设置为hidden是会隐藏元素,但是其他元素的布局不改变,相当于此元素变成透明。

inherit则是从父元素继承visibility属性的值。

而对于collapse,可能用的不是特别多,我们先来看下它的介绍:

  • 对于一般的元素,它的表现跟hidden是一样的;
  • 如果这个元素是table相关的元素,例如table行,table group,table列,table column group,它的表现却跟display: none一样,也就是说,它们占用的空间也会释放。

下面一起来看看这个案例😊:

html代码

<table>
  <tr class="tr1">
    <td>
      tr1
    </td>
    <td>
      tr1
    </td>
  </tr>
  <tr>
    <td>
      tr2
    </td>
    <td>
      tr2
    </td>
  </tr>
</table>
复制代码

css代码

table {
  border: 1px solid red;
}
td {
  border: 1px solid blue;
}
.tr1 {

}
复制代码

我在没给.tr1设置任何属性的时候,页面呈现的效果是这样的,很正常:

如果设置了visibility: hidden

.tr1 {
  visibility: hidden;
}
复制代码

效果如下:

虽然第一行被隐藏了,但是它的空间还是在的,就像是"隐身"了一样。

而如果设置了visibility: collapse

.tr1 {
  visibility: collapse;
}
复制代码

效果如下:

最终的效果会和display: none;一样。

如果你问呆呆这属性有什么实际的用处没有...咳咳,抱歉,好像还真没有😅。也可能是呆呆不知道,知道的小伙伴还请提出哟,一起学习一哈。

github.com/LinDaiDai/n…

七、块状元素width:100%与width:auto的区别?

这两个属性值相信大家都不陌生了,先让我们来看个案例理解一下:

html代码

<div class="super">
  <div class="sub1">
    我是呆呆的第一个崽
  </div>
  <div class="sub2">
    我是呆呆的第二个崽
  </div>
  <div class="sub3">
    我是呆呆的第三个崽
  </div>
</div>
复制代码

css代码:

.super {
  width: 200px;
  height: 100px;
  background: skyblue;
}
.sub1 {
  background: #d0e4a9;
}
.sub2 {
  width: 100%;
  background: #c077af;
}
.sub3 {
  width: auto;
  background: #f8d29d;
}
复制代码

最开始时,三个崽表现的效果是一样的,并无很大差别:

此时如果我们对后面两个崽做一下改动:

.sub2 {
  width: 100%;
  padding-left: 10px;
  margin-left: 10px;
  background: #c077af;
}
.sub3 {
  width: auto;
  padding-left: 10px;
  margin-left: 10px;
  background: #f8d29d;
}
复制代码

给他们都加上左内边距和左外边距,此时的效果就变成了这样:

大家会发现,设置了width: 100%的崽,它的宽度会和父级的一样,此时如果再给他设置了额外的padding,那它就会比父级还要宽了,也就是会超出父级容器。而因为还设置了margin-left,所以左边也会有一段距离。

但是设置了width: auto的崽就比较乖了,无论怎样它都不会超出父级,而是选择压缩自己的宽度。

因此我们可以得出结论:

  • 两者的计算方式不同(这里的计算规则都是基于box-sizing: content-box;的情况):
    • 对于width: auto;它的总宽度是等于父宽度的(这里的父宽度是指父级内容的宽度不包括padding、border、margin),即使给元素设置了padding、border、margin等属性,它也会自动分配水平空间。
    • 对于width: 100%;它表示的是元素内容的宽度等于父宽度,所以它的总宽度是有可能超过父级的,因为如果设置了额外的padding、border,就可能比父级宽了。
  • 无论是width:100%还是auto,其计算的参照都是父级内容区width值,而非总宽度值,也就是不包括padding、border、margin
  • 一般width:auto使用的多一些,因为这样灵活;而width:100%使用比较少,因为在增加padding或者margin的时候,容易使其突破父级框,破坏布局。

github.com/LinDaiDai/n…

参考文章

知识无价,支持原创。

参考文章:

后语

你盼世界,我盼望你无bug。这篇文章就介绍到这里。

您每周也许会花48小时的时间在工作💻上,会花49小时的时间在睡觉😴上,也许还可以再花20分钟的时间在呆呆的7道题上,日积月累,我相信我们都能见证彼此的成长😊。

什么?你问我为什么系列的名字叫DD?因为呆呆呀,哈哈😄。

喜欢霖呆呆的小伙还希望可以关注霖呆呆的公众号 LinDaiDai 或者扫一扫下面的二维码👇👇👇。

img
img

我会不定时的更新一些前端方面的知识内容以及自己的原创文章🎉

你的鼓励就是我持续创作的主要动力 😊。

往期题目可以戳下面👇:

或者你也可以查看github上的issues「我是issues」

本文使用 mdnice 排版