1.23面试题汇总
对象类型与原始值类型的不同之处?
-
原始值类型保存在栈中,值是直接存储在变量访问的位置, 复制原始类型变量的值时,会将值拷贝一份,和原来的值是相互独立的
-
引用变量的名称储存在栈中,但是把其实际对象储存在堆中,且存在一个指针由变量名指向储存在堆中的实际对象,复制对象类型变量的值时,会将存储在变量中的值拷贝一份,也就是它所指向的对象在内存中的地址值。复制操作结束后,它们俩指向同一对象吗,存在深浅拷贝问题,新对象修改会影响旧对象
typeof能否正常判断类型?有何局限
不能,原始值类型除了null都可以正确判断,对象类型除了function其它都显示'object'
== 与 === 区别
- == 首先会判断两者类型是否相同。相同的话就直接进行比较
如果对比双方的类型不一样的话,就会进行类型转换
- === 判断两者类型和值是否相同
模块化有什么优点?有哪几种方案
优点:解决命名冲突 提高代码复用性 提高项目的可维护性
- IIFE(立即执行函数)
var module1 = (function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1 : m1,
m2 : m2
};
})();
- CommonJS
CommonJS采用的是同步导入的方式
CommonJS模块规范主要分为三部分:模块定义、模块标识、模块引用。主要在node中使用,webpack中也有使用
//模块定义、模块标识
//在数据库模块database.js文件中,把各个函数写好,然后暴露出来
module.exports={
'saveDatabase':saveDatabase,//保存数据函数
'saveLastSpider':saveLastSpider,//保存最后一条数据函数
'getInfoByType':getInfoByType,//通过类型查找函数
'getInfoByOrder':getInfoByOrder,//通过排序查找函数
}
// 模块引用
var database = require('./database');//引用模块,将生成的对象指向database对象(命名可以是任意定的)
database.saveDatabase([1,2,3,4,5]);//通过database对象调用相应的函数
- AMD(Asynchronous Module Definition)、
AMD采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
// AMD需要接受两个参数
// 第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数
// 导出
define([id], [dependencies], factory) // 模块名 依赖前置 工厂方法(对象就作为该模块的输出值)
define('mod2', ['mod1'], function (mod1) {
// ...
return {
// ...
}
})
导入
require(['mod1','mod2'],function(mod1, mod2){
// ...
});
- CMD
CMD是SeaJS 在推广过程中对模块定义的规范化产出
对于依赖的模块AMD是提前执行,CMD是延迟执行
define(function (requie, exports, module) {
//依赖可以就近书写
var a = require('./a');
a.test();
...
//软依赖
if (status) {
var b = requie('./b');
b.test();
}
});
- ES Module
ES6在语言标准的层面上,实现了模块功能,成为浏览器和服务器通用的模块解决方案,完全可以取代 CommonJS 和 AMD 规范
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能;
ES Module 与 CommonJS的差别
-
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
-
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
-
CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
-
CommonJS 是动态语法,可以写在判断中 ES Module 是 静态语法,只能写在最顶层(ES2020的import()可以实现动态语法)
-
CommonJS 的
this
指向当前模块,ES Module采用严格模式,this
是undefined
什么是Promise的链
每次调用 then 或 catch 之后返回的都是一个全新的Promise,因此又可以接着使用then或catch方法,由此形成promise链
Promise 构造函数执行和 then 函数执行有什么区别
-
构造 Promise 的时候,构造函数内部的代码是立即执行的(同步代码)
-
then函数在promise.resolve()执行后执行
Promise.resolve()在本轮“事件循环”结束时执行
通过 new 的方式创建对象和通过字面量创建有什么区别
// new Object()
// {}
-
new Object() 方式创建对象本质上是方法调用,涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的 堆栈信息,方法调用结束后,还要释放该堆栈,性能不如字面量的方式。
-
通过对象字面量定义对象时,不会调用Object构造函数。
字面量创建对象,不会调用 Object构造函数, 简洁且性能更好
为什么0.1+0.2!==0.3?
因为在JS中,浮点数是使用64位
固定长度来表示的,其中的1
位表示符号位,11位
用来表示指数位,剩下的52位
尾数位,由于只有52位表示尾数位。
而0.1
转为二进制是一个无限循环数0.0001100110011001100......(1100循环)
由于只能存储52位
尾数位,所以会出现精度缺失,把它存到内存中再取出来转换成十进制就不是原来的0.1了,就变成了0.100000000000000005551115123126
0.1+0.2 转换成二进制计算后
// 0.1 和 0.2 都转化成二进制后再进行运算
0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111
// 转成十进制正好是 0.30000000000000004
什么是执行栈?
-
首先栈特点:先进后出
-
当进入一个执行环境,就会创建出它的执行上下文,然后进行压栈,当程序执行完成时,它的执行上下文就会被销毁,进行弹栈。
-
栈底永远是全局环境的执行上下文,栈顶永远是正在执行函数的执行上下文
-
只有浏览器关闭的时候全局执行上下文才会弹出
为什么js是单线程的?单线程优缺点是什么?
-
因为JS里面有可视的Dom,如果是多线程的话,这个线程正在删除DOM节点,另一个线程正在编辑Dom节点,导致浏览器不知道该听谁的
-
优点:不会出现因线程之间争夺资源导致的死锁现象,所有代码都是同步执行的,没有线程切换的资源开销
-
缺点:单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着,容易造成堵塞
事件触发的过程是怎样的
三个阶段:
-
捕获阶段:事件从根节点流向目标节点,途中流经各个DOM节点,在各个节点上触发捕获事件,直到达到目标节点。
-
目标阶段:事件到达目标节点时,就到了目标阶段,事件在目标节点上被触发
-
冒泡阶段:事件在目标节点上触发后,不会终止,一层层向上冒,回溯到根节点
下面输出是什么
<div id="div1">
<div id="div2">
<div id="div3">
</div>
</div>
</div>
<script>
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
const div3 = document.getElementById('div3')
div1.addEventListener('click', function () {
console.log(1);
})
div1.addEventListener('click', function () {
console.log(3);
}, false)
div1.addEventListener('click', function () {
console.log(2);
}, true)
div2.addEventListener('click', function () {
console.log(4);
})
div2.addEventListener('click', function () {
console.log(6);
}, false)
div2.addEventListener('click', function () {
console.log(5);
}, true)
div3.addEventListener('click', function () {
console.log(7);
})
div3.addEventListener('click', function () {
console.log(9);
}, false)
div3.addEventListener('click', function () {
console.log(8);
}, true)
div1.dispatchEvent(new Event('click'))
div2.dispatchEvent(new Event('click'))
div3.dispatchEvent(new Event('click'))
// 结果: 1 3 2 2 4 6 5 2 5 7 9 8
// dispatchEvent方法默认的是不进行冒泡
// 分析: div1被点击后执行 1 3 2 然后2是div捕获了div2的第三个点击事件
// 4 6 5 是 div2的点击事件
// 2 5 分别是div1和div2捕获了div3的点击事件 然后div3的点击事件执行 输出 7 9 8
</script>