博主目前在蚂蚁集团-体验技术部,AntV/S2 是博主所在团队的开源项目——多维交叉分析表格,欢迎使用,感谢到 S2 github 仓库点赞 star,有任何关于前端面试、就业、技术问题都可给在文章后留言。
目录
- 一、让元素居中的几种方式:
- 二、清除浮动的方式:
- 三、手撕 instanceof
- 四、手写深拷贝
- 五、手写Promise.all和Promise.race
- 六、手写继承的几种方式
- 七、怪异盒模型
- 八、盒子塌陷及解决办法
- 九、BFC
- 十、px、em、rem、vh、vw、vm
- 十一、解决移动端响应式的问题
- 十二、dom事件流
- 十三、检测数据类型
- 十四、JS垃圾回收机制
- 十五、闭包
- 十六、this指向
- 十七、改变this指向的几种方法
- 十八、Map和WeakMap的区别
- 十九、meta标签及作用
- 二十、url的组成
- 二十一、var、let、const的区别
- 二十二、this.\$router.push() 和 this.\$router.replace()的区别
- 二十三、HTTP2.0的特点
- 二十四、JS改变原数组和不改变原数组的方法
一、让元素居中的几种方式:
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;如此一来,该引用类型的值的引用次数变为0,垃圾回收器会清理引用次数为0的值并回收其内存。注意: 对于循环引用的值无法回收。
- 标记清除: 垃圾回收器在运行时会给存储在内存中的所有变量加上标记;当变量进入执行环境时,它会去掉执行环境中的变量以及被执行环境中的变量引用的标记;当离开执行环境时,再次被加上的标记的变量就是准备被删除的变量。
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()