归子莫的博客

「笔杆揭不起,绘不出青烟别春泥 ————归子莫」

前端面试—Js面试题汇总

博客说明

文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢!

1、有几种方式可以实现继承

JS里常用的有如下两种继承方式:
原型链继承(对象间的继承)
类式继承(构造函数间的继承)

方法一:原型链

原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的指针。如果:我们让原型对象A等于另一个类型B的实例,那么原型对象A就会有一个指针指向B的原型对象,相应的B的原型对象中保存着指向其构造函数的指针。假如B的原型对象又是另一个类型的实例,那么上述的关系依旧成立,如此层层递进,就构成了实例与原型的链条。
注意: 通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样做会重写原型链。
问题: 原型链很强大,可以利用它来实现继承,但是也有一些问题,主要的问题还是包含引用类型值的原型属性会被所有实例共享。因此我们在构造函数中定义实例属性。但是在通过原型来实现继承时,原型对象其实变成了另一个类型的实例。于是原先定义在构造函数中的实例属性变成了原型属性了。
原型链的另一个问题是: 在创建子类型的实例时,不能在不影响所有对象实例的情况下,给超类型的构造函数传递参数。

方法二:构造函数

为了解决原型中包含引用类型值带来的一些问题,引入了借用构造函数的技术。这种技术的基础思想是:在子类型构造函数的内部调用超类型构造函数。

Parent.call(this)在新创建的Child实例环境下调用Parent构造函数。这样,就在新的Child对象上,此处的kid1和kid2对象上执行Parent()函数中定义的对象初始化代码。这样,每个Child实例就都会具有自己的friends属性的副本了。

借用构造函数的方式可以在子类型的构造函数中向超类型构造函数传递参数。
构造函数模式的问题: 在于方法都在构造函数中定义,函数复用无从谈起,因此,借用构造函数的模式也很少单独使用。

方法三:组合继承

组合继承指的是将原型链和借用构造函数的技术组合在一块,从而发挥二者之长。即:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

Person构造函数定义了两个属性:name和friends。Person的原型定义了一个方法sayName()。Child构造函数在调用Parent构造函数时,传入了name参数,紧接着又定义了自己的属性age。然后将Person的实例赋值给Child的原型,然后又在该原型上定义了方法sayAge().这样,两个不同的Child实例既分别拥有自己的属性,包括引用类型的属性,又可以使用相同的方法了。

组合继承避免了原型链和构造函数的缺陷,融合了他们的有点,成为JavaScript中最常用的继承模式。而且,instanceOf和isPropertyOf()也能够识别基于组合继承创建的对象。

方法四:原型式继承

这是另一种继承,没有严格意义上的构造函数。思路是:借助原型可以基于已有的对象创建新对象,同时还不必要创建自定义类型。
在object()函数内部,先创建一个临时的构造函数,然后将传入的对象作为构造函数的原型,最后返回这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅复制。

方法五:寄生式继承

寄生式继承是与原型式继承紧密相关的一种思路,与寄生式构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
使用寄生式继承来为对象添加函数,会由于不能做到函数的复用而降低效率;这一点和构造函数继承模式类似。

方法六:寄生组合式继承

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链来继承方法。寄生组合式继承的基本思路:不必为了指定子类型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承超类型的原型,然后再将结果指定给子类型的原型。

原型实现继承的优缺点及解决方式

优点

可以继承构造函数属性,也可继承原型属性

缺点

  1. 在创建子类实例化时,不能向超类型的构造函数中传参

  2. 子类型继承了父类型原型中的所有属性和方法,但对于引用类型属性值所有实例共享,故不能在不改变其他实例情况下改变。

解决方式

使用组合继承。

垃圾回收机制(闭包的延伸)

js拥有特殊的垃圾回收机制,当一个变量在内存中失去引用,js会通过特殊的算法将其回收,并释放内存。分为以下两个阶段:

  1. 标记阶段:垃圾回收器,从根对象开始遍历,访问到的每一个对象都会被标示为可到达对象。
  2. 清除阶段:垃圾回收器在对内存当中进行线性遍历,如果发现该对象没有被标记为可到达对象,那么就会被垃圾回收机制回收。
    这里面牵扯到了引用计数法,每次引用都被会‘➕1’ 如果标记清零,那么就会被回收掉。

简述深浅拷贝

浅拷贝

通常需要拷贝的对象内部只有一层的这种对象。
常用的方法

  1. Object.assign方法来实现
  2. 扩展运算符 ...obj
深拷贝

通常是嵌套二层或以上的复杂对象
常用方法

  1. JSON.parse(JSON.stringfy(object)); 该方法忽略掉undefined、忽略Symbol、忽略function。只适合简单深拷贝
  2. 手写递归方法去实现。
  3. 通过第三方库提供的深拷贝实现。
讲一下let、var、const的区别
  • var 没有块级作用域,支持变量提升。
  • let 有块级作用域,不支持变量提升。不允许重复声明,暂存性死区。不能通过window.变量名进行访问.
  • const 有块级作用域,不支持变量提升,不允许重复声明,暂存性死区。声明一个变量一旦声明就不能改变,改变报错。

call apply区别,原生实现bind

call、apply区别

相同点:都是重定向this指针的方法。
不同点:call和apply的第二个参数不相同,call是若干个参数的列表。apply是一个数组

手写一个call方法

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
// 在这之前需要重新认识一下call方法的执行操作
let mock = { value : 1 };
function mockNum(){
console.log('value',this.value)
}
mockNum.call(mock) // 改变了函数中this的指向,当前this指向了mock对象

转换一下实现方法就是

let mock = {
value:1;
mockNum:function(){
console.log('value',this.value)
}
}
mock.mockNum();
所以经过上面这个操作的演化而来的结果就是如下步骤:
1. 将函数设为一个对象的属性
2. 并将这个函数的属性调用
3. 删除该函数

Function.prototype.Mycall = function(context){
let obj = context || window;
obj.fn = this; // 这一步可以看做是this其实就指的当前函数。
let args = [...arguments].slice(1); // 返回删除第一个元素的数组;
let result = obj.fn(...args); // 调用函数
delete obj.fn;
return result;
}

// 操作一下
let mock = { value : 1 };
function mockNum(){
console.log('value',this.value);
}
mockNum.Mycall(mock) // value 1

然后根据上面的方法再手写一个apply方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Function.prototype.Myapply = function (context){
let obj = context || window;
obj.fn = this;
let result = arguments[1] ? obj.fn(arguments[1]) : obj.fn([]);
delete obj.fn;
return result;
}
let mock3 = {
arr: [1, 2, 3, 4, 5],
};
function arrx2(arr) {
return this.arr.concat(arr).map((x) => x * 2);
}
console.log("arrx2", arrx2.myApply(mock3));
bind

bind方法是直接返回一个新的函数,需要手动去调用才能执行。

创建一个新函数,当这个新函数被调用时,bind()方法的第一个参数将作为运行他的this,之后的一系列参数将会在传递的实参传入作为他的参数;

特点:1. 返回一个函数。 2. 可以传入参数;

】手写一个bind方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
例如:
let foo = { value : 1 };
function bar() {
console.log('bindFoo',this.value);
// return this.value // 考虑到函数可能有返回值
}
let bindFoo = bar.bind(foo);
bindFoo() // 1 // 如果有返回值的情况下 bindFoo() === 1;

Function.prototype.Mybind = function(obj){
if(typeof this !== 'function') throw new Error('not a function');
let self = this;
let args = [...arguments].clice(1);
return function F(){
if(this instanceof F){
return new self(...args,...arguments);
}
return self.apply(obj,args.concat([...arguments]));
}
}
捕获和冒泡

捕获:就是从根元素开始向目标元素递进的一个关系;从上而下
冒泡:是从目标元素开始向根元素冒泡的过程;想象一下水里的泡泡从下而上。

stopPropagation 通常理解它是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。

箭头函数和普通函数的区别是什么

普通函数this:
  1. this总是代表它的直接调用者。
  2. 在默认情况下,没找到直接调用者,this指的是window。
  3. 在严格模式下,没有直接调用者的函数中的this是undefined。
  4. 使用call,apply,bind绑定,this指的是绑定的对象。
箭头函数this:
  1. 在使用=>定义函数的时候,this的指向是 定义时所在的对象,而不是使用时所在的对象;
  2. 不能够用作构造函数,这就是说,不能够使用new命令,否则就会抛出一个错误;
  3. 不能够使用 arguments 对象;
  4. 不能使用 yield 命令;
js跨域如何解决———————-

目前暂时已知的跨域方法是:

  1. jsonp跨域,原理:script标签没有跨域限制的漏洞实现的一种跨域方法,只支持get请求。安全问题会受到威胁。
  2. cors跨域,通过后端服务器实现,Access-Control-Allow-Origin
  3. postMessage window的一个属性方法。
  4. websocket
  5. nginx反向代理
  6. iframe跨域
webpack proxy跨域

首先需要明白webpack proxy跨域只能用作与开发阶段,临时解决本地请求服务器产生的跨域问题。并不适合线上环境。配置在webpack的devServer属性中。webpack中的devsever配置后,打包阶段在本地临时生成了一个node服务器,浏览器请求服务器相当于请求本地服务。

深度优先和广度优先

广度优先:尝试访问尽可能靠近它的目标节点,然后逐层向下遍历,直至最远的节点层级。
深度优先:从起始节点开始,一直向下找到最后一个节点,然后返回,又继续下一条路径。知道找遍所有的节点。

简单介绍一下event loop

js作为单线程语言。在执行过程中,会产生执行环境。这些执行环境中的代码被顺序的加入到执行栈中,如果遇到异步代码,会被挂起并加入到任务队列当中,等到主线程任务执行完毕,event loop就会从任务队列取出需要执行的代码放入到执行栈中执行。所以本质上来讲,js中的异步还是同步的行为。

任务队列有分为宏任务和微任务队列。
一次正确的event loop执行顺序如下:

  1. 执行所有同步代码
  2. 执行栈为空,查询是否有需要执行的微任务。
  3. 微任务(有:则执行,无:则跳出)
  4. 必要的话开始渲染UI
  5. 开始下一轮的任务队列执行宏任务中的异步代码。

如何实现图片懒加载

当访问一个页面的时候,先把img元素或是其他元素的背景图片路径替换成一张大小为1*1px图片的路径(这样就只需请求一次),只有当图片出现在浏览器的可视区域内时,才设置图片真正的路径,让图片显示出来。这就是图片懒加载。
通常可以借助IntersectionObserver API。

instanceof和typeof

instanceof原理

instanceOf用来判断右边的prototype是否在左边的原型链上,告诉我们左边是否是右边的实例。

typeof 检测对象

除开函数是function类型之外。像常见的数组,对象或者是正则,日期等等都是object;

感谢

万能的网络

以及勤劳的自己,个人博客GitHub

微信公众号

评论