UDP协议为什么不可靠?
UDP在传输数据之前不需要先建立连接,远地主机的运输层在接收到UDP报文后,不需要确认,提供不可靠交付。总结就以下四点:
- 不保证消息交付:不确认,不重传,无超时
- 不保证交付顺序:不设置包序号,不重排,不会发生队首阻塞
- 不跟踪连接状态:不必建立连接或重启状态机
- 不进行拥塞控制:不内置客户端或网络反馈机制
浏览器是如何对 HTML5 的离线储存资源进行管理和加载?
- 在线的情况下,浏览器发现 html 头部有 manifest 属性,它会请求 manifest 文件,如果是第一次访问页面 ,那么浏览器就会根据 manifest 文件的内容下载相应的资源并且进行离线存储。如果已经访问过页面并且资源已经进行离线存储了,那么浏览器就会使用离线的资源加载页面,然后浏览器会对比新的 manifest 文件与旧的 manifest 文件,如果文件没有发生改变,就不做任何操作,如果文件改变了,就会重新下载文件中的资源并进行离线存储。
- 离线的情况下,浏览器会直接使用离线存储的资源。
说一下类组件和函数组件的区别?
1. 语法上的区别:
函数式组件是一个纯函数,它是需要接受props参数并且返回一个React元素就可以了。类组件是需要继承React.Component的,而且class组件需要创建render并且返回React元素,语法上来讲更复杂。
2. 调用方式
函数式组件可以直接调用,返回一个新的React元素;类组件在调用时是需要创建一个实例的,然后通过调用实例里的render方法来返回一个React元素。
3. 状态管理
函数式组件没有状态管理,类组件有状态管理。
4. 使用场景
类组件没有具体的要求。函数式组件一般是用在大型项目中来分割大组件(函数式组件不用创建实例,所有更高效),一般情况下能用函数式组件就不用类组件,提升效率。
复制代码
Set 和 Map有什么区别?
1、Map是键值对,Set是值得集合,当然键和值可以是任何得值
2、Map可以通过get方法获取值,而set不能因为它只有值
3、都能通过迭代器进行for...of 遍历
4、Set的值是唯一的可以做数组去重,而Map由于没有格式限制,可以做数据存储
复制代码
说一说js是什么语言
JavaScript是一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类型。它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在HTML(标准通用标记语言下的一个应用)网页上使用,用来给HTML网页增加动态功能。
js语言是弱语言类型, 因此我们在项目开发中当我们随意更该某个变量的数据类型后
有可能会导致其他引用这个变量的方法中报错等等。
复制代码
说说浏览器缓存
缓存可以减少网络 IO 消耗,提高访问速度。浏览器缓存是一种操作简单、效果显著的前端性能优化手段
很多时候,大家倾向于将浏览器缓存简单地理解为“HTTP 缓存”。
但事实上,浏览器缓存机制有四个方面,它们按照获取资源时请求的优先级依次排列如下:
Memory Cache
Service Worker Cache
HTTP Cache
Push Cache
缓存它又分为强缓存和协商缓存。优先级较高的是强缓存,在命中强缓存失败的情况下,才会走协商缓存
实现强缓存,过去我们一直用 expires。 当服务器返回响应时,在 Response Headers 中将过期时间写入 expires 字段,现在一般使用Cache-Control 两者同时出现使用Cache-Control 协商缓存,Last-Modified 是一个时间戳,如果我们启用了协商缓存,它会在首次请求时随着 Response Headers 返回:每次请求去判断这个时间戳是否发生变化。 从而去决定你是304读取缓存还是给你返回最新的数据
复制代码
原型
构造函数是一种特殊的方法,主要用来在创建对象时初始化对象。每个构造函数都有prototype(原型)(箭头函数以及Function.prototype.bind()没有)属性,
这个prototype(原型)属性是一个指针,指向一个对象,这个对象的用途是包含特定类型的所有实例共享的
属性和方法,即这个原型对象是用来给实例对象共享属性和方法的。每个实例对象的__proto__都指向这个
构造函数/类的prototype属性。
面向对象的三大特性:继承/多态/封装
关于new操作符:
1. new执行的函数, 函数内部默认生成了一个对象
2. 函数内部的this默认指向了这个new生成的对象
3. new执行函数生成的这个对象, 是函数的默认返回值
ES5例子:
function Person(obj) {
this.name = obj.name
this.age= obj.age
}
// 原型方法
Person.prototype.say = function() {
console.log('你好,', this.name )
}
// p为实例化对象,new Person()这个操作称为构造函数的实例化
let p = new Person({name: '番茄', age: '27'})
console.log(p.name, p.age)
p.say()
ES6例子:
class Person{
constructor(obj) {
this.name = obj.name
this.age= obj.age
}
say() {
console.log(this.name)
}
}
let p = new Person({name: 'ES6-番茄', age: '27'})
console.log(p.name, p.age)
p.say()
复制代码
webpack配置入口出口
module.exports={
//入口文件的配置项
entry:{},
//出口文件的配置项
output:{},
//模块:例如解读CSS,图片如何转换,压缩
module:{},
//插件,用于生产模版和各项功能
plugins:[],
//配置webpack开发服务功能
devServer:{}
}
简单描述了一下这几个属性是干什么的。
描述一下npm run dev / npm run build执行的是哪些文件
通过配置proxyTable来达到开发环境跨域的问题,然后又可以扩展和他聊聊跨域的产生,如何跨域
最后可以在聊聊webpack的优化,例如babel-loader的优化,gzip压缩等等
复制代码
说一下vue3.0你了解多少?
复制代码
你在工作终于到那些问题,解决方法是什么
经常遇到的问题就是Cannot read property ‘prototype’ of undefined
解决办法通过浏览器报错提示代码定位问题,解决问题
Vue项目中遇到视图不更新,方法不执行,埋点不触发等问题
一般解决方案查看浏览器报错,查看代码运行到那个阶段未之行结束,阅读源码以及相关文档等
然后举出来最近开发的项目中遇到的算是两个比较大的问题。
复制代码
说一下data为什么是一个函数而不是一个对象?
JavaScript中的对象是引用类型的数据,当多个实例引用同一个对象时,只要一个实例对这个对象进行操作,其他实例中的数据也会发生变化。而在Vue中,我们更多的是想要复用组件,那就需要每个组件都有自己的数据,这样组件之间才不会相互干扰。所以组件的数据不能写成对象的形式,而是要写成函数的形式。数据以函数返回值的形式定义,这样当我们每次复用组件的时候,就会返回一个新的data,也就是说每个组件都有自己的私有数据空间,它们各自维护自己的数据,不会干扰其他组件的正常运行。
说一下常见的git操作
git branch 查看本地所有分支
git status 查看当前状态
git commit 提交
git branch -a 查看所有的分支
git branch -r 查看远程所有分支
git commit -am "nit" 提交并且加注释
git remote add origin [email protected]:ndshow
git push origin master 将文件给推到服务器上
git remote show origin 显示远程库origin里的资源
git push origin master:develop
git push origin master:hb-dev 将本地库与服务器上的库进行关联
git checkout --track origin/dev 切换到远程dev分支
git branch -D master develop 删除本地库develop
git checkout -b dev 建立一个新的本地分支dev
git merge origin/dev 将分支dev与当前分支进行合并
git checkout dev 切换到本地dev分支
git remote show 查看远程库
git add .
git rm 文件名(包括路径) 从git中删除指定文件
git clone git://github.com/schacon/grit.git 从服务器上将代码给拉下来
git config --list 看所有用户
git ls-files 看已经被提交的
git rm [file name] 删除一个文件
git commit -a 提交当前repos的所有的改变
git add [file name] 添加一个文件到git index
git commit -v 当你用-v参数的时候可以看commit的差异
git commit -m "This is the message describing the commit" 添加commit信息
git commit -a -a是代表add,把所有的change加到git index里然后再commit
git commit -a -v 一般提交命令
git log 看你commit的日志
git diff 查看尚未暂存的更新
git rm a.a 移除文件(从暂存区和工作区中删除)
git rm --cached a.a 移除文件(只从暂存区中删除)
git commit -m "remove" 移除文件(从Git中删除)
git rm -f a.a 强行移除修改后文件(从暂存区和工作区中删除)
git diff --cached 或 $ git diff --staged 查看尚未提交的更新
git stash push 将文件给push到一个临时空间中
git stash pop 将文件从临时空间pop下来
webpack3和webpack4区别
1.mode
webpack增加了一个mode配置,只有两种值development | production。对不同的环境他会启用不同的配置。
2.CommonsChunkPlugin
CommonChunksPlugin已经从webpack4中移除。
可使用optimization.splitChunks进行模块划分(提取公用代码)。
但是需要注意一个问题,默认配置只会对异步请求的模块进行提取拆分,如果要对entry进行拆分
需要设置optimization.splitChunks.chunks = 'all'。
3.webpack4使用MiniCssExtractPlugin取代ExtractTextWebpackPlugin。
4.代码分割。
使用动态import,而不是用system.import或者require.ensure
5.vue-loader。
使用vue-loader插件为.vue文件中的各部分使用相对应的loader,比如css-loader等
6.UglifyJsPlugin
现在也不需要使用这个plugin了,只需要使用optimization.minimize为true就行,production mode下面自动为true
optimization.minimizer可以配置你自己的压缩程序
复制代码
New操作符做了什么事情?
1、首先创建了一个新对象
2、设置原型,将对象的原型设置为函数的prototype对象
3、让函数的this指向这个对象,执行构造函数的代码(为这个新对象添加属性)
4、判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象
复制代码
JS闭包,你了解多少?
应该有面试官问过你:
- 什么是闭包?
- 闭包有哪些实际运用场景?
- 闭包是如何产生的?
- 闭包产生的变量如何被回收?
这些问题其实都可以被看作是同一个问题,那就是面试官在问你:你对JS闭包了解多少?
来总结一下我听到过的答案,尽量完全复原候选人面试的时候说的原话。
答案1:
就是一个function
里面return
了一个子函数,子函数访问了外面那个函数的变量。
答案2:
for循环里面可以用闭包来解决问题。
for(var i = 0; i < 10; i++){
setTimeout(()=>console.log(i),0)
}
// 控制台输出10遍10.
for(var i = 0; i < 10; i++){
(function(a){
setTimeout(()=>console.log(a),0)
})(i)
}
// 控制台输出0-9
复制代码
答案3:
当前作用域产产生了对父作用域的引用。
答案4:
不知道。是跟浏览器的垃圾回收机制有关吗?
开杠了。请问,小伙伴的答案和以上的内容有多少相似程度?
其实,拿着这些问题好好想想,你就会发现这些问题都只是为了最终那一个问题。
闭包的底层实现原理
1. JS执行上下文
我们都知道,我们手写的js代码是要经过浏览器V8进行预编译后才能真正的被执行。例如变量提升、函数提升。举个栗子。
// 栗子:
var d = 'abc';
function a(){
console.log("函数a");
};
console.log(a); // ƒ a(){ console.log("函数a"); }
a(); // '函数a'
var a = "变量a";
console.log(a); // '变量a'
a(); // a is not a function
var c = 123;
// 输出结果及顺序:
// ƒ a(){ console.log("函数a"); }
// '函数a'
// '变量a'
// a is not a function
// 栗子预编后相当于:
function a(){
console.log("函数a");
};
var d;
console.log(a); // ƒ a(){ console.log("函数a"); }
a(); // '函数a'
a = "变量a"; // 此时变量a赋值,函数声明被覆盖
console.log(a); // "变量a"
a(); // a is not a function
复制代码
那么问题来了。 请问是谁来执行预编译操作的?那这个谁又是在哪里进行预编译的?
是的,你的疑惑没有错。js代码运行需要一个运行环境,那这个环境就是执行上下文。 是的,js运行前的预编译也是在这个环境中进行。
js执行上下文分三种:
全局执行上下文
: 代码开始执行时首先进入的环境。函数执行上下文
:函数调用时,会开始执行函数中的代码。eval执行上下文
:不建议使用,可忽略。
那么,执行上下文的周期,分为两个阶段:
创建阶段
- 创建词法环境
- 生成变量对象(
VO
),建立作用域链、作用域链、作用域链(重要的事说三遍) - 确认
this
指向,并绑定this
执行阶段
。这个阶段进行变量赋值,函数引用及执行代码。
你现在猜猜看,预编译是发生在什么时候?
噢,我忘记说了,其实与编译还有另一个称呼:执行期上下文
。
预编译发生在函数执行之前。预编译四部曲为:
- 创建
AO
对象 - 找形参和变量声明,将变量和形参作为AO属性名,值为
undefined
- 将实参和形参相统一
- 在函数体里找到函数声明,值赋予函数体。最后程序输出变量值的时候,就是从
AO
对象中拿。
所以,预编译真正的结果是:
var AO = {
a = function a(){console.log("函数a");};
d = 'abc'
}
复制代码
我们重新来。
1. 什么叫变量对象?
变量对象是 js
代码在进入执行上下文时,js
引擎在内存中建立的一个对象,用来存放当前执行环境中的变量。
2. 变量对象(VO)的创建过程
变量对象的创建,是在执行上下文创建阶段,依次经过以下三个过程:
创建
arguments
对象。对于函数执行环境,首先查询是否有传入的实参,如果有,则会将参数名是实参值组成的键值对放入
arguments
对象中。否则,将参数名和undefined
组成的键值对放入arguments
对象中。
//举个栗子
function bar(a, b, c) {
console.log(arguments); // [1, 2]
console.log(arguments[2]); // undefined
}
bar(1,2)
复制代码
- 当遇到同名的函数时,后面的会覆盖前面的。
console.log(a); // function a() {console.log('Is a ?') }
function a() {
console.log('Is a');
}
function a() {
console.log('Is a ?')
}
/**ps: 在执行第一行代码之前,函数声明已经创建完成.后面的对之前的声明进行了覆盖。**/
复制代码
- 检查当前环境中的变量声明并赋值为
undefined
。当遇到同名的函数声明,为了避免函数被赋值为undefined
,会忽略此声明
console.log(a); // function a() {console.log('Is a ?') }
console.log(b); // undefined
function a() {
console.log('Is a ');
}
function a() {
console.log('Is a ?');
}
var b = 'Is b';
var a = 10086;
/**这段代码执行一下,你会发现 a 打印结果仍旧是一个函数,而 b 则是 undefined。**/
复制代码
根据以上三个步骤,对于变量提升也就知道是怎么回事了。
3. 变量对象变为活动对象
执行上下文的第二个阶段,称为执行阶段,在此时,会进行变量赋值,函数引用并执行其他代码,此时,变量对象变为活动对象。
我们还是举上面的例子:
console.log(a); // function a() {console.log('fjdsfs') }
console.log(b); // undefined
function a() {
console.log('Is a');
}
function a() {
console.log('Is a?');
}
var b = 'Is b';
console.log(b); // 'Is b'
var a = 10086;
console.log(a); // 10086
var b = 'Is b?';
console.log(b); // 'Is b?'
复制代码
在上面的代码中,代码真正开始执行是从第一行 console.log() 开始的,自这之前,执行上下文是这样的:
// 创建过程
EC= {
VO: {}; // 创建变量对象
scopeChain: {}; // 作用域链
}
VO = {
argument: {...}; // 当前为全局上下文,所以这个属性值是空的
a: // 函数 a 的引用地址 b: undefiend // 见上文创建变量对象的第三步}
复制代码
词法作用域(Lexical scope
)
这里想说明,我们在函数执行上下文中有变量,在全局执行上下文中有变量。JavaScript
的一个复杂之处在于它如何查找变量,如果在函数执行上下文中找不到变量,它将在调用上下文中寻找它,如果在它的调用上下文中没有找到,就一直往上一级,直到它在全局执行上下文中查找为止。(如果最后找不到,它就是 undefined
)。
再来举个栗子:
1: let top = 0; //
2: function createWarp() {
3: function add(a, b) {
4: let ret = a + b
5: return ret
6: }
7: return add
8: }
9: let sum = createWarp()
10: let result = sum(top, 8)
11: console.log('result:',result)
复制代码
分析过程如下:
- 在全局上下文中声明变量
top
并赋值为0. - 2 - 8行。在全局执行上下文中声明了一个名为
createWarp
的变量,并为其分配了一个函数定义。其中第3-7行描述了其函数定义,并将函数定义存储到那个变量(createWarp
)中。 - 第9行。我们在全局执行上下文中声明了一个名为
sum
的新变量,暂时,值为undefined
。 - 第9行。遇到
()
,表明需要执行或调用一个函数。那么查找全局执行上下文的内存并查找名为createWarp
的变量。 明显,已经在步骤2中创建完毕。接着,调用它。 - 调用函数时,回到第2行。创建一个新的
createWarp
执行上下文。我们可以在createWarp
的执行上下文中创建自有变量。js
引擎createWarp
的上下文添加到调用堆栈(call stack
)。因为这个函数没有参数,直接跳到它的主体部分. - 3 - 6 行。我们有一个新的函数声明,在
createWarp
执行上下文中创建一个变量add
。add
只存在于createWarp
执行上下文中, 其函数定义存储在名为add
的自有变量中。 - 第7行,我们返回变量
add
的内容。js引擎查找一个名为add
的变量并找到它. 第4行和第5行括号之间的内容构成该函数定义。 createWarp
调用完毕,createWarp
执行上下文将被销毁。add 变量也跟着被销毁。 但add
函数定义仍然存在,因为它返回并赋值给了sum
变量。 (ps:这才是闭包产生的变量存于内存当中的真相
)- 接下来就是简单的执行过程,不再赘述。。
- ……
- 代码执行完毕,全局执行上下文被销毁。sum 和 result 也跟着被销毁。
小结一下
现在,如果再让你回答什么是闭包,你能答出多少?
其实,大家说的都对。不管是函数返回一个函数,还是产生了外部作用域的引用,都是有道理的。
所以,什么是闭包?
- 解释一下作用域链是如何产生的。
- 解释一下js执行上下文的创建、执行过程。
- 解释一下闭包所产生的变量放在哪了。
- 最后请把以上3点结合起来说给面试官听。
深拷贝浅拷贝
浅拷贝:浅拷贝通过ES6新特性Object.assign()或者通过扩展运算法...来达到浅拷贝的目的,浅拷贝修改
副本,不会影响原数据,但缺点是浅拷贝只能拷贝第一层的数据,且都是值类型数据,如果有引用型数据,修改
副本会影响原数据。
深拷贝:通过利用JSON.parse(JSON.stringify())来实现深拷贝的目的,但利用JSON拷贝也是有缺点的,
当要拷贝的数据中含有undefined/function/symbol类型是无法进行拷贝的,当然我们想项目开发中需要
深拷贝的数据一般不会含有以上三种类型,如有需要可以自己在封装一个函数来实现。
复制代码
说一下你对盒模型的理解?
CSS3中的盒模型有以下两种:标准盒模型、IE盒模型
盒模型都是由四个部分组成的,分别是margin、border、padding和content
标准盒模型和IE盒模型的区别在于设置width和height时, 所对应的范围不同
1、标准盒模型的width和height属性的范围只包含了content
2、IE盒模型的width和height属性的范围包含了border、padding和content
可以通过修改元素的box-sizing属性来改变元素的盒模型;
1、box-sizing:content-box表示标准盒模型(默认值)
2、box-sizing:border-box表示IE盒模型(怪异盒模型)
复制代码
CDN的作用
CDN一般会用来托管Web资源(包括文本、图片和脚本等),可供下载的资源(媒体文件、软件、文档等),应用程序(门户网站等)。使用CDN来加速这些资源的访问。
(1)在性能方面,引入CDN的作用在于:
- 用户收到的内容来自最近的数据中心,延迟更低,内容加载更快
- 部分资源请求分配给了CDN,减少了服务器的负载
(2)在安全方面,CDN有助于防御DDoS、MITM等网络攻击:
- 针对DDoS:通过监控分析异常流量,限制其请求频率
- 针对MITM:从源服务器到 CDN 节点到 ISP(Internet Service Provider),全链路 HTTPS 通信
除此之外,CDN作为一种基础的云服务,同样具有资源托管、按需扩展(能够应对流量高峰)等方面的优势。
代码输出结果
Promise.resolve().then(() => {
return new Error('error!!!')
}).then(res => {
console.log("then: ", res)
}).catch(err => {
console.log("catch: ", err)
})
复制代码
输出结果如下:
"then: " "Error: error!!!"
复制代码
返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的return new Error('error!!!')
也被包裹成了return Promise.resolve(new Error('error!!!'))
,因此它会被then捕获而不是catch。
Vuex有哪些基本属性?为什么 Vuex 的 mutation 中不能做异步操作?
有五种,分别是 State、 Getter、Mutation 、Action、 Module
1、state => 基本数据(数据源存放地)
2、getters => 从基本数据派生出来的数据
3、mutations => 提交更改数据的方法,同步
4、actions => 像一个装饰器,包裹mutations,使之可以异步。
5、modules => 模块化Vuex
1、Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过 Action 来提交 mutation实现,这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用。
2、每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。
复制代码