《前端100问》50
41、下面代码输出什么
var a = 10;
(function () {
console.log(a)//undefined
a = 5
console.log(window.a)//10
var a = 20;
console.log(a)//20
})()
分别为 undefined
10
20
,原因是作用域问题
在内部声名var a = 20
;相当于先声明 var a
然后再执行赋值操作,这是在 IIFE 内形成的独立作用域。
如果把 var a=20
注释掉,那么 a
只有在外部有声明,显示的就是外部的 a
变量的值了。结果 a
会是 10
5
5
42、实现一个 sleep 函数
比如 sleep(1000) 意味着等待1000毫秒,可从 Promise、Generator、Async/Await 等角度实现
// Promise 实现
const sleep = time => new Promise(resolve => setTimeout(resolve, time))
sleep(1000).then(() => {
console.log(1)
})
// Generator
function* sleepGenerator(time) {
yield new Promise(resolve => setTimeout(resolve, time))
}
//next() 返回一个由 yield表达式生成的值。value拿到返回值
sleepGenerator(1000)
.next()
.value.then(() => {
console.log(1)
})
//async
const sleep = time => new Promise(resolve => setTimeout(resolve, time))
async function output() {
await sleep(1000)
console.log(1)
}
output()
// ES5 回调函数
function sleep(callback, time) {
setTimeout(callback, time)
}
sleep(function output() {
console.log(1)
}, 1000)
43、使用 sort() 进行排序,输出结果
使用 sort() 对数组 [3, 15, 8, 29, 102, 22] 进行排序,输出结果
[3, 15, 8, 29, 102, 22].sort() // [ 102, 15, 22, 29, 3, 8 ]
根据MDN上对Array.sort()
的解释,默认的排序方法会将数组元素转换为字符串,然后比较字符串中字符的UTF-16编码顺序来进行排序。所以'102'
会排在 '15'
前面。
Array.prototype.sort()
sort()
方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的
返回值:
排序后的数组。请注意,数组已原地排序,并且不进行复制。
[3, 15, 8, 29, 102, 22].sort(function(a,b) {
return a-b;
})
//输出: [3, 8, 15, 22, 29, 102]
44、介绍 HTTPS 握手过程
一、Client → Server
Client Hello
握手开始时,总是由先客户端会发送 Client Hello
信息给服务端,主要包含
- Version Number 客户端支持的协议版本
- Randomly Generated Data 32 字节长度的随机值,用于之后生成主密钥。
- Session Identification Session ID,第一次连接时为空。
- Cipher Suite 客户端支持的加密算法列表,按优先级顺序排列。
二、Server → Client
Server Hello
接着,服务端收到客户端发来的消息之后,会返回 Server Hello
信息给客户端,告知客户端接下来使用的一些参数
- Version Number 通信协议版本
- Randomly Generated Data 32 字节长度的随机值,用于之后生成主密钥
- Session Identification Session ID
- Cipher Suite 加密算法
Server Certificate 证书
服务端还会带上证书返回给客户端。证书中含有服务端的公钥、网站地址、证书的颁发机构等信息。
客户端收到服务端返回的证书之后,会验证该证书的真实合法性。
Server Key Exchange 额外数据
这个是可选的,取决于使用的加密算法。主要是携带密钥交换的额外数据。
Server Hello Done
表示服务端已经发送完毕,并等待客户端回应。
三、Client → Server
Client Key Exchange
客户端使用之前发送给服务端及服务端返回的随机数,生成预主密钥,然后用服务端返回的公钥进行加密。
Change Cipher Spec
告诉服务端,之后的所有信息都会使用协商好的密钥和算法加密
Client Finished
客户端的握手工作已经完成。这条信息是握手过程中所有消息的散列值。
四、Server → Client
Change Cipher Spec Message
告知客户端,会使用刚刚协商的密钥来加密信息
Server Finished Message
表示服务端的握手工作已经完成
数字证书申请流程
- 网站提交身份信息给CA机构
- CA机构审核信息的真实性
- 对信息进行Hash,计算信息摘要
- CA机构的密钥加密信息摘要,得到数字签名
浏览器验证证书
- 浏览器利用证书的原始信息计算得到信息摘要
- 利用CA的公钥解密数字证书中的数字签名,解密出来的数据也是信息摘要
- 比较两个摘要是否相等
45、HTTPS 握手过程中,客户端如何验证证书的合法性
首先浏览器读取证书中的证书所有者、有效期等信息进行校验,校验证书的网站域名是否与证书颁发的域名一致,校验证书是否在有效期内
浏览器开始查找操作系统中已内置的受信任的证书发布机构CA,与服务器发来的证书中的颁发者CA比对,用于校验证书是否为合法机构颁发
两种情况:
如果找不到,浏览器就会报错,说明服务器发来的证书是不可信任的。
如果找到,那么浏览器就会从操作系统中取出颁发者CA 的公钥(多数浏览器开发商发布
版本时,会事先在内部植入常用认证机关的公开密钥),然后对服务器发来的证书里面的签名进行解密
浏览器使用相同的hash算法计算出服务器发来的证书的hash值,将这个计算的hash值与证书中签名做对比
对比结果一致,则证明服务器发来的证书合法,没有被冒充
46、输出以下代码执行的结果并解释为什么
var obj = {
'2': 3,
'3': 4,
'length': 2,
'splice': Array.prototype.splice,
'push': Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj)
MDN描述:
push方法将值追加到数组中。
push
方法具有通用性。该方法和 call()
或 apply()
一起使用时,可应用在类似数组的对象上。
push
方法根据 length
属性来决定从哪里开始插入给定的值。
如果 length
不能被转成一个数值,则插入的元素索引为 0,包括 length
不存在时。
当 length
不存在时,将会创建它。
var obj = {
'2': 3,
'3': 4,
'length': 2,
'push': Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj)
//打印: {2: 1, 3: 2, length: 4, push: ƒ}
var obj1 = {
'2': 3,
'3': 4,
'length': 2,
'splice': Array.prototype.splice,
'push': Array.prototype.push
}
obj1.push(1)
obj1.push(2)
console.log(obj1)
//Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ]
var obj2 = {
2: 3,
3: 4,
push: Array.prototype.push
}
obj2.push(1)
obj2.push(2)
console.log(obj2)
// {0: 1, 1: 2, 2: 3, 3: 4, length: 2, push: ƒ}
解释:
push
方法具有通用性,所以可以给类数组使用。具体是根据类数组的 length
属性,将其转换为数值当做对象[数值]
来使用,而对象使用对象[数值]
时,数值一律当成字符处理也是就是obj[1]
和 obj['1']
是等价的。
所以push
会解析 length
的值 往obj[2]
push 一个 1
,所以obj
的属性 2
的值会变成 1
,此时 obj
并没有数组的独有方法,所以JS解析器还是会打印一个对象。
而obj1
有一个只有数组的才有的 splice
方法,而且 length
为 4
所以 JS解析器会将其当成数组来打印输出,从下标0
开始读数据,但是并没有数据会补空最后输出。
47、双向绑定和 vuex 是否冲突
在严格模式下直接使用确实会有问题。
在严格模式中使用Vuex,当用户输入时,v-model 会试图直接修改属性值,但这个修改不是在 mutation中修改的,所以会抛出一个错误。
处理方法:
来自官网文档:
当在严格模式中使用 Vuex 时,在属于 Vuex 的 state 上使用 v-model
会比较棘手:
<input v-model="obj.message">
假设这里的 obj
是在计算属性中返回的一个属于 Vuex store 的对象,在用户输入时,v-model
会试图直接修改 obj.message
。在严格模式中,由于这个修改不是在 mutation 函数中执行的, 这里会抛出一个错误。
方法一:不用v-model
直接绑定value
值和监听input
事件
用“Vuex 的思维”去解决这个问题的方法是:给 <input>
中绑定 value,然后侦听 input
或者 change
事件,在事件回调中调用一个方法:
<input :value="message" @input="updateMessage">
// ...
computed: {
...mapState({
message: state => state.obj.message
})
},
methods: {
updateMessage (e) {
this.$store.commit('updateMessage', e.target.value)
}
}
下面是 mutation 函数:
// ...
mutations: {
updateMessage (state, message) {
state.obj.message = message
}
}
方法二:双向绑定的计算属性 给计算属性设置getter
和setter
必须承认,这样做比简单地使用“v-model
+ 局部状态”要啰嗦得多,并且也损失了一些 v-model
中很有用的特性。另一个方法是使用带有 setter 的双向绑定计算属性:
<input v-model="message">
// ...
computed: {
message: {
get () {
return this.$store.state.obj.message //读 Vuex 数据
},
set (value) {
this.$store.commit('updateMessage', value)//提交 mutation 更改 Vuex 数据
}
}
}
48、call 和 apply 的区别是什么,哪个性能更好一些
Function.prototype.apply
和Function.prototype.call
的作用是一样的,区别在于传入参数的不同;- 第一个参数都是,指定函数体内
this
的指向; - 第二个参数开始不同,
apply
是传入带下标的集合,数组或者类数组,apply
把它传给函数作为参数,call
从第二个开始传入的参数是不固定的,都会传给函数作为参数。 call
比apply
的性能要好,平常可以多用call
,call
传入参数的格式正是内部所需要的格式。
49、为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 gif 图片?
前端监控的原理
所谓的前端监控,其实是在满足一定条件后,由Web页面将用户信息(UA/鼠标点击位置/页面报错/停留时长/etc)上报给服务器的过程。一般是将上报数据用url_encode(百度统计/CNZZ)或JSON编码(神策/诸葛io)为字符串,通过url参数传递给服务器,然后在服务器端统一处理。
这套流程的关键在于:
1)能够收集到用户信息;
2)能够将收集到的数据上报给服务器。也就是说,只要能上报数据,无论是请求GIF文件还是请求js文件或者是调用页面接口,服务器端其实并不关心具体的上报方式。
首先,为什么不能直接用GET/POST/HEAD请求接口进行上报?
一般而言,打点域名都不是当前域名,所以所有的接口请求都会构成跨域。而跨域请求很容易出现由于配置不当被浏览器拦截并报错,这是不能接受的。所以,直接排除。
为什么不能用请求其他的文件资源(js/css/ttf)的方式进行上报?
这和浏览器的特性有关。通常,创建资源节点后只有将对象注入到浏览器DOM树后,浏览器才会实际发送资源请求。反复操作DOM不仅会引发性能问题,而且载入js/css资源还会阻塞页面渲染,影响用户体验。
但是图片请求例外。构造图片打点不仅不用插入DOM,只要在 JS 中 new 出 Image 对象就能发起请求,而且还没有阻塞问题,在没有 JS 的浏览器环境中也能通过 img标签正常打点,这是其他类型的资源请求所做不到的。
同样都是图片,上报时选用了1x1的透明GIF,而不是其他的PNG/JEPG/BMP文件?
首先,1x1像素是最小的合法图片。
而且,因为是通过图片打点,所以图片最好是透明的,这样一来不会影响页面本身展示效果,二者表示图片透明只要使用一个二进制位标记图片是透明色即可,不用存储色彩空间数据,可以节约体积。
因为需要透明色,所以可以直接排除JEPG(BMP32格式可以支持透明色)。
在体积方面最小的BMP文件需要74个字节,PNG需要67个字节,而合法的GIF,只需要43个字节。
同样的响应,GIF可以比BMP节约41%的流量,比PNG节约35%的流量。
所以,总结:
前端监控使用GIF进行上报主要是因为:
- 没有跨域问题;
- 能够完成整个 HTTP 请求+响应(尽管不需要响应内容)
- 触发 GET 请求之后不需要获取和处理数据、服务器也不需要发送数据
- 不会阻塞页面加载,影响用户体验
- 相比 XMLHttpRequest 对象发送 GET 请求,性能上更好
- 在所有图片中体积最小,相较BMP/PNG,可以节约41%/35%的网络资源。
50、实现 (5).add(3).minus(2) 功能。
JavaScript 的 Number
对象是经过封装的能让你处理数字值的对象。Number
对象由 Number()
构造器创建。
(5)
和 Number(5)
完全等价
Number.isNaN()
确定传递的值是否是 NaN。
Number.prototype.valueOf()
valueOf()
方法返回一个被 Number
对象包装的原始值。
Number.prototype.add = function (n) {
if (typeof n !== 'number') throw new Error('请输入数字~')
return this.valueOf() + n
}
Number.prototype.minus = function (n) {
if (typeof n !== 'number') throw new Error('请输入数字~')
return this.valueOf() - n
}
console.log((5).add(3).minus(2))
Number.prototype.add = function (value) {
let number = parseFloat(value)
if (typeof number !== 'number' || Number.isNaN(number)) {
throw new Error('请输入数字或者数字字符串~')
}
return this + number
}
Number.prototype.minus = function (value) {
let number = parseFloat(value)
if (typeof number !== 'number' || Number.isNaN(number)) {
throw new Error('请输入数字或者数字字符串~')
}
return this - number
}
console.log((5).add(3).minus(2))
//更加强壮,排除了NaN 兼容了字符串
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!