每日一题

188 阅读6分钟

😀HTML && CSS

1. BFC(块级格式化上下文)

BFC的概念

BFCBlock Formatting Context的简写,可以直译成块级格式化上下文。它会创建一个特殊的区域,在这个区域中,只有block box参与布局;规定了在这个区域中如何进行布局,如何进行定位,区域内元素的相互关系和相互作用。这个区域不受外界影响。

如何形成BFC

  • 根元素<html>或其他包含根元素的元素
  • 浮动元素(元素的float不是none
  • 绝对定位元素(元素的position为absolute或fixed
  • display的值为inline-block、table-cell、table-caption
  • overflow的值不为visible的块元素
  • 弹性元素(display为flex或inline-flex元素的直接子元素
  • 网格元素(display为grid或inline-grid元素的直接子元素

BFC的特点

  • 内部的box会独占宽度,且在垂直方向上一个接一个排列
  • box在垂直方向的间距由margin属性决定,但是同一个BFC的两个相邻box的margin会重叠
  • 每个box在水平方向上的左边缘与BFC的左边缘相对齐,即使存在浮动也是如此
  • BFC区域不会与浮动元素重叠,而是会依次排列
  • BFC区域是一个独立的渲染容器,容器内的元素和BFC区域外的元素不会有任何干扰
  • 浮动元素的高度也参与BFC的高度计算 根据以上特点,可以用于实现边距重叠,清除浮动,自适应多栏布局
2. 如何实现水平垂直居中

居中元素固定宽高(width: 100px; height: 100px;)

// 1. [absolute] + [-margin]
// 父元素设置
position: relative;
// 子元素设置
position: absolute;
left: 50%;
top: 50%;
margin-left: -50px;
margin-top: -50px;

// 2. [absolute] + [margin auto]
// 父元素设置
position: relative;
// 子元素设置
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;

// 3. [absolute] + [calc]
// 父元素设置
position: relative;
// 子元素设置
position: absolute;
left: calc(50% - 50px);
top: calc(50% - 50px);

居中元素不固定宽高

// 1. [absolute] + [transform]
// 父元素设置
position: relative;
// 子元素设置
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%); // transform的translate的属性可以基于自身的宽高设置百分比

// 2. [lineheight]
// 父元素设置
line-height: 200px;
text-align: center;
// 子元素设置
display: inline-block;
vertical-align: middle;
line-height: inherit;

// 3. [css-table]
// 父元素设置
display: table-cell;
text-align: center;
vertical-align: middle;
// 子元素设置
display: inline-block;

// 4. [flex]
// 父元素设置
display: flex;
justify-content: center;
align-items: center;

// 5. [grid]
// 父元素设置
display: grid;
// 子元素设置
justify-self: center;
align-self: center;
3. 实现主题切换
// 1. 使用css变量实现主题切换(ie兼容问题可以使用插件解决)
<template>
  <button @click="handleToggleTheme">toggle-theme</button>
  <p>努力奋斗!</p>
</template>

<script setup>
  import { onMounted } from "vue";

  onMounted(() => {
    if (localStorage.getItem("blackTheme")) {
      document.body.classList.add("black-theme");
    }
  });

  function handleToggleTheme() {
    if (document.body.classList.contains("black-theme")) {
      document.body.classList.remove("black-theme");
      localStorage.removeItem("blackTheme");
    } else {
      document.body.classList.add("black-theme");
      localStorage.setItem("blackTheme", true);
    }
  }
</script>

<style>
  body {
    color: var(--text-color);
    background: var(--bg-color);
  }
  :root {
    --bg-color: white;
    --text-color: black;
  }
  .black-theme {
    --bg-color: black;
    --text-color: white;
  }
</style>

😁JavaScript

1. JavaScript数据类型及其判断

JavaScript中具有7种数据类型,分别是number、string、boolean、undefined、null、object、symbol(ES6)。前5种为基本类型。object类型包含了function、array、data等。常用的类型判断方法有:typeof、instanceof、Array.isArray、Object.prototype.toString、constructor

使用typeof判断数据类型

typeof 1  // number
typeof 'string' // string
typeof true  // boolean
typeof undefined  // undefined
typeof null  // object
typeof function() {}  // function
typeof []  // object
typeof {}  // object
typeof Symbol(1) // symbol

使用instanceof判断数据类型

instanceof用于检测实例对象的原型链上是否存在构造函数的prototype属性

function Person() {}
const people = new Person()
people instanceof Person  // true

5 instanceof Number  // false
new Number(5) instanceof Number  // true

Array.isArray只能判断是否为数组

Array.isArray([])  // true
Array.isArray(1)  // false

使用Object.prototype.toString判断数据类型(万能方法,任何类型都能判断出来)

Object.prototype.toString.call(1) // [object Number]
Object.prototype.toString.call('string') // [object String]
Object.prototype.toString.call(true) // [object Boolean]
Object.prototype.toString.call(undefined) // [object Undefined]
Object.prototype.toString.call(null)  // [object Null]
Object.prototype.toString.call(function() {}) // [object Function]
Object.prototype.toString.call([]) // [object Array]
Object.prototype.toString.call({}) // [object Object]
Object.prototype.toString.call(Symbol(1)) // [object Symbol]

// 封装getType方法
function getType(data) {
  return Object.prototype.toString.call(data).slice(8, -1).toLowerCase()
}

constructor返回的是构造函数本身

1.constructor  // ƒ Number() { [native code] }
'string'.constructor // ƒ String() { [native code] }
true.constructor  // ƒ Boolean() { [native code] }
function() {}.constructor  // ƒ Function() { [native code] }
[].constructor  // ƒ Array() { [native code] }
{}.constructor  // ƒ Object() { [native code] }
Symbol(1).constructor  // ƒ Symbol() { [native code] }
undefined.constructor  // TypeError
null.constructor  // TypeError
// undefined、null 读取constructor属性会报错!!!
2. 实现New

new关键字到底做了什么

  1. 创建一个空对象,这个对象将作为执行构造函数之后返回的对象实例
  2. 空对象的__proto__指向构造函数的prototype属性
  3. 空对象赋值给构造函数内部的this,并执行构造函数逻辑
  4. 返回创建的对象或构造函数的显示返回值
function myNew(fn, ...args) {
  // 不是函数报错
  if (typeof fn !== "function") {
    throw TypeError(`Expected a function, but get a ${typeof fn}`);
  }
  // let obj = {}; // 创建空对象
  // obj.__proto__ = fn.prototype; // 将对象的__proto__指向构造函数的prototype属性
  let obj = Object.create(fn.prototype); // 创建空对象,并将对象的__proto__指向构造函数的prototype属性
  // const result = fn.call(obj, ...args)
  const result = fn.apply(obj, args); // 对象作为this调用构造函数
  return typeof result === "object" ? result : obj;
}
3. Object.create 的实现原理
function myCreate(obj) {
  // __proto__在IE下已被禁用,兼容性不好,不建议使用
  // let emptyObj = {};
  // emptyObj .__proto__ = obj;
  // return emptyObj ;
  // 推荐方法
  // 使用空方法原型挂在目标对象,做实例化
  // 空方法实例化可以创建空对象
  // 方法的原型等价于实例化后的对象的原型链,即 (方法).prototype = (实例对象).__proto__ 
  function Fun() {};
  Fun.prototype = obj;
  return new Fun();
}
4. 实现多维数组扁平化
const array = [1, [2, [3, [4, [5, 6]]], 7, 8], 9, 10];
/**
 * 1. Array.prototype.flat(depth)
 * flat()方法会按照一个可指定的深度[depth]递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回
 * flat()方法会移除数组中的空项
 */
 array.flat(Infinity)  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
 
 // 2. reduce
function flatten(array) {
  return array.reduce((res, cur) => {
    return Array.isArray(cur) ? res.concat(flatten(cur)) : res.concat(cur);
  }, []);
}
flatten(array)  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// 3. forEach
function flatten(array, res = []) {
  array.forEach((item) => {
    Array.isArray(item) ? flatten(item, res) : res.push(item);
  });
  return res;
}
flatten(array)  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

/**
 * 4. while + Array.prototype.some()
 * some()方法测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回的是一个Boolean类型的值
 */
function flatten(array) {
  while (array.some((item) => Array.isArray(item))) {
    array = [].concat(...array);
  }
  return array;
}
flatten(array)  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// 5. 需要数组所有元素类型相同
function flatten(array) {
  return array
    .toString()
    .split(",")
    .map((x) => Number(x));
}
flatten(array)  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
5. 扁平化数组变成树
const array = [
  { name: "中国", id: "1", parentId: "0" },
  { name: "辽宁", id: "1-1", parentId: "1" },
  { name: "大连", id: "1-1-1", parentId: "1-1" },
  { name: "沈阳", id: "1-1-2", parentId: "1-1" },
  { name: "山东", id: "1-2", parentId: "1" },
  { name: "青岛", id: "1-1-3", parentId: "1-2" },
];
// 1. forEach
function getTree(arr, parentId = "0") {
  let array = [];
  arr.forEach((item) => {
    if (item.parentId === parentId) {
      let child = getTree(arr, item.id);
      if (child.length > 0) {
        item.child = child;
      }
      array.push(item);
    }
  });
  return array;
}
6. 实现 add(1)(2)(3)(4) = 10
// 1. 函数柯里化
// 柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

const add = w => x => y => z => w + x + y + z
add(1)(2)(3)(4)  // 10

// 2
function add() {
  const args = [...arguments];
  function fn() {
    args.push(...arguments);
    return fn;
  };
  fn.valueOf = function () {
    return args.reduce((sum, cur) => {
      return sum + cur;
    }, 0);
  };
  return fn;
}
add(1)(2)(3)(4).valueOf()  // 10
add(1)(2, 3)(4).valueOf()  // 10