0.1+0.2 === 0.3嘛?为什么?
不相等。在js中,javascript用Number类型来表示数字(整数或浮点数),遵循IEEE 754标准,通过64位二进制来表示一个数字(1 + 11 + 52):符号位 + 指数位(阶码) + 尾码
- 1 符号位,0 表示正数,1 表示负数 s
- 11 指数位(e)
- 52 尾数,小数部分(即有效数字)
0.1 的二进制表示即为: 0.00011001100110011…,是一个无限循环小数,由于储存空间有限,js对二进制进行了截取,造成了精度丢失。同理0.2也是如此。所以我们得到了是两个近似值相加的结果:
// 0.1 和 0.2 都转化成二进制后再进行运算
0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111
// 转成十进制正好是 0.30000000000000004
延伸:
理论上用有限的空间来存储无限的小数是不可能保证精确的,但我们可以处理一下得到我们期望的结果。当你拿到
1.4000000000000001 这样的数据要展示时,建议使用 toPrecision 凑整并 parseFloat 转成数字后再显示,如下:
parseFloat(1.4000000000000001.toPrecision(12)) === 1.4 // True
对于运算类操作,如 ±/* ,就不能使用 toPrecision 了。正确的做法是把小数转成整数后再运算。
js的数据类型
基本类型:Number、Boolean、null、undefined、String、symbol(ES6新增)
引用类型:Object。里面包含function、Array、Date
谷歌67版本中还出现了一种 bigInt(ES2020)。是指安全存储、操作大整数。(但是很多人不把这个做为一个类型)。
js整数是怎么表示的?
通过Number类型来表示的,遵循IEEE 754标准,通过64位二进制来表示一个数字(1 + 11 + 52),最大的安全数字是Math.pow(2,53),对于16位的二进制。
Number()的存储空间是多大?如果后台发送一个超过最大自己的数字怎么办
js的最大安全数字为Math.pow(2,53),53位有效数字,值为:9007199254740992
。如果超出了,会发生截断,等于js能支持的最大数字。比如900719925474099222,那么得到的值会不精确,也就是900719925474099200。
写代码:实现函数能够深度克隆基本类型
浅克隆:
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;
}
事件流是什么?
事件流是网页元素接受事件的顺序,‘DOM2级事件’规定的事件流包括三个阶段:
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
首先发生的事件捕获,为截获事件提供机会。然后是实际的目标接受事件。最后一个阶段是事件冒泡阶段,可以在这个阶段对事件作出响应。虽然捕获阶段在规范中规定不允许响应事件,但是实际上还是会执行,所以有两次机会获取到目标对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件冒泡</title>
</head>
<body>
<div>
<p id="parEle">我是父元素 <span id="sonEle">我是子元素</span></p>
</div>
</body>
</html>
<script type="text/javascript">
var sonEle = document.getElementById('sonEle');
var parEle = document.getElementById('parEle');
parEle.addEventListener('click', function () {
alert('父级 冒泡');
}, false);
parEle.addEventListener('click', function () {
alert('父级 捕获');
}, true);
sonEle.addEventListener('click', function () {
alert('子级冒泡');
}, false);
sonEle.addEventListener('click', function () {
alert('子级捕获');
}, true);
</script>
当有多个嵌套元素,即在捕获阶段
又在`冒泡阶段调用事件处理程序时,事件按照dom事件流的顺序执行事件处理程序:
- 父级捕获
- 子级冒泡
- 子级捕获
- 父级冒泡
且当事件处于目标阶段时,事件调用顺序决定于绑定事件的书写顺序 ,按上面的例子为,先调用冒泡阶段的事件处理程序,再调用捕获阶段的事件处理程序。依次alert出“子集冒泡”,“子集捕获”。
事件是如何实现的?
基于发布的订阅模式,就是在浏览器加载的时候会读取相关事件的代码,但是只有实际等到具体的事件触发的时候才会执行。
比如点击按钮,这是个事件(Event),而负责处理时间的代码段通常被称为事件处理程序(Event Handler),也就是【启动对话框的显示】这个动作。
在Web端,我们常见的就是DOM事件:
- DOM0级事件,直接在html元素上绑定on-event,比如onclick,取消的话,dom.onclick = null,同一事件只能有一个处理程序,后面的会覆盖前面的。
- DOM2级事件,通过addEventListener注册事件,通过removeEventListener来删除事件,一个事件可以有多个事件处理程序,按照顺序执行,捕获事件和冒泡事件。
- DOM3级事件,增加了事件类型,比如UI事件,焦点事件,鼠标事件
new一个函数发生了什么?
构造调用:
- 创造一个全新的对象
- 这个对象会被执行[[Prototype]]连接,将这个新对象的[[Prototype]]链接到这个构造函数.prototype所指向的对象
- 这个新函数会绑定到函数调用的this
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
new一个构造函数,如果函数返回 return {}、return null、return 1、return true会发生什么情况?
如果函数返回一个对象,那么new这个函数调用返回这个函数的返回对象,否则返回new创建的新对象
symbol有什么用处?
可以用来表示一个独一无二的变量,防止命名冲突。还可以利用symbol
不会被常规的方法(除了Objet.getOwnPropertySymbols
外)遍历到,所以可以用来模拟私有变量。
主要用来提供遍历接口,布置了symbol.iterator
的对象才可以使用for ...of
循环,可以统一处理数据结构。调用之后会返回一个遍历器对象,包含有一个next方法,使用next方法后由两个返回值value和done分别表示函数当前执行位置的值和是否遍历完毕。
Symbol.for()可以在全局访问symbol
闭包是什么?
闭包是指有权访问另一个函数作用域中的变量函数。
javascript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个代码作用域规则会确定,执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。
什么是作用域?
ES5中只存在两种作用域:全局作用域和函数作用域。在javascript中,我们将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量(变量名或者函数名)查找
什么是作用域链?
首先要了解作用域链,当访问一个变量时,编译器在执行这段代码时,会首先从当前的作用域中查找是否有这个标识符,如果没有找到,就回去父作用域查找,如果父作用域还没有找到继续向上查找,直到全局作用域为止,而作用域链,就是有当前作用域与上层作用域的一系列变量对象组成,它保证了当前执行的作用域对符合访问权限的变量和函数的有序访问。
闭包产生的本质
当前环境中存在指向父级作用域的引用
什么是闭包
闭包是一种特殊的对象,它由两部分组成:执行上下文(代号A),以及在该执行上下文中创建的函数(代号B),当B执行时,如果访问了A中变量对象的值,那么闭包就会产生,且在Chrome中使用这个执行上下文A的函数名代指闭包。
一般如何产生闭包
- 返回函数
- 函数当做参数传递
闭包的应用场景
- 柯里化 bind
- 模块
NaN是什么,用typeof会输出什么?
Not a Number,表示非数字,typeof NaN === ‘number’
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
- “”
了解this嘛,bind,call,apply具体指什么?
它们都是函数的方法
- call:Array.prototype.call(this,args1,args2)
- apply:Array.prototype.apply(this,[args1,args2]
四条规则:
- 默认绑定:没有其他修饰(bind,apply,call),在非严格模式下定义指向全局对象,在严格模式下定义指向undefined
function foo() {
console.log(this.a);
}
var a = 2;
foo();
//2
- 隐式绑定:调用位置是否有上下文对象,或者是否被某个对象拥有或者包含,那么隐式绑定规则会把函数调用中的this绑定到这个上下文对象。而且,对象属性链只有上一层或者说最后一层在调用中起作用。
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo,
}
obj.foo(); // 2
- 显示绑定:通过在函数上运行call和apply,来显示的绑定this
function foo() {
console.log(this.a);
}
var obj = {
a: 2
};
foo.call(obj);
- 显示绑定之硬绑定
function foo(something) {
console.log(this.a, something);
return this.a + something;
}
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments);
};
}
var obj = {
a: 2
}
var bar = bind(foo, obj);
New绑定,new调用函数会创建一个全新的对象,并将这个对象绑定到函数调用的this
- New绑定时,如果new一个硬绑定函数,那么会用new新建的对象替换这个硬绑定this
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log(bar.a)
//2
手写bind、apply、call
//call
Function.prototype.call = function (context,...args) {
context = context || window;
const fnSymbol = Symbol("fn");
context[fnSymbol] = this;
context[fnSymbol](...args);
delete context[fnSymbol];
}
//apply
Function.prototype.apply = function (context,argsArr) {
context = context || window;
const fnSymbol = Symbol("fn");
context[fnSymbol] = this;
context[fnSymbol](...argsArr)
delete context[fnSymbol]
}
// bind
Function.prototype.bind = function(context,...args) {
context = context || window;
const fnSymbol = Symbol("fn");
context[fnSymbol] = this;
return function (..._args){
args = args.concat(_args);
context[fnSymbol](...args);
delete context[fnSymbol];
}
}
setTimeout(fn,0)多久财智星,EventLoop
setTimeout按照顺序放到队列里面,然后等待函数调用栈清空之后才开始执行,而这些操作进入队列的顺序,则由设定的延迟时间来决定。
手写Promise原理
class MyPromise{
constructor(fn){
this.resolvedCallbacks = [];
this.rejectedCallbacks = [];
this.state = 'PENDING';
this.value = '';
fn(this.resolve.bind(this),this.reject.bind(this))
}
resolve(value){
if(this.state == 'PENDING'){
this.state = 'RESOLVED';
this.value = value;
this.resolvedCallbacks.map(cb => cb(value));
}
}
reject(value){
if(this.state == 'PENDING'){
this.state = 'REJECTED'
this.value = value
this.rejectedCallbacks.map(cb => cb(value));
}
}
then(onFulfilled,onRejected){
if(this.state === 'PENDING'){
this.resolvedCallbacks.push(onFulfilled);
this.rejectedCallbacks.push(onRejected);
}
if(this.state === 'RESOLVED'){
onFulfilled(this.value);
}
if(this.state == 'REJECTED'){
onRejected(this.value)
}
}
}
js脚本加载问题,async、defer问题