Giter Club home page Giter Club logo

niubility-coding-js's People

Contributors

lindaidai avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

niubility-coding-js's Issues

💪第6期第3题: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

⛺️第2期第1题:设计一个方法提取对象中所有value大于2的键值对并返回最新的对象

设计一个方法提取对象中所有value大于2的键值对并返回最新的对象

实现:

var obj = { a: 1, b: 3, c: 4 }
foo(obj) // { b: 3, c: 4 }

方法有很多种,这里提供一种比较简洁的写法,用到了ES10Object.fromEntries()

var obj = { a: 1, b: 3, c: 4 }
function foo (obj) {
  return Object.fromEntries(
    Object.entries(obj).filter(([key, value]) => value > 2)
  )
}
var obj2 = foo(obj) // { b: 3, c: 4 }
console.log(obj2)
// ES8中 Object.entries()的作用:
var obj = { a: 1, b: 2 }
var entries = Object.entries(obj); // [['a', 1], ['b', 2]]
// ES10中 Object.fromEntries()的作用:
Object.fromEntries(entries); // { a: 1, b: 2 }

🎁第7期第1题:几种字符串转为数字的方法,有什么区别?

几种字符串转为数字的方法,有什么区别?

(题目来源:CavsZhouyou/Front-End-Interview-Notebook)

JS中将字符串转换为数字的方式有很多种,以下我列举了一些常用的,看看非纯数字来进行转换会发生什么:

let str = '33.3c'

console.log(Number(str));

console.log(parseInt(str));

console.log(parseFloat(str));

console.log(str++);

console.log(str>>>2);

大家可以先思考一下再来看结果。

let str = '33.3c'

console.log(Number(str)); // NaN

console.log(parseInt(str)); // 33

console.log(parseFloat(str)); // 33.3

console.log(str++); // NaN

console.log(str>>>2); // 0

嘻嘻😁,我们来简单分析一下:

  • Number()方法会对传入的值进行强转换为数字,如果传入的字符串包含了非数字的话则被转为NaN

  • parseInt()方法会将传入的值转为整数,若是碰到非数字部分则终止

    例如:

    console.log(parseInt("33c3.3c")); // 33
  • parseFloat()方法和parseInt()很像,不过它会保留小数,另外它在碰到非数字部分也会终止:

    console.log(parseFloat("33c3.3c")); // 33
  • ++这种方式的话就是就会有一个隐式转换的过程,将字符串转换为数字,类似于Number()

  • >>>无符号右移,>>是有符号右移,在这里呆呆认为str应该是会被先隐式转换为数字,然后再进行右移的,因为str被转为数字的结果是NaN,而NaN右移的结果是0

    str 转为数字是 NaN;
    
    NaN>>>2 的结果是 0;

如果对隐式类型转换还不熟悉的小伙伴可以看呆呆的这篇文章哟:[【精】从206个console.log()完全弄懂数据类型转换的前世今生(下)](

💪第6期第5题:HTML5中的自动完成功能autocomplete是做什么的?

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

💪第6期第6题:CSS中的visibility有个collapse属性值是干嘛用的?

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;一样。

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

🌶️第3期第7题:float:left对比inline-block

float:left对比inline-block

  • 文档流:浮动会脱离文档流,且使得被覆盖的元素的文字内容会环绕在周围;而inline-block不会脱离文档流也就不会覆盖其它元素。浮动也会引发父级高度塌陷问题。
  • 水平位置:不能给有浮动元素的父级设置text-align:center使得子集浮动元素居中,而inline-block却可以。
  • 垂直对齐inline-block元素沿着默认的基线对齐(baseline),若是两个元素的font-size不同则可能会看到一高一低,你可以通过设置vertical-align: top或者bottom;来使得它们基于顶线或者底线对齐(注意这个是设置到元素本身而不是设置到它们的父级)。而浮动元素紧贴顶部,不会有这个问题。
  • 空白inline-block包含html空白节点。如果你的html中一系列元素每个元素之间都换行了,当你对这些元素设置inline-block时,这些元素之间就会出现空白。而浮动元素会忽略空白节点,互相紧贴。

针对第三点,垂直对齐可以看下面👇这个案例:

默认情况下:

css代码为:

.sub {
  background: hotpink;
  display: inline-block;
}

设置了vertial-align: top;后:

css代码为:

.sub {
  background: hotpink;
  display: inline-block;
  vertical-align: top;
}

🎁第7期第3题:如何判断一个对象是否为空对象?

空对象?咳咳,就是这个:

let obj = {}
  1. for...in...
function isEmptyObj (obj) {
	for (i in obj) {
		return false
	}
	return true;
}
console.log(isEmptyObj(obj)); // true

不过这种方法貌似有一个弊端,因为for...in...是会把对象原型链上的属性也列举出来,例如下面这样就会判断错误:

function isEmptyObj (obj) {
  for (i in obj) {
    return false
  }
  return true;
}
let obj = {};
obj.__proto__.num = 'dsfdf'
console.log(isEmptyObj(obj)); // false
  1. JSON.stringify()

😂,这个是呆呆很久之前用的一种方法:

function isEmptyObj (obj) {
	return JSON.stringify(obj) === '{}';
}
console.log(isEmptyObj(obj)); // true
  1. Object.keys()
function isEmptyObj (obj) {
	return Object.keys(obj).length === 0;
}
console.log(isEmptyObj(obj)); // true

🍃第1期第1题:Array(3)和Array(3, 4)的区别?

Array(3)和Array(3, 4)的区别?

console.log(Array(3))
console.log(Array(3, 4))

console.log(new Array(3))
console.log(new Array(3, 4))

console.log(Array.of(3))
console.log(Array.of(3, 4))

考察知识点:

  • Array()new Array()
  • Array()参数个数不同时的不同表现
  • Array.of()的作用

结果:

console.log(Array(3)) // [empty x 3]
console.log(Array(3, 4)) // [3, 4]

console.log(new Array(3)) // [empty x 3]
console.log(new Array(3, 4)) // [3, 4]

console.log(Array.of(3)) // [3]
console.log(Array.of(3, 4)) // [3, 4]

总结:

  • Array使不使用new效果都是一样的
  • Array方法,如果参数是一位的话,这个参数表示的是数组的长度,并创建此长度的空数组
  • Array方法,如果参数是多位的话则每一个参数都是数组的一项,会按顺序返回数组
  • Array.of()接收任意个参数,将按顺序成为返回数组中的元素,并返回这个新数组。

⛺️第2期第7题:圆?半圆?椭圆?

圆?半圆?椭圆?

div {
  width: 100px;
  height: 100px;
  background-color: red;
  margin-top: 20px;
}
.box1 { /* 圆 */
  /* border-radius: 50%; */
  border-radius: 50px;
}
.box2 { /* 半圆 */
  height: 50px;
  border-radius: 50px 50px 0 0;
}
.box3 { /* 椭圆 */
  height: 50px;
  border-radius: 50px/25px; /* x轴/y轴 */
}

⛽️第5期第5题:`insertAdjacentHTML`和`insertAdjacentElement`的区别

insertAdjacentHTMLinsertAdjacentElement的区别

第二个参数的类型不同, 前者接收的是是要被解析为HTML或XML元素的字符串,而后者接收的是一个element元素。

const one = document.getElementById('one');
one.insertAdjacentHTML('afterend', '<div id="two">我是two</div>');

const one = document.getElementById('one');
const two = document.createElement('div')
two.innerHTML = '我是two';
one.insertAdjacentElement('afterend', two);

⛺️第2期第4题:实现一个拖拽(兼容写法)

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

考察知识点

  1. event的兼容性
  • 其它浏览器window.event
  • 火狐下没有window.event,所以用传入的参数ev代替
  • 最终写法:var oEvent = ev || window.event
  1. 实现拖拽的事件有哪些(box为需要拖拽的元素)
  • box.onmousedown
  • document.onmousemove
  • box.onmouseup
  1. 实现的事件顺序
  • 首先监听box.onmousedown,即鼠标按下box时触发的事件,记录下鼠标按下时距离屏幕上边和左边的距离,以及box距离屏幕上边和左边的距离,再用前者减去后者得到差值distanceXdistanceY
  • 然后在此事件中监听document.onmousemove事件,记录下每次鼠标移动时距离屏幕上边和左边的距离,然后用它们减去distanceXdistanceY,再将其赋值给boxlefttop,使其能跟着鼠标移动
  • 不过需要考虑box距离屏幕最上面/下面/左边/右边的边界情况
  • box.onmouseup的时候需要将document.onmousemove事件设置为null

如图所示:

Coding

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;
    }
  }
}

🍃第1期第4题:addEventListener和attachEvent的区别?

addEventListener和attachEvent的区别?

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

⛽️第5期第4题:知道`insertAdjacentHTML`方法吗?

知道insertAdjacentHTML方法吗?

这个方法是呆呆最近在看公司项目代码时了解到的,之前一直没有注意它。

首先对于它的用法:

insertAdjacentHTML() 方法将指定的文本解析为 Element 元素,并将结果节点插入到DOM树中的指定位置。它不会重新解析它正在使用的元素,因此它不会破坏元素内的现有元素。这避免了额外的序列化步骤,使其比直接使用innerHTML操作更快。

其次它的作用对象是一个元素element,例如const container = document.getElementById('container')

语法上呢:

element.insertAdjacentHTML(position, text);
  • position:一个DOMString,也就是表示插入内容相对元素的位置,且必须是下面的字符串之一:
    • 'beforebegin':元素自身的前面。
    • 'afterbegin':插入元素内部的第一个子节点之前。
    • 'beforeend':插入元素内部的最后一个子节点之后。
    • 'afterend':元素自身的后面。
  • text:是要被解析为HTML或XML元素,并插入到DOM树中的 DOMString

案例

让我们来看看它的用法,例如🌰现在有一个HTML的结构为:

<div id="one">我是one</div>

JavaScript代码中加上这段话:

const one = document.getElementById('one');
one.insertAdjacentHTML('afterend', '<div id="two">我是two</div>');

现在最终的渲染结果就变成了这样:

<div id="one">我是one</div><div id="two">我是two</div>

工作上的用法

在项目中,主要可以应用于这样的场景:一个空的容器(你可以理解为一个div),开始需要一个loading的效果,在数据加载完毕之后,需要把loading取掉且清空容器内的元素并以其它方式重新渲染出容器的内容。

这里呆呆就以定时器来模拟一下数据加载的过程,实现代码如下:

<body>
	<div id="container"></div>
</body>
<script>
	const container = document.getElementById('container');
  const loading = '<div id="loading">loading</div>'; // loading可能是一个组件
  container.insertAdjacentHTML('beforeend', loading);
  setTimeout(() => {
    container.innerHTML = ''
  }, 2000)
</script>

(当然,我们不要为了刻意用而去用,适合自己的才是最好的)

安全问题

  • 使用 insertAdjacentHTML 插入用户输入的HTML内容的时候,需要转义之后才能使用。

    例如:

    const one = document.getElementById('one');
    // 由于 encodeURI('<div id="two">我是two</div>')会被转译为:
    // %3Cdiv%20id=%22two%22%3E%E6%88%91%E6%98%AFtwo%3C/div%3E
    // 因此最终会被当成 "%3Cdiv%20id=%22two%22%3E%E6%88%91%E6%98%AFtwo%3C/div%3E"字符串渲染
    one.insertAdjacentHTML('afterend', encodeURI('<div id="two">我是two</div>'));
  • 如果只是为了插入文本内容(而不是HTML节点),不建议使用这个方法,建议使用node.textContent 或者 node.insertAdjacentText()。因为这样不需要经过HTML解释器的转换,性能会好一点。(这里是引用的MDN-insertAdjacentHTML上的内容)

🍃第1期第7题:文字多行超出显示省略号

文字多行超出显示省略号

div {
  width: 200px;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}

该方法适用于WebKit浏览器及移动端。

跨浏览器兼容方案:

p {
  position:relative;
  line-height:1.4em;
  /* 3 times the line-height to show 3 lines */
  height:4.2em;
  overflow:hidden;
}
p::after {
  content:"...";
  font-weight:bold;
  position:absolute;
  bottom:0;
  right:0;
  padding:0 20px 1px 45px;
}

⛺️第2期第2题:实现一个padStart()或padEnd()的polyfill

实现一个padStart()或padEnd()的polyfill

String.prototype.padStartString.prototype.padEndES8中新增的方法,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。我们先看下使用语法:

String.padStart(targetLength,[padString])

用法:

'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'

// 1. 若是输入的目标长度小于字符串原本的长度则返回字符串本身
'xxx'.padStart(2, 's') // 'xxx'

// 2. 第二个参数的默认值为 " ",长度是为1的
// 3. 而此参数可能是个不确定长度的字符串,若是要填充的内容达到了目标长度,则将不要的部分截取
'xxx'.padStart(5, 'sss') // ssxxx

// 4. 可用来处理日期、金额格式化问题
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

polyfill实现:

String.prototype.myPadStart = function (targetLen, padString = " ") {
  if (!targetLen) {
    throw new Error('请输入需要填充到的长度');
  }
  let originStr = String(this); // 获取到调用的字符串, 因为this原本是String{},所以需要用String转为字符串
  let originLen = originStr.length; // 调用的字符串原本的长度
  if (originLen >= targetLen) return originStr; // 若是 原本 > 目标 则返回原本字符串
  let diffNum = targetLen - originLen; // 10 - 6 // 差值
  for (let i = 0; i < diffNum; i++) { // 要添加几个成员
    for (let j = 0; j < padString.length; j++) { // 输入的padString的长度可能不为1
      if (originStr.length === targetLen) break; // 判断每一次添加之后是否到了目标长度
      originStr = `${padString[j]}${originStr}`;
    }
    if (originStr.length === targetLen) break;
  }
  return originStr;
}
console.log('xxx'.myPadStart(16))
console.log('xxx'.padStart(16))

还是比较简单的,而padEnd的实现和它一样,只需要把第二层for循环里的${padString[j]}${orignStr}换下位置就可以了。

🌶️第3期第5题:children以及childNodes的区别

children以及childNodes的区别

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

🌶️第3期第2题:创建一个函数batches返回能烹饪出面包的最大值

创建一个函数batches返回能烹饪出面包的最大值

(题目来源:https://github.com/30-seconds/30-seconds-of-interviews)

/**
batches函数接收两个参数:
1. recipe 制作一个面包需要的各个材料的值
2. available 现有的各个材料的值
要求传入 recipe 和 available,然后根据两者计算出能够烹饪出面包的最大值
**/

// 0个面包,因为 butter黄油需要50ml,但是现在只有48ml
batches(
  { milk: 100, butter: 50, flour: 5 },
  { milk: 132, butter: 48, flour: 51 }
)
batches(
  { milk: 100, butter: 4, flour: 10 },
  { milk: 1288, butter: 9, flour: 95 }
)

// 1个面包
batches(
  { milk: 100, butter: 50, flour: 10 },
  { milk: 198, butter: 52, flour: 10 }
)

// 2个面包
batches(
  { milk: 2, butter: 40, flour: 20 },
  { milk: 5, butter: 120, flour: 500 }
)

这道题的解题思路其实就是比较recipeavailable两个对象的每一个属性值,用后者的属性值除以前者的属性值,然后得到一个数,例如0个面包中的:

  • available.milk / recipe.milk,得到1.32
  • available.butter / recipe.butter,得到0.96
  • available.flour / recipe.flour,得到10.2

然后取三个结果中的最小值0.96,再向下取整,得出最终能制作的面包个数为0

所以我们可以得出第一种解题方法:

const batches = (recipe, available) =>
  Math.floor(
    Math.min(...Object.keys(recipe).map(k => available[k] / recipe[k] || 0))
  )

过程分析:

  • Object.keys(recipe),迭代recipe对象,得到所有的key['milk', 'butter', 'flour']
  • 之后使用map遍历刚刚所有的key,并返回available[k]/recipe[k]的值:[1.32, 0.96, 10.2]
  • 需要得出上面一步数组中的最小值,所以可以使用Math.min(...arr)方法来获取
  • 最后将最小值0.96向下取整得到0

当然这道题你也可以使用Object.entries(),效果是一样的:

const batches = (recipe, available) =>
  Math.floor(
    // Math.min(...Object.keys(recipe).map(k => available[k] / recipe[k] || 0))
    Math.min(...Object.entries(recipe).map(([k, v]) => available[k] / v || 0))
  )

☁️第4期第7题:脱离文档流是不是指该元素从DOM树中脱离?

脱离文档流是不是指该元素从DOM树中脱离?

并不会,DOM树是HTML页面的层级结构,指的是元素与元素之间的关系,例如包裹我的是我的父级,与我并列的是我的兄弟级,类似这样的关系称之为层级结构。

而文档流则类似于排队,我本应该在队伍中的,然而我脱离了队伍,但是我与我的父亲,兄弟,儿子的关系还在。

💪第6期第2题:如何判断当前脚本运行在浏览器还是 node 环境中?

如何判断当前脚本运行在浏览器还是 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将大括号{}认为是一个空的代码块(额,呆呆试了一下貌似也没有这方面的问题)。

🎁第7期第4题:如何让Chrome浏览器支持小于12px的字体大小?

如何让Chrome浏览器支持小于12px的字体大小?

使用:-webkit-transform: scale(0.8);

注意⚠️它修改的整个元素的大小,所以如果是内联元素的话则需要转换为块元素或者内联块元素

<style>
  .font_size_12 {
    font-size: 12px;
  }
  .font_size_small {
    font-size: 12px;
    display: inline-block;
    font-size: 10px;
    -webkit-transform: scale(0.8);
  }
</style>
<body>
  <div class="font_size_12">
    霖呆呆
    <span class="font_size_small">
      小号霖呆呆
    </span>
  </div>
</body>

效果如下:

其它的方法,原来还有一个-webkit-text-size-adjust:none;属性,设置了整个之后就可以去掉Chrome的字体限制,但是在Chrome更新到27版本之后就被干掉了。呆呆在现在的Chrome中试了一下已经没有效果了。

另外,网上还有说把要缩小的字设置变为图片...靠图片来展示...

貌似都不太靠谱呀,有靠谱的小伙伴还希望可以留言哦。

🌶️第3期第6题:float:left对比position:absolute

float:left对比position:absolute

相同点:

  • 脱离文档流,也就是将元素从普通的布局排版中拿走,其他盒子在定位的时候,会当做脱离文档流的元素不存在而进行定位。
  • 包裹性:也就是都会让元素inline-block化。等同于没有高度与宽度的inline-block元素。
    • 对于块状元素默认的宽度为100%,若设置为了绝对定位则宽度由内容决定
    • 对于内联元素原本设置width属性无效,若设置了浮动则可以设置width
  • 破坏性:都会导致父级高度塌陷。但若是设置了绝对定位的话,其父级即使设置为float:left;也还是不能解决高度塌陷的问题。

不同点:

  • 虽然它们都会脱离文档流,但是使用float脱离文档流时,其他盒子会无视这个元素,但其他盒子内的文本依然会为这个元素让出位置,环绕在周围。而对于使用position:absolute脱离文档流的元素,其他盒子与其他盒子内的文本都会无视它。

⛽️第5期第7题:说说will-change

说说will-change

will-changeCSS3新增的标准属性,它的作用很单纯,就是"增强页面渲染性能",当我们在通过某些行为触发页面进行大面积绘制的时候,浏览器往往是没有准备,只能被动的使用CUP去计算和重绘,由于事先没有准备,对于一些复杂的渲染可能会出现掉帧、卡顿等情况。

will-change则是在真正的行为触发之前告诉浏览器可能要进行重绘了,相当于浏览器把CUP拉上了,能从容的面对接下来的变形。

常用的语法主要有:

  • whil-change: scroll-position; 即将开始滚动
  • will-change: contents; 内容要动画或者变化了
  • will-transform; transform相关的属性要变化了(常用)

注意:

  • will-change虽然可以开启加速,但是一定要适度使用
  • 开启加速的代价为手机的耗电量会增加
  • 使用时遵循最小化影响原则,可以对伪元素开启加速,独立渲染
  • 可以写在伪类中,例如hover中,这样移出元素的时候就会自动removewill-change
  • 如果使用JS添加了will-change,注意要及时remove掉,方式就是style.willChange = 'auto'

⛽️第5期第2题:介绍一下NaN并实现一个isNaN

介绍一下NaN并实现一个isNaN

介绍一下NaN

  • NaN属性是代表非数字值的特殊值,该属性用于指示某个值不是数字;
  • NaN是不等于NaN的,即NaN === NaN的结果是false
  • 使用Object.is()来比较两个NaN结果是true,即Object.is(NaN, NaN)的结果是true
  • typeof NaN"number"
  • 方法parseInt()parseFloat()在不能解析指定的字符串时就返回这个值;
  • 可以使用isNaN来判断一个变量是不是NaN,它是JS内置对象Number上的静态方法。

(关于第三点,大家可以看一下我之前的一篇文章哟,里面的「第二补:JS类型检测-Object.is()和===的区别」有提到:读《三元-JS灵魂之问》总结,给自己的一份原生JS补给(上))

实现一个isNaN:

对于isNaNpolyfill实现起来就比较简单了,只需要利用NaN不等于它自身的这一点即可:

const isNaN = v => v !== v;

🎁第7期第7题:b与strong的区别以及i和em的区别?

b与strong的区别以及i和em的区别?

首先描述一下这四个标签的显示效果吧:

  • <b><strong>包裹的文字会被加粗
  • <i><em>包裹的文字会以斜体的方式呈现

HTML代码:

<b>霖呆呆</b>
<strong>霖呆呆</strong>
<i>霖呆呆</i>
<em>霖呆呆</em>

效果如下:

咱再来说说他们在语义上的区别吧。

  • <b>标签和<i>标签都是自然样式标签,都只是在样式上加粗和变斜,并没有什么实际的意义。并且据了解,这两种标签在HTML4.01中已经不被推荐使用了。
  • <strong>标签和<em>的话是语义样式标签。就像是<h1>、<h2>一样都有自己的语义。<em>表示一般的强调文本,而<strong>表示更强的强调文本。另外在使用阅读设备的时候,<strong>会重读(这点呆呆也没有实践过所以不太敢保证)。

⛺️第2期第3题:用正则写一个根据name获取cookie中的值的方法

用正则写一个根据name获取cookie中的值的方法

function getCookie(name) {
  var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]*)'));
  if (match) return unescape(match[2]);
}
  1. 获取页面上的cookie可以使用 document.cookie
    这里获取到的是类似于这样的字符串:
'username=lindaidai; user-id=12345; user-roles=home, me, setting'

可以看到这么几个信息:

  • 每一个cookie都是由 name=value 这样的形式存储的
  • 每一项的开头可能是一个空串''(比如username的开头其实就是), 也可能是一个空字符串' '(比如user-id的开头就是)
  • 每一项用";"来区分
  • 如果某项中有多个值的时候,是用","来连接的(比如user-roles的值)
  • 每一项的结尾可能是有";"的(比如username的结尾),也可能是没有的(比如user-roles的结尾)
  1. 所以我们将这里的正则拆分一下:
  • '(^| )'表示的就是获取每一项的开头,因为我们知道如果^不是放在[]里的话就是表示开头匹配。所以这里(^| )的意思其实就被拆分为(^)表示的匹配username这种情况,它前面什么都没有是一个空串(你可以把(^)理解为^它后面还有一个隐藏的'');而| 表示的就是或者是一个" "(为了匹配user-id开头的这种情况)
  • +name+这没什么好说的
  • =([^;]*)这里匹配的就是=后面的值了,比如lindaidai;刚刚说了^要是放在[]里的话就表示"除了^后面的内容都能匹配",也就是非的意思。所以这里([^;]*)表示的是除了";"这个字符串别的都匹配(*应该都知道什么意思吧,匹配0次或多次)
  • 有的大佬等号后面是这样写的'=([^;]*)(;|$)',而最后为什么可以把'(;|$)'给省略呢?因为其实最后一个cookie项是没有';'的,所以它可以合并到=([^;]*)这一步。
  1. 最后获取到的match其实是一个长度为4的数组。比如:
[
  "username=lindaidai;",
  "",
  "lindaidai",
  ";"
]
  • 第0项:全量
  • 第1项:开头
  • 第2项:中间的值
  • 第3项:结尾

所以我们是要拿第2项match[2]的值。

  1. 为了防止获取到的值是%xxx这样的字符序列,需要用unescape()方法解码。

☁️第4期第4题:JS三种加载方式的区别

JS三种加载方式的区别

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

正常模式

这种情况下 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。

☁️第4期第3题:Babel是如何编译Class的?

Babel是如何编译Class的?

(参考来源:相学长-你的Tree-Shaking并没什么卵用)

就拿下面的类来说:

class Person {
  constructor ({ name }) {
    this.name = name
    this.getSex = function () {
      return 'boy'
    }
  }
  getName () {
    return this.name
  }
  static getLook () {
    return 'sunshine'
  }
}

如果你对Class或者里面的static还不熟悉的话可得先看看呆呆的这篇文章了:《【何不三连】比继承家业还要简单的JS继承题-封装篇(牛刀小试)》

当我们在使用babel的这些plugin或者使用preset的时候,有一个配置属性loose它默认是为false,在这样的条件下:

Class编译后:

  • 总体来说Class会被封装成一个IIFE立即执行函数
  • 立即执行函数返回的是一个与类同名的构造函数
  • 实例属性和方法定义在构造函数内(如namegetSex())
  • 类内部声明的属性方法(getName)和静态属性方法(getLook)是会被Object.defineProperty所处理,将其可枚举属性设置为false

(下面的代码看着好像很长,其实划分一下并没有什么东西的)

编译后的代码:

"use strict";

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

function _defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    var descriptor = props[i];
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor);
  }
}

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}

var Person = /*#__PURE__*/ (function () {
  function Person(_ref) {
    var name = _ref.name;

    _classCallCheck(this, Person);

    this.name = name;

    this.getSex = function () {
      return "boy";
    };
  }

  _createClass(
    Person,
    [
      {
        key: "getName",
        value: function getName() {
          return this.name;
        },
      },
    ],
    [
      {
        key: "getLook",
        value: function getLook() {
          return "sunshine";
        },
      },
    ]
  );

  return Person;
})();

为什么Babel对于类的处理会使用Object.defineProperty这种形式呢?它和直接使用原型链有什么不同吗?

  • 通过原型链声明的属性和方法是可枚举的,也就是可以被for...of...搜寻到
  • 而类内部声明的方法是不可枚举的

所以,babel为了符合ES6真正的语义,编译类时采取了Object.defineProperty来定义原型方法。

但是可以通过设置babelloose模式(宽松模式)为true,它会不严格遵循ES6的语义,而采取更符合我们平常编写代码时的习惯去编译代码,在.babelrc中可以如下设置:

{
  "presets": [["env", { "loose": true }]]
}

比如上述的Person类的属性方法将会编译成直接在原型链上声明方法:

"use strict";

var Person = /*#__PURE__*/function () {
  function Person(_ref) {
    var name = _ref.name;
    this.name = name;

    this.getSex = function () {
      return 'boy';
    };
  }

  var _proto = Person.prototype;

  _proto.getName = function getName() {
    return this.name;
  };

  Person.getLook = function getLook() {
    return 'sunshine';
  };

  return Person;
}();

总结

  • 当使用Babel编译时默认的loosefalse,即非宽松模式

  • 无论哪种模式,转换后的定义在类内部的属性方法是被定义在构造函数的原型对象上的;静态属性被定义到构造函数上

  • 只不过非宽松模式时,这些属性方法会被_createClass函数处理,函数内通过Object.defineProperty()设置属性的可枚举值enumerablefalse

  • 由于在_createClass函数内使用了Object,所以非宽松模式下是会产生副作用的,而宽松模式下不会。

  • webpack中的UglifyJS依旧还是会将宽松模式认为是有副作用的,而rollup程序流程分析的功能,可以更好的判断代码是否真正产生副作用,所以它会认为宽松模式没有副作用。

    (副作用大致理解为:一个函数会、或者可能会对函数外部变量产生影响的行为。)

💪第6期第1题:[,,,]的长度

[,,,]的长度

(题目来源: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

☁️第4期第6题:如何解决inline-block空白问题?

原本的代码为:

<style>
.sub {
  background: hotpink;
  display: inline-block;
}
</style>
<body>
  <div class="super">
    <div class="sub">
      孩子
    </div>
    <div class="sub">
      孩子
    </div>
    <div class="sub">
      孩子
    </div>
  </div>
</body>

效果为:

可以看到每个孩子之间都会有一个空白。inline-block元素间有空格或是换行,因此产生了间隙。

解决办法:

  • (1) 删除html中的空白:不要让元素之间换行:

    <div class="super">
      <div class="sub">
        孩子
      </div><div class="sub">
        孩子
      </div><div class="sub">
        孩子
      </div>
    </div>
  • (2) 设置负的边距:你可以用负边距来补齐空白。但你需要调整font-size,因为空白的宽度与这个属性有关系。例如下面这个例子:

    .sub {
      background: hotpink;
      display: inline-block;
      font-size:16px;
      margin-left: -0.4em;
    }
  • (3) 给父级设置font-size: 0:不管空白多大,由于空白跟font-size的关系,设置这个属性即可把空白的宽度设置为0。但是如果你的子级有字的话,也得单独给子级设置字体大小。

  • (4) 注释

    <div class="super">
      <div class="sub">
        孩子
      </div><!--
      --><div class="sub sub2">
        孩子
      </div><!--
      --><div class="sub">
        孩子
      </div>
    </div>

☁️第4期第2题:实现一个pipe函数

实现一个pipe函数

(题目来源:30-seconds-of-interviews)

如下所示,实现一个pipe函数:

const square = v => v * v
const double = v => v * 2
const addOne = v => v + 1
const res = pipe(square, double, addOne)
console.log(res(3)) // 19; addOne(double(square(3)))

首先看到这道题,pipe是可以接收任意个数的函数,并且返回的是一个新的函数res

(1) pipe基本结构

那么我们可以得出pipe的基本结构是这样的:

const pipe = function (...fns) {
  return function (param) {}
}

它本身是一个函数,然后我们可以利用...fns获取到所有传入的函数参数square、double这些。

之后它会返回一个函数,且这个函数中是可以接收参数param的。

(2) 返回的函数

接下来的逻辑主要就是在于返回的函数上了,在这个返回的函数中,我们需要对param进行层层处理。

OK👌,这很容易就让人想到了...reduce...

我们可以对fns函数数组使用reduce,之后reduce的初始值为传入的参数param

让我们一起来看看最终的代码:

const pipe = function (...fns) {
  return function (param) {
    return fns.reduce((pre, fn) => {
      return fn(pre)
    }, param)
  }
}

最终返回的是经过fns数组中所有函数处理过的值。

当然,我们也可以用简洁点的写法:

const pipe = (...fns) => param => fns.reduce((pre, fn) => fn(pre), param)

这样就得到了我们想要的pipe函数了:

const square = v => v * v
const double = v => v * 2
const addOne = v => v + 1
const pipe = (...fns) => param => fns.reduce((pre, fn) => fn(pre), param)
const res = pipe(square, double, addOne)
console.log(res(3)) // 19; addOne(double(square(3)))

🎁第7期第2题:转换类数组的几种方式

转换类数组的几种方式

类数组概念

拥有length属性和若干索引属性的对象就被称为类数组,它和数组类似,但是不能调用数组的方法。

常见类数组:

DOM方法返回的批量的DOM集合, arguments,另外函数也可以被看为是类数组,因为它拥有length属性,length的值就是它可接收的参数的个数。

转换为数组

先让我们来定义一个类数组:

function test () {
  console.log(Array.isArray(arguments)) // false
}
test('霖', '呆', '呆')

然后来看看可以有哪几种转换方法:

  1. 通过call和数组的slice方法:
[].slice.call(arguments)

// 当然也可以是这样,因为slice是Array.prototype上的方法

Array.prototype.slice.call(arguments)
  1. 通过call和数组的splice方法:
[].splice.call(arguments)
  1. 通过apply和数组的concat方法:
[].concat.apply(arguments)
  1. 通过Array.from()
Array.from(arguments)
  1. ...展开操作符:
[...arguments]

来写个简写吧:

  • slice + call
  • splice + call
  • concat + apply
  • Array.from
  • ...

不过貌似这个不用特意去记,想一下数组有哪些方法可以用基本就能想起来了。

⛽️第5期第3题:按位取反,为什么`~2 = -3`?

按位取反,为什么~2 = -3?

接下来,分享一道与JavaScript原生无关的题目吧,主要也是看到群里有小伙伴问了关于按位取反~的用法,这边统一科普一下,😁。

正常一个数字,例如12,或者-1-2

如果我们对它们进行按位取反的话,结果会是这样:

  • ~1 = -2
  • ~2 = -3
  • ~-1 = 0
  • ~-2 = 1

看不懂没关系,让我们来一步步看看实现的过程哈。

在这里其实是分了正数和负数的,因为符号不同取反的过程也会不同。

1.1 正数按位取反

先让我们来看看正数的按位取反。

比如先看看~1 = -2,过程如下:

1. 十进制转为二进制原码

首先将十进制的1转化为二进制原码为:0000 0001

2. 二进制原码按位取反

之后将原码按位取反:

也就是将0000 0001 => 1111 1110

(取反应该知道啥意思吧?就是0换成11换成0)

3. 取反后的二进制转为原码

再将取反后的二进制码转为原码二进制:

也就是将1111 1110 => 1000 0010

这里你估计看着都点懵了,当我们将取反后的二进制转为原码二进制的时候,其实是有以下两步的:

  1. 需要判断取反后的二进制的第一个位是不是1,这个第一位我们称之为符号位,如果是1的话就表示即将要转成的数是一个负数,如果是0的话表示即将要转的数是一个正数,这个符号位是不能动的;在这里我们可以看到1111 1110的第一位是1,所以表示即将要转的数是一个负数,同时我们不动它。
  2. 然后将除了第一位以外其它位数取反并+1。所以会有这么两个过程:
    • 1111 1110 => 1000 0001
    • 1000 0001 => 1000 0010 (这步是对上一步的结果+1,因为上一步的最后一个数是1,所以它再加上1就需要向前进一位了,因此变成了1000 0010)

4. 将原码二进制转为十进制

最后一步就是将我们前面得到的1000 0010这个二进制转化为十进制了。

第一位符号位,是1,则表示是个负数,所以结果为-2

OK👌,搞懂了这个步骤之后再让我们自己来转换一下~2 = -3吧:

1. 0000 0010
2. 1111 1101
3. 1000 0011
4. -3

正数按位取反总结

  1. 十进制转为二进制原码
  2. 二进制原码按位取反
  3. 符号位保留,其余位取反+1
  4. 二进制原码转为十进制

1.2 负数按位取反

负数的按位取反和上面就有些不一样了,主要是第二步和第三步调换一下顺序:

  1. 十进制转为二进制原码
  2. 符号位保留,其余位取反+1
  3. 二进制原码按位取反
  4. 二进制原码转为十进制

例如:~-1 =0 的转换过程:

1. 十进制转为二进制原码

这步和正数按位取反是一样的:

-1 => 1000 0001

2. 符号位保留,其余位取反+1

转换过程:

  • 1000 0001 => 1111 1110 (取反)
  • 1111 1110 => 1111 1111 (取反后 + 1)

3. 二进制原码按位取反

将刚刚得到的再进行按位取反:

1111 1111 => 0000 0000

4. 二进制原码转为十进制

0000 0000 => 0

OK👌,现在自己来转换一下~-2 = 1吧:

1. 1000 0010
2. 1111 1110
3. 0000 0001
4. 1

这里没啥诀窍,关键就是要记住转换的过程然后不断的练习吧 😂。

另外关于~~的用法还可以看呆呆的另一篇文章哟《JS中按位取反运算符~及其它运算符》

🍃第1期第3题:实现 arr[-1] = arr[arr.length - 1]

实现 arr[-1] = arr[arr.length - 1]

这道题的意思是:提供一个createArr()方法,用此方法创建的数组满足arr[-1] = arr[arr.length - 1]

function createArr (...elements) {
  // ...代码
  return arr
}
var arr1 = createArr(1, 2, 3)
console.log(arr1[-1]) // 3
console.log(arr1[-2]) // 2

解题思路:

其实对于这类题目,我首先想到的会是Object.defineProperty()或者Proxy。因为这里涉及到了对数组值的获取,显然用Proxy是比较合适的。什么?你问我为什么不用Object.defineProperty()?因为这个方法是针对于对象的某一个属性的呀,对数组来说不合适。

所以对于这道题,我们也许可以使用Proxy代理每次传入进来的下标,也就是重写一下数组的get方法,在这个方法中我们去处理这方面的逻辑,一起来看看代码吧😊:

function createArr (...elements) {
  let handler = {
    get (target, key, receiver) { // 第三个参数传不传都可以
      let index = Number(key) // 或者 let index = ~~key
      if (index < 0) {
        index = String(target.length + index)
      }
      return Reflect.get(target, index, receiver)
    }
  }
  let target = [...elements] // 创建一个新数组
  return new Proxy(target, handler)
}
var arr1 = createArr(1, 2, 3)
console.log(arr1[-1]) // 3
console.log(arr1[-2]) // 2

注意点:

  • get接收到的第二个参数key表示的是数组下标,它是字符串形式,所以需要转为Number,当然方法有很多种了,使用Number()也可以,使用~~双非按位取反运算符也可以。对比于Number()的好处就是Number(undefined)会转换为NaN;但是使用~~能保证一直是数字,~~undefined === 0。(什么?你还不知道区别?那你得看霖呆呆的这篇文章了:JS中按位取反运算符~及其它运算符)

  • 接下来只需要判断一下传入进来的下标是不是小于0的,小于0的话加上数组的长度就可以了

  • 然后返回index这一项使用的是Reflect.get(target, index)。什么?为什么不直接用target[index]?当然这样也可以,对比target[index]的区别就是Reflect.get(target, index)如果传入的target不是一个Object的话(数组也属于对象),就会报一个类型错误TypeError,而target[index]返回的会是一个undefined。比如这样:

    var obj = 5
    console.log(obj['b']) // undefined
    console.log(Reflect.get(obj, 'b')) // Uncaught TypeError: Reflect.get called on non-object

扩展点:

呆呆这边主要是想扩展一下get的第三个参数receiver(接受者),在MDN上的解释是:

如果target对象中指定了getterreceiver则为getter调用时的this值。

来看个例子理解一下😊。

案例一

例如我们开始有这么一个对象:

var obj = {
  fn: function () {
    console.log('lindaidai')
  }
}

现在使用Proxy来赋值到obj1中:

var obj = {
  fn: function () {
    console.log('lindaidai')
  }
}
var obj1 = new Proxy(obj, {
  get (target, key, receiver) {
    console.log(receiver === obj1) // true
    console.log(receiver === target) // false
    return target[key]
  }
})
obj1.fn()

可以看到,receiver表示的是obj1这个新的代理对象,target表示的是被代理的对象obj

所以,receiver可以表示使用代理对象本身

案例二

另一种情况,receiver也可以表示是从其继承的对象

var proxy = new Proxy({}, {
  get (target, key, receiver) {
    return receiver;
  }
})
console.log(proxy.getReceiver === proxy) // true
var inherits = Object.create(proxy)
console.log(inherits.getReceiver === inherits) // true

这个案例中,我新建了一个空对象的代理对象proxy,使用proxy.getReceiver获取它的receiver,发现它就是代理对象本身。

而如果我将这个代理对象作为一个原型对象,创建出一个新的对象inherits,也就是实现了原型式继承,那么这时候receiver的值就是这个被继承的对象inherits

总结

  • 可以使用Proxy代理,来改变每次获取数组的值。
  • Proxyget中,第二个参数是字符串,即使传入的是数组下标。
  • 对比于Number()的好处就是Number(undefined)会转换为NaN;但是使用~~能保证一直是数字,~~undefined === 0
  • 对比target[index]的区别就是Reflect.get(target, index)如果传入的target不是一个Object的话(数组也属于对象),就会报一个类型错误TypeError,而target[index]返回的会是一个undefined
  • Proxyget中,第三个参数receiver通常为使用代理对象本身或从其继承的对象。

⛽️第5期第1题:实现mask函数将"123456"转为"##3456",只保留最后四个字符

实现mask函数将"123456"转为"##3456",只保留最后四个字符

(题目来源:https://github.com/30-seconds/30-seconds-of-interviews)

首先介绍一下题目的意思吧😄,案例🌰如下:

const mask = (str, maskChar = '#') => {
  // 代码
}
console.log(mask('123456')); // '##3456'
console.log(mask('lindaidai')); // '#####idai'

这道题的难度应该没有那么大,处理方式也有很多。呆呆这边主要是讲解一下如何使用padStart来实现的。

简单介绍一下padStart方法吧,它是ES8新增的实例函数,与它作用差不多的还有一个叫padEnd的函数:

  • String.prototype.padStart
  • String.prototype.padEnd

作用:允许将空字符串或其他字符串添加到原始字符串的开头或结尾。

语法

padStart(targetLength, [padString])
  • targetLength: 必填,当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
  • padString: 可选,填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断,此参数的缺省值为" "

例如:

'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'

哈哈,另外想要了解如何实现一个padStart的小伙伴可以看呆呆之前一篇文章哟:DD每周前端七题详解-第二期

言归正传,让我们回到这道题目哈,首先让我们来处理一下输入参数边界的情况,例如输入的str不存在或者长度小于4的时候:

const mask = (str, maskChar = '#') => {
  if (!str || str.length <= 4) return str;
}

其次,我们可以只保留住str的末尾四个字符,然后使用padStart将这四个字符填充至str.length即可,如下:

const mask = (str, maskChar = '#') => {
  if (!str || str.length <= 4) return str;
  return str.slice(-4).padStart(str.length, maskChar);
}
  • slice不会影响原本的字符串
  • 使用padStart填充即可

🍃第1期第5题:addEventListener函数的第三个参数

addEventListener函数的第三个参数

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

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

🎁第7期第6题:空元素(单标签)元素有哪些?

空元素(单标签)元素有哪些?

首先说一下空元素或者说是单标签元素是什么吧,其实就是标签内没有内容的 HTML 标签,例如:

<br />
<hr />
<input />
<img />
<link />
<meta>
  • 以上这些标签加不加"/"都可以

🌶️第3期第1题:使用delete删除数组元素,其长度会改变吗?

使用delete删除数组元素,其长度会改变吗?

(题目来源:haizlin/fe-interview#2462)

咱来写个案例🌰看看就知道了:

var arr = [1, 2, 3]
delete arr[1]
console.log(arr)
console.log(arr.length)

结果如下:

通过结果,我们可以得出结论:使用delete删除数组元素,其长度是不会改变的。

关于这一点,大家可以把数组理解为是一个特殊的对象,其中的每一项转换为对象的伪代码为:

key: value
// 对应:
0: 1
1: 2
2: 3
length: 3

所以我们使用delete操作符删除一个数组元素时,相当于移除了数组中的一个属性,被删除的元素已经不再属于该数组。但是这种改变并不会影响数组的length属性。

扩展:

  • 如果你想让一个数组元素继续存在但是其值是 undefined,那么可以使用将 undefined 赋值给这个元素而不是使用 delete。例如:arr[1] = undefined
  • 如果你想通过改变数组的内容来移除一个数组元素,请使用splice() 方法。例如:arr.splice(1, 1)

⛺️第2期第6题:如何画扇形?三角形?

如何画扇形?三角形?

/*扇形*/
.sector {
  width: 0;
  height: 0;
  border: 100px solid red;
  border-color: red transparent transparent transparent;
  border-radius: 50%;
}
/*或者*/
.sector {
  width: 100px;
  height: 100px;
  border: 100px solid transparent;
  border-top-color: red;
  box-sizing: border-box; /* 这步很重要 */
  border-radius: 50%;
}
/*三角形*/
.triangle {
  width: 0;
  height: 0;
  border: 100px solid red;
  border-color: red transparent transparent transparent;
}
/*或者*/
.triangle {
  width: 100px;
  height: 100px;
  border: 100px solid transparent;
  border-top-color: red;
  box-sizing: border-box;
}

💪第6期第4题:form表单中的label标签的作用?

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

⛽️第5期第6题:实现九宫格布局

实现九宫格布局

实现效果如下:

先来看一下HTML方面的代码:

<div id="container">
    <div class="item item-1">1</div>
    <div class="item item-2">2</div>
    <div class="item item-3">3</div>
    <div class="item item-4">4</div>
    <div class="item item-5">5</div>
    <div class="item item-6">6</div>
    <div class="item item-7">7</div>
    <div class="item item-8">8</div>
    <div class="item item-9">9</div>
</div>

还有一些item上的基础css代码:

#container {
    /* css代码 */
}

.item {
    font-size: 2em;
    text-align: center;
    border: 1px solid #e5e4e9;
}

.item-1 {
    background-color: #ef342a;
}

.item-2 {
    background-color: #f68f26;
}

.item-3 {
    background-color: #4ba946;
}

.item-4 {
    background-color: #0376c2;
}

.item-5 {
    background-color: #c077af;
}

.item-6 {
    background-color: #f8d29d;
}

.item-7 {
    background-color: #b5a87f;
}

.item-8 {
    background-color: #d0e4a9;
}

.item-9 {
    background-color: #4dc7ec;
}

方案一

第一种方案可以使用浮动+百分比:

#container {
    width: 150px;
    height: 150px;
}
.item {
    float: left;
    width: 33.33%;
    height: 33.33%;
    box-sizing: border-box;
    font-size: 2em;
    text-align: center;
    border: 1px solid #e5e4e9;
}

方案二

还可以使用flex布局的方式:

#container {
    width: 150px;
    height: 150px;
    display: flex;
    flex-wrap: wrap;
}
.item {
    width: 33.33%;
    height: 33.33%;
    box-sizing: border-box;
    font-size: 2em;
    text-align: center;
    border: 1px solid #e5e4e9;
}

方案三

另外的话,也许还可以试试grid

#container {
    display: grid;
    grid-template-columns: 50px 50px 50px;
    grid-template-rows: 50px 50px 50px;
}
.item {
    font-size: 2em;
    text-align: center;
    border: 1px solid #e5e4e9;
}

答案参考:haizlin/fe-interview#2270

#35

七、说说will-change

will-changeCSS3新增的标准属性,它的作用很单纯,就是"增强页面渲染性能",当我们在通过某些行为触发页面进行大面积绘制的时候,浏览器往往是没有准备,只能被动的使用CUP去计算和重绘,由于事先没有准备,对于一些复杂的渲染可能会出现掉帧、卡顿等情况。

will-change则是在真正的行为触发之前告诉浏览器可能要进行重绘了,相当于浏览器把CUP拉上了,能从容的面对接下来的变形。

常用的语法主要有:

  • whil-change: scroll-position; 即将开始滚动
  • will-change: contents; 内容要动画或者变化了
  • will-transform; transform相关的属性要变化了(常用)

注意:

  • will-change虽然可以开启加速,但是一定要适度使用
  • 开启加速的代价为手机的耗电量会增加
  • 使用时遵循最小化影响原则,可以对伪元素开启加速,独立渲染
  • 可以写在伪类中,例如hover中,这样移出元素的时候就会自动removewill-change
  • 如果使用JS添加了will-change,注意要及时remove掉,方式就是style.willChange = 'auto'

⛺️第2期第5题:如何阻止冒泡和默认事件(兼容写法)

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

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;
  }
}

☁️第4期第1题:following function return?

following function return?

以下代码输出什么?

function getName () {
  return
  {
    name: 'LinDaiDai'
  }
}
console.log(getName())

这道题其实涉及到了JavaScript中的一个名为ASI的机制,全名Automatic Semicolon Insertion,好吧,不要整的那么高大上了,其实就是自动插入分号的机制。

按照ECMAScript标准,一些 特定语句(statement) 必须以分号结尾。分号代表这段语句的终止。但是有时候为了方便,这些分号是有可以省略的。这种情况下解释器会自己判断语句该在哪里终止。这种行为被叫做“自动插入分号”,简称ASI (Automatic Semicolon Insertion) 。实际上分号并没有真的被插入,这只是个便于解释的形象说法。

也就是说这道题在执行的时候,会在return关键字后面自动插入一个分号,所以这道题就相当于是这样:

function getName () {
  return;
  {
    name: 'LinDaiDai'
  }
}
console.log(getName())

因此最终的结果也就是undefined

💪第6期第7题:块状元素width:100%与width:auto的区别?

块状元素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的时候,容易使其突破父级框,破坏布局。

☁️第4期第5题:如何让`<p>测试 空格</p>`这两个词之间的空格变大?

如何让<p>测试 空格</p>这两个词之间的空格变大?

(题目来源:haizlin/fe-interview#2440)

这道题的意思是说,原本有一段HTML代码如下:

<p>测试 空格</p>

"测试""空格"两个词之间有一个空格,然后如何将这个空格变大。

这边有这么两种方法:

  • 通过给p标签设置word-spacing,将这个属性设置成自己想要的值。
  • 将这个空格用一个span标签包裹起来,然后设置span标签的letter-spacing或者word-spacing

我分别用letter-spacingword-spacing来处理了pspan标签:

<style>
  .p-letter-spacing {
    letter-spacing: 10px;
  }
  .p-word-spacing {
    word-spacing: 10px;
  }
  .span-letter-spacing {
    letter-spacing: 10px;
  }
  .span-word-spacing {
    word-spacing: 10px;
  }
</style>
<body>
  <p>测试 空格</p>
  <p class="p-letter-spacing">测试 空格</p>
  <p class="p-word-spacing">测试 空格</p>
  <p>测试<span class="span-letter-spacing"> </span>空格</p>
  <p>测试<span class="span-word-spacing"> </span>空格</p>
</body>

让我们一起来看看效果:

大家可以看到效果,我用letter-spacingword-spacing处理p标签,是会呈现不同的效果的,letter-spacing把中文之间的间隙也放大了,而word-spacing则不放大中文之间的间隙。

span标签中只有一个空格,所以letter-spacingword-spacing效果一样。

因此我们可以得出letter-spacingword-spacing的结论:

  • letter-spacingword-spacing这两个属性都用来添加他们对应的元素中的空白。
  • letter-spacing添加字母之间的空白,而word-spacing添加每个单词之间的空白。
  • word-spacing对中文也是有效的,只不过它以空格作为区分。

🍃第1期第2题:请创建一个长度为100,值为对应下标的数组

请创建一个长度为100,值为对应下标的数组

// cool点的写法:
[...Array(100).keys()]

// 其他方法:
Array(100).join(",").split(",").map((v, i) => i)
Array(100).fill().map((v, i) => i)

后面两种方法相信大家都能看到懂,也没啥好说的了😄,让我们仔细看一下这个比较cool的写法:

  • 使用Array(100)创建一个内容全为empty的数组:[empty x 100]

  • 使用keys()方法从数组创建一个包含数组键的可迭代对象:

    可迭代对象是不是让你想到了generator,没错的,这里的keys()Array.prototype上的方法,大家可能会把它和我们之前用的比较多的Object.keys()搞混。

    说一下它的作用吧,它其实就像刚刚介绍的一样,会创建一个可迭代对象,那么小伙伴们应该知道,一个可迭代对象返回的数据会是这样的:

    { value: 0, done: false }
    

    value为这次的返回值,done为当前可迭代对象的逻辑块是否执行完成。

    所以你会看到以下这段代码是会这样执行的:

    let it = Array(100).keys()
    console.log(it.next) // {value: 0, done: false}
    console.log(it.next) // {value: 1, done: false}
    console.log(it.next) // {value: 2, done: false}
    console.log(it.next) // {value: 3, done: false}
  • 至于[...arr]这个就是ES6的写法了,转化为数组,当然你也可以直接用Array.from()。这两种方式都支持将可迭代对象转换为数组。

🌶️第3期第4题:target="_blank"有哪些问题?

target="_blank"有哪些问题?

存在问题:

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

解决方案:

  1. 尽量不用target="_blank"

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

(参考来源:慎用target="_blank"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.