前端面试基础知识题
1. bind、call、apply 有什么区别?如何实现一个bind?
apply、call、bind三者的区别在于:
- 三者都可以改变函数的this对象指向
- 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window
- 三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入
- bind 是返回绑定this之后的函数,apply 、call 则是立即执行
实现bind的步骤,我们可以分解成为三部分:
- 修改this指向
- 动态传递参数
// 方式一:只在bind中传递函数参数
fn.bind(obj,1,2)()
// 方式二:在bind中传递函数参数,也在返回函数中传递参数
fn.bind(obj,1)(2)
- 兼容new关键字
整体实现代码如下:
Function.prototype.myBind = function (context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 获取参数
const args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 根据调用方式,传入不同绑定值
return fn.apply(this instanceof Fn ? new fn(...arguments) : context, args.concat(...arguments));
}
}
2. 说说ajax的原理,以及如何实现?
AJAX 全称(Async Javascript and XML)
即异步的 JavaScript 和 XML,是一种创建交互式网页应用的网页开发技术,可以在不重新加载整个网页的情况下,与服务器交换数据,并且更新部分网页
Ajax的原理简单来说通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用JavaScript来操作DOM而更新页面
流程图如下
下面举个例子:领导想找小李汇报一下工作,就委托秘书去叫小李,自己就接着做其他事情,直到秘书告诉他小李已经到了,最后小李跟领导汇报工作
Ajax请求数据流程与“领导想找小李汇报一下工作”类似,上述秘书就相当于XMLHttpRequest对象,领导相当于浏览器,响应数据相当于小李.浏览器可以发送HTTP请求后,接着做其他事情,等收到XHR返回来的数据再进行操作
实现 Ajax 异步交互需要服务器逻辑进行配合,需要完成以下步骤:
- 创建 Ajax 的核心对象 XMLHttpRequest 对象
- 通过 XMLHttpRequest 对象的 open() 方法与服务端建立连接
- 构建请求所需的数据内容,并通过 XMLHttpRequest 对象的 send() 方法发送给服务器端
- 通过 XMLHttpRequest 对象提供的 onreadystatechange 事件监听服务器端你的通信状态
- 接受并处理服务端向客户端响应的数据结果
- 将处理结果更新到 HTML 页面中
3. Javascript如何实现继承?
继承(inheritance)是面向对象软件技术当中的一个概念。如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”。继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码。在子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。虽然JavaScript并不是真正的面向对象语言,但它天生的灵活性,使应用场景更加丰富。
JavaScripy常见的继承方式:
- 原型链继承
- 构造函数继承(借助 call)
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
通过Object.create 来划分不同的继承方式,最后的寄生式组合继承方式是通过组合继承改造之后的最优继承方式,而 extends 的语法糖和寄生组合继承的方式基本类似。
4. ES5怎么实现继承
继承这个概念在面向对象编程思想里面十分重要,也是面试必考的考点之一。javascript的继承主要是依托其原型与原型链的概念来实现的。
ECMAscript将原型链作为实现继承的主要方法。
ES6提供了Class关键字来实现类的定义,Class 可以通过extends关键字实现继承,让子类继承父类的属性和方法。
ES5的四种常用的实现方式:原型链继承、构造函数继承、组合式继承、寄生式组合继承。
- 说到js继承,最开始想到的应该是是原型链继承,通过把子类实例的原型指向父类实例来继承父类的属性和方法,但原型链继承的缺陷在于对子类实例继承的引用类型的修改会影响到所有的实例对象以及无法向父类的构造方法传参。
- 构造函数继承, 通过在子类构造函数中调用父类构造函数并传入子类this来获取父类的属性和方法,但构造函数继承也存在缺陷,构造函数继承不能继承到父类原型链上的属性和方法。
- 后面有了组合式继承,但也有了新的问题,每次都会执行两次父类的构造方法,最终有了寄生式组合式继承。
5. JavaScript中的原型,原型链分别是什么?
原型
JavaScript 常被描述为一种基于原型的语言——每个对象拥有一个原型对象
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非实例对象本身
下面举个例子:
函数可以有属性。 每个函数都有一个特殊的属性叫作原型prototype
function doSomething(){}
console.log( doSomething.prototype );
控制台输出
{
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
上面这个对象,就是大家常说的原型对象。可以看到,原型对象有一个自有属性constructor,这个属性指向该函数,如下图关系展示:
原型链
原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法
下面举个例子:
function Person(name) {
this.name = name;
this.age = 18;
this.sayName = function() {
console.log(this.name);
}
}
// 第二步 创建实例
var person = new Person('person')
根据代码,我们可以得到下图
下面分析一下:
- 构造函数Person存在原型对象Person.prototype
- 构造函数生成实例对象person,person的__proto__指向构造函数Person原型对象
- Person.prototype.proto 指向内置对象,因为 Person.prototype 是个对象,默认是由 Object 函数作为类创建的,而 Object.prototype 为内置对象
- Person.proto 指向内置匿名函数 anonymous,因为 Person 是个函数对象,默认由 Function 作为类创建
- Function.prototype 和 Function.proto 同时指向内置匿名函数 anonymous,这样原型链的终点就是 null
总结
下面首先要看几个概念:
__proto__作为不同对象之间的桥梁,用来指向创建它的构造函数的原型对象的
每个对象的__proto__都是指向它的构造函数的原型对象prototype的
1person1.__proto__ === Person.prototype
构造函数是一个函数对象,是通过 Function 构造器产生的
1Person.__proto__ === Function.prototype
原型对象本身是一个普通对象,而普通对象的构造函数都是Object
1Person.prototype.__proto__ === Object.prototype
刚刚上面说了,所有的构造器都是函数对象,函数对象都是 Function 构造产生的
1Object.__proto__ === Function.prototype
Object 的原型对象也有__proto__属性指向null,null是原型链的顶端
1Object.prototype.__proto__ === null
下面作出总结:
- 一切对象都是继承自Object对象,Object 对象直接继承根源对象 null
- 一切的函数对象(包括 Object 对象),都是继承自 Function 对象
- Object 对象直接继承自 Function 对象
- Function对象的__proto__会指向自己的原型对象,最终还是继承自Object对象
6. 怎么预防按钮的重复点击?
先看看在那些场景会导致重复请求:
1.手速快,不小心双击操作按钮。
2.很小心的点击了一次按钮,因为请求响应比较慢,页面没有任何提示,怀疑上次点击没生效,再次点击操作按钮。
3.很小心的点击了一次按钮,因为请求响应比较慢,页面没有任何提示,刷新页面,再次点击操作按钮。
前端方案
我们可以对症下药:
1.控制按钮,在短时间内被多次点击,第一次以后的点击无效。
2.控制按钮,在点击按钮触发的请求响应之前,再次点击无效。
3.配置特殊的URL,然后控制这些URL请求的最小时间间隔。如果再次请求跟前一次请求间隔很小,弹窗二次提示,是否继续操作。
7. 岛屿数量
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
8. promise.catch后面的.then还会执行吗?
会继续执行。
虽然Promise是开发过程中使用非常频繁的一个技术点,但是它的一些细节可能很多人都没有去关注过。我们都知道.then, .catch, .finally都可以链式调用,其本质上是因为返回了一个新的Promise实例。
catch的语法形式如下:
1p.catch(onRejected);
.catch只会处理rejected的情况,并且也会返回一个新的Promise实例。
.catch(onRejected)与then(undefined, onRejected)在表现上是一致的。
事实上,catch(onRejected)从内部调用了then(undefined, onRejected)。
- 如果.catch(onRejected)的onRejected回调中返回了一个状态为rejected的Promise实例,那么.catch返回的Promise实例的状态也将变成rejected。
- 如果.catch(onRejected)的onRejected回调中抛出了异常,那么.catch返回的Promise实例的状态也将变成rejected。
- 其他情况下,.catch返回的Promise实例的状态将是fulfilled。
9. 前端怎么实现跨域请求?
CORS、Nginx代理跨域、Node中间件代理跨域、WebSocket、postMessage、JSONP
具体描述请点击此链接
10. 怎么实现图片懒加载?
懒加载是一种网页性能优化的方式,它能极大的提升用户体验。就比如说图片,图片一直是影响网页性能的主要元凶,现在一张图片超过几兆已经是很经常的事了。如果每次进入页面就请求所有的图片资源,那么可能等图片加载出来用户也早就走了。所以,我们需要懒加载,进入页面的时候,只请求可视区域的图片资源。
总结出来就两个点:
全部加载的话会影响用户体验
浪费用户的流量,有些用户并不想全部看完,全部加载会耗费大量流量。
html 实现:实现方式是给 img 标签加上 loading=“lazy”
js 实现:原理通过js监听页面的滚动也能实现。