前端面试知识点整理——JavaScript
文章目录
- 一、js基本数据类型和复杂数据类型的区别
- 二、var、let、const的区别
- 三、ES5 继承和 ES6 继承的区别
- 四、null 和 undefined 区别
- 五、typeof和 instanceof 区别
- 六、instanceof 的原理和弊端
- 七、null > 0, null < 0, null >= 0, null <= 0, null == 0 分别输出什么?
- 八、js 的类型转换
- 九、prototype 是啥,什么是原型,什么是实例原型,说说你的理解
- 十、js 为什么不支持多继承
- 十一、深拷贝和浅拷贝的区别
- 十二、匿名函数、箭头函数
- 十三、变量提升
- 十四、NaN
- 十五、map和object的区别
- 十六、map和weakmap区别
- 十七、常用的正则表达式
- 十八、json
- 十九、将类数组转为数组
- 二十、常见位运算符
- 二十一、ajax请求
- 二十二、异步编程的实现方式
一、js基本数据类型和复杂数据类型的区别
基本数据类型(简单数据类型):number、boolean、null、undefined、string、symbol、(bigInt)
复杂数据类型(引用数据类型):object(Object、Array、Function、Date)
区别:
1.声明变量时不同的内存分配
2.不同的访问机制
3.复制变量时的不同
4.参数传递时的不同
基本包装类型:String、Boolean、Number都是,是一种把简单数据类型包装成复杂数据类型的过程,让简单数据类型拥有了属性和方法。如str.length
二、var、let、const的区别
1.let、const都是块级局部变量,只在当前代码块起作用,var不受块级作用域的限制,他只存在全局作用域和函数作用域的概念
2.let、const必须先声明后使用,而且const必须在声明时进行初始化,后面不能进行修改(对象或数组本身不能修改,但是里面的内容可以修改)。而var存在变量提升,可以先使用后声明。
3.同一作用域下,let、const不能声明同名变量,但是var可以
三、ES5 继承和 ES6 继承的区别
ES5:ES5的继承是通过构造函数(可以定义私有属性/方法、不能定义共享的属性/方法)和原型对象(可以定义公有属性和方法、不能定义私有属性方法)来实现的,称为组合继承。在这里,子构造函数属性的继承可以通过Father.call(this,参数1,参数2)来实现(子类可以拥有自己独立的属性),改变了父构造函数的this指向;方法的继承是通过让子类原型对象 等于父类实例,并通过原型链的查找机制来获得位于 父原型对象中的方法(方法可复用)。这里不能通过 子原型对象 = 父原型对象的方式,这会导致两个对象相互影响,同步改变。
ES6:ES6新增了class的概念,子类通过extends来继承父类,并利用super()方法来调用父类的构造函数和方法。
1、extends关键字:子类通过extends关键字,继承了父类的所有属性和方法。父类中的所有方法默认是添加到子类的原型上,所以extends继承的实质仍然是原型链。
2、super关键字:super这个关键字,既可以当作函数使用,也可以当作对象使用。当作函数使用时,super代表父类的构造函数,并在子类中隐式执行Parent.apply(this),从而将父类实例对象的属性和方法,添加到子类的this上面。super作为对象时,在子类中指向父类的原型对象。
四、null 和 undefined 区别
null和undefined都在if语句中都是false, ==返回true
null表示"没有对象",即该处不应该有值。typeof null 返回的是object,null+1=1
undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。 typeof undefined返回undefined , undefined + 1=NaN
典型用法:
(1)变量被声明了,但没有赋值时,就等于undefined。
(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时,默认返回undefined。
五、typeof和 instanceof 区别
typeof适用于基础数据类型判断,引用类型判断都是object(除了function),可以返回number、string、boolean、function、object、undefined、symbol,注意null返回object。 instanceof 判断一个实例是否属于某种类型,返回一个布尔值,但严重存在原型继承
六、instanceof 的原理和弊端
/**
* instanceof的原理
* 就是左侧的参数实例的__proto__属性 沿着原型链一直往上延伸查找每一个原型 是否与右侧的参数的原型相等
*/
function instance_of(L, R) {
let O = R.prototype
L = L.__proto__
while(L) {
if(L === null) {
return false
}
if(L === O) {
return true
}
L = L.__proto__
}
return false
}
console.log(instance_of([], Array))
console.log(instance_of([], Object))
解决原型链的问题:
Array.isArray()针对数组的操作
Object.prototype.toString.call(’’) === ‘[object String]’
解决方案
因为js中的一切都是对象,任何都不例外,对所有值类型应用 Object.prototype.toString.call() 方法结果如下:
console.log(Object.prototype.toString.call(123)) //[object Number]
console.log(Object.prototype.toString.call(‘123’)) //[object String]
console.log(Object.prototype.toString.call(undefined)) //[object Undefined]
console.log(Object.prototype.toString.call(true)) //[object Boolean]
console.log(Object.prototype.toString.call({})) //[object Object]
console.log(Object.prototype.toString.call([])) //[object Array]
console.log(Object.prototype.toString.call(function(){})) //[object Function]
判断是否为函数
function isFunction(it) {
return Object.prototype.toString.call(it) === ‘[object Function]’;
}
判断是否为数组:
function isArray(o) {
return Object.prototype.toString.call(o) === ‘[object Array]’;
}
七、null > 0, null < 0, null >= 0, null <= 0, null == 0 分别输出什么?
1.在关系运算符中,null,undefined会被Number()强制转换成数字类型;
Number(null) //0
Number(undefined) //NaN
因此:
null >= 0; //true
null <= 0; //true
null > 0; //false
null < 0; //false
2.在相等运算符中,null,undefined则不会转化为数字类型,而是经过特殊处理
null == 0 //false
null == undefined //true
null == null //true
undefined == undefined //true
八、js 的类型转换
1.隐式转换
2.转为String:String()、toString()
3.转为数值:Number()、parseInt()、parseFloat()
4.转换成布尔值:Boolean()
九、prototype 是啥,什么是原型,什么是实例原型,说说你的理解
每个构造函数都有一个prototype属性,指向一个对象,这个prototype就是一个原型对象,他所拥有的所有属性和方法,都会被构造函数所拥有
每个对象(包括实例)都有一个__proto__属性,指向构造函数的prototype原型对象,它提供了一种查找机制(原型链查找机制)
十、js 为什么不支持多继承
javascript是可以利用call方法和prototype属性来实现多继承的。继承方法与单继承相似,只是将需要继承的多个父类依次实现,另外对于属性或共有方法重命的时候,以最后继承的属性和方法为主。因为会覆盖前面的继承。
十一、深拷贝和浅拷贝的区别
【JS】深拷贝与浅拷贝的区别,实现深拷贝的几种方法
手写深拷贝函数:
function deepCopy(obj) {
var cloneObj;
if (obj && typeof obj !== 'object') cloneObj = obj;
else if (obj && typeof obj === 'object') {
cloneObj = Array.isArray(obj) ? [] : {};
for (let k in obj) {
if (obj.hasOwnProperty(k)) {
if (obj[k] && typeof obj[k] === 'object') {
cloneObj[k] = deepCopy(obj[k]); //递归
}
else {
cloneObj[k] = obj[k];
}
}
}
}
return cloneObj;
}
var obj = {
name: 'vivian',
age: 18,
course: {
time: 12,
msg: 'nihao'
}
}
var o = deepCopy(obj);
console.log(o);
十二、匿名函数、箭头函数
匿名函数:就是定义时未直接指定名称的函数
因为非匿名函数在定义时就已经创建了函数对象和作用域对象,因此,即使未调用,也占用内存空间
而匿名函数仅在调用时,才临时创建函数对象和作用域链对象,调用完立即释放,所以匿名函数比非匿名函数更节省内存空间
箭头函数:
(1)箭头函数没有自己的this,导致内部的this就是外层代码的this,他的this是定义时候的对象,而不是使用时所在的对象。call、bind、apply都不能改变
(2)因为箭头函数没有this,因此他不能当作构造函数,也就是说不能使用new命令,否则会抛出一个错误
(3)不可以使用arguments对象,该对象在函数体内不存在
(4)不可以使用yield命令,因此箭头函数不能用作generator函数
十三、变量提升
预解析+执行
好处
提高性能:预先为变量分配栈空间
容错性更好:使一些不规范的代码也可以正常执行
问题:函数内一些变量提升会导致输出undefined、循环变量输出最后的值
十四、NaN
typeof NaN;返回“number”
NaN != NaN;返回true
十五、map和object的区别
map有size、set、get、has、delete、clear方法
map结构提供三个遍历器生成函数和一个遍历方法:keys() values() entries() forEach()
十六、map和weakmap区别
weakmap有set、get、has、delete方法,没有size和clear
区别:weakmap只接受对象作为键名,weakmap的键名所引用的对象都是弱引用,也就是不计引用,因此,周期时间内,会被垃圾回收器回收,不易造成内存泄漏
十七、常用的正则表达式
//以字母开头 后面可以是数字、字母、下划线 长度为6-30
var reg = /^[a-zA-Z]\w{5,29}$/g;
//手机号码
var reg = /^1[34578]\d{9}$/g;
//匹配日期 yyyy-mm-dd
var reg = /^[0-9]{4}-(0[1-9]|1[1-2])-(0[1-9]|[12][1-9]|3[01])$/g;
//匹配qq号
var reg = /^[1-9][0-9]{4,10}$/g;
十八、json
JSON.stringify() 传入一个json格式的数据结构,将其转换为一个json字符串
JSON.parse() 将json字符串转换为js数据结构
十九、将类数组转为数组
Array.prototype.slice.call(arguments);
Array.prototype.splice.call(arguments, 0);
Array.prototype.concat.apply([], arguments);
Array.from(arguments);
例如:要想arguments使用数组的forEach方法:
//法一
function foo() {
Array.prototype.forEach.call(arguments, a => console.log(a));
}
//法二
function foo() {
let arr = Array.from(arguments);
arr.forEach(a => console.log(a));
}
//法三
function foo() {
let arr = [...arguments];
arr.forEach(a => console.log(a));
}
二十、常见位运算符
&(与)|(或)^(异或)~(非)<<(左移) >>(右移)
二十一、ajax请求
let xhr = new XMLHttpRequest();
xhr.open('GET', '/url', true);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
} else {
alert(xhr.statusText);
}
}
//请求成功回调函数
xhr.onload = e => {
console.log('request success');
}
//请求结束
xhr.onloadend = e => {
console.log('request loadend');
}
//请求出错
xhr.onerror = e => {
console.log('request error');
}
//请求超时
xhr.ontimeout = e => {
console.log('request timeout');
}
//设置超时时间 0表示永不超时
xhr.timeout = 0;
//初始化请求
xhr.open('GET/POST/DELETE/...', 'URL', true || false);
//设置期望返回的数据类型'json' 'text' 'document'...
xhr.responseType = '';
//设置请求头
xhr.setRequestHeader('', '');
//发动请求
xhr.send(null || new FormData || 'a=1&b=2' || 'json字符串');
用promise封装一下:
function getJSON(url) {
let promise = new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onreadystatechange = function() {
if(this.readyState !== 4) return;
if(this.status === 200) {
resolve(response);
} else {
reject(new Error(this.statusText));
}
};
//设置错误监听函数
xhr.onerror = function() {
reject(new Error(this.statusText));
};
//设置响应的数据类型
xhr.responseType = "json";
//设置请求头信息
xhr.setRequestHeader("Accept", "application/json");
//发送http请求
xhr.send(null);
});
return promise;
}
二十二、异步编程的实现方式
(1)回调函数:多个回调函数会造成回调地狱,代码不易维护
(2)Promise:以同步的方式编写异步操作,使用then的链式调用,避免了回调地狱
(3)generator:调用generator函数会返回一个遍历器对象,代表generator的内部指针,遇到yield停止执行,next()恢复执行,返回一个有着value和done两个属性的对象,value表示当前内部状态的值(yield表达式后面的表达式的值),done是布尔值,表示是否遍历结束。next()会执行到遇到下一个yield为止。
yield表达式本身返回undefined,可以用过next(’’)的参数给上一个yield作为返回值。可以在generator运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。
yield* 返回一份遍历器对象,任何数据结构只要有iterator接口,就可以被yield*遍历
(4)async:async函数是generator和promise实现的一个自动执行的语法糖,它内部自带执行器,当函数内部执行到一个await语句的时候,如果语句返回一个promise对象,函数就会等待promise对象的状态变为resolve之后再继续执行。因此可以将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动执行。
async/await对比Promise的优势
- 代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调⽤也会带来额外的阅读负担
- Promise传递中间值⾮常麻烦,⽽async/await⼏乎是同步的写法,⾮常优雅
- 错误处理友好,async/await可以⽤成熟的try/catch,Promise的错误捕获⾮常冗余
- 调试友好,Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then代码块,因为调试器只能跟踪同步代码的每⼀步。
参考:
「2021」高频前端面试题汇总之JavaScript篇(上)
「2021」高频前端面试题汇总之JavaScript篇(下) 这里边有些输出题挺不错的
Generator 函数的语法