前端面试题

1 JavaScript ️

1.1 如何理解原型链 (什么是原型链)

每个函数都拥有一个 prototype 属性,每个函数实例对象都拥有一个__proto__ 属性,而这个属性指向了函数的 prototype,当我们访问实例对象的属性或者方法时,会先从自身构造函数中查找,如果没有就通过 __proto__ 去原型中查找,这个查找的过程我们称之为原型链。(跟作用域链有点像)

// 定义动物 - 父类
function Animal(){
    this.age = 10;
    this.say = function(){
        return 'hello tom';
    }
}
// 定义猫 - 子类
function Cat(){
    this.name = 'tom';
}
// 通过原型继承动物类
Cat.prototype = new Animal()
// 实例化一个cat对象
var cat = new Cat();
// 打印返回true
cat.__proto__ === Cat.prototype
// 打印age,会先查找cat,再查找Animal
console.log(cat.age)

我们可以看到cat实例对象__proto__指向了Animal,当cat没有age的时候,会通过__proto__到原型上查找,如果原型上依然没有,会继续向Object上查找。

1.2 Promise、Promise.all、Promise.race 分别怎么用?

Promise

Promise 是异步编程的一种解决方案,比传统的解决方案回调函数和事件更合理和更强大。

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。

一个Promise的当前状态必须为以下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected),状态的改变只能是单向的,且变化后不可在改变。

一个Promise必须提供一个 then 方法以访问其当前值、终值和据因。

promise.then(onFulfilled, onRejected) 回调函数只能执行一次,且返回 promise 对象

Promise的每个操作返回的都是Promise对象,可支持链式调用。

通过 then 方法执行回调函数,Promise的回调函数是放在事件循环中的微队列。

Promise的具体用法如下(背代码):

 function fn(){
     return new Promise((resolve, reject)=>{
         成功时调用 resolve(数据)
         失败时调用 reject(错误)
     })
 }
 fn().then(success1, fail1).then(success2, fail2)

Promise.all

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

Promise.all([promise1, promise2]).then(success1, fail1)

promise1promise2都成功才会调用success1

Promise.race

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

Promise.race([promise1, promise2]).then(success1, fail1)

promise1promise2只要有一个成功就会调用success1

2 Vue

3 手写代码

3.1 手写实现 ajax

凡是和后台有过数据交互的小伙伴肯定都接触过 ajax. 我们可以通过 ajax 来实现页面的无刷新请求数据,这样就能在保证良好用户体验的同时,将更多的内容展示给用户
ajax 在我们的开发工作中已经司空见惯,几乎所有我们频繁使用的库和框架都提供了经过完善封装后的 ajax 方法,如 jQuery、zepto 等等,这使得我们的数据请求变得异常简洁明了

但是这也带来了很明显的缺陷,就是我们知道如何去使用封装后的 ajax,却不会通过原生的 js 来 ajax,更甚者(如只用过 jQuery 的小伙伴)会将 ajax 与$.ajax()等同

而这个问题也是面试中常常出现的一题,所以这次就来亲手实现一下原生 js 的 ajax

XMLHttpRequest对象

我们常用的 ajax 就是通过 XMLHttpRequest 对象实现的,这个对象有很多的属性和事件,在使用之前,我们需要先将它实例化

const xhr = new XMLHttpRequest()

实例化后,我们就可以通过 xhr 来发起一个请求

// xhr 具有一个 open 方法,这个方法的作用类似于初始化,并不会发起真正的请求
// open 方法具有 5 个参数,但是常用的是前 3 个
// method: 请求方式 —— get / post
// url:请求的地址
// async:是否异步请求,默认为 true(异步)
xhr.open(method, url, async)
	
// send 方法发送请求,并接受一个可选参数
// 当请求方式为 post 时,可以将请求体的参数传入
// 当请求方式为 get 时,可以不传或传入 null
// 不管是 get 还是 post,参数都需要通过 encodeURIComponent 编码后拼接
xhr.send(data)

在通过send方法发送请求后,xhr 对象在收到响应数据时会自动填充到其对应的属性中,xhr 具有以下常用属性:

responseText: 请求返回的数据内容
**responseXML: ** 如果响应内容是"text/xml"“application/xml”,这个属性将保存响应数据的 XML DOM文档
status: 响应的HTTP状态,如 200 304 404 等
statusText: HTTP状态说明
readyStatus: 请求/响应过程的当前活动阶段
timeout: 设置请求超时时间

上面写了那么多,我们并没有发现收到数据时的回调方法,那么该怎么在收到数据时进行处理呢?这个问题的重点需要放在readyStatus这个属性上

readyStatus的值会随着请求各阶段的变化而改变,其一共有 5 个值:

xhr.readyStatus==0 尚未调用 open 方法
xhr.readyStatus==1 已调用 open 但还未发送请求(未调用 send)
xhr.readyStatus==2 已发送请求(已调用 send)
xhr.readyStatus==3 已接收到请求返回的数据
xhr.readyStatus==4 请求已完成

readyStatus的状态发生改变时,会触发 xhr 的事件onreadystatechange,于是我们就可以在这个方法中,对接收到的数据进行处理

xhr.onreadystatechange = () => {
	   if (xhr.readyStatus === 4) {
	       // HTTP 状态在 200-300 之间表示请求成功
	       // HTTP 状态为 304 表示请求内容未发生改变,可直接从缓存中读取
	       if (xhr.status >= 200 && 
	           xhr.status < 300 || 
	           xhr.status == 304) {
	           console.log('请求成功', xhr.responseText)
	       }
	   }
}

当网络不佳时,我们需要给请求设置一个超时时间

// 超时时间单位为毫秒
xhr.timeout = 1000
	
// 当请求超时时,会触发 ontimeout 方法
xhr.ontimeout = () => console.log('请求超时')

以上就是 XMLHttpRequest 对象的基础内容,它还有很多其他的属性和时间,如onerror onabort onload等等,同时 ajax 的跨域、兼容 IE 浏览器等问题我们也并没有提到。对这些感兴趣的小伙伴可以自己 Google 一下,或者等以后有机会,我们再一起来学习这些内容

下面奉上自己封装的一个极简 ajax 请求方法,在生产项目中不能使用,但是对于临时测试请求个本地文件什么的还是没什么问题的,该 ajax 方法通过 Promise 方式实现回调

function ajax (options) {
        let url = options.url
        const method = options.method.toLocaleLowerCase() || 'get'
        const async = options.async != false // default is true
        const data = options.data
        const xhr = new XMLHttpRequest()

        if (options.timeout && options.timeout > 0) {
            xhr.timeout = options.timeout
        }

        return new Promise ( (resolve, reject) => {
            xhr.ontimeout = () => reject && reject('请求超时')
            xhr.onreadystatechange = () => {
                if (xhr.readyState == 4) {
                    if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
                        resolve && resolve(xhr.responseText)
                    } else {
                        reject && reject()
                    }
                }
            }
            xhr.onerror = err => reject && reject(err)

            let paramArr = []
            let encodeData
            if (data instanceof Object) {
                for (let key in data) {
                    // 参数拼接需要通过 encodeURIComponent 进行编码
                    paramArr.push( encodeURIComponent(key) + '=' + encodeURIComponent(data[key]) )
                }
                encodeData = paramArr.join('&')
            }

            if (method === 'get') {
                  // 检测 url 中是否已存在 ? 及其位置
                const index = url.indexOf('?')
                if (index === -1) url += '?'
                else if (index !== url.length -1) url += '&'
                  // 拼接 url
                url += encodeData
            }

            xhr.open(method, url, async)
            if (method === 'get') xhr.send(null)
            else {
                // post 方式需要设置请求头
                xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8')
                xhr.send(encodeData)
            }
        } )
    }

使用方式

ajax({
    url: 'your request url',
    method: 'get',
    async: true,
    timeout: 1000,
    data: {
        test: 1,
        aaa: 2
    }
}).then(
    res => console.log('请求成功: ' + res),
    err => console.log('请求失败: ' + err)
)

你可能感兴趣的:(前端面试)