前端面试常见题整理

本文介绍了如何在前端开发中通过多种方式让元素居中,包括固定宽度和高度、未知尺寸、定位transform、flex布局、table-cell布局,以及JavaScript动态调整。此外,讲解了清除浮动的三种常见方法:overflow、clear:both和伪元素::after。深入剖析了实例和手写代码,适合前端开发者参考。
摘要由CSDN通过智能技术生成

博主目前在蚂蚁集团-体验技术部,AntV/S2 是博主所在团队的开源项目——多维交叉分析表格,欢迎使用,感谢到 S2 github 仓库点赞 star,有任何关于前端面试、就业、技术问题都可给在文章后留言。

一、让元素居中的几种方式:

1、盒子宽度和高度是已知的。

思路:

  • 父元素相对定位;
  • 子元素绝对定位;
  • left: 50%; top: 50%;
  • margin-left: 负的一半宽度; margin-top: 负的一半高度。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>盒子垂直水平居中demo1</title>
    <style type="text/css">
        html,body {
            height: 100%;
            position: relative;
            overflow: hidden;
        }
        .box {
            height: 150px;
            width: 300px;
            background-color: antiquewhite;
            border: 2px solid #000;
            line-height: 146px;
            text-align: center;
            position: absolute;
            top: 50%;
            left: 50%;
            margin-top: -75px;
            margin-left: -150px;
        }
    </style>
</head>
<body>
    <div class="box">
        盒子垂直水平居中
    </div>
</body>
</html>

2、盒子宽度和高度是未知的(有高、宽,但是不知道)。

思路:

  • 父元素相对定位;
  • 子元素绝对定位;
  • top: 0; right: 0; bottom: 0; left: 0;
  • margin: auto;
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>盒子垂直水平居中demo2</title>
    <style type="text/css">
        html,body {
            height: 100%;
            position: relative;
            overflow: hidden;
        }
        .box {
            height: 150px;
            width: 300px;
            background-color: antiquewhite;
            border: 2px solid #000;
            line-height: 146px;
            text-align: center;
            position: absolute;
            top: 0;
            right: 0;
            bottom: 0;
            left: 0;
            margin: auto;
        }
    </style>
</head>
<body>
    <div class="box">
        盒子垂直水平居中
    </div>
</body>
</html>

3、平移:定位 + transform。

思路:

  • 父元素相对定位;
  • 子元素绝对定位;
  • top: 50%; left: 50%;
  • transform: translate(-50%, -50%);
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>盒子垂直水平居中demo3</title>
    <style type="text/css">
        html,body {
            height: 100%;
            position: relative;
            overflow: hidden;
        }
        .box {
            background-color: antiquewhite;
            border: 2px solid #000;
            line-height: 146px;
            text-align: center;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }
    </style>
</head>
<body>
    <div class="box">
        盒子垂直水平居中
    </div>
</body>
</html>

4、flex 布局。

思路:

  • 在父级元素中采用flex布局:display: flex; justify-content: center; align-items: center;
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>盒子垂直水平居中demo4</title>
    <style type="text/css">
        html,body {
            height: 100%;
            overflow: hidden;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .box {
            height: 150px;
            width: 300px;
            background-color: antiquewhite;
            border: 2px solid #000;
            line-height: 146px;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="box">
        盒子垂直水平居中
    </div>
</body>
</html>

5、父元素:display: table-cell 布局。

思路:

  • 父元素:display: table-cell; vertical-align: middle; text-align: center;
  • 父元素有固定的宽高;
  • 子元素:display: inline-block;
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>盒子垂直水平居中demo6</title>
    <style type="text/css">
        .box1 {
            height: 300px;
            width: 600px;
            background-color: blue;
            display: table-cell;
            vertical-align: middle;
            text-align: center;
            overflow: hidden;
        }
        .box2 {
            display: inline-block;
            height: 150px;
            width: 300px;
            background-color: antiquewhite;
            border: 2px solid #000;
            line-height: 146px;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="box1">
        <div class="box2">
            盒子垂直水平居中
        </div>
    </div>
</body>
</html>

6、通过JavaScript的方式。

思路:

  • 父元素相对定位;
  • 子元素绝对定位;
  • 获取父元素的 clientHeight 和 clientWidth;
  • 获取子元素的 offsetHeight 和 offsetWidth;
  • 计算子元素的 top 和 left。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>盒子垂直水平居中demo6</title>
    <style type="text/css">
        html,body {
            height: 100%;
            overflow: hidden;
            position: relative;
        }
        .box {
            height: 150px;
            width: 300px;
            background-color: antiquewhite;
            border: 2px solid #000;
            line-height: 146px;
            text-align: center;
            position: absolute;
        }
    </style>
</head>
<body>
    <div class="box" id="box">
        盒子垂直水平居中
    </div>
    <script type="text/javascript">
        let HTML = document.documentElement,
            winH = HTML.clientHeight,
            winW = HTML.clientWidth,
            boxH = box.offsetHeight,
            boxW = box.offsetWidth;
        box.style.top = (winH - boxH) / 2 + "px";
        box.style.left = (winW - boxW) / 2 + "px";
    </script>
</body>
</html>

二、清除浮动的方式:

浮动产生的原因:一个盒子使用CSS的float属性,导致父级对象盒子不能被撑开。

1、使用overflow: hidden;

子元素设置浮动后,直接给父元素添加overflow: hidden; 优缺点:语义化且代码量少,但是可能因为增加元素导致超出尺寸而被隐藏。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>使用overflow: hidden</title>
    <style type="text/css">
        .container {
            width: 800px;
            border: 5px solid #d4d4d4;
            overflow: hidden;
        }
        .box {
            width: 200px;
            height: 150px;
            float: left;
            background-color: #a0522d;
            margin: 10px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="box"></div>
        <div class="box"></div>
    </div>
</body>
</html>

2、使用clear: both

添加一个空div,给这个div加上clear: both;的属性;优缺点:更加语义化,但是增加了一个空div,违背了结构样式行为应该分离的原则。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>使用clear: both</title>
    <style type="text/css">
        .container {
            width: 800px;
            border: 5px solid #d4d4d4;
        }
        .box {
            width: 200px;
            height: 150px;
            float: left;
            background-color: #a0522d;
            margin: 10px;
        }
        .clear {
            clear: both;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="box"></div>
        <div class="box"></div>
        <div class="clear"></div>
    </div>
</body>
</html>

3、使用伪元素 ::after

在末尾添加一个看不见的块元素来清除浮动;不仅语义化,而且遵循了结构样式行为应该分离的原则,没有什么缺点,是目前的主流方法(推荐)。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>使用伪元素 ::after</title>
    <style type="text/css">
        .container {
            width: 800px;
            border: 5px solid #d4d4d4;
        }
        .container::after {
            content: '';
            display: block;
            clear: both;
        }
        .box {
            width: 200px;
            height: 150px;
            float: left;
            background-color: #a0522d;
            margin: 10px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="box"></div>
        <div class="box"></div>
    </div>
</body>
</html>

三、手撕 instanceof

instanceof的作用是:检测构造函数的prototype属性是否出现在某个实例对象的原型链上。

function _instanceof(instanceObject, classFunc) {
    let proto = instanceObject.__proto__;
    classFunc = classFunc.prototype;
    while (true) {
        if (proto === null) {
            return false;
        }
        if (proto === classFunc) {
            return true;
        }
        proto = proto.__proto__;
    }
}

四、手写深拷贝

// 定义检测数据类型的函数
const checkType = (targetNode) => {
    // 比如结果是:[object Array],返回 Array
    return Object.prototype.toString.call(targetNode).slice(8, -1);
}
const deepCopy = (target) => {
    // 判断拷贝的数据类型,初始化 result 为最终拷贝的数据
    let result;
    let targetType = checkType(target);
    if (targetType === "Object") {
        result = {};
    } else if (targetType === "Array") {
        result = []
    } else {
        return target;
    }
    // 遍历目标数据
    for (let i in target) {
        // 遍历数据中的每一项值
        let value = target[i];
        if (checkType(value) === "Object" || checkType(value) === "Array") {
            // 如果对象/数组中嵌套了对象/数组,递归:
            result[i] = deepCopy(value);
        } else {
            // 如果是基本数据类型或函数:
            result[i] = value;
        }
    }
    return result;
}

五、手写Promise.all和Promise.race

1、Promise.all

将多个Promise实例包装成一个新的Promise实例,成功返回一个结果数组,失败返回最先被reject状态的值。

Promise.all = function(iterator) {
    let len = iterator.length;
    // 用于计数,当等于len时就resolve
    let count = 0;
    // 用于存放结果
    let res = [];
    return new Promise((resolve, reject) => {
        for (let i = 0; i < len; i++) {
            Promise.resolve(iterator[i])
            .then(data => {
                res[i] = data;
                if (++count === len) {
                    resolve(res);
                }
            })
            .catch(err => {
                reject(err);
            })
        }
    })
}

2、Promise.race

返回多个Promise中结果获得最快的那个,不管结果本身是成功还是失败。

Promise.race = function(iterator) {
    return new Promise((resolve, reject) => {
        for (let p of iterator) {
            Promise.resolve(p)
            .then(res => {
                resolve(res);
            })
            .catch(err => {
                reject(err);
            })
        }
    })
}

六、手写继承的几种方式

1、原型链继承:

重点: 新实例的原型等于父类的实例
特点: 新实例可以继承的属性:实例构造函数的属性、父类构造函数的属性、父类原型的属性(新实例不能继承父类实例的属性)
缺点: 新实例无法向构造函数传参;继承单一;所有新实例都会共享父类实例的属性。

function Person() {
   this.name = "gfh";
}
Person.prototype.getName = function() {
   console.log("name:", this.name);
}
function Child() {}
// 新实例的原型等于父类的实例
Child.prototype = new Person();
let child1 = new Child();
child1.getName();

2、借助构造函数继承

重点: 用 call() 或 apply() 方法将父类构造函数引入子类构造函数。
特点: 只继承了父类构造函数的属性,没有继承父类原型的属性;解决了原型链继承的缺点;可以继承多个构造函数的属性(多个 call());在子实例中可向父实例传参。
缺点: 只能继承父类构造函数的属性(不能继承方法);无法实现构造函数的复用;每个新实例都有父类构造函数的副本,臃肿。

function Person() {
   this.name = "gfh";
   this.hobbies = ["eatting", "sleeping"];
}
Person.prototype.getName = function() {
   console.log("name:", this.name);
}
function Child(age) {
   // 通过 call() 方法将父类构造函数引入子类构造函数
   Person.call(this);
   this.age = age;
}
let child1 = new Child(24);
let child2 = new Child(25);
console.log(child1.name); // gfh
console.log(child1.age); // 24
child1.hobbies.push("coding");
console.log(child1.hobbies); // ["eatting", "sleeping", "coding"]
console.log(child2.hobbies); // ["eatting", "sleeping"]

3、组合继承(组合原型链继承和借用构造函数继承)(常用)

重点: 组合了两种模式的优点,传参和复用。
优点: 可以继承父类构造函数的属性,可以传参、可以复用;每个新实例引入的构造函数属性是私有的。
缺点: 调用两次父类构造函数,耗内存;子类的构造函数会代替原型上的那个父类构造函数。

function Person(name) {
   this.name = name;
   this.hobbies = ["eatting", "sleeping"];
}
Person.prototype.getName = function() {
   console.log("name:", this.name);
}
function Child(name,age) {
   // 构造函数继承
   Person.call(this, name);
   this.age = age;
}
// 原型链继承
Child.prototype = new Person();
let child1 = new Child("gfh",24);
let child2 = new Child("zs",25);
console.log(child1.name, "-", child1.age); // gfh-24
console.log(child2.name, "-", child2.age); // zs-25
child1.getName();  // name:gfh

4、原型式继承

重点: 用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了可以随意增添属性的实例或对象。Object.create()就是这个原理。
优点: 类似于复制一个对象,用函数来包装。
缺点: 所有实例都会继承原型上的属性;无法实现复用。

function CreateObj(obj) {
   function F() {}
   F.prototype = obj;
   console.log(obj.__proto__ === Object.prototype); // true
   console.log(F.prototype.constructor === Object); // true
   return new F();
}
let person = {name: "zs", friends: ["ls", "ww"]};
let p1 = CreateObj(person);
let p2 = CreateObj(person);
p1.name = "ls";
console.log(p2.name); // "zs"
p1.friends.push("zl");
console.log(person); // {name: "zs", friends: ["ls", "ww", "zl"]}

5、寄生式继承

重点: 给原型式继承外套了个壳子
优点: 没有创建自定义类型,只是套了个壳子返回对象,这个函数顺理成章就成了新对象。
缺点: 没有用到原型,无法复用。

let person = {name: "zs", hobbies: ["eatting", "sleeping"]};
function CreateObj(obj) {
   function F() {}
   F.prototype = obj;
   return new F();
}
let p1 = CreateBoj(person);
let p2 = Object.create(person);
console.log(p1.name); // "zs"
console.log(p2.hobbies); // ["eatting", "sleeping"]
function CreateChild(obj) {
   let newobj = CreateObj(obj);
   newobj.sayName = function() {
   	console.log("name:", this.name);
   }
   return newobj;
}
let c1 = CreateChild(person);
c1.sayName(); // name: "zs"

6、寄生组合式继承(常用)

重点: 修复了组合继承的问题。

function Parent(name) {
    this.name = name;
    this.hobbies = ["eatting", "sleeping"];
}
Parent.prototype.sayName = function() {
    console.log(this.name);
}
function Child(name, age) {
    Parent.call(this, name);
    this.age = age;
}
function CreateObj(obj) {
    function F() {};
    F.prototype = obj;
    return new F();
}
function prototype(child, parent) {
    let prototype = CreateObj(parent.prototype);
    prototype.constructor = child;
    child.prototype = prototype;
}
prototype(Child, Parent);
let child1 = new Child("zs", 24);
console.log(child1); // Child {name: "zs", hobbies: ["eatting", "sleeping"], age: 24}

7、class 继承

class Parent {
   constructor() {
       this.hobbies = ["eatting", "sleeping"];
   }
   addHobbies() {
       this.hobbies.push("codding");
   }
}
class Child extends Parent {
   constructor() {
       super();
   }
}
let child1 = new Child();
child1.addHobbies();
child1.hobbies; // ["eatting", "sleeping", "codding"]

七、怪异盒模型

添加 box-sizing: border-box; 属性,盒子的width = content width + 2 * border + 2 * padding

八、盒子塌陷及解决办法

盒子塌陷:当父元素没有设置足够的高度时,而子元素设置了浮动的属性,子元素就会跑到父元素的外面,尤其是当父元素的高度设为auto时,父元素中又没有其他非浮动的可见元素,父元素的高度就会塌陷为0。
解决办法:清除浮动(上文第三点

九、BFC

BFC(块级格式化上下文):是web页面可视化渲染CSS的一部分,是布局过程中生成块级盒子的区域,也是浮动元素和其他元素的交互限定区域。BFC就是一个独立的渲染区域,内部元素的渲染不会影响边界以外的元素。

形成BFC的条件

  • 根元素或包含根元素的元素
  • float: right | left | inherit(≠none)
  • position: absolute | fixed;
  • display: flex | inline-block | inline-flex | table-cell | table-caption
  • overflow: hidden | auto | scroll(≠visible)

BFC的作用

  • 清除浮动(overflow: hidden)
  • 避免普通文档流块元素外边距重叠

参考

十、px、em、rem、vh、vw、vm

  • px:绝对长度单位。
  • em:相对长度单位,相对于当前对象内文本的字体尺寸,如果当前对象内的文本字体尺寸未被人为设定,则相对于浏览器的默认尺寸(1em = 16px),一般再body选择器中声明 font-size: 62.5%; 即 1em = 10px;
  • rem 相对长度单位,相对于html根元素font-size的值,通常在html中加入 font-size: 62.5%; 即1rem = 10px;
  • vh、vw:根绝窗口的高度、宽度,等分为100份,100vw表示满宽。窗口只浏览器的可视区域。
  • vm:相对于视口中高度和宽度较小的那个,将较小的那个等分为100份,同vh、vw。

十一、解决移动端响应式的问题

  • flex布局
  • rem + 媒体查询
  • rem + vw

十二、dom事件流

事件发生时,会在元素节点之间按特定的顺序传播。

  • 捕获阶段
  • 目标阶段
  • 冒泡阶段:在这个阶段对事件做出响应

触发事件捕获:

addEventListener(event, function, useCapture)

“useCapture” 有两个可能的值:

  • false(默认值)—— 在冒泡阶段设置处理程序;
  • true —— 在捕获阶段设置处理程序。

参考

十三、检测数据类型

  • typeof
  • instanceof
  • constructor
  • Object.prototype.toString.call
    参考

十四、JS垃圾回收机制

  1. 引用计数: 申明一个变量,将一个引用类型的值赋给这个变量,那么这个变量的引用就加1;如果这个变量的值又指向另外一个值,或者说这个变量被重新赋值了,那么以上引用类型的值的引用就减1;如此一来,该引用类型的值的引用次数变为0,垃圾回收器会清理引用次数为0的值并回收其内存。注意: 对于循环引用的值无法回收。
  2. 标记清除: 垃圾回收器在运行时会给存储在内存中的所有变量加上标记;当变量进入执行环境时,它会去掉执行环境中的变量以及被执行环境中的变量引用的标记;当离开执行环境时,再次被加上的标记的变量就是准备被删除的变量。

V8引擎垃圾回收策略:
采用分代回收的思想,新生代:对象的存活时间较短,只经过一次垃圾回收:采用复制算法和标记整理;老生代:对象的存活时间较长,经过一次或多次回收,主要采用标记清除。

十五、闭包

闭包是有权限访问另外一个函数作用域中变量的函数。
闭包的特点:

  • 闭包内的变量不会影响全局变量,也不会被全局变量影响;
  • 闭包中被函数引用的局部变量不会被垃圾回收机制回收;
  • 可以创建私有变量和私有函数;
  • 可能造成内存泄漏

十六、this指向

十七、改变this指向的几种方法

bind(会创建新的函数)
call和apply不会创建新的函数,只是在调用时绑定一下而已
call和apply的区别:第一个参数都是要绑定的this,apply第二个参数是数组,而call是把apply的第二个参数单列出来。

十八、Map和WeakMap的区别

  • Map的键可以是任意类型,WeakMap的键只能是对象(null除外);
  • Map的键是跟内存地址绑定的,只要内存地址不一样,就视为两个键,WeakMap的键是弱引用,键所指的对象可以被垃圾回收机制回收,此时的键是无效的;
  • Map可以遍历,WeakMap不可以遍历。

十九、meta标签及作用

描述html的元数据

  • charset:定义文档的字符编码。
<meta charset="UTF-8" />
  • http-equiv:将content内容关联到http头部。
<meta http-equiv="X-UA-Compatible" content="IE=edge">
  • name:将content属性关联到一个名称。
<meta name="viewport" content="width=device-width, initial-scale=1.0">
  • content:定义与http-equiv和name相关的元信息。

二十、url的组成

https://www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#anchor

协议名、域名、端口号、路径、查询参数、锚点

二十一、var、let、const的区别

var :
函数作用域和全局作用域。
吊起。
可重新分配和重新申报。
let :
块范围。
没有吊起来。
可重新分配且不可重新申报。
const :
块范围。
没有吊起来。
不可重新分配且不可重新申报。

二十二、this.$router.push() 和 this.$router.replace()的区别

  • this.$router.push()会跳转到不同的url,向history栈中添加一个记录,点击返回按钮跳转到上一个记录;
  • this.$router.replace()会跳转到不同的url,但是不会向history栈中添加记录,点击返回跳转到上上一条记录,上一条记录是不存在的。
  • this.$router.go(n)向前或向后跳转n个页面。

二十三、HTTP2.0的特点

  • 多路复用。在一个TCP连接中,客户端可以同时发送多个请求或响应。
  • 二进制分帧。采用二进制的形式传输数据。
  • 首部压缩。对请求的首部进行压缩。
  • 服务器推送。允许服务器主动推送资源到客户端。

二十四、JS改变原数组和不改变原数组的方法

改变原数组:

  • unshift()
  • shift()
  • push()
  • pop()
  • reverse()
  • sort()
  • splice()

不改变原数组:

  • slice()
  • concat()
  • join()
  • map()
  • filter()
  • forEach()
  • every()
  • reduce()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值