五年面试,三年模拟(js篇)

构造函数的new都做了些什么

  1. JS内部首先会先生成一个对象;
  2. 再把函数中的this指向该对象;
  3. 然后执行构造函数中的语句;
  4. 最终返回该对象实例。

什么是promise

Promise 是一个允许我们处理异步操作的对象。

promise有哪些类方法

  1. promise.all并行执行多个promise,所有传参的promise的resolve 回调都结束才会执行resolve回调,反之执行reject需要用catch捕获
  2. promise.race返回第一个resolve的promise,剩下的promise哪怕成功resolve了也不会返回

promise的链式调用是怎么实现的

因为then方法会返回一个新的Promise。这个新的Promise会根据它接收的参数来决定状态, 如果接收到的不是一个Promise, 那么它就自动置为Fullfilled, 如果接收到的参数是Promise, 那么就将它的resolve函数放在接收到的Promise中的then方法执行。

js中有哪几种数据类型

最简单的问题,不过遇到还是经常漏答

一共有8种数据类型:String、Number、Boolean、Null、Undefined、Object、Symbol、BigInt.

8种数据类型又可以分为基本类型引用类型,Object为引用类型,其余均为基本类型.

ps:Object类型中有数组(Array)、函数(Function)以及两个特殊的对象:正则(RegExp)和日期(Date)
pps: 基本类型存放在栈中,引用类型存放在堆内存中

let、var和const的区别

1.var 声明的变量,在全局范围内都有效,let 和 const 声明的,只在它所在的代码块内有效

2.let 和 const 声明的变量不存在变量提升现象(var 定义变量可以先使用,后声明)

3.let 不允许在相同的作用域内重复声明同一个变量(const声明对象时必须赋值,且值不允许改变,也不能在同一个作用域重复声明)

4.const 在声明时必须初始化赋值,一旦声明,其声明的值就不允许改变,更不允许重复声明

箭头函数和普通函数的区别

1.this指向不同,普通函数的this指向直接调用者,没有直接调用者,this指向window;箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this

2.箭头函数没有 arguments 对象

数组的pop()和unshift()区别

这么基础的问题…我竟然答错了

pop()删除数组的最后一个元素,并返回新数组;unshift()向数组前追加内容

如何理解闭包

闭包简单来说就是函数嵌套函数(结构如下),内部函数引用来自外部函数的变量,从而导致垃圾回收机制没有把当前变量回收掉。

1
2
3
4
5
6
7
8
9
10
闭包的基本结构:两个嵌套的函数,内部函数通过return给返回出来

function f1() {
function f2() {
alert("我是js闭包!");
}
return f2;
}
var f = f1();
f();

闭包的使用场景:ajax请求成功后的回调、防抖和节流

闭包的缺点:内存泄漏

解决方法:将闭包引用的外部函数中活动对象清除

如果有a,b,c三个函数,c的传参需要a和b返回的结果,如何处理

下意识的回答就是写在回调里面,不过面试官肯定不是想听到这个回答

使用promise.all

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 代码示例
function ajax1() {
return new Promise((resolve, reject) => {
$.ajax({
url: 'https://api.imjad.cn/hitokoto/',
type: 'post',
success: res => {
resolve(res)
}
})
})
}
function ajax2() {
return new Promise((resolve, reject) => {
$.ajax({
url: 'https://api.imjad.cn/hitokoto/',
type: 'post',
success: res => {
resolve(res)
}
})
})
}

Promise.all([ajax1(), ajax2()]).then(values => {
console.log(values)
})

null、undefined和nan区别

1.null表示”没有对象”,即该处不应该有值

2.undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义(变量被声明了,但没有赋值时,就等于undefined)

3.nan表示不是一个数字

== 和 === 的区别以及新增的 Object.is

== 抽象相等,比较时,会先进行隐式强制类型转换,然后再比较值

=== 严格相等,会比较两个值的类型和值

ps:es6中新增了一个新的方法Object.is()来比较两个值严格相等,它与严格比较运算符(===)基本一致,不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

arguments对象是不是数组?以及如何转成数组

1
2
3
4
5
// arguments并不是一个真正的数组,而是类数组,如何证明?看如下代码,arguments没有数组的push方法
function func() {
arguments.push(11);
}
func(); // 报错 arguments.push is not a function

arguments对象转成数组的几种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 第一种 通过Array.prototype属性下的数组的方法
Array.prototype.slice.apply(arguments); // 或者 Array.prototype.slice.call(arguments) 或者 Array.prototype.concat.call([], arguments)

// 第二种 遍历arguments,返回数组
function func() {
let arr = []
for (let i = 0; i < arguments.length; i++) {
arr.push(arguments[i])
}
console.log(arr)
}
func(1, 2, 3);

// 第三种 es6新增的Array.from 方法
function func() {
let arr = Array.from(arguments);
arr.push(444);
console.log(arr)
}

常见的解决跨域的方法

跨域的产生是因为浏览器的’同源策略’,即协议、域名、端口有任何一个不同都会被当成不同的域,然后ajax请求就会失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 第一种 JSONP
// 利用 <script> 标签没有跨域限制的漏洞。通过 <script> 标签指向一个需要访问的地址并提供一个回调函数来接收数据
<script src="http://domain/api?param1=a&param2=b&callback=jsonp"></script>
<script>
function jsonp(data) {
console.log(data)
}
</script>

// 第二种 proxy代理
// 又可以细分为:nginx代理跨域、webpack代理跨域

// 第三种 使用CORS
// 即在服务端设置响应头并返回
app.use(cors({
origin: function (ctx) {
if (ctx.url === '/test') {
return "*"; // 允许来自所有域名请求
} else {
if (tool.env() === 'production') {
return 'http://106.53.78.195';
} else {
return 'http://localhost:8080'; // 这样就能只允许 http:/ / localhost: 8080 这个域名的请求了
}
}
},
}))

请简述JavaScript中的this

js中this关键字表示当前对象的一个引用

简述javascript中this的指向

1.this永远指向函数运行时所在的对象

2.普通的函数调用,this指向调用对象

3.构造函数,如果不用new操作符而直接调用,那即this指向window。用new操作符生成对象实例后,this就指向了新生成的对象

for…of和for…in区别

for…of是es6新增的遍历数组和对象的方法

1.for…of输出的是value,for…in输出的是key

2.for…of遍历不可迭代对象(普通对象)时会报错,for…in则可以正常输出key

3.for…in更适合遍历对象,for…of适合数组

4.for…in遍历的是对象的可枚举属性,包括对象自身及从原型链继承而来的属性;for…of遍历的是可迭代属性

什么是事件委托/事件代理?

将本该绑定在多个子元素上的事件绑定到他们的祖先元素上,尤其是在动态添加子元素的时候,可以非常方便的提高程序性能,减小内存空间

什么是事件冒泡?以及如何阻止冒泡?

当某个元素的某类型事件被触发时,那么它的父元素同类型的事件也会被触发,一直触发到根源上。

如何阻止事件冒泡?调用当前事件对象的stopPropagation()方法

1
2
3
4
$("span").click(function (event) {
event.stopPropagation();
// do something
});

防抖和节流的原理

防抖和节流的原理都是利用了闭包。

防抖的原理是使用了闭包,通过不断地记录新的定时器与清除旧的定时器,达到函数调用不断延迟,且仅调用一次的效果。

节流的原理也是使用了闭包,记录了开始时间start及每一次触发时间curr,两变量相减得到差值,当插值大于节流分片的时间时,可执行函数,同时更新start。另外通过setTImeout防止函数持续触发时间太短,没有调用函数,对于定时器也有所每次清除。

什么是原型和原型链

什么是原型?

每个函数对象都会有一个prototype属性, 叫做原型.

什么是原型链?

查找对象属性的过程。(常说的原型链条就是 [[prototype]]链 或者 __proto__链.)

假设要查询对象o的属性x,如果o中不存在x,那么将会继续在o的原型对象中查询属性x。如果原型对象中也没有x,但这个原型对象也有原型,那么继续在这个原型对象的原型上执行查询,直到找到x或者查找到一个原型是null的对象为止。可以看到,对象的原型属性构成了一个”链”,通过这个”链”可以实现属性的继承。

深拷贝与浅拷贝的区别

浅拷贝是复制,两个对象指向同一个地址;

深拷贝是新开栈,两个对象指向不同的地址

ps:Object.assign的拷贝,是对于第一层属性的拷贝,所以是浅拷贝。

堆栈区别

在js中,每一个数据都需要一个内存空间。内存空间又被分为两种,栈内存(stack)与堆内存(heap)。

1.栈内存一般储存基础数据类型;堆内存一般储存引用数据类型

2.栈会自动分配内存空间,会自动释放;堆动态分配内存,大小不定也不会自动释放

localStorage、sessionStorage和cookie区别

特性 Cookie localStorage sessionStorage
数据的生命期 一般由服务器生成,可设置失效时间。如果在浏览器端生成Cookie,默认是关闭浏览器后失效 除非被清除,否则永久保存 仅在当前会话下有效,关闭页面或浏览器后被清除
存放数据大小 4k左右 一般为5MB 一般为5MB
与服务器端通信 每次都会携带在HTTP头中 不参与和服务器的通信 不参与和服务器的通信

比较两个javaScript对象是否相等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 第一种 使用JSON.stringify()

var obj1 = {
name: "xiaoming",
sex: "male"
};

var obj2 = {
name: "xiaoming",
sex: "male"
};
console.log(JSON.stringify(obj1) == JSON.stringify(obj2)) // true
console.log(JSON.stringify(obj1) === JSON.stringify(obj2)) // true

第二种 for in循环遍历对比

function equals(x, y) {
// 首先判断是不是引用类型的,如果有一个不是,那就进行直接判断
var f1 = x instanceof Object;
var f2 = y instanceof Object;
if (!f1 || !f2) {
return x === y
}
if (Object.keys(x).length !== Object.keys(y).length) {
return false
}
var newX = Object.keys(x);
for (var p in newX) {
p = newX[p];
console.log(p)
var a = x[p] instanceof Object;
var b = y[p] instanceof Object;
if (a && b) {
equals(x[p], y[p])
} else if (x[p] != y[p]) {
return false;
}
}
return true;

}
console.log(equals(obj1, obj2))

oninput和onchange区别

1.只有input和textarea元素才有oninput事件;而onchange可以用于input、select和textarea元素

2.input的onchange事件需要失去焦点才会触发(当鼠标在其他地方点一下才会触发);oninput事件在用户输入时触发,它是在元素值发生变化时立即触发

如何判断一个对象是否是数组

1
2
3
4
5
6
7
// 第一种 使用instanceof运算符
var a = [];
a instanceof Array //true

// 第二种 数组方法 isArray()
var arr = [1, 2, 3]
Array.isArray(arr) // true

++i 和 i++ 区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var i=1;
console.log(i); // 输出1

var a=i++;
console.log(i); //输出2
console.log(a); //输出1

-------------------------------------

var i=1;
console.log(i);// 输出1

var a=++i;
console.log(i); //输出2
console.log(a); //输出2

使用i++时,i先将自身的值赋值给变量a,然后再自增1
使用++i时,i先将自身的值自增1,再将自增后的值赋值给变量a

在函数定义前输出函数会输出啥?

1
2
3
4
5
6
7
8
9
10
console.log(test) 

// 输出如下内容
// ƒ test() {
// console.log(123);
// }

function test() {
console.log(123);
}

换一种写法

1
2
3
4
console.log(test) // undefined
var test = function () {
console.log(123);
}

为什么会这样,因为仅仅是将声明提升了,赋值操作并没有提升


五年面试,三年模拟(js篇)
https://xypecho.github.io/2021/03/18/五年面试,三年模拟-js篇/
作者
很青的青蛙
发布于
2021年3月18日
许可协议