CSS
1、三栏布局问题(左右固定宽度 中间自适应)
flex
<div class="container">
<div class="left"></div>
<div class="middle"></div>
<div class="right"></div>
</div>
.container {
display: flex;
}
.left, .right {
width: 200px
}
.middle {
flex: 1;
}
优点:比较完美 移动端首选;
缺点:不兼容 ie9 及以下;
grid
<div class="container">
<div class="left"></div>
<div class="middle"></div>
<div class="right"></div>
</div>
.container {
display: grid;
grid-template-columns: 200px auto 200px;
}
优点:简单强大 解决二维布局问题;
缺点:不兼容 ie9 及以下,很多国产手机浏览器有兼容问题;
float + margin
<div class="container">
<div class="left"></div>
<div class="middle"></div>
<div class="right"></div>
</div>
.container {
overflow: hidden;
}
.left {
float:left;
height:100%;
width:200px;
}
.right {
float:right;
height:100%;
width:200px;
}
.middle{
height:100%;
margin:0 200px;
}
优点:快捷 简单 兼容性较好;
缺点: 有局限性 脱离文档流 需要清除浮动等;
2、CSS 盒模型
margin-area: 外边距区域;
border-area: 边框区域;
padding-area: 内边距区域;
content-area: 内容区域;
标准盒模型和 IE 盒模型:
标准盒模型: box-sizing 值为 content-box 时,在高度和宽度之外绘制内外边距以及边框;
IE盒模型:box-sizing 值为 border-box 时,内边距和边框在已设置的宽高内绘制;
3、BFC
BFC(Block Formatting Context)块级格式化上下文,是 Web 页面中盒模型布局的 CSS 渲染模式,指一个独立的渲染区域或者说是一个隔离的独立容器。
形成条件:
- 浮动元素,float 除 none 以外的值;
- 定位元素,position(absolute,fixed);
- display 为以下其中之一的值 inline-block,table-cell,table-caption;
- overflow 除了 visible 以外的值(hidden,auto,scroll);
特征:
- 内部的 Box 会在垂直方向上一个接一个的放置;
- 垂直方向上的距离由margin 决定;(解决外边距重叠问题)
- bfc 的区域不会与 float 的元素区域重叠;(防止浮动文字环绕)
- 计算 bfc 的高度时,浮动元素也参与计算;(清除浮动)
- bfc 就是页面上的一个独立容器,容器里面的子元素不会影响外面元素;
4、外边距重叠
块的上外边距(margin-top)和下外边距(margin-bottom)有时合并(折叠)为单个边距,其大小为单个边距的最大值(或如果它们相等,则仅为其中一个),这种行为称为边距折叠。
注意有设定float和position=absolute的元素不会产生外边距重叠行为。
相关文章:MDN 文档
DOM
1、DOM 事件级别
DOM 0级:
写法:el.οnclick=function(){}
DOM 0事件绑定,给元素的事件行为绑定方法,这些方法都是在当前元素事件行为的冒泡阶段(或者目标阶段)执行的。
DOM 2级
写法:el.addEventListener(event-name, callback, useCapture)
event-name: 事件名称,可以是标准的DOM事件;
callback: 回调函数,当事件触发时,函数会被注入一个参数为当前的事件对象 event;
useCapture: 默认是false,代表事件句柄在冒泡阶段执行;
DOM 3级
写法和DOM2级一致 只是在DOM 2级事件的基础上添加了更多的事件类型
新增事件:
- UI事件,当用户与页面上的元素交互时触发,如:load、scroll;
- 焦点事件,当元素获得或失去焦点时触发,如:blur、focus;
- 鼠标事件,当用户通过鼠标在页面执行操作时触发如:dblclick、mouseup;
- 滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel;
- 文本事件,当在文档中输入文本时触发,如:textInput;
- 键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress;
- 合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart;
- 变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified;
2、事件模型 事件流
事件模型: 捕获和冒泡;
事件流:
- 捕获阶段:事件从 window 对象自上而下向目标节点传递阶段;
- 目标阶段:目标节点处理对应事件;
- 冒泡阶段:事件从目标节点向 window 对象自下而上传播阶段;
3、事件代理
由于在冒泡阶段事件从下而上传播,因此可以将子节点的监听函数放置在父节点上,统一处理子节点的事件。减少内存消耗,提高性能(不需要为每一个子元素绑定事件)。
4、Event 对象
阻止默认行为:
event.preventDefault()
阻止冒泡:
- event.stopPropation 阻止事件冒泡到父元素
- event.stopImmediatePropation 既能阻止事件向父元素冒泡,也能阻止元素同事件类型的其它监听器被触发
target 和 currentTarget:
currentTarget始终是监听事件者,而target是事件的真正发出者。
5、自定义事件
// 创建事件:
// event 不能传参
let event = new Event('eventName')
// CustomEvent是可以传递参数的
let customEvent = new CustomEvent('eventName', {})
// 监听事件
/*
* addEventListener(event, function, useCapture)
* useCapture 指定事件是否 在捕获或冒泡阶段执行;
* true - 事件在捕获阶段执行
* false- 默认,事件在冒泡阶段执行
*/
dom.addEventListener('eventName', function (e) {...}, false)
// 触发事件
dom. dispatchEvent('eventName')
JavaScript
1、JavaScript 单线程
js 作为浏览器脚本语言,其主要用途是与用户互动,以及操作DOM。如果存在多个线程就会有很复杂的同步问题,例如:一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
2、JavaScript 同步任务与异步任务
由于 js 单线程的特点,每次只能执行单个任务。如果前序任务始终没有结束,后序的任务只能等待,效率较低。所以增加了同步任务和异步任务的区分。
- 同步任务:在主线程上排队执行的任务,需要按顺序一一执行;
- 异步任务:不进入主线程而是进入任务队列,只有通知主线程任务可以被执行时,才会加入主线程执行;
3、Event-Loop 事件轮询:
Javascript 的“线程”有一种机制:在每次调用 JS 引擎时,可以随着时间的推移执行你的程序的多个代码块儿,这称为“事件轮询(Event Loop)。
JavaScript 实现异步的具体方式:
- 同步代码直接执行
- 异步函数放置到异步队列中
- 同步代码执行完毕,异步队列轮询执行
宏任务与微任务
宏任务:
分类:
函数 | 浏览器 | node |
---|---|---|
I/O | 是 | 是 |
setTimeout | 是 | 是 |
setInterval | 是 | 是 |
setImmediate | 否 | 是 |
requestAnimationFrame | 是 | 否 |
特性:
- 宏任务所在的队列就是宏任务队列;
- 第一个宏任务队列中只有一个任务:执行主线程上的JS代码;如果遇到上方表格中的异步任务,会创建出一个新的宏任务队列,存放这些异步函数执行完成后的回调函数;
- 宏任务中可以创建微任务,但是在宏任务中创建的微任务不会影响当前宏任务的执行;
- 当一个宏任务队列中的任务全部执行完后,会查看是否有微任务队列,如果有就会优先执行微任务队列中的所有任务,如果没有就查看是否有宏任务队列;
微任务:
分类:
函数 | 浏览器 | node |
---|---|---|
process.nextTick | 否 | 是 |
mutionObserver | 是 | 否 |
promise.then/catch/finally | 否 | 是 |
mutionObserver:
- 用来监视 DOM 变动
- 等待所有脚本任务完成后,才会运行,即采用异步方式
- 把 DOM 变动记录封装成一个数组进行处理,而不是一条条地个别处理 DOM 变动
- 即可以观察发生在 DOM 节点的所有变动,也可以观察某一类变动
经典面试题
console.log(1);
setTimeout(()=>{
console.log(2);
new Promise((resolve,reject)=>{
console.log(3);
resolve()
}).then(res=>{
console.log(4);
})
})
new Promise((resolve,reject)=>{
resolve()
}).then(res=>{
console.log(5);
}).then(res=>{
console.log(6);
})
new Promise((resolve,reject)=>{
console.log(7);
resolve()
}).then(res=>{
console.log(8);
}).then(res=>{
console.log(9);
})
setTimeout(()=>{
console.log(10);
new Promise((resolve,reject)=>{
console.log(11);
resolve()
}).then(res=>{
console.log(12);
})
})
console.log(13);
输出结果:
依次 1、7、13、5、8、6、9、2、3、4、10、11、12
4、创建对象:
- 字面量方式(简单,运行速度更快)
let obj = { test: a}
- 构造函数
function Test () {
this.test = a
}
let obj = new Test()
- Object.create(proto, [propertiesObject])
//Object.create()方法创建的对象时,属性是在原型下面的
let obj = Object.creat({test: a})
5、原型链:
原型链和原型对象是js的核心,原型链保证函数或对象中的方法、属性可以让向下传递,js通过原型链才得以实现函数或对象的继承
prototype 和 constructor:
prototype 指向函数的原型对象,只有函数才拥有该属性。
constructor 指向原型对象的构造函数。
_proto_:
每个对象都有 proto,指向了创建该对象的构造函数原型。由于js中是没有类的概念,而为了实现继承,通过 proto 将对象和原型联系起来组成原型链,就可以让对象访问到不属于自己的属性。
Foo、Function 和 Object 都是函数,它们的 proto 都指向 Function.prototype.
原型对象 _proto_都指向了 Object.prototype,js原型链最终指向的是 Object 原型对象。
总结
实例的 _proto_ 指向原型对象的 prototype,实例远行对象的 _proto_ 是 Object 的原型对象(null 除外)
image.png
举例:
- instanceof 原理
<script>
function Person(){
}
function Foo(){
}
//显示改变Foo.prototype指向Person的实例对象(原型继承)
Foo.prototype=new Person()
let a=new Foo()
console.log(a.__proto__===Foo.prototype); //true
console.log(a instanceof Foo);//true
console.log(Foo.prototype.__proto__===Person.prototype);//true
console.log(a instanceof Person);//true
console.log(a instanceof Object);//true
// 这个时候改变Foo.prototype的指向
Foo.prototype={}
// Foo.prototype已经不在a的原型链上面了
console.log(a.__proto__===Foo.prototype);//false
console.log(a instanceof Foo);//false
//Person.prototype依然在a的原型链上面
console.log(a instanceof Person);//true
</script>
- new运算符原理
1、创建一个新对象;
2、将空对象的 _proto_ 指向构造函的 prototype;
3、使用apply调用构造器函数,属性和方法被添加到 this 引用的对象中;
4、如果构造函数中没有返回其它对象,那么返回 this,即创建的这个的新对象,否则,返回构造函数中返回的对象;
function _new(func) {
// 第一步 创建新对象
let obj= {};
// 第二步 空对象的_proto_指向了构造函数的prototype成员对象
obj.__proto__ = func.prototype;//
// 一二步合并就相当于 let obj=Object.create(func.prototype)
// 第三步 使用apply调用构造器函数,属性和方法被添加到 this 引用的对象中
let result = func.apply(obj);
if (result && (typeof (result) == "object" || typeof (result) == "function")) {
// 如果构造函数执行的结果返回的是一个对象,那么返回这个对象
return result;
}
// 如果构造函数返回的不是一个对象,返回创建的新对象
return obj;
}
5、继承实现:
声明父类:
function Father(name) {
this.name = name || "father";
this.sayName = function() {
console.log(this.name);
}
this.color = ["red", "blue"]
}
Father.prototype.age = 18;
Father.prototype.sayAge = function() {
console.log(this.age)
}
1) 原型链继承
function Son(name) {
this.name = name || 'son'
}
Son.prototype = new Father()
优点:
- 简单易于实现;
- 父类新增的属性和方法,子类都可以访问到;
缺点:
- 无法实现多继承,因为原型一次只能被一个实例更改;
- 来自原型对象的所有属性被所有实例共享;
- 创建子类实例时,无法向父构造函数传参;
2)构造继承:复制父类的实例属性给子类
function Son(name) {
Father.call(this, '父级需要的参数')
this.name = name
}
let s = new Son("son");
console.log(s.name); // son
//s.sayAge(); // 抛出错误(无法继承父类原型方法)
s.sayName(); // son
console.log(s.age); // undefined (无法继承父类原型属性)
console.log(s instanceof Father); // false
console.log(s instanceof Son); // true
优点:
- 解决了原型链继承中子类实例共享父类引用属性的问题;
- 创建子类实例时,可以向父类传递参数;
- 可以实现多继承(call多个父类对象);
缺点:
- 实例并不是父类的实例,只是子类的实例;
- 只能继承父类实例的属性和方法,不能继承其原型上的属性和方法;
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能;
3)原型链、构造函数组合继承
使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承。
function Son(name) {
// 第一次调用父类构造器 子类实例增加父类实例
Father.call(this, "我是传给父类的参数");
this.name = name || "son";
}
// 经过new运算符 第二次调用父类构造器 子类原型也增加了父类实例
Son.prototype = new Father();
let s = new Son("son");
console.log(s.name); // son
s.sayAge(); // 18
s.sayName(); // son
console.log(s.age); // 18
console.log(s instanceof Father); // true
console.log(s instanceof Son); // true
console.log(s.constructor === Father); // true
console.log(s.constructor === Son); // false
优点:
- 弥补了构造继承的缺点,现在既可以继承实例的属性和方法,也可以继承原型的属性和方法;
- 既是子类的实例,也是父类的实例;
- 可以向父类传递参数;
- 函数可以复用;
缺点:
- 调用了两次父类构造函数,生成了两份实例;
- constructor指向问题;
4)*寄生组合继承:
通过寄生方式,砍掉父类的实例属性,避免了组合继承生成两份实例的缺点;
function Son (name) {
let f = Father.call(this, '传递给父级的参数')
f.name = name || 'son'
}
# 借用Object.create()方法
Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son
# 自己动手创建一个中间类
// (function() {
// let NoneFun = function() {};
// NoneFun.prototype = Father.prototype;
// Son.prototype = new NoneFun();
// Son.prototype.constructor = Son;
// })();
let s = new Son("son");
console.log(s.name); // son
s.sayAge(); // 18
s.sayName(); // son
console.log(s.age); // 18
console.log(s instanceof Father); // true
console.log(s instanceof Son); // true
console.log(s.constructor === Father); // false
console.log(s.constructor === Son); // true
优点:
- js实现继承首选方式;
缺点:
- 实现较复杂(可通过Object.create简化);
5)实例继承:为父类实例添加新特征,作为子类实例返回
function Son (name) {
let f = new Father('传递给父级的参数')
f.name = name || 'son'
return f
}
let s = new Son("son"); //或者直接调用子类构造函数 let s = Son("son");
console.log(s.name); // son
s.sayAge(); // 18
s.sayName(); // son
console.log(s.age); // 18
console.log(s instanceof Father); // true
console.log(s instanceof Son); // false
console.log(s.constructor === Father); // true
console.log(s.constructor === Son); // false
优点:
- 不限制调用方式,不管是new 的方式声明子类还是通过直接调用函数方法声明,返回的对象具有相同的效果;
缺点:
- 实例是父类的实例,不是子类的实例;
- 不支持多继承;
6)拷贝继承:对父类实例中的的方法与属性拷贝给子类的原型
function Son (name) {
let f = new Father('要传给父级的数据')
for (let k in f) {
Son.prototype[k] = f[k]
}
Son.prototype.name = name
}
let s = new Son("son");
console.log(s.name); // son
s.sayAge(); // 18
s.sayName(); // son
console.log(s.age); // 18
console.log(s instanceof Father); // false
console.log(s instanceof Son); // true
console.log(s.constructor === Father); // false
console.log(s.constructor === Son); // true
优点:
- 支持多继承;
缺点:
- 效率低,性能差,占用内存高(因为需要拷贝父类属性);
- 无法获取父类不可枚举的方法(不可枚举的方法,不能使用for-in访问到);
7)ES6 Class 继承
class Son extends Father {
constructor(name) {
super(name);
this.name = name || "son";
}
}
let s = new Son("son");
console.log(s.name); // son
s.sayAge(); // 18
s.sayName(); // son
console.log(s.age); // 18
console.log(s instanceof Father); // true
console.log(s instanceof Son); // true
console.log(s.constructor === Father); // false
console.log(s.constructor === Son); // true
6、节流和防抖
节流:一定时间内执行的操作只执行一次。
应用场景:
- 鼠标不断点击触发,mousedown(单位时间内只触发一次);
- 监听滚动事件,比如是否滑到底部自动加载更多(懒加载);
function throttle (fn, timer) {
let canRun = true
return function () {
if ( !canRun ) return
canRun = false
setTimeout(() => {
fn().apply(this, arguments)
canRun = true
}, timer)
}
}
防抖:动作停止后的时间超过设定的时间时执行一次函数。注意:这里的动作停止表示你停止了触发这个函数,从这个时间点开始计算,当间隔时间等于你设定时间,才会执行里面的回调函数。如果你一直在触发这个函数并且两次触发间隔小于设定时间,则函数一直不会执行.
应用场景:
- 搜索栏用户输入结束后,请求联想数据;
- window resize时,页面不断调整会不断触犯;
function debance (fn, delayTime) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.call(this, arguments)
}, delayTime)
}
}
HTTP 相关
框架 Framework
1、SSR 框架的选择, Nuxt、Next、Nest?
Next:性能居中,lighthouse 测试报告中比其他两者低。
优势:
- 默认情况每一个组件都是服务端渲染;
- 自动代码拆分,加快页面加载速度;
缺点:
- 数据会在客户端和服务器重复加载;
Nuxt:性能为三者中最低,lighthouse 测试报告中的大多项都是领先者。
优点:
- 主要范围是UI渲染,同时抽象出客户端/服务器分布;
- 项目结构清晰;
- 路由级别的异步数据获取;
缺点:
- 周边资源较少;
- 高流量可能会给服务器带来压力;
Nest:性能是三者中最好的,lighthouse 测试报告得分较低。
优点:
- 基于TypeScript的Web框架,可以进行严格的类型定义;
- 自动生成Swagger文档;
- 为开发人员提供更少的上下文切换。从Angular代码到Nest的过渡相对容易;
缺点:
- 缺少文档;
- 与其他框架相比,Nest的社区规模较小;
参考文章:原文链接
网友评论