Javascript
JavaScript
判断数组的方法
- 通过 Object.prototype.toString.call()做判断
obj.__proto__
=== Array.prototype;- Array.isArray()
- obj instanceof Array
- Array.prototype.isPrototypeOf(obj)
JavaScript
去重方法
- 1.遍历数组法
新建一个数组,遍历要去重的数组,当值不在新数组的时候(indexOf 为-1)就加入该新数组
- 2.数组下标判断法
如果当前数组的第 i 项在当前数组中第一次出现的位置不是 i,表示第 i 项是重复的.忽略掉,否则存入结果数组
- 3.排序后相邻去除法
给传入的数组排序.排序后相同的值会相邻.然后遍历排序后的数组,新数组之加入不与前一值重复的值
- 4.优化遍历数组法
双层循环,外层循环表示从 0 到 arr.length,内层循环表示从 i+1 到 arr.length
- 5.ES6 中 set 方法
- 6.利用 ES6 中 sort
先排序,然后根据排序后的结果进行遍历及其相邻的元素对比
- 7.利用 includes
检测数组中是否存在某个值
- 8.利用 for 嵌套 for,然后 splice 去重
- 9.利用 hasOwnProperty
利用 hasOwnProperty 哦安短是否存在对象属性
- 10.利用递归去重
- 11.利用 Map 数据结构去重
- 12.利用 reduce+includes
- 13.[...new Set(arr)]
JavaScript
的new
做了什么
function Person() {
this.name = name;
this.age = age;
this.sex = sex;
this.sayName = function() {
return this.name;
};
}
var person = new Person("tom", 21, "famle");
console.log(person.name);
- 创建一个新的对象
- 将新的对象的
__proto__
指向构造函数的 prototype 对象 - 将构造函数的作用域赋值给新的对象(也就是 this 指向新对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新的对象
var Obj = {};
Obj._proto_ = Person.prototype();
Person.call(Obj);
let const var
区别
var
声明的变量挂载在window
上,而let
const
不会var
声明的变量存在变量提升,let
和const
不存在变量提升let
和const
声明形成块作用域- 同一作用域下
let
和const
不能声明同名变量,而var
可以 - 暂存死区
this
指向
1:this 永远指向一个对象;
2:this 的指向完全取决于函数调用的位置;
class Test {
constructor() {}
fun = () => {
console.log(this);
};
fun1() {
console.log(this);
}
}
let test = new Test();
test.fun(); //Test
test.fun.call(this); //Test
test.fun1(); //Test
test.fun1.call(this); //window
JavaScript
改变this
指向办法
- call()
- apply()
- bind()
- that
call,apply,bind
区别
bind 返回对应函数,便于稍后调用;apply 和 call 则是立即调用
除此外,在 ES6 的箭头函数下,call 和 apply 将失效
箭头函数和普通函数区别
- 箭头函数是匿名函数,不能作为构造函数,不能使用 new
- 箭头函数不能绑定 arguments,取而代之用 rest 参数解决
- 箭头函数没有原型属性
- 箭头函数的 this 永远指向其上下文的 this,没有改变其指向,普通函数的 this 指向调用它的对象
- 箭头函数不绑定 this,会捕获其所在的上下文的 this,作为自己的 this 的值
JavaScript
事件委托
事件委托就是利用冒泡的原理将事件加到父元素或者祖先元素上,触发执行效果
优点
- 可以提高 js 性能
- 可以动态添加 dom 元素,不需要因为元素的变动而修改事件的绑定
Promise
链式调用
return this
Promise
相比较ajax
有哪些优势
- Promise 三个状态
- pending:准备状态
- fulfilled:成功
- rejected:失败
-
Promise 对象接受一个回调函数作为参数,改回调参数接受两个参数,分别是成功时的回调 resolve 和失败时的回调 reject,另外 resolve 的参数除了正常值以外,还可能是一个 Promise 对象的实例;reject 参数通常是一个 Error 对象的实例
-
then 方法返回一个新的 promise 对象,并接受两个参数 onResolved(fulfilled 状态回调)
-
catch 方法返回一个新的 promise 实例
-
finally 方法不管 Promise 状态如何都会执行,该方法的回调函数不接受任何参数
-
Promise.all()方法将多个 Promise 实例包装成一个新的 Promise 实例,该方法接受一个由 promise 对象组成的数组作为参数
promise.all()方法返回新的实例 catch 方法,如果参数中某个实例调用了 catch 方法,将不会触发 promise.all()方法返回的新实例的 catch 方法
-
promise.race()
-
promise.resolve()
-
promise.reject()
Promise
优点
-
统一异步的 API
逐渐被用作浏览器的异步 API,统一了各种各样的 API,以及不兼容的模式和写法
-
Promise 与事件对比
和事件相比较,promise 更适合处理一次性的结果,在结果计算出来之前或之后注册回调函数都是可以的,都可以拿到正确的值,
-
Promise 与回调对比
解决了回调低于的问题,将异步操作以同步操作的流程表达出来
-
Promise 额外的好处就是更好的错误处理,并且写起来很轻松
Promise
缺点
- 无法取消 Promise,一旦新建他就会立即执行,无法中途取消
- 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部
- 当处于 pending 状态时,无法知道进行到哪个阶段
- Promise 真正执行回调的时候,定义 Promise 那部分实际已经走完了,所以 promise 的报错堆栈上下文不大友好
异步问题同步化解决方案
JavaScript
统计字符串中字符出现的次数
var arr = "hello world";
var obj = {};
for (var i = 0; i < arr.length; i++) {
var key = arr[i];
if (obj[key]) {
obj[key]++;
} else {
obj[key] = 1;
}
}
console.log(obj);
JavaScript
函数柯里化
柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。通俗点说就是将一个函数拆分成多个函数,是固定部分参数,返回一个接受剩余参数的函数,也称为部分计算函数,目的是为了缩小适用范围,创建一个针对性更强的函数
// 实现一个简单的加法
function add(a, b) {
return a + b;
}
add(1, 2);
// 用柯里化实现
const currying = (x) => {
return (y) => {
return x + y;
};
};
console.log(currying(1)(2)); // 3
-
优点:
1)可以实现参数复用
2)实现延迟执行;先收集所有参数,最后执行
3)实现功能细化
-
缺点:
- 实现出现闭包,占用大量内存,产生性能问题
- 递归效率低
JavaScript
防抖和节流
防抖
定义
在事件被触发后 n 秒后再执行回调函数,如果这个 n 秒内又被触发,则重新计时
应用场景
- 用户输入框连续输入一串字符串后,只会在输入完成后去执行最后一次查询的 ajax 请求,这样可以有效减少请求次数,节约请求资源
- window 的 resize,scroll 时间,不断的调整浏览器窗口大小,或者滚动时触发对应时间,防抖让其只触发一次
节流
定义
规定一个单位时间,在这个单位时间内,只能有一次触发时间的回调函数执行,如果同一单位时间内某事件被触发多次,只能一次能生效
应用场景
- 鼠标连续不断的触发某事件,只能单位时间内只触发一次
- 在页面的无限加载场景下,需要用户在滚动页面是,每隔一段时间发一次 ajax 请求,而不是在用户停下滚动页面操作时才去请求数据
- 监听滚动事件,比如是否滑到底部自动加载更多,用 throttle 来判断
JavaScript
宏任务和微任务
setTimeout(() => {
console.log(1);
}, 0);
console.log(2);
new Promise((resolve, reject) => {
console.log(3);
resolve();
}).then((res) => {
console.log(4);
});
setTimeout(() => {
console.log(5);
}, 0);
console.log(6);
答案
2 3 6 4 1 5
CSS
css 垂直居中的办法
仅仅居中元素宽高适用
- absolute +负 margin
- absolute + margin auto
- absolute + calc
居中元素不定宽高
- absolute + transform
- line-height+text-align
- writing-mode
- table
- css-table
- flex
- grid
JavaScript
模块化规范有哪些
CommonJs
AMD
CMD
ES6
Modules
Vue
Vue
为什么只有一个根元素
抽象出来的 dom 树只能有一个根节点
为什么抽象出来的 dom 树只有一个跟节点
- 从查找和遍历的角度来说,如果有多个根,那么我们的查找和遍历的效率会很低
- 如果一个树有多个根,说明可以优化,肯定会有一个节点是可以访问到所有的节点,那么这个节点就会成为新的根节点
- 如果一个组件有多个入口或者多个根,那不就意味你的组件还可以进一步拆分成多个组件,进一步组件优化,降低代码之间耦合程度
vue
实现深度监听
浏览器
浏览器如何渲染页面
- 1.根据 html 文件构建 dom 树和 cssom 树,构建 dom 树期间,如果遇到 js,阻塞了 dom 树以及 cssom 树的构建,优先加载 js 文件,再继续构建 dom 树以及 cssom 树
- 2.构建渲染树(Render Tree)
- 3.页面的重绘(repaint)与重排(reflow 也称回流),页面渲染完成后,若有 js 操作了 dom,根据 js 对 dom 操作的动作大小,浏览器对页面进行重绘或回流
浏览器重绘和回流
回流
当 Render Tree 中的部分或全部元素的尺寸,结构或者处罚某些属性时,浏览器会重新计算并渲染页面,称为回流。此时浏览器需要重新进行计算,计算后还需要重新页面布局,因此是较重的操作。
会导致回流的操作有:
- 页面初次渲染
- 浏览器窗口发生变化
- 元素尺寸,位置,内容发生变化
- 元素字体大小变化
- 添加或者删除可见的 dom 元素
- 激活 css 伪类
- 查询某些属性或者调用某些方法
clientWidth, clientHeight, clientTop, clientLeft
offsetWidth, offsetHeight, offsetTop, offsetLeft
scrollWidth, scrollHeight, scrollTop, scrollLeft
scrollIntorView(), scrollInToViewIfNeeded()
getComputedStyle()
getBoundingClientRect()
scrollTop
重绘
当元素的样式改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要对 UI 层面重新绘制,因为损耗较少
回流一定会导致重绘,但是重绘不一定导致回流
如何避免
CSS
- 避免使用 table 布局
- 尽可能在 dom 树的最末端改变 class
- 避免设置多层内联样式
- 避免 CSS 表达式
Javascript
- 避免频繁的操作样式,最好一次性写好 style 属性,或者将样式定义为 class,并且一次性更改 class
- 避免频繁操作 dom,创建一个 documentFragment,这上面应有所有的 dom 操作,最后把他添加到文档中
- 也可以设置 display:none,操作结束后再把它显示出来,因为 display 属性为 none 的元素上进行 dom 操作不会引起重绘和回流
- 避免频繁读取引发回流和重绘的属性,如果要多次使用,建议先把他缓存起来
- 对具有复杂动画的元素使用绝对定位,使他脱离文档流,否则会引起父元素及其后续元素频繁回流
http
Tcp 过程
-
第一次握手:
客户端发送
SYN
报文,并发送序号为X
-
第二次握手:
服务端发送
SYN
+ACK
报文,并发送序号Y
,在确认序号为X+1
-
第三次握手.
客户端发送
ACK
报文,并且发送序号Z
,在确认序号为Y+1
为什么连接的时候是三次握手,关闭的时候却是四次握手
因为当Server
端收到Client
端的SYN
连接请求报文后,可以直接发送SYN+ACK
报文。其中ACK
报文是用来应答的,SYN
报文是用来同步的。但是关闭连接时,当Server
端收到FIN
报文时,很可能并不会立即关闭SOCKET
,所以只能先回复一个ACK
报文,告诉Client
端,"你发的FIN
报文我收到了"。只有等到我Server
端所有的报文都发送完了,我才能发送FIN
报文,因此不能一起发送。故需要四步握手。
http
状态码
| 状态码 | 分类描述 |
| ------ | ---------------------------------------------- |
| 1* | 信息,服务器收到请求,需要请求者继续执行操作 |
| 2* | 成功,操作被成功接收并处理 |
| 3* | 重定向,需要进一步的操作以完成请求 |
| 4* | 客户端错误,请求包含语法错误或无法完成请求 |
| 5* | 服务器错误,服务器在处理请求的过程中发生了错误 |
http
与https
HTTP
协议通过客户端请求-->服务端响应的方式进行通信。但是HTTP
有一个致命的缺点就是:不够安全。
HTTP
协议的信息传输完全以明文的方式,不做加密,相当于在网络上裸奔。
HTTPS
加密方式:非对称秘钥
webpack
webpack 常用插件
- html-webpack-plugin(html 文件生成插件)
- imagemin-webpack-plugin(图片压缩插件)
- clean-webpack-plugin(清除空文件夹插件)
- min-css-extract-plugin(css 公共代码提取)
- copy-webpack-plugin(文件复制插件)
- happyPack(多线程打包插件)(不维护)
- thread-loader(多线程打包)
webpack
做过哪些优化
- 优化 webpack 的构建速度
- 使用高版本 webpack
- 多线程,多实例构建
- 缩小打包作用域
- 充分利用缓存提升二次构建的速度
- DLL 分包处理
- 优化
webpack
打包的体积
-
压缩代码
-
提取公共资源
-
Tree Shaking
-
Scope hoisiting
构建后代码会存在大量闭包,造成体积大,运行时代码创建的函数作用域变多,内存开销大,
Scope hoisiting
将所有模块按照引用顺序放在一个函数作用域内,然后适当的重命名一些变量以防止变量名冲突 -
图片压缩
-
动态
Polyfill
webpack
和rollup
区别
- webpack 拆分代码,按需加载
- rollup 所有资源放在同一个地方,一次性加载,利用 tree-shake 特性来剔除项目中未使用的代码,减少冗余,但是 webpack2 已经逐渐支持 tree-shaking
- webpack ES2015 模块打包支持
- 应用使用 webpack,对于类库使用 rollup
webpack
插件是如何实现的
webpack 本质是一种事件流机制,核心模块:tapable(Sync+Async)Hooks构造出=>Compiler(编译)+Compilation(创建bundles)
- Compiler 对象代表了完整的 webpack 环境配置,这个对象在启动 webpack 时被一次性创建,并配置好所有可操作的设置,包括
option,loader和plugin
,当在 webpack 环境中应用一个插件时,插件将受到 compiler 对象的引用,可以使用它来访问 webpack 的主环境 - Compilation 对象代表了一次资源版本的构建,当 webpack 开发环境中间时,每当检测到一个文件变化,就会创建新的 compilation,从而生成一组新的编译资源,一个 compilation 对象表现了当前的模块资源,编译生成的资源,变化的文件以及被跟踪依赖的状态信息.compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用
- 创建一个插件函数,在其 prototype 上定一个 apply 方法,指定一个绑定到 webpack 自身事件钩子
- 函数内,处理 webpack 内部示例的特定数据
- 处理完成后,调用 webpack 提供的回调
前端工程化
前端工程化就是为了让前端开发能够“自成体系”,个人认为主要应该从模块化、组件化、规范化、自动化四个方面思考
- 技术选型
- 统一规范
- 规范的代码可以促进团队合作
- 规范的代码可以降低维护成本
- 规范的代码有助于 code review
- 养成代码规范的习惯,有助于程序员自身的成长
- 测试
- 部署
- 监控
- 性能优化
- 重构
评论区