数据类型存储
基本数据类型存储在栈,引用数据类型存储在堆中,并在栈中存储一个引用地址。
栈中大小固定,需要被频繁存取,而堆可以按优先级排序,且大小不固定,JS中不能直接对堆进行操作。
判断数据类型
- typeof
typeof-对象,数组,null都会返回object,其他正确, typeof null返回object是因为底层二进制数据用前三位判断类型,000被判断为object。 - instanceof
instanceof只能判断引用数据类型 - constructor 第一个作用:实例对象通过
constructor访问原型,第二个作用,判断数据类型(注意,改变原型后不能准确判断) - Object.prototype.toString.call(),截取字符串得到结果
Object.prototype.toString.call(a).slice(8, -1)
同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?
这是因为toString是Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object原型上的toString方法。
undefined 和 null 区别
undefined表示未定义,null表示空对象,typeofnull返回Object,两者==返回true,===返回false,在进行数值运算时,比如说null + 1 = 1, 而undefined + 1 = NAN
string为什么会有length属性
对于基本数据类型,当我们读取其值时,后台会创建对应包装类型的实例,然后调用其属性或方法,调用完就会销毁,所以我们能够读取属性,但不能为他添加属性
== 和 === 区别
==会根据变量的值进行类型转换,如1 == '1'为true,而 === 是严格比较,有如下几种情况
- 字符串就是逐位比较
- 数字数值相等,其中正0等于负0,NaN不等于任何值,包括NaN
- 布尔值也是相等
- 对象的引用地址相等,即引用同一个对象,指向同一个堆地址
- Null==undefined 为true ===为false
手写instanceof
const _instanceof = function(left, right) {
let proto = Object.getPrototypeOf(left)
let prototype = right.prototype
while(1) {
if(!proto) return false
if(proto === proto) return true
proto = Object.getPrototypeOf(proto)
}
}
0.1+0.2 !== 0.3
二进制相加,修正可用toFix(指定位数)
isNaN 和 Number.isNaN 函数的区别?
-
函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。
-
函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。
|| 和 &&
- ||如果结果为true返回第一个表达式的值,否则返回第二个
- &&相反,如果结果为true返回第二个表达式的值,否则返回第一个
隐式类型转换
首先简单讲讲原理,js每个值自带一个ToPrimitive方法,这个方法会调用ValueOf和toString方法进行转换
+如果两边至少有一个字符串,则结果会转变为字符串-,*,/转化为数字==会将两边尽量转化为Number>,<如果两边都是字符串,就按字母表比较,否则尽量转为数字- 对象的转换规则
var objToNumber = value => Number(value.valueOf().toString())
objToNumber([]) === 0
objToNumber({}) === NaN
Bigint解决了什么问题
JS中最大安全数字为Number.MAX_SAFE_INTEGER,超过这个数就会造成精度缺失的问题。
let var const
- let和const都是es6新提出的,这两个的作用域都是块级作用域,且二者都没有变量提升,都不能在同一块级作用域内进行重复声明,如果在声明前使用变量,就会出现暂时性死区。
- let声明的变量可以修改,const声明的变量如果是基本数据类型,是不能更改的;如果是引用数据类型如对象数组,由于其在栈中存放的是引用地址,所以只要不改变其引用地址,即往对象或数组的增删改内容是可以的。
new运算符
let person = new Person()
这段代码中,new的作用是什么?
- 创建一个空对象
- 将这个空对象的隐式原型指向构造函数的显式原型
- 将构造函数中的this指向这个空对象
- 判断返回值,如果构造函数有返回值,则判断这个返回值是否为对象类型,如果不是则返回第一步的空对象。
new一个箭头函数会发生什么
由于箭头函数不能改变this指向,也没有prototype属性,更没有arguments参数,所以无法完成上面的第二三步。
箭头函数和普通函数的区别
- 箭头函数没有自己的this,它的this是从上一次作用域继承而来的
- 箭头函数的this不能被call, apply, bind修改
- 箭头函数没有arguments,prototype属性
箭头函数的this指向
箭头函数的this指向继承自上一层作用域的this,可以通过babel理解
// ES6
const obj = {
getArrow() {
return () => {
console.log(this === obj);
};
}
}
// ES5,由 Babel 转译
var obj = {
getArrow: function getArrow() {
var _this = this;
return function () {
console.log(_this === obj);
};
}
};
Map WeakMap
WeakMap和Map的区别就是WeakMap只能以对象作为key,其存在的意义在于:当我们以某个对象作为Map的键时,我们就形成了对那个对象的引用,可当我们不需要这个对象时,除非我们手动释放这个引用,否则垃圾回收机制是不会释放这个对象占用的内存的
而WeakMap键名所引用的对象都是弱引用,即垃圾回收机制不会将其考虑在内,所以当这个对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存,即一旦不再需要,WeakMap里面的键值对会自动消失。
Set和WeakSet也是同理。
让js延迟加载的方式
- defer/async标签
- 监听文档解析事件,解析完成后动态创建script标签引入js脚本
- 把script标签放在最下面
类数组对象的定义和转换方式
类数组就是有一个length属性和若干索引值的对象,如arguments,转换方法如下:
- Array.prototype.slice.call()
- Array.from
- [...]扩展运算符
- Array.prototype.splice.call(arrayLike, 0)
- Array.prototype.concat.call([], arrayLike)
常见的位运算符
- 与&
- 或|
- 异或^ 二者不同时返回1
- 取反~ 如~3 0011取反为 1100 得-4
- 左移运算符
<<左移一位相当于乘2 - 右移运算符
>>右移一位相当于除2
遍历arguments的方法
- 第一种,把arguments转换成数组在遍历
- 第二种,调用Array原型链上的方法,用call等改变指向使arguments调用
DOM 和 BOM
- DOM是文档对象模型,定义了处理网页的方法与接口
- BOM是浏览器对象模型,其中核心为window对象,window对象既是js访问视窗的接口,也是全局对象,document为window的子对象
AJAX请求
创建AJAX请求的步骤:
- 首先创建一个
XMLHTTPRequest对象xhr - 然后在这个对象上用
open方法创建一个HTTP请求,参数为请求方式,url和是否异步 - 在这个对象上用
onreadystatechange方法监听状态变化,当readyState等于4时,判断http状态码,如果为200就可以处理responseText,否则就处理statusText,当然也可以通过onerror处理。 - 除此之外,也可以通过
setRequestHeader方法设置请求头等 - 最后xhr.send()发送请求
fetch
fetch是es6新推出的API,原生支持promise,但不支持abort,也不能监测请求进度,且默认不携带cookie
let url = ''
fetch(url, {
method: 'POST',
body: {
a: 1
}
}).then(res => res.json())
.then((res) => {
})
变量提升
变量提升是因为JS引擎在预编译时会创建一个全局执行上下文环境,创建时会将变量设置为undefined, 函数声明为可用。函数执行前也是同理,会创建一个函数执行上下文,进行相同操作。
DOM节点的相关操作
- 创建
document.createElement() - 插入
fatherElement.appendChild()或InsertBefore - 删除
fatherElement.removeChild() - 获取 ...
for in 和 for of 区别
- for in 用来专门遍历对象,由于会遍历整个原型链,所以性能很差,不推荐使用
- for of可以遍历任何具有iterator接口的数据类型,包括数组、类数组,set,map等,遍历的值为value。
this绑定
- 默认绑定:全局定义变量或函数,this默认指向window,严格模式下为undefined
- 隐式绑定,函数作为某个对象的方法使用,this默认指向函数的上级对象
- 显示绑定,call apply bind
- new操作符, 指向实例对象
map和forEach的区别
- forEach没有返回值,而且forEach并不会改变原来的对象,但forEach里的callback可能会对原来的对象进行修改。因为forEach本身是一个值引用。
let objArr = [
{ name: '云牧', age: 20 },
{ name: '许嵩', age: 30 },
];
objArr.forEach((item) => {
item = {
name: '邓紫棋',
age: '29',
};
});
console.log(JSON.stringify(objArr));
// 打印结果:[{"name": "云牧","age": 20},{"name": "许嵩","age": 30}]
首先forEach相当于在栈中开启一个空间与原数组的这个键指向同一个地址,然后你对这个item进行赋值,相当于切断了这个指向,那么原来的数组中指向未变,自然不会修改原数组
let objArr = [
{ name: '云牧', age: 20 },
{ name: '许嵩', age: 30 },
];
objArr.forEach((item) => {
item = {
name: '邓紫棋',
age: '29',
};
});
console.log(JSON.stringify(objArr));
// 打印结果:[{"name": "云牧","age": 20},{"name": "许嵩","age": 30}]
而你直接修改对象的某个属性,当然是可以更改的,因为都指向同一个地址
let objArr = [
{ name: '云牧', age: 28 },
{ name: '许嵩', age: 30 },
];
objArr.forEach((item) => {
item.name = '邓紫棋';
});
console.log(JSON.stringify(objArr));
// 打印结果:[{"name":"邓紫棋","age":28},{"name":"邓紫棋","age":30}]
- map有返回值,会返回修改之后的数组,然后对每个元素都进行回调函数的操作。
原型与原型链
先从原型开始将起。
- 每一个构造函数都有一个prototype属性,指向一个对象,这个对象就是原型对象,原型对象上存放着实例共有的属性。
- 每一个原型对象都有一个constructor属性,指向指向它的那个构造函数。
- 每一个对象都有一个隐式原型__proto__属性,指向他的原型对象。
- 访问对象的属性时,会首先在对象的自身上查找,如果没有,就会到对象的原型对象上查找,直到原型对象为null。
而这些对象与其原型对象构成层层相连,则为原型链。原型链的终点是Object.prototype,指向null
作用域与作用域链
作用域就是程序运行时某些特定部分中变量,对象等的可访问性。作用域可以分为全局作用域和局部作用域,局部作用域可以分为函数作用域和块级作用域。块级作用域是由{}创建,let和const的作用域即为块级作用域,所声明的变量在指定块的作用域外无法访问。(需注意,当用let声明时,for循环中每一次循环会是一个独立的块级作用域)。
- 从使用方面,作用域链是js的变量查找方式,当访问一个变量时,js引擎首先会在当前作用域查找,如果没有,就会往其上一层作用域寻找。以此类推直到找到全局作用域内仍未找到,则报错。
- 作用域链在js内以数组形式存储,第一个索引是函数本身的执行上下文,下一个是对象外部执行上下文
Object.proto == Function.prototype
返回true,因为Object就是function Object()
webpack基础题
用过哪些常见loader
- url-loader 由于处理图片、字体等。可以设置options的limit阈值,大于阈值则交给file-loader处理,小于则返回base64编码格式
- style-loader、css-loader,less/sass-loader 编译css\sass文件
- babel-loader 把ES6转化为ES5
- ts-loader 编译ts文件
- postcss-loader 配合autoprefixer补齐浏览器前缀
- vue-loader 编译vue单文件组件
用过哪些常见plugin
- mini-css-extract-plugin:分离样式,从js中将css样式提取成单独文件
- html-webpack-plugin:自动引入js文件
- clean-webpack-plugin:清理打包目录
- Uglify-js-plugin:压缩js,清除生产环境的console等
- speed-measure-webpack-plugin:测量每个loader和plugin耗费时间
loader和plugin区别
- loader本质上是一个函数,对除js外的资源进行处理。由于webpack只认识js,所以需要loader对其他类型的资源进行转译的预处理。
- plugin就是插件,它可以扩展webpack的功能,它通过监听webpack运行时广播的事件,在合适时间通过webpack的api改变输出结果。
webpack构建流程
- 初始化:加载和合并配置参数,加载plugin,实例化Compiler对象
- 确定出口 根据配置的entry找到所有入口文件
- 从入口文件出发,调用配置的loader进行编译,然后在找到这个模块依赖的模块,通过递归完成所有编译
- 通过入口文件和模块的依赖关系,将Module组装成Chunk,将Chunk转化成文件输出到文件系统
sourcemap是什么
source map就是把编译打包压缩后的代码,映射回源代码的过程,用来调试源码。