为什么 0.1 + 0.2 != 0.3,请详述理由
因为 JS 采用 IEEE 754 双精度版本(64位),并且只要采用 IEEE 754 的语言都有该问题。
我们都知道计算机表示十进制是采用二进制表示的,所以 0.1
在二进制表示为
// (0011) 表示循环
0.1 = 2^-4 * 1.10011(0011)
那么如何得到这个二进制的呢,我们可以来演算下
小数算二进制和整数不同。乘法计算时,只计算小数位,整数位用作每一位的二进制,并且得到的第一位为最高位。所以我们得出 0.1 = 2^-4 * 1.10011(0011)
,那么 0.2
的演算也基本如上所示,只需要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)
。
回来继续说 IEEE 754 双精度。六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.1
和 0.2
都是无限循环的二进制了,所以在小数位末尾处需要判断是否进位(就和十进制的四舍五入一样)。
所以 2^-4 * 1.10011...001
进位后就变成了 2^-4 * 1.10011(0011 * 12次)010
。那么把这两个二进制加起来会得出 2^-2 * 1.0011(0011 * 11次)0100
, 这个值算成十进制就是 0.30000000000000004
下面说一下原生解决办法,如下代码所示
parseFloat((0.1 + 0.2).toFixed(10))
常⽤的meta标签有哪些
meta
标签由 name
和 content
属性定义,用来描述网页文档的属性,比如网页的作者,网页描述,关键词等,除了HTTP标准固定了一些name
作为大家使用的共识,开发者还可以自定义name。
常用的meta标签:
(1)charset
,用来描述HTML文档的编码类型:
(2) keywords
,页面关键词:
(3)description
,页面描述:
(4)refresh
,页面重定向和刷新:
(5)viewport
,适配移动端,可以控制视口的大小和比例:
其中,content
参数有以下几种:
width viewport
:宽度(数值/device-width)height viewport
:高度(数值/device-height)initial-scale
:初始缩放比例maximum-scale
:最大缩放比例minimum-scale
:最小缩放比例user-scalable
:是否允许用户缩放(yes/no)
(6)搜索引擎索引方式:
其中,content
参数有以下几种:
all
:文件将被检索,且页面上的链接可以被查询;none
:文件将不被检索,且页面上的链接不可以被查询;index
:文件将被检索;follow
:页面上的链接可以被查询;noindex
:文件将不被检索;nofollow
:页面上的链接不可以被查询。
说一下SPA单页面有什么优缺点?
优点:
1.体验好,不刷新,减少 请求 数据ajax异步获取 页面流程;
2.前后端分离
3.减轻服务端压力
4.共用一套后端程序代码,适配多端
缺点:
1.首屏加载过慢;
2.SEO 不利于搜索引擎抓取
const对象的属性可以修改吗
const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。
但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。
JS 隐式转换,显示转换
一般非基础类型进行转换时会先调用 valueOf,如果 valueOf 无法返回基本类型值,就会调用 toString
字符串和数字
- "+" 操作符,如果有一个为字符串,那么都转化到字符串然后执行字符串拼接
- "-" 操作符,转换为数字,相减 (-a, a * 1 a/1) 都能进行隐式强制类型转换
[] + {} 和 {} + []
布尔值到数字
- 1 + true = 2
- 1 + false = 1
转换为布尔值
- for 中第二个
- while
- if
- 三元表达式
- || (逻辑或) && (逻辑与)左边的操作数
符号
- 不能被转换为数字
- 能被转换为布尔值(都是 true)
- 可以被转换成字符串 "Symbol(cool)"
宽松相等和严格相等
宽松相等允许进行强制类型转换,而严格相等不允许
字符串与数字
转换为数字然后比较
其他类型与布尔类型
- 先把布尔类型转换为数字,然后继续进行比较
对象与非对象
- 执行对象的 ToPrimitive(对象)然后继续进行比较
假值列表
- undefined
- null
- false
- +0, -0, NaN
- ""
对盒模型的理解
CSS3中的盒模型有以下两种:标准盒子模型、IE盒子模型 盒模型都是由四个部分组成的,分别是margin、border、padding和content。
标准盒模型和IE盒模型的区别在于设置width和height时,所对应的范围不同:
- 标准盒模型的width和height属性的范围只包含了content,
- IE盒模型的width和height属性的范围包含了border、padding和content。
可以通过修改元素的box-sizing属性来改变元素的盒模型:
box-sizeing: content-box
表示标准盒模型(默认值)box-sizeing: border-box
表示IE盒模型(怪异盒模型)
如何阻止事件冒泡
- 普通浏览器使用:event.stopPropagation()
- IE浏览器使用:event.cancelBubble = true;
懒加载的概念
懒加载也叫做延迟加载、按需加载,指的是在长网页中延迟加载图片数据,是一种较好的网页性能优化的方式。在比较长的网页或应用中,如果图片很多,所有的图片都被加载出来,而用户只能看到可视窗口的那一部分图片数据,这样就浪费了性能。
如果使用图片的懒加载就可以解决以上问题。在滚动屏幕之前,可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,减少了服务器的负载。懒加载适用于图片较多,页面列表较长(长列表)的场景中。
常见的HTTP请求方法
- GET: 向服务器获取数据;
- POST:将实体提交到指定的资源,通常会造成服务器资源的修改;
- PUT:上传文件,更新数据;
- DELETE:删除服务器上的对象;
- HEAD:获取报文首部,与GET相比,不返回报文主体部分;
- OPTIONS:询问支持的请求方法,用来跨域请求;
- CONNECT:要求在与代理服务器通信时建立隧道,使用隧道进行TCP通信;
- TRACE: 回显服务器收到的请求,主要⽤于测试或诊断。
代码输出结果
Promise.reject('err!!!')
.then((res) => {
console.log('success', res)
}, (err) => {
console.log('error', err)
}).catch(err => {
console.log('catch', err)
})
输出结果如下:
error err!!!
我们知道,.then
函数中的两个参数:
- 第一个参数是用来处理Promise成功的函数
- 第二个则是处理失败的函数
也就是说Promise.resolve('1')
的值会进入成功的函数,Promise.reject('2')
的值会进入失败的函数。
在这道题中,错误直接被then
的第二个参数捕获了,所以就不会被catch
捕获了,输出结果为:error err!!!'
但是,如果是像下面这样:
Promise.resolve()
.then(function success (res) {
throw new Error('error!!!')
}, function fail1 (err) {
console.log('fail1', err)
}).catch(function fail2 (err) {
console.log('fail2', err)
})
在then
的第一参数中抛出了错误,那么他就不会被第二个参数不活了,而是被后面的catch
捕获到。
行内元素有哪些?块级元素有哪些? 空(void)元素有那些?
- 行内元素有:
a b span img input select strong
; - 块级元素有:
div ul ol li dl dt dd h1 h2 h3 h4 h5 h6 p
;
空元素,即没有内容的HTML元素。空元素是在开始标签中关闭的,也就是空元素没有闭合标签:
- 常见的有:
、
、、
、
、
;
- 鲜见的有:
、
、
、
、
、、
、、
、
、
。
代码输出结果
console.log(1)
setTimeout(() => {
console.log(2)
})
new Promise(resolve => {
console.log(3)
resolve(4)
}).then(d => console.log(d))
setTimeout(() => {
console.log(5)
new Promise(resolve => {
resolve(6)
}).then(d => console.log(d))
})
setTimeout(() => {
console.log(7)
})
console.log(8)
输出结果如下:
1
3
8
4
2
5
6
7
代码执行过程如下:
- 首先执行script代码,打印出1;
- 遇到第一个定时器,加入到宏任务队列;
- 遇到Promise,执行代码,打印出3,遇到resolve,将其加入到微任务队列;
- 遇到第二个定时器,加入到宏任务队列;
- 遇到第三个定时器,加入到宏任务队列;
- 继续执行script代码,打印出8,第一轮执行结束;
- 执行微任务队列,打印出第一个Promise的resolve结果:4;
- 开始执行宏任务队列,执行第一个定时器,打印出2;
- 此时没有微任务,继续执行宏任务中的第二个定时器,首先打印出5,遇到Promise,首选打印出6,遇到resolve,将其加入到微任务队列;
- 执行微任务队列,打印出6;
- 执行宏任务队列中的最后一个定时器,打印出7。
对this对象的理解
this 是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this 的指向可以通过四种调用模式来判断。
- 第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。
- 第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时,this 指向这个对象。
- 第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。
- 第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。
这四种方式,使用构造器调用模式的优先级最高,然后是 apply、call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式。
什么是 DOM 和 BOM?
- DOM 指的是文档对象模型,它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。
- BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 location 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象。
写代码:实现函数能够深度克隆基本类型
浅克隆:
function shallowClone(obj) {
let cloneObj = {};
for (let i in obj) {
cloneObj[i] = obj[i];
}
return cloneObj;
}
深克隆:
- 考虑基础类型
引用类型
- RegExp、Date、函数 不是 JSON 安全的
- 会丢失 constructor,所有的构造函数都指向 Object
- 破解循环引用
function deepCopy(obj) {
if (typeof obj === 'object') {
var result = obj.constructor === Array ? [] : {};
for (var i in obj) {
result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];
}
} else {
var result = obj;
}
return result;
}
Promise.any
描述:只要 promises
中有一个fulfilled
,就返回第一个fulfilled
的Promise
实例的返回值。
实现
Promise.any = function(promises) {
return new Promise((resolve, reject) => {
if(Array.isArray(promises)) {
if(promises.length === 0) return reject(new AggregateError("All promises were rejected"));
let count = 0;
promises.forEach((item, index) => {
Promise.resolve(item).then(
value => resolve(value),
reason => {
count++;
if(count === promises.length) {
reject(new AggregateError("All promises were rejected"));
};
}
);
})
}
else return reject(new TypeError("Argument is not iterable"));
});
}
为什么udp不会粘包?
- TCP协议是⾯向流的协议,UDP是⾯向消息的协议。UDP段都是⼀条消息,应⽤程序必须以消息为单位提取数据,不能⼀次提取任意字节的数据
- UDP具有保护消息边界,在每个UDP包中就有了消息头(消息来源地址,端⼝等信息),这样对于接收端来说就容易进⾏区分处理了。传输协议把数据当作⼀条独⽴的消息在⽹上传输,接收端只能接收独⽴的消息。接收端⼀次只能接收发送端发出的⼀个数据包,如果⼀次接受数据的⼤⼩⼩于发送端⼀次发送的数据⼤⼩,就会丢失⼀部分数据,即使丢失,接受端也不会分两次去接收。
== 操作符的强制类型转换规则?
对于 ==
来说,如果对比双方的类型不一样,就会进行类型转换。假如对比 x
和 y
是否相同,就会进行如下判断流程:
- 首先会判断两者类型是否相同,相同的话就比较两者的大小;
- 类型不相同的话,就会进行类型转换;
- 会先判断是否在对比
null
和undefined
,是的话就会返回true
- 判断两者类型是否为
string
和number
,是的话就会将字符串转换为number
1 == '1'
↓
1 == 1
- 判断其中一方是否为
boolean
,是的话就会把boolean
转为number
再进行判断
'1' == true
↓
'1' == 1
↓
1 == 1
- 判断其中一方是否为
object
且另一方为string
、number
或者symbol
,是的话就会把object
转为原始类型再进行判断
'1' == { name: 'js' } ↓'1' == '[object Object]'
使用 clear 属性清除浮动的原理?
使用clear属性清除浮动,其语法如下:
clear:none|left|right|both
如果单看字面意思,clear:left 是“清除左浮动”,clear:right 是“清除右浮动”,实际上,这种解释是有问题的,因为浮动一直还在,并没有清除。
官方对clear属性解释:“元素盒子的边不能和前面的浮动元素相邻”,对元素设置clear属性是为了避免浮动元素对该元素的影响,而不是清除掉浮动。
还需要注意 clear 属性指的是元素盒子的边不能和前面的浮动元素相邻,注意这里“前面的”3个字,也就是clear属性对“后面的”浮动元素是不闻不问的。考虑到float属性要么是left,要么是right,不可能同时存在,同时由于clear属性对“后面的”浮动元素不闻不问,因此,当clear:left有效的时候,clear:right必定无效,也就是此时clear:left等同于设置clear:both;同样地,clear:right如果有效也是等同于设置clear:both。由此可见,clear:left和clear:right这两个声明就没有任何使用的价值,至少在CSS世界中是如此,直接使用clear:both吧。
一般使用伪元素的方式清除浮动:
.clear::after{ content:''; display: block; clear:both;}
clear属性只有块级元素才有效的,而::after等伪元素默认都是内联水平,这就是借助伪元素清除浮动影响时需要设置display属性值的原因。